14 KiB
SAP SAC Custom Widget Advanced Topics
Advanced features including custom types, script data types, and administration.
Source: SAP Custom Widget Developer Guide
Table of Contents
- Custom Types
- Script API Data Types
- Widget Installation
- Third-Party Library Integration
- Advanced Data Binding
- Multi-Language Support
Custom Types
Custom types enable complex data structures in widget properties and script interactions.
Custom Data Structures
Define reusable object types in JSON:
{
"id": "com.company.advancedwidget",
"version": "1.0.0",
"name": "Advanced Widget",
"types": {
"ChartConfig": {
"description": "Chart configuration object",
"properties": {
"chartType": {
"type": "string",
"default": "bar",
"description": "Type of chart"
},
"showLegend": {
"type": "boolean",
"default": true,
"description": "Show chart legend"
},
"colors": {
"type": "string[]",
"default": [],
"description": "Color palette"
}
}
},
"DataPoint": {
"description": "Single data point",
"properties": {
"label": {
"type": "string",
"default": "",
"description": "Data point label"
},
"value": {
"type": "number",
"default": 0,
"description": "Data point value"
},
"color": {
"type": "string",
"default": "#336699",
"description": "Data point color"
}
}
}
},
"properties": {
"config": {
"type": "ChartConfig",
"default": {
"chartType": "bar",
"showLegend": true,
"colors": ["#5470c6", "#91cc75", "#fac858"]
},
"description": "Chart configuration"
},
"dataPoints": {
"type": "DataPoint[]",
"default": [],
"description": "Array of data points"
}
}
}
Custom Enumerations
Define allowed values:
{
"types": {
"ChartTypeEnum": {
"description": "Allowed chart types",
"values": [
{
"id": "bar",
"description": "Bar Chart"
},
{
"id": "line",
"description": "Line Chart"
},
{
"id": "pie",
"description": "Pie Chart"
},
{
"id": "area",
"description": "Area Chart"
}
]
},
"AlignmentEnum": {
"description": "Text alignment options",
"values": [
{ "id": "left", "description": "Left aligned" },
{ "id": "center", "description": "Center aligned" },
{ "id": "right", "description": "Right aligned" }
]
}
},
"properties": {
"chartType": {
"type": "ChartTypeEnum",
"default": "bar",
"description": "Type of chart to display"
},
"titleAlignment": {
"type": "AlignmentEnum",
"default": "center",
"description": "Title text alignment"
}
}
}
Using Custom Types in Web Component
class AdvancedWidget extends HTMLElement {
constructor() {
super();
this._props = {
config: {
chartType: "bar",
showLegend: true,
colors: ["#5470c6", "#91cc75", "#fac858"]
},
dataPoints: []
};
}
// Getter returns the full object
get config() {
return this._props.config;
}
// Setter accepts object and validates
set config(value) {
if (typeof value !== "object") {
console.warn("config must be an object");
return;
}
this._props.config = {
...this._props.config,
...value
};
this._render();
}
get dataPoints() {
return this._props.dataPoints;
}
set dataPoints(value) {
if (!Array.isArray(value)) {
console.warn("dataPoints must be an array");
return;
}
this._props.dataPoints = value;
this._render();
}
}
Type Name Qualification
Internally, custom type names are qualified with widget ID to avoid conflicts:
- Defined as:
ChartConfig - Internal name:
com.company.advancedwidget.ChartConfig
Script API Data Types
Types available for properties and method parameters.
Selection Type
Represents a data selection in SAC:
{
"properties": {
"currentSelection": {
"type": "Selection",
"default": {},
"description": "Current data selection"
}
},
"methods": {
"setSelection": {
"description": "Set data selection",
"parameters": [
{
"name": "selection",
"type": "Selection",
"description": "Selection to apply"
}
],
"body": "this._setSelection(selection);"
}
}
}
Usage in Scripts:
// In SAC script
var selection = {
"Account": "Revenue",
"Year": "2024"
};
Widget_1.setSelection(selection);
MemberInfo Type
Information about a dimension member:
// MemberInfo object structure
{
id: "MEMBER_ID", // Technical ID
description: "Member Name", // Display name
dimensionId: "DIM_ID", // Parent dimension
modelId: "MODEL_ID", // Data model
displayId: "DISPLAY_ID" // Display ID
}
Using in Widget:
class MyWidget extends HTMLElement {
setMemberInfo(memberInfo) {
this._currentMember = memberInfo;
this._shadowRoot.getElementById("memberLabel").textContent =
memberInfo.description || memberInfo.id;
}
}
ResultMemberInfo Type
Extended member information from result set:
// ResultMemberInfo structure
{
id: "MEMBER_ID",
description: "Member Name",
parentId: "PARENT_ID", // For hierarchies
properties: {
"Property1": "Value1"
}
}
DataSource Methods
Access data source information:
// In SAC script with data binding
var ds = Widget_1.getDataSource();
// Get members
var members = ds.getMembers("Account", { limit: 100 });
// Get result member
var selection = { "Account": "Revenue" };
var memberInfo = ds.getResultMember("Account", selection);
// Get data cell value
var value = ds.getData(selection);
Color Type
SAC Color type for color properties:
{
"properties": {
"primaryColor": {
"type": "Color",
"default": "#336699",
"description": "Primary widget color"
}
}
}
Widget Installation
Administrator Steps
-
Access Custom Widgets:
- Main Menu > Analytic Applications
- Select Custom Widgets tab
-
Upload Widget:
- Click + (Add) button
- Select JSON file from local system
- Widget appears in list after upload
-
Manage Widgets:
- View installed widgets in list
- Delete widgets no longer needed
- Update by re-uploading JSON
Requirements
- Role: Administrator or custom widget manager
- Files: JSON metadata file (resource files hosted externally)
- Hosting: Resource files accessible via HTTPS
SAC-Hosted Widgets (QRC Q2 2023+)
Upload resource files directly to SAC:
-
Prepare Files:
- Pack JSON and JS files into ZIP
- Or upload individually to SAC Files
-
Configure JSON for SAC Hosting:
{ "webcomponents": [ { "kind": "main", "tag": "my-widget", "url": "/my-widget.js", "integrity": "", "ignoreIntegrity": true } ] }Note: URL starts with
/for SAC-hosted files -
Upload to SAC:
- Go to Files > Public Files
- Create folder for widget
- Upload JS files
- Upload JSON to Custom Widgets
Using Widgets in Stories
- Open story in Edit mode
- Open widget panel (Insert > Widget)
- Find custom widget in Custom section
- Drag onto canvas
- Configure via Builder/Styling panels
Third-Party Library Integration
Supported Libraries
Common libraries used with SAC widgets:
| Library | Use Case | CDN |
|---|---|---|
| ECharts | Charts | [https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js](https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js`) |
| D3.js | Data viz | [https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js](https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js`) |
| Chart.js | Charts | [https://cdn.jsdelivr.net/npm/chart.js@4/dist/chart.umd.js](https://cdn.jsdelivr.net/npm/chart.js@4/dist/chart.umd.js`) |
| Leaflet | Maps | [https://unpkg.com/leaflet@1.9/dist/leaflet.js](https://unpkg.com/leaflet@1.9/dist/leaflet.js`) |
| Moment.js | Dates | [https://cdn.jsdelivr.net/npm/moment@2/moment.min.js](https://cdn.jsdelivr.net/npm/moment@2/moment.min.js`) |
Integration Pattern
(function() {
// Library URLs
const LIBS = {
echarts: "[https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"](https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js")
};
// Track loading state
const libState = {
echarts: { loaded: false, loading: false, callbacks: [] }
};
// Load library once
function loadLibrary(name) {
return new Promise((resolve, reject) => {
const state = libState[name];
// Already loaded
if (state.loaded) {
resolve(window[name === "echarts" ? "echarts" : name]);
return;
}
// Loading - queue callback
if (state.loading) {
state.callbacks.push({ resolve, reject });
return;
}
// Start loading
state.loading = true;
const script = document.createElement("script");
script.src = LIBS[name];
script.onload = () => {
state.loaded = true;
state.loading = false;
const lib = window[name === "echarts" ? "echarts" : name];
resolve(lib);
state.callbacks.forEach(cb => cb.resolve(lib));
state.callbacks = [];
};
script.onerror = (err) => {
state.loading = false;
reject(err);
state.callbacks.forEach(cb => cb.reject(err));
state.callbacks = [];
};
document.head.appendChild(script);
});
}
class ChartWidget extends HTMLElement {
async connectedCallback() {
try {
const echarts = await loadLibrary("echarts");
this._initChart(echarts);
} catch (error) {
this._showError("Failed to load chart library");
}
}
_initChart(echarts) {
const container = this._shadowRoot.getElementById("chart");
this._chart = echarts.init(container);
this._render();
}
}
customElements.define("chart-widget", ChartWidget);
})();
License Considerations
Important: Review third-party library licenses before deployment.
- MIT/Apache: Generally safe for commercial use
- GPL: May have copyleft requirements
- Commercial: May require license purchase
Check license compatibility with SAC deployment.
Advanced Data Binding
Multiple Data Bindings
{
"dataBindings": {
"primaryData": {
"feeds": [
{ "id": "xAxis", "description": "X-Axis", "type": "dimension" },
{ "id": "yAxis", "description": "Y-Axis", "type": "mainStructureMember" }
]
},
"secondaryData": {
"feeds": [
{ "id": "categories", "description": "Categories", "type": "dimension" },
{ "id": "values", "description": "Values", "type": "mainStructureMember" }
]
}
}
}
Note: Currently only the first dataBinding is used. Multiple bindings are defined but only one is active.
Accessing Metadata
_processData() {
const data = this.primaryData;
if (!data || !data.data) return;
// Access metadata
const metadata = data.metadata;
// Dimension info
if (metadata.dimensions) {
Object.entries(metadata.dimensions).forEach(([key, dim]) => {
console.log(`Dimension: ${dim.description}`);
});
}
// Measure info
if (metadata.mainStructureMembers) {
Object.entries(metadata.mainStructureMembers).forEach(([key, measure]) => {
console.log(`Measure: ${measure.description}, Unit: ${measure.unitOfMeasure}`);
});
}
}
DataBinding Object Methods
// Get DataBinding object
const binding = this.dataBindings.getDataBinding("primaryData");
// Available methods (async)
const resultSet = await binding.getResultSet();
const members = await binding.getMembers("DimensionName");
Multi-Language Support
Externalize Strings
{
"properties": {
"titleKey": {
"type": "string",
"default": "WIDGET_TITLE",
"description": "Translation key for title"
}
}
}
Translation Pattern
class MyWidget extends HTMLElement {
constructor() {
super();
this._translations = {
en: {
WIDGET_TITLE: "My Widget",
NO_DATA: "No data available",
LOADING: "Loading..."
},
de: {
WIDGET_TITLE: "Mein Widget",
NO_DATA: "Keine Daten verfügbar",
LOADING: "Laden..."
}
};
this._locale = "en";
}
_t(key) {
const translations = this._translations[this._locale] || this._translations.en;
return translations[key] || key;
}
_render() {
this._shadowRoot.getElementById("title").textContent = this._t(this._props.titleKey);
}
// Set locale from SAC context
setLocale(locale) {
this._locale = locale.substring(0, 2); // "en-US" -> "en"
this._render();
}
}
Debugging Advanced Widgets
Console Inspection
// Expose widget for debugging
connectedCallback() {
// Make accessible in console
window.__myWidget = this;
// Log initialization
console.log("[MyWidget] Initialized", {
props: this._props,
dataBinding: this.primaryData
});
}
Performance Profiling
_render() {
const start = performance.now();
// Rendering logic
this._doRender();
const duration = performance.now() - start;
if (duration > 16) { // > 1 frame
console.warn(`[MyWidget] Slow render: ${duration.toFixed(2)}ms`);
}
}
Resources
Last Updated: 2025-11-22