794 lines
17 KiB
Markdown
794 lines
17 KiB
Markdown
# 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)
|