All skills
secondsky avatar

/sap-sac-scripting

@620a19a
by Eddiesecondsky/sap-skills456 stars
120

Comprehensive SAC scripting skill for SAP Analytics Cloud Analytics Designer and Optimized Story Experience. This skill should be used when the user asks to "create SAC script", "debug Analytics Designer", "optimize SAC performance", "planning operations in SAC", "filter data in SAC", "use DataSource API", "chart scripting", "table manipulation", "SAC event handlers", "version management", "data locking", "Optimized Story Experience API", "OSE scripting", "OSE widget API", "OSE DataSource", "story scripting API", "OSE planning API", "OSE method", "optimized story", "SAC story scripting", "story script", "SAC scripting", "debug SAC runtime in Microsoft Edge via CDP", or works with SAC widgets, planning models, or analytics applications.

Use this Skill: https://skilld.dev/gh/secondsky/sap-skills/sap-sac-scripting

This session only. Nothing lands on disk.

referencesapi-advanced-widgets.md

≈4.4k tokens on demand. Your agent reads this file only when SKILL.md points to it.

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 (current release)


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

Related Documentation

Official References:

Source: SKILL.md on GitHub

1 alert16d5 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    The skill is a comprehensive reference and template collection for SAP Analytics Cloud (SAC) scripting. It provides secure instructions for setting up an associated Model Context Protocol (MCP) server, including commit pinning and security flags during installation. It also incorporates multiple security best practices for developers, such as origin validation for iframe communication.

  • Socket16d

    1 alert: gptAnomaly

  • Snyk16d

    Risk: MEDIUM · 1 issue

  • Runlayer6mo

    32/61 files flagged

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at 620a19a. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 weeks ago.

Activeupdated 2 months ago
Other metadata
metadata
{
  "maintainer": "Eduard Jiglau",
  "maintainer_email": "hello@sap-ai-skills.com",
  "website": "https://sap-ai-skills.com",
  "version": "2.4.1",
  "last_verified": "2026-06-11",
  "sac_version": "Q2 2026 (2026.8)",
  "api_reference_version": "2025.20 (OSE Q2 2026)",
  "documentation_source": "https://help.sap.com/docs/SAP_ANALYTICS_CLOUD",
  "reference_files": 66,
  "template_patterns": 56,
  "agents": 4,
  "commands": 4,
  "status": "docs_audited_runtime_pending",
  "known_issues": [
    "Live SAC story/runtime checks require tenant evidence before claiming production validation."
  ]
}

README badge

README badge for secondsky/sap-skills/sap-sac-scripting