Interactively generates SAC custom widget scaffold with widget.json and widget.js files based on user requirements
Interactively generates SAP Analytics Cloud custom widget scaffold with widget.json and widget.js files based on user requirements
/plugin marketplace add secondsky/sap-skills/plugin install sap-sac-custom-widget@sap-skillswidget-nameInteractively create a complete SAP Analytics Cloud custom widget scaffold including widget.json metadata and widget.js Web Component implementation.
/widget-generate
/widget-generate my-chart-widget
/widget-generate --quick
When invoked without --quick, ask the user these questions:
Widget Name
Widget ID
Data Binding
Components
Third-Party Library
Initial Properties
Generate minimal widget with defaults:
Full metadata file with:
Complete Web Component with:
styling-panel.js - If styling panel selectedbuilder-panel.js - If builder panel selectedwidget-name/
├── widget.json
├── widget.js
├── styling-panel.js (if selected)
└── builder-panel.js (if selected)
{
"id": "com.company.widgetname",
"version": "1.0.0",
"name": "Widget Name",
"description": "A custom widget for SAP Analytics Cloud",
"vendor": "Company Name",
"license": "MIT",
"icon": "",
"webcomponents": [
{
"kind": "main",
"tag": "widget-name",
"url": "widget.js",
"integrity": "",
"ignoreIntegrity": true
}
],
"properties": {
"title": {
"type": "string",
"default": "Widget Title"
}
},
"methods": {},
"events": {}
}
{
"dataBindings": {
"myData": {
"feeds": [
{
"id": "dimensions",
"description": "Dimensions",
"type": "dimension"
},
{
"id": "measures",
"description": "Measures",
"type": "mainStructureMember"
}
]
}
}
}
(function() {
const template = document.createElement("template");
template.innerHTML = `
<style>
:host {
display: block;
width: 100%;
height: 100%;
font-family: var(--sapFontFamily, Arial, sans-serif);
}
.container {
width: 100%;
height: 100%;
padding: 16px;
box-sizing: border-box;
}
.title {
font-size: 16px;
font-weight: 600;
margin-bottom: 12px;
color: var(--sapTextColor, #333);
}
.content {
width: 100%;
height: calc(100% - 40px);
}
</style>
<div class="container">
<div class="title" id="title"></div>
<div class="content" id="content"></div>
</div>
`;
class WidgetName extends HTMLElement {
constructor() {
super();
this._shadowRoot = this.attachShadow({ mode: "open" });
this._shadowRoot.appendChild(template.content.cloneNode(true));
this._props = {};
}
// SAC Lifecycle Functions
onCustomWidgetBeforeUpdate(changedProperties) {
this._props = { ...this._props, ...changedProperties };
}
onCustomWidgetAfterUpdate(changedProperties) {
this._render();
}
onCustomWidgetResize() {
this._render();
}
onCustomWidgetDestroy() {
// Cleanup resources here
}
// Property getter/setter
get title() {
return this._props.title;
}
set title(value) {
this._props.title = value;
this.dispatchEvent(new CustomEvent("propertiesChanged", {
detail: { properties: { title: value } }
}));
}
// Render function
_render() {
const titleEl = this._shadowRoot.getElementById("title");
if (titleEl && this._props.title) {
titleEl.textContent = this._props.title;
}
// Add your rendering logic here
}
}
customElements.define("widget-name", WidgetName);
})();
After files are generated, provide:
Next Steps
Development Tips
Customization Pointers
When this command is invoked:
/widget-validate to verify