Files
gh-secondsky-sap-skills-ski…/templates/common-patterns.js
2025-11-30 08:55:33 +08:00

832 lines
23 KiB
JavaScript

/**
* SAP Analytics Cloud - Common Scripting Patterns
*
* Ready-to-use code patterns for SAC Analytics Designer and Optimized Story Experience.
* Copy and adapt these patterns to your application.
*
* Source: SAP Analytics Cloud Scripting Skill
* Version: 2025.14+
*/
// =============================================================================
// FILTERING PATTERNS
// =============================================================================
/**
* Pattern 1: Apply filter from chart selection to table
* Use in: Chart.onSelect event
*/
function filterTableFromChartSelection() {
var selections = Chart_1.getSelections();
if (selections.length > 0) {
var selectedMember = selections[0]["{{DimensionId}}"];
Table_1.getDataSource().setDimensionFilter("{{DimensionId}}", selectedMember);
}
}
/**
* Pattern 2: Apply multiple filters efficiently (batch)
* Use when: Setting multiple filters at once
*/
function applyMultipleFilters(year, region, product) {
var ds = Chart_1.getDataSource();
// Pause to prevent multiple refreshes
ds.setRefreshPaused(true);
ds.setDimensionFilter("Year", year);
ds.setDimensionFilter("Region", region);
ds.setDimensionFilter("Product", product);
// Single refresh
ds.setRefreshPaused(false);
}
/**
* Pattern 3: Sync filters across multiple widgets
* Use when: Multiple charts/tables should have same filters
*/
function syncFiltersAcrossWidgets() {
var sourceDs = Chart_1.getDataSource();
// Copy all filters efficiently
Table_1.getDataSource().copyDimensionFilterFrom(sourceDs);
Chart_2.getDataSource().copyDimensionFilterFrom(sourceDs);
}
/**
* Pattern 4: Reset all filters
* Use in: Reset button onClick event
*/
function resetAllFilters() {
var widgets = [Chart_1, Table_1, Chart_2];
widgets.forEach(function(widget) {
widget.getDataSource().clearAllFilters();
});
// Reset dropdowns
Dropdown_Year.setSelectedKey("");
Dropdown_Region.setSelectedKey("");
}
/**
* Pattern 5: Filter from dropdown selection
* Use in: Dropdown.onSelect event
*/
function filterFromDropdown() {
var selectedYear = Dropdown_Year.getSelectedKey();
if (selectedYear) {
Application.showBusyIndicator();
Chart_1.getDataSource().setDimensionFilter("Year", selectedYear);
Table_1.getDataSource().setDimensionFilter("Year", selectedYear);
Application.hideBusyIndicator();
}
}
// =============================================================================
// DATA ACCESS PATTERNS
// =============================================================================
/**
* Pattern 6: Find active version from attribute
* Use when: Need to identify active planning cycle/version from master data
*/
function findActiveVersion() {
var allVersions = PlanningModel_1.getMembers("Version");
var activeVersion = null;
for (var i = 0; i < allVersions.length; i++) {
if (allVersions[i].properties.Active === "X") {
activeVersion = allVersions[i].id;
break;
}
}
console.log("Active Version: " + activeVersion);
return activeVersion;
}
/**
* Pattern 7: Get booked values only (not all master data)
* Use when: Need only members that have data, not entire master data list
*/
function getBookedValuesOnly() {
var bookedMembers = Table_1.getDataSource().getMembers(
"{{DimensionId}}",
{ accessMode: MemberAccessMode.BookedValues }
);
console.log("Booked members:", bookedMembers);
return bookedMembers;
}
/**
* Pattern 8: Get data value for specific selection
* Use when: Need to read specific cell value
*/
function getSpecificDataValue() {
var selection = {
"@MeasureDimension": "[Account].[parentId].&[Revenue]",
"Location": "[Location].[Country].&[US]",
"Year": "[Date].[Year].&[2024]"
};
var data = Chart_1.getDataSource().getData(selection);
console.log("Formatted:", data.formattedValue);
console.log("Raw:", data.rawValue);
return data;
}
// =============================================================================
// CHART MANIPULATION PATTERNS
// =============================================================================
/**
* Pattern 9: Dynamic measure swap
* Use in: Button onClick or Dropdown onSelect
*/
function swapMeasure(newMeasureId) {
// Get current measure
var currentMeasures = Chart_1.getMembers(Feed.ValueAxis);
// Remove current measure
if (currentMeasures.length > 0) {
Chart_1.removeMember(Feed.ValueAxis, currentMeasures[0]);
}
// Add new measure
Chart_1.addMember(Feed.ValueAxis, newMeasureId);
}
/**
* Pattern 10: Dynamic dimension swap
* Use when: Changing chart axis dimension
*/
function swapDimension(newDimensionId) {
// Get current dimension
var currentDimensions = Chart_1.getMembers(Feed.CategoryAxis);
// Remove current
if (currentDimensions.length > 0) {
Chart_1.removeDimension(Feed.CategoryAxis, currentDimensions[0]);
}
// Add new
Chart_1.addDimension(Feed.CategoryAxis, newDimensionId);
}
/**
* Pattern 11: Top N ranking
* Use when: Show only top performers
*/
function showTopN(n, measureId) {
Chart_1.rankBy(measureId, n, SortOrder.Descending);
}
// =============================================================================
// VISIBILITY TOGGLE PATTERNS
// =============================================================================
/**
* Pattern 12: Switch between chart and table
* Use in: Toggle button onClick event
*/
function switchChartTable(showChart) {
Chart_1.setVisible(showChart);
Table_1.setVisible(!showChart);
Button_ShowChart.setVisible(!showChart);
Button_ShowTable.setVisible(showChart);
}
/**
* Pattern 13: Expandable panel pattern
* Use when: Toggle detail section visibility
*/
function toggleDetailPanel() {
var isVisible = Panel_Details.isVisible();
Panel_Details.setVisible(!isVisible);
// Update button text
if (isVisible) {
Button_Toggle.setText("Show Details");
} else {
Button_Toggle.setText("Hide Details");
}
}
// =============================================================================
// USER FEEDBACK PATTERNS
// =============================================================================
/**
* Pattern 14: Long operation with busy indicator
* Use when: Performing operations that take time
*/
function performLongOperation() {
Application.showBusyIndicator();
try {
// Perform operation
Table_1.getDataSource().refreshData();
Chart_1.getDataSource().refreshData();
Application.showMessage(
ApplicationMessageType.Success,
"Data refreshed successfully"
);
} catch (error) {
console.log("Error:", error);
Application.showMessage(
ApplicationMessageType.Error,
"Failed to refresh data"
);
} finally {
Application.hideBusyIndicator();
}
}
/**
* Pattern 15: Confirmation popup pattern
* Use when: Confirming destructive actions
*/
// In main button onClick:
function showConfirmation() {
Popup_Confirm.open();
}
// In popup Yes button onClick:
function onConfirmYes() {
Popup_Confirm.close();
performAction();
}
// In popup No button onClick:
function onConfirmNo() {
Popup_Confirm.close();
}
// =============================================================================
// SELECTION HANDLING PATTERNS
// =============================================================================
/**
* Pattern 16: Handle multi-selection from table
* Use in: Table.onSelect event
*/
function handleMultiSelection() {
var selections = Table_1.getSelections();
if (selections.length === 0) {
Application.showMessage(
ApplicationMessageType.Warning,
"Please select at least one row"
);
return;
}
// Process all selections
var selectedIds = selections.map(function(sel) {
return sel["{{DimensionId}}"];
});
console.log("Selected IDs:", selectedIds);
// Apply as filter (if single value needed)
if (selections.length === 1) {
Chart_1.getDataSource().setDimensionFilter(
"{{DimensionId}}",
selectedIds[0]
);
}
}
/**
* Pattern 17: Clear selection
* Use when: Need to programmatically clear widget selection
*/
function clearSelection() {
Chart_1.getDataSource().removeDimensionFilter("{{DimensionId}}");
Table_1.getDataSource().removeDimensionFilter("{{DimensionId}}");
}
// =============================================================================
// INITIALIZATION PATTERNS
// =============================================================================
/**
* Pattern 18: Set initial filters from URL parameters
* Prerequisites:
* - Create global variable with p_ prefix (e.g., p_year)
* - URL: ?p_year=2024&p_region=EMEA
*/
// In Script Object (not onInitialization):
function applyUrlParameters() {
if (p_year) {
Chart_1.getDataSource().setDimensionFilter("Year", p_year);
Dropdown_Year.setSelectedKey(p_year);
}
if (p_region) {
Chart_1.getDataSource().setDimensionFilter("Region", p_region);
Dropdown_Region.setSelectedKey(p_region);
}
}
/**
* Pattern 19: Dynamic title update
* Use when: Title should reflect current filters
*/
function updateDynamicTitle() {
var year = Dropdown_Year.getSelectedKey() || "All Years";
var region = Dropdown_Region.getSelectedKey() || "All Regions";
Text_Title.setText("Sales Report - " + year + " | " + region);
}
// =============================================================================
// DEBUGGING PATTERNS
// =============================================================================
/**
* Pattern 20: Debug logging helper
* Use during development
*/
function debugLog(label, value) {
console.log("=== " + label + " ===");
console.log(value);
console.log("Type:", typeof value);
if (Array.isArray(value)) {
console.log("Length:", value.length);
}
}
/**
* Pattern 21: Log all selections
* Use when debugging selection handling
*/
function logSelections(widgetName, widget) {
var selections = widget.getSelections();
console.log(widgetName + " selections:");
console.log(JSON.stringify(selections));
}
// =============================================================================
// EXPORT PATTERNS
// =============================================================================
/**
* Pattern 22: Export with dynamic filename
* Use in: Export button onClick
*/
function exportWithDynamicName() {
var today = new Date().toISOString().split('T')[0];
var year = Dropdown_Year.getSelectedKey() || "AllYears";
Table_1.export(ExportType.Excel, {
fileName: "SalesReport_" + year + "_" + today
});
}
/**
* Pattern 23: PDF export with header/footer
* Use in: Export PDF button onClick
*/
function exportPDF() {
var year = Dropdown_Year.getSelectedKey() || "All Years";
var today = new Date().toLocaleDateString();
Application.export(ExportType.PDF, {
scope: ExportScope.All,
header: "Sales Analysis Report - " + year,
footer: "Generated: " + today + " | Page {page}",
orientation: "landscape"
});
}
// =============================================================================
// TYPE CONVERSION PATTERNS (SAC requires explicit conversion)
// =============================================================================
/**
* Pattern 24: Explicit type conversion (required in SAC)
* Use when: Concatenating strings with numbers
*/
function typeConversionExamples() {
var myNumber = 42;
var myDecimal = 3.14;
var myBoolean = true;
// WRONG: Auto-conversion not allowed
// console.log("Value: " + myNumber);
// CORRECT: Explicit conversion
console.log("Integer: " + myNumber.toString());
console.log("Decimal: " + myDecimal.toString());
console.log("Boolean: " + myBoolean.toString());
// Using ConvertUtils
var numStr = ConvertUtils.numberToString(myNumber);
var strNum = ConvertUtils.stringToInteger("42");
}
/**
* Pattern 25: String formatting utilities
* Use when: Formatting data for display
*/
function stringFormatting() {
var value = 1234.567;
// Fixed decimal places
var fixed = value.toFixed(2); // "1234.57"
// Format number (use NumberFormat)
var formatted = NumberFormat.format(value, "#,##0.00");
// Date formatting
var now = new Date();
var dateStr = DateFormat.format(now, "yyyy-MM-dd");
var timeStr = DateFormat.format(now, "HH:mm:ss");
}
// =============================================================================
// LOOP PATTERNS (SAC-specific requirements)
// =============================================================================
/**
* Pattern 26: For loop with explicit iterator declaration
* IMPORTANT: SAC requires 'var' declaration for loop iterator
*/
function forLoopPattern() {
var members = Table_1.getDataSource().getMembers("Location", 10);
// CORRECT: Iterator declared with var
for (var i = 0; i < members.length; i++) {
console.log(members[i].id + ": " + members[i].description);
}
// WRONG: Iterator not declared (will cause error)
// for (i = 0; i < members.length; i++) { ... }
}
/**
* Pattern 27: While loop pattern
* Use when: Iterating until condition met
*/
function whileLoopPattern() {
var count = 0;
var maxIterations = 10;
while (count < maxIterations) {
console.log("Iteration: " + count.toString());
count++;
// Safety break condition
if (count > 100) {
console.log("Safety break triggered");
break;
}
}
}
/**
* Pattern 28: For-in loop for selections
* Use when: Iterating over selection object properties
*/
function forInLoopPattern() {
var selection = Table_1.getSelections()[0];
if (selection) {
console.log("Selection properties:");
for (var propKey in selection) {
var propValue = selection[propKey];
console.log(" " + propKey + ": " + propValue);
}
}
}
// =============================================================================
// ARRAY PATTERNS
// =============================================================================
/**
* Pattern 29: Create typed 1D array
* Use when: Creating arrays for API calls
*/
function create1DArray() {
// Using ArrayUtils for typed arrays
var numberArray = ArrayUtils.create(Type.number);
numberArray.push(1);
numberArray.push(2);
numberArray.push(3);
var stringArray = ArrayUtils.create(Type.string);
stringArray.push("A");
stringArray.push("B");
// Literal syntax also works
var simpleArray = [1, 2, 3];
}
/**
* Pattern 30: Create 2D array (matrix)
* Use when: Building tabular data structures
*/
function create2DArray() {
var numCols = 4;
var numRows = 3;
// Create first row (required for type inference)
var firstRow = ArrayUtils.create(Type.number);
var arr2D = [firstRow];
// Add remaining rows
for (var i = 1; i < numRows; i++) {
arr2D.push(ArrayUtils.create(Type.number));
}
// Fill with values
var count = 0;
for (var row = 0; row < numRows; row++) {
for (var col = 0; col < numCols; col++) {
arr2D[row][col] = count;
count++;
}
}
// Access values
var value = arr2D[1][2]; // Row 1, Column 2
console.log("Value at [1][2]: " + value.toString());
}
// =============================================================================
// ADVANCED FILTER PATTERNS
// =============================================================================
/**
* Pattern 31: Exclude filter (filter OUT specific values)
* Use when: Removing specific members from drill-down
*/
function excludeFilterPattern() {
// Exclude single value
DS_1.setDimensionFilter("EMPLOYEE_ID", {
value: "230",
exclude: true
});
// Exclude multiple values
DS_1.setDimensionFilter("EMPLOYEE_ID", {
values: ["230", "240", "250"],
exclude: true
});
}
/**
* Pattern 32: Range filter (numeric dimensions only)
* Use when: Filtering by numeric ranges
* NOTE: Not supported for time dimensions or SAP BW
*/
function rangeFilterPattern() {
// Between range
DS_1.setDimensionFilter("EMPLOYEE_ID", {
from: "100",
to: "500"
});
// Less than
DS_1.setDimensionFilter("EMPLOYEE_ID", {
less: "100"
});
// Greater than or equal
DS_1.setDimensionFilter("EMPLOYEE_ID", {
greaterOrEqual: "500"
});
// Multiple ranges (OR logic)
DS_1.setDimensionFilter("EMPLOYEE_ID", [
{ less: "100" },
{ greater: "900" }
]);
}
/**
* Pattern 33: Get and process dimension filters
* Use when: Need to read current filter state
*/
function getFilterState() {
var filters = Table_1.getDataSource().getDimensionFilters("COUNTRY");
for (var i = 0; i < filters.length; i++) {
var filter = filters[i];
switch (filter.type) {
case FilterValueType.Single:
var single = cast(Type.SingleFilterValue, filter);
console.log("Single filter: " + single.value);
break;
case FilterValueType.Multiple:
var multi = cast(Type.MultipleFilterValue, filter);
console.log("Multiple filter: " + multi.values.join(", "));
break;
case FilterValueType.Range:
var range = cast(Type.RangeFilterValue, filter);
if (range.from) {
console.log("Range: " + range.from + " to " + range.to);
}
break;
}
}
}
// =============================================================================
// HIERARCHY PATTERNS
// =============================================================================
/**
* Pattern 34: Set hierarchy drill level
* Use when: Controlling hierarchy expansion
*/
function setHierarchyLevel() {
var ds = Table_1.getDataSource();
// Set active hierarchy
ds.setHierarchy("Location", "State_Hierarchy");
// Set drill level (0 = collapsed, higher = more expanded)
ds.setHierarchyLevel("Location", 2);
// Get current level
var currentLevel = ds.getHierarchyLevel("Location");
console.log("Current level: " + currentLevel.toString());
}
/**
* Pattern 35: Expand/collapse hierarchy nodes
* Use when: Programmatic hierarchy manipulation
*/
function expandCollapseNode() {
var ds = Chart_1.getDataSource();
// Expand specific node
ds.expandNode("Location", {
"Location": "[Location].[State_Hierarchy].&[California]",
"@MeasureDimension": "[Account].[parentId].&[Revenue]"
});
// Collapse node
ds.collapseNode("Location", {
"Location": "[Location].[State_Hierarchy].&[California]",
"@MeasureDimension": "[Account].[parentId].&[Revenue]"
});
}
// =============================================================================
// R VISUALIZATION PATTERNS
// =============================================================================
/**
* Pattern 36: Read R environment variable from JavaScript
* Use when: Getting calculated values from R script
*/
function readFromREnvironment() {
// R script created: gmCorrelation <- cor(grossMargin, grossMarginPlan)
var correlation = RVisualization_1
.getEnvironmentValues()
.getNumber("gmCorrelation");
console.log("Correlation coefficient: " + correlation.toString());
// Update text widget with R result
Text_Correlation.setText("Correlation: " + correlation.toFixed(3));
}
/**
* Pattern 37: Write to R environment from JavaScript
* Use when: Passing user input to R script
*/
function writeToREnvironment() {
// Get user selection
var userSelection = Dropdown_1.getSelectedKey();
var numValue = ConvertUtils.stringToInteger(userSelection);
// Pass to R environment
RVisualization_1
.getInputParameters()
.setNumber("userSelection", numValue);
// R script can now use: userSelection variable
}
// =============================================================================
// METHOD CHAINING PATTERNS
// =============================================================================
/**
* Pattern 38: Method chaining for concise code
* Use when: One-time data access, debugging
*/
function methodChainingExamples() {
// Chained logging (no intermediate variables)
console.log(Chart_1.getDataSource().getVariables());
// Chained filter application
Chart_1.getDataSource().setDimensionFilter("Year", "2024");
// Chained member access
var firstMember = Table_1.getDataSource()
.getMembers("Location", 1)[0];
// When NOT to chain: need to reuse result
var ds = Chart_1.getDataSource(); // Reuse ds
ds.setDimensionFilter("Year", "2024");
ds.setDimensionFilter("Region", "EMEA");
ds.refreshData();
}
// =============================================================================
// EQUALITY COMPARISON PATTERNS
// =============================================================================
/**
* Pattern 39: Using strict equality (recommended)
* SAC supports === always, == only with same types
*/
function equalityComparison() {
var aNumber = 1;
var aString = "1";
// CORRECT: Strict equality (always works)
if (aNumber === 1) {
console.log("Number equals 1");
}
if (aString === "1") {
console.log("String equals '1'");
}
// CORRECT: Compare same types
if (aString == "1") {
console.log("String comparison works");
}
// WOULD ERROR: Different types with ==
// if (aNumber == "1") { } // Don't do this!
}
// =============================================================================
// DATASOURCE INFO PATTERNS
// =============================================================================
/**
* Pattern 40: Get data source metadata
* Use when: Need model information for logging or display
*/
function getDataSourceInfo() {
var dsInfo = Table_1.getDataSource().getInfo();
console.log("Model: " + dsInfo.modelName);
console.log("Model ID: " + dsInfo.modelId);
console.log("Description: " + dsInfo.modelDescription);
// SAP BW only properties
if (dsInfo.sourceName !== undefined) {
console.log("Source: " + dsInfo.sourceName);
console.log("Last changed by: " + dsInfo.sourceLastChangedBy);
if (dsInfo.sourceLastRefreshedAt) {
console.log("Last refresh: " +
dsInfo.sourceLastRefreshedAt.toISOString());
}
}
// Display in UI
Text_ModelInfo.setText("Model: " + dsInfo.modelName);
}