659 lines
14 KiB
Markdown
659 lines
14 KiB
Markdown
# SAP SAC Custom Widget Advanced Topics
|
|
|
|
Advanced features including custom types, script data types, and administration.
|
|
|
|
**Source**: [SAP Custom Widget Developer Guide](https://help.sap.com/doc/c813a28922b54e50bd2a307b099787dc/release/en-US/CustomWidgetDevGuide_en.pdf)
|
|
|
|
---
|
|
|
|
## Table of Contents
|
|
|
|
1. [Custom Types](#custom-types)
|
|
2. [Script API Data Types](#script-api-data-types)
|
|
3. [Widget Installation](#widget-installation)
|
|
4. [Third-Party Library Integration](#third-party-library-integration)
|
|
5. [Advanced Data Binding](#advanced-data-binding)
|
|
6. [Multi-Language Support](#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:
|
|
|
|
```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:
|
|
|
|
```json
|
|
{
|
|
"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
|
|
|
|
```javascript
|
|
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:
|
|
|
|
```json
|
|
{
|
|
"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**:
|
|
```javascript
|
|
// In SAC script
|
|
var selection = {
|
|
"Account": "Revenue",
|
|
"Year": "2024"
|
|
};
|
|
Widget_1.setSelection(selection);
|
|
```
|
|
|
|
### MemberInfo Type
|
|
|
|
Information about a dimension member:
|
|
|
|
```javascript
|
|
// 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**:
|
|
```javascript
|
|
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:
|
|
|
|
```javascript
|
|
// ResultMemberInfo structure
|
|
{
|
|
id: "MEMBER_ID",
|
|
description: "Member Name",
|
|
parentId: "PARENT_ID", // For hierarchies
|
|
properties: {
|
|
"Property1": "Value1"
|
|
}
|
|
}
|
|
```
|
|
|
|
### DataSource Methods
|
|
|
|
Access data source information:
|
|
|
|
```javascript
|
|
// 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:
|
|
|
|
```json
|
|
{
|
|
"properties": {
|
|
"primaryColor": {
|
|
"type": "Color",
|
|
"default": "#336699",
|
|
"description": "Primary widget color"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Widget Installation
|
|
|
|
### Administrator Steps
|
|
|
|
1. **Access Custom Widgets**:
|
|
- Main Menu > **Analytic Applications**
|
|
- Select **Custom Widgets** tab
|
|
|
|
2. **Upload Widget**:
|
|
- Click **+** (Add) button
|
|
- Select JSON file from local system
|
|
- Widget appears in list after upload
|
|
|
|
3. **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:
|
|
|
|
1. **Prepare Files**:
|
|
- Pack JSON and JS files into ZIP
|
|
- Or upload individually to SAC Files
|
|
|
|
2. **Configure JSON for SAC Hosting**:
|
|
```json
|
|
{
|
|
"webcomponents": [
|
|
{
|
|
"kind": "main",
|
|
"tag": "my-widget",
|
|
"url": "/my-widget.js",
|
|
"integrity": "",
|
|
"ignoreIntegrity": true
|
|
}
|
|
]
|
|
}
|
|
```
|
|
Note: URL starts with `/` for SAC-hosted files
|
|
|
|
3. **Upload to SAC**:
|
|
- Go to Files > Public Files
|
|
- Create folder for widget
|
|
- Upload JS files
|
|
- Upload JSON to Custom Widgets
|
|
|
|
### Using Widgets in Stories
|
|
|
|
1. Open story in Edit mode
|
|
2. Open widget panel (Insert > Widget)
|
|
3. Find custom widget in Custom section
|
|
4. Drag onto canvas
|
|
5. 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
|
|
|
|
```javascript
|
|
(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
|
|
|
|
```json
|
|
{
|
|
"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
|
|
|
|
```javascript
|
|
_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
|
|
|
|
```javascript
|
|
// 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
|
|
|
|
```json
|
|
{
|
|
"properties": {
|
|
"titleKey": {
|
|
"type": "string",
|
|
"default": "WIDGET_TITLE",
|
|
"description": "Translation key for title"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Translation Pattern
|
|
|
|
```javascript
|
|
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
|
|
|
|
```javascript
|
|
// 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
|
|
|
|
```javascript
|
|
_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
|
|
|
|
- [SAP Custom Widget Developer Guide (PDF)](https://help.sap.com/doc/c813a28922b54e50bd2a307b099787dc/release/en-US/CustomWidgetDevGuide_en.pdf)
|
|
- [Analytics Designer API Reference](https://help.sap.com/doc/958d4c11261f42e992e8d01a4c0dde25/release/en-US/index.html)
|
|
- [Hosting in SAC](https://community.sap.com/t5/technology-blogs-by-sap/hosting-and-uploading-custom-widgets-resource-files-into-sap-analytics/ba-p/13563064)
|
|
|
|
---
|
|
|
|
**Last Updated**: 2025-11-22
|