26 KiB
26 KiB
SAP Analytics Cloud - JavaScript Patterns for Planning
Sources:
- https://www.denisreis.com/sap-analytics-cloud-javascript-api-code-snippets/
- https://help.sap.com/doc/958d4c11261f42e992e8d01a4c0dde25/release/en-US/index.html Last Updated: 2025-11-22
Table of Contents
- Application Patterns
- Member Filtering Patterns
- Data Entry Patterns
- Version Management Patterns
- Data Action Patterns
- Navigation Patterns
- Error Handling Patterns
- Performance Patterns
Application Patterns
Application Initialization
// onInitialization event
Application.showBusyIndicator();
try {
// Set default filters
var currentYear = new Date().getFullYear().toString();
Table_1.getDataSource().setDimensionFilter("Year", currentYear);
// Get user info for personalization
var userInfo = Application.getUserInfo();
Text_Welcome.setText("Welcome, " + userInfo.displayName);
// Load user preferences from app state
loadUserPreferences();
} catch (e) {
console.log("Initialization error: " + e.message);
} finally {
Application.hideBusyIndicator();
}
Show/Hide Busy Indicator
// Wrap long operations
function performLongOperation() {
Application.showBusyIndicator();
// Do work...
processData();
Application.hideBusyIndicator();
}
// With error handling
function safeOperation() {
Application.showBusyIndicator();
try {
riskyOperation();
} catch (e) {
Application.showMessage("Error: " + e.message);
} finally {
Application.hideBusyIndicator();
}
}
Message Handling
// Simple toast message
Application.showMessage("Operation completed successfully");
// For critical messages, use custom dialog
function showErrorDialog(title, message) {
// Assuming Dialog_Error is a popup widget
Text_ErrorTitle.setText(title);
Text_ErrorMessage.setText(message);
Popup_Error.open();
}
Member Filtering Patterns
Find Active Member by Attribute
// Find version marked as "Active" in master data
function findActivePlanVersion() {
var allVersions = PlanningModel_1.getMembers("Version");
for (var i = 0; i < allVersions.length; i++) {
if (allVersions[i].properties.Active === "X") {
return allVersions[i].id;
}
}
return null; // No active version found
}
// Usage
var activeVersion = findActivePlanVersion();
if (activeVersion) {
Table_1.getDataSource().setDimensionFilter("Version",
"[Version].[parentId].&[public." + activeVersion + "]");
}
Get Booked Values Only
// Performance optimization: only get members with data
var bookedAccounts = Table_1.getDataSource().getMembers("Account", {
accessMode: MemberAccessMode.BookedValues
});
console.log("Found " + bookedAccounts.length + " accounts with data");
Filter by Property Value
// Get all cost centers for a specific region
function getCostCentersByRegion(region) {
var allCCs = PlanningModel_1.getMembers("CostCenter", {
limit: 10000
});
var regionCCs = [];
for (var i = 0; i < allCCs.length; i++) {
if (allCCs[i].properties.Region === region) {
regionCCs.push(allCCs[i].id);
}
}
return regionCCs;
}
// Apply as filter
var emeaCCs = getCostCentersByRegion("EMEA");
Table_1.getDataSource().setDimensionFilter("CostCenter", emeaCCs);
Dynamic Planning Cycle Filter
// Set filter based on active planning cycle from master data
function setActivePlanningCycleFilter() {
Application.showBusyIndicator();
Table_1.setVisible(false);
var cycles = PlanningModel_1.getMembers("PlanningCycle");
var activeCycle = "";
for (var i = 0; i < cycles.length; i++) {
if (cycles[i].properties.Flag === "PC+0") {
activeCycle = cycles[i].id;
break;
}
}
if (activeCycle) {
// MDX filter syntax
Table_1.getDataSource().setDimensionFilter("Date",
"[Date].[YQM].&[" + activeCycle + "]");
}
Table_1.setVisible(true);
Application.hideBusyIndicator();
}
Populate Dropdown from Members
// Fill dropdown with dimension members
function populateDropdown(dropdown, dimensionId, filterProperty, filterValue) {
dropdown.removeAllItems();
var members = PlanningModel_1.getMembers(dimensionId, {limit: 1000});
for (var i = 0; i < members.length; i++) {
var member = members[i];
// Optional: filter by property
if (filterProperty && member.properties[filterProperty] !== filterValue) {
continue;
}
dropdown.addItem(member.id, member.description || member.id);
}
}
// Usage
populateDropdown(Dropdown_CostCenter, "CostCenter", "Status", "Active");
Data Entry Patterns
Check Planning Enabled
// Verify planning is enabled before operations
function verifyPlanningEnabled() {
var planning = Table_1.getPlanning();
if (!planning || !planning.isEnabled()) {
Application.showMessage("Planning is not enabled for this table");
return false;
}
return true;
}
Set Cell Value
// Set value for selected cell
function setSelectedCellValue(value) {
if (!verifyPlanningEnabled()) return;
var selections = Table_1.getSelections();
if (selections.length === 0) {
Application.showMessage("Please select a cell first");
return;
}
var selection = selections[0];
// Check data locking
var dataLocking = Table_1.getPlanning().getDataLocking();
var lockState = dataLocking.getState(selection);
if (lockState === DataLockingState.Locked) {
Application.showMessage("This cell is locked and cannot be edited");
return;
}
// Set the value
Table_1.getPlanning().setUserInput(selection, value.toString());
}
Apply Factor to Value
// Multiply existing value by factor
function applyFactor(factor) {
if (!verifyPlanningEnabled()) return;
var selection = Table_1.getSelections()[0];
if (!selection) return;
// Prefix with "*" to multiply
Table_1.getPlanning().setUserInput(selection, "*" + factor.toString());
}
// Usage: increase by 10%
applyFactor(1.1);
// Usage: decrease by 50%
applyFactor(0.5);
Enable/Disable Planning Based on Business Rules
// Disable planning in Q4 (budget freeze)
function checkPlanningAvailability() {
var currentMonth = new Date().getMonth() + 1;
if (currentMonth >= 10 && currentMonth <= 12) {
Table_Budget.getPlanning().setEnabled(false);
Button_Save.setEnabled(false);
Application.showMessage("Budget changes locked in Q4");
} else {
Table_Budget.getPlanning().setEnabled(true);
Button_Save.setEnabled(true);
}
}
Submit Data Changes
// Submit pending changes
function submitPlanningData() {
if (!verifyPlanningEnabled()) return;
Application.showBusyIndicator();
try {
Table_1.getPlanning().submitData();
Application.showMessage("Data submitted successfully");
} catch (e) {
Application.showMessage("Submit failed: " + e.message);
} finally {
Application.hideBusyIndicator();
}
}
Bulk Data Entry
// Set values for multiple cells
function setBulkValues(valueMap) {
// valueMap: [{selection: {...}, value: "100"}, ...]
Application.showBusyIndicator();
var planning = Table_1.getPlanning();
var successCount = 0;
var errorCount = 0;
for (var i = 0; i < valueMap.length; i++) {
try {
planning.setUserInput(valueMap[i].selection, valueMap[i].value);
successCount++;
} catch (e) {
errorCount++;
console.log("Error setting value: " + e.message);
}
}
// Submit all changes at once
planning.submitData();
Application.showMessage("Set " + successCount + " values, " + errorCount + " errors");
Application.hideBusyIndicator();
}
Version Management Patterns
Get All Versions
// List all available versions
function listVersions() {
var planning = Table_1.getPlanning();
console.log("=== Public Versions ===");
var publicVersions = planning.getPublicVersions();
for (var i = 0; i < publicVersions.length; i++) {
console.log(publicVersions[i].id + ": " + publicVersions[i].description);
}
console.log("=== Private Versions ===");
var privateVersions = planning.getPrivateVersions();
for (var j = 0; j < privateVersions.length; j++) {
console.log(privateVersions[j].id + ": " + privateVersions[j].description);
}
}
Publish Version
// Publish private version or edit mode
function publishVersion() {
var planning = Table_1.getPlanning();
var privateVersion = planning.getPrivateVersion();
if (!privateVersion) {
Application.showMessage("No private version to publish");
return;
}
if (!privateVersion.isDirty()) {
Application.showMessage("No changes to publish");
return;
}
Application.showBusyIndicator();
try {
privateVersion.publish();
Application.showMessage("Version published successfully");
} catch (e) {
Application.showMessage("Publish failed: " + e.message);
} finally {
Application.hideBusyIndicator();
}
}
Publish As New Version
// Create new public version from private
function publishAsNewVersion(newVersionId, newDescription) {
var planning = Table_1.getPlanning();
var privateVersion = planning.getPrivateVersion();
if (!privateVersion) {
Application.showMessage("No private version available");
return;
}
Application.showBusyIndicator();
try {
privateVersion.publishAs(newVersionId, newDescription);
Application.showMessage("New version '" + newVersionId + "' created");
} catch (e) {
Application.showMessage("Publish As failed: " + e.message);
} finally {
Application.hideBusyIndicator();
}
}
// Usage
publishAsNewVersion("Budget_2025_v2", "Revised Budget 2025");
Revert Changes
// Discard all changes
function revertChanges() {
var planning = Table_1.getPlanning();
var privateVersion = planning.getPrivateVersion();
if (!privateVersion) {
Application.showMessage("No private version to revert");
return;
}
// Confirm before reverting
// (In real app, use custom confirmation dialog)
privateVersion.revert();
Application.showMessage("Changes reverted");
}
Check Dirty Status Before Publishing
// Use isDirty() to avoid unnecessary publish attempts
function publishIfDirty() {
var version = Table_1.getPlanning().getPublicVersion("Forecast");
if (!version.isDirty()) {
Application.showMessage("No changes to publish");
return;
}
Application.showBusyIndicator();
var success = version.publish();
Application.hideBusyIndicator();
if (success) {
Application.showMessage("Published successfully");
} else {
Application.showMessage("Publish failed");
}
}
Copy Version with Options
// Create new version from existing
function copyVersionAsNewBudget(sourceId, targetId) {
var planning = Table_1.getPlanning();
var source = planning.getPublicVersion(sourceId);
if (!source) {
Application.showMessage("Source version not found");
return;
}
Application.showBusyIndicator();
var success = source.copy(
targetId,
PlanningCopyOptions.AllData,
PlanningCategory.Budget
);
Application.hideBusyIndicator();
if (success) {
Application.showMessage("Budget " + targetId + " created");
Application.refreshData();
}
}
Version Selection UI
// Populate version dropdown and handle selection
function setupVersionSelector() {
var planning = Table_1.getPlanning();
var versions = planning.getPublicVersions();
Dropdown_Version.removeAllItems();
for (var i = 0; i < versions.length; i++) {
Dropdown_Version.addItem(versions[i].id, versions[i].description);
}
}
// onSelect event for Dropdown_Version
function onVersionSelected() {
var selectedVersion = Dropdown_Version.getSelectedKey();
Table_1.getDataSource().setDimensionFilter("Version",
"[Version].[parentId].&[public." + selectedVersion + "]");
}
Data Action Patterns
Execute with Parameters
// Execute data action with parameters
function executeDataAction() {
// Set parameters
DataAction_Copy.setParameterValue("SourceVersion", "Actual");
DataAction_Copy.setParameterValue("TargetVersion", Dropdown_TargetVersion.getSelectedKey());
DataAction_Copy.setParameterValue("Year", InputField_Year.getValue());
Application.showBusyIndicator();
DataAction_Copy.execute();
}
// In onExecutionComplete event
function onDataActionComplete() {
Application.hideBusyIndicator();
Application.showMessage("Data action completed");
Application.refreshData();
}
Execute in Background
// Non-blocking execution for long-running actions
function executeInBackground() {
DataAction_LargeCalculation.setParameterValue("Scope", "ALL");
DataAction_LargeCalculation.executeInBackground();
Application.showMessage("Data action started in background");
}
// Monitor status in onExecutionStatusUpdate event
function onStatusUpdate(status) {
// status can be: Running, Success, Failed, Cancelled
console.log("Status: " + status);
if (status === "Success" || status === "Failed") {
Application.refreshData();
}
}
Conditional Execution
// Execute different actions based on conditions
function executeConditionalAction() {
var selectedAction = Dropdown_ActionType.getSelectedKey();
var targetVersion = Dropdown_Version.getSelectedKey();
switch (selectedAction) {
case "COPY":
DataAction_Copy.setParameterValue("TargetVersion", targetVersion);
DataAction_Copy.execute();
break;
case "ALLOCATE":
DataAction_Allocate.setParameterValue("TargetVersion", targetVersion);
DataAction_Allocate.execute();
break;
case "CLEAR":
DataAction_Clear.setParameterValue("TargetVersion", targetVersion);
DataAction_Clear.execute();
break;
default:
Application.showMessage("Unknown action type");
}
}
Navigation Patterns
Open Another Story
// Navigate to detail story with context
function openDetailStory(entityId) {
// Pass parameters via URL
var storyId = "story_id_here";
var params = "?p_entity=" + entityId;
NavigationUtils.openStory(storyId + params);
}
Open External URL
// Open SAP documentation
function openDocumentation() {
NavigationUtils.openUrl(
"[https://help.sap.com/docs/SAP_ANALYTICS_CLOUD"](https://help.sap.com/docs/SAP_ANALYTICS_CLOUD")
);
}
Refresh Data
// Refresh all data sources after external changes
function refreshAllData() {
Application.showBusyIndicator();
Application.refreshData();
Application.hideBusyIndicator();
Application.showMessage("Data refreshed");
}
Error Handling Patterns
Try-Catch Pattern
// Wrap risky operations
function safeOperation() {
try {
riskyFunction();
Application.showMessage("Success");
} catch (error) {
console.log("Error: " + error.message);
Application.showMessage("Operation failed: " + error.message);
}
}
Validation Pattern
// Validate before operation
function validateAndExecute() {
// Check required selections
var selection = Table_1.getSelections();
if (selection.length === 0) {
Application.showMessage("Please select data first");
return false;
}
// Check required inputs
var year = InputField_Year.getValue();
if (!year || year.length !== 4) {
Application.showMessage("Please enter valid year (YYYY)");
return false;
}
// Check planning enabled
if (!Table_1.getPlanning().isEnabled()) {
Application.showMessage("Planning not enabled");
return false;
}
// All validations passed
executeAction();
return true;
}
Null Check Pattern
// Handle potentially null objects
function safeGetVersion() {
var planning = Table_1.getPlanning();
if (!planning) {
console.log("Planning object not available");
return null;
}
var privateVersion = planning.getPrivateVersion();
if (!privateVersion) {
console.log("No private version exists");
return null;
}
return privateVersion;
}
Performance Patterns
Limit Member Retrieval
// Always set limit for getMembers
var members = PlanningModel_1.getMembers("CostCenter", {
limit: 5000 // Don't load unlimited members
});
// Use offset for pagination
var page2 = PlanningModel_1.getMembers("CostCenter", {
limit: 1000,
offset: 1000
});
Cache Member Lists
// Store frequently used member lists
var memberCache = {};
function getCachedMembers(dimensionId) {
if (!memberCache[dimensionId]) {
memberCache[dimensionId] = PlanningModel_1.getMembers(dimensionId, {
limit: 10000
});
}
return memberCache[dimensionId];
}
// Clear cache when model changes
function clearMemberCache() {
memberCache = {};
}
Batch UI Updates
// Hide table during batch updates
function batchUpdate() {
Table_1.setVisible(false);
Application.showBusyIndicator();
// Perform multiple operations
updateFilters();
updateSorting();
updateFormatting();
Table_1.setVisible(true);
Application.hideBusyIndicator();
}
Debounce Pattern
// Avoid rapid repeated calls (pseudo-code concept)
var debounceTimer = null;
function debouncedSearch(searchTerm) {
// Clear previous timer
if (debounceTimer) {
// In SAC, you'd use a different approach
// as clearTimeout isn't directly available
}
// Delay execution
// Note: SAC doesn't have setTimeout, use onTimeout event instead
performSearch(searchTerm);
}
Data Locking Patterns
Check Lock State Before Editing
// Verify cell can be edited before allowing user input
function canEditCell(selection) {
var dataLocking = Table_1.getPlanning().getDataLocking();
if (!dataLocking) {
return true; // Data locking not enabled, allow edit
}
var lockState = dataLocking.getState(selection);
switch (lockState) {
case DataLockingState.Open:
return true;
case DataLockingState.Restricted:
Application.showMessage("Only data owner can edit this cell");
return false;
case DataLockingState.Locked:
Application.showMessage("This data is locked and cannot be edited");
return false;
case DataLockingState.Mixed:
Application.showMessage("Selection contains mixed lock states");
return false;
default:
console.log("Unknown lock state");
return false;
}
}
Set Lock State on Public Version
// Lock data after approval
function lockApprovedData() {
var selection = Table_1.getSelections()[0];
var dataLocking = Table_1.getPlanning().getDataLocking();
if (!dataLocking) {
Application.showMessage("Data locking not enabled on this model");
return;
}
// Note: Can only set lock state on public versions
var success = dataLocking.setState(selection, DataLockingState.Locked);
if (success) {
Application.showMessage("Data locked successfully");
} else {
Application.showMessage("Failed to lock data - ensure you're on a public version");
}
}
Members on the Fly Patterns
Create New Dimension Member
// Dynamically add new cost center
function createCostCenter(id, description, region) {
Application.showBusyIndicator();
try {
PlanningModel_1.createMembers("CostCenter", {
id: id,
description: description,
properties: {
"CUSTOM_Region": region,
"CUSTOM_Status": "Active"
}
});
// IMPORTANT: Refresh to see new member
Application.refreshData();
Application.showMessage("Cost center created: " + id);
} catch (e) {
Application.showMessage("Error: " + e.message);
} finally {
Application.hideBusyIndicator();
}
}
Update Member with Data Locking Owner
// Assign data locking ownership
function assignDataOwner(dimensionId, memberId, userId) {
PlanningModel_1.updateMembers(dimensionId, {
id: memberId,
dataLockingOwners: [{
id: userId,
type: UserType.User
}]
});
Application.refreshData();
Application.showMessage("Data owner assigned");
}
Get Members with Pagination
// Load members in pages for large dimensions
function loadMembersInPages(dimensionId, pageSize) {
var allMembers = [];
var offset = 0;
var hasMore = true;
while (hasMore) {
var page = PlanningModel_1.getMembers(dimensionId, {
offset: offset.toString(),
limit: pageSize.toString()
});
if (page.length > 0) {
allMembers = allMembers.concat(page);
offset += pageSize;
} else {
hasMore = false;
}
}
return allMembers;
}
Utility Patterns
Type Conversion
// Convert string to integer
var planningCycle = "2025";
var cycleNumber = ConvertUtils.stringToInteger(planningCycle);
cycleNumber++;
var nextCycle = ConvertUtils.numberToString(cycleNumber); // "2026"
Date Formatting
// Format current date
var today = new Date();
var formatted = DateFormat.format(today, "yyyy-MM-dd");
console.log("Today: " + formatted);
Number Formatting
// Format currency value
var amount = 1234567.89;
var formatted = NumberFormat.format(amount, "#,##0.00");
console.log("Amount: " + formatted); // "1,234,567.89"
Array Operations
// Check if array contains value
var selectedItems = ["A", "B", "C"];
if (ArrayUtils.contains(selectedItems, "B")) {
console.log("B is selected");
}
// Find index
var index = ArrayUtils.indexOf(selectedItems, "C"); // 2
Complete Example: Budget Entry Application
// === onInitialization Event ===
Application.showBusyIndicator();
// Set default version
var versions = Table_Budget.getPlanning().getPublicVersions();
for (var i = 0; i < versions.length; i++) {
Dropdown_Version.addItem(versions[i].id, versions[i].description);
}
// Set current year
var currentYear = new Date().getFullYear().toString();
InputField_Year.setValue(currentYear);
// Load user's cost centers
var userInfo = Application.getUserInfo();
loadUserCostCenters(userInfo.userId);
Application.hideBusyIndicator();
// === Version Selection ===
function onVersionSelected() {
var version = Dropdown_Version.getSelectedKey();
Table_Budget.getDataSource().setDimensionFilter("Version",
"[Version].[parentId].&[public." + version + "]");
}
// === Save Button ===
function onSaveClick() {
if (!Table_Budget.getPlanning().isEnabled()) {
Application.showMessage("Planning not enabled");
return;
}
Application.showBusyIndicator();
try {
Table_Budget.getPlanning().submitData();
Application.showMessage("Budget saved successfully");
} catch (e) {
Application.showMessage("Save failed: " + e.message);
} finally {
Application.hideBusyIndicator();
}
}
// === Publish Button ===
function onPublishClick() {
var privateVer = Table_Budget.getPlanning().getPrivateVersion();
if (!privateVer || !privateVer.isDirty()) {
Application.showMessage("No changes to publish");
return;
}
Application.showBusyIndicator();
try {
privateVer.publish();
Application.showMessage("Budget published successfully");
} catch (e) {
Application.showMessage("Publish failed: " + e.message);
} finally {
Application.hideBusyIndicator();
}
}
// === Apply Growth Rate ===
function onApplyGrowthClick() {
var growthRate = ConvertUtils.stringToNumber(InputField_GrowthRate.getValue());
if (isNaN(growthRate)) {
Application.showMessage("Invalid growth rate");
return;
}
var factor = 1 + (growthRate / 100);
var selections = Table_Budget.getSelections();
if (selections.length === 0) {
Application.showMessage("Select cells to apply growth");
return;
}
Application.showBusyIndicator();
for (var i = 0; i < selections.length; i++) {
Table_Budget.getPlanning().setUserInput(selections[i], "*" + factor);
}
Table_Budget.getPlanning().submitData();
Application.showMessage("Growth rate applied");
Application.hideBusyIndicator();
}
Documentation Links:
- API Reference: https://help.sap.com/doc/958d4c11261f42e992e8d01a4c0dde25/release/en-US/index.html
- Code Snippets Blog: https://www.denisreis.com/sap-analytics-cloud-javascript-api-code-snippets/
- SAP Community Scripting: https://community.sap.com/t5/technology-blog-posts-by-sap/start-your-scripting-journey-the-easy-way-with-sap-analytics-cloud-part/ba-p/13582659