Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:55:33 +08:00
commit b82cab49fc
60 changed files with 10317 additions and 0 deletions

View File

@@ -0,0 +1,12 @@
{
"name": "sap-sac-scripting",
"description": "Scripting for SAC Analytics Designer and stories. Covers DataSource API, Chart/Table manipulation, Planning operations, Calendar integration, and 39 code templates.",
"version": "1.1.0",
"author": {
"name": "Zhongwei Li",
"email": "zhongweili@tubi.tv"
},
"skills": [
"./"
]
}

3
README.md Normal file
View File

@@ -0,0 +1,3 @@
# sap-sac-scripting
Scripting for SAC Analytics Designer and stories. Covers DataSource API, Chart/Table manipulation, Planning operations, Calendar integration, and 39 code templates.

184
SKILL.md Normal file
View File

@@ -0,0 +1,184 @@
---
name: sap-sac-scripting
description: |
Comprehensive SAC scripting skill for Analytics Designer and Optimized Story Experience. Use when building analytic applications, planning models, or enhanced stories. Covers DataSource API, Chart/Table manipulation, Planning operations, Calendar integration, Bookmarks, Timer API, Container widgets, Layout API, R Visualizations, Custom Widgets, Navigation, variables, event handlers, debugging, performance optimization, and 2025.23 features: comments APIs, Search to Insight, smart grouping, time-series forecast, geo map quick menus, Explorer/Smart Insights, composites scripting. Includes 40 code templates.
license: GPL-3.0
metadata:
version: 1.7.0
last_verified: 2025-11-27
sac_version: "2025.23"
api_reference_version: "2025.14"
documentation_source: [https://help.sap.com/docs/SAP_ANALYTICS_CLOUD](https://help.sap.com/docs/SAP_ANALYTICS_CLOUD)
reference_files: 52
template_patterns: 40
status: production
---
# SAP Analytics Cloud Scripting
Comprehensive skill for scripting in SAP Analytics Cloud (SAC) Analytics Designer and Optimized Story Experience.
## What's New in SAC 2025.23
Time series forecast API, Search to Insight, comments APIs, smart grouping, Explorer & Smart Insights, geo map enhancements, and composite scripting support. See `references/whats-new-2025.23.md` for complete details.
## When to Use This Skill
Use this skill when working on tasks involving:
- **Analytics Designer Development**: Creating interactive dashboards, planning applications, event handlers
- **Optimized Story Experience**: Enhancing stories with scripting capabilities
- **Data Operations**: Filtering, data access, hierarchies, data actions
- **Planning Operations**: Version management, data locking, workflows
- **UI/UX Enhancements**: Popups, navigation, responsive design
- **Advanced Features**: Calendar integration, bookmarks, R visualizations
## Quick Start
### Script Editor Access
- **Analytics Designer**: Edit mode → Select widget → Scripts tab
- **Optimized Story Experience**: Advanced Mode → Select widget → Add script
### Basic Script Structure
```javascript
// Event handler example
var selections = Chart_1.getSelections();
if (selections.length > 0) {
Table_1.getDataSource().setDimensionFilter("Location", selections[0]["Location"]);
}
```
## Core APIs
### DataSource API
- **Reference**: `Chart_1.getDataSource()` or `Table_1.getDataSource()`
- **Key Methods**: `getMembers()`, `getData()`, `setDimensionFilter()`, `refreshData()`
- **Performance**: Use `getResultSet()` (no backend) instead of `getMembers()` (hits backend)
### Planning API
- **Access**: `Table_1.getPlanning()`
- **Operations**: Version management (`getPublicVersion()`, `publish()`, `copy()`)
- **Data Locking**: Check/modify lock states
### Widget APIs
- **Charts**: `addDimension()`, `addMeasure()`, `getSelections()`
- **Tables**: `addDimensionToRows()`, `setZeroSuppressionEnabled()`
- **Containers**: Panel, TabStrip, PageBook for layout management
### Application Object
- **Utilities**: `showBusyIndicator()`, `showMessage()`
- **Info**: `getInfo()`, `getUserInfo()`, `getTheme()`
## Common Patterns
### Filter Based on Selection
```javascript
var selections = Chart_1.getSelections();
if (selections.length > 0) {
Table_1.getDataSource().setDimensionFilter("Location", selections[0]["Location"]);
}
```
### Pause/Resume Refresh (Performance)
```javascript
ds.setRefreshPaused(true);
// Apply multiple operations
ds.setRefreshPaused(false); // Single backend call
```
### Get Booked Values Only
```javascript
var members = ds.getMembers("Dimension", {accessMode: MemberAccessMode.BookedValues});
```
## Debugging
### Console Logging
```javascript
console.log("Debug info:", myVariable);
console.log("Selections:", Chart_1.getSelections());
```
### Browser Tools
- Open with F12 → Console tab
- Filter by "Info" type
- Look for "sandbox.worker.main.*.js"
### Performance Logging
Add URL parameter: `?APP_PERFORMANCE_LOGGING=true`
## Performance Best Practices
1. **Minimize Backend Trips**: Use `getResultSet()` over `getMembers()`
2. **Batch Operations**: Pause refresh, apply changes, resume
3. **Cache References**: Store `getDataSource()` in variables
4. **Empty onInitialization**: Avoid heavy startup operations
## Developer Best Practices
### Naming Conventions
- Charts: `chartB_revenue` (Bar), `chartL_sales` (Line)
- Tables: `tbl_transactions`
- Panels: `pnl_header`
- Buttons: `btn_export_pdf`
### Script Annotation
```javascript
/*
* Script: onSelect - chartB_revenue_by_region
* Purpose: Filter detail table when user selects a region
*/
```
## Events
### Application Events
- `onInitialization`: Runs once on load (keep empty!)
- `onResize`: Application resize
- `onOrientationChange`: Mobile orientation change
### Widget Events
- `onSelect`: Data point selection (Chart/Table)
- `onResultChanged`: Data changes
- `onClick`: Button click
## Planning Story Architecture
### Multi-Story Pattern
```
Planning_Application/
├── 00_Entry_Point.story
├── 01_Configuration.story
├── 02_Plan_FTE.story
├── 03_Plan_Costs.story
└── 04_Reports.story
```
### Navigation Script
```javascript
var urlParameters = ArrayUtils.create(Type.UrlParameter);
urlParameters.push(UrlParameter.create("page", "0"));
NavigationUtils.openStory(storyId, "", urlParameters, false);
```
## Bundled Resources
**Reference Files** (52 files):
- Core APIs: `references/api-datasource.md`, `references/api-widgets.md`
- Advanced: `references/api-calendar-bookmarks.md`, `references/api-advanced-widgets.md`
- Best Practices: `references/best-practices-developer.md`
- Language: `references/scripting-language-fundamentals.md`
**Templates** (40 patterns):
- `templates/common-patterns.js`: 40 scripting patterns
- `templates/planning-operations.js`: Planning-specific patterns
## Official Documentation
- **Analytics Designer API**: [Analytics Designer API](https://help.sap.com/doc/958d4c11261f42e992e8d01a4c0dde25/latest/en-US/)
- **Optimized Story Experience API**: [Optimized Story Experience API](https://help.sap.com/doc/1639cb9ccaa54b2592224df577abe822/latest/en-US/)
- **SAC Scripting Docs**: [SAC Scripting Docs](https://help.sap.com/docs/SAP_ANALYTICS_CLOUD)
---
**Version**: 1.7.0 | **Last Verified**: 2025-11-27 | **SAC Version**: 2025.23

838
SKILL.md.backup Normal file
View File

@@ -0,0 +1,838 @@
---
name: sap-sac-scripting
description: |
Comprehensive SAC scripting skill for Analytics Designer and Optimized Story Experience. Use when building analytic applications, planning models, or enhanced stories. Covers DataSource API, Chart/Table manipulation, Planning operations, Calendar integration, Bookmarks, Timer API, Container widgets, Layout API, R Visualizations, Custom Widgets, Navigation, variables, event handlers, debugging, performance optimization, and 2025.23 features: comments APIs, Search to Insight, smart grouping, time-series forecast, geo map quick menus, Explorer/Smart Insights, composites scripting. Includes 40 code templates.
license: GPL-3.0
metadata:
version: 1.7.0
last_verified: 2025-11-27
sac_version: "2025.23"
api_reference_version: "2025.14"
documentation_source: https://help.sap.com/docs/SAP_ANALYTICS_CLOUD
reference_files: 52
template_patterns: 40
status: production
---
# SAP Analytics Cloud Scripting Skill
Comprehensive skill for scripting in SAP Analytics Cloud (SAC) Analytics Designer and Optimized Story Experience.
---
## Whats New (2025.23)
- Time series forecast API for time-series/line charts (`references/time-series-forecast.md`).
- Search to Insight technical object with dialog/apply-to-chart APIs (`references/search-to-insight.md`).
- Widget/table comments APIs (`references/comments.md`).
- Smart grouping for bubble/scatter charts (`references/smart-grouping.md`).
- Explorer & Smart Insights launch/apply patterns (`references/explorer-smart-insights.md`).
- Geo map quick menus and scripting hooks (`references/geomap.md`).
- Composite scripting and composite feature restrictions (`references/composites-scripting.md`, `references/composites-restrictions.md`).
- Blending limitations and Analytics Designer known restrictions (`references/blending-limitations.md`, `references/analytics-designer-restrictions.md`).
- Authentication-required Help Portal page logged for awareness (`references/auth-required.md`).
---
## When to Use This Skill
Use this skill when working on tasks involving:
**Analytics Designer Development**:
- Creating analytic applications with interactive dashboards
- Building planning applications for data entry and forecasting
- Writing event handler scripts (onInitialization, onSelect, onResultChanged)
- Implementing widget interactions (charts, tables, input controls)
- Managing data sources and applying filters programmatically
**Optimized Story Experience**:
- Enhancing stories with scripting capabilities
- Adding interactivity to existing stories
- Implementing custom business logic
- Creating dynamic visualizations
**Data Operations**:
- Filtering and manipulating data sources
- Reading dimension members and measure values
- Working with hierarchies and selections
- Implementing data actions and multi-actions
**Planning Operations**:
- Managing planning versions (public/private)
- Publishing and copying versions
- Handling data locking
- Implementing data entry workflows
**UI/UX Enhancements**:
- Creating popups and dialogs
- Managing widget visibility
- Implementing dynamic navigation
- Building responsive applications
- Using container widgets (Panel, TabStrip, PageBook, Flow Layout)
- Implementing Layout API for dynamic sizing/positioning
- Adding/exposing comments on widgets and table cells (`references/comments.md`)
- Applying dynamic text via script variables (`references/text-widget.md`)
- Using geo maps with quick menu control and scripted layers (`references/geomap.md`)
- Binding widget values to variables (`references/bind-widget-values.md`)
- Using input fields/text areas and sliders/range sliders (`references/input-fields.md`, `references/sliders.md`)
- Bookmark settings, APIs, and bookmark sets (`references/bookmark-settings.md`, `references/bookmark-set-tech-object.md`)
**Calendar & Workflow Integration**:
- Creating and managing calendar tasks and processes
- Implementing approval workflows (submit, approve, reject)
- Adding reminders and notifications
- Integrating with planning calendar events
**Bookmarks & State Management**:
- Saving and restoring application state
- Creating global and personal bookmarks
- URL parameter integration
- Linked Analysis for cross-widget filtering
**Advanced Features**:
- Timer-based operations and animations
- R Visualizations for specialized charts
- Custom Widget development
- Cross-application navigation
- Search to Insight technical object, dialog control, apply-to-chart (`references/search-to-insight.md`)
- Time-series forecast enablement on charts (`references/time-series-forecast.md`)
- Smart grouping for correlation charts (`references/smart-grouping.md`)
- Explorer & Smart Insights launch and application patterns (`references/explorer-smart-insights.md`)
- Scripting inside composites (`references/composites-scripting.md`)
- Pattern-based functions for ML-driven text pattern detection (`references/pattern-functions.md`)
**Technical Objects & Actions**:
- Data actions, multi actions, and planning model technical objects (`references/data-actions-tech-object.md`, `references/multi-actions-tech-object.md`, `references/planning-model-tech-object.md`)
- Calendar integration, data change insights, timer, text pool (`references/calendar-integration-tech-object.md`, `references/data-change-insights-tech-object.md`, `references/timer-tech-object.md`, `references/text-pool-tech-object.md`)
- Export technical objects for PDF/PowerPoint (`references/export-pdf-tech-object.md`, `references/export-ppt-tech-object.md`)
- OData service integration (`references/odata-service.md`)
- Bookmark set technical object (`references/bookmark-set-tech-object.md`)
- onAfterDataEntryProcess event handling for tables (`references/on-after-data-entry-process.md`)
- MemberInfo performance guidance (`references/memberinfo-performance.md`)
**Debugging & Optimization**:
- Debugging scripts with console.log and debugger statement
- Optimizing application performance
- Troubleshooting runtime errors
- Script editor basics (`references/script-editor.md`) and value help (`references/value-help.md`)
- Optimize type libraries and automatic refactoring tools (`references/optimize-type-libraries.md`, `references/automatic-refactoring.md`)
- Check errors/references and debug mode with breakpoints (`references/check-errors.md`, `references/check-references.md`, `references/debug-scripts.md`)
- Script performance popup for runtime profiling (`references/script-performance-popup.md`)
---
## Quick Start
### Script Editor Access
1. **Analytics Designer**: Open application in edit mode → Select widget → Click Scripts tab
2. **Optimized Story Experience**: Enable Advanced Mode → Select widget → Add script
### Basic Script Structure
```javascript
// Event handler script (e.g., onSelect of a chart)
var selections = Chart_1.getSelections();
if (selections.length > 0) {
var selectedMember = selections[0]["Location"];
Table_1.getDataSource().setDimensionFilter("Location", selectedMember);
}
```
### Script Types
1. **Event Handlers**: Triggered by widget events (onSelect, onClick, onResultChanged)
2. **Application Events**: onInitialization, onResize, onOrientationChange
3. **Script Objects**: Reusable functions callable from any event handler
---
## Core Concepts
### 1. Variables
**Local Variables** (within event handler):
```javascript
var myString = "Hello";
var myNumber = 42;
var myBoolean = true;
var myArray = ["a", "b", "c"];
```
**Global Variables** (across entire application):
- Create via Outline panel → Global Variables → (+)
- Types: String, Integer, Number, Boolean (can be arrays)
- Access from any script: `GlobalVariable_1 = "value";`
**URL Parameters**:
- Prefix global variable name with `p_` to pass via URL
- URL format: `?p_myVariable=value`
### 2. DataSource API
Central API for data access. Get reference via widgets:
```javascript
var ds = Chart_1.getDataSource();
var ds = Table_1.getDataSource();
```
**Key Methods**:
| Method | Description |
|--------|-------------|
| `getDimensions()` | Returns all dimensions |
| `getMeasures()` | Returns all measures |
| `getMembers(dimensionId, options)` | Returns dimension members |
| `getData(selection)` | Returns data values |
| `getResultSet()` | Returns current result set (no backend trip) |
| `setDimensionFilter(dimension, value)` | Sets filter on dimension |
| `removeDimensionFilter(dimension)` | Removes filter |
| `getDimensionFilters(dimension)` | Gets current filters |
| `refreshData()` | Refreshes data from backend |
| `getVariables()` | Returns model variables |
| `setVariableValue(variable, value)` | Sets variable value |
**Reference**: See `references/api-datasource.md` for complete DataSource API.
### 3. Widget APIs
**Chart**:
```javascript
Chart_1.addDimension(Feed.CategoryAxis, "Location");
Chart_1.addMeasure(Feed.ValueAxis, "[Account].[parentId].&[Revenue]");
Chart_1.removeMember(Feed.ValueAxis, measureId);
var selections = Chart_1.getSelections();
Chart_1.setVisible(true);
```
**Table**:
```javascript
Table_1.addDimensionToRows("Product");
Table_1.addDimensionToColumns("Date");
Table_1.setZeroSuppressionEnabled(true);
var selections = Table_1.getSelections();
```
**Reference**: See `references/api-widgets.md` for complete widget APIs.
### 4. Planning API
Access via table widget:
```javascript
var planning = Table_1.getPlanning();
```
**Version Management**:
```javascript
// Get public version
var publicVersion = planning.getPublicVersion("Budget2024");
// Get private version
var privateVersion = planning.getPrivateVersion("myDraft");
// Check for unsaved changes
if (publicVersion.isDirty()) {
publicVersion.publish();
}
// Copy version
sourceVersion.copy("NewVersion", PlanningCopyOption.AllData, PlanningCategory.Budget);
```
**Data Locking**:
```javascript
var selection = Table_1.getSelections()[0];
var lockState = planning.getDataLocking().getState(selection);
```
**Reference**: See `references/api-planning.md` for complete Planning API.
### 5. Application API
Global Application object:
```javascript
// User feedback
Application.showBusyIndicator();
Application.hideBusyIndicator();
// Messages
Application.showMessage(ApplicationMessageType.Info, "Operation completed");
// Application info
var appInfo = Application.getInfo();
var userInfo = Application.getUserInfo();
// Theme
var theme = Application.getTheme();
```
### 6. Events
**Application Events**:
- `onInitialization`: Runs once when application loads
- `onPostMessageReceived`: Fires when host app sends PostMessage (iFrame scenarios)
- `onResize`: Fires when application is resized
- `onOrientationChange`: Mobile orientation change
**Widget Events**:
- `onSelect`: User selects data point (Chart, Table)
- `onResultChanged`: Data in widget changes
- `onClick`: Button click, etc.
**Best Practice**: Keep `onInitialization` empty for performance. Use static filters instead.
**iFrame Embedding**: See `references/iframe-embedding-lumira-migration.md` for PostMessage integration.
---
## Common Patterns
### Filter Data Based on Selection
```javascript
// onSelect event of Chart_1
var selections = Chart_1.getSelections();
if (selections.length > 0) {
var selectedLocation = selections[0]["Location"];
Table_1.getDataSource().setDimensionFilter("Location", selectedLocation);
}
```
### Find Active Version from Attribute
```javascript
var allVersions = PlanningModel_1.getMembers("Version");
var activeVersion = "";
for (var i = 0; i < allVersions.length; i++) {
if (allVersions[i].properties.Active === "X") {
activeVersion = allVersions[i].id;
break;
}
}
console.log("Active Version: " + activeVersion);
```
### Dynamic Measure Swap
```javascript
// Get current measure
var currentMeasure = Chart_1.getMembers(Feed.ValueAxis);
// Remove current measure
Chart_1.removeMember(Feed.ValueAxis, currentMeasure[0]);
// Add new measure
Chart_1.addMember(Feed.ValueAxis, "[Account].[parentId].&[NewMeasure]");
```
### Switch Between Chart and Table
```javascript
// Show chart, hide table
Chart_1.setVisible(true);
Table_1.setVisible(false);
Button_ShowTable.setVisible(true);
Button_ShowChart.setVisible(false);
```
### Pause and Resume Refresh
```javascript
// Pause before multiple operations
Chart_1.getDataSource().setRefreshPaused(true);
Table_1.getDataSource().setRefreshPaused(true);
// Apply multiple filters
Chart_1.getDataSource().setDimensionFilter("Year", "2024");
Chart_1.getDataSource().setDimensionFilter("Region", "EMEA");
// Resume refresh (single backend call)
Chart_1.getDataSource().setRefreshPaused(false);
Table_1.getDataSource().setRefreshPaused(false);
```
### Get Booked Values Only
```javascript
var members = Table_1.getDataSource()
.getMembers("Dimension", {accessMode: MemberAccessMode.BookedValues});
```
**Reference**: See `templates/common-patterns.js` for more patterns.
---
## Debugging
### Console Logging
```javascript
console.log("Variable value:", myVariable);
console.log("Selections:", Chart_1.getSelections());
```
**Access Console**:
1. Run application
2. Press F12 or Ctrl+Shift+J
3. Go to Console tab
4. Filter by "Info" type
5. Expand "sandbox.worker.main.*.js" for your logs
### Debugger Statement
```javascript
debugger; // Execution pauses here
var value = getData();
```
**Enable Debug Mode**:
1. Add `;debug=true` to application URL
2. Open Developer Tools (F12)
3. Go to Sources tab
4. Find code in "sandbox worker main" file
### Performance Logging
Add URL parameter: `?APP_PERFORMANCE_LOGGING=true`
```javascript
// In console
window.sap.raptr.getEntriesByMarker("(Application)")
.filter(e => e.entryType === 'measure')
.sort((a,b) => (a.startTime + a.duration) - (b.startTime + b.duration));
```
### Error Handling
```javascript
try {
var data = Table_1.getDataSource().getData(selection);
// Process data
} catch (error) {
console.log("Error:", error);
Application.showMessage(ApplicationMessageType.Error, "Operation failed");
}
```
---
## Performance Best Practices
### 1. Minimize Backend Trips
```javascript
// GOOD: getResultSet() - no backend trip
var resultSet = Chart_1.getDataSource().getResultSet();
// AVOID: getMembers() - always hits backend
var members = Chart_1.getDataSource().getMembers("Dimension");
```
### 2. Batch Filter Operations
```javascript
// GOOD: Pause refresh, apply multiple filters, resume
ds.setRefreshPaused(true);
ds.setDimensionFilter("Dim1", value1);
ds.setDimensionFilter("Dim2", value2);
ds.setDimensionFilter("Dim3", value3);
ds.setRefreshPaused(false);
// AVOID: Each filter triggers refresh
ds.setDimensionFilter("Dim1", value1); // Refresh
ds.setDimensionFilter("Dim2", value2); // Refresh
ds.setDimensionFilter("Dim3", value3); // Refresh
```
### 3. Copy Filters Efficiently
```javascript
// GOOD: Copy all filters at once
Table_1.getDataSource().copyDimensionFilterFrom(Chart_1.getDataSource());
// AVOID: Set each filter individually
var filters = Chart_1.getDataSource().getDimensionFilters("Dim1");
Table_1.getDataSource().setDimensionFilter("Dim1", filters[0].value);
```
### 4. Empty onInitialization
```javascript
// AVOID: Heavy operations in onInitialization
// This blocks application startup
// GOOD: Use URL parameters for initial values
// Use static widget filters instead of script filters
// Load invisible widgets in background
```
### 5. Cache DataSource References
```javascript
// GOOD: Cache reference outside loop
var ds = Table_1.getDataSource();
for (var i = 0; i < items.length; i++) {
ds.setDimensionFilter("Dim", items[i]);
}
// AVOID: Get reference inside loop
for (var i = 0; i < items.length; i++) {
Table_1.getDataSource().setDimensionFilter("Dim", items[i]);
}
```
---
## Developer Best Practices
Maintainable SAC stories require consistent conventions. Key principles:
### 1. Naming Conventions
Use prefixes to identify widget types at a glance:
| Widget Type | Prefix | Example |
|-------------|--------|---------|
| Bar Chart | `chartB_` | `chartB_revenue_by_state` |
| Line Chart | `chartL_` | `chartL_margin_by_product` |
| Table | `tbl_` | `tbl_invoices_by_month` |
| Panel | `pnl_` | `pnl_header` |
| Button | `btn_` | `btn_export_pdf` |
| Dropdown | `ddl_` | `ddl_product` |
| KPI/Numeric | `kpi_` | `kpi_actuals_vs_budget` |
### 2. Layout Organization
- Group related widgets in panels
- Order widgets in Outline view: top→bottom, left→right
- Use panel visibility for show/hide sections:
```javascript
// Hide entire section instead of individual widgets
pnl_details.setVisible(false);
```
### 3. Script Annotation
Add summary + line-level comments to all scripts:
```javascript
/*
* Script: onSelect - chartB_revenue_by_region
* Purpose: Filter detail table when user selects a region
* Dependencies: chartB_revenue_by_region, tbl_transactions
*/
// Get user's selection from the chart
var selections = chartB_revenue_by_region.getSelections();
```
### 4. Design Tips
- **Disable Panel Auto Scroll**: Prevents unwanted scroll bars
- **Avoid Flow Panels**: Fixed layouts are easier to maintain
- **Use % for Left/Right**: Change from px to % after layout is finalized for responsive sizing
**Full Reference**: See `references/best-practices-developer.md` for complete naming table and patterns.
**Source**: [SAP Community - Building Stories That Other Developers Actually Want to Inherit](https://community.sap.com/t5/technology-blog-posts-by-members/building-stories-that-other-developers-actually-want-to-inherit/ba-p/14168133)
---
## Planning Story Architecture
For complex planning processes, use multi-story architecture with a central entry point.
### Key Principles
1. **Entry Point Design**: Create overview page with KPI tiles and organized navigation sections
2. **Multi-Story Separation**: One story per planning phase, stored in folder structure
3. **Script-Based Navigation**: Use `NavigationUtils.openStory()` instead of hyperlinks
4. **User Assistance**: Consistent sidebar with filters, step-by-step instructions, external links
5. **Guided Process**: "Guide Me!" popup for focused step-by-step workflow
6. **Button Color Coding**: Green (positive), Red (negative), Blue (neutral)
### Navigation Script Pattern
```javascript
// Navigate to target story with page and mode parameters
var urlParameters = ArrayUtils.create(Type.UrlParameter);
urlParameters.push(UrlParameter.create("mode", "view"));
urlParameters.push(UrlParameter.create("page", "0"));
NavigationUtils.openStory(storyId, "", urlParameters, false);
```
### Folder Organization
```
Planning_Application/
├── 00_Entry_Point.story
├── 01_Configuration.story
├── 02_Plan_FTE.story
├── 03_Plan_Costs.story
└── 04_Reports.story
```
**Full Reference**: See `references/best-practices-planning-stories.md` for complete patterns including sidebar design, guided process popup implementation, and button guidelines.
**Source**: [SAP PRESS - Best Practices for Planning Stories](https://blog.sap-press.com/best-practices-for-planning-stories-in-sap-analytics-cloud)
---
## Popups and Dialogs
### Create Popup
1. Outline panel → Popups → (+)
2. Add widgets to popup (tables, charts, buttons)
3. Optionally enable header/footer for dialog style
### Show/Hide Popup
```javascript
// Show popup
Popup_1.open();
// Hide popup
Popup_1.close();
```
### Busy Indicator
```javascript
Application.showBusyIndicator();
// Perform operations
Table_1.getDataSource().refreshData();
Application.hideBusyIndicator();
```
---
## Utility Functions
### Type Conversion
```javascript
// String to Integer
var num = ConvertUtils.stringToInteger("42");
// Number to String
var str = ConvertUtils.numberToString(42);
```
### Array Operations
```javascript
// ArrayUtils available for common operations
var arr = [1, 2, 3];
```
### Date Formatting
```javascript
// DateFormat utility
var formatted = DateFormat.format(dateValue, "yyyy-MM-dd");
```
---
## Script Objects (Reusable Functions)
Create reusable functions not tied to specific events:
1. Outline panel → Script Objects → (+)
2. Add functions to script object
3. Call from any event: `ScriptObject_1.myFunction(param);`
```javascript
// In ScriptObject_1
function applyStandardFilters(dataSource, year, region) {
dataSource.setDimensionFilter("Year", year);
dataSource.setDimensionFilter("Region", region);
}
// In event handler
ScriptObject_1.applyStandardFilters(Table_1.getDataSource(), "2024", "EMEA");
```
---
## Export Capabilities
### PDF Export
```javascript
var exportSettings = {
scope: ExportScope.All,
header: "Report Title",
footer: "Page {page}",
orientation: "landscape"
};
Application.export(ExportType.PDF, exportSettings);
```
### Excel Export
```javascript
Application.export(ExportType.Excel, {
scope: ExportScope.All,
includeHierarchy: true
});
```
### CSV Export
```javascript
Table_1.export(ExportType.CSV);
```
---
## Restrictions & Unsupported (2025.23)
- **Composites**: many editor/runtime gaps; export/bookmark only at composite level; limited linked analysis; no `onInitialization` event (`references/composites-restrictions.md`).
- **Analytics Designer**: popup sizing/theme issues, planning/version API limits, DataSource filter/variable constraints, chart feed/event limits, export quirks, Safari R-vis cookie issue, embedding not supported, input-control constraints (`references/analytics-designer-restrictions.md`).
- **Blending**: primary-model-only API support; several filter/table/planning/comment APIs unsupported; IDs include modelId; value help differences (`references/blending-limitations.md`).
- **Authentication-required page**: one Help Portal article needs SAP login (`references/auth-required.md`).
---
## Official Documentation Links
### API References (Always Current)
- **Analytics Designer API Reference 2025.23**: https://help.sap.com/doc/958d4c11261f42e992e8d01a4c0dde25/release/en-US/index.html
- **Optimized Story Experience API Reference 2025.23**: https://help.sap.com/doc/1639cb9ccaa54b2592224df577abe822/release/en-US/index.html
### SAP Help Portal
- **Scripting Documentation**: https://help.sap.com/docs/SAP_ANALYTICS_CLOUD/00f68c2e08b941f081002fd3691d86a7/6a4db9a9c8634bcb86cecbf1f1dbbf8e.html
- **Performance Best Practices**: https://help.sap.com/docs/SAP_ANALYTICS_CLOUD/00f68c2e08b941f081002fd3691d86a7/fbe339efda1241b5a3f46cf17f54cdff.html
### Learning Resources
- **SAP Learning Journey**: https://learning.sap.com/learning-journeys/acquiring-basic-scripting-skills-to-extend-stories-in-sap-analytics-cloud
- **SAP Developer Tutorials**: https://developers.sap.com/tutorials/sac-analytics-designer-create-1-create.html
### Community Resources
- **SAP Community - SAC**: https://community.sap.com/topics/cloud-analytics
- **Code Snippets**: https://www.denisreis.com/sap-analytics-cloud-javascript-api-code-snippets/
---
## Bundled Reference Files
This skill includes detailed reference documentation (52 files):
**Core APIs**:
1. **references/api-datasource.md**: Complete DataSource API (36+ methods)
2. **references/api-widgets.md**: Chart, Table, Input Controls, GeoMap APIs
3. **references/api-planning.md**: Planning API, version management, data locking
4. **references/api-application.md**: Application object, utilities, events
**Advanced APIs**:
5. **references/api-calendar-bookmarks.md**: Calendar integration, Bookmarks, Linked Analysis, Timer API
6. **references/api-advanced-widgets.md**: Container widgets, Layout API, R Visualization, Custom Widgets, Navigation
7. **references/api-data-operations.md**: Range/exclude filters, dimension properties, hierarchies, members, DataSource info
**Scripting Fundamentals**:
8. **references/scripting-language-fundamentals.md**: Type system, variables, control flow, loops, operators, built-in objects, arrays, method chaining, security
9. **references/debugging-browser-tools.md**: Console logging, browser debugging, debug mode, breakpoints, R visualization debugging, performance logging
**Best Practices**:
10. **references/best-practices-developer.md**: Naming conventions, layout organization, script annotation, responsive design
11. **references/best-practices-planning-stories.md**: Multi-story architecture, entry point design, navigation scripting, user assistance patterns
**Embedding & Migration**:
12. **references/iframe-embedding-lumira-migration.md**: iFrame PostMessage communication, Lumira Designer migration guidance
**2025.23 Addenda (Help Portal extracts)**:
13. **references/comments.md** Comments APIs for widgets/table cells
14. **references/search-to-insight.md** Search to Insight technical object
15. **references/time-series-forecast.md** Forecast enablement on charts
16. **references/composites-scripting.md** Add scripts to composites
17. **references/composites-restrictions.md** Composite restrictions
18. **references/blending-limitations.md** Blending unsupported APIs and notes
19. **references/analytics-designer-restrictions.md** Known restrictions
20. **references/geomap.md** Geo map quick menus and scripting
21. **references/text-widget.md** Dynamic text & text widget APIs
22. **references/smart-grouping.md** Smart grouping APIs
23. **references/explorer-smart-insights.md** Explorer and Smart Insights
24. **references/auth-required.md** Auth-required Help Portal page
25. **references/script-editor.md** Script editor basics
26. **references/value-help.md** Value help/auto-complete in editor
27. **references/script-variables.md** Global/script variables & URL params
28. **references/script-objects.md** Reusable script objects
29. **references/pattern-functions.md** Pattern-based functions (ML)
30. **references/optimize-type-libraries.md** Optimize type libraries
31. **references/automatic-refactoring.md** Automatic refactoring tools
32. **references/debug-scripts.md** Debug mode & breakpoints
33. **references/check-errors.md** Errors panel usage
34. **references/check-references.md** Reference checking
35. **references/script-performance-popup.md** Script performance analysis
36. **references/input-fields.md** Input fields/text areas
37. **references/sliders.md** Sliders and range sliders
38. **references/bind-widget-values.md** Bind widget values to variables
39. **references/bookmark-settings.md** Bookmark settings/APIs
40. **references/bookmark-set-tech-object.md** Bookmark set technical object
41. **references/calendar-integration-tech-object.md** Calendar integration tech object
42. **references/data-change-insights-tech-object.md** Data change insights tech object
43. **references/export-pdf-tech-object.md** Export to PDF tech object
44. **references/export-ppt-tech-object.md** Export to PowerPoint tech object
45. **references/text-pool-tech-object.md** Text pool tech object
46. **references/timer-tech-object.md** Timer tech object
47. **references/planning-model-tech-object.md** Planning model tech object
48. **references/data-actions-tech-object.md** Data actions tech object
49. **references/multi-actions-tech-object.md** Multi actions tech object
50. **references/odata-service.md** OData service technical object
51. **references/on-after-data-entry-process.md** Table onAfterDataEntryProcess event
52. **references/memberinfo-performance.md** MemberInfo performance guidance
**Templates** (40 ready-to-use patterns):
1. **templates/common-patterns.js**: 40 patterns - filtering, data access, loops, arrays, R visualization, type conversion, etc.
2. **templates/planning-operations.js**: Version management, workflows, data actions
---
## Version Compatibility
- **Minimum SAC Version**: 2025.23
- **API Reference Version**: 2025.23
- **Analytics Designer**: Fully supported
- **Optimized Story Experience**: Fully supported (Advanced Mode)
**Note**: Analytics Designer is being strategically replaced by Advanced Mode in Optimized Story Experience. Both share similar scripting APIs.
---
## Instructions for Claude
When assisting with SAC scripting:
1. **Use correct API syntax**: Reference official API documentation
2. **Prefer getResultSet()**: Over getMembers() for performance
3. **Batch operations**: Use pause/resume refresh pattern
4. **Include error handling**: Use try-catch for robust code
5. **Follow naming conventions**: CamelCase for variables, descriptive names
6. **Test incrementally**: Suggest testing every 5-10 lines
7. **Link to documentation**: Provide relevant SAP Help links
8. **Consider performance**: Avoid heavy operations in onInitialization
When writing scripts:
- Start with variable declarations
- Use console.log() for debugging
- Cache DataSource references
- Handle empty selections gracefully
- Use Application.showBusyIndicator() for long operations
For troubleshooting:
- Check browser console (F12)
- Verify widget names in Outline
- Confirm DataSource is connected
- Test with simple console.log first
- Use `;debug=true` URL parameter
---
**License**: MIT
**Version**: 1.6.0
**Maintained by**: SAP Skills Maintainers
**Repository**: https://github.com/secondsky/sap-skills

269
plugin.lock.json Normal file
View File

@@ -0,0 +1,269 @@
{
"$schema": "internal://schemas/plugin.lock.v1.json",
"pluginId": "gh:secondsky/sap-skills:skills/sap-sac-scripting",
"normalized": {
"repo": null,
"ref": "refs/tags/v20251128.0",
"commit": "1bbfbdb431686a41c7ba12a8b9216ad4ac02684a",
"treeHash": "b77479b2b547697e4c4dc8773010758843abbc7001077ae06dd5c018bf891fcb",
"generatedAt": "2025-11-28T10:28:14.747430Z",
"toolVersion": "publish_plugins.py@0.2.0"
},
"origin": {
"remote": "git@github.com:zhongweili/42plugin-data.git",
"branch": "master",
"commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390",
"repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data"
},
"manifest": {
"name": "sap-sac-scripting",
"description": "Scripting for SAC Analytics Designer and stories. Covers DataSource API, Chart/Table manipulation, Planning operations, Calendar integration, and 39 code templates.",
"version": "1.1.0"
},
"content": {
"files": [
{
"path": "SKILL.md.backup",
"sha256": "547ae1186b8c3038fa7fc75ed35b0c76126d0c41d7f49b64e6e978660288b1c9"
},
{
"path": "README.md",
"sha256": "2dcf847211cc7593f5b61a9a4962c85a655bd322f4e179bfa2b0987f4371c2aa"
},
{
"path": "SKILL.md",
"sha256": "d26d8337591e2992cec39c4b9ff14776669da798253ec400cb26674704d2de19"
},
{
"path": "references/debug-scripts.md",
"sha256": "0010810d27c85585044acdde48af99e2da35b107db232adc7e1ce71acaca608e"
},
{
"path": "references/whats-new-2025.23.md",
"sha256": "5a21afe34dc35face7993578821f7a25f7784ddc7c7730cb702d48869aa31443"
},
{
"path": "references/on-after-data-entry-process.md",
"sha256": "7ea449b9d26ef911bc25439c463b28bd18a22437fe044473a9f81429759d39ee"
},
{
"path": "references/auth-required.md",
"sha256": "bc6cfb9d2b9187eeae7c2126f167e72851a0862a418e83ff38fd4fa3c9c3cc94"
},
{
"path": "references/value-help.md",
"sha256": "a13e075fd15c22c098d108884f6c60c8f107882255ec8283b961e8e41f0c3f3b"
},
{
"path": "references/api-calendar-bookmarks.md",
"sha256": "d17574c98187aa2330605ff8a6e3d2ff2a73500857e6a1d730a3edeea7835041"
},
{
"path": "references/api-planning.md",
"sha256": "990aced69b0008fc6b250dc6685729d1a11dd0046e6f540059b87c7def392341"
},
{
"path": "references/api-advanced-widgets.md",
"sha256": "2077aa0651d80c60c72c7c1bd4a4b65acb38154c0e74abb492e414bc022fd4e9"
},
{
"path": "references/script-editor.md",
"sha256": "505b1f3d135291766084ce95f900ce20da4fabbceef1689528bc99a21637fae3"
},
{
"path": "references/check-errors.md",
"sha256": "e487c98dea7628b733c0b2d3bf15f455b2c228d0d10af6231498d5a9b272af9f"
},
{
"path": "references/api-widgets.md",
"sha256": "57e8052ea3af6117c92bb83668fbfe257cd5b333873d1e9ce23840f5df7dfd3d"
},
{
"path": "references/export-pdf-tech-object.md",
"sha256": "3918705dff6ee1f96b6534c2e12a9346daf0611d1fc653c4ce0071fde28d770d"
},
{
"path": "references/best-practices-developer.md",
"sha256": "76660939b55ba4b817b054b1d54c6ae4d12df80380394a0be420e28ec35f4ca8"
},
{
"path": "references/timer-tech-object.md",
"sha256": "4f60fcec7ff973673946c75918863b2054d9559c3c09d43d3fe04ae6200f3721"
},
{
"path": "references/bookmark-settings.md",
"sha256": "3e0b0a4d085c84bdeea9e16c385fd5a3e5544b7f900d288de126f221428071c6"
},
{
"path": "references/api-datasource.md",
"sha256": "d5b8762df1a21ced35c839cc49f368ccb79d61fcd8ce9827e23af4a2a1f4626b"
},
{
"path": "references/time-series-forecast.md",
"sha256": "caa875abc2916eff0770dece47812a8e7b020aecb2434b5e529641f19a330ef8"
},
{
"path": "references/analytics-designer-restrictions.md",
"sha256": "6667eb372ed56274e2dad386dceb67df7a3a965d7f715d230f61e35874bbfb52"
},
{
"path": "references/explorer-smart-insights.md",
"sha256": "56e1307eb1fcaac0bb13cd819b6ccc5284591aeaf8f2fa3ec444899f3f4b98f6"
},
{
"path": "references/composites-scripting.md",
"sha256": "90a72b0bb44bb097ae5465b4af3b01b7952c0586dca2ce137de55fc4d6d45265"
},
{
"path": "references/comments.md",
"sha256": "dfefa8cdd0c00d70e2aeba5473e85082a7e0ff22a6a7bef98fc28fb592d76508"
},
{
"path": "references/data-actions-tech-object.md",
"sha256": "a2d0ab94ac79914e066e9309e7b7a56c2c37dcce52c19bccf32cde93b5086264"
},
{
"path": "references/best-practices-planning-stories.md",
"sha256": "90908818a83ce5dcee109d8b2377acb536a7671bfa82b0b9a5dd512daec48a3b"
},
{
"path": "references/text-pool-tech-object.md",
"sha256": "369a8e18369b80e60c5fb786b67d22922abd2f52195f0f93d73d66b60043b784"
},
{
"path": "references/debugging-browser-tools.md",
"sha256": "9d5fd4e1102b3cf87b64fd322d0c6236ff9dc0ffdfec56aca06c9e9e3db2d044"
},
{
"path": "references/export-ppt-tech-object.md",
"sha256": "82c4cf0ca3c4a9106237b7c62fdd52f417f9534a78aa5246e022c280525576c1"
},
{
"path": "references/script-objects.md",
"sha256": "5ca12ef7f5d5773407fa8c17d7e81a9d16625c67ba0466b007cc4d45023fc9c4"
},
{
"path": "references/script-variables.md",
"sha256": "58f4da2bb61fe177affa8508a46908aaff97be4dca7adb38decf9bc34b4d50f1"
},
{
"path": "references/geomap.md",
"sha256": "d71c67dc9bbd200017288129b3d95fe80b63cd99c7f9e73c0542d40a51f85d82"
},
{
"path": "references/bind-widget-values.md",
"sha256": "f16d5503c1f3d30227e86a1c3350338650a56da42d239e155cd772d83ad6aacc"
},
{
"path": "references/calendar-integration-tech-object.md",
"sha256": "8f1c0335f71ed95605e726250fc6c1faec05485fd18839407fc8aa0d1dcde30f"
},
{
"path": "references/multi-actions-tech-object.md",
"sha256": "d8fe2b8ad0ea68f02506b51c134cb4d23507ba46cf95e2ee09abcd929c8bbb9a"
},
{
"path": "references/input-fields.md",
"sha256": "58994c479d2b3f30ce4a5349d2f7bb84ae2f976e6032d8f12f897a1a83360fe0"
},
{
"path": "references/memberinfo-performance.md",
"sha256": "b33fc8b80b551fea4301a6782f05b90a5503c6a8225c89005f6de7b5a6bca3b1"
},
{
"path": "references/scripting-language-fundamentals.md",
"sha256": "5eb5cae858c366a98fd1ba76991f967f4c629c46999e9d39846ec8dcf709c153"
},
{
"path": "references/script-performance-popup.md",
"sha256": "6e05ee402f9f6d7254d375138f18852192fba913695f979759c2066139d0dd8d"
},
{
"path": "references/smart-grouping.md",
"sha256": "f89c599c8791df0b6ed2d238e59f0be2d25715b008925f161ba2d9fc0d77c63f"
},
{
"path": "references/check-references.md",
"sha256": "4a9fba587f08e71f74c881c8ce2ec22cd243ffe58b6341d0026087b68dbee4f8"
},
{
"path": "references/odata-service.md",
"sha256": "19fa4a81a93e8f7d93166ee41095d2c9c1debf5b64fc542b2efc0bca6f9b0723"
},
{
"path": "references/pattern-functions.md",
"sha256": "9447795a591b7eba9ea2d5b87c3f59e232de6c5121cf0a1c746b346d1539a7ba"
},
{
"path": "references/blending-limitations.md",
"sha256": "903015983ae955f9de2b4e837b9046de13fd30a4f8ee8b39cba91fc9e17937dd"
},
{
"path": "references/iframe-embedding-lumira-migration.md",
"sha256": "48909291129e096afb9eebf1b28a71c9e27ac4206a78d450e55a373f6eca2782"
},
{
"path": "references/search-to-insight.md",
"sha256": "c304ee533f96517a57d7d19f2d2e71557565ee8331f6939b7f269b8151326e3a"
},
{
"path": "references/text-widget.md",
"sha256": "b082e3da444d221749742acd270195189e00357f72b2fd3235554656cdf9f250"
},
{
"path": "references/sliders.md",
"sha256": "68fd8b32037d733d17ce354831d2f8584517e54dae97c7dfe69f7b900d7a9f04"
},
{
"path": "references/optimize-type-libraries.md",
"sha256": "919523edc20416f1bf1bdfc22b74b8dfeb2c90f213ef9a559b6a3831097b77f5"
},
{
"path": "references/composites-restrictions.md",
"sha256": "64ca5585d7c8f9ae35dd72cbe493253fe049c5ef93189675a1a43c230d70c5d4"
},
{
"path": "references/api-application.md",
"sha256": "218966e292962e0132784fb7c9377561d0220312f048851fe56996c4c2cec2ea"
},
{
"path": "references/planning-model-tech-object.md",
"sha256": "8f76a9778c943c44d40055967ea2b7fcaafdb06e3f141015dbebb0bb1ba82502"
},
{
"path": "references/data-change-insights-tech-object.md",
"sha256": "151f1a8ce734f63c3272f04fc29afa4cc3b22c1788515e390ddf52a4190b1c81"
},
{
"path": "references/bookmark-set-tech-object.md",
"sha256": "ada614792fa385586cd7cb68a351484dd0317d795b408a5e33cb40f338530b91"
},
{
"path": "references/api-data-operations.md",
"sha256": "45d1f1f8d1ca3792ea5a03066583e7018b2775d62bbb4a3455c2b585aa9ba086"
},
{
"path": "references/automatic-refactoring.md",
"sha256": "7b3d6c51ec6d52d691f516e99c940296f120e1f01deea3d0ae54d0a694d7b4d3"
},
{
"path": ".claude-plugin/plugin.json",
"sha256": "95f723e8587fe25271b158e42ed473dd622bf20e89e28b90005c67514c6e0c8b"
},
{
"path": "templates/common-patterns.js",
"sha256": "156cb76b683b9ff3afb4936d2f645f4e2c9a1a425eeaa2cb49d9f34eddd05d75"
},
{
"path": "templates/planning-operations.js",
"sha256": "f28248ac3c5892daeda6ca3b04265d86a54c6a23b1784dbb6fa639648b684a25"
}
],
"dirSha256": "b77479b2b547697e4c4dc8773010758843abbc7001077ae06dd5c018bf891fcb"
},
"security": {
"scannedAt": null,
"scannerVersion": null,
"flags": []
}
}

View File

@@ -0,0 +1,52 @@
# Analytics Designer Restrictions (2025.23)
Source: `help-portal-5128c44bafd04e19a4e1ee352ee1f14e.md` (Known Restrictions in Analytics Designer).
## Popup limitations
- Popups larger than main canvas render poorly; add ≥2 widgets for proper display.
- Height/width settings fail with a single widget.
- Filter line widgets in popups lose source reference after reload; click each popup to reactivate.
- `setTheme()` does not affect popups directly; place widgets in a panel and set theme there.
- New theme settings require previewing each popup during design.
- `enter`/`backspace` keys fail on multiple tables with Optimized Presentation; avoid that option.
- Planning mass entry, distribute values, value locks, version history unsupported in popups—place table on canvas/panel and toggle visibility.
## Model API limits
- `PlanningModel.createMembers()` works only for generic dimensions (not Date).
- Version management APIs unsupported for BPC writeback-enabled versions; use UI publish/version tools instead.
## DataSource function limits
- `setVariableValue`: no runtime/design-time validation; unsupported combos can error.
- `setDimensionFilter`: ranges on fiscal hierarchies unsupported; users may still change filters in tables even if selection mode locked; runtime filter selection follows design-time selection mode.
## Smart Discovery
- Cannot run after SmartDiscoveryAPIs without specifying Entity (pre-2021.1 versions).
## Styling limits
- Explorer quick menus not controlled via styling panel (Filter/Exclude, Drill, Smart Insight remain).
- Tables: “Restore to Theme Preference” fails after styling changes; default theme not reapplied.
## Export limits
- Cannot save a theme as new after deletion.
- Navigation panel: changing sort order unsupported for SAC models on SAP HANA.
- PDF export: garbled text possible for CJK/Russian/Arabic; CSS partially applied; custom widgets may be incomplete; only visible widgets in containers export (no scrolled content).
- Number format APIs do not affect tooltip measures (only axis measures).
## Chart API limits
- `addMeasure`/`removeMeasure` feeds supported only: `Feed.ValueAxis`, `Feed.ValueAxis2`, `Feed.Color`, `Feed.bubbleWidth`.
- `onSelect` event unsupported for cluster bubble charts.
## Browser limits
- Safari: R visualizations with HTML blocked when 3rd-party cookies blocked; Request Desktop Website not supported for analytic apps on mobile Safari.
## Embedding
- Embedding analytic apps into stories/digital boardroom via web page widget not officially supported.
## Input control limits
- Only dimension member input controls; calculation input controls limited to restricted measures.
- Dynamic forecast: calculation input controls for version/cut-over date in forecast tables unsupported.
- Display: after `setSelectedMembers()` control may show member ID when collapsed/all members selected/hierarchy flat.
## Implementation notes
- Use provided workarounds where possible; verify restricted features in target browser/device.

View File

@@ -0,0 +1,793 @@
# Advanced Widgets & Layout API Reference
Complete reference for Container Widgets, Layout APIs, R Visualizations, Custom Widgets, and Navigation in SAP Analytics Cloud.
**Source**: [Analytics Designer API Reference 2025.14](https://help.sap.com/doc/958d4c11261f42e992e8d01a4c0dde25/release/en-US/index.html)
---
## Table of Contents
1. [Container Widgets](#container-widgets)
2. [Layout API](#layout-api)
3. [Navigation API](#navigation-api)
4. [R Visualization](#r-visualization)
5. [Custom Widgets](#custom-widgets)
6. [Script Objects](#script-objects)
7. [Technical Objects](#technical-objects)
---
## Container Widgets
SAC provides five container widget types for organizing content.
### Panel
Basic container for grouping widgets.
```javascript
// Show/hide panel
Panel_1.setVisible(true);
Panel_1.setVisible(false);
// Check visibility
var isVisible = Panel_1.isVisible();
```
**Characteristics**:
- Groups widgets together
- Move, copy, delete affects all contained widgets
- No navigation functionality
- Cannot execute scripts on panel click directly
### TabStrip
Container with tabbed navigation.
```javascript
// Get active tab
var activeTab = TabStrip_1.getSelectedTab();
// Set active tab
TabStrip_1.setSelectedTab("Tab_Sales");
// Get all tabs
var tabs = TabStrip_1.getTabs();
```
**Events**:
```javascript
TabStrip_1.onSelect = function() {
var selectedTab = TabStrip_1.getSelectedTab();
console.log("Switched to tab:", selectedTab);
// Refresh data for newly visible tab
if (selectedTab === "Tab_Details") {
Table_Details.getDataSource().refreshData();
}
};
```
### PageBook
Container for page-based navigation.
```javascript
// Navigate to page
PageBook_1.setSelectedPage("Page_Dashboard");
// Get current page
var currentPage = PageBook_1.getSelectedPage();
// Navigate by index
PageBook_1.setSelectedPageIndex(0); // First page
```
**Page Navigation Pattern**:
```javascript
// Navigation buttons
Button_Next.onClick = function() {
var pages = PageBook_1.getPages();
var currentIndex = pages.indexOf(PageBook_1.getSelectedPage());
if (currentIndex < pages.length - 1) {
PageBook_1.setSelectedPageIndex(currentIndex + 1);
}
};
Button_Previous.onClick = function() {
var pages = PageBook_1.getPages();
var currentIndex = pages.indexOf(PageBook_1.getSelectedPage());
if (currentIndex > 0) {
PageBook_1.setSelectedPageIndex(currentIndex - 1);
}
};
```
### Flow Layout Panel
Responsive container with automatic reflow.
```javascript
// Same visibility controls as Panel
FlowLayoutPanel_1.setVisible(true);
```
**Characteristics**:
- Widgets placed sequentially (not freely positioned)
- Automatic reflow on resize
- Responsive behavior (widgets wrap to next row)
- Similar to responsive lanes
**Use Case**: Create responsive layouts that adapt to screen size.
### Popup
Overlay container that appears above content.
```javascript
// Open popup
Popup_1.open();
// Close popup
Popup_1.close();
```
**Events**:
```javascript
Popup_1.onOpen = function() {
// Initialize popup content
refreshPopupData();
};
Popup_1.onClose = function() {
// Cleanup
clearPopupSelections();
};
```
**Dialog Mode**: Enable "Header & Footer" in Builder for dialog style with title bar and buttons.
---
## Layout API
Dynamically control widget size and position.
### Position Methods
```javascript
// Get position (pixels from edge)
var left = Widget_1.getLeft();
var top = Widget_1.getTop();
var right = Widget_1.getRight();
var bottom = Widget_1.getBottom();
// Set position
Widget_1.setLeft(100);
Widget_1.setTop(50);
Widget_1.setRight(200);
Widget_1.setBottom(100);
```
### Size Methods
```javascript
// Get dimensions
var width = Widget_1.getWidth();
var height = Widget_1.getHeight();
// Set dimensions (pixels or percentage)
Widget_1.setWidth(500); // 500 pixels
Widget_1.setHeight(300); // 300 pixels
// Percentage-based
Widget_1.setWidth("50%");
Widget_1.setHeight("auto");
```
### Responsive Layout Pattern
```javascript
Application.onResize = function(windowWidth, windowHeight) {
// Mobile layout
if (windowWidth < 768) {
Chart_1.setWidth("100%");
Chart_1.setHeight(200);
Table_1.setWidth("100%");
Panel_Sidebar.setVisible(false);
}
// Tablet layout
else if (windowWidth < 1024) {
Chart_1.setWidth("50%");
Chart_1.setHeight(300);
Table_1.setWidth("50%");
Panel_Sidebar.setVisible(true);
}
// Desktop layout
else {
Chart_1.setWidth("70%");
Chart_1.setHeight(400);
Table_1.setWidth("30%");
Panel_Sidebar.setVisible(true);
}
};
```
### Collapsible Panel Pattern
```javascript
var isPanelExpanded = true;
Button_TogglePanel.onClick = function() {
isPanelExpanded = !isPanelExpanded;
if (isPanelExpanded) {
Panel_Navigation.setWidth(250);
Panel_Content.setLeft(260);
Button_TogglePanel.setText("◀");
} else {
Panel_Navigation.setWidth(50);
Panel_Content.setLeft(60);
Button_TogglePanel.setText("▶");
}
};
```
---
## Navigation API
Navigate between applications and stories.
### NavigationUtils
```javascript
// Open another application
NavigationUtils.openApplication("APP_ID", {
mode: ApplicationMode.View,
newWindow: true
});
// Open a story
NavigationUtils.openStory("STORY_ID", {
newWindow: false
});
// Open URL
NavigationUtils.openUrl("[https://example.com",](https://example.com",) true); // true = new window
```
### Create Application URL with Parameters
```javascript
var url = NavigationUtils.createApplicationUrl("APP_ID", {
p_year: "2024",
p_region: "EMEA",
bookmarkId: "DEFAULT"
});
console.log("Share URL:", url);
```
### Page Navigation (Within Application)
For applications with multiple pages:
```javascript
// Switch to page
Application.setActivePage("Page_Details");
// Get current page
var currentPage = Application.getActivePage();
```
### Custom Navigation Menu Pattern
```javascript
// Navigation sidebar buttons
Button_Dashboard.onClick = function() {
highlightNavButton(Button_Dashboard);
PageBook_1.setSelectedPage("Page_Dashboard");
};
Button_Analysis.onClick = function() {
highlightNavButton(Button_Analysis);
PageBook_1.setSelectedPage("Page_Analysis");
};
Button_Settings.onClick = function() {
highlightNavButton(Button_Settings);
PageBook_1.setSelectedPage("Page_Settings");
};
// Helper function
function highlightNavButton(activeButton) {
var navButtons = [Button_Dashboard, Button_Analysis, Button_Settings];
navButtons.forEach(function(btn) {
if (btn === activeButton) {
btn.setCssClass("nav-button-active");
} else {
btn.setCssClass("nav-button");
}
});
}
```
### Cross-Story Navigation
```javascript
// Navigate to story with filters
Button_DrillToStory.onClick = function() {
var selections = Table_1.getSelections();
if (selections.length > 0) {
var productId = selections[0]["Product"];
NavigationUtils.openStory("DETAIL_STORY_ID", {
newWindow: false,
urlParameters: {
p_product: productId,
p_year: Dropdown_Year.getSelectedKey()
}
});
}
};
```
---
## R Visualization
R widgets allow custom visualizations using R scripts.
### Adding R Visualization
1. Insert → R Visualization in Story
2. Configure Input Data (data source binding)
3. Write R script in Script Editor
### R Widget Configuration
**Input Data**: Binds SAC data to R variables
**R Script**: Executes R code on the input data
### Common R Libraries
```r
# Available libraries include:
library(ggplot2) # Data visualization
library(plotly) # Interactive charts
library(dplyr) # Data manipulation
library(tidyr) # Data tidying
library(scales) # Scale functions
```
### Basic R Chart Example
```r
# Data comes from SAC as data frame
# 'data' is the input data variable
library(ggplot2)
# Create bar chart
ggplot(data, aes(x=Category, y=Value, fill=Region)) +
geom_bar(stat="identity", position="dodge") +
theme_minimal() +
labs(title="Sales by Category and Region")
```
### Interactive Plotly Example
```r
library(plotly)
# Create interactive chart
plot_ly(data,
x = ~Month,
y = ~Revenue,
type = 'scatter',
mode = 'lines+markers',
color = ~Product) %>%
layout(title = "Monthly Revenue Trend")
```
### Animated R Visualization
```r
library(gganimate)
library(ggplot2)
# Animated chart
p <- ggplot(data, aes(x=Year, y=Value, color=Category)) +
geom_point(size=5) +
transition_time(Year) +
labs(title = "Year: {frame_time}")
animate(p, nframes=50)
```
### R Visualization Limitations
- No direct script interaction with SAC widgets
- Input data must be pre-filtered via data source binding
- Cannot call R scripts programmatically from SAC script
### Use Cases
- Specialized statistical visualizations
- Animated charts
- Complex data science visualizations
- Charts not available in standard SAC library
---
## Custom Widgets
Extend SAC with custom web components.
### Custom Widget Structure
```
my-widget/
├── widget.json # Metadata and configuration
├── widget.js # JavaScript implementation
├── widget.css # Optional styling
└── assets/ # Optional images/resources
```
### widget.json Structure
```json
{
"id": "com.company.mywidget",
"version": "1.0.0",
"name": "My Custom Widget",
"description": "A custom widget for SAC",
"icon": "data:image/png;base64,...",
"webComponent": {
"tagName": "my-custom-widget",
"entryPoint": "widget.js"
},
"properties": {
"title": {
"type": "string",
"default": "Widget Title"
},
"threshold": {
"type": "number",
"default": 100
}
},
"methods": {
"refresh": {
"returnType": "void",
"description": "Refreshes the widget"
}
},
"events": {
"onClick": {
"description": "Fires when widget is clicked"
}
}
}
```
### widget.js Structure
```javascript
(function() {
let template = document.createElement("template");
template.innerHTML = `
<style>
.container { padding: 10px; }
.title { font-weight: bold; }
</style>
<div class="container">
<div class="title"></div>
<div class="content"></div>
</div>
`;
class MyWidget extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" });
this.shadowRoot.appendChild(template.content.cloneNode(true));
this._props = {};
}
// Lifecycle: widget added to DOM
connectedCallback() {
this.render();
}
// Called when properties change
onCustomWidgetBeforeUpdate(changedProps) {
this._props = { ...this._props, ...changedProps };
}
onCustomWidgetAfterUpdate(changedProps) {
this.render();
}
// Called when widget is resized
onCustomWidgetResize(width, height) {
// Handle resize
}
// Called when widget is removed
onCustomWidgetDestroy() {
// Cleanup
}
render() {
let titleEl = this.shadowRoot.querySelector(".title");
titleEl.textContent = this._props.title || "";
}
// Custom method (callable from SAC script)
refresh() {
this.render();
}
}
customElements.define("my-custom-widget", MyWidget);
})();
```
### Using Custom Widgets in SAC
```javascript
// Access custom widget
CustomWidget_1.setTitle("New Title");
// Call custom method
CustomWidget_1.refresh();
// Handle custom event
CustomWidget_1.onClick = function() {
// Widget was clicked
Application.showMessage(ApplicationMessageType.Info, "Widget clicked!");
};
```
### Custom Widget with Data Binding
```javascript
// In custom widget (supports Linked Analysis)
this.dataBindings
.getDataBinding()
.getLinkedAnalysis()
.setFilters(selection);
```
### Timer Custom Widget Example
```javascript
// TimeCountdown widget pattern
class TimeCountdownWidget extends HTMLElement {
constructor() {
super();
this._endDate = new Date();
this._timer = null;
}
connectedCallback() {
this.startCountdown();
}
startCountdown() {
this._timer = setInterval(() => {
this.updateDisplay();
}, 1000);
}
updateDisplay() {
const now = new Date();
const diff = this._endDate - now;
if (diff <= 0) {
clearInterval(this._timer);
this.innerHTML = "Time's up!";
return;
}
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((diff % (1000 * 60)) / 1000);
this.innerHTML = `${days}d ${hours}h ${minutes}m ${seconds}s`;
}
}
```
---
## Script Objects
Reusable function containers.
### Creating Script Objects
1. Outline panel → Script Objects → (+)
2. Name the script object (e.g., `Utils`)
3. Add functions
### Script Object Functions
```javascript
// In ScriptObject: Utils
// Function: formatCurrency
function formatCurrency(value, currency) {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: currency || 'USD'
}).format(value);
}
// Function: getQuarter
function getQuarter(date) {
var month = date.getMonth();
return Math.floor(month / 3) + 1;
}
// Function: applyStandardFilters
function applyStandardFilters(dataSource, year, region) {
dataSource.setRefreshPaused(true);
dataSource.setDimensionFilter("Year", year);
dataSource.setDimensionFilter("Region", region);
dataSource.setRefreshPaused(false);
}
```
### Calling Script Object Functions
```javascript
// From any event handler
var formatted = Utils.formatCurrency(1234.56, "EUR");
// Returns: "€1,234.56"
var quarter = Utils.getQuarter(new Date());
// Returns: 1-4
Utils.applyStandardFilters(
Chart_1.getDataSource(),
"2024",
"EMEA"
);
```
---
## Technical Objects
Special objects for advanced functionality.
### Available Technical Objects
| Object | Description |
|--------|-------------|
| BookmarkSet | Save/load application state |
| Timer | Scheduled script execution |
| Calendar | Calendar integration |
| DataAction | Execute data actions |
| MultiAction | Execute multiple actions |
| ScriptVariable | Application-wide variables |
### Adding Technical Objects
1. Outline panel → Technical Objects
2. Click (+) next to desired object type
3. Configure in Builder panel
### Global Script Variables
Create via Outline → Script Variables:
```javascript
// Access global variable
var currentYear = GlobalYear;
// Set global variable
GlobalYear = "2024";
// URL parameter (prefix with p_)
// URL: ?p_Year=2024
// Variable name: p_Year
var urlYear = p_Year;
```
---
## Complete Example: Custom Dashboard Layout
```javascript
// Initialize responsive layout
Application.onInitialization = function() {
// Set initial layout based on window size
adjustLayout(window.innerWidth);
};
Application.onResize = function(width, height) {
adjustLayout(width);
};
function adjustLayout(width) {
// Mobile
if (width < 768) {
setMobileLayout();
}
// Tablet
else if (width < 1200) {
setTabletLayout();
}
// Desktop
else {
setDesktopLayout();
}
}
function setMobileLayout() {
Panel_Sidebar.setVisible(false);
Panel_Main.setWidth("100%");
Panel_Main.setLeft(0);
TabStrip_Charts.setSelectedTab("Tab_Summary");
Chart_Detail.setVisible(false);
}
function setTabletLayout() {
Panel_Sidebar.setVisible(true);
Panel_Sidebar.setWidth(200);
Panel_Main.setWidth("calc(100% - 210px)");
Panel_Main.setLeft(210);
Chart_Detail.setVisible(true);
Chart_Detail.setWidth("100%");
}
function setDesktopLayout() {
Panel_Sidebar.setVisible(true);
Panel_Sidebar.setWidth(250);
Panel_Main.setWidth("calc(100% - 260px)");
Panel_Main.setLeft(260);
Chart_Detail.setVisible(true);
Chart_Detail.setWidth("50%");
}
// Navigation
Button_Dashboard.onClick = function() {
PageBook_1.setSelectedPage("Page_Dashboard");
Utils.highlightNavButton("Dashboard");
};
Button_Analysis.onClick = function() {
PageBook_1.setSelectedPage("Page_Analysis");
Utils.highlightNavButton("Analysis");
};
Button_Export.onClick = function() {
Popup_Export.open();
};
```
---
## Related Documentation
- [DataSource API](api-datasource.md)
- [Widgets API](api-widgets.md)
- [Planning API](api-planning.md)
- [Calendar & Bookmarks API](api-calendar-bookmarks.md)
**Official References**:
- [Analytics Designer API Reference 2025.14](https://help.sap.com/doc/958d4c11261f42e992e8d01a4c0dde25/release/en-US/index.html)
- [Custom Widget Developer Guide](https://help.sap.com/doc/c813a28922b54e50bd2a307b099787dc/release/en-US/CustomWidgetDevGuide_en.pdf)

View File

@@ -0,0 +1,630 @@
# Application API Reference
Complete reference for the Application object and utility APIs in SAP Analytics Cloud scripting.
**Source**: [Analytics Designer API Reference 2025.14](https://help.sap.com/doc/958d4c11261f42e992e8d01a4c0dde25/release/en-US/index.html)
---
## Table of Contents
1. [Application Object](#application-object)
2. [User Feedback](#user-feedback)
3. [Application Information](#application-information)
4. [Navigation](#navigation)
5. [Export Functions](#export-functions)
6. [Theme and Styling](#theme-and-styling)
7. [Application Events](#application-events)
8. [Utility APIs](#utility-apis)
9. [Enumerations](#enumerations)
---
## Application Object
The global `Application` object provides access to application-level functionality.
```javascript
// Always available - no need to get reference
Application.showBusyIndicator();
```
---
## User Feedback
### showBusyIndicator()
Shows loading indicator overlay.
```javascript
Application.showBusyIndicator();
// Perform long operation
await someAsyncOperation();
Application.hideBusyIndicator();
```
### hideBusyIndicator()
Hides loading indicator.
```javascript
Application.hideBusyIndicator();
```
### showMessage(type, message)
Displays message to user.
**Parameters**:
| Parameter | Type | Description |
|-----------|------|-------------|
| type | ApplicationMessageType | Message type |
| message | string | Message text |
```javascript
// Info message
Application.showMessage(ApplicationMessageType.Info, "Operation completed");
// Success message
Application.showMessage(ApplicationMessageType.Success, "Data saved");
// Warning message
Application.showMessage(ApplicationMessageType.Warning, "Unsaved changes");
// Error message
Application.showMessage(ApplicationMessageType.Error, "Operation failed");
```
### Confirm Dialog Pattern
```javascript
// Use popup/dialog for confirmations
// Create Popup_Confirm with Yes/No buttons
Button_Delete.onClick = function() {
Popup_Confirm.open();
};
Button_Yes.onClick = function() {
// Perform action
performDelete();
Popup_Confirm.close();
};
Button_No.onClick = function() {
Popup_Confirm.close();
};
```
---
## Application Information
### getInfo()
Returns application metadata.
```javascript
var info = Application.getInfo();
// Returns: ApplicationInfo object
// - id: string
// - name: string
// - description: string
```
### getUserInfo()
Returns current user information.
```javascript
var user = Application.getUserInfo();
// Returns: UserInfo object
// - id: string
// - displayName: string
// - email: string
```
**Example**:
```javascript
var user = Application.getUserInfo();
Text_Welcome.setText("Welcome, " + user.displayName);
```
### getRolesInfo()
Returns user roles.
```javascript
var roles = Application.getRolesInfo();
```
### getMode()
Returns current application mode.
```javascript
var mode = Application.getMode();
// Returns: ApplicationMode enum value
// - ApplicationMode.View
// - ApplicationMode.Present
// - ApplicationMode.Embed
```
**Example: Conditional Display**:
```javascript
if (Application.getMode() === ApplicationMode.Present) {
Button_Edit.setVisible(false);
}
```
---
## Navigation
### NavigationUtils
Navigate to other applications or stories.
#### openApplication(appId, options)
Opens another analytic application.
```javascript
NavigationUtils.openApplication("APP_ID", {
mode: ApplicationMode.View,
newWindow: true
});
```
#### openStory(storyId, options)
Opens a story.
```javascript
NavigationUtils.openStory("STORY_ID", {
newWindow: false
});
```
#### createApplicationUrl(appId, parameters)
Creates URL for an application with parameters.
```javascript
var url = NavigationUtils.createApplicationUrl("APP_ID", {
p_year: "2024",
p_region: "EMEA"
});
// Returns: URL string with encoded parameters
```
### Page Navigation (Within Application)
```javascript
// Get active page
var page = Application.getActivePage();
// Set active page
Application.setActivePage("Page_Detail");
```
### Tab Navigation
```javascript
// Switch tab
TabStrip_1.setSelectedTab("Tab_Chart");
```
---
## Export Functions
### Export PDF
```javascript
Application.export(ExportType.PDF, {
scope: ExportScope.All,
header: "Sales Report",
footer: "Confidential - Page {page}",
orientation: "landscape",
paperSize: "A4"
});
```
### Export Excel
```javascript
Application.export(ExportType.Excel, {
scope: ExportScope.All,
includeHierarchy: true,
fileName: "SalesData"
});
```
### Export CSV (Widget Level)
```javascript
Table_1.export(ExportType.CSV, {
fileName: "TableExport"
});
```
### Export PowerPoint
```javascript
Application.export(ExportType.PowerPoint, {
scope: ExportScope.All,
header: "Quarterly Review"
});
```
---
## Theme and Styling
### getTheme()
Returns current theme.
```javascript
var theme = Application.getTheme();
// Returns: "sap_fiori_3" | "sap_fiori_3_dark" | etc.
```
### setTheme(themeId)
Sets application theme.
```javascript
Application.setTheme("sap_fiori_3_dark");
```
### getCssClass()
Returns application CSS class.
```javascript
var cssClass = Application.getCssClass();
```
### setCssClass(className)
Sets application CSS class.
```javascript
Application.setCssClass("myCustomTheme");
```
---
## Application Events
### onInitialization
Fires once when application loads.
```javascript
Application.onInitialization = function() {
// Initialize application state
// BEST PRACTICE: Keep this empty for performance
};
```
**Best Practice**: Avoid heavy operations. Use:
- URL parameters for initial values
- Static widget filters
- Background loading for hidden widgets
### onResize
Fires when application is resized.
```javascript
Application.onResize = function(width, height) {
console.log("New size:", width, height);
// Adjust layout
if (width < 768) {
Chart_1.setVisible(false);
Table_1.setVisible(true);
} else {
Chart_1.setVisible(true);
Table_1.setVisible(true);
}
};
```
### onOrientationChange
Fires on mobile when orientation changes.
```javascript
Application.onOrientationChange = function(orientation) {
console.log("Orientation:", orientation);
// "portrait" | "landscape"
};
```
---
## Utility APIs
### ConvertUtils
Type conversion utilities.
```javascript
// String to Integer
var num = ConvertUtils.stringToInteger("42");
// Returns: 42
// Number to String
var str = ConvertUtils.numberToString(42);
// Returns: "42"
// String to Number (with decimals)
var decimal = ConvertUtils.stringToNumber("42.5");
// Returns: 42.5
```
### NumberFormat
Number formatting options.
```javascript
var format = {
decimalPlaces: 2,
scalingFactor: 1000, // Display in thousands
showSign: true,
groupingSeparator: ","
};
```
### DateFormat
Date formatting utilities.
```javascript
// Format date
var formatted = DateFormat.format(dateValue, "yyyy-MM-dd");
// Returns: "2024-01-15"
// Parse date
var parsed = DateFormat.parse("2024-01-15", "yyyy-MM-dd");
```
### StringUtils
String manipulation.
```javascript
// Available utilities for string operations
var trimmed = myString.trim();
var upper = myString.toUpperCase();
var lower = myString.toLowerCase();
```
### ArrayUtils
Array operations.
```javascript
// Standard JavaScript array methods
var arr = [1, 2, 3, 4, 5];
// Filter
var filtered = arr.filter(function(item) {
return item > 2;
});
// Map
var doubled = arr.map(function(item) {
return item * 2;
});
// Find
var found = arr.find(function(item) {
return item === 3;
});
```
---
## Enumerations
### ApplicationMessageType
```javascript
ApplicationMessageType.Info // Informational
ApplicationMessageType.Success // Success/confirmation
ApplicationMessageType.Warning // Warning
ApplicationMessageType.Error // Error
```
### ApplicationMode
```javascript
ApplicationMode.View // Normal view mode
ApplicationMode.Present // Presentation mode
ApplicationMode.Embed // Embedded in another app
```
### ExportType
```javascript
ExportType.PDF // PDF export
ExportType.Excel // Excel export
ExportType.CSV // CSV export
ExportType.PowerPoint // PowerPoint export
```
### ExportScope
```javascript
ExportScope.All // All content
ExportScope.PointOfView // Current point of view only
```
---
## Debugging Helpers
### console.log()
Print debug information.
```javascript
console.log("Debug message");
console.log("Variable:", myVariable);
console.log("Object:", JSON.stringify(myObject));
```
**Access Console**:
1. Run application
2. Press F12 (Developer Tools)
3. Go to Console tab
4. Look in "sandbox.worker.main.*.js"
### Debug Mode
Enable enhanced debugging:
```javascript
// Add to URL
// ?debug=true
// Or at end of existing URL
// ;debug=true
```
### debugger Statement
Pause execution for step-through debugging.
```javascript
debugger; // Execution pauses here
var value = someCalculation();
```
### Performance Logging
```javascript
// Add URL parameter
// ?APP_PERFORMANCE_LOGGING=true
// In console
window.sap.raptr.getEntriesByMarker("(Application)")
.filter(e => e.entryType === 'measure')
.sort((a,b) => (a.startTime + a.duration) - (b.startTime + b.duration));
```
---
## Complete Examples
### Example 1: User-Aware Application
```javascript
Application.onInitialization = function() {
// Get user info
var user = Application.getUserInfo();
Text_Welcome.setText("Welcome, " + user.displayName);
// Check roles
var roles = Application.getRolesInfo();
var isAdmin = roles.some(function(role) {
return role.id === "ADMIN";
});
// Show admin controls if authorized
Panel_AdminControls.setVisible(isAdmin);
};
```
### Example 2: Responsive Layout
```javascript
Application.onResize = function(width, height) {
// Mobile layout (< 768px)
if (width < 768) {
Chart_1.setVisible(false);
Table_1.setVisible(true);
Button_ToggleView.setVisible(true);
}
// Tablet layout (768-1024px)
else if (width < 1024) {
Chart_1.setVisible(true);
Table_1.setVisible(false);
Button_ToggleView.setVisible(true);
}
// Desktop layout (>= 1024px)
else {
Chart_1.setVisible(true);
Table_1.setVisible(true);
Button_ToggleView.setVisible(false);
}
};
```
### Example 3: Export Report
```javascript
Button_ExportPDF.onClick = function() {
var year = Dropdown_Year.getSelectedKey();
var region = Dropdown_Region.getSelectedKey();
Application.export(ExportType.PDF, {
scope: ExportScope.All,
header: "Sales Report - " + year + " - " + region,
footer: "Generated on " + new Date().toLocaleDateString() + " | Page {page}",
orientation: "landscape",
paperSize: "A4"
});
};
Button_ExportExcel.onClick = function() {
Table_1.export(ExportType.Excel, {
fileName: "SalesData_" + new Date().toISOString().split('T')[0]
});
};
```
### Example 4: Navigation with Parameters
```javascript
// Navigate to detail application
Button_ViewDetails.onClick = function() {
var selections = Table_1.getSelections();
if (selections.length > 0) {
var productId = selections[0]["Product"];
var year = Dropdown_Year.getSelectedKey();
NavigationUtils.openApplication("PRODUCT_DETAIL_APP", {
mode: ApplicationMode.View,
newWindow: true,
parameters: {
p_product: productId,
p_year: year
}
});
} else {
Application.showMessage(
ApplicationMessageType.Warning,
"Please select a product first"
);
}
};
```
---
## Related Documentation
- [DataSource API](api-datasource.md)
- [Widgets API](api-widgets.md)
- [Planning API](api-planning.md)
**Official Reference**: [https://help.sap.com/doc/958d4c11261f42e992e8d01a4c0dde25/release/en-US/index.html](https://help.sap.com/doc/958d4c11261f42e992e8d01a4c0dde25/release/en-US/index.html)

View File

@@ -0,0 +1,603 @@
# Calendar, Bookmarks & Advanced Integration API Reference
Complete reference for Calendar integration, Bookmarks, Linked Analysis, and Timer APIs in SAP Analytics Cloud.
**Source**: [Analytics Designer API Reference 2025.14](https://help.sap.com/doc/958d4c11261f42e992e8d01a4c0dde25/release/en-US/index.html)
---
## Table of Contents
1. [Calendar Integration API](#calendar-integration-api)
2. [Bookmarks API](#bookmarks-api)
3. [Linked Analysis API](#linked-analysis-api)
4. [Timer API](#timer-api)
5. [Notification Badges](#notification-badges)
---
## Calendar Integration API
SAC Calendar allows integration with planning workflows, tasks, and processes.
### Getting Calendar Events
#### getCalendarEventById(eventId)
Retrieves an existing calendar event by ID.
```javascript
var event = Calendar.getCalendarEventById("EVENT_ID_STRING");
```
**Note**: The ID is a unique string (like a story/model ID), not the display name.
#### getCurrentEvent()
Returns the event from which the application was started.
```javascript
var currentEvent = Calendar.getCurrentEvent();
if (currentEvent) {
console.log("Started from event:", currentEvent.getName());
}
```
### Calendar Event Methods
Once you have an event reference, these methods are available:
| Method | Description |
|--------|-------------|
| `getId()` | Returns event unique ID |
| `getName()` | Returns event display name |
| `getDescription()` | Returns event description |
| `getStartDate()` | Returns start date |
| `getDueDate()` | Returns due date |
| `getStatus()` | Returns current status |
| `getType()` | Returns event type |
| `getProgress()` | Returns completion percentage |
| `hasUserRole(role)` | Checks if user has specific role |
| `activate()` | Activates the event |
### Calendar Composite Task
For composite tasks (multi-step workflows):
#### CalendarCompositeTask Methods
```javascript
// Approval workflow
task.approve(); // Approve task (requires Reviewer role)
task.reject(); // Reject task
task.submit(); // Submit for review
task.decline(); // Decline task
// Check permissions
task.canUserApprove();
task.canUserReject();
task.canUserSubmit();
task.canUserDecline();
// Reviewer management
task.addReviewer(userId);
task.removeReviewer(userId);
```
### Creating Calendar Events
#### Create Process
```javascript
var processProperties = {
name: "Q4 Planning",
startDate: new Date(2024, 9, 1), // October 1, 2024
endDate: new Date(2024, 11, 31), // December 31, 2024
description: "Q4 Budget Planning Process",
owners: ["user1@company.com"],
assignees: ["user2@company.com", "user3@company.com"]
};
var newProcess = Calendar.createProcess(processProperties);
```
#### Create Task
```javascript
var taskProperties = {
name: "Review Budget",
startDate: new Date(2024, 10, 1),
dueDate: new Date(2024, 10, 15),
description: "Review Q4 budget allocations",
assignees: ["reviewer@company.com"],
parentId: parentProcessId,
reminders: [
{ daysBeforeDue: 3 },
{ daysBeforeDue: 1 }
]
};
var newTask = Calendar.createTask(taskProperties);
```
### CalendarCompositeTaskCreateProperties
Full list of properties when creating tasks:
| Property | Type | Description |
|----------|------|-------------|
| name | string | Task name (required) |
| startDate | Date | Start date (required) |
| endDate | Date | End date (required) |
| dueDate | Date | Due date |
| description | string | Task description |
| owners | string[] | Owner user IDs |
| assignees | string[] | Assignee user IDs |
| reviewers | string[] | Reviewer user IDs |
| parentId | string | Parent process ID |
| dependencies | string[] | Dependent task IDs |
| contextFilters | object | Context filter settings |
| reminders | object[] | Reminder configurations |
| workFiles | string[] | Attached work file IDs |
### Calendar Event Status
```javascript
CalendarTaskStatus.Open // Not started
CalendarTaskStatus.InProgress // In progress
CalendarTaskStatus.Successful // Completed successfully
CalendarTaskStatus.Failed // Failed
CalendarTaskStatus.Canceled // Canceled
```
### Reminder Configuration
```javascript
// Add reminder to event
event.addReminder({
daysBeforeDue: 5,
notificationType: "Email"
});
```
### Bulk Operations Example
```javascript
// Bulk modify context filters on calendar events
function updateEventFilters(eventIds, newFilter) {
eventIds.forEach(function(eventId) {
var event = Calendar.getCalendarEventById(eventId);
if (event) {
event.setContextFilter("Year", newFilter);
}
});
}
```
---
## Bookmarks API
Bookmarks save application state for later restoration.
### What Bookmarks Save
- Dimension and measure selections in Tables/Charts
- Filter states
- Hierarchical levels
- Dropdown/Radio button/Checkbox selections
- Script Variables (primitive types: string, boolean, integer, number)
- Widget visibility states
- Sorting configurations
**Not Saved**: Non-primitive type script variables (complex objects)
### BookmarkSet Technical Object
Add a BookmarkSet technical object to enable bookmark functionality.
#### saveBookmark(options)
Saves current application state as a bookmark.
```javascript
BookmarkSet_1.saveBookmark({
name: "Q4 Analysis View",
isGlobal: true // false for personal bookmark
});
```
#### getAll()
Returns all available bookmarks.
```javascript
var bookmarks = BookmarkSet_1.getAll();
bookmarks.forEach(function(bookmark) {
console.log(bookmark.id + ": " + bookmark.name);
});
```
#### apply(bookmarkId)
Applies a saved bookmark.
```javascript
// Apply by ID
Bookmarks.apply("22305278-9717-4296-8809-298841349359");
// Or from bookmark info
var selectedBookmark = Dropdown_Bookmarks.getSelectedKey();
BookmarkSet_1.apply(selectedBookmark);
```
#### getAppliedBookmark()
Returns currently applied bookmark info.
```javascript
var currentBookmark = BookmarkSet_1.getAppliedBookmark();
if (currentBookmark) {
console.log("Current bookmark:", currentBookmark.name);
}
```
#### deleteBookmark(bookmarkInfo)
Deletes a bookmark.
```javascript
var bookmarkToDelete = BookmarkSet_1.getAppliedBookmark();
BookmarkSet_1.deleteBookmark(bookmarkToDelete);
```
### URL Parameters for Bookmarks
Bookmarks can be loaded via URL:
```
# Load specific bookmark
?bookmarkId=XXXXXXXXXXX
# Load default bookmark
?bookmarkId=DEFAULT
```
### Bookmark Dropdown Pattern
```javascript
// Populate dropdown with bookmarks
function populateBookmarkDropdown() {
var bookmarks = BookmarkSet_1.getAll();
var items = bookmarks.map(function(b) {
return { key: b.id, text: b.name };
});
Dropdown_Bookmarks.setItems(items);
}
// Apply selected bookmark
Dropdown_Bookmarks.onSelect = function() {
var bookmarkId = Dropdown_Bookmarks.getSelectedKey();
if (bookmarkId) {
BookmarkSet_1.apply(bookmarkId);
}
};
```
### Global vs Personal Bookmarks
| Type | Visibility | Use Case |
|------|-----------|----------|
| Personal | Only creator | Individual analysis views |
| Global | All viewers | Shared scenarios, published views |
---
## Linked Analysis API
Linked Analysis enables cross-widget filtering based on selections.
### In Analytics Applications
Linked Analysis must be implemented via scripting in Analytic Applications (unlike Stories where it's automatic).
### setFilters(selections)
Applies selection filters to linked widgets.
```javascript
// In Chart_1 onSelect event
var selections = Chart_1.getSelections();
if (selections.length > 0) {
// Set filters on linked widgets
LinkedAnalysis.setFilters(selections);
}
```
### removeFilters()
Removes linked analysis filters.
```javascript
LinkedAnalysis.removeFilters();
```
### Custom Linked Analysis Pattern
```javascript
// Create reusable linked analysis function
function applyLinkedAnalysis(sourceWidget, targetWidgets) {
var selections = sourceWidget.getSelections();
if (selections.length === 0) {
// Clear filters on all targets
targetWidgets.forEach(function(widget) {
widget.getDataSource().clearAllFilters();
});
return;
}
// Apply selection to all targets
var selection = selections[0];
targetWidgets.forEach(function(widget) {
var ds = widget.getDataSource();
Object.keys(selection).forEach(function(dimId) {
ds.setDimensionFilter(dimId, selection[dimId]);
});
});
}
// Usage in Chart_1.onSelect
applyLinkedAnalysis(Chart_1, [Table_1, Chart_2, Chart_3]);
```
### With Custom Widgets
Starting from QRC Q3 2023, Custom Widgets support Linked Analysis:
```javascript
// In custom widget code
this.dataBindings
.getDataBinding()
.getLinkedAnalysis()
.setFilters(selection);
// Remove filters
this.dataBindings
.getDataBinding()
.getLinkedAnalysis()
.removeFilters();
```
---
## Timer API
Timer enables scheduled script execution and animations.
### Timer Technical Object
Add a Timer from the Outline panel under Technical Objects.
### Timer Methods
```javascript
// Start timer (interval in milliseconds)
Timer_1.start(1000); // Every 1 second
// Stop timer
Timer_1.stop();
// Check if running
var isRunning = Timer_1.isRunning();
// Get interval
var interval = Timer_1.getInterval();
// Set interval
Timer_1.setInterval(2000); // Change to 2 seconds
```
### Timer Events
```javascript
// onTimeout - fires at each interval
Timer_1.onTimeout = function() {
// Update data or animations
refreshDashboard();
};
```
### Auto-Refresh Pattern
```javascript
// Auto-refresh dashboard every 30 seconds
Timer_Refresh.start(30000);
Timer_Refresh.onTimeout = function() {
Application.showBusyIndicator();
Chart_1.getDataSource().refreshData();
Table_1.getDataSource().refreshData();
Application.hideBusyIndicator();
// Update timestamp
Text_LastRefresh.setText(
"Last updated: " + new Date().toLocaleTimeString()
);
};
// Stop on user interaction
Button_Pause.onClick = function() {
if (Timer_Refresh.isRunning()) {
Timer_Refresh.stop();
Button_Pause.setText("Resume Auto-Refresh");
} else {
Timer_Refresh.start(30000);
Button_Pause.setText("Pause Auto-Refresh");
}
};
```
### Countdown Timer Pattern
```javascript
var countdown = 60; // 60 seconds
Timer_Countdown.start(1000); // 1 second intervals
Timer_Countdown.onTimeout = function() {
countdown--;
Text_Countdown.setText("Time remaining: " + countdown + "s");
if (countdown <= 0) {
Timer_Countdown.stop();
performAction();
}
};
```
### Animation Pattern
```javascript
var currentIndex = 0;
var dataPoints = ["Jan", "Feb", "Mar", "Apr", "May", "Jun"];
Timer_Animation.start(2000); // 2 second intervals
Timer_Animation.onTimeout = function() {
// Highlight next data point
Chart_1.getDataSource().setDimensionFilter("Month", dataPoints[currentIndex]);
currentIndex++;
if (currentIndex >= dataPoints.length) {
currentIndex = 0; // Loop back
}
};
```
---
## Notification Badges
### Application Messages
```javascript
// Display notifications
Application.showMessage(ApplicationMessageType.Info, "Information message");
Application.showMessage(ApplicationMessageType.Success, "Success message");
Application.showMessage(ApplicationMessageType.Warning, "Warning message");
Application.showMessage(ApplicationMessageType.Error, "Error message");
```
### Custom Notification Pattern
```javascript
// Check for alerts and show notification
function checkAlerts() {
var data = Chart_1.getDataSource().getData({
"@MeasureDimension": "[Account].[parentId].&[Revenue]"
});
if (data.rawValue < 10000) {
Application.showMessage(
ApplicationMessageType.Warning,
"Revenue below threshold: " + data.formattedValue
);
// Open alert popup
Popup_Alert.open();
}
}
// Call on data change
Chart_1.onResultChanged = function() {
checkAlerts();
};
```
### Tooltip Patterns
Tooltips appear automatically on chart data points. For custom tooltips:
```javascript
// Use popup as custom tooltip
Chart_1.onSelect = function() {
var selections = Chart_1.getSelections();
if (selections.length > 0) {
// Get detailed data
var details = getDetailedInfo(selections[0]);
// Update tooltip content
Text_TooltipContent.setText(details);
// Position and show popup
Popup_Tooltip.open();
}
};
```
---
## Complete Example: Planning Workflow with Calendar
```javascript
// Initialize from calendar event
Application.onInitialization = function() {
var event = Calendar.getCurrentEvent();
if (event) {
// Set context from calendar event
var year = event.getContextFilter("Year");
var version = event.getContextFilter("Version");
if (year) {
Table_1.getDataSource().setDimensionFilter("Year", year);
}
if (version) {
Table_1.getDataSource().setDimensionFilter("Version", version);
}
// Update title
Text_Title.setText("Planning: " + event.getName());
// Show task info
Text_DueDate.setText("Due: " + event.getDueDate().toLocaleDateString());
}
};
// Submit task
Button_Submit.onClick = function() {
var event = Calendar.getCurrentEvent();
if (event && event.canUserSubmit()) {
// Save data first
Table_1.getPlanning().submitData();
// Submit for review
event.submit();
Application.showMessage(
ApplicationMessageType.Success,
"Task submitted for review"
);
} else {
Application.showMessage(
ApplicationMessageType.Warning,
"Cannot submit task"
);
}
};
```
---
## Related Documentation
- [DataSource API](api-datasource.md)
- [Widgets API](api-widgets.md)
- [Planning API](api-planning.md)
- [Application API](api-application.md)
**Official Reference**: [https://help.sap.com/doc/958d4c11261f42e992e8d01a4c0dde25/release/en-US/index.html](https://help.sap.com/doc/958d4c11261f42e992e8d01a4c0dde25/release/en-US/index.html)

View File

@@ -0,0 +1,526 @@
# SAC Data Operations API Reference
Comprehensive guide for working with data sources, filters, hierarchies, and members in SAP Analytics Cloud scripting.
---
## Table of Contents
1. [Data Sources Overview](#data-sources-overview)
2. [Range and Exclude Filters](#range-and-exclude-filters)
3. [Getting Dimension Filters](#getting-dimension-filters)
4. [Dimension Properties](#dimension-properties)
5. [Hierarchies](#hierarchies)
6. [Getting Members](#getting-members)
7. [DataSource Information](#datasource-information)
8. [Pattern-Based Functions](#pattern-based-functions)
---
## Data Sources Overview
Data sources are accessed through data-bound widgets:
```javascript
var ds = Chart_1.getDataSource();
var ds = Table_1.getDataSource();
```
### Key Method Categories
| Category | Methods |
|----------|---------|
| Filters | `setDimensionFilter()`, `removeDimensionFilter()`, `getDimensionFilters()` |
| Members | `getMembers()`, `getMember()` |
| Dimensions | `getDimensions()`, `getDimensionProperties()` |
| Hierarchies | `setHierarchy()`, `setHierarchyLevel()`, `expandNode()`, `collapseNode()` |
| Data | `getData()`, `getResultSet()`, `refreshData()` |
| Variables | `getVariables()`, `setVariableValue()` |
---
## Range and Exclude Filters
The `DataSource.setDimensionFilter()` method supports both range filters and exclude filters.
### Exclude Filters
Filter out specific members from the drill-down.
**Single Value Include**:
```javascript
// Keep only employee ID 230 in drill-down
DS_1.setDimensionFilter("EMPLOYEE_ID", {value: "230"});
```
**Single Value Exclude**:
```javascript
// Remove employee ID 230 from drill-down
DS_1.setDimensionFilter("EMPLOYEE_ID", {value: "230", exclude: true});
```
**Multiple Values Include**:
```javascript
// Keep employees 230 and 240 in drill-down
DS_1.setDimensionFilter("EMPLOYEE_ID", {values: ["230", "240"]});
```
**Multiple Values Exclude**:
```javascript
// Remove employees 230 and 240 from drill-down
DS_1.setDimensionFilter("EMPLOYEE_ID", {values: ["230", "240"], exclude: true});
```
### Range Filters
Filter ranges of members in the drill-down.
**Important Limitations**:
- Range filters can **only be applied to numeric dimensions**
- A time dimension is **not** a numeric dimension
- SAP BW does **not** support numeric dimensions
**Between Range**:
```javascript
// Keep employees with IDs between 230 and 240
DS_1.setDimensionFilter("EMPLOYEE_ID", {from: "230", to: "240"});
```
**Less Than**:
```javascript
// Keep employees with IDs less than 230
DS_1.setDimensionFilter("EMPLOYEE_ID", {less: "230"});
```
**Less Than or Equal**:
```javascript
// Keep employees with IDs less than or equal to 230
DS_1.setDimensionFilter("EMPLOYEE_ID", {lessOrEqual: "230"});
```
**Greater Than or Equal**:
```javascript
// Keep employees with IDs greater than or equal to 230
DS_1.setDimensionFilter("EMPLOYEE_ID", {greaterOrEqual: "230"});
```
**Greater Than**:
```javascript
// Keep employees with IDs greater than 230
DS_1.setDimensionFilter("EMPLOYEE_ID", {greater: "230"});
```
**Multiple Range Filters**:
```javascript
// Keep employees with IDs less than 230 OR greater than 240
DS_1.setDimensionFilter("EMPLOYEE_ID", [{less: "230"}, {greater: "240"}]);
```
---
## Getting Dimension Filters
Retrieve current filter values with `getDimensionFilters()`.
### Method Signature
```javascript
getDimensionFilters(dimension: string | DimensionInfo): FilterValue[]
```
### Basic Usage
```javascript
var values = Table_1.getDataSource().getDimensionFilters("COUNTRY");
```
### Working with Filter Types
Filter values can be `SingleFilterValue`, `MultipleFilterValue`, or `RangeFilterValue`. Use the `type` property to determine which:
```javascript
var value = Table_1.getDataSource().getDimensionFilters("COUNTRY")[0];
switch (value.type) {
case FilterValueType.Single:
var singleValue = cast(Type.SingleFilterValue, value);
console.log(singleValue.value);
break;
case FilterValueType.Multiple:
var multipleValue = cast(Type.MultipleFilterValue, value);
console.log(multipleValue.values); // Array of values
break;
case FilterValueType.Range:
var rangeValue = cast(Type.RangeFilterValue, value);
console.log(rangeValue.from);
console.log(rangeValue.to);
// Also available: less, lessOrEqual, greater, greaterOrEqual
break;
default:
break;
}
```
### Limitations
- Time range filters are **not** currently returned
- SAP BW may have valid filters not supported by SAP Analytics Cloud
---
## Dimension Properties
Retrieve dimension properties of a data source.
### Method Signature
```javascript
getDimensionProperties(dimension: string | DimensionInfo): DimensionPropertyInfo[]
```
### Usage
```javascript
var properties = Table_1.getDataSource().getDimensionProperties("Location");
for (var i = 0; i < properties.length; i++) {
console.log("Property: " + properties[i].id);
console.log("Description: " + properties[i].description);
}
```
---
## Hierarchies
Set hierarchy levels and expand/collapse nodes.
**Note**: Currently supported only by data sources of **Table** and **Chart** widgets.
### Set Hierarchy Level
```javascript
DataSource.setHierarchyLevel(dimension: string|DimensionInfo, level?: integer): void
DataSource.getHierarchyLevel(dimension: string|DimensionInfo): integer
```
**Chart Example**:
```javascript
var ds = Chart_1.getDataSource();
ds.setHierarchy("Location_4nm2e04531", "State_47acc246_4m5x6u3k6s");
ds.setHierarchyLevel("Location_4nm2e04531", 2);
```
**Table Example**:
```javascript
var ds = Table_1.getDataSource();
ds.setHierarchy("Location_4nm2e04531", "State_47acc246_4m5x6u3k6s");
ds.setHierarchyLevel("Location_4nm2e04531", 2);
```
### Expand/Collapse Hierarchy Nodes
```javascript
DataSource.expandNode(dimension: string|DimensionInfo, selection: Selection): void
DataSource.collapseNode(dimension: string|DimensionInfo, selection: Selection): void
```
**Expand Single Node (Chart)**:
```javascript
// Chart with Location in category axis, hierarchy level 1
// Expand California node
Chart_1.getDataSource().expandNode("Location_4nm2e04531", {
"Location_4nm2e04531": "[Location_4nm2e04531].[State_47acc246_4m5x6u3k6s].&[SA1]",
"@MeasureDimension": "[Account_BestRunJ_sold].[parentId].&[Discount]"
});
```
**Expand All Nodes with Specific Value (Chart)**:
```javascript
// Chart with Location and Product in category axis
// Expand all Alcohol nodes
Chart_1.getDataSource().expandNode("Product_3e315003an", {
"Product_3e315003an": "[Product_3e315003an].[Product_Catego_3o3x5e06y2].&[PC4]",
"@MeasureDimension": "[Account_BestRunJ_sold].[parentId].&[Discount]"
});
```
**Expand Specific Node (Table)**:
```javascript
// Table with Location and Product in rows
// Expand California + Alcohol node
Table_1.getDataSource().expandNode("Location_4nm2e04531", {
"Product_3e315003an": "[Product_3e315003an].[Product_Catego_3o3x5e06y2].&[PC4]",
"Location_4nm2e04531": "[Location_4nm2e04531].[State_47acc246_4m5x6u3k6s].&[SA1]",
"@MeasureDimension": "[Account_BestRunJ_sold].[parentId].&[Discount]"
});
```
---
## Getting Members
### Get Single Member
```javascript
DataSource.getMember(
dimension: string | DimensionInfo,
memberId: string,
hierarchy?: string | HierarchyInfo
): MemberInfo
```
**Flat Hierarchy**:
```javascript
// With flat presentation hierarchy
Table_1.getDataSource().getMember("Location_4nm2e04531", "CT1");
// Returns: {id: 'CT1', description: 'Los Angeles', dimensionId: 'Location_4nm2e04531', displayId: 'CT1'}
```
**With Specific Hierarchy**:
```javascript
// With State hierarchy active
Table_1.getDataSource().getMember(
"Location_4nm2e04531",
"[Location_4nm2e04531].[State_47acc246_4m5x6u3k6s].&[CT1]"
);
// Returns: {id: '[Location_4nm2e04531].[State_47acc246_4m5x6u3k6s].&[CT1]', description: 'Los Angeles', ...}
```
**Important**: Member ID format depends on the active hierarchy:
- Flat hierarchy: `"CT1"`
- Actual hierarchy: `"[Location_4nm2e04531].[State_47acc246_4m5x6u3k6s].&[CT1]"`
### Get Multiple Members
```javascript
DataSource.getMembers(
dimension: string | DimensionInfo,
options?: integer | MembersOptions
): MemberInfo[]
```
**Basic Usage (Limit Count)**:
```javascript
// Get first 3 members
Table_1.getDataSource().getMembers("Location_4nm2e04531", 3);
// Returns array of 3 MemberInfo objects
```
**With Options Object**:
```javascript
Table_1.getDataSource().getMembers("Location_4nm2e04531", {limit: 3});
```
### MembersOptions
```javascript
{
// Type of members: MemberAccessMode.MasterData (default) or MemberAccessMode.BookedValues
accessMode: MemberAccessMode,
// Hierarchy ID (default: currently active hierarchy)
hierarchyId: string,
// Maximum number of returned members (default: 200)
limit: integer
}
```
**With Hierarchy**:
```javascript
Table_1.getDataSource().getMembers("Location_4nm2e04531", {
limit: 2,
hierarchyId: "State_47acc246_4m5x6u3k6s"
});
```
**Master Data vs Booked Values**:
```javascript
// Master Data (all possible members)
Table_1.getDataSource().getMembers("Location_4nm2e04531", {
accessMode: MemberAccessMode.MasterData
});
// Returns all members including states
// Booked Values (only members with data)
Table_1.getDataSource().getMembers("Location_4nm2e04531", {
accessMode: MemberAccessMode.BookedValues
});
// Returns only members that have actual data
```
**Tip**: To find booked values:
1. Create table with dimension
2. Set desired hierarchy
3. Open ... menu → Deselect "Unbooked Values"
4. Table shows only booked values
---
## DataSource Information
Get metadata about a data source.
### Method Signature
```javascript
DataSource.getInfo(): DataSourceInfo
```
### DataSourceInfo Properties
```javascript
class DataSourceInfo {
modelName: string,
modelId: string,
modelDescription: string,
sourceName: string, // SAP BW only
sourceDescription: string, // SAP BW only
sourceLastChangedBy: string, // SAP BW only
sourceLastRefreshedAt: Date // SAP BW only
}
```
**Note**: `sourceName`, `sourceDescription`, `sourceLastChangedBy`, and `sourceLastRefreshedAt` are only supported for SAP BW models. For other models they return `undefined`.
### Usage Example
```javascript
var dsInfo = Table_1.getDataSource().getInfo();
console.log("Model name: " + dsInfo.modelName);
console.log("Model ID: " + dsInfo.modelId);
console.log("Model description: " + dsInfo.modelDescription);
console.log("Source name: " + dsInfo.sourceName);
console.log("Source description: " + dsInfo.sourceDescription);
console.log("Source last changed by: " + dsInfo.sourceLastChangedBy);
var strLastRefresh = "undefined";
if (dsInfo.sourceLastRefreshedAt !== undefined) {
strLastRefresh = dsInfo.sourceLastRefreshedAt.toISOString();
}
console.log("Source last refreshed at: " + strLastRefresh);
```
**SAP BW Output**:
```
Model name: HAL_TEST_Scenario_Query
Model ID: t.H:C9gjfpmu5ntxaf3dbfwtyl5wab
Model description: Sample scenario query
Source name: TEST_SCENARIO_QUERY
Source description: Test Query Scenario
Source last changed by: SYSTEM
Source last refreshed at: 2021-09-23T22:00:00.000Z
```
**SAP HANA Output**:
```
Model name: BestRunJuice_SampleModel
Model ID: t.2.CMRCZ9NPY3VAER9AO6PT80G12:...
Model description: Sample Model
Source name: undefined
Source description: undefined
Source last changed by: undefined
Source last refreshed at: undefined
```
---
## Pattern-Based Functions
Create string transformation functions using input/output examples instead of code.
### Adding Pattern-Based Function
1. In Outline, add a **ScriptObject**
2. Choose **...** → **Add Pattern Based Function**
3. Define function name and description
### Creating the Pattern
1. Click **+** next to "Create Pattern"
2. Define **Training Example**: Input → Output mapping
- Example: `john.doe@sap.com``John Doe`
3. Click **Create** to generate pattern via machine learning
4. Add more examples if ambiguous (up to 3)
5. Click **+** next to "Verify Pattern" to test
6. Click **Done** when complete
### Using in Scripts
```javascript
var fullName = ScriptObject_1.myPatternBasedFunction("joe.doe@sap.com");
// Returns: "Joe Doe"
```
### Example: Date Transformation
Transform dates from `MM.DD.YYYY` to `DD.MM.YY`:
**Training Examples**:
- Input: `10.11.2011` → Output: `11.10.11`
- Input: `09.05.2020` → Output: `05.09.20` (needed for disambiguation)
### Example: String Extraction
Transform appointment text to structured format:
**Input**: `John Doe has an appointment on 06.07.20 at 3:00pm.`
**Output**: `Name: John Doe, Date: 06.07.20, Time: 3:00pm`
### Troubleshooting
- If pattern defaults to returning input, add more training examples
- Click **Reset** to undo changes and restore last working pattern
- Maximum 3 training examples supported
---
## Best Practices
### Filter Performance
```javascript
// GOOD: Pause refresh before multiple filters
var ds = Table_1.getDataSource();
ds.setRefreshPaused(true);
ds.setDimensionFilter("Year", "2024");
ds.setDimensionFilter("Region", "EMEA");
ds.setDimensionFilter("Product", "Widget");
ds.setRefreshPaused(false); // Single backend call
// BAD: Each filter triggers refresh
ds.setDimensionFilter("Year", "2024"); // Refresh
ds.setDimensionFilter("Region", "EMEA"); // Refresh
ds.setDimensionFilter("Product", "Widget"); // Refresh
```
### Member Retrieval
```javascript
// GOOD: Use getResultSet() when possible (no backend trip)
var resultSet = Chart_1.getDataSource().getResultSet();
// EXPENSIVE: getMembers() always hits backend
var members = Chart_1.getDataSource().getMembers("Dimension");
// GOOD: Specify limit to reduce data transfer
var members = ds.getMembers("Dimension", {limit: 50});
// GOOD: Use BookedValues when you only need data with values
var members = ds.getMembers("Dimension", {
accessMode: MemberAccessMode.BookedValues
});
```
---
**Source**: SAP Analytics Designer Development Guide - Chapter 4: Scripting in Analytics Designer
**Last Updated**: 2025-11-23

View File

@@ -0,0 +1,463 @@
# DataSource API Reference
Complete reference for the DataSource API in SAP Analytics Cloud scripting.
**Source**: [Analytics Designer API Reference 2025.14](https://help.sap.com/doc/958d4c11261f42e992e8d01a4c0dde25/release/en-US/index.html)
---
## Table of Contents
1. [Getting DataSource Reference](#getting-datasource-reference)
2. [Information Methods](#information-methods)
3. [Dimension Methods](#dimension-methods)
4. [Measure Methods](#measure-methods)
5. [Filter Methods](#filter-methods)
6. [Variable Methods](#variable-methods)
7. [Data Access Methods](#data-access-methods)
8. [Hierarchy Methods](#hierarchy-methods)
9. [Refresh Control](#refresh-control)
10. [DataSource Types](#datasource-types)
---
## Getting DataSource Reference
DataSources cannot be referenced directly. Obtain reference via widget:
```javascript
// From Chart
var ds = Chart_1.getDataSource();
// From Table
var ds = Table_1.getDataSource();
// From GeoMap Layer
var ds = GeoMap_1.getLayer("LayerName").getDataSource();
```
Returns `undefined` if widget has no data binding.
---
## Information Methods
### getInfo()
Returns information about the data source.
```javascript
var info = ds.getInfo();
// Returns: DataSourceInfo object
// - id: string
// - description: string
// - modelId: string
// - type: DataSourceType
```
### getDataSourceInfo()
Returns detailed data source metadata.
```javascript
var dsInfo = ds.getDataSourceInfo();
```
---
## Dimension Methods
### getDimensions()
Returns all dimensions of the data source.
```javascript
var dimensions = ds.getDimensions();
// Returns: Array of DimensionInfo objects
// Each contains: { id: string, description: string }
```
**Example**:
```javascript
var dims = Chart_1.getDataSource().getDimensions();
for (var i = 0; i < dims.length; i++) {
console.log(dims[i].id + ": " + dims[i].description);
}
```
### getMembers(dimensionId, options)
Returns members of a dimension.
**Parameters**:
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| dimensionId | string | Yes | Dimension identifier |
| options | object | No | Filter options |
**Options**:
- `accessMode`: `MemberAccessMode.BookedValues` | `MemberAccessMode.All`
- `hierarchyId`: string - Specific hierarchy
- `level`: number - Hierarchy level
```javascript
// All members
var members = ds.getMembers("Location");
// Booked values only
var bookedMembers = ds.getMembers("Location", {
accessMode: MemberAccessMode.BookedValues
});
// Specific hierarchy level
var levelMembers = ds.getMembers("Date", {
hierarchyId: "YQM",
level: 2
});
```
**Returns**: Array of MemberInfo objects
```javascript
{
dimensionId: string,
id: string, // Full qualified ID
description: string,
displayId: string, // Short ID
properties: object // Custom attributes
}
```
**Performance Note**: `getMembers()` always triggers backend request. Use `getResultSet()` when possible.
### getMember(dimensionId, memberId)
Returns specific member info.
```javascript
var member = ds.getMember("Location", "[Location].[Country].&[US]");
```
### getMemberDisplayMode(dimensionId)
Returns display mode for dimension (Key, Description, Text, etc.).
```javascript
var mode = ds.getMemberDisplayMode("Product");
// Returns: "Description" | "Key" | "KeyDescription" | etc.
```
---
## Measure Methods
### getMeasures()
Returns all measures of the data source.
```javascript
var measures = ds.getMeasures();
// Returns: Array of MeasureInfo objects
// dimensionId is always "@MeasureDimension"
```
**Example**:
```javascript
var measures = Chart_1.getDataSource().getMeasures();
measures.forEach(function(m) {
console.log(m.id + ": " + m.description);
});
```
---
## Filter Methods
### setDimensionFilter(dimensionId, filterValue)
Sets filter on a dimension. Overwrites existing filter.
```javascript
// Single value
ds.setDimensionFilter("Year", "2024");
// Qualified member ID
ds.setDimensionFilter("Date", "[Date].[YQM].&[2024]");
// Multiple values (array)
ds.setDimensionFilter("Region", ["EMEA", "APAC", "AMER"]);
```
**Important**: Does not affect Advanced Filters set in designer.
### removeDimensionFilter(dimensionId)
Removes filter from dimension.
```javascript
ds.removeDimensionFilter("Year");
```
### getDimensionFilters(dimensionId)
Returns current filters on a dimension.
```javascript
var filters = ds.getDimensionFilters("Year");
// Returns: Array of FilterInfo objects
// { value: string, type: "Single" | "Range" | "Multiple" }
```
**Example**:
```javascript
var yearFilter = ds.getDimensionFilters("Year")[0];
console.log(yearFilter);
// { value: '[Date].[YQM].&[2024]', type: 'Single' }
```
### copyDimensionFilterFrom(sourceDataSource, dimensionId?)
Copies filters from another data source.
```javascript
// Copy all filters
Table_1.getDataSource().copyDimensionFilterFrom(Chart_1.getDataSource());
// Copy specific dimension filter
Table_1.getDataSource().copyDimensionFilterFrom(
Chart_1.getDataSource(),
"Location"
);
```
**Performance Tip**: More efficient than setting filters individually.
### clearAllFilters()
Removes all dimension filters.
```javascript
ds.clearAllFilters();
```
---
## Variable Methods
### getVariables()
Returns all variables of the data source.
```javascript
var variables = ds.getVariables();
// Returns: Array of VariableInfo objects
```
### getVariableValues(variableId)
Returns values of a variable.
```javascript
var values = ds.getVariableValues("VAR_YEAR");
// Returns: Array of VariableValue objects
```
### setVariableValue(variableId, value)
Sets value of a variable.
```javascript
ds.setVariableValue("VAR_YEAR", "2024");
```
---
## Data Access Methods
### getData(selection)
Returns data values for a specific selection.
```javascript
var selection = {
"@MeasureDimension": "[Account].[parentId].&[Revenue]",
"Location": "[Location].[Country].&[US]"
};
var data = ds.getData(selection);
// Returns: { formattedValue: string, rawValue: number }
```
**Example**:
```javascript
var data = Chart_1.getDataSource().getData({
"@MeasureDimension": "[Account].[parentId].&[Quantity_sold]",
"Location": "[Location].[State].&[CA]"
});
console.log("Formatted:", data.formattedValue);
console.log("Raw:", data.rawValue);
```
### getResultSet()
Returns current result set without backend trip.
```javascript
var resultSet = ds.getResultSet();
```
**Performance**: Significantly faster than `getMembers()`. Use when possible.
### getResultMember(dimensionId, selection)
Returns member details from result set.
```javascript
var member = ds.getResultMember("Location", selection);
// Includes parent relationship info
```
### getDataSelections()
Returns current data selections as key-value pairs.
```javascript
var selections = ds.getDataSelections();
// Returns: Object mapping dimension IDs to member IDs
```
---
## Hierarchy Methods
### getHierarchies(dimensionId)
Returns hierarchies for a dimension.
```javascript
var hierarchies = ds.getHierarchies("Date");
// Returns: Array of { id: string, description: string }
```
### collapseNode(dimensionId, selection)
Collapses hierarchy node.
```javascript
ds.collapseNode("Date", {
"@MeasureDimension": "[Account].[Revenue]",
"Date": "[Date].[YQM].&[2024]"
});
```
### expandNode(dimensionId, selection)
Expands hierarchy node.
```javascript
ds.expandNode("Date", selection);
```
---
## Refresh Control
### refreshData()
Refreshes data from backend.
```javascript
ds.refreshData();
```
### setRefreshPaused(paused)
Pauses or resumes automatic refresh.
```javascript
// Pause before multiple operations
ds.setRefreshPaused(true);
// Apply multiple changes
ds.setDimensionFilter("Year", "2024");
ds.setDimensionFilter("Region", "EMEA");
ds.setDimensionFilter("Product", "Widget");
// Resume (single backend call)
ds.setRefreshPaused(false);
```
**Best Practice**: Always use for batch filter operations.
### isRefreshPaused()
Returns current pause state.
```javascript
if (ds.isRefreshPaused()) {
console.log("Refresh is paused");
}
```
---
## DataSource Types
### DataSourceType Enumeration
```javascript
DataSourceType.Model // SAC model
DataSourceType.Query // Live query
DataSourceType.Calculation // Calculated source
```
### MemberAccessMode Enumeration
```javascript
MemberAccessMode.All // All master data members
MemberAccessMode.BookedValues // Only members with data
```
---
## Complete Example
```javascript
// Complex filtering scenario
function applyComplexFilters(chartDs, tableDs, year, region, product) {
// Pause both data sources
chartDs.setRefreshPaused(true);
tableDs.setRefreshPaused(true);
try {
// Apply filters to chart
chartDs.setDimensionFilter("Year", year);
chartDs.setDimensionFilter("Region", region);
chartDs.setDimensionFilter("Product", product);
// Copy filters to table
tableDs.copyDimensionFilterFrom(chartDs);
} catch (error) {
console.log("Error applying filters:", error);
}
// Resume both (triggers single refresh each)
chartDs.setRefreshPaused(false);
tableDs.setRefreshPaused(false);
}
// Usage
var chartDs = Chart_1.getDataSource();
var tableDs = Table_1.getDataSource();
applyComplexFilters(chartDs, tableDs, "2024", "EMEA", "Widget-A");
```
---
## Related Documentation
- [Widgets API](api-widgets.md)
- [Planning API](api-planning.md)
- [Application API](api-application.md)
**Official Reference**: [https://help.sap.com/doc/958d4c11261f42e992e8d01a4c0dde25/release/en-US/index.html](https://help.sap.com/doc/958d4c11261f42e992e8d01a4c0dde25/release/en-US/index.html)

541
references/api-planning.md Normal file
View File

@@ -0,0 +1,541 @@
# Planning API Reference
Complete reference for planning operations in SAP Analytics Cloud scripting.
**Source**: [Analytics Designer API Reference 2025.14](https://help.sap.com/doc/958d4c11261f42e992e8d01a4c0dde25/release/en-US/index.html)
---
## Table of Contents
1. [Getting Planning Reference](#getting-planning-reference)
2. [Version Management](#version-management)
3. [Data Operations](#data-operations)
4. [Data Locking](#data-locking)
5. [Data Actions](#data-actions)
6. [Multi-Actions](#multi-actions)
7. [Planning Enumerations](#planning-enumerations)
8. [Complete Examples](#complete-examples)
---
## Getting Planning Reference
Access Planning API via Table widget:
```javascript
var planning = Table_1.getPlanning();
```
**Note**: Planning features require a planning-enabled model connected to the table.
---
## Version Management
### Public Versions
#### getPublicVersion(versionId)
Returns a public version object.
```javascript
var budgetVersion = Table_1.getPlanning().getPublicVersion("Budget2024");
```
#### getPublicVersions()
Returns all public versions.
```javascript
var allPublicVersions = Table_1.getPlanning().getPublicVersions();
```
### Private Versions
#### getPrivateVersion(versionId)
Returns a private version object.
```javascript
var draftVersion = Table_1.getPlanning().getPrivateVersion("myDraft");
```
#### getPrivateVersions()
Returns all private versions.
```javascript
var allPrivateVersions = Table_1.getPlanning().getPrivateVersions();
```
#### createPrivateVersion(versionId, options)
Creates a new private version.
```javascript
var newDraft = Table_1.getPlanning()
.createPrivateVersion("Draft_" + Date.now().toString());
```
### Version Object Methods
#### isDirty()
Checks if version has unsaved changes.
```javascript
var version = Table_1.getPlanning().getPublicVersion("Budget2024");
if (version.isDirty()) {
console.log("Version has unsaved changes");
}
```
#### publish()
Publishes changes to the version.
```javascript
var version = Table_1.getPlanning().getPublicVersion("Budget2024");
if (version) {
if (version.isDirty()) {
version.publish();
}
}
```
#### copy(targetVersionId, copyOption, category?)
Copies version to a new version.
**Parameters**:
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| targetVersionId | string | Yes | Name for new version |
| copyOption | PlanningCopyOption | Yes | What to copy |
| category | PlanningCategory | No | Version category |
```javascript
// Copy public version to new private version
var budgetVersion = Table_1.getPlanning().getPublicVersion("Budget2024");
if (budgetVersion) {
budgetVersion.copy(
"Budget_Copy_" + Date.now().toString(),
PlanningCopyOption.PlanningArea
);
}
// Copy with category
var sourceVersion = Table_1.getPlanning().getPrivateVersion("Source");
if (sourceVersion) {
sourceVersion.copy(
"TargetVersion",
PlanningCopyOption.AllData,
PlanningCategory.Budget
);
}
// Copy as Forecast
var forecastSource = Table_1.getPlanning().getPrivateVersion("ForecastDraft");
if (forecastSource) {
forecastSource.copy(
"Forecast_" + Date.now().toString(),
PlanningCopyOption.PlanningArea,
PlanningCategory.Forecast
);
}
```
#### revert()
Reverts unsaved changes in the version.
```javascript
var version = Table_1.getPlanning().getPublicVersion("Budget2024");
version.revert();
```
#### delete()
Deletes a private version.
```javascript
var draft = Table_1.getPlanning().getPrivateVersion("OldDraft");
if (draft) {
draft.delete();
}
```
---
## Data Operations
### submitData()
Submits pending data changes.
```javascript
Table_1.getPlanning().submitData();
```
### getPlanningArea()
Returns planning area configuration.
```javascript
var planningArea = Table_1.getPlanning().getPlanningArea();
```
---
## Data Locking
### getDataLocking()
Returns data locking interface.
```javascript
var dataLocking = Table_1.getPlanning().getDataLocking();
```
### getState(selection)
Returns lock state for a selection.
```javascript
var selection = Table_1.getSelections()[0];
var lockState = Table_1.getPlanning().getDataLocking().getState(selection);
```
**Lock States**:
- `Locked` - Data is locked by another user
- `Unlocked` - Data is available for editing
- `LockedByMe` - Data is locked by current user
### Example: Check Before Edit
```javascript
var selection = Table_1.getSelections()[0];
var lockState = Table_1.getPlanning().getDataLocking().getState(selection);
if (lockState === DataLockingState.Locked) {
Application.showMessage(
ApplicationMessageType.Warning,
"Data is locked by another user"
);
} else {
// Proceed with edit
performEdit();
}
```
---
## Data Actions
Execute predefined data actions.
### Execute Data Action
```javascript
// Simple execution
DataAction_1.execute();
// With parameters
DataAction_1.setParameterValue("YEAR", "2024");
DataAction_1.setParameterValue("VERSION", "Budget");
DataAction_1.execute();
```
### Data Action Events
```javascript
DataAction_1.onBeforeExecute = function() {
Application.showBusyIndicator();
};
DataAction_1.onAfterExecute = function(result) {
Application.hideBusyIndicator();
if (result.success) {
Application.showMessage(ApplicationMessageType.Info, "Data action completed");
} else {
Application.showMessage(ApplicationMessageType.Error, "Data action failed");
}
};
```
### Background Execution
```javascript
// Execute in background
DataAction_1.executeInBackground();
// Monitor status
DataAction_1.onExecutionStatusUpdate = function(status) {
console.log("Status:", status);
if (status === ExecutionStatus.Completed) {
Table_1.getDataSource().refreshData();
}
};
```
---
## Multi-Actions
Execute multiple data actions in sequence.
### Execute Multi-Action
```javascript
MultiAction_1.execute();
```
### With Parameters
```javascript
MultiAction_1.setParameterValue("YEAR", "2024");
MultiAction_1.setParameterValue("SCENARIO", "Actual");
MultiAction_1.execute();
```
### Events
```javascript
MultiAction_1.onBeforeExecute = function() {
Application.showBusyIndicator();
};
MultiAction_1.onAfterExecute = function(result) {
Application.hideBusyIndicator();
Table_1.getDataSource().refreshData();
};
```
---
## Planning Enumerations
### PlanningCopyOption
```javascript
PlanningCopyOption.AllData // Copy all data
PlanningCopyOption.PlanningArea // Copy planning area only
```
### PlanningCategory
```javascript
PlanningCategory.Budget // Budget category
PlanningCategory.Forecast // Forecast category
PlanningCategory.Actual // Actual category
PlanningCategory.Plan // Plan category
```
### DataLockingState
```javascript
DataLockingState.Locked // Locked by another user
DataLockingState.Unlocked // Available for editing
DataLockingState.LockedByMe // Locked by current user
```
### ExecutionStatus
```javascript
ExecutionStatus.Running // In progress
ExecutionStatus.Completed // Successfully completed
ExecutionStatus.Failed // Execution failed
ExecutionStatus.Cancelled // Cancelled by user
```
---
## Complete Examples
### Example 1: Version Publishing Workflow
```javascript
// Button: Publish Budget
Button_Publish.onClick = function() {
Application.showBusyIndicator();
try {
var budgetVersion = Table_1.getPlanning().getPublicVersion("Budget2024");
if (!budgetVersion) {
Application.showMessage(
ApplicationMessageType.Error,
"Budget version not found"
);
return;
}
if (!budgetVersion.isDirty()) {
Application.showMessage(
ApplicationMessageType.Info,
"No changes to publish"
);
return;
}
budgetVersion.publish();
Application.showMessage(
ApplicationMessageType.Success,
"Budget published successfully"
);
} catch (error) {
console.log("Error:", error);
Application.showMessage(
ApplicationMessageType.Error,
"Failed to publish budget"
);
} finally {
Application.hideBusyIndicator();
}
};
```
### Example 2: Create Working Copy
```javascript
// Button: Create Working Copy
Button_CreateCopy.onClick = function() {
var sourceVersion = Table_1.getPlanning().getPublicVersion("Budget2024");
if (sourceVersion) {
var timestamp = Date.now().toString();
var copyName = "WorkingCopy_" + timestamp;
sourceVersion.copy(copyName, PlanningCopyOption.AllData);
Application.showMessage(
ApplicationMessageType.Info,
"Created working copy: " + copyName
);
}
};
```
### Example 3: Data Action with Confirmation
```javascript
// Button: Run Allocation
Button_RunAllocation.onClick = function() {
Popup_Confirm.open();
};
// Confirm button in popup
Button_ConfirmYes.onClick = function() {
Popup_Confirm.close();
Application.showBusyIndicator();
// Set parameters from dropdown selections
DataAction_Allocation.setParameterValue(
"SOURCE_VERSION",
Dropdown_SourceVersion.getSelectedKey()
);
DataAction_Allocation.setParameterValue(
"TARGET_VERSION",
Dropdown_TargetVersion.getSelectedKey()
);
DataAction_Allocation.setParameterValue(
"YEAR",
Dropdown_Year.getSelectedKey()
);
DataAction_Allocation.execute();
};
DataAction_Allocation.onAfterExecute = function(result) {
Application.hideBusyIndicator();
if (result.success) {
// Refresh data
Table_1.getDataSource().refreshData();
Application.showMessage(
ApplicationMessageType.Success,
"Allocation completed successfully"
);
} else {
Application.showMessage(
ApplicationMessageType.Error,
"Allocation failed: " + result.message
);
}
};
```
### Example 4: Find Active Planning Cycle
```javascript
// Find active planning cycle from attribute
function findActivePlanningCycle() {
var allCycles = PlanningModel_1.getMembers("PlanningCycle");
var activeCycle = null;
for (var i = 0; i < allCycles.length; i++) {
if (allCycles[i].properties.IsActive === "X") {
activeCycle = allCycles[i].id;
break;
}
}
return activeCycle;
}
// Use in initialization or filter setup
var activeCycle = findActivePlanningCycle();
if (activeCycle) {
Table_1.getDataSource().setDimensionFilter(
"PlanningCycle",
activeCycle
);
}
```
### Example 5: Revert Changes
```javascript
// Button: Discard Changes
Button_Revert.onClick = function() {
var version = Table_1.getPlanning().getPublicVersion("Budget2024");
if (version && version.isDirty()) {
version.revert();
Table_1.getDataSource().refreshData();
Application.showMessage(
ApplicationMessageType.Info,
"Changes discarded"
);
} else {
Application.showMessage(
ApplicationMessageType.Info,
"No changes to discard"
);
}
};
```
---
## Best Practices
1. **Always check version exists** before operations
2. **Use isDirty()** before publish to avoid unnecessary saves
3. **Show busy indicator** during long operations
4. **Handle errors** with try-catch blocks
5. **Refresh data** after successful operations
6. **Check lock state** before editing shared data
---
## Related Documentation
- [DataSource API](api-datasource.md)
- [Widgets API](api-widgets.md)
- [Application API](api-application.md)
**Official Reference**: [https://help.sap.com/doc/958d4c11261f42e992e8d01a4c0dde25/release/en-US/index.html](https://help.sap.com/doc/958d4c11261f42e992e8d01a4c0dde25/release/en-US/index.html)

622
references/api-widgets.md Normal file
View File

@@ -0,0 +1,622 @@
# Widget APIs Reference
Complete reference for widget scripting APIs in SAP Analytics Cloud.
**Source**: [Analytics Designer API Reference 2025.14](https://help.sap.com/doc/958d4c11261f42e992e8d01a4c0dde25/release/en-US/index.html)
---
## Table of Contents
1. [Common Widget Methods](#common-widget-methods)
2. [Chart Widget](#chart-widget)
3. [Table Widget](#table-widget)
4. [Input Controls](#input-controls)
5. [Button Widget](#button-widget)
6. [Text Widget](#text-widget)
7. [Image Widget](#image-widget)
8. [GeoMap Widget](#geomap-widget)
9. [Popup and Dialog](#popup-and-dialog)
10. [Feed Constants](#feed-constants)
---
## Common Widget Methods
All widgets share these base methods:
### Visibility
```javascript
Widget_1.setVisible(true); // Show widget
Widget_1.setVisible(false); // Hide widget
var isVisible = Widget_1.isVisible();
```
### Enabled State
```javascript
Widget_1.setEnabled(true); // Enable widget
Widget_1.setEnabled(false); // Disable widget
var isEnabled = Widget_1.isEnabled();
```
### Styling
```javascript
Widget_1.setCssClass("myCustomClass");
var cssClass = Widget_1.getCssClass();
```
### Focus
```javascript
Widget_1.focus();
```
---
## Chart Widget
### Data Source Access
```javascript
var ds = Chart_1.getDataSource();
```
### Dimension and Measure Management
#### addDimension(feed, dimensionId)
```javascript
Chart_1.addDimension(Feed.CategoryAxis, "Location");
Chart_1.addDimension(Feed.Color, "Product");
```
#### removeDimension(feed, dimensionId)
```javascript
Chart_1.removeDimension(Feed.CategoryAxis, "Location");
```
#### addMeasure(feed, measureId, index?)
```javascript
// Add measure to value axis
Chart_1.addMeasure(Feed.ValueAxis, "[Account].[parentId].&[Revenue]");
// Add at specific position
Chart_1.addMeasure(Feed.ValueAxis, "[Account].[parentId].&[Cost]", 1);
```
#### removeMeasure(feed, measureId)
```javascript
Chart_1.removeMeasure(Feed.ValueAxis, "[Account].[parentId].&[Revenue]");
```
#### getMembers(feed)
```javascript
var measures = Chart_1.getMembers(Feed.ValueAxis);
var dimensions = Chart_1.getMembers(Feed.CategoryAxis);
```
#### removeMember(feed, memberId)
```javascript
var currentMeasures = Chart_1.getMembers(Feed.ValueAxis);
Chart_1.removeMember(Feed.ValueAxis, currentMeasures[0]);
```
### Selections
#### getSelections()
Returns user-selected data points.
```javascript
var selections = Chart_1.getSelections();
// Returns: Array of selection objects
// Each object maps dimension ID to member ID
```
**Example**:
```javascript
var selections = Chart_1.getSelections();
if (selections.length > 0) {
var selectedLocation = selections[0]["Location"];
var selectedYear = selections[0]["Date"];
console.log("Selected:", selectedLocation, selectedYear);
}
```
### Sorting
#### sortByValue(measureId, order)
```javascript
Chart_1.sortByValue("[Account].[parentId].&[Revenue]", SortOrder.Descending);
```
#### sortByMember(dimensionId, memberId, order)
```javascript
Chart_1.sortByMember("Date", "[Date].[Year].&[2024]", SortOrder.Ascending);
```
### Ranking
#### rankBy(measureId, count, order)
```javascript
// Top 10 by revenue
Chart_1.rankBy("[Account].[parentId].&[Revenue]", 10, SortOrder.Descending);
// Bottom 5
Chart_1.rankBy("[Account].[parentId].&[Revenue]", 5, SortOrder.Ascending);
```
### Formatting
#### getNumberFormat(measureId)
```javascript
var format = Chart_1.getNumberFormat("[Account].[parentId].&[Revenue]");
```
#### setNumberFormat(measureId, format)
```javascript
Chart_1.setNumberFormat("[Account].[parentId].&[Revenue]", {
decimalPlaces: 2,
scalingFactor: 1000,
showSign: true
});
```
### Chart Events
```javascript
// onSelect - User selects data point
Chart_1.onSelect = function() {
var selections = Chart_1.getSelections();
// Handle selection
};
// onResultChanged - Data changes
Chart_1.onResultChanged = function() {
// Handle data change
};
```
---
## Table Widget
### Data Source Access
```javascript
var ds = Table_1.getDataSource();
```
### Dimension Management
#### addDimensionToRows(dimensionId)
```javascript
Table_1.addDimensionToRows("Product");
```
#### addDimensionToColumns(dimensionId)
```javascript
Table_1.addDimensionToColumns("Date");
```
#### removeDimensionFromRows(dimensionId)
```javascript
Table_1.removeDimensionFromRows("Product");
```
#### removeDimensionFromColumns(dimensionId)
```javascript
Table_1.removeDimensionFromColumns("Date");
```
### Display Options
#### setZeroSuppressionEnabled(enabled)
```javascript
Table_1.setZeroSuppressionEnabled(true); // Hide zero rows
```
#### setCompactDisplayEnabled(enabled)
```javascript
Table_1.setCompactDisplayEnabled(true);
```
### Selections
```javascript
var selections = Table_1.getSelections();
```
### Planning Access
```javascript
var planning = Table_1.getPlanning();
// See Planning API reference
```
### Comments
```javascript
var comments = Table_1.getComments();
```
### Table Events
```javascript
// onSelect - User selects cell
Table_1.onSelect = function() {
var selections = Table_1.getSelections();
};
// onResultChanged - Data changes
Table_1.onResultChanged = function() {
// Handle data change
};
```
---
## Input Controls
### Dropdown
```javascript
// Get selected value
var value = Dropdown_1.getSelectedKey();
// Set selected value
Dropdown_1.setSelectedKey("2024");
// Get all items
var items = Dropdown_1.getItems();
// Set items
Dropdown_1.setItems([
{ key: "2023", text: "Year 2023" },
{ key: "2024", text: "Year 2024" }
]);
```
**Events**:
```javascript
Dropdown_1.onSelect = function() {
var selected = Dropdown_1.getSelectedKey();
// Apply filter
Chart_1.getDataSource().setDimensionFilter("Year", selected);
};
```
### ListBox
```javascript
// Single selection
var value = ListBox_1.getSelectedKey();
// Multiple selection
var values = ListBox_1.getSelectedKeys();
// Set selection
ListBox_1.setSelectedKey("item1");
ListBox_1.setSelectedKeys(["item1", "item2"]);
```
### CheckboxGroup
```javascript
// Get checked items
var checked = CheckboxGroup_1.getSelectedKeys();
// Set checked items
CheckboxGroup_1.setSelectedKeys(["opt1", "opt3"]);
```
### RadioButtonGroup
```javascript
var selected = RadioButtonGroup_1.getSelectedKey();
RadioButtonGroup_1.setSelectedKey("option2");
```
### Slider
```javascript
// Get value
var value = Slider_1.getValue();
// Set value
Slider_1.setValue(75);
// Set range
Slider_1.setMin(0);
Slider_1.setMax(100);
Slider_1.setStep(5);
```
### RangeSlider
```javascript
// Get range values
var startValue = RangeSlider_1.getStartValue();
var endValue = RangeSlider_1.getEndValue();
// Set range
RangeSlider_1.setStartValue(10);
RangeSlider_1.setEndValue(90);
```
### InputField
```javascript
// Get/Set value
var text = InputField_1.getValue();
InputField_1.setValue("New text");
// Placeholder
InputField_1.setPlaceholder("Enter value...");
```
### Switch
```javascript
// Get state
var isOn = Switch_1.isSelected();
// Set state
Switch_1.setSelected(true);
```
### DatePicker
```javascript
// Get selected date
var date = DatePicker_1.getValue();
// Set date
DatePicker_1.setValue(new Date(2024, 0, 1));
```
---
## Button Widget
### Properties
```javascript
// Set text
Button_1.setText("Click Me");
// Get text
var text = Button_1.getText();
// Set icon
Button_1.setIcon("sap-icon://accept");
```
### Events
```javascript
Button_1.onClick = function() {
// Handle click
Application.showMessage(ApplicationMessageType.Info, "Button clicked!");
};
```
---
## Text Widget
```javascript
// Set text
Text_1.setText("Hello World");
Text_1.setText("Value: " + myVariable);
// Get text
var text = Text_1.getText();
// Apply styling
Text_1.setCssClass("highlightText");
```
### Dynamic Text with Placeholders
```javascript
// Set text with bound values
Text_1.setText("Revenue: " + revenue.formattedValue);
```
---
## Image Widget
```javascript
// Set image source
Image_1.setSrc("[https://example.com/image.png](https://example.com/image.png)");
// From content network
Image_1.setSrc("sap-icon://home");
// Alt text
Image_1.setAlt("Company Logo");
```
---
## GeoMap Widget
### Layer Access
```javascript
var layer = GeoMap_1.getLayer("LayerName");
var ds = layer.getDataSource();
```
### Visibility
```javascript
GeoMap_1.setLayerVisible("LayerName", true);
GeoMap_1.setLayerVisible("LayerName", false);
```
### Selection
```javascript
var selections = GeoMap_1.getSelections();
```
---
## Popup and Dialog
### Create and Open
```javascript
// Open popup
Popup_1.open();
// Close popup
Popup_1.close();
```
### Dialog Mode
Enable in Builder panel: "Enable header & footer"
```javascript
// Dialog with custom buttons
// Configure in Builder panel
```
### Popup Events
```javascript
Popup_1.onOpen = function() {
// Popup opened
};
Popup_1.onClose = function() {
// Popup closed
};
```
### Example: Confirmation Dialog
```javascript
// Button click opens popup
Button_Confirm.onClick = function() {
Popup_Confirm.open();
};
// Confirm button in popup
Button_Yes.onClick = function() {
// Perform action
Table_1.getPlanning().getPublicVersion("Budget").publish();
Popup_Confirm.close();
};
// Cancel button
Button_No.onClick = function() {
Popup_Confirm.close();
};
```
---
## Feed Constants
Use with Chart dimension/measure methods:
```javascript
Feed.CategoryAxis // X-axis dimension
Feed.Color // Color/legend dimension
Feed.ValueAxis // Y-axis measures
Feed.ValueAxis2 // Secondary Y-axis
Feed.Size // Bubble size (bubble charts)
```
---
## SortOrder Enumeration
```javascript
SortOrder.Ascending // A-Z, 0-9
SortOrder.Descending // Z-A, 9-0
SortOrder.Default // Default order
```
---
## Complete Example: Interactive Dashboard
```javascript
// Dropdown filter changes chart and table
Dropdown_Year.onSelect = function() {
var year = Dropdown_Year.getSelectedKey();
Application.showBusyIndicator();
// Pause refreshes
Chart_1.getDataSource().setRefreshPaused(true);
Table_1.getDataSource().setRefreshPaused(true);
// Apply filter to both
Chart_1.getDataSource().setDimensionFilter("Year", year);
Table_1.getDataSource().setDimensionFilter("Year", year);
// Update title
Text_Title.setText("Sales Report - " + year);
// Resume refreshes
Chart_1.getDataSource().setRefreshPaused(false);
Table_1.getDataSource().setRefreshPaused(false);
Application.hideBusyIndicator();
};
// Chart selection filters table
Chart_1.onSelect = function() {
var selections = Chart_1.getSelections();
if (selections.length > 0) {
var region = selections[0]["Region"];
Table_1.getDataSource().setDimensionFilter("Region", region);
}
};
// Reset button
Button_Reset.onClick = function() {
Chart_1.getDataSource().removeDimensionFilter("Year");
Chart_1.getDataSource().removeDimensionFilter("Region");
Table_1.getDataSource().removeDimensionFilter("Year");
Table_1.getDataSource().removeDimensionFilter("Region");
Dropdown_Year.setSelectedKey("");
Text_Title.setText("Sales Report - All Years");
};
```
---
## Related Documentation
- [DataSource API](api-datasource.md)
- [Planning API](api-planning.md)
- [Application API](api-application.md)
**Official Reference**: [https://help.sap.com/doc/958d4c11261f42e992e8d01a4c0dde25/release/en-US/index.html](https://help.sap.com/doc/958d4c11261f42e992e8d01a4c0dde25/release/en-US/index.html)

View File

@@ -0,0 +1,16 @@
# Authentication-Required Page (2025.23)
Source: `help-portal-8a2db072d3a8463899b7224563d787d2.md` (Authentication Required).
## Status
- ❌ Page requires SAP ID authentication; scraping redirected to SAP login.
## URLs
- Original: `.../8a2db072d3a8463899b7224563d787d2.html?locale=en-US&version=2025.23`
- Redirect: `[https://accounts.sap.com/saml2/idp/sso/accounts.sap.com`](https://accounts.sap.com/saml2/idp/sso/accounts.sap.com`)
## Recommendations
1. Access via authenticated SAP Help Portal account.
2. Look for equivalent public documentation if available.
3. Check other Help Portal pages for similar content that is publicly accessible.

View File

@@ -0,0 +1,15 @@
# Automatic Refactoring (2025.23)
Source: `help-portal-da7f92f6a2ac4ab9b5498da68a82712b.md`.
## Overview
Built-in refactoring helps keep scripts consistent and maintainable.
## Capabilities
- Rename identifiers across scripts.
- Update references safely when moving/renaming objects.
## Notes
- Use refactor tools instead of manual search/replace to avoid broken references.
- Verify changes in value help after refactoring.

View File

@@ -0,0 +1,304 @@
# Developer Best Practices for Maintainable SAC Stories
Best practices for building SAC stories that are scalable, maintainable, and developer-friendly.
**Source**: [Building Stories That Other Developers Actually Want to Inherit](https://community.sap.com/t5/technology-blog-posts-by-members/building-stories-that-other-developers-actually-want-to-inherit/ba-p/14168133) by JBARLOW (SAP Community, July 2025)
---
## Table of Contents
1. [Widget Naming Conventions](#widget-naming-conventions)
2. [Layout Organization](#layout-organization)
3. [Script Annotation Standards](#script-annotation-standards)
4. [Design Tips](#design-tips)
5. [Summary](#summary)
---
## Widget Naming Conventions
The most impactful practice for maintainable SAC stories is consistent widget naming.
### Standard Prefixes
| Widget Type | Prefix | Example |
|-------------|--------|---------|
| Bar Chart | `chartB_` | `chartB_revenue_by_state` |
| Line Chart | `chartL_` | `chartL_margin_by_product` |
| Numeric Point Chart | `kpi_` | `kpi_actuals_vs_budget` |
| Waterfall Chart | `chartWF_` | `chartWF_spend` |
| Table | `tbl_` | `tbl_invoices_by_month` |
| Panel | `pnl_` | `pnl_title` |
| Text Box | `txt_` | `txt_title` |
| Image | `img_` | `img_logo` |
| Shape | `shape_` | `shape_header_divider` |
| Button | `btn_` | `btn_page2` |
| Dropdown List | `ddl_` | `ddl_product` |
| List Box | `lb_` | `lb_store` |
| Radio Button Group | `rbg_` | `rbg_managers` |
| Checkbox | `chk_` | `chk_include_forecast` |
### Why Naming Matters
**Without naming conventions** (Bad):
```
Outline View:
├── Chart
├── Chart_1
├── Chart_2
├── Table
├── Table_1
├── Button
├── Button_1
```
**With naming conventions** (Good):
```
Outline View:
├── chartB_revenue_by_region
├── chartL_revenue_trend
├── chartWF_budget_variance
├── tbl_detailed_transactions
├── tbl_summary_by_product
├── btn_export_pdf
├── btn_reset_filters
```
### Benefits
1. **Autocomplete clarity**: When typing in script editor, CTRL+Space shows meaningful names
2. **Easier debugging**: Quickly identify which widget is referenced in error messages
3. **Team collaboration**: Other developers immediately understand widget purposes
4. **Self-documenting**: Code becomes readable without additional comments
### Implementation Guidelines
- Include naming conventions in organization's design guidelines
- Apply consistently from project start
- Don't get lazy and stop mid-project
- Use lowercase with underscores for readability
---
## Layout Organization
Use panels to group related content for easier navigation and maintenance.
### Panel-Based Structure
```
Story Layout:
├── pnl_header
│ ├── img_logo
│ ├── txt_title
│ └── shape_header_divider
├── pnl_filters
│ ├── ddl_year
│ ├── ddl_region
│ └── btn_apply_filters
├── pnl_kpis
│ ├── kpi_revenue
│ ├── kpi_margin
│ └── kpi_growth
├── pnl_main_chart
│ └── chartB_revenue_by_product
└── pnl_details
└── tbl_transactions
```
### Ordering Convention
Order widgets in Outline view to match visual layout:
- **Top to bottom**
- **Left to right**
This allows developers to understand layout without viewing the canvas.
### Benefits of Panels
1. **Quick navigation**: Find content blocks instantly in Outline
2. **Easy repositioning**: Move entire sections by dragging one panel
3. **Simplified scripting**: Show/hide panel instead of multiple widgets
4. **Performance**: No significant performance impact from using many panels
### Show/Hide Pattern
```javascript
// Instead of hiding multiple widgets individually
// AVOID:
Chart_1.setVisible(false);
Table_1.setVisible(false);
Text_1.setVisible(false);
Button_1.setVisible(false);
// Hide the containing panel
// GOOD:
pnl_details.setVisible(false);
```
---
## Script Annotation Standards
Well-documented scripts dramatically reduce maintenance friction.
### Summary-Level Comments
Add a summary block at the start of each script explaining:
- **Purpose**: What this script accomplishes
- **Interaction**: How it connects to other scripts
- **Dependencies**: Widgets, variables, or data sources referenced
```javascript
/*
* ============================================
* Script: onSelect - chartB_revenue_by_region
* ============================================
* Purpose: Filter detail table when user selects a region in the chart
*
* Interaction:
* - Reads: chartB_revenue_by_region selections
* - Updates: tbl_transactions filter
* - Triggers: onResultChanged on tbl_transactions
*
* Dependencies:
* - chartB_revenue_by_region (Chart widget)
* - tbl_transactions (Table widget)
* - GlobalVar_selectedRegion (String variable)
* ============================================
*/
// Script implementation below...
```
### Line-Level Comments
Add inline comments explaining non-obvious logic:
```javascript
// Get user's selection from the chart
var selections = chartB_revenue_by_region.getSelections();
// Guard clause: exit if nothing selected
if (selections.length === 0) {
return;
}
// Extract region ID from selection object
// Note: Key name matches dimension ID in model
var selectedRegion = selections[0]["Region"];
// Store in global variable for other scripts to access
GlobalVar_selectedRegion = selectedRegion;
// Apply filter to detail table
// Using setDimensionFilter to replace any existing filter
tbl_transactions.getDataSource().setDimensionFilter("Region", selectedRegion);
```
### AI-Assisted Annotation
For existing undocumented scripts:
1. Copy script to AI assistant (ChatGPT, Claude, etc.)
2. Request annotation and summary generation
3. **Always review and verify** the AI output for accuracy
4. Adjust terminology to match your project conventions
---
## Design Tips
### Panel Auto Scroll
**Recommendation**: Turn OFF
- Prevents unnecessary scroll bars
- Provides hard boundaries for widget positioning
- Makes layout arrangement predictable
```
Panel Settings → Auto Scroll → Disabled
```
### Flow Panels
**Recommendation**: Avoid
- Flow panels make precise positioning difficult
- Fixed layouts are easier to maintain
- Plan your layout before building; flow panels indicate unclear requirements
### Dynamic/Responsive Sizing
Build stories that adapt to varying screen sizes:
**Development Workflow**:
1. Build with **auto widget widths** and **fixed px** Left/Right values
2. Test and finalize layout
3. Change Left/Right from **px** to **%** for responsiveness
```javascript
// Example: Widget positioning
// Fixed (initial development):
// Left: 20px, Right: 20px
// Responsive (after layout finalized):
// Left: 2%, Right: 2%
```
**Advanced Responsive Design**:
- Use CSS classes for font size adjustments based on widget size
- Consider breakpoints for significantly different screen sizes
- Test on target devices before deployment
---
## Summary
Building maintainable SAC stories requires discipline in four areas:
| Area | Key Practice | Impact |
|------|--------------|--------|
| **Naming** | Use consistent prefixes for all widgets | Immediate clarity in scripts and Outline |
| **Layout** | Group content in panels, order logically | Easy navigation and repositioning |
| **Annotation** | Summary + line comments for all scripts | Reduced onboarding time for new developers |
| **Design** | Disable auto scroll, use % sizing | Predictable, responsive layouts |
> *"Good design isn't just for end users—it starts with the developer."*
> — JBARLOW, SAP Community
---
## Quick Reference Card
### Naming Prefixes
```
chartB_ chartL_ chartWF_ kpi_ tbl_ pnl_
txt_ img_ shape_ btn_ ddl_ lb_ rbg_ chk_
```
### Panel Visibility Pattern
```javascript
pnl_section.setVisible(true/false); // Show/hide entire section
```
### Script Header Template
```javascript
/*
* Script: [event] - [widget_name]
* Purpose: [description]
* Dependencies: [list]
*/
```
### Responsive Sizing
```
Development: px values → Production: % values
```
---
**License**: GPL-3.0
**Last Updated**: 2025-11-22
**Repository**: [https://github.com/secondsky/sap-skills](https://github.com/secondsky/sap-skills)

View File

@@ -0,0 +1,367 @@
# Best Practices for Planning Stories in SAP Analytics Cloud
Architectural patterns and user experience guidelines for building professional planning applications with multi-story navigation.
**Source**: [SAP PRESS Blog - Best Practices for Planning Stories in SAP Analytics Cloud](https://blog.sap-press.com/best-practices-for-planning-stories-in-sap-analytics-cloud)
**Book**: *Application Development with SAP Analytics Cloud* by Josef Hampp and Jan Lang (SAP PRESS, 2024)
---
## Table of Contents
1. [Entry Point Design](#entry-point-design)
2. [Multi-Story Architecture](#multi-story-architecture)
3. [Story Navigation via Scripting](#story-navigation-via-scripting)
4. [User Assistance Patterns](#user-assistance-patterns)
5. [Guided Process Implementation](#guided-process-implementation)
6. [Button Design Guidelines](#button-design-guidelines)
---
## Entry Point Design
Create an overview/start page as the single entry point for users into the planning process.
### Structure
A well-designed entry page should be **structured and not overloaded**:
```
┌─────────────────────────────────────────────────────────┐
│ KPI TILES (Top Section) │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Metric1 │ │ Metric2 │ │ Metric3 │ │ Metric4 │ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
├─────────────────────────────────────────────────────────┤
│ NAVIGATION SECTIONS (Visually Separated) │
│ ┌─────────────────┐ ┌─────────────────┐ ┌────────────┐│
│ │ Configure App │ │ Plan FTE │ │ Reports ││
│ │ & Parameters │ │ Demands & Costs │ │ ││
│ │ │ │ │ │ ││
│ │ > Settings │ │ > Workforce │ │ > Summary ││
│ │ > Parameters │ │ > Budget │ │ > Details ││
│ └─────────────────┘ └─────────────────┘ └────────────┘│
└─────────────────────────────────────────────────────────┘
```
### Key Elements
| Element | Purpose |
|---------|---------|
| KPI Tiles | Display most important key figures at a glance |
| Navigation Areas | Group related planning functions (3-4 max) |
| Jump Links | Text fields with onClick scripts to navigate to sub-stories |
| Visual Separation | Clear boundaries between functional areas |
### Benefits
- Provides orientation for users in complex planning processes
- Reduces cognitive load with organized navigation
- Enables easy access to all planning phases from one location
---
## Multi-Story Architecture
Separate individual planning steps into distinct stories for simplified maintenance.
### Folder Organization
Store all related stories in a dedicated folder structure:
```
SAC File System/
└── Planning_Application_2024/
├── 00_Entry_Point.story
├── 01_Application_Configuration.story
├── 02_Plan_FTE_Demands.story
├── 03_Plan_Costs.story
├── 04_Summary_Report.story
└── 05_Detail_Report.story
```
### Benefits
| Benefit | Description |
|---------|-------------|
| **Easier Maintenance** | Update one story without affecting others |
| **Team Collaboration** | Multiple developers can work on different stories |
| **Version Control** | Track changes at story level |
| **Performance** | Smaller stories load faster |
| **Testing** | Test individual planning steps independently |
### Implementation Guidelines
1. Create one story per planning phase or major step
2. Use consistent naming with numeric prefixes for ordering
3. Share data sources across stories using the same model
4. Implement consistent navigation patterns across all stories
---
## Story Navigation via Scripting
Use scripting instead of standard hyperlinks for cross-story navigation with URL parameters.
### Full Navigation Script
Add this script to a Script Object for reuse across your entry point:
```javascript
/**
* Opens a target story with specified display mode and page
* @param {string} arg_StoryIdPageIndex - Format: "storyId|pageIndex"
* @param {string} arg_displayMode - "view" or "edit"
*/
function navigateToStory(arg_StoryIdPageIndex, arg_displayMode) {
var delimiter_storyIdPageIndex = "|";
// Extract story ID and page index from the combination provided
var storyId = arg_StoryIdPageIndex.substring(
0,
arg_StoryIdPageIndex.indexOf(delimiter_storyIdPageIndex)
);
var pageIndex = arg_StoryIdPageIndex.substring(
arg_StoryIdPageIndex.indexOf(delimiter_storyIdPageIndex) + 1
);
// Create and fill array of URL parameters
var urlParameters = ArrayUtils.create(Type.UrlParameter);
urlParameters.push(UrlParameter.create("mode", arg_displayMode));
urlParameters.push(UrlParameter.create("page", pageIndex));
// Optional: Add custom URL parameters
// urlParameters.push(UrlParameter.create("p_myParam", "value"));
// Open the target story
NavigationUtils.openStory(storyId, "", urlParameters, false);
}
```
### Usage in onClick Event
```javascript
// onClick of txt_navigate_to_workforce
// Story ID from the target story's URL, page index 0-based
ScriptObject_Navigation.navigateToStory("STORY123ABC|0", "view");
```
### Storing Story IDs
Create a Script Object to store story IDs for easy maintenance:
```javascript
// ScriptObject_StoryRegistry
var STORIES = {
CONFIGURATION: "ABC123DEF|0",
WORKFORCE_PLAN: "GHI456JKL|0",
COST_PLAN: "MNO789PQR|0",
SUMMARY_REPORT: "STU012VWX|1",
DETAIL_REPORT: "YZA345BCD|0"
};
function getStoryReference(storyName) {
return STORIES[storyName];
}
```
---
## User Assistance Patterns
Implement consistent help features across all planning stories.
### Sidebar Structure
Include a sidebar on every story (except entry page) with:
```
┌──────────────────────┐
│ FILTERS │
│ ├─ Year: [2024 ▼] │
│ ├─ Region: [All ▼] │
│ └─ [Apply Filters] │
├──────────────────────┤
│ NAVIGATION │
│ ├─ ← Back to Entry │
│ ├─ → Next Step │
│ └─ ↺ Home │
├──────────────────────┤
│ INSTRUCTIONS │
│ Step 1: Select... │
│ Step 2: Enter... │
│ Step 3: Save... │
├──────────────────────┤
│ EXTERNAL LINKS │
│ ├─ 📖 Documentation │
│ ├─ ❓ Ask Question │
│ └─ Process Info │
└──────────────────────┘
```
### Step-by-Step Instructions
Provide clear instructions directly in the sidebar:
```
INSTRUCTIONS
────────────────────
Step 1: Select the planning version
from the dropdown above
Step 2: Enter FTE values in the table
for each cost center
Step 3: Review the calculated costs
in the summary section
Step 4: Click "Save" to store your
changes
Step 5: Click "Submit" when ready
for approval
```
### Benefits
- Ensures consistent user experience across all stories
- Reduces support requests with inline help
- Enables occasional users to complete planning correctly
---
## Guided Process Implementation
Implement a "Guide Me!" button that opens a focused popup with step-by-step guidance.
### Popup Design
```
┌─────────────────────────────────────┐
│ ✨ Planning Guide X│
├─────────────────────────────────────┤
│ │
│ Welcome to Workforce Planning! │
│ │
│ Follow these steps: │
│ │
│ ☐ 1. Select your cost center │
│ ☐ 2. Choose the planning period │
│ ☐ 3. Enter FTE values │
│ ☐ 4. Review calculated costs │
│ ☐ 5. Submit for approval │
│ │
│ ┌─────────┐ ┌─────────────────┐ │
│ │ Close │ │ Start Planning │ │
│ └─────────┘ └─────────────────┘ │
│ │
└─────────────────────────────────────┘
```
### Implementation
1. Create a Popup widget with header enabled
2. Add text widgets with step instructions
3. Add navigation buttons
4. Trigger popup from "Guide Me!" button:
```javascript
// onClick of btn_guide_me
Popup_Guide.open();
```
### Dynamic Step Highlighting (Advanced)
```javascript
// Track current step in global variable
// GlobalVar_currentStep (Integer)
function highlightCurrentStep() {
// Reset all step indicators
txt_step1.setStyle("opacity", "0.5");
txt_step2.setStyle("opacity", "0.5");
txt_step3.setStyle("opacity", "0.5");
// Highlight current step
switch (GlobalVar_currentStep) {
case 1:
txt_step1.setStyle("opacity", "1.0");
break;
case 2:
txt_step2.setStyle("opacity", "1.0");
break;
case 3:
txt_step3.setStyle("opacity", "1.0");
break;
}
}
```
---
## Button Design Guidelines
Use color coding to make button functions immediately clear to users.
### Color Conventions
| Color | Usage | Examples |
|-------|-------|----------|
| **Green** | Positive/Confirm actions | Save, Submit, Approve, Publish |
| **Red** | Negative/Destructive actions | Delete, Reject, Revert, Cancel |
| **Blue** | Navigation/Neutral actions | Next, Back, View, Export |
| **Gray** | Secondary/Disabled actions | Reset, Clear, Close |
### Button Layout Example
```
┌─────────────────────────────────────────────┐
│ ACTION BUTTONS │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 🔴 Reset │ │ 🔵 Export │ │ 🟢 Save │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
└─────────────────────────────────────────────┘
```
### Implementation Tips
1. Use consistent button sizing across the application
2. Group related buttons together
3. Place primary action (usually green) on the right
4. Add confirmation dialogs for destructive actions:
```javascript
// onClick of btn_delete
var confirmed = Application.showConfirmDialog(
"Confirm Delete",
"Are you sure you want to delete this version?",
"Delete",
"Cancel"
);
if (confirmed) {
// Perform delete operation
planning.getPrivateVersion("draft").delete();
}
```
---
## Summary
| Best Practice | Key Takeaway |
|---------------|--------------|
| **Entry Point** | Single overview page with KPIs and organized navigation |
| **Multi-Story** | Separate stories per planning phase, stored in folders |
| **Navigation** | Use scripting with `NavigationUtils.openStory()` |
| **User Assistance** | Consistent sidebar with filters, instructions, links |
| **Guided Process** | "Guide Me!" popup with step-by-step workflow |
| **Buttons** | Color-coded: Green=positive, Red=negative, Blue=neutral |
---
**License**: GPL-3.0
**Last Updated**: 2025-11-22
**Repository**: [https://github.com/secondsky/sap-skills](https://github.com/secondsky/sap-skills)

View File

@@ -0,0 +1,16 @@
# Bind Widget Values to Variables (2025.23)
Source: `help-portal-0192dc472ba946989d05d444e598e265.md`.
## Overview
Bind widget values (for example, input controls) to variables for two-way data flow.
## Steps
1. Select widget → Bind value to variable.
2. Use script to read/write the bound variable.
3. Variable changes propagate to widget and vice versa.
## Notes
- Ensure variable types align with widget value types.
- Useful for centralized state management across widgets.

View File

@@ -0,0 +1,26 @@
# Blending Limitations (2025.23)
Source: `help-portal-77eb1188969e4e0ea7dfe92b73e67ee2.md` (Blend Data in Analytic Applications).
## General restrictions
- All story restrictions also apply to analytic applications.
- Analytics Designer specifics: filter line, data point comment, navigation panel not supported.
## Scripting considerations
- APIs/value help only operate on the **primary** model of blended charts/tables.
- Dimension/measure member IDs include modelId in blended scenarios.
- Calculated measures use UUID (no modelId); value help for blended charts returns no modelId.
- Log chart/table selections in console to capture full IDs.
## Unsupported APIs for blending
- **Planning APIs**: all.
- **Navigation panel APIs**: all.
- **Comment APIs**: all.
- **Data explorer APIs**: all.
- **Filter APIs**: `Chart.getDataSource().setDimensionFilter()`, `.removeDimensionFilter()`, `DataSource.copyDimensionFilterFrom()`, `DataSource.copyVariableValueFrom()`.
- **Table APIs**: `Table.sortByMember()`, `sortByValue()`, `removeSorting()`, `rankBy()`, `removeRanking()`, `setBreakGroupingEnabled()`.
## Implementation notes
- Always confirm dimension IDs when reusing API outputs.
- Test carefully—many operations are unsupported in blended mode.

View File

@@ -0,0 +1,11 @@
# Bookmark Set Technical Object (2025.23)
Source: `help-portal-6f64b8234dc448c6b427f7df4a39ec4e.md`.
## Purpose
Technical object to manage bookmark sets programmatically.
## Notes
- Create bookmark sets, organize and apply saved application states.
- Integrate with bookmark APIs and Linked Analysis strategies.

View File

@@ -0,0 +1,15 @@
# Bookmark Settings & APIs (2025.23)
Source: `help-portal-51e76005e0ee45e1a3b9a96271f11509.md`.
## Overview
Configure and script bookmark behavior in analytic applications.
## Capabilities
- Save and restore application state (global/personal).
- Control bookmark visibility and defaults.
- Programmatically apply bookmarks via API.
## Notes
- Align bookmark strategy with Linked Analysis and filter design.

View File

@@ -0,0 +1,14 @@
# Calendar Integration Technical Object (2025.23)
Source: `help-portal-a727e860122944b0be6a8351456174f8.md`.
## Purpose
Programmatic access to calendar tasks, processes, and workflows from analytic applications.
## Capabilities
- Create, update, and manage calendar items.
- Integrate planning workflows (submit/approve/reject) with scripts.
## Notes
- Align with planning calendar permissions and scheduling rules.

View File

@@ -0,0 +1,14 @@
# Check Errors in Scripting (2025.23)
Source: `help-portal-d57d183a83f247e390c562719a42de51.md`.
## Errors panel
1. Toolbar → Views → **Info Panel****Errors**.
2. Filter by widget name; toggle errors vs warnings; expand/collapse all.
3. Click an entry to jump to script with highlight.
4. Fixed items disappear automatically; close via Info Panel or panel close icon.
## Notes
- Use before runtime to catch missing references/typos.
- Complements browser-console debugging.

View File

@@ -0,0 +1,15 @@
# Check References in Scripting (2025.23)
Source: `help-portal-ad9735ccc415440cb440060fac9d3500.md`.
## Purpose
Identify missing or broken references across scripts and widgets.
## Usage
- Use the reference checking tool in the script editor/IDE to list unresolved references.
- Navigate directly from findings to affected scripts to fix IDs or object names.
## Notes
- Run after refactoring/renaming widgets.
- Pair with Errors panel for full coverage (`check-errors.md`).

20
references/comments.md Normal file
View File

@@ -0,0 +1,20 @@
# Comments in Analytic Applications (2025.23)
Source: `help-portal-0f47c5728c484f5c86db06b43f11b1db.md` (Work with Comments in Analytic Applications).
## Supported widgets
- Chart, table, geo map, image, shape, text, RSS reader, web page, R visualization, clock.
## Runtime behavior
- Users can view/create comments on widgets or table cells for analytic and planning models.
- Comment mode toggle available in toolbar during view mode.
- Embedded apps: enable Commenting in Embedded Mode via System Administration → System Configuration.
## Script APIs (design time)
- Show/hide comments programmatically.
- Table cell comment APIs: get comments (by ID or data context), post comments, remove comments.
## Implementation notes
- Same table-cell commenting pattern as stories; scripting provides additional control.
- Works across analytic and planning models.

View File

@@ -0,0 +1,42 @@
# Composites Restrictions (2025.23)
Source: `help-portal-2249cc4e678e4f46ace92501320f5ef6.md` (Restrictions in Composites).
## File Repository
- Cannot schedule publication or publish to catalog.
- Not shown in Catalog/Favorites/Shared With Me tabs.
## Composite Editor unsupported
- View mode, multiple pages, page styling, popups, technical objects, export, bookmark.
- Link dimensions/linked models, chart scaling, input task, smart discovery, data analyzer.
- Layouts panel, theme, CSS editor, rename account/measure/dimension.
- Calculation/account/dimension input controls, save as template, publish to catalog.
- Toolbar customization, global settings, copy to, auto refresh, performance tools, loading optimization, filter panel, device preview, commenting.
## Composite Editor partial
- Widgets: limited set available.
- Linked analysis: only “Only This Widget” or “Only Selected Widgets”.
- Thresholds: adding to tables not supported.
- Edit prompts: auto-open prompt option unavailable.
- Dynamic text sources limited; cannot use input controls, story filters, model variables, page number.
- Hyperlink: cannot link to specific story page.
- Dropdown/checkbox/radio/input: only manual input; write-back unavailable.
- Scripting: widget APIs supported; global APIs only partially.
- Page styling: ID, title, canvas size, page layout unavailable.
- `onInitialization` event: not supported.
## Optimized Story Experience unsupported
- Linked widgets diagram; video data story.
- Copy/paste composites or pages with composites.
- Save As in view mode.
## Optimized Story Experience partial
- Chart color: palette retained only when no dimension/account in Color section.
- Export/bookmark: only the composite as a whole, not individual widgets.
- Commenting: supported on individual widgets in view mode only.
- Linked analysis: composites can only be targets for “All Widgets in Story/Page” or “Only Selected Widgets”.
## Implementation notes
- Scripting support limited to widget APIs; many advanced features absent.
- Export works at composite level only.

View File

@@ -0,0 +1,18 @@
# Scripting in Composites (Optimized) 2025.23
Source: `help-portal-6f7a3c387b7d4efcabcf95990884d4a5.md` (Add Scripts to Your Composites).
## Add scripts to a composite
1. Hover over the composite in the Outline.
2. Click the **Edit Scripts** (⚙) icon.
3. Select an available event.
4. Write scripts for that event.
## Available APIs
- Standard composite APIs for behavior/properties.
- Interface functions defined in the composite; value help lists available functions.
## Implementation notes
- Scripts are per-event on the composite.
- Use interface functions to coordinate with host story/components.

View File

@@ -0,0 +1,640 @@
# Data Actions Technical Object (2025.23)
**Source**: [Analytics Designer API Reference - Data Action](https://help.sap.com/docs/SAP_ANALYTICS_CLOUD/00f68c2e08b941f081002fd3691d86a5/7c52c448e17c4746b6e767b8370f744e.html)
---
## Table of Contents
1. [Getting Data Action Reference](#getting-data-action-reference)
2. [Execution Methods](#execution-methods)
3. [Parameter Management](#parameter-management)
4. [Event Handlers](#event-handlers)
5. [Execution Status Monitoring](#execution-status-monitoring)
6. [Error Handling](#error-handling)
7. [Best Practices](#best-practices)
---
## Getting Data Action Reference
Data Actions are widgets that can be accessed directly by their assigned ID in scripts.
```javascript
// Direct access to Data Action widget
DataAction_1.execute();
```
**Note**: Data Actions must be added to the story canvas before they can be scripted.
---
## Execution Methods
### execute()
Executes the data action synchronously.
```javascript
// Simple execution
DataAction_1.execute();
// With parameters (set parameters before execution)
DataAction_1.setParameterValue("YEAR", "2024");
DataAction_1.setParameterValue("VERSION", "Budget");
DataAction_1.execute();
```
### executeInBackground()
Executes the data action asynchronously without blocking the UI.
```javascript
// Execute in background for long-running actions
DataAction_1.executeInBackground();
// Monitor completion with event handler
DataAction_1.onExecutionStatusUpdate = function(status) {
if (status === ExecutionStatus.Completed) {
Table_1.getDataSource().refreshData();
Application.showMessage(ApplicationMessageType.Info, "Data action completed");
}
};
```
---
## Parameter Management
### setParameterValue(parameterName, value)
Sets a parameter value for the data action.
**Parameters**:
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| parameterName | string | Yes | Name of the parameter defined in the data action |
| value | string/number | Yes | Value to pass to the parameter |
```javascript
// Set single parameter
DataAction_1.setParameterValue("COST_CENTER", "1000");
// Set multiple parameters
var parameters = {
"YEAR": "2024",
"MONTH": "12",
"SCENARIO": "Actual",
"AMOUNT": 100000
};
Object.keys(parameters).forEach(function(param) {
DataAction_1.setParameterValue(param, parameters[param]);
});
// Execute with all parameters set
DataAction_1.execute();
```
### getParameterValue(parameterName)
Gets the current value of a parameter.
```javascript
var yearValue = DataAction_1.getParameterValue("YEAR");
if (yearValue) {
console.log("Current year parameter:", yearValue);
}
```
---
## Event Handlers
### onBeforeExecute
Triggered before the data action executes.
```javascript
DataAction_1.onBeforeExecute = function() {
// Show loading indicator
Application.showBusyIndicator("Processing data action...");
// Validate parameters
var year = DataAction_1.getParameterValue("YEAR");
if (!year) {
Application.showMessage(
ApplicationMessageType.Error,
"Year parameter is required"
);
return false; // Prevent execution
}
// Log execution start
console.log("Executing data action at:", new Date());
};
```
### onAfterExecute
Triggered after the data action completes execution.
```javascript
DataAction_1.onAfterExecute = function(result) {
// Hide loading indicator
Application.hideBusyIndicator();
// Check execution result
if (result.success) {
Application.showMessage(
ApplicationMessageType.Info,
"Data action completed successfully"
);
// Refresh affected data
Table_1.getDataSource().refreshData();
// Log success
console.log("Data action executed successfully:", result);
} else {
// Handle errors
Application.showMessage(
ApplicationMessageType.Error,
"Data action failed: " + (result.message || "Unknown error")
);
console.error("Data action failed:", result);
}
};
```
### onExecutionStatusUpdate
Triggered during background execution when status changes.
```javascript
DataAction_1.onExecutionStatusUpdate = function(status) {
switch(status) {
case ExecutionStatus.Running:
console.log("Data action is running...");
break;
case ExecutionStatus.Completed:
console.log("Data action completed successfully");
Table_1.getDataSource().refreshData();
Application.showMessage(
ApplicationMessageType.Info,
"Data action completed"
);
break;
case ExecutionStatus.Failed:
console.error("Data action failed");
Application.showMessage(
ApplicationMessageType.Error,
"Data action execution failed"
);
break;
case ExecutionStatus.Cancelled:
console.log("Data action was cancelled");
Application.showMessage(
ApplicationMessageType.Warning,
"Data action cancelled by user"
);
break;
}
};
```
---
## Execution Status Monitoring
Monitor the execution state for better user experience.
```javascript
// Function to check data action status
function checkDataActionStatus() {
var isRunning = DataAction_1.isExecuting();
if (isRunning) {
console.log("Data action is currently executing");
return true;
} else {
console.log("Data action is not executing");
return false;
}
}
// Execute with status monitoring
function executeWithMonitoring() {
if (checkDataActionStatus()) {
Application.showMessage(
ApplicationMessageType.Warning,
"Data action is already running"
);
return;
}
DataAction_1.execute();
}
```
---
## Error Handling
### Try-Catch Pattern
```javascript
function safeExecute() {
try {
// Validate inputs
if (!validateInputs()) {
return;
}
// Execute data action
DataAction_1.execute();
} catch (error) {
console.error("Error executing data action:", error);
Application.showMessage(
ApplicationMessageType.Error,
"An error occurred: " + error.message
);
}
}
function validateInputs() {
// Check required parameters
var requiredParams = ["YEAR", "VERSION"];
var missingParams = [];
requiredParams.forEach(function(param) {
var value = DataAction_1.getParameterValue(param);
if (!value) {
missingParams.push(param);
}
});
if (missingParams.length > 0) {
Application.showMessage(
ApplicationMessageType.Error,
"Missing required parameters: " + missingParams.join(", ")
);
return false;
}
return true;
}
```
### Data Locking Integration
```javascript
// Check data locks before execution
function executeWithLockCheck() {
var selection = Table_1.getSelections()[0];
if (selection) {
var dataLocking = Table_1.getPlanning().getDataLocking();
var lockState = dataLocking.getState(selection);
if (lockState === DataLockingState.Locked) {
Application.showMessage(
ApplicationMessageType.Warning,
"Data is locked by another user. Cannot execute data action."
);
return;
}
}
// Proceed with execution
DataAction_1.execute();
}
```
---
## Best Practices
### 1. Parameter Validation
Always validate parameters before execution:
```javascript
function validateAllParameters() {
var params = DataAction_1.getParameters();
var isValid = true;
params.forEach(function(param) {
var value = DataAction_1.getParameterValue(param.name);
if (param.isRequired && !value) {
Application.showMessage(
ApplicationMessageType.Error,
"Required parameter '" + param.name + "' is missing"
);
isValid = false;
}
});
return isValid;
}
```
### 2. User Feedback
Provide clear feedback to users:
```javascript
function executeWithFeedback() {
// Show start message
Application.showMessage(
ApplicationMessageType.Info,
"Starting data action execution..."
);
// Set up event handlers
DataAction_1.onBeforeExecute = function() {
Application.showBusyIndicator("Executing data action...");
};
DataAction_1.onAfterExecute = function(result) {
Application.hideBusyIndicator();
if (result.success) {
Application.showMessage(
ApplicationMessageType.Success,
"Data action completed successfully!"
);
// Refresh data
Table_1.getDataSource().refreshData();
} else {
Application.showMessage(
ApplicationMessageType.Error,
"Data action failed: " + result.message
);
}
};
// Execute
DataAction_1.execute();
}
```
### 3. Batch Execution
Execute multiple data actions efficiently:
```javascript
function executeBatch() {
var dataActions = [DataAction_1, DataAction_2, DataAction_3];
var completed = 0;
var total = dataActions.length;
Application.showBusyIndicator("Executing batch actions...");
dataActions.forEach(function(action, index) {
action.onAfterExecute = function(result) {
completed++;
if (result.success) {
console.log("Action", index + 1, "completed");
} else {
console.error("Action", index + 1, "failed:", result);
}
// Check if all actions are complete
if (completed === total) {
Application.hideBusyIndicator();
Table_1.getDataSource().refreshData();
Application.showMessage(
ApplicationMessageType.Info,
"All data actions completed"
);
}
};
// Execute with slight delay to avoid overwhelming system
setTimeout(function() {
action.execute();
}, index * 100);
});
}
```
### 4. Performance Considerations
- Use `executeInBackground()` for long-running actions
- Avoid executing multiple actions simultaneously on the same data
- Refresh data sources only after all related actions complete
- Implement proper error handling to prevent UI freezing
---
## Complete Example
```javascript
// Complete example of data action management
var DataManager = {
// Initialize data action
init: function() {
// Set up event handlers
this.setupEventHandlers();
// Set default parameters
this.setDefaultParameters();
},
// Set up event handlers
setupEventHandlers: function() {
var self = this;
DataAction_1.onBeforeExecute = function() {
return self.validateBeforeExecute();
};
DataAction_1.onAfterExecute = function(result) {
self.handleAfterExecute(result);
};
DataAction_1.onExecutionStatusUpdate = function(status) {
self.handleStatusUpdate(status);
};
},
// Set default parameters
setDefaultParameters: function() {
var currentDate = new Date();
DataAction_1.setParameterValue("YEAR", currentDate.getFullYear().toString());
DataAction_1.setParameterValue("MONTH", (currentDate.getMonth() + 1).toString());
},
// Validate before execution
validateBeforeExecute: function() {
// Check data locks
if (this.checkDataLocks()) {
return false;
}
// Validate parameters
if (!this.validateParameters()) {
return false;
}
// Show loading
Application.showBusyIndicator("Executing data action...");
return true;
},
// Check data locks
checkDataLocks: function() {
var selection = Table_1.getSelections()[0];
if (selection) {
var lockState = Table_1.getPlanning().getDataLocking().getState(selection);
if (lockState === DataLockingState.Locked) {
Application.showMessage(
ApplicationMessageType.Warning,
"Data is locked by another user"
);
return true;
}
}
return false;
},
// Validate parameters
validateParameters: function() {
var required = ["YEAR", "SCENARIO"];
var missing = [];
required.forEach(function(param) {
var value = DataAction_1.getParameterValue(param);
if (!value) {
missing.push(param);
}
});
if (missing.length > 0) {
Application.showMessage(
ApplicationMessageType.Error,
"Missing parameters: " + missing.join(", ")
);
return false;
}
return true;
},
// Handle after execution
handleAfterExecute: function(result) {
Application.hideBusyIndicator();
if (result.success) {
this.onSuccess(result);
} else {
this.onError(result);
}
},
// Handle success
onSuccess: function(result) {
Application.showMessage(
ApplicationMessageType.Success,
"Data action completed successfully"
);
// Refresh data
Table_1.getDataSource().refreshData();
// Log success
console.log("Data action success:", result);
},
// Handle error
onError: function(result) {
Application.showMessage(
ApplicationMessageType.Error,
"Data action failed: " + (result.message || "Unknown error")
);
console.error("Data action error:", result);
},
// Handle status updates
handleStatusUpdate: function(status) {
console.log("Data action status:", status);
switch(status) {
case ExecutionStatus.Running:
console.log("Execution in progress...");
break;
case ExecutionStatus.Completed:
console.log("Execution completed");
break;
case ExecutionStatus.Failed:
console.error("Execution failed");
break;
case ExecutionStatus.Cancelled:
console.log("Execution cancelled");
break;
}
},
// Execute data action
execute: function(parameters) {
// Set parameters if provided
if (parameters) {
Object.keys(parameters).forEach(function(key) {
DataAction_1.setParameterValue(key, parameters[key]);
});
}
// Execute
DataAction_1.execute();
},
// Execute in background
executeInBackground: function(parameters) {
// Set parameters if provided
if (parameters) {
Object.keys(parameters).forEach(function(key) {
DataAction_1.setParameterValue(key, parameters[key]);
});
}
// Execute in background
DataAction_1.executeInBackground();
}
};
// Usage example:
// Initialize on story load
DataManager.init();
// Execute with custom parameters
DataManager.execute({
"YEAR": "2024",
"SCENARIO": "Budget",
"AMOUNT": 100000
});
```
---
## Notes
- Always handle data locks before executing data actions that modify data
- Use `executeInBackground()` for long-running actions to avoid UI freezing
- Provide clear user feedback through loading indicators and messages
- Validate all required parameters before execution
- Consider implementing retry logic for failed executions
- Refresh affected data sources after successful data action execution

View File

@@ -0,0 +1,13 @@
# Data Change Insights Technical Object (2025.23)
Source: `help-portal-1c28c2d941ae44769666b4aee00eacce.md`.
## Purpose
Detect and surface insights from data changes.
## Capabilities
- Track changes and expose them via scripting for user notifications or workflows.
## Notes
- Use with planning scenarios where change awareness is critical.

View File

@@ -0,0 +1,19 @@
# Debugging Scripts (2025.23)
Source: `help-portal-704272da511143d686ca6949040b9a68.md`.
## Locate scripts in browser devtools
- Run the app once, open **Sources** in Chrome DevTools.
- Search `Ctrl+P` for files named `<WIDGET_NAME>.<FUNCTION_NAME>.js` inside folder `<APPLICATION_NAME>` under `sandbox.worker.main...`.
- `onInitialization` scripts use widget name `Application`.
## Breakpoints
- Add breakpoints on line numbers in DevTools; blue marker indicates active breakpoint.
- Multiple breakpoints allowed; click to remove.
## Debug mode
- Add `debugger;` statements in scripts.
- Launch app with URL parameter `debug=true` to pause automatically at those statements:
`[https://<TENANT>/.../application/<APP_ID>/?mode=present&debug=true`](https://<TENANT>/.../application/<APP_ID>/?mode=present&debug=true`)
- Comments are preserved in transformed JS to aid identification.

View File

@@ -0,0 +1,425 @@
# SAC Debugging and Browser Tools Reference
Complete guide for debugging Analytics Designer scripts using browser developer tools.
---
## Table of Contents
1. [Console Logging](#console-logging)
2. [Browser Debugging](#browser-debugging)
3. [Debug Mode](#debug-mode)
4. [Breakpoints](#breakpoints)
5. [Script Editor Tools](#script-editor-tools)
6. [R Visualization Debugging](#r-visualization-debugging)
7. [Performance Logging](#performance-logging)
8. [Common Issues](#common-issues)
---
## Console Logging
The primary debugging method using `console.log()`.
### Basic Usage
```javascript
var nth = 1;
console.log("Hello World, " + nth.toString());
// Output: Hello World, 1
```
### Logging Objects
```javascript
// Log complex objects
var selections = Chart_1.getSelections();
console.log(selections);
// Log data source variables
var ds = Chart_1.getDataSource();
console.log(ds.getVariables());
// Log member info
var members = ds.getMembers("Location", 5);
console.log(members);
```
### Viewing Console Output
1. Run the analytic application
2. Press **F12** or **Ctrl+Shift+J** to open Developer Tools
3. Go to **Console** tab
4. Filter by "Info" type if needed
5. Expand `sandbox.worker.main.*.js` entries for your logs
**Note**: Scripts are stored as minified variables and aren't directly debuggable in the console. Use `console.log()` to inspect values.
---
## Browser Debugging
Detailed steps for debugging Analytics Designer scripts in Chrome browser.
**Note**: Analytics Designer supports debugging only in **Chrome** browser.
**Note**: Scripts are transformed before execution, so they won't look exactly like the code in the script editor.
**Note**: Scripts must run at least once in the current session to appear in dev tools.
### Script Naming Convention
Analytics Designer scripts follow a specific naming pattern:
- **Folder**: `<APPLICATION_NAME>`
- **Script**: `<WIDGET_NAME>.<FUNCTION_NAME>.js`
**Example**:
- Application: `My Demo Application`
- Widget: `Button_1` with `onClick` event
- Script name: `Button_1.onClick.js`
- Folder: `My_Demo_Application`
**Note**: Special characters (except `-` and `.`) are replaced with underscore (`_`).
### Find Script by Name (Quick Method)
1. Press **F12** to open Developer Tools
2. Press **Ctrl+P**
3. Start typing part of the script name (e.g., `Button_1.on`)
4. Select from the list
### Find Script by File Tree
1. Press **F12** to open Developer Tools
2. Select **Sources** tab
3. Open node starting with `sandbox.worker.main`
4. Open **AnalyticApplication** node
5. Find folder with your application name
6. Scripts executed in current session appear here
**File Tree Structure**:
```
sandbox.worker.main.js
└── AnalyticApplication
└── My_Demo_Application
├── Application.onInitialization.js
├── Button_1.onClick.js
└── ScriptObject_1.returnText.js
```
---
## Debug Mode
Enable enhanced debugging features with debug mode.
### Enabling Debug Mode
Append `;debug=true` to the application URL:
```
[https://your-tenant.sapanalytics.cloud/app.html?story=STORY_ID;debug=true](https://your-tenant.sapanalytics.cloud/app.html?story=STORY_ID;debug=true)
```
### Debug Mode Features
1. **debugger; statement support**: Pause execution at specific lines
2. **Comment preservation**: Comments in scripts are kept in transformed code
3. **Script name suffix**: Scripts get `-dbg` suffix (e.g., `Button_1.onClick-dbg.js`)
### Using debugger; Statement
With debug mode enabled, add `debugger;` to pause execution:
```javascript
/*
* This is a block comment
*/
// Debug point
debugger;
// This is a comment
return Text_1.getPlainText();
```
**Advantages over breakpoints**:
- Define pause location while writing code
- Don't need to find script in dev tools first
- Persists across sessions
---
## Breakpoints
Set breakpoints to pause script execution at specific lines.
### Setting a Breakpoint
1. Open Developer Tools (F12)
2. Navigate to your script in Sources tab
3. Click on the **line number** where you want to pause
4. A **blue marker** appears on that line
### Multiple Breakpoints
Add several breakpoints to pause at different points:
- Each clicked line number gets a blue marker
- Script pauses at each breakpoint when executed
### Removing a Breakpoint
Click on the blue marker to remove it.
### Execution Controls
When paused at a breakpoint:
| Button | Action |
|--------|--------|
| Resume (F8) | Continue execution |
| Step Over (F10) | Execute current line, move to next |
| Step Into (F11) | Enter function call |
| Step Out (Shift+F11) | Exit current function |
### Inspecting Variables
While paused:
1. Hover over variables to see values
2. Use **Scope** panel to see all variables
3. Use **Watch** panel to track specific expressions
4. Use **Console** to evaluate expressions
---
## Script Editor Tools
Built-in debugging features in the Analytics Designer script editor.
### Errors and Warnings
The Info panel displays validation results:
1. Open **Info** panel at bottom of designer
2. Click **Errors** tab
3. Search or filter (errors only, warnings only, both)
4. Double-click error to open script and jump to location
**Visual Indicators**:
- **Red underline**: Error
- **Orange underline**: Warning
- **Red marker**: Error at line number
- **Orange marker**: Warning at line number
### Find References
Find all places where a widget or scripting object is used:
1. Right-click object in **Outline**
2. Select **Find References**
3. Results appear in **Reference list** tab of Info panel
### Renaming with Refactoring
When you rename objects, all references update automatically:
**Renamable Objects**:
- Widgets
- Gadgets
- Script variables
- Script objects
- Script object functions
- Function arguments
**Renaming Methods**:
1. **Via Outline**:
- Select object in Outline
- Click **...** (More) button
- Select **Rename**
- Enter new name
2. **Via Styling Panel**:
- Select object in Outline
- Enter new name in **Name** field
- Click **Done** (for script objects)
3. **Function Arguments**:
- Select script object function
- Click **Edit** button for argument
- Enter new name
- Click **Done**
---
## R Visualization Debugging
Debug R scripts and their JavaScript integration.
### R Widget Runtime Environments
R widgets have two separate environments:
1. **R environment**: Server-side, in R engine
2. **JavaScript environment**: Browser-side, with other widget scripts
### Execution Order
**On Startup**:
- R script runs
- `onResultChanged` JavaScript event does NOT run (initial state)
**On Data Change**:
1. R script runs first
2. `onResultChanged` JavaScript event runs
### Reading R Environment from JavaScript
```javascript
// R script creates: gmCorrelation <- cor(grossMargin, grossMarginPlan)
// JavaScript reads the value
var nCor = this.getEnvironmentValues().getNumber("gmCorrelation");
var sCor = nCor.toString();
console.log("Margin Correlation: " + sCor);
```
### Writing to R Environment from JavaScript
```javascript
// Set R environment variable from JavaScript
RVisualization_1.getInputParameters().setNumber("userSelection", 0);
```
### Available Methods
**Reading**:
- `getEnvironmentValues().getNumber(variableName)`
- `getEnvironmentValues().getString(variableName)`
**Writing**:
- `getInputParameters().setNumber(variableName, value)`
- `getInputParameters().setString(variableName, value)`
---
## Performance Logging
Enable detailed performance tracking for optimization.
### Enabling Performance Logging
Add URL parameter:
```
?APP_PERFORMANCE_LOGGING=true
```
### Viewing Performance Data
In browser console:
```javascript
// Get all performance entries for the application
window.sap.raptr.getEntriesByMarker("(Application)")
.filter(e => e.entryType === 'measure')
.sort((a,b) => (a.startTime + a.duration) - (b.startTime + b.duration));
```
### Performance Entry Properties
| Property | Description |
|----------|-------------|
| `name` | Operation name |
| `entryType` | Entry type (measure, mark) |
| `startTime` | Start timestamp |
| `duration` | Execution time in ms |
---
## Common Issues
### Script Not Appearing in Dev Tools
**Problem**: Can't find script in Sources tab.
**Solution**: Script must run at least once. Trigger the event (click button, select chart, etc.) then refresh dev tools.
### Breakpoint Not Hit
**Problem**: Breakpoint set but execution doesn't pause.
**Solution**:
1. Ensure script is for the correct event
2. Check if the event actually triggers
3. Try using `debugger;` statement instead
### debugger; Statement Ignored
**Problem**: `debugger;` doesn't pause execution.
**Solution**: Enable debug mode by adding `;debug=true` to URL.
### Console.log Not Showing
**Problem**: `console.log()` output not visible.
**Solution**:
1. Open correct browser (Chrome)
2. Check Console tab
3. Clear filters that might hide output
4. Look in `sandbox.worker.main.*.js` entries
### Variables Show as undefined
**Problem**: Variables appear undefined when inspected.
**Solution**: Scripts are transformed before execution. Variable names may differ. Use `console.log()` to inspect actual values.
### Script Looks Different in Debugger
**Problem**: Code in debugger doesn't match script editor.
**This is expected**: Analytics Designer transforms scripts before browser execution. The logic is the same, but syntax may differ.
---
## Debugging Checklist
Before debugging:
- [ ] Using Chrome browser
- [ ] Developer Tools open (F12)
- [ ] Script has been executed at least once
- [ ] For `debugger;`, debug mode enabled (`;debug=true`)
- [ ] Console tab open for `console.log()` output
- [ ] Sources tab open for breakpoints
During debugging:
- [ ] Start with simple `console.log()` to verify code runs
- [ ] Check Info panel for script errors first
- [ ] Use Find References to understand dependencies
- [ ] Test incrementally (every 5-10 lines)
- [ ] Check variable types match expected
---
## Keyboard Shortcuts Summary
| Shortcut | Action |
|----------|--------|
| F12 | Open Developer Tools |
| Ctrl+Shift+J | Open Console directly |
| Ctrl+P | Quick file search (in Sources) |
| F8 | Resume execution |
| F10 | Step over |
| F11 | Step into |
| Shift+F11 | Step out |
For script editor shortcuts, see: [https://help.sap.com/doc/00f68c2e08b941f081002fd3691d86a7/release/en-US/68dfa2fd057c4d13ad2772825e83b491.html](https://help.sap.com/doc/00f68c2e08b941f081002fd3691d86a7/release/en-US/68dfa2fd057c4d13ad2772825e83b491.html)
---
**Source**: SAP Analytics Designer Development Guide - Chapter 4: Scripting in Analytics Designer
**Last Updated**: 2025-11-23

View File

@@ -0,0 +1,33 @@
# Explorer & Smart Insights (2025.23)
Source: `help-portal-a7bd9775a97748d685464b2ef58bce21.md` (Work with Explorer and Smart Insights).
## Launch Explorer
- Manual: Save/run app → select chart/table → More Actions → Open Explorer.
- Script API: launch via script; can add extra dimensions/measures to exploration scope.
## Explorer features
- Change dimensions/measures, chart types; show/hide elements.
- Table sorting; export data to file.
- “Open in New Story” (enable in widget styling quick menus).
## Apply explorer results
1. Create **Data Explorer Configuration** technical object (Outline → Scripting).
2. Add custom menu items in Menu Settings.
3. Implement `onMenuItemSelect()` script.
4. Users pick custom menu items in explorer visualization area.
## Saving results
- Bookmark the application.
- Publish to PDF.
## Smart Insights (requires Explorer enabled)
1. Select a data point (chart) or cell (table).
2. Choose **Smart Insights** in context menu.
3. Insights include correlations, exceptions, clusters, links, predictions.
4. Continue exploration or create story (if Open in New Story enabled).
## Implementation notes
- Smart Insights depends on Explorer being enabled.
- Custom menu items let users apply exploration results to other widgets.

View File

@@ -0,0 +1,14 @@
# Export to PDF Technical Object (2025.23)
Source: `help-portal-c9ab6a29d2bc4182be740685cf7ebb20.md`.
## Purpose
Trigger PDF export programmatically for analytic applications.
## Capabilities
- Configure export scope and options via API.
- Integrate export into custom UI (buttons, workflows).
## Notes
- Consider export limitations (see `analytics-designer-restrictions.md`).

View File

@@ -0,0 +1,13 @@
# Export to PowerPoint Technical Object (2025.23)
Source: `help-portal-1f75c4965591451c87e7cf89a4c50eeb.md`.
## Purpose
Programmatic export of application content to PowerPoint.
## Capabilities
- Configure export settings and trigger via scripts.
## Notes
- Align slide output expectations with widget visibility and layouts.

26
references/geomap.md Normal file
View File

@@ -0,0 +1,26 @@
# Geo Map Widget (2025.23)
Source: `help-portal-7200e99b1cdc4463bf5f60ab3d2ab8ae.md` (Geo Map).
## Properties
- **Name**: unique string, default `GeoMap_1`.
## Quick menu properties (defaults)
- Drill (Enabled) filter selected data points.
- Full Screen (Enabled).
- Create Story from Widget (Disabled).
- Filter/Exclude (Enabled).
### Visibility control
- Main toggle “Visible in Runtime” plus individual checkboxes for each quick menu item.
## Scripting support
- Change layers.
- Manipulate properties.
- Handle geo map events.
## Implementation notes
- Works in stories and analytic applications.
- Quick menu items can be controlled individually or globally.
- See Analytics Designer API Reference for detailed APIs.

View File

@@ -0,0 +1,314 @@
# iFrame Embedding and Lumira Designer Migration
Reference documentation for iFrame PostMessage communication and migration guidance from Lumira Designer to SAP Analytics Cloud.
---
## Table of Contents
1. [iFrame Embedding with PostMessage](#iframe-embedding-with-postmessage)
2. [Differences Between SAP Analytics Cloud and Lumira Designer](#differences-between-sap-analytics-cloud-and-lumira-designer)
---
## iFrame Embedding with PostMessage
When your analytic application is embedded in an iFrame, it can communicate bidirectionally with the host web application using JavaScript PostMessage.
### Application Events for Embedded Scenarios
SAC analytic applications have two primary application events:
1. **onInitialization**: Runs once when the application is instantiated by a user
2. **onPostMessageReceived**: Triggered when the host application sends a PostMessage
### onPostMessageReceived Event
This event fires whenever the host application makes a PostMessage call into the embedded analytic application.
**Reference**: [https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage)
### Event Parameters
The `onPostMessageReceived` event provides two input parameters:
| Parameter | Type | Description |
|-----------|------|-------------|
| `origin` | string | The domain of the host application |
| `message` | any | The message content passed via PostMessage |
### origin Parameter
The `origin` parameter contains the domain of the host application.
**Important Security Notes**:
- iFrame contents don't need to be in the same origin as the host app
- Same-origin policies may still be in effect
- Be careful about **clickjacking attacks** and **malicious iFrame hosts**
- **Always validate** the `origin` parameter to ensure the iFrame host is expected
**Security Best Practice**:
```javascript
// onPostMessageReceived event
if (origin !== "[https://trusted-domain.com](https://trusted-domain.com)") {
console.log("Unauthorized origin: " + origin);
return; // Reject messages from unknown origins
}
// Process message from trusted origin
processMessage(message);
```
### message Parameter
The `message` parameter contains the standard JavaScript PostMessage content passed into SAP Analytics Cloud.
**Characteristics**:
- Does **not** follow any specific format
- Could contain almost any data type
- Encoded using the **structured clone algorithm**
- Some documented restrictions on what can and can't be encoded
### Structured Clone Algorithm
The structured clone algorithm has some limitations on what can be cloned:
**Supported**:
- Primitive types (string, number, boolean)
- Arrays
- Plain objects
- Date objects
- Map, Set, ArrayBuffer, TypedArray
**Not Supported**:
- Functions
- DOM nodes
- Symbols
- Error objects
- Property descriptors, setters, getters
### Example: Receiving PostMessage
```javascript
// onPostMessageReceived event handler
// Parameters: origin, message
// Security check
if (origin !== "[https://my-portal.company.com](https://my-portal.company.com)") {
console.log("Rejected message from: " + origin);
return;
}
// Log the received message
console.log("Received message from: " + origin);
console.log("Message content: " + JSON.stringify(message));
// Process message based on type
if (message.type === "filter") {
// Apply filter from host application
var dimension = message.dimension;
var value = message.value;
Chart_1.getDataSource().setDimensionFilter(dimension, value);
} else if (message.type === "refresh") {
// Refresh data
Chart_1.getDataSource().refreshData();
} else if (message.type === "navigate") {
// Handle navigation request
var pageId = message.pageId;
// Navigate to page...
}
```
### Host Application Example
In the host web application (outside SAP Analytics Cloud):
```javascript
// Get reference to the SAC iFrame
var sacFrame = document.getElementById("sac-iframe");
// Send filter command to embedded SAC application
sacFrame.contentWindow.postMessage({
type: "filter",
dimension: "Region",
value: "EMEA"
}, "[https://your-tenant.sapanalytics.cloud](https://your-tenant.sapanalytics.cloud)");
// Send refresh command
sacFrame.contentWindow.postMessage({
type: "refresh"
}, "[https://your-tenant.sapanalytics.cloud](https://your-tenant.sapanalytics.cloud)");
```
### Security Recommendations
1. **Always validate origin**: Check that messages come from expected domains
2. **Use specific target origins**: Don't use `"*"` as target origin
3. **Validate message structure**: Ensure message format matches expectations
4. **Log suspicious activity**: Track rejected messages for security monitoring
5. **Implement allowlist**: Maintain list of trusted origins
```javascript
// Recommended: Origin allowlist pattern
var trustedOrigins = [
"[https://portal.company.com",](https://portal.company.com",)
"[https://intranet.company.com"](https://intranet.company.com")
];
// In onPostMessageReceived
var isTrusted = false;
for (var i = 0; i < trustedOrigins.length; i++) {
if (origin === trustedOrigins[i]) {
isTrusted = true;
break;
}
}
if (!isTrusted) {
console.log("Blocked message from untrusted origin: " + origin);
return;
}
```
---
## Differences Between SAP Analytics Cloud and Lumira Designer
Design Studio / Lumira Designer and SAP Analytics Cloud, analytics designer have broadly similar scripting environments. Both are JavaScript-based and perform similar missions. Analytics designer's scripting framework was informed by experience with Design Studio.
However, there are important differences to understand when migrating.
### Execution Environment
| Aspect | Lumira Designer | Analytics Designer (SAC) |
|--------|----------------|--------------------------|
| **Script Execution** | Server-side | Browser-side (client) |
| **Proximity** | Close to the data | Close to the user |
| **JavaScript Engine** | Server JavaScript engine | Browser native JavaScript |
**Key Implication**: This "close-to-data vs close-to-user" philosophical difference affects how you design scripts.
### Copy-and-Paste Compatibility
**Analytics Designer is NOT copy-and-paste compatible with Lumira Designer.**
Scripts written for Lumira Designer will likely need modification to work in Analytics Designer. This is partially a consequence of the architectural differences.
### Data Source Access
| Feature | Lumira Designer | Analytics Designer |
|---------|----------------|-------------------|
| Standalone Data Sources | Available as global variables | Hidden within data-bound widgets |
| Access Method | Direct access by name | Must use `getDataSource()` method |
**Lumira Designer**:
```javascript
// Direct access to data source
DS_1.setFilter("Location", "USA");
```
**Analytics Designer**:
```javascript
// Access through widget
var ds = Chart_1.getDataSource();
ds.setDimensionFilter("Location", "USA");
```
**Note**: When standalone data sources become available in SAC, you'll be able to access them as global variables, similar to Lumira.
### Type System Differences
| Feature | Lumira Designer | Analytics Designer |
|---------|----------------|-------------------|
| Type Conversion | More lenient | Strict (no automatic conversion) |
| Equality Operator | `==` works across types | `==` only valid with same types |
| Recommended Operator | `==` or `===` | `===` (strict equality) |
**Lumira Designer** (allowed):
```javascript
var aNumber = 1;
if (aNumber == "1") { // Works - auto type coercion
// Executes
}
```
**Analytics Designer** (must use explicit conversion or strict equality):
```javascript
var aNumber = 1;
// Option 1: Strict equality with correct type
if (aNumber === 1) { // Recommended
// Executes
}
// Option 2: Explicit conversion
if (aNumber.toString() === "1") {
// Executes
}
// WRONG: Different types with ==
// if (aNumber == "1") { } // Error in SAC!
```
### API Method Names
Some API methods have different names between platforms:
| Operation | Lumira Designer | Analytics Designer |
|-----------|----------------|-------------------|
| Set filter | `setFilter()` | `setDimensionFilter()` |
| Remove filter | `removeFilter()` | `removeDimensionFilter()` |
| Get filter | `getFilter()` | `getDimensionFilters()` |
### Summary of Key Migration Steps
When migrating from Lumira Designer to SAP Analytics Cloud:
1. **Update data source access**: Change direct data source references to `widget.getDataSource()`
2. **Fix type conversions**: Add explicit `.toString()` or other conversion methods
3. **Update equality operators**: Change `==` to `===` for comparisons, especially with different types
4. **Update API method names**: Replace Lumira-specific methods with SAC equivalents
5. **Review script location**: Consider that scripts now run in browser, not on server
6. **Test thoroughly**: Behavior may differ due to execution environment changes
### Example Migration
**Original Lumira Designer Script**:
```javascript
// Lumira Designer
DS_1.setFilter("Year", selectedYear);
var members = DS_1.getMembers("Location");
var count = members.length;
console.log("Count: " + count); // Auto-converts count to string
```
**Migrated Analytics Designer Script**:
```javascript
// Analytics Designer (SAC)
var ds = Chart_1.getDataSource();
ds.setDimensionFilter("Year", selectedYear);
var members = ds.getMembers("Location");
var count = members.length;
console.log("Count: " + count.toString()); // Explicit conversion required
```
---
## Additional Resources
- **SAP Analytics Designer Documentation**: [https://help.sap.com/docs/SAP_ANALYTICS_CLOUD](https://help.sap.com/docs/SAP_ANALYTICS_CLOUD)
- **Lumira Designer Migration Guide**: Check SAP Help Portal for official migration documentation
- **PostMessage API**: [https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage)
---
**Source**: SAP Analytics Designer Development Guide - Chapter 4: Scripting in Analytics Designer (Sections 4.2.3.1 and 4.10)
**Last Updated**: 2025-11-23

View File

@@ -0,0 +1,15 @@
# Input Fields & Text Areas (2025.23)
Source: `help-portal-c82546f4ec8546698b69b426c08d1450.md`.
## Overview
Guidance for scripting input fields and text areas in analytic applications.
## Key points
- Configure input controls and text areas for user entry.
- Validate and bind entered values to variables or data sources.
- Handle events for change/submit to trigger downstream logic.
## Notes
- Use appropriate data types; sanitize input before applying to filters or actions.

View File

@@ -0,0 +1,11 @@
# MemberInfo Performance Tip (2025.23)
Source: `help-portal-e821badae672480193d58608128f5bf7.md`.
## Purpose
Guidance to improve performance when using `MemberInfo` objects.
## Notes
- Use efficient access patterns to avoid unnecessary backend calls.
- Cache member info where possible when used repeatedly.

View File

@@ -0,0 +1,14 @@
# Multi Actions Technical Object (2025.23)
Source: `help-portal-3bbc80d4df134f2887a037a85a4ea700.md`.
## Purpose
Run multiple actions in sequence/parallel as a packaged operation.
## Capabilities
- Configure and execute multi actions from scripts.
- Combine data actions and other steps for end-to-end automation.
## Notes
- Monitor execution status; provide user feedback during long runs.

View File

@@ -0,0 +1,14 @@
# OData Service (2025.23)
Source: `help-portal-05a5e8a72280470e9e6c174c35bc993a.md` (and duplicate -2).
## Purpose
OData service technical object for integrating external data via OData in analytic applications.
## Capabilities
- Configure service endpoint and consume OData feeds.
- Use in scripts to read/manipulate OData-sourced data.
## Notes
- Duplicate extract exists (`...-2.md`); content identical.

View File

@@ -0,0 +1,14 @@
# Table onAfterDataEntryProcess Event (2025.23)
Source: `help-portal-6d801f5209df484fbfef6a4a90ce53ff.md`.
## Purpose
Event hook fired after data entry processing in tables.
## Usage
- Attach scripts to react after user data entry is processed (for example, validations, refreshes, follow-up actions).
- Suitable for planning tables to confirm writebacks or update dependent widgets.
## Notes
- Keep handlers lightweight to avoid post-entry lag.

View File

@@ -0,0 +1,14 @@
# Optimize Type Libraries (2025.23)
Source: `help-portal-919e7adeed7744ffb96539fce4f01945.md`.
## Purpose
Improve script editor performance and code completion by optimizing loaded type libraries.
## Guidance
- Prune unused libraries to speed up value help and reduce noise.
- Keep frequently used objects/types loaded for productivity.
## Implementation notes
- Use Help Portal guidance to manage type library settings within Analytics Designer.

View File

@@ -0,0 +1,15 @@
# Pattern-Based Functions (2025.23)
Source: `help-portal-9af108a1175845b2be00d49c92bd81ad.md`.
## Overview
Pattern-based functions leverage ML to recognize text patterns and return structured outputs.
## Usage examples
- Detect email, URL, phone patterns in text fields.
- Validate or extract pattern matches for downstream logic.
## Notes
- Use for user input validation and transformation.
- Combine with error messaging when patterns are not matched.

View File

@@ -0,0 +1,14 @@
# Planning Model Technical Object (2025.23)
Source: `help-portal-edb6447bac6346b3a4908ffd5956b796.md`.
## Purpose
Expose planning model interactions via a technical object for scripts.
## Capabilities
- Access model metadata and operations in analytic applications.
- Coordinate with planning APIs for data entry, locking, and version handling.
## Notes
- Use alongside table planning APIs for end-to-end planning workflows.

View File

@@ -0,0 +1,21 @@
# Script Editor Basics (2025.23)
Source: `help-portal-2560f7cb6c2745dab84d2e12652a12b4.md` (Write Scripts in Script Editor).
## Purpose
Write JavaScript-based scripts on widgets/pages/popups to drive interactivity in analytic applications or optimized stories.
## Open the editor
1. In Outline, hover widget/page/popup → **Edit Scripts** (gear).
2. If multiple events exist, choose the event (for example, `onSelect`, `onInitialization`).
3. Editor opens in new tab titled `<Widget> - <Event>`.
## Authoring pattern
`<ComponentVariable>.<function>(<arguments>);`
## Notes
- View available objects/functions in Analytics Designer or Optimized Story Experience API references.
- Edit Scripts also available via widget **More Actions** menu.
- Related indicator (gear) shows when scripts exist; run in view mode to test.
- See also: script variables (`script-variables.md`), script objects (`script-objects.md`), keyboard shortcuts (Help Portal).

View File

@@ -0,0 +1,17 @@
# Script Objects (2025.23)
Source: `help-portal-c3993b39e8564fe3aad3dadae90f2e7e.md`.
## Purpose
Reusable functions decoupled from widget events; callable across the application.
## Create
1. Outline → Script Objects → (+)
2. Define functions inside the object file.
3. Call from events: `MyScriptObject.myFunction(args);`
## Best practices
- Group related helpers per domain (filters, formatting, navigation).
- Avoid UI-specific IDs inside script objects; pass them as parameters.
- Keep functions small; document expected inputs/outputs.

View File

@@ -0,0 +1,15 @@
# Script Performance Popup (2025.23)
Source: `help-portal-39b680bd2d914f0cbeb81638df3c9084.md`.
## Purpose
Analyze script execution performance in analytic applications.
## Usage
- Open the performance popup to inspect execution time and bottlenecks of recent script runs.
- Use after complex interactions to spot slow functions.
## Notes
- Combine with batching/pause-refresh patterns to improve slow sections.
- Re-run scenarios after optimization to compare results.

View File

@@ -0,0 +1,20 @@
# Script Variables (2025.23)
Source: `help-portal-82bbae6b7005482c87bfd59a91735b45.md`.
## Types & scope
- Global variables defined in Outline → Global Variables (String, Integer, Number, Boolean; arrays allowed).
- Runtime scope across application; accessible from any script.
- URL parameter support: prefix variable with `p_` to read from query string.
## Usage
- Read/write directly: `MyGlobalVar = "value";`
- Use as dynamic text sources (see `text-widget.md`).
- Use in filters, bindings, and calculations.
## URL parameter pattern
- `...?p_MyVar=North` sets global variable `MyVar` at startup.
## Notes
- Keep naming consistent; document variable intent in Outline description.

View File

@@ -0,0 +1,509 @@
# SAC Scripting Language Fundamentals
Complete reference for the Analytics Designer scripting language based on official SAP documentation.
---
## Table of Contents
1. [Language Overview](#language-overview)
2. [Type System](#type-system)
3. [Variables and Scope](#variables-and-scope)
4. [Control Flow](#control-flow)
5. [Loops](#loops)
6. [Operators](#operators)
7. [Built-in Objects](#built-in-objects)
8. [Arrays](#arrays)
9. [Method Chaining](#method-chaining)
10. [Script Runtime Security](#script-runtime-security)
---
## Language Overview
The Analytics Designer scripting language is a **limited subset of JavaScript** with the following characteristics:
- Extended with a logical type system enforcing **type safety at design time**
- Executes natively in the browser as true JavaScript
- All scripts run and validate against **strict mode**
- Some advanced JavaScript features are hidden
- Scripts are tied to **events** or **global script objects**
### What Makes It Different from Standard JavaScript
| Feature | Standard JavaScript | Analytics Designer |
|---------|--------------------|--------------------|
| Typing | Weak, dynamic | Strong, static |
| Type conversion | Automatic | Must be explicit |
| Variable reuse | Can change type | Type is fixed |
| External libraries | Can import | Not supported |
| DOM access | Full access | Blocked |
| Global variables | Accessible | Isolated |
---
## Type System
### Strong and Static Typing
Analytics Designer enforces strict types to enable powerful tooling (code completion, validation).
**Key Rules**:
1. Once a variable has a type, it **cannot change**
2. Variable names **cannot be reused** for different types
3. Type conversions must be **explicit**
### No Automatic Type Casting
Standard JavaScript allows implicit type coercion:
```javascript
// Valid JavaScript, but ERROR in Analytics Designer
var nth = 1;
console.log("Hello World, " + nth); // Auto-converts nth to string
```
In Analytics Designer, you must explicitly cast:
```javascript
// Correct: Explicit type conversion
var nth = 1;
console.log("Hello World, " + nth.toString());
```
### Type Declaration
Variables are declared with `var` and their type is inferred from the initial value:
```javascript
var myString = "Hello"; // Type: string
var myNumber = 42; // Type: integer
var myDecimal = 3.14; // Type: number
var myBoolean = true; // Type: boolean
var myArray = ["a", "b"]; // Type: string[]
```
---
## Variables and Scope
### Local Variables
Declared within event handlers or functions, scoped to that block:
```javascript
// In onSelect event
var selectedValue = Chart_1.getSelections()[0];
var filterValue = selectedValue["Location"];
```
### Global Variables
Created via Outline panel → Script Variables → (+). Available across entire application:
```javascript
// Access global variable from any script
GlobalVariable_1 = "new value";
var currentValue = GlobalVariable_1;
```
### Script Variables in Calculated Measures
Script variables can be referenced in calculated measures for what-if simulations:
```javascript
// Formula: [Gross_Margin] * [@ScriptVariable_Rate]
// Change ScriptVariable_Rate via script to see updated results
```
### URL Parameters
Prefix global variable name with `p_` to pass values via URL:
```
[https://your-sac-tenant.com/app?p_myVariable=value](https://your-sac-tenant.com/app?p_myVariable=value)
```
---
## Control Flow
### if/else Statements
Standard syntax, but remember type safety rules:
```javascript
if (nth === 1) {
console.log("if...");
} else if (nth < 3) {
console.log("else if...");
} else {
console.log("else...");
}
```
### switch Statements
Standard JavaScript switch is supported:
```javascript
switch (i) {
case 0:
day = "Zero";
break;
case 1:
day = "One";
break;
case 2:
day = "Two";
break;
default:
day = "Unknown";
}
```
### break Statement
Use `break` to exit loops and switch statements.
---
## Loops
### for Loop
**Important**: You must explicitly declare the loop iterator.
```javascript
// ERROR: Iterator not declared
for (i = 0; i < 3; i++) {
console.log(i.toString());
}
// CORRECT: Iterator declared with var
for (var i = 0; i < 3; i++) {
console.log(i.toString());
}
```
### while Loop
Fully supported:
```javascript
var nth = 1;
while (nth < 3) {
console.log("Hello while, " + nth.toString());
nth++;
}
```
### for...in Loop
Iterate over object properties (useful for selections):
```javascript
var selection = {
"Color": "red",
"Location": "GER"
};
for (var propKey in selection) {
var propValue = selection[propKey];
console.log(propKey + ": " + propValue);
}
```
**Note**: `foreach` iterators are **not supported**.
---
## Operators
### Equality Operators
Analytics Designer supports two equality operators with important differences:
| Operator | Name | Behavior |
|----------|------|----------|
| `===` | Triple equals (strict) | Compares value AND type |
| `==` | Double equals | Only valid if both sides have same static type |
**Triple Equals (Recommended)**:
```javascript
var aNumber = 1;
if (aNumber === "1") {
// FALSE: Different types (number vs string)
}
```
**Double Equals**:
```javascript
var aNumber = 1;
if (aNumber == "1") {
// TRUE in standard JS, but ERROR in Analytics Designer
// unless both sides have same static type
}
```
**Best Practice**: Always use triple equals (`===`) for comparisons.
---
## Built-in Objects
Analytics Designer supports standard JavaScript built-in objects:
### Math
```javascript
var max = Math.max(10, 20);
var min = Math.min(10, 20);
var rounded = Math.round(3.7);
var random = Math.random();
var power = Math.pow(2, 3); // 8
var sqrt = Math.sqrt(16); // 4
```
### Date
```javascript
var now = new Date();
var year = now.getFullYear();
var month = now.getMonth(); // 0-11
var day = now.getDate();
var formatted = now.toISOString();
```
### Number
```javascript
var num = 42;
var str = num.toString();
var fixed = (3.14159).toFixed(2); // "3.14"
```
### String
```javascript
var str = "Hello World";
var upper = str.toUpperCase();
var lower = str.toLowerCase();
var sub = str.substring(0, 5); // "Hello"
var index = str.indexOf("World"); // 6
var replaced = str.replace("World", "SAC");
var split = str.split(" "); // ["Hello", "World"]
var len = str.length; // 11
```
### Array
```javascript
var arr = [1, 2, 3];
arr.push(4); // Add to end
var last = arr.pop(); // Remove from end
var len = arr.length;
var joined = arr.join(", ");
var sorted = arr.sort();
```
**Note**: External JavaScript libraries **cannot be imported**.
---
## Arrays
### One-Dimensional Arrays
```javascript
// Create typed array
var arr1D = ArrayUtils.create(Type.number);
// Or use literal syntax
var myArray = [1, 2, 3];
```
### Two-Dimensional Arrays
Analytics Designer doesn't have a direct 2D array method, but you can create one using nested 1D arrays:
```javascript
var numCols = 4;
var numRows = 3;
// Create first row
var arr1D = ArrayUtils.create(Type.number);
// Create 2D array containing the first row
var arr2D = [arr1D];
// Add remaining rows
for (var i = 1; i < numRows; i++) {
arr2D.push(ArrayUtils.create(Type.number));
}
```
**Note**: You cannot use `var arr2D = []` because Analytics Designer cannot infer the content type from an empty array.
### Setting Values in 2D Array
```javascript
arr2D[row][col] = value;
// Example: Fill with sequential numbers
var count = 0;
for (var row = 0; row < numRows; row++) {
for (var col = 0; col < numCols; col++) {
arr2D[row][col] = count;
count = count + 1;
}
}
```
### Getting Values from 2D Array
```javascript
for (var row = 0; row < numRows; row++) {
for (var col = 0; col < numCols; col++) {
console.log(arr2D[row][col].toString());
}
console.log(""); // Row separator
}
```
---
## Method Chaining
Chain method calls for compact code:
```javascript
// Without chaining (verbose)
var theDataSource = Chart_1.getDataSource();
var theVariables = theDataSource.getVariables();
console.log(theVariables);
// With chaining (compact)
console.log(Chart_1.getDataSource().getVariables());
```
**Use Cases**:
- Debug logging
- One-time data retrieval
- Reducing visual clutter
**When NOT to Chain**:
- When you need to reuse intermediate results
- When debugging complex operations
---
## Script Runtime Security
Analytics Designer validates scripts before execution to prevent security risks.
### What Is Blocked
The execution is isolated to prevent:
- **DOM access**: Cannot manipulate HTML elements
- **Global variable access**: Cannot access browser globals
- **Prototype modification**: Cannot alter built-in prototypes
- **Network requests**: Cannot send arbitrary HTTP requests
- **Script imports**: Cannot load external JavaScript
- **ActiveX/plugins**: Cannot use browser plugins
- **Web Workers**: Cannot spawn additional workers
- **Cookies**: Cannot access browser cookies
- **Cross-domain access**: Enforced same-origin policies
### Validation
- Same validation logic as script editor
- Not all validations performed at runtime (e.g., analytic data validation)
- Only allowed JavaScript subset can execute
- Critical features use secured alternative APIs
---
## The `this` Keyword
Use `this` to reference the current widget or script object:
```javascript
// In Chart_1 onSelect event
var theDataSource = this.getDataSource();
console.log(theDataSource.getVariables());
// Equivalent to:
var theDataSource = Chart_1.getDataSource();
console.log(theDataSource.getVariables());
```
**Usage Contexts**:
- Within widget scripts: `this` = the widget instance
- Within script object functions: `this` = the script object
- Explicitly reference parent: Use widget name (`Chart_1`)
- Either approach is valid (stylistic choice)
---
## Code Completion and Value Help
The script editor provides intelligent assistance:
### Code Completion
- **Standard completion**: Complete local/global identifiers
- **Semantic completion**: Suggest member functions
- **Fuzzy matching**: Find widgets even with typos (e.g., "cose" → "console")
### Value Help
Context-aware value proposals:
- Measures of a data source for function parameters
- Dimension members for filter methods
- Widget names in the application
### Keyboard Shortcut
Press `CTRL+Spacebar` to:
- Auto-complete if only one valid option
- Display value help list if multiple options
---
## Accessing Objects
Every object in the Outline (widgets, script variables, script objects) is a global object accessible by name:
```javascript
// Access widget
var isVisible = Chart_1.isVisible();
// Access script variable
var currentValue = ScriptVariable_1;
// Access script object function
ScriptObject_1.myFunction();
```
### Finding Widgets with Fuzzy Matching
Type partial names and use CTRL+Spacebar:
- Completes automatically if unique match
- Shows list if multiple matches
- Works even with typos
---
## External Resources
- **API Reference**: [https://help.sap.com/doc/958d4c11261f42e992e8d01a4c0dde25/release/en-US/index.html](https://help.sap.com/doc/958d4c11261f42e992e8d01a4c0dde25/release/en-US/index.html)
- **Scripting Documentation**: [https://help.sap.com/docs/SAP_ANALYTICS_CLOUD/00f68c2e08b941f081002fd3691d86a7/6a4db9a9c8634bcb86cecbf1f1dbbf8e.html](https://help.sap.com/docs/SAP_ANALYTICS_CLOUD/00f68c2e08b941f081002fd3691d86a7/6a4db9a9c8634bcb86cecbf1f1dbbf8e.html)
---
**Source**: SAP Analytics Designer Development Guide - Chapter 4: Scripting in Analytics Designer
**Last Updated**: 2025-11-23

View File

@@ -0,0 +1,32 @@
# Search to Insight Technical Object (2025.23)
Source: `help-portal-8a5c547a501640c38ad549109d4314b1.md` (Work with Search to Insight in Analytic Applications).
## Data support
- Indexed live HANA models and acquired models (with access).
- Dimensions without/with data access control (respects user access).
## Modes
- **Simple mode**: natural-language questions for users with limited model knowledge.
- **Advanced mode**: switch models and select dimensions during queries.
## Create technical object
1. In **Scripting** section of Outline, select the icon next to Search to Insight.
2. Side panel opens with default name `SearchToInsight_1`.
3. Under **Models to Search**, add one or more models.
4. Click **Done** to save.
## Key APIs / workflows
- Open/close the Search to Insight dialog.
- `applySearchToChart()` to apply a question result to a chart.
- Variable management APIs: save variable values; reuse when calling `applySearchToChart()`.
## Use cases
- Start different modes (simple/advanced).
- Receive questions from host HTML page and apply to charts.
- Persist and reapply variables together with search results.
## Implementation notes
- Create the technical object before users can query.
- Multiple models can be added to the search scope.

12
references/sliders.md Normal file
View File

@@ -0,0 +1,12 @@
# Sliders & Range Sliders (2025.23)
Source: `help-portal-9a00daeb47ab4264a278d9928afb95da.md`.
## Overview
Use sliders/range sliders to let users adjust numeric values.
## Guidance
- Bind slider values to variables or widget properties.
- Handle change events to refresh data or recalculate KPIs.
- Configure min/max/step to match business rules.

View File

@@ -0,0 +1,21 @@
# Smart Grouping for Charts (2025.23)
Source: `help-portal-fa8a42c6f906486897653e2f5e3708e3.md` (Apply Smart Grouping to Charts).
## Scope
- Bubble and scatterplot charts for correlation analysis.
## Prerequisite
- Enable smart grouping in the charts Builder panel before scripting.
## APIs
- Set number of groups (example):
```javascript
Chart_1.setSmartGroupingNumberOfGroups(5);
```
- Additional controls: enable/disable smart grouping, allow user-defined group labels, include/exclude tooltip measures.
## Implementation notes
- Designed for automated grouping of similar data points.
- Use Analytics Designer API Reference for full parameter list.

View File

@@ -0,0 +1,14 @@
# Text Pool Technical Object (2025.23)
Source: `help-portal-da0de4bbb64b42f39de50de885d4007c.md`.
## Purpose
Centralize text resources for reuse across the application.
## Capabilities
- Manage multilingual text entries programmatically.
- Retrieve and apply texts in scripts to widgets.
## Notes
- Use to avoid hard-coded strings and support localization.

20
references/text-widget.md Normal file
View File

@@ -0,0 +1,20 @@
# Text Widget & Dynamic Text (2025.23)
Source: `help-portal-bf11f30235854d6d852d00addb7fd1f6.md` (Use Text Widgets in Analytic Applications).
## Dynamic text sources
- Analytic application properties.
- Input controls.
- Tile filters & variables.
- Model variables.
- **Script variables** (Analytics Designer specific; values update automatically at runtime).
## APIs
- `getPlainText()` returns text with line breaks preserved.
- `applyText()` sets text; use `\n` for line breaks.
## Notes
- Text widget available in stories and analytic applications.
- Add via toolbar → **Text**.
- Script variables enable fully dynamic text scenarios.

View File

@@ -0,0 +1,21 @@
# Time Series Forecast APIs (2025.23)
Source: `help-portal-366bdc2291e347bfbedff1bbf9377fca.md` (Work with Time Series Forecast in Charts).
## Purpose
Enable predictive time series forecasts on time-series or line charts within analytic applications.
## Core API
```javascript
// Enable automatic forecast on a chart
Chart_1.getDataSource().setForecastEnabled(true);
```
## Options
- Configure number of forecast periods via API.
## Implementation notes
- Algorithm runs on historical data for selected measure.
- Works with time series and line charts.
- Use Analytics Designer API Reference for additional parameters.

View File

@@ -0,0 +1,14 @@
# Timer Technical Object (2025.23)
Source: `help-portal-142a436d0f2a4e97a9af15853ca9b739.md`.
## Purpose
Schedule time-based actions within analytic applications.
## Capabilities
- Start/stop timers, handle timer events to trigger scripts.
- Use for periodic refresh, animations, or delayed actions.
## Notes
- Avoid excessive timer frequency to reduce backend calls and UI churn.

16
references/value-help.md Normal file
View File

@@ -0,0 +1,16 @@
# Value Help in Script Editor (2025.23)
Source: `help-portal-407bdedb4fd74f3aa2a3bd7b8b8d812e.md`.
## How to invoke
- Press `CTRL + SPACE` in the script editor for context-aware suggestions.
## Contexts
- **Measures**: opens measure metadata selector.
- **Variable values**: for dimension variables, opens member selector; non-dimension variables show values in completion list.
- **Selection objects**: offers keys (dimensions) and values (dimension members) for the associated data source; works after dot access on selection object.
## Notes
- Use value help to avoid typos in member IDs, variables, and selection object properties.
- For dimension properties, value help includes member values for comparisons/assignments.

View File

@@ -0,0 +1,130 @@
# What's New in SAC Scripting (2025.23)
Complete overview of new scripting features and updates in SAP Analytics Cloud 2025.23.
**Release Date**: November 3, 2025
**SAC Version**: 2025.23
---
## Major New Features
### Time Series Forecast API
- **File**: `references/time-series-forecast.md`
- **Purpose**: Enable time-series forecasting on line and time-series charts
- **Key Methods**:
- `Chart.getTimeSeriesForecastEnabled()`
- `Chart.setTimeSeriesForecastEnabled(boolean)`
- `Chart.getTimeSeriesForecastOptions()`
- `Chart.setTimeSeriesForecastOptions(options)`
### Search to Insight Technical Object
- **File**: `references/search-to-insight.md`
- **Purpose**: Integrate natural language search with visual analytics
- **Key Features**:
- Dialog-based search interface
- Apply search results to charts
- Technical object for programmatic control
### Comments APIs
- **File**: `references/comments.md`
- **Purpose**: Add, view, and manage comments on widgets and table cells
- **Key Features**:
- Widget-level comments
- Table cell comments
- Threaded comment support
- Comment synchronization
### Smart Grouping
- **File**: `references/smart-grouping.md`
- **Purpose**: Automatic grouping of data points in bubble and scatter charts
- **Key Features**:
- ML-driven grouping algorithms
- Configurable grouping parameters
- Visual group indicators
### Explorer & Smart Insights
- **File**: `references/explorer-smart-insights.md`
- **Purpose**: Advanced data exploration and AI-powered insights
- **Key Features**:
- Launch Explorer programmatically
- Apply Smart Insights to visualizations
- Context-aware analysis
### Geo Map Enhancements
- **File**: `references/geomap.md`
- **Purpose**: Enhanced geographical visualization capabilities
- **Key Features**:
- Quick menu implementation
- Scripted layer control
- Improved interaction patterns
### Composite Scripting
- **Files**:
- `references/composites-scripting.md`
- `references/composites-restrictions.md`
- **Purpose**: Scripting within composite widgets
- **Key Features**:
- Event handling in composites
- Cross-widget communication
- Known limitations and workarounds
### Data Blending Updates
- **File**: `references/blending-limitations.md`
- **Purpose**: Updated support for blended data models
- **Key Updates**:
- Expanded API coverage
- New blending scenarios supported
- Performance improvements
### Analytics Designer Enhancements
- **File**: `references/analytics-designer-restrictions.md`
- **Purpose**: Updated list of known restrictions
- **Key Updates**:
- Reduced restrictions in 2025.23
- New capabilities added
- Migration guidance
---
## Documentation Updates
### Help Portal Updates
- **File**: `references/auth-required.md`
- **Note**: Some Help Portal articles now require SAP login
- **Impact**: Direct links may prompt for authentication
### API Reference
- Documentation version updated to reflect new methods
- Enhanced examples for all new features
- Performance optimization guidance
---
## Migration Considerations
### For Developers
1. Review existing code for deprecated patterns
2. Test new features in development environment
3. Update error handling for new APIs
4. Consider performance implications of new features
### For Administrators
1. Plan upgrade to SAC 2025.23
2. Update user training materials
3. Review security implications of new features
4. Test compatibility with existing models
---
## Related Resources
- [SAP Analytics Cloud Release Notes](https://help.sap.com/docs/SAP_ANALYTICS_CLOUD)
- [Analytics Designer API Reference](https://help.sap.com/doc/958d4c11261f42e992e8d01a4c0dde25/latest/en-US/index.html)
- [Optimized Story Experience API Reference](https://help.sap.com/doc/1639cb9ccaa54b2592224df577abe822/latest/en-US/index.html)
---
**Last Updated**: 2025-11-27
**SAC Version**: 2025.23
**Documentation Version**: 1.0.0

View File

@@ -0,0 +1,831 @@
/**
* 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);
}

View File

@@ -0,0 +1,587 @@
/**
* SAP Analytics Cloud - Planning Operations Patterns
*
* Ready-to-use code patterns for SAC Planning applications.
* Copy and adapt these patterns to your application.
*
* Source: SAP Analytics Cloud Scripting Skill
* Version: 2025.14+
*/
// =============================================================================
// VERSION MANAGEMENT PATTERNS
// =============================================================================
/**
* Pattern 1: Get and display version info
* Use in: Application initialization or info button
*/
function displayVersionInfo() {
var planning = Table_1.getPlanning();
var publicVersions = planning.getPublicVersions();
var privateVersions = planning.getPrivateVersions();
console.log("Public Versions:", publicVersions.length);
console.log("Private Versions:", privateVersions.length);
publicVersions.forEach(function(v) {
console.log("Public: " + v.id);
});
}
/**
* Pattern 2: Publish version with validation
* Use in: Publish button onClick
*/
function publishVersion(versionId) {
Application.showBusyIndicator();
try {
var version = Table_1.getPlanning().getPublicVersion(versionId);
if (!version) {
Application.showMessage(
ApplicationMessageType.Error,
"Version '" + versionId + "' not found"
);
return false;
}
if (!version.isDirty()) {
Application.showMessage(
ApplicationMessageType.Info,
"No changes to publish"
);
return false;
}
version.publish();
Application.showMessage(
ApplicationMessageType.Success,
"Version published successfully"
);
// Refresh data
Table_1.getDataSource().refreshData();
return true;
} catch (error) {
console.log("Publish error:", error);
Application.showMessage(
ApplicationMessageType.Error,
"Failed to publish: " + error
);
return false;
} finally {
Application.hideBusyIndicator();
}
}
/**
* Pattern 3: Copy public version to new private version
* Use in: Create working copy button
*/
function createWorkingCopy(sourceVersionId) {
Application.showBusyIndicator();
try {
var sourceVersion = Table_1.getPlanning().getPublicVersion(sourceVersionId);
if (!sourceVersion) {
Application.showMessage(
ApplicationMessageType.Error,
"Source version not found"
);
return null;
}
var timestamp = Date.now().toString();
var newVersionId = "WorkingCopy_" + timestamp;
sourceVersion.copy(newVersionId, PlanningCopyOption.AllData);
Application.showMessage(
ApplicationMessageType.Success,
"Working copy created: " + newVersionId
);
return newVersionId;
} catch (error) {
console.log("Copy error:", error);
Application.showMessage(
ApplicationMessageType.Error,
"Failed to create copy"
);
return null;
} finally {
Application.hideBusyIndicator();
}
}
/**
* Pattern 4: Copy with specific category (Budget/Forecast)
* Use in: Category-specific copy operations
*/
function copyAsCategory(sourceVersionId, targetVersionId, category) {
Application.showBusyIndicator();
try {
var sourceVersion = Table_1.getPlanning().getPrivateVersion(sourceVersionId);
if (!sourceVersion) {
sourceVersion = Table_1.getPlanning().getPublicVersion(sourceVersionId);
}
if (!sourceVersion) {
Application.showMessage(
ApplicationMessageType.Error,
"Source version not found"
);
return false;
}
// Copy with category
sourceVersion.copy(
targetVersionId,
PlanningCopyOption.AllData,
category // PlanningCategory.Budget or PlanningCategory.Forecast
);
Application.showMessage(
ApplicationMessageType.Success,
"Version copied successfully"
);
return true;
} catch (error) {
console.log("Copy error:", error);
Application.showMessage(
ApplicationMessageType.Error,
"Failed to copy version"
);
return false;
} finally {
Application.hideBusyIndicator();
}
}
/**
* Pattern 5: Revert unsaved changes
* Use in: Discard changes button
*/
function revertChanges(versionId) {
var version = Table_1.getPlanning().getPublicVersion(versionId);
if (!version) {
Application.showMessage(
ApplicationMessageType.Error,
"Version not found"
);
return;
}
if (!version.isDirty()) {
Application.showMessage(
ApplicationMessageType.Info,
"No changes to revert"
);
return;
}
version.revert();
Table_1.getDataSource().refreshData();
Application.showMessage(
ApplicationMessageType.Info,
"Changes reverted"
);
}
/**
* Pattern 6: Delete private version
* Use in: Delete version button (with confirmation)
*/
function deletePrivateVersion(versionId) {
var version = Table_1.getPlanning().getPrivateVersion(versionId);
if (!version) {
Application.showMessage(
ApplicationMessageType.Error,
"Private version not found"
);
return false;
}
try {
version.delete();
Application.showMessage(
ApplicationMessageType.Success,
"Version deleted"
);
return true;
} catch (error) {
Application.showMessage(
ApplicationMessageType.Error,
"Failed to delete version"
);
return false;
}
}
// =============================================================================
// DATA LOCKING PATTERNS
// =============================================================================
/**
* Pattern 7: Check lock state before edit
* Use before: Any data modification
*/
function checkLockState() {
var selections = Table_1.getSelections();
if (selections.length === 0) {
Application.showMessage(
ApplicationMessageType.Warning,
"Please select a cell first"
);
return null;
}
var lockState = Table_1.getPlanning()
.getDataLocking()
.getState(selections[0]);
return lockState;
}
/**
* Pattern 8: Lock state validation with message
* Use in: Edit button onClick
*/
function validateAndEdit() {
var lockState = checkLockState();
if (lockState === null) {
return;
}
switch (lockState) {
case DataLockingState.Locked:
Application.showMessage(
ApplicationMessageType.Warning,
"Data is locked by another user. Cannot edit."
);
break;
case DataLockingState.LockedByMe:
Application.showMessage(
ApplicationMessageType.Info,
"Data is locked by you. Proceeding with edit."
);
performEdit();
break;
case DataLockingState.Unlocked:
performEdit();
break;
}
}
function performEdit() {
// Implement edit logic
console.log("Performing edit...");
}
// =============================================================================
// DATA ACTION PATTERNS
// =============================================================================
/**
* Pattern 9: Execute data action with parameters
* Use in: Run data action button
*/
function runDataAction(year, version) {
Application.showBusyIndicator();
// Set parameters
DataAction_Allocation.setParameterValue("YEAR", year);
DataAction_Allocation.setParameterValue("VERSION", version);
// Execute
DataAction_Allocation.execute();
}
// Data action event handlers
DataAction_Allocation.onBeforeExecute = function() {
console.log("Data action starting...");
};
DataAction_Allocation.onAfterExecute = function(result) {
Application.hideBusyIndicator();
if (result.success) {
Table_1.getDataSource().refreshData();
Application.showMessage(
ApplicationMessageType.Success,
"Data action completed successfully"
);
} else {
Application.showMessage(
ApplicationMessageType.Error,
"Data action failed: " + (result.message || "Unknown error")
);
}
};
/**
* Pattern 10: Data action with dropdown parameters
* Use in: Dynamic data action execution
*/
function runDataActionFromDropdowns() {
var sourceVersion = Dropdown_SourceVersion.getSelectedKey();
var targetVersion = Dropdown_TargetVersion.getSelectedKey();
var year = Dropdown_Year.getSelectedKey();
if (!sourceVersion || !targetVersion || !year) {
Application.showMessage(
ApplicationMessageType.Warning,
"Please select all parameters"
);
return;
}
Application.showBusyIndicator();
DataAction_Copy.setParameterValue("SOURCE_VERSION", sourceVersion);
DataAction_Copy.setParameterValue("TARGET_VERSION", targetVersion);
DataAction_Copy.setParameterValue("YEAR", year);
DataAction_Copy.execute();
}
/**
* Pattern 11: Background data action execution
* Use for: Long-running data actions
*/
function runDataActionInBackground() {
Application.showMessage(
ApplicationMessageType.Info,
"Data action started in background"
);
DataAction_LongRunning.executeInBackground();
}
DataAction_LongRunning.onExecutionStatusUpdate = function(status) {
console.log("Background status:", status);
switch (status) {
case ExecutionStatus.Running:
console.log("Still running...");
break;
case ExecutionStatus.Completed:
Table_1.getDataSource().refreshData();
Application.showMessage(
ApplicationMessageType.Success,
"Background task completed"
);
break;
case ExecutionStatus.Failed:
Application.showMessage(
ApplicationMessageType.Error,
"Background task failed"
);
break;
case ExecutionStatus.Cancelled:
Application.showMessage(
ApplicationMessageType.Info,
"Background task cancelled"
);
break;
}
};
// =============================================================================
// PLANNING WORKFLOW PATTERNS
// =============================================================================
/**
* Pattern 12: Complete planning workflow
* Create copy → Edit → Publish
*/
var WorkflowState = {
workingVersion: null
};
// Step 1: Create working copy
function startPlanningWorkflow(sourceVersionId) {
var newVersionId = createWorkingCopy(sourceVersionId);
if (newVersionId) {
WorkflowState.workingVersion = newVersionId;
Table_1.getDataSource().setDimensionFilter("Version", newVersionId);
Application.showMessage(
ApplicationMessageType.Info,
"Editing version: " + newVersionId
);
}
}
// Step 2: Save progress
function savePlanningProgress() {
if (!WorkflowState.workingVersion) {
Application.showMessage(
ApplicationMessageType.Warning,
"No active working version"
);
return;
}
Table_1.getPlanning().submitData();
Application.showMessage(
ApplicationMessageType.Success,
"Changes saved"
);
}
// Step 3: Complete workflow
function completePlanningWorkflow() {
if (!WorkflowState.workingVersion) {
Application.showMessage(
ApplicationMessageType.Warning,
"No active working version"
);
return;
}
var version = Table_1.getPlanning().getPrivateVersion(WorkflowState.workingVersion);
if (version && version.isDirty()) {
version.publish();
}
WorkflowState.workingVersion = null;
Application.showMessage(
ApplicationMessageType.Success,
"Planning workflow completed"
);
}
/**
* Pattern 13: Find active planning cycle from attribute
* Use in: Auto-filter to current planning cycle
*/
function findActivePlanningCycle() {
var allCycles = PlanningModel_1.getMembers("PlanningCycle");
for (var i = 0; i < allCycles.length; i++) {
// Check for active flag attribute
if (allCycles[i].properties.IsActive === "X" ||
allCycles[i].properties.Flag === "PC+0") {
return allCycles[i].id;
}
}
return null;
}
/**
* Pattern 14: Set year filter based on planning cycle
* Use in: Dynamic year filtering
*/
function setYearFromPlanningCycle() {
var activeCycle = findActivePlanningCycle();
if (activeCycle) {
// Format the filter value
var yearFilter = "[Date].[YQM].&[" + activeCycle + "]";
Table_1.getDataSource().setDimensionFilter("Date", yearFilter);
console.log("Active planning cycle set:", activeCycle);
}
}
// =============================================================================
// INPUT FORM PATTERNS
// =============================================================================
/**
* Pattern 15: Submit data after input
* Use in: Save button for input forms
*/
function submitInputData() {
Application.showBusyIndicator();
try {
// Submit pending changes
Table_1.getPlanning().submitData();
// Get current version and publish
var version = Table_1.getPlanning().getPublicVersion("Budget2024");
if (version && version.isDirty()) {
version.publish();
}
Application.showMessage(
ApplicationMessageType.Success,
"Data submitted successfully"
);
} catch (error) {
console.log("Submit error:", error);
Application.showMessage(
ApplicationMessageType.Error,
"Failed to submit data"
);
} finally {
Application.hideBusyIndicator();
}
}
/**
* Pattern 16: Validate before submit
* Use in: Pre-submission validation
*/
function validateAndSubmit() {
// Check for required selections
if (!Dropdown_CostCenter.getSelectedKey()) {
Application.showMessage(
ApplicationMessageType.Warning,
"Please select a cost center"
);
return;
}
// Check lock state
var selections = Table_1.getSelections();
if (selections.length > 0) {
var lockState = Table_1.getPlanning()
.getDataLocking()
.getState(selections[0]);
if (lockState === DataLockingState.Locked) {
Application.showMessage(
ApplicationMessageType.Error,
"Data is locked. Cannot submit."
);
return;
}
}
// Proceed with submit
submitInputData();
}