604 lines
14 KiB
Markdown
604 lines
14 KiB
Markdown
# 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)
|