Files
gh-secondsky-sap-skills-ski…/references/api-advanced-widgets.md
2025-11-30 08:55:33 +08:00

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

  1. Container Widgets
  2. Layout API
  3. Navigation API
  4. R Visualization
  5. Custom Widgets
  6. Script Objects
  7. 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

  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

# 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

  1. Outline panel → Script Objects → (+)
  2. Name the script object (e.g., Utils)
  3. 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

  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:

// 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();
};

Official References: