17 KiB
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
Table of Contents
- Container Widgets
- Layout API
- Navigation API
- R Visualization
- Custom Widgets
- Script Objects
- Technical Objects
Container Widgets
SAC provides five container widget types for organizing content.
Panel
Basic container for grouping widgets.
// 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.
// 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:
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.
// 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:
// 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.
// 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.
// Open popup
Popup_1.open();
// Close popup
Popup_1.close();
Events:
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
// 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
// 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
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
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
// 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
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:
// Switch to page
Application.setActivePage("Page_Details");
// Get current page
var currentPage = Application.getActivePage();
Custom Navigation Menu Pattern
// 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
// 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
- Insert → R Visualization in Story
- Configure Input Data (data source binding)
- 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
# 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
# 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
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
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
{
"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
(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
// 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
// In custom widget (supports Linked Analysis)
this.dataBindings
.getDataBinding()
.getLinkedAnalysis()
.setFilters(selection);
Timer Custom Widget Example
// 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
- Outline panel → Script Objects → (+)
- Name the script object (e.g.,
Utils) - Add functions
Script Object Functions
// 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
// 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
- Outline panel → Technical Objects
- Click (+) next to desired object type
- Configure in Builder panel
Global Script Variables
Create via Outline → Script Variables:
// 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
// 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
Official References: