398 lines
11 KiB
Markdown
398 lines
11 KiB
Markdown
# SAP SAC Custom Widget Integration and Migration
|
|
|
|
Coverage of script integration, content transport, story compatibility, and planning features.
|
|
|
|
**Sources**:
|
|
- [SAP Community - Content Transport](https://community.sap.com/t5/technology-blog-posts-by-members/sac-content-transport-migration-using-ctms/ba-p/13742318)
|
|
- [Optimized Story Experience](https://community.sap.com/t5/technology-blog-posts-by-sap/the-new-optimized-story-experience-the-unification-of-story-and-analytic/ba-p/13558887)
|
|
- [Analytics Designer API Reference](https://help.sap.com/doc/958d4c11261f42e992e8d01a4c0dde25/release/en-US/index.html)
|
|
|
|
---
|
|
|
|
## Table of Contents
|
|
|
|
1. [Script Integration](#script-integration)
|
|
2. [Content Transport and Migration](#content-transport-and-migration)
|
|
3. [Story Compatibility](#story-compatibility)
|
|
4. [Planning Integration](#planning-integration)
|
|
5. [API Methods Reference](#api-methods-reference)
|
|
|
|
---
|
|
|
|
## Script Integration
|
|
|
|
### Global Script Objects
|
|
|
|
Custom widgets can interact with SAC's global script objects.
|
|
|
|
**Script Object Structure**:
|
|
- Script objects act as containers for reusable functions
|
|
- Functions not tied to events, invoked directly
|
|
- Accessible from all scripts in the story
|
|
|
|
**Invoking Script Functions**:
|
|
```javascript
|
|
// In SAC script
|
|
ScriptObjectName.ScriptFunctionName();
|
|
|
|
// Example
|
|
Utils.formatCurrency(1000, "USD"); // Returns "$1,000.00"
|
|
```
|
|
|
|
### Script Variables
|
|
|
|
**Global Variables**:
|
|
- Defined at story level
|
|
- Accessible from all script blocks
|
|
- Can receive values from URL parameters
|
|
|
|
**Using with Custom Widgets**:
|
|
```javascript
|
|
// In SAC script
|
|
var myValue = GlobalVariable_1;
|
|
CustomWidget_1.setValue(myValue);
|
|
|
|
// Widget method receives value
|
|
class MyWidget extends HTMLElement {
|
|
setValue(val) {
|
|
this._props.value = val;
|
|
this._render();
|
|
}
|
|
}
|
|
```
|
|
|
|
### Script Object Integration Pattern
|
|
|
|
```javascript
|
|
// Custom widget firing events for script handling
|
|
class MyWidget extends HTMLElement {
|
|
_handleUserAction(data) {
|
|
// Fire event that SAC script can handle
|
|
this.dispatchEvent(new CustomEvent("onUserAction", {
|
|
detail: { actionData: data }
|
|
}));
|
|
}
|
|
}
|
|
|
|
// In SAC script (event handler)
|
|
CustomWidget_1.onUserAction = function() {
|
|
var eventData = CustomWidget_1.getEventInfo();
|
|
// Process event, call other script objects
|
|
DataProcessor.handleAction(eventData.actionData);
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
## Content Transport and Migration
|
|
|
|
### Transport Methods
|
|
|
|
**1. Content Network (Same Region)**
|
|
- Source and destination on same region
|
|
- Same or +1 quarterly version
|
|
- Access: Main Menu > Transport > Export/Import > Content Network Storage
|
|
|
|
**2. Import/Export (Any Region)**
|
|
- No region restriction
|
|
- Version constraints apply
|
|
- More flexible but manual
|
|
|
|
### Custom Widget Transport
|
|
|
|
**Supported Scenarios**:
|
|
- Cloud Foundry to Cloud Foundry tenants
|
|
- Same hosting configuration required
|
|
|
|
**Not Supported**:
|
|
- Cloud Foundry to Neo platform
|
|
- Different hosting configurations may cause issues
|
|
|
|
### Common Transport Issue
|
|
|
|
**Error**: "The system couldn't load the custom widget"
|
|
|
|
**Causes**:
|
|
- Widget JSON transported but resource files not accessible
|
|
- Different hosting URLs between source/target
|
|
- Integrity hash mismatch after transport
|
|
|
|
**Solution**:
|
|
```json
|
|
// Ensure resource URLs are accessible from target tenant
|
|
{
|
|
"webcomponents": [
|
|
{
|
|
"kind": "main",
|
|
"tag": "my-widget",
|
|
"url": "[https://globally-accessible-host.com/widget.js",](https://globally-accessible-host.com/widget.js",)
|
|
"integrity": "sha256-...",
|
|
"ignoreIntegrity": false
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### Transport Best Practices
|
|
|
|
1. **Use globally accessible hosting** (GitHub Pages, CDN, SAC-hosted)
|
|
2. **Verify URLs before transport** - Ensure target can reach resource files
|
|
3. **Re-upload JSON** if hosting changes - Update URLs post-transport
|
|
4. **Test in target** before production use
|
|
|
|
### CTMS Integration
|
|
|
|
Cloud Transport Management Service (CTMS) provides automated transport:
|
|
|
|
1. Integrate CTMS with SAC
|
|
2. Define transport routes
|
|
3. Upload packages via SAC interface
|
|
4. CTMS handles deployment to destination
|
|
|
|
**Limitation**: CTMS is basic - no destination location selection like native Content Network.
|
|
|
|
---
|
|
|
|
## Story Compatibility
|
|
|
|
### Story Types
|
|
|
|
| Type | Custom Widgets | Scripting | CSS/Themes |
|
|
|------|----------------|-----------|------------|
|
|
| Classic Story | Limited | No | Limited |
|
|
| Optimized Story (Classic Responsive) | Yes | Limited | Limited |
|
|
| Optimized Story (Advanced Responsive) | Full | Full | Full |
|
|
|
|
### Optimized Story Experience (QRC Q2 2023+)
|
|
|
|
**Advanced Responsive Layout** features:
|
|
- Full custom widget support
|
|
- Complete scripting capabilities
|
|
- CSS and theme customization
|
|
- Device preview
|
|
- Data binding
|
|
|
|
### Classic Story Conversion
|
|
|
|
**Conversion Status Types**:
|
|
|
|
1. **Ready to convert** - No issues, direct conversion
|
|
2. **Feature limitation** - Some features not supported in optimized
|
|
3. **Blocked** - Issues must be resolved first
|
|
|
|
**Conversion Notes**:
|
|
- Conversion is permanent
|
|
- Save as copy recommended
|
|
- Converted stories use Classic Responsive Layout initially
|
|
|
|
### Custom Widget Compatibility
|
|
|
|
**In Optimized Stories**:
|
|
```json
|
|
{
|
|
"id": "com.company.widget",
|
|
"dataBindings": {
|
|
"myData": {
|
|
"feeds": [...]
|
|
}
|
|
}
|
|
}
|
|
```
|
|
- Full data binding support
|
|
- Script integration
|
|
- Builder/Styling panels
|
|
|
|
**In Classic Stories**:
|
|
- Limited support
|
|
- No data binding
|
|
- Basic property configuration only
|
|
|
|
---
|
|
|
|
## Planning Integration
|
|
|
|
### ⚠️ Important Limitations
|
|
|
|
Before implementing planning widgets, review these constraints:
|
|
|
|
1. **Builder Panel + Data Binding Conflict**: Cannot combine custom Builder Panel with data binding in the same widget
|
|
2. **Hierarchies Not Supported**: Data binding works with flat data only; select "flat" representation in SAC
|
|
|
|
See details in [Data Binding Limitations](#data-binding-limitations) below.
|
|
|
|
### Custom Widgets for Planning
|
|
|
|
Custom widgets can support SAP Analytics Cloud Planning scenarios:
|
|
|
|
**Use Cases**:
|
|
- Custom input controls
|
|
- Specialized data entry forms
|
|
- Planning workflow visualization
|
|
- Custom approval interfaces
|
|
|
|
### Data Binding Limitations
|
|
|
|
**Known Limitations**:
|
|
|
|
1. **Builder Panel + Data Binding Conflict**:
|
|
- Cannot combine custom Builder Panel with data binding
|
|
- Builder Panel overrides data binding functionality
|
|
- Choose one approach per widget
|
|
|
|
2. **Hierarchies Not Supported**:
|
|
- Data binding works with flat data only
|
|
- Select "flat" representation in SAC properties
|
|
- Hierarchical dimensions require alternative approach
|
|
|
|
### Planning API Integration
|
|
|
|
**Available through Script**:
|
|
```javascript
|
|
// In SAC script
|
|
var ds = Table_1.getDataSource();
|
|
|
|
// Planning operations (via DataSource)
|
|
ds.setUserInput(selection, value); // Write data
|
|
ds.submitData(); // Commit changes
|
|
ds.revertData(); // Rollback
|
|
```
|
|
|
|
**Custom Widget Access**:
|
|
```javascript
|
|
// Widget receives DataSource via method
|
|
class PlanningWidget extends HTMLElement {
|
|
async setDataSource(dataSource) {
|
|
this._ds = dataSource;
|
|
// Can now call dataSource methods
|
|
}
|
|
}
|
|
```
|
|
|
|
### Input Control Pattern
|
|
|
|
```javascript
|
|
class CustomInputWidget extends HTMLElement {
|
|
constructor() {
|
|
super();
|
|
this._setupInputHandlers();
|
|
}
|
|
|
|
_setupInputHandlers() {
|
|
this._shadowRoot.getElementById("input").addEventListener("change", (e) => {
|
|
// Fire event with new value
|
|
this.dispatchEvent(new CustomEvent("onValueChange", {
|
|
detail: { newValue: e.target.value }
|
|
}));
|
|
});
|
|
}
|
|
}
|
|
|
|
// SAC script handles the planning write-back
|
|
CustomInputWidget_1.onValueChange = function() {
|
|
var info = CustomInputWidget_1.getEventInfo();
|
|
var selection = { "Account": "Forecast", "Time": "2024.Q1" };
|
|
Table_1.getDataSource().setUserInput(selection, info.newValue);
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
## API Methods Reference
|
|
|
|
### DataSource Methods (via Script)
|
|
|
|
| Method | Description | Parameters |
|
|
|--------|-------------|------------|
|
|
| `getData(selection)` | Get data cell value | Selection object |
|
|
| `getResultSet()` | Get current result set | None |
|
|
| `getMembers(dimension)` | Get dimension members | Dimension name |
|
|
| `getResultMember(dim, selection)` | Get member info | Dimension, Selection |
|
|
| `getDimensionFilters(dimension)` | Get filter values | Dimension name |
|
|
| `setDimensionFilter(dim, member)` | Set filter | Dimension, MemberInfo |
|
|
| `removeDimensionFilter(dimension)` | Clear filter | Dimension name |
|
|
|
|
### Variable Methods
|
|
|
|
| Method | Description | Parameters |
|
|
|--------|-------------|------------|
|
|
| `setVariableValue(name, value)` | Set variable | Variable name, value |
|
|
| `getVariableValues()` | Get all variables | None |
|
|
|
|
**Performance Tip**: Group `setVariableValue()` calls together for automatic request merging.
|
|
|
|
### Custom Widget Data Binding Methods
|
|
|
|
```javascript
|
|
// Access data binding
|
|
const binding = this.dataBindings.getDataBinding("myBinding");
|
|
|
|
// Get result set (async)
|
|
const resultSet = await binding.getResultSet();
|
|
|
|
// Direct property access
|
|
const data = this.myBinding.data;
|
|
const metadata = this.myBinding.metadata;
|
|
```
|
|
|
|
### Event Info Pattern
|
|
|
|
```javascript
|
|
// In custom widget
|
|
this.dispatchEvent(new CustomEvent("onSelect", {
|
|
detail: {
|
|
selectedId: "item-123",
|
|
selectedValue: 100
|
|
}
|
|
}));
|
|
|
|
// In SAC script
|
|
Widget_1.onSelect = function() {
|
|
var info = Widget_1.getEventInfo();
|
|
// info.selectedId = "item-123"
|
|
// info.selectedValue = 100
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
## Migration Checklist
|
|
|
|
### Before Transport
|
|
|
|
- [ ] Verify resource file hosting is globally accessible
|
|
- [ ] Update URLs if changing hosting strategy
|
|
- [ ] Regenerate integrity hashes if files changed
|
|
- [ ] Test widget in source tenant
|
|
- [ ] Document any script dependencies
|
|
|
|
### After Transport
|
|
|
|
- [ ] Verify widget loads in target tenant
|
|
- [ ] Test all functionality
|
|
- [ ] Check script object references still work
|
|
- [ ] Verify data binding if applicable
|
|
- [ ] Test in view mode (not just edit mode)
|
|
|
|
### Troubleshooting
|
|
|
|
| Symptom | Likely Cause | Solution |
|
|
|---------|--------------|----------|
|
|
| Widget won't load | URL not accessible | Verify hosting, CORS |
|
|
| Integrity warning | Hash mismatch | Regenerate hash |
|
|
| Script errors | Missing script objects | Recreate in target |
|
|
| No data | Data binding lost | Reconfigure binding |
|
|
| Styling broken | CSS not loaded | Check styling panel config |
|
|
|
|
---
|
|
|
|
## Resources
|
|
|
|
- [Analytics Designer API Reference](https://help.sap.com/doc/958d4c11261f42e992e8d01a4c0dde25/release/en-US/index.html)
|
|
- [Custom Widget Developer Guide](https://help.sap.com/doc/c813a28922b54e50bd2a307b099787dc/release/en-US/CustomWidgetDevGuide_en.pdf)
|
|
- [SAC Content Transport](https://community.sap.com/t5/technology-blog-posts-by-members/sac-content-transport-migration-using-ctms/ba-p/13742318)
|
|
- [Optimized Story Experience](https://community.sap.com/t5/technology-blog-posts-by-sap/the-new-optimized-story-experience-the-unification-of-story-and-analytic/ba-p/13558887)
|
|
|
|
---
|
|
|
|
**Last Updated**: 2025-11-22
|