SAPUI5 Development Skill Related Skills - sap-fiori-tools : Use for rapid Fiori application development, Page Editor configuration, and deployment automation - sap-cap-capire : Use for backend service integration, OData model binding, and CAP service consumption - sap-btp-cloud-platform : Use for deployment options, HTML5 Application Repository service, and BTP integration - sap-abap : Use when connecting to ABAP backends or consuming OData services from SAP systems - sap-api-style : Use when documenting UI5 application APIs or following REST/OData standards - sap-dependency-security : Use fo…

\n }\n }\"/>\n```\n\n### Server-Side Validation\n\n**Always validate** on server:\n\n```javascript\n// Client sends data\noModel.create(\"/Products\", {\n name: sName,\n price: fPrice\n}, {\n success: function() {\n // Server has validated input\n },\n error: function(oError) {\n // Server rejected invalid input\n MessageBox.error(\"Validation failed\");\n }\n});\n```\n\n---\n\n## Secure Storage\n\n### Sensitive Data\n\n**Never store** sensitive data in localStorage or sessionStorage:\n\n```javascript\n// DANGEROUS\nlocalStorage.setItem(\"password\", sPassword);\nlocalStorage.setItem(\"apiKey\", sApiKey);\n\n// SAFE - Use secure httpOnly cookies (server-side)\n// Or short-lived memory-only storage\n```\n\n### Session Management\n\n**Use server-side sessions**:\n\n```javascript\n// Login\nfetch(\"/api/login\", {\n method: \"POST\",\n credentials: \"include\", // Send cookies\n body: JSON.stringify({ username, password })\n});\n\n// Requests automatically include session cookie\nfetch(\"/api/data\", {\n credentials: \"include\"\n});\n\n// Logout\nfetch(\"/api/logout\", {\n method: \"POST\",\n credentials: \"include\"\n});\n```\n\n---\n\n## SQL Injection Prevention\n\n### OData Services\n\n**Use parameterized queries** in backend:\n\n```javascript\n// Client-side (safe - OData handles escaping)\noModel.read(\"/Products\", {\n filters: [\n new Filter(\"Name\", FilterOperator.EQ, sUserInput)\n ]\n});\n\n// Backend must use parameterized queries\n// DO NOT concatenate SQL strings\n```\n\n---\n\n## Secure Configuration\n\n### Manifest.json\n\n**Don't expose** sensitive information:\n\n```json\n// DANGEROUS\n{\n \"sap.app\": {\n \"dataSources\": {\n \"mainService\": {\n \"uri\": \"https://api.mycompany.com/odata/?apikey=secret123\"\n }\n }\n }\n}\n\n// SAFE\n{\n \"sap.app\": {\n \"dataSources\": {\n \"mainService\": {\n \"uri\": \"https://api.mycompany.com/odata/\"\n // API key sent via server-side proxy\n }\n }\n }\n}\n```\n\n### Environment Variables\n\n**Use environment-specific** configuration:\n\n```javascript\n// Build-time replacement\nvar API_URL = \"#{API_URL}#\"; // Replaced during build\n\n// Or runtime configuration\nfetch(\"/config.json\").then(config => {\n // Use config.apiUrl\n});\n```\n\n---\n\n## Security Testing\n\n### Manual Testing\n\n1. **XSS Testing**:\n - Input `\u003cscript>alert('XSS')\u003c/script>`\n - Input `\u003cimg src=x onerror=alert('XSS')>`\n - Verify automatic encoding\n\n2. **CSRF Testing**:\n - Attempt cross-origin requests\n - Verify token validation\n\n3. **Authentication**:\n - Test without credentials\n - Test with expired tokens\n - Test privilege escalation\n\n### Automated Testing\n\n**OWASP ZAP**:\n```bash\n# Scan application\nzap-cli quick-scan -s xss,sqli http://localhost:8080\n```\n\n**Retire.js** (Check for vulnerable libraries):\n```bash\nnpm install -g retire\nretire --js --path webapp/\n```\n\n---\n\n## Security Checklist\n\n### XSS Prevention\n- [ ] Use data binding for user input\n- [ ] Never use innerHTML\n- [ ] Sanitize HTML content if needed\n- [ ] Validate URLs before use\n- [ ] No javascript: URLs\n\n### CSP\n- [ ] CSP header configured\n- [ ] Application works with CSP enabled\n- [ ] No CSP violations in console\n- [ ] script-src and style-src configured\n\n### Clickjacking\n- [ ] X-Frame-Options header set\n- [ ] frame-ancestors CSP directive set\n- [ ] Framebuster script if needed\n\n### Authentication\n- [ ] No credentials in code\n- [ ] Secure token storage\n- [ ] HTTPS only in production\n- [ ] Session timeout implemented\n\n### Authorization\n- [ ] Server-side permission checks\n- [ ] Client-side checks for UI only\n- [ ] Role-based access control\n- [ ] Least privilege principle\n\n### Data Transmission\n- [ ] HTTPS everywhere\n- [ ] CORS properly configured\n- [ ] No sensitive data in URLs\n- [ ] Secure cookie flags (HttpOnly, Secure)\n\n### Input Validation\n- [ ] Client-side format validation\n- [ ] Server-side security validation\n- [ ] Data types with constraints\n- [ ] Whitelist, not blacklist\n\n### Secure Storage\n- [ ] No sensitive data in localStorage\n- [ ] Session management server-side\n- [ ] httpOnly cookies for tokens\n\n---\n\n## Common Vulnerabilities\n\n### 1. Reflected XSS\n\n**Vulnerable**:\n```javascript\nvar sSearch = new URLSearchParams(window.location.search).get(\"q\");\nthis.byId(\"searchField\").getDomRef().innerHTML = sSearch;\n```\n\n**Fixed**:\n```javascript\nvar sSearch = new URLSearchParams(window.location.search).get(\"q\");\nthis.byId(\"searchField\").setValue(sSearch);\n```\n\n### 2. DOM-based XSS\n\n**Vulnerable**:\n```javascript\nvar sUrl = location.hash.substring(1);\nwindow.location = sUrl;\n```\n\n**Fixed**:\n```javascript\nsap.ui.require([\"sap/base/security/URLWhitelist\"], function(URLWhitelist) {\n var sUrl = location.hash.substring(1);\n if (URLWhitelist.validate(sUrl)) {\n window.location = sUrl;\n }\n});\n```\n\n### 3. Insecure Direct Object Reference\n\n**Vulnerable**:\n```javascript\n// User can modify ID in URL\nvar sId = oRouter.getParameter(\"id\");\noModel.read(\"/Orders(\" + sId + \")\"); // Access to any order\n```\n\n**Fixed**:\n```javascript\n// Server checks if user has access to this order\nvar sId = oRouter.getParameter(\"id\");\noModel.read(\"/Orders(\" + sId + \")\", {\n // Server validates: user can only access their own orders\n});\n```\n\n---\n\n## Official Documentation\n\n- **Security**: [https://github.com/SAP-docs/sapui5/tree/main/docs/05_Developing_Apps](https://github.com/SAP-docs/sapui5/tree/main/docs/05_Developing_Apps) (search: security, secure-programming)\n- **XSS Prevention**: [https://github.com/SAP-docs/sapui5/tree/main/docs/05_Developing_Apps](https://github.com/SAP-docs/sapui5/tree/main/docs/05_Developing_Apps) (search: cross-site-scripting)\n- **CSP**: [https://github.com/SAP-docs/sapui5/tree/main/docs/05_Developing_Apps](https://github.com/SAP-docs/sapui5/tree/main/docs/05_Developing_Apps) (search: content-security-policy)\n- **URL Whitelist**: [https://sapui5.hana.ondemand.com/#/api/sap.base.security.URLWhitelist](https://sapui5.hana.ondemand.com/#/api/sap.base.security.URLWhitelist)\n- **OWASP Top 10**: [https://owasp.org/www-project-top-ten/](https://owasp.org/www-project-top-ten/)\n\n---\n\n**Note**: This document covers security best practices for SAPUI5. Security is not optional - it must be built into every application from the start. Always follow the principle of defense in depth.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":14778,"content_sha256":"7ec4fecebf3ce4ebd8fdc3010b3ebd5d4d03172b122d4e1f6378f64cfb09eddd"},{"filename":"references/testing.md","content":"# SAPUI5 Testing Guide\n\n**Source**: Official SAP SAPUI5 Documentation\n**Documentation**: [https://github.com/SAP-docs/sapui5/tree/main/docs/04_Essentials](https://github.com/SAP-docs/sapui5/tree/main/docs/04_Essentials)\n**Last Updated**: 2025-11-21\n\n---\n\n## Testing Strategy\n\nSAPUI5 applications should include:\n\n1. **Unit Tests** (QUnit): Test individual functions and modules\n2. **Integration Tests** (OPA5): Test user interactions and flows\n3. **Mock Server**: Simulate backend for testing without real services\n\n**Documentation**: [https://github.com/SAP-docs/sapui5/tree/main/docs/04_Essentials](https://github.com/SAP-docs/sapui5/tree/main/docs/04_Essentials) (search: testing, qunit, opa)\n\n---\n\n## QUnit Testing\n\nUnit testing framework for JavaScript code.\n\n### Setup\n\n**test/unit/unitTests.qunit.html**:\n```html\n\u003c!DOCTYPE html>\n\u003chtml>\n\u003chead>\n \u003cmeta charset=\"utf-8\">\n \u003ctitle>Unit Tests\u003c/title>\n\n \u003cscript\n id=\"sap-ui-bootstrap\"\n src=\"../../resources/sap-ui-core.js\"\n data-sap-ui-theme=\"sap_horizon\"\n data-sap-ui-resourceroots='{\n \"com.mycompany.myapp\": \"../../\"\n }'\n data-sap-ui-async=\"true\">\n \u003c/script>\n\n \u003clink rel=\"stylesheet\" type=\"text/css\" href=\"../../resources/sap/ui/thirdparty/qunit-2.css\">\n \u003cscript src=\"../../resources/sap/ui/thirdparty/qunit-2.js\">\u003c/script>\n \u003cscript src=\"../../resources/sap/ui/qunit/qunit-junit.js\">\u003c/script>\n \u003cscript src=\"../../resources/sap/ui/qunit/qunit-coverage.js\">\u003c/script>\n\n \u003cscript src=\"unitTests.qunit.js\">\u003c/script>\n\u003c/head>\n\u003cbody>\n \u003cdiv id=\"qunit\">\u003c/div>\n \u003cdiv id=\"qunit-fixture\">\u003c/div>\n\u003c/body>\n\u003c/html>\n```\n\n**test/unit/unitTests.qunit.js**:\n```javascript\nQUnit.config.autostart = false;\n\nsap.ui.require([\n \"com/mycompany/myapp/test/unit/model/formatter\"\n], function() {\n \"use strict\";\n QUnit.start();\n});\n```\n\n### Writing Unit Tests\n\n**test/unit/model/formatter.js**:\n```javascript\nsap.ui.define([\n \"com/mycompany/myapp/model/formatter\",\n \"sap/ui/model/resource/ResourceModel\"\n], function(formatter, ResourceModel) {\n \"use strict\";\n\n QUnit.module(\"Formatter Tests\");\n\n QUnit.test(\"Should format price correctly\", function(assert) {\n // Arrange\n var fPrice = 123.456;\n\n // Act\n var sResult = formatter.formatPrice(fPrice);\n\n // Assert\n assert.strictEqual(sResult, \"123.46 EUR\", \"Price formatted correctly\");\n });\n\n QUnit.test(\"Should return empty string for null price\", function(assert) {\n // Arrange & Act\n var sResult = formatter.formatPrice(null);\n\n // Assert\n assert.strictEqual(sResult, \"\", \"Empty string for null\");\n });\n\n QUnit.test(\"Should format status text\", function(assert) {\n // Arrange\n var sStatus = \"A\";\n\n // Act\n var sResult = formatter.formatStatus(sStatus);\n\n // Assert\n assert.strictEqual(sResult, \"Approved\", \"Status formatted correctly\");\n });\n\n QUnit.test(\"Should calculate total\", function(assert) {\n // Arrange\n var iQuantity = 5;\n var fPrice = 10.5;\n\n // Act\n var fTotal = formatter.calculateTotal(iQuantity, fPrice);\n\n // Assert\n assert.strictEqual(fTotal, 52.5, \"Total calculated correctly\");\n });\n});\n```\n\n### Async Tests\n\n```javascript\nQUnit.test(\"Should load data asynchronously\", function(assert) {\n // Indicate async test\n var done = assert.async();\n\n // Arrange\n var oModel = new JSONModel();\n\n // Act\n oModel.loadData(\"test/data/products.json\");\n\n // Assert after data loaded\n oModel.attachRequestCompleted(function() {\n var aProducts = oModel.getProperty(\"/products\");\n assert.ok(aProducts.length > 0, \"Data loaded successfully\");\n done(); // Mark test complete\n });\n});\n```\n\n### Testing Controllers\n\n```javascript\nsap.ui.define([\n \"com/mycompany/myapp/controller/Main.controller\",\n \"sap/ui/thirdparty/sinon\",\n \"sap/ui/thirdparty/sinon-qunit\"\n], function(MainController) {\n \"use strict\";\n\n QUnit.module(\"Main Controller\", {\n beforeEach: function() {\n this.oController = new MainController();\n },\n afterEach: function() {\n this.oController.destroy();\n }\n });\n\n QUnit.test(\"Should initialize correctly\", function(assert) {\n // Arrange - spy on method\n var oSpy = sinon.spy(this.oController, \"_loadData\");\n\n // Act\n this.oController.onInit();\n\n // Assert\n assert.ok(oSpy.calledOnce, \"Load data called on init\");\n });\n});\n```\n\n**Documentation**: [https://github.com/SAP-docs/sapui5/tree/main/docs/04_Essentials](https://github.com/SAP-docs/sapui5/tree/main/docs/04_Essentials) (search: qunit)\n\n---\n\n## OPA5 Testing\n\nOne Page Acceptance testing for integration tests.\n\n### Setup\n\n**test/integration/opaTests.qunit.html**:\n```html\n\u003c!DOCTYPE html>\n\u003chtml>\n\u003chead>\n \u003cmeta charset=\"utf-8\">\n \u003ctitle>Integration Tests\u003c/title>\n\n \u003cscript\n id=\"sap-ui-bootstrap\"\n src=\"../../resources/sap-ui-core.js\"\n data-sap-ui-theme=\"sap_horizon\"\n data-sap-ui-resourceroots='{\n \"com.mycompany.myapp\": \"../../\"\n }'\n data-sap-ui-async=\"true\">\n \u003c/script>\n\n \u003clink rel=\"stylesheet\" type=\"text/css\" href=\"../../resources/sap/ui/thirdparty/qunit-2.css\">\n \u003cscript src=\"../../resources/sap/ui/thirdparty/qunit-2.js\">\u003c/script>\n \u003cscript src=\"../../resources/sap/ui/qunit/qunit-junit.js\">\u003c/script>\n\n \u003cscript src=\"opaTests.qunit.js\">\u003c/script>\n\u003c/head>\n\u003cbody>\n \u003cdiv id=\"qunit\">\u003c/div>\n \u003cdiv id=\"qunit-fixture\">\u003c/div>\n\u003c/body>\n\u003c/html>\n```\n\n**test/integration/opaTests.qunit.js**:\n```javascript\nQUnit.config.autostart = false;\n\nsap.ui.require([\n \"sap/ui/test/Opa5\",\n \"com/mycompany/myapp/test/integration/arrangements/Startup\",\n \"com/mycompany/myapp/test/integration/WorklistJourney\"\n], function(Opa5, Startup) {\n \"use strict\";\n\n Opa5.extendConfig({\n arrangements: new Startup(),\n viewNamespace: \"com.mycompany.myapp.view.\",\n autoWait: true\n });\n\n QUnit.start();\n});\n```\n\n### Page Objects\n\n**test/integration/pages/Worklist.js**:\n```javascript\nsap.ui.define([\n \"sap/ui/test/Opa5\",\n \"sap/ui/test/actions/Press\",\n \"sap/ui/test/actions/EnterText\",\n \"sap/ui/test/matchers/AggregationLengthEquals\",\n \"sap/ui/test/matchers/PropertyStrictEquals\"\n], function(Opa5, Press, EnterText, AggregationLengthEquals, PropertyStrictEquals) {\n \"use strict\";\n\n var sViewName = \"Worklist\";\n\n Opa5.createPageObjects({\n onTheWorklistPage: {\n actions: {\n iPressOnTheFirstListItem: function() {\n return this.waitFor({\n id: \"table\",\n viewName: sViewName,\n actions: new Press(),\n errorMessage: \"The table is not visible\"\n });\n },\n\n iEnterSearchTerm: function(sSearchTerm) {\n return this.waitFor({\n id: \"searchField\",\n viewName: sViewName,\n actions: new EnterText({ text: sSearchTerm }),\n errorMessage: \"Search field not found\"\n });\n },\n\n iPressTheAddButton: function() {\n return this.waitFor({\n id: \"addButton\",\n viewName: sViewName,\n actions: new Press(),\n errorMessage: \"Add button not found\"\n });\n }\n },\n\n assertions: {\n theTableShouldHaveAllEntries: function() {\n return this.waitFor({\n id: \"table\",\n viewName: sViewName,\n matchers: new AggregationLengthEquals({\n name: \"items\",\n length: 10\n }),\n success: function() {\n Opa5.assert.ok(true, \"Table has 10 entries\");\n },\n errorMessage: \"Table does not have expected entries\"\n });\n },\n\n theTableShouldBeVisible: function() {\n return this.waitFor({\n id: \"table\",\n viewName: sViewName,\n success: function(oTable) {\n Opa5.assert.ok(oTable.getVisible(), \"Table is visible\");\n },\n errorMessage: \"Table is not visible\"\n });\n },\n\n iShouldSeeTheCorrectTitle: function() {\n return this.waitFor({\n id: \"page\",\n viewName: sViewName,\n matchers: new PropertyStrictEquals({\n name: \"title\",\n value: \"Worklist\"\n }),\n success: function() {\n Opa5.assert.ok(true, \"Page title is correct\");\n },\n errorMessage: \"Page title is incorrect\"\n });\n }\n }\n }\n });\n});\n```\n\n### Journeys (Test Scenarios)\n\n**test/integration/WorklistJourney.js**:\n```javascript\nsap.ui.define([\n \"sap/ui/test/opaQunit\",\n \"./pages/Worklist\",\n \"./pages/Object\"\n], function(opaTest) {\n \"use strict\";\n\n QUnit.module(\"Worklist Journey\");\n\n opaTest(\"Should see the table with all entries\", function(Given, When, Then) {\n // Arrangements\n Given.iStartMyApp();\n\n // Actions\n // (none - just checking initial state)\n\n // Assertions\n Then.onTheWorklistPage.theTableShouldBeVisible();\n Then.onTheWorklistPage.theTableShouldHaveAllEntries();\n\n // Cleanup\n Then.iTeardownMyApp();\n });\n\n opaTest(\"Should be able to search for items\", function(Given, When, Then) {\n // Arrangements\n Given.iStartMyApp();\n\n // Actions\n When.onTheWorklistPage.iEnterSearchTerm(\"Product\");\n\n // Assertions\n Then.onTheWorklistPage.theTableShouldHaveAllEntries();\n\n // Cleanup\n Then.iTeardownMyApp();\n });\n\n opaTest(\"Should navigate to object page when item is pressed\", function(Given, When, Then) {\n // Arrangements\n Given.iStartMyApp();\n\n // Actions\n When.onTheWorklistPage.iPressOnTheFirstListItem();\n\n // Assertions\n Then.onTheObjectPage.iShouldSeeTheObjectPage();\n\n // Cleanup\n Then.iTeardownMyApp();\n });\n});\n```\n\n### Startup Arrangements\n\n**test/integration/arrangements/Startup.js**:\n```javascript\nsap.ui.define([\n \"sap/ui/test/Opa5\"\n], function(Opa5) {\n \"use strict\";\n\n return Opa5.extend(\"com.mycompany.myapp.test.integration.arrangements.Startup\", {\n iStartMyApp: function() {\n this.iStartMyUIComponent({\n componentConfig: {\n name: \"com.mycompany.myapp\",\n async: true,\n manifest: true\n },\n hash: \"\",\n autoWait: true\n });\n }\n });\n});\n```\n\n**Documentation**: [https://github.com/SAP-docs/sapui5/tree/main/docs/04_Essentials](https://github.com/SAP-docs/sapui5/tree/main/docs/04_Essentials) (search: opa5, integration-testing)\n\n---\n\n## Mock Server\n\nSimulate OData backend for testing.\n\n### Setup\n\n**localService/metadata.xml**:\nCopy metadata from real service or create manually.\n\n**localService/mockdata/Products.json**:\n```json\n[\n {\n \"ProductID\": 1,\n \"Name\": \"Product 1\",\n \"Price\": \"100.00\",\n \"CategoryID\": 1\n },\n {\n \"ProductID\": 2,\n \"Name\": \"Product 2\",\n \"Price\": \"200.00\",\n \"CategoryID\": 2\n }\n]\n```\n\n**localService/mockserver.js**:\n```javascript\nsap.ui.define([\n \"sap/ui/core/util/MockServer\",\n \"sap/base/util/UriParameters\"\n], function(MockServer, UriParameters) {\n \"use strict\";\n\n return {\n init: function() {\n // Create mock server\n var oMockServer = new MockServer({\n rootUri: \"/sap/opu/odata/sap/SERVICE_SRV/\"\n });\n\n // Configure mock server\n var sPath = sap.ui.require.toUrl(\"com/mycompany/myapp/localService\");\n\n MockServer.config({\n autoRespond: true,\n autoRespondAfter: 1000 // 1 second delay\n });\n\n // Simulate backend\n oMockServer.simulate(sPath + \"/metadata.xml\", {\n sMockdataBaseUrl: sPath + \"/mockdata\",\n bGenerateMissingMockData: true\n });\n\n // Custom request handling\n var aRequests = oMockServer.getRequests();\n\n // Add custom response for specific request\n aRequests.push({\n method: \"GET\",\n path: new RegExp(\"Products\\\\?.*\"),\n response: function(oXhr) {\n oXhr.respondJSON(200, {}, JSON.stringify({\n d: {\n results: [\n // Custom data\n ]\n }\n }));\n return true;\n }\n });\n\n oMockServer.setRequests(aRequests);\n\n // Start mock server\n oMockServer.start();\n\n return oMockServer;\n }\n };\n});\n```\n\n**test/initMockServer.js**:\n```javascript\nsap.ui.define([\n \"com/mycompany/myapp/localService/mockserver\"\n], function(mockserver) {\n \"use strict\";\n\n // Initialize mock server\n mockserver.init();\n\n // Initialize application\n sap.ui.require([\n \"sap/ui/core/ComponentSupport\"\n ]);\n});\n```\n\n**Start with Mock Server**:\n```html\n\u003c!-- In test HTML file -->\n\u003cscript src=\"test/initMockServer.js\">\u003c/script>\n```\n\n**Conditional Mock Server** (development only):\n```javascript\nsap.ui.define([\n \"sap/base/util/UriParameters\"\n], function(UriParameters) {\n \"use strict\";\n\n var oUriParameters = new UriParameters(window.location.href);\n var bUseMockServer = oUriParameters.get(\"useMockServer\") === \"true\";\n\n if (bUseMockServer) {\n sap.ui.require([\n \"com/mycompany/myapp/localService/mockserver\"\n ], function(mockserver) {\n mockserver.init();\n initApp();\n });\n } else {\n initApp();\n }\n\n function initApp() {\n sap.ui.require([\"sap/ui/core/ComponentSupport\"]);\n }\n});\n```\n\n**Usage**: `index.html?useMockServer=true`\n\n**Documentation**: [https://github.com/SAP-docs/sapui5/tree/main/docs/04_Essentials](https://github.com/SAP-docs/sapui5/tree/main/docs/04_Essentials) (search: mock-server)\n\n---\n\n## Test Automation Best Practices\n\n### 1. Test Structure (AAA Pattern)\n\n```javascript\nQUnit.test(\"Test description\", function(assert) {\n // Arrange - setup test data and conditions\n var oModel = new JSONModel({ value: 10 });\n\n // Act - execute the function being tested\n var iResult = myFunction(oModel.getProperty(\"/value\"));\n\n // Assert - verify the result\n assert.strictEqual(iResult, 20, \"Result is correct\");\n});\n```\n\n### 2. Use Descriptive Test Names\n\n```javascript\n// Good\nQUnit.test(\"Should format currency with two decimal places\", function(assert) {});\nQUnit.test(\"Should return empty string when price is null\", function(assert) {});\n\n// Bad\nQUnit.test(\"Test 1\", function(assert) {});\nQUnit.test(\"Format test\", function(assert) {});\n```\n\n### 3. Test One Thing Per Test\n\n```javascript\n// Good - separate tests\nQUnit.test(\"Should add item to cart\", function(assert) {});\nQUnit.test(\"Should calculate correct total\", function(assert) {});\n\n// Bad - testing multiple things\nQUnit.test(\"Should add item and calculate total\", function(assert) {});\n```\n\n### 4. Use Spies and Stubs (Sinon.js)\n\n```javascript\nQUnit.test(\"Should call service method\", function(assert) {\n // Arrange\n var oStub = sinon.stub(oService, \"getData\").returns([]);\n\n // Act\n oController.loadData();\n\n // Assert\n assert.ok(oStub.calledOnce, \"Service called once\");\n\n // Cleanup\n oStub.restore();\n});\n```\n\n### 5. Clean Up After Tests\n\n```javascript\nQUnit.module(\"Test Module\", {\n beforeEach: function() {\n // Setup before each test\n this.oModel = new JSONModel();\n },\n afterEach: function() {\n // Cleanup after each test\n this.oModel.destroy();\n }\n});\n```\n\n### 6. Use autoWait in OPA5\n\n```javascript\nOpa5.extendConfig({\n autoWait: true // Automatically waits for UI to be ready\n});\n```\n\n### 7. Organize Tests by Feature\n\n```\ntest/\n├── unit/\n│ ├── model/\n│ │ └── formatter.js\n│ ├── controller/\n│ │ └── Main.controller.js\n│ └── unitTests.qunit.html\n├── integration/\n│ ├── pages/\n│ │ ├── Worklist.js\n│ │ └── Object.js\n│ ├── WorklistJourney.js\n│ ├── NavigationJourney.js\n│ └── opaTests.qunit.html\n└── localService/\n ├── mockserver.js\n ├── metadata.xml\n └── mockdata/\n```\n\n---\n\n## Code Coverage\n\nEnable code coverage in QUnit:\n\n**test/unit/unitTests.qunit.html**:\n```html\n\u003cscript src=\"../../resources/sap/ui/qunit/qunit-coverage.js\">\u003c/script>\n```\n\n**View Coverage Report**:\n1. Run tests in browser\n2. Click \"Enable coverage\" in QUnit toolbar\n3. View coverage report after tests complete\n\n**Coverage Goals**:\n- Unit tests: 80%+ coverage\n- Integration tests: Cover all critical user flows\n\n**Documentation**: [https://github.com/SAP-docs/sapui5/tree/main/docs/04_Essentials](https://github.com/SAP-docs/sapui5/tree/main/docs/04_Essentials) (search: code-coverage)\n\n---\n\n## Continuous Integration\n\n### Karma Runner\n\n**karma.conf.js**:\n```javascript\nmodule.exports = function(config) {\n config.set({\n frameworks: [\"ui5\"],\n ui5: {\n type: \"application\",\n configPath: \"ui5.yaml\",\n paths: {\n webapp: \"webapp\"\n }\n },\n browsers: [\"ChromeHeadless\"],\n reporters: [\"progress\", \"coverage\"],\n coverageReporter: {\n type: \"html\",\n dir: \"coverage/\"\n },\n singleRun: true\n });\n};\n```\n\n**package.json**:\n```json\n{\n \"scripts\": {\n \"test\": \"karma start\"\n },\n \"devDependencies\": {\n \"karma\": \"^6.0.0\",\n \"karma-ui5\": \"^2.0.0\",\n \"karma-chrome-launcher\": \"^3.0.0\",\n \"karma-coverage\": \"^2.0.0\"\n }\n}\n```\n\n**Run Tests**:\n```bash\nnpm test\n```\n\n---\n\n## Testing Tools\n\n### UI5 Inspector\n\nBrowser extension for debugging SAPUI5 apps:\n- View control tree\n- Inspect control properties\n- View bindings\n- Check performance\n\n**Installation**: Available for Chrome and Firefox\n\n### Support Assistant\n\nBuilt-in tool for quality checks:\n\n```javascript\n// Enable in code\nsap.ui.require([\"sap/ui/support/RuleAnalyzer\"], function(RuleAnalyzer) {\n RuleAnalyzer.analyze();\n});\n```\n\n**Or use keyboard**: `Ctrl+Alt+Shift+S`\n\n**Documentation**: [https://github.com/SAP-docs/sapui5/tree/main/docs/04_Essentials](https://github.com/SAP-docs/sapui5/tree/main/docs/04_Essentials) (search: support-assistant)\n\n---\n\n## Links to Official Documentation\n\n- **Testing Overview**: [https://github.com/SAP-docs/sapui5/tree/main/docs/04_Essentials](https://github.com/SAP-docs/sapui5/tree/main/docs/04_Essentials) (search: testing)\n- **QUnit**: [https://github.com/SAP-docs/sapui5/tree/main/docs/04_Essentials](https://github.com/SAP-docs/sapui5/tree/main/docs/04_Essentials) (search: qunit)\n- **OPA5**: [https://github.com/SAP-docs/sapui5/tree/main/docs/04_Essentials](https://github.com/SAP-docs/sapui5/tree/main/docs/04_Essentials) (search: opa5)\n- **Mock Server**: [https://github.com/SAP-docs/sapui5/tree/main/docs/04_Essentials](https://github.com/SAP-docs/sapui5/tree/main/docs/04_Essentials) (search: mock-server)\n- **Get Started Testing**: [https://github.com/SAP-docs/sapui5/tree/main/docs/03_Get-Started](https://github.com/SAP-docs/sapui5/tree/main/docs/03_Get-Started) (search: testing)\n\n---\n\n**Note**: This document covers testing strategies and best practices for SAPUI5 applications. For specific testing scenarios and advanced techniques, refer to the official documentation links provided.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":20526,"content_sha256":"be9aaf18c3461cd098188581070e323b23dccee5646d977159239f6f49b282a1"},{"filename":"references/typescript-support.md","content":"# TypeScript Support in SAPUI5\n\n**Source**: Official SAP SAPUI5 Documentation\n**Documentation**: [https://github.com/SAP-docs/sapui5/tree/main/docs/02_Read-Me-First](https://github.com/SAP-docs/sapui5/tree/main/docs/02_Read-Me-First)\n**Last Updated**: 2025-11-21\n\n---\n\n## Overview\n\nTypeScript enhances JavaScript development by adding type information, enabling early error detection and improved code assistance. SAPUI5 provides dedicated type definitions for fully-typed application development.\n\n**Benefits**:\n- Early error detection during development\n- Better code completion and IntelliSense\n- Improved refactoring support\n- Enhanced documentation through types\n- Safer code through compile-time checks\n\n**Production Status**: TypeScript support is production-ready and actively maintained by the SAPUI5 team.\n\n---\n\n## Type Definition Packages\n\n### @sapui5/types (Recommended)\n\nOfficially maintained by the SAPUI5 development team:\n\n```bash\nnpm install --save-dev @sapui5/types\n```\n\n**Features**:\n- Official type definitions\n- Regular updates with SAPUI5 releases\n- Full framework coverage\n- Maintained by SAP\n\n**Configuration** (tsconfig.json):\n```json\n{\n \"compilerOptions\": {\n \"module\": \"es2022\",\n \"moduleResolution\": \"node\",\n \"target\": \"es2022\",\n \"lib\": [\"es2022\", \"dom\"],\n \"types\": [\"@sapui5/types\"],\n \"skipLibCheck\": true,\n \"strict\": true\n }\n}\n```\n\n### @types/openui5 (Community)\n\nCommunity-maintained via DefinitelyTyped:\n\n```bash\nnpm install --save-dev @types/openui5\n```\n\n**Use Case**: Alternative for OpenUI5 projects or when specific versions needed.\n\n---\n\n## Project Setup\n\n### Basic TypeScript SAPUI5 Project\n\n**package.json**:\n```json\n{\n \"name\": \"my-sapui5-ts-app\",\n \"version\": \"1.0.0\",\n \"scripts\": {\n \"build\": \"tsc && ui5 build\",\n \"start\": \"ui5 serve\",\n \"watch\": \"tsc --watch\"\n },\n \"devDependencies\": {\n \"@sapui5/types\": \"^1.120.0\",\n \"@ui5/cli\": \"^3.0.0\",\n \"typescript\": \"^5.0.0\"\n }\n}\n```\n\n**tsconfig.json**:\n```json\n{\n \"compilerOptions\": {\n \"target\": \"es2022\",\n \"module\": \"es2022\",\n \"moduleResolution\": \"node\",\n \"lib\": [\"es2022\", \"dom\"],\n \"types\": [\"@sapui5/types\"],\n \"outDir\": \"./webapp\",\n \"rootDir\": \"./src\",\n \"strict\": true,\n \"esModuleInterop\": true,\n \"skipLibCheck\": true,\n \"forceConsistentCasingInFileNames\": true,\n \"resolveJsonModule\": true,\n \"declaration\": true,\n \"sourceMap\": true\n },\n \"include\": [\"src/**/*\"],\n \"exclude\": [\"node_modules\"]\n}\n```\n\n**ui5.yaml**:\n```yaml\nspecVersion: \"3.0\"\nmetadata:\n name: my.sapui5.ts.app\ntype: application\nframework:\n name: SAPUI5\n version: \"1.120.0\"\n libraries:\n - name: sap.m\n - name: sap.ui.core\n - name: sap.f\n```\n\n---\n\n## TypeScript Component\n\n**src/Component.ts**:\n```typescript\nimport UIComponent from \"sap/ui/core/UIComponent\";\nimport models from \"./model/models\";\nimport Device from \"sap/ui/Device\";\nimport JSONModel from \"sap/ui/model/json/JSONModel\";\n\n/**\n * @namespace my.sapui5.ts.app\n */\nexport default class Component extends UIComponent {\n public static metadata = {\n manifest: \"json\"\n };\n\n private contentDensityClass: string;\n\n public init(): void {\n // Call parent init\n super.init();\n\n // Set device model\n this.setModel(models.createDeviceModel(), \"device\");\n\n // Create router\n this.getRouter().initialize();\n }\n\n /**\n * Get content density CSS class\n */\n public getContentDensityClass(): string {\n if (this.contentDensityClass === undefined) {\n // Check if touch device\n if (!Device.support.touch) {\n this.contentDensityClass = \"sapUiSizeCompact\";\n } else {\n this.contentDensityClass = \"sapUiSizeCozy\";\n }\n }\n return this.contentDensityClass;\n }\n}\n```\n\n---\n\n## TypeScript Controller\n\n**src/controller/Main.controller.ts**:\n```typescript\nimport Controller from \"sap/ui/core/mvc/Controller\";\nimport MessageToast from \"sap/m/MessageToast\";\nimport MessageBox from \"sap/m/MessageBox\";\nimport JSONModel from \"sap/ui/model/json/JSONModel\";\nimport Filter from \"sap/ui/model/Filter\";\nimport FilterOperator from \"sap/ui/model/FilterOperator\";\nimport Event from \"sap/ui/base/Event\";\nimport ResourceBundle from \"sap/base/i18n/ResourceBundle\";\nimport ResourceModel from \"sap/ui/model/resource/ResourceModel\";\nimport Table from \"sap/m/Table\";\nimport SearchField from \"sap/m/SearchField\";\n\n/**\n * @namespace my.sapui5.ts.app.controller\n */\nexport default class Main extends Controller {\n private viewModel: JSONModel;\n\n /*eslint-disable @typescript-eslint/no-empty-function*/\n public onInit(): void {\n // Create view model\n this.viewModel = new JSONModel({\n busy: false,\n selectedItemsCount: 0\n });\n this.getView()?.setModel(this.viewModel, \"view\");\n }\n\n /**\n * Search handler\n */\n public onSearch(event: Event): void {\n const searchField = event.getSource() as SearchField;\n const query = searchField.getValue();\n\n const filters: Filter[] = [];\n if (query && query.length > 0) {\n filters.push(new Filter(\"Name\", FilterOperator.Contains, query));\n }\n\n const table = this.byId(\"productsTable\") as Table;\n const binding = table.getBinding(\"items\");\n binding?.filter(filters);\n }\n\n /**\n * Button press handler\n */\n public onPress(): void {\n const resourceBundle = this.getResourceBundle();\n MessageToast.show(resourceBundle.getText(\"buttonPressed\") as string);\n }\n\n /**\n * Delete confirmation\n */\n public onDelete(): void {\n const resourceBundle = this.getResourceBundle();\n MessageBox.confirm(\n resourceBundle.getText(\"deleteConfirm\") as string,\n {\n onClose: (action: string) => {\n if (action === MessageBox.Action.OK) {\n this.performDelete();\n }\n }\n }\n );\n }\n\n /**\n * Get resource bundle\n */\n private getResourceBundle(): ResourceBundle {\n const model = this.getView()?.getModel(\"i18n\") as ResourceModel;\n return model.getResourceBundle() as ResourceBundle;\n }\n\n /**\n * Perform delete operation\n */\n private performDelete(): void {\n // Delete logic here\n MessageToast.show(\"Item deleted\");\n }\n}\n```\n\n---\n\n## TypeScript Model/Formatter\n\n**src/model/formatter.ts**:\n```typescript\nimport DateFormat from \"sap/ui/core/format/DateFormat\";\nimport NumberFormat from \"sap/ui/core/format/NumberFormat\";\n\nexport default {\n /**\n * Format date to localized string\n */\n formatDate(date: Date | string | null): string {\n if (!date) {\n return \"\";\n }\n\n const dateFormat = DateFormat.getDateInstance({\n pattern: \"dd.MM.yyyy\"\n });\n\n return dateFormat.format(new Date(date));\n },\n\n /**\n * Format currency\n */\n formatCurrency(amount: number | null, currency: string): string {\n if (amount === null || amount === undefined) {\n return \"\";\n }\n\n const currencyFormat = NumberFormat.getCurrencyInstance();\n return currencyFormat.format(amount, currency);\n },\n\n /**\n * Format status\n */\n formatStatus(status: string): string {\n const statusMap: Record\u003cstring, string> = {\n \"A\": \"Approved\",\n \"R\": \"Rejected\",\n \"P\": \"Pending\"\n };\n\n return statusMap[status] || status;\n },\n\n /**\n * Format status state\n */\n formatStatusState(status: string): string {\n const stateMap: Record\u003cstring, string> = {\n \"A\": \"Success\",\n \"R\": \"Error\",\n \"P\": \"Warning\"\n };\n\n return stateMap[status] || \"None\";\n }\n};\n```\n\n---\n\n## Custom Control in TypeScript\n\n**src/control/CustomButton.ts**:\n```typescript\nimport Button from \"sap/m/Button\";\nimport RenderManager from \"sap/ui/core/RenderManager\";\n\n/**\n * @namespace my.sapui5.ts.app.control\n */\nexport default class CustomButton extends Button {\n public static metadata = {\n properties: {\n customProperty: {\n type: \"string\",\n defaultValue: \"\"\n }\n },\n events: {\n customPress: {\n parameters: {\n value: { type: \"string\" }\n }\n }\n }\n };\n\n // Property getter/setter\n public getCustomProperty(): string {\n return this.getProperty(\"customProperty\") as string;\n }\n\n public setCustomProperty(value: string): this {\n this.setProperty(\"customProperty\", value);\n return this;\n }\n\n // Custom renderer\n public static renderer = {\n ...Button.renderer,\n\n render: function(rm: RenderManager, control: CustomButton): void {\n // Custom rendering logic\n Button.renderer.render(rm, control);\n }\n };\n\n /**\n * Custom method\n */\n public customMethod(): void {\n this.fireEvent(\"customPress\", {\n value: this.getCustomProperty()\n });\n }\n}\n```\n\n---\n\n## Type Definitions for Custom Types\n\n**src/types/index.d.ts**:\n```typescript\ndeclare namespace my.sapui5.ts.app {\n // Custom types\n interface Product {\n id: string;\n name: string;\n price: number;\n currency: string;\n category: string;\n status: \"A\" | \"R\" | \"P\";\n }\n\n interface AppConfiguration {\n apiUrl: string;\n timeout: number;\n debug: boolean;\n }\n\n type StatusType = \"A\" | \"R\" | \"P\";\n type ViewMode = \"display\" | \"edit\" | \"create\";\n}\n```\n\n---\n\n## Best Practices\n\n### 1. Use Strict Mode\n\nEnable strict TypeScript checking:\n```json\n{\n \"compilerOptions\": {\n \"strict\": true,\n \"noImplicitAny\": true,\n \"strictNullChecks\": true,\n \"strictFunctionTypes\": true\n }\n}\n```\n\n### 2. Type Assertions\n\nUse type assertions when type cannot be inferred:\n```typescript\nconst table = this.byId(\"productsTable\") as Table;\nconst model = this.getView()?.getModel() as JSONModel;\n```\n\n### 3. Optional Chaining\n\nUse optional chaining for nullable values:\n```typescript\nconst view = this.getView();\nview?.setModel(model);\n```\n\n### 4. Generics with Models\n\nType your model data:\n```typescript\ninterface Product {\n id: string;\n name: string;\n price: number;\n}\n\nconst model = new JSONModel\u003cProduct[]>([]);\nconst data = model.getData(); // Type: Product[]\n```\n\n### 5. Event Typing\n\nType event handlers properly:\n```typescript\nimport Event from \"sap/ui/base/Event\";\nimport Input from \"sap/m/Input\";\n\npublic onChange(event: Event): void {\n const input = event.getSource() as Input;\n const value = input.getValue();\n}\n```\n\n---\n\n## Common Issues\n\n### Issue: Cannot find module 'sap/...'\n\n**Solution**: Install type definitions:\n```bash\nnpm install --save-dev @sapui5/types\n```\n\nAdd to tsconfig.json:\n```json\n{\n \"compilerOptions\": {\n \"types\": [\"@sapui5/types\"]\n }\n}\n```\n\n### Issue: Type errors with metadata\n\n**Solution**: Use static metadata property:\n```typescript\nexport default class MyController extends Controller {\n public static metadata = {\n // metadata here\n };\n}\n```\n\n### Issue: 'this' implicitly has type 'any'\n\n**Solution**: Use arrow functions or bind 'this':\n```typescript\n// Arrow function\n.then((data) => {\n this.processData(data);\n});\n\n// Bind\n.then(function(data) {\n this.processData(data);\n}.bind(this));\n```\n\n---\n\n## Compatibility\n\n**TypeScript Version**: 4.0+ (5.0+ recommended)\n**SAPUI5 Version**: 1.60+ (1.120+ recommended)\n**Node.js Version**: 16+ (18+ recommended)\n\n---\n\n## Migration from JavaScript\n\n### Step 1: Rename Files\n\nRename `.js` files to `.ts`:\n```bash\nmv Component.js Component.ts\nmv controller/Main.controller.js controller/Main.controller.ts\n```\n\n### Step 2: Add Type Annotations\n\nAdd types gradually:\n```typescript\n// Before (JS)\nonPress: function(oEvent) {\n var sValue = oEvent.getSource().getValue();\n}\n\n// After (TS)\npublic onPress(event: Event): void {\n const source = event.getSource() as Input;\n const value: string = source.getValue();\n}\n```\n\n### Step 3: Fix Type Errors\n\nAddress compilation errors one by one:\n- Add missing imports\n- Type function parameters\n- Use type assertions where needed\n- Enable strict mode gradually\n\n---\n\n## Testing with TypeScript\n\n**QUnit Test** (test/unit/model/formatter.ts):\n```typescript\nimport formatter from \"my/sapui5/ts/app/model/formatter\";\n\nQUnit.module(\"Formatter Tests\");\n\nQUnit.test(\"Should format date correctly\", (assert) => {\n const date = new Date(2025, 0, 21);\n const result = formatter.formatDate(date);\n assert.ok(result.includes(\"21\"), \"Date formatted correctly\");\n});\n\nQUnit.test(\"Should format currency\", (assert) => {\n const result = formatter.formatCurrency(100, \"EUR\");\n assert.ok(result.includes(\"100\"), \"Currency formatted\");\n});\n```\n\n---\n\n## Official Resources\n\n- **TypeScript Documentation**: [https://github.com/SAP-docs/sapui5/tree/main/docs/02_Read-Me-First](https://github.com/SAP-docs/sapui5/tree/main/docs/02_Read-Me-First) (search: typescript)\n- **UI5 TypeScript Tutorial**: [https://github.com/SAP-samples/ui5-typescript-tutorial](https://github.com/SAP-samples/ui5-typescript-tutorial)\n- **TypeScript Guidelines**: Official SAPUI5 TypeScript guidelines\n- **Sample Applications**: TypeScript To-Do List demo\n\n---\n\n---\n\n## Advanced TypeScript Topics\n\nFor advanced TypeScript patterns including:\n- **Metadata-Driven Controls (MDC)** with TypeScript\n- **Control library development** in TypeScript\n- Using `@ui5/ts-interface-generator` for control APIs\n- Enum registration and library.ts patterns\n\nSee `references/mdc-typescript-advanced.md` for detailed implementation guides.\n\n---\n\n**Note**: This document covers TypeScript support in SAPUI5. The framework actively maintains type definitions, making TypeScript a viable choice for new projects and migrations.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":14190,"content_sha256":"eadd29ed811e00f8aa90019b89be16aa185c542b80207ecfd3d9d91d34b59f3a"},{"filename":"templates/basic-component.js","content":"/**\n * SAPUI5 Component Template\n *\n * Usage: Replace placeholders with actual values:\n * - {{namespace}}: Your app namespace (e.g., com.mycompany.myapp)\n * - {{appId}}: Application ID\n *\n * File: Component.js\n */\n\nsap.ui.define([\n \"sap/ui/core/UIComponent\",\n \"sap/ui/model/json/JSONModel\",\n \"sap/ui/Device\"\n], function(UIComponent, JSONModel, Device) {\n \"use strict\";\n\n return UIComponent.extend(\"{{namespace}}.Component\", {\n\n metadata: {\n manifest: \"json\"\n },\n\n /**\n * Component initialization\n * Called once when component is instantiated\n */\n init: function() {\n // Call parent init\n UIComponent.prototype.init.apply(this, arguments);\n\n // Create device model\n var oDeviceModel = new JSONModel(Device);\n oDeviceModel.setDefaultBindingMode(\"OneWay\");\n this.setModel(oDeviceModel, \"device\");\n\n // Create router\n this.getRouter().initialize();\n },\n\n /**\n * Get content density class based on device\n * @returns {string} CSS class\n */\n getContentDensityClass: function() {\n if (!this._sContentDensityClass) {\n if (!Device.support.touch) {\n this._sContentDensityClass = \"sapUiSizeCompact\";\n } else {\n this._sContentDensityClass = \"sapUiSizeCozy\";\n }\n }\n return this._sContentDensityClass;\n }\n });\n});\n","content_type":"text/javascript; charset=utf-8","language":"javascript","size":1537,"content_sha256":"aa33e063c44c2e7f577bd11bff5fbb68d9218cc7b365bdef59993e7c1ec2bbc1"},{"filename":"templates/controller.js","content":"/**\n * SAPUI5 Controller Template\n *\n * Usage: Replace placeholders with actual values:\n * - {{namespace}}: Your app namespace\n * - {{ControllerName}}: Controller name\n *\n * File: controller/{{ControllerName}}.controller.js\n */\n\nsap.ui.define([\n \"sap/ui/core/mvc/Controller\",\n \"sap/ui/model/json/JSONModel\",\n \"sap/ui/model/Filter\",\n \"sap/ui/model/FilterOperator\",\n \"sap/m/MessageToast\",\n \"sap/m/MessageBox\",\n \"{{namespace}}/model/formatter\"\n], function(Controller, JSONModel, Filter, FilterOperator, MessageToast, MessageBox, formatter) {\n \"use strict\";\n\n return Controller.extend(\"{{namespace}}.controller.{{ControllerName}}\", {\n\n formatter: formatter,\n\n /* =========================================================== */\n /* lifecycle methods */\n /* =========================================================== */\n\n /**\n * Called when controller is instantiated\n */\n onInit: function() {\n // Create view model\n var oViewModel = new JSONModel({\n busy: false,\n selectedItemsCount: 0,\n title: \"\"\n });\n this.getView().setModel(oViewModel, \"view\");\n\n // Get router\n var oRouter = this.getOwnerComponent().getRouter();\n oRouter.getRoute(\"{{routeName}}\").attachPatternMatched(this._onObjectMatched, this);\n },\n\n /**\n * Called before view is rendered\n */\n onBeforeRendering: function() {\n // Preparation before rendering\n },\n\n /**\n * Called after view is rendered\n */\n onAfterRendering: function() {\n // DOM manipulation if needed\n },\n\n /**\n * Called when controller is destroyed\n */\n onExit: function() {\n // Cleanup\n },\n\n /* =========================================================== */\n /* event handlers */\n /* =========================================================== */\n\n /**\n * Refresh data\n */\n onRefresh: function() {\n var oBinding = this.byId(\"table\").getBinding(\"items\");\n if (oBinding) {\n oBinding.refresh();\n MessageToast.show(this.getResourceBundle().getText(\"refreshSuccess\"));\n }\n },\n\n /**\n * Search handler\n * @param {sap.ui.base.Event} oEvent Search event\n */\n onSearch: function(oEvent) {\n var sQuery = oEvent.getParameter(\"query\") || oEvent.getParameter(\"newValue\");\n var aFilters = [];\n\n if (sQuery && sQuery.length > 0) {\n aFilters.push(new Filter({\n filters: [\n new Filter(\"{{Field1}}\", FilterOperator.Contains, sQuery),\n new Filter(\"{{Field2}}\", FilterOperator.Contains, sQuery)\n ],\n and: false\n }));\n }\n\n this.byId(\"table\").getBinding(\"items\").filter(aFilters);\n },\n\n /**\n * Item press handler\n * @param {sap.ui.base.Event} oEvent Press event\n */\n onPress: function(oEvent) {\n var oItem = oEvent.getSource();\n var oContext = oItem.getBindingContext();\n var sObjectId = oContext.getProperty(\"{{IdField}}\");\n\n this.getOwnerComponent().getRouter().navTo(\"detail\", {\n objectId: sObjectId\n });\n },\n\n /**\n * Selection change handler\n * @param {sap.ui.base.Event} oEvent Selection change event\n */\n onSelectionChange: function(oEvent) {\n var iSelectedItems = this.byId(\"table\").getSelectedItems().length;\n this.getView().getModel(\"view\").setProperty(\"/selectedItemsCount\", iSelectedItems);\n },\n\n /**\n * Add button press handler\n */\n onAdd: function() {\n var oModel = this.getView().getModel();\n var oContext = oModel.createEntry(\"/{{EntitySet}}\", {\n properties: {\n {{Field1}}: \"\",\n {{Field2}}: \"\",\n {{Field3}}: 0\n }\n });\n\n // Navigate to detail page or open dialog\n MessageToast.show(this.getResourceBundle().getText(\"addSuccess\"));\n },\n\n /**\n * Delete button press handler\n */\n onDelete: function() {\n var that = this;\n var aSelectedItems = this.byId(\"table\").getSelectedItems();\n\n if (aSelectedItems.length === 0) {\n MessageBox.warning(this.getResourceBundle().getText(\"noItemsSelected\"));\n return;\n }\n\n MessageBox.confirm(\n this.getResourceBundle().getText(\"deleteConfirm\", [aSelectedItems.length]),\n {\n onClose: function(sAction) {\n if (sAction === MessageBox.Action.OK) {\n that._deleteSelectedItems(aSelectedItems);\n }\n }\n }\n );\n },\n\n /* =========================================================== */\n /* internal methods */\n /* =========================================================== */\n\n /**\n * Route pattern matched handler\n * @param {sap.ui.base.Event} oEvent Pattern matched event\n * @private\n */\n _onObjectMatched: function(oEvent) {\n var sObjectId = oEvent.getParameter(\"arguments\").objectId;\n\n this.getView().bindElement({\n path: \"/{{EntitySet}}('\" + sObjectId + \"')\",\n events: {\n dataRequested: function() {\n this.getView().getModel(\"view\").setProperty(\"/busy\", true);\n }.bind(this),\n dataReceived: function() {\n this.getView().getModel(\"view\").setProperty(\"/busy\", false);\n }.bind(this)\n }\n });\n },\n\n /**\n * Delete selected items\n * @param {Array} aSelectedItems Selected items\n * @private\n */\n _deleteSelectedItems: function(aSelectedItems) {\n var oModel = this.getView().getModel();\n var that = this;\n\n this.getView().getModel(\"view\").setProperty(\"/busy\", true);\n\n var aPromises = aSelectedItems.map(function(oItem) {\n var sPath = oItem.getBindingContext().getPath();\n return new Promise(function(resolve, reject) {\n oModel.remove(sPath, {\n success: resolve,\n error: reject\n });\n });\n });\n\n Promise.all(aPromises)\n .then(function() {\n that.getView().getModel(\"view\").setProperty(\"/busy\", false);\n MessageToast.show(that.getResourceBundle().getText(\"deleteSuccess\"));\n that.byId(\"table\").removeSelections();\n })\n .catch(function(oError) {\n that.getView().getModel(\"view\").setProperty(\"/busy\", false);\n MessageBox.error(that.getResourceBundle().getText(\"deleteError\"));\n });\n },\n\n /**\n * Get resource bundle for i18n\n * @returns {sap.ui.model.resource.ResourceModel} Resource bundle\n * @private\n */\n getResourceBundle: function() {\n return this.getOwnerComponent().getModel(\"i18n\").getResourceBundle();\n }\n });\n});\n","content_type":"text/javascript; charset=utf-8","language":"javascript","size":7883,"content_sha256":"bb7715bb66f62b2b1ae422f961097a1c98d1605e360ada8a8b98abc4a49e7d9e"},{"filename":"templates/formatter.js","content":"/**\n * SAPUI5 Formatter Template\n *\n * Common formatter functions for data display\n *\n * File: model/formatter.js\n */\n\nsap.ui.define([\n \"sap/ui/core/format/DateFormat\",\n \"sap/ui/core/format/NumberFormat\"\n], function(DateFormat, NumberFormat) {\n \"use strict\";\n\n return {\n /**\n * Format date to localized string\n * @param {Date} oDate Date object\n * @returns {string} Formatted date\n */\n formatDate: function(oDate) {\n if (!oDate) {\n return \"\";\n }\n var oDateFormat = DateFormat.getDateInstance({\n pattern: \"dd.MM.yyyy\"\n });\n return oDateFormat.format(oDate);\n },\n\n /**\n * Format date and time to localized string\n * @param {Date} oDateTime DateTime object\n * @returns {string} Formatted date and time\n */\n formatDateTime: function(oDateTime) {\n if (!oDateTime) {\n return \"\";\n }\n var oDateFormat = DateFormat.getDateTimeInstance({\n pattern: \"dd.MM.yyyy HH:mm:ss\"\n });\n return oDateFormat.format(oDateTime);\n },\n\n /**\n * Format number with 2 decimal places\n * @param {number} fNumber Number to format\n * @returns {string} Formatted number\n */\n formatNumber: function(fNumber) {\n if (fNumber === null || fNumber === undefined) {\n return \"\";\n }\n var oNumberFormat = NumberFormat.getFloatInstance({\n minFractionDigits: 2,\n maxFractionDigits: 2\n });\n return oNumberFormat.format(fNumber);\n },\n\n /**\n * Format currency with symbol\n * @param {number} fAmount Amount\n * @param {string} sCurrency Currency code\n * @returns {string} Formatted currency\n */\n formatCurrency: function(fAmount, sCurrency) {\n if (fAmount === null || fAmount === undefined) {\n return \"\";\n }\n var oCurrencyFormat = NumberFormat.getCurrencyInstance();\n return oCurrencyFormat.format(fAmount, sCurrency);\n },\n\n /**\n * Format status to display text\n * @param {string} sStatus Status code\n * @returns {string} Status text\n */\n statusText: function(sStatus) {\n var oResourceBundle = this.getOwnerComponent().getModel(\"i18n\").getResourceBundle();\n\n var mStatusText = {\n \"A\": oResourceBundle.getText(\"statusApproved\"),\n \"R\": oResourceBundle.getText(\"statusRejected\"),\n \"P\": oResourceBundle.getText(\"statusPending\"),\n \"D\": oResourceBundle.getText(\"statusDraft\")\n };\n\n return mStatusText[sStatus] || sStatus;\n },\n\n /**\n * Format status to sap.ui.core.ValueState\n * @param {string} sStatus Status code\n * @returns {string} Value state\n */\n statusState: function(sStatus) {\n var mStatusState = {\n \"A\": \"Success\",\n \"R\": \"Error\",\n \"P\": \"Warning\",\n \"D\": \"None\"\n };\n\n return mStatusState[sStatus] || \"None\";\n },\n\n /**\n * Format boolean to Yes/No text\n * @param {boolean} bValue Boolean value\n * @returns {string} Yes or No\n */\n formatBoolean: function(bValue) {\n var oResourceBundle = this.getOwnerComponent().getModel(\"i18n\").getResourceBundle();\n return bValue ? oResourceBundle.getText(\"yes\") : oResourceBundle.getText(\"no\");\n },\n\n /**\n * Truncate long text with ellipsis\n * @param {string} sText Text to truncate\n * @param {number} iMaxLength Maximum length\n * @returns {string} Truncated text\n */\n truncateText: function(sText, iMaxLength) {\n if (!sText) {\n return \"\";\n }\n iMaxLength = iMaxLength || 50;\n if (sText.length \u003c= iMaxLength) {\n return sText;\n }\n return sText.substring(0, iMaxLength) + \"...\";\n },\n\n /**\n * Calculate percentage\n * @param {number} fValue Current value\n * @param {number} fTotal Total value\n * @returns {string} Percentage with % sign\n */\n formatPercentage: function(fValue, fTotal) {\n if (!fTotal || fTotal === 0) {\n return \"0%\";\n }\n var fPercentage = (fValue / fTotal) * 100;\n return fPercentage.toFixed(1) + \"%\";\n },\n\n /**\n * Format full name from first and last name\n * @param {string} sFirstName First name\n * @param {string} sLastName Last name\n * @returns {string} Full name\n */\n formatFullName: function(sFirstName, sLastName) {\n if (!sFirstName && !sLastName) {\n return \"\";\n }\n return (sFirstName || \"\") + \" \" + (sLastName || \"\");\n },\n\n /**\n * Format file size in human-readable format\n * @param {number} iBytes File size in bytes\n * @returns {string} Formatted file size\n */\n formatFileSize: function(iBytes) {\n if (!iBytes || iBytes === 0) {\n return \"0 B\";\n }\n\n var aUnits = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\"];\n var iUnit = 0;\n\n while (iBytes >= 1024 && iUnit \u003c aUnits.length - 1) {\n iBytes /= 1024;\n iUnit++;\n }\n\n return iBytes.toFixed(1) + \" \" + aUnits[iUnit];\n },\n\n /**\n * Highlight search term in text\n * @param {string} sText Full text\n * @param {string} sSearchTerm Search term to highlight\n * @returns {string} Text with highlighted term\n */\n highlightText: function(sText, sSearchTerm) {\n if (!sText || !sSearchTerm) {\n return sText;\n }\n\n var sRegex = new RegExp(\"(\" + sSearchTerm + \")\", \"gi\");\n return sText.replace(sRegex, \"\u003cstrong>$1\u003c/strong>\");\n }\n };\n});\n","content_type":"text/javascript; charset=utf-8","language":"javascript","size":6277,"content_sha256":"de1c1c152f7bdef7af32891c1830326cefef087fc0c9b9003e07a28d22a9a6e7"},{"filename":"templates/manifest.json","content":"{\n \"_version\": \"1.42.0\",\n \"sap.app\": {\n \"id\": \"{{namespace}}\",\n \"type\": \"application\",\n \"i18n\": \"i18n/i18n.properties\",\n \"title\": \"{{appTitle}}\",\n \"description\": \"{{appDescription}}\",\n \"applicationVersion\": {\n \"version\": \"1.0.0\"\n },\n \"dataSources\": {\n \"mainService\": {\n \"uri\": \"/sap/opu/odata/sap/SERVICE_SRV/\",\n \"type\": \"OData\",\n \"settings\": {\n \"localUri\": \"localService/metadata.xml\",\n \"odataVersion\": \"2.0\"\n }\n }\n }\n },\n \"sap.ui\": {\n \"technology\": \"UI5\",\n \"icons\": {\n \"icon\": \"\",\n \"favIcon\": \"\",\n \"phone\": \"\",\n \"phone@2\": \"\",\n \"tablet\": \"\",\n \"tablet@2\": \"\"\n },\n \"deviceTypes\": {\n \"desktop\": true,\n \"tablet\": true,\n \"phone\": true\n }\n },\n \"sap.ui5\": {\n \"flexEnabled\": true,\n \"dependencies\": {\n \"minUI5Version\": \"1.120.0\",\n \"libs\": {\n \"sap.ui.core\": {},\n \"sap.m\": {},\n \"sap.ui.layout\": {},\n \"sap.f\": {}\n }\n },\n \"contentDensities\": {\n \"compact\": true,\n \"cozy\": true\n },\n \"models\": {\n \"i18n\": {\n \"type\": \"sap.ui.model.resource.ResourceModel\",\n \"settings\": {\n \"bundleName\": \"{{namespace}}.i18n.i18n\",\n \"supportedLocales\": [\"\"],\n \"fallbackLocale\": \"\"\n }\n },\n \"\": {\n \"dataSource\": \"mainService\",\n \"preload\": true,\n \"settings\": {\n \"defaultBindingMode\": \"TwoWay\",\n \"defaultCountMode\": \"Inline\",\n \"refreshAfterChange\": false,\n \"metadataUrlParams\": {\n \"sap-value-list\": \"none\"\n }\n }\n }\n },\n \"resources\": {\n \"css\": [\n {\n \"uri\": \"css/style.css\"\n }\n ]\n },\n \"routing\": {\n \"config\": {\n \"routerClass\": \"sap.m.routing.Router\",\n \"type\": \"View\",\n \"viewType\": \"XML\",\n \"path\": \"{{namespace}}.view\",\n \"controlId\": \"app\",\n \"controlAggregation\": \"pages\",\n \"transition\": \"slide\",\n \"bypassed\": {\n \"target\": \"notFound\"\n },\n \"async\": true\n },\n \"routes\": [\n {\n \"pattern\": \"\",\n \"name\": \"main\",\n \"target\": \"main\"\n },\n {\n \"pattern\": \"detail/{objectId}\",\n \"name\": \"detail\",\n \"target\": \"detail\"\n }\n ],\n \"targets\": {\n \"main\": {\n \"viewName\": \"Main\",\n \"viewId\": \"main\",\n \"viewLevel\": 1\n },\n \"detail\": {\n \"viewName\": \"Detail\",\n \"viewId\": \"detail\",\n \"viewLevel\": 2\n },\n \"notFound\": {\n \"viewName\": \"NotFound\",\n \"viewId\": \"notFound\"\n }\n }\n },\n \"rootView\": {\n \"viewName\": \"{{namespace}}.view.App\",\n \"type\": \"XML\",\n \"async\": true,\n \"id\": \"app\"\n }\n }\n}\n","content_type":"application/json; charset=utf-8","language":"json","size":3755,"content_sha256":"02ab8c97791b208307d1dd7a6874565aded35b87bd866e5b058213d053ad5cc7"},{"filename":"templates/xml-view.xml","content":"\u003cmvc:View\n controllerName=\"{{namespace}}.controller.{{ControllerName}}\"\n xmlns=\"sap.m\"\n xmlns:mvc=\"sap.ui.core.mvc\"\n xmlns:core=\"sap.ui.core\"\n displayBlock=\"true\">\n\n \u003cPage\n id=\"page\"\n title=\"{i18n>{{pageTitle}}}\">\n\n \u003cheaderContent>\n \u003cButton\n icon=\"sap-icon://refresh\"\n press=\".onRefresh\"\n tooltip=\"{i18n>refreshTooltip}\"/>\n \u003c/headerContent>\n\n \u003ccontent>\n \u003cTable\n id=\"table\"\n inset=\"false\"\n items=\"{\n path: '/{{EntitySet}}',\n sorter: {\n path: '{{SortField}}'\n }\n }\"\n growing=\"true\"\n growingThreshold=\"20\"\n mode=\"SingleSelectMaster\"\n selectionChange=\".onSelectionChange\">\n\n \u003cheaderToolbar>\n \u003cOverflowToolbar>\n \u003cTitle\n text=\"{i18n>{{tableTitle}}}\"\n level=\"H2\"/>\n \u003cToolbarSpacer/>\n \u003cSearchField\n width=\"50%\"\n search=\".onSearch\"\n placeholder=\"{i18n>searchPlaceholder}\"/>\n \u003cButton\n icon=\"sap-icon://add\"\n press=\".onAdd\"\n tooltip=\"{i18n>addTooltip}\"/>\n \u003c/OverflowToolbar>\n \u003c/headerToolbar>\n\n \u003ccolumns>\n \u003cColumn\n width=\"12em\">\n \u003cText text=\"{i18n>{{column1}}}\"/>\n \u003c/Column>\n \u003cColumn\n minScreenWidth=\"Tablet\"\n demandPopin=\"true\"\n hAlign=\"End\">\n \u003cText text=\"{i18n>{{column2}}}\"/>\n \u003c/Column>\n \u003cColumn\n minScreenWidth=\"Tablet\"\n demandPopin=\"true\">\n \u003cText text=\"{i18n>{{column3}}}\"/>\n \u003c/Column>\n \u003c/columns>\n\n \u003citems>\n \u003cColumnListItem\n type=\"Navigation\"\n press=\".onPress\">\n \u003ccells>\n \u003cObjectIdentifier\n title=\"{{{Field1}}}\"\n text=\"{{{Field2}}}\"/>\n \u003cObjectNumber\n number=\"{\n path: '{{Field3}}',\n type: 'sap.ui.model.type.Float',\n formatOptions: {\n minFractionDigits: 2,\n maxFractionDigits: 2\n }\n }\"\n unit=\"{{{CurrencyField}}}\"/>\n \u003cObjectStatus\n text=\"{\n path: '{{StatusField}}',\n formatter: '.formatter.statusText'\n }\"\n state=\"{\n path: '{{StatusField}}',\n formatter: '.formatter.statusState'\n }\"/>\n \u003c/cells>\n \u003c/ColumnListItem>\n \u003c/items>\n \u003c/Table>\n \u003c/content>\n\n \u003cfooter>\n \u003cOverflowToolbar>\n \u003cToolbarSpacer/>\n \u003cButton\n text=\"{i18n>deleteButton}\"\n icon=\"sap-icon://delete\"\n type=\"Reject\"\n press=\".onDelete\"\n enabled=\"{= ${view>/selectedItemsCount} > 0}\"/>\n \u003c/OverflowToolbar>\n \u003c/footer>\n \u003c/Page>\n\u003c/mvc:View>\n","content_type":"application/xml; charset=utf-8","language":"xml","size":4094,"content_sha256":"03643a523d3e84ba441c9661e44bab117eb0b4fdb33f371feeef06138e29a11c"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"SAPUI5 Development Skill","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Related Skills","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"sap-fiori-tools","type":"text","marks":[{"type":"strong"}]},{"text":": Use for rapid Fiori application development, Page Editor configuration, and deployment automation","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"sap-cap-capire","type":"text","marks":[{"type":"strong"}]},{"text":": Use for backend service integration, OData model binding, and CAP service consumption","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"sap-btp-cloud-platform","type":"text","marks":[{"type":"strong"}]},{"text":": Use for deployment options, HTML5 Application Repository service, and BTP integration","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"sap-abap","type":"text","marks":[{"type":"strong"}]},{"text":": Use when connecting to ABAP backends or consuming OData services from SAP systems","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"sap-api-style","type":"text","marks":[{"type":"strong"}]},{"text":": Use when documenting UI5 application APIs or following REST/OData standards","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"sap-dependency-security","type":"text","marks":[{"type":"strong"}]},{"text":": Use for secure dependency upgrades, lockfile policies, supply-chain controls, and exact MCP server pins in SAPUI5 frontend toolchains","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"When to Use This Skill","type":"text"}]},{"type":"paragraph","content":[{"text":"Use this skill when building SAPUI5/OpenUI5 applications, XML views, controllers, custom controls, routing, OData v2/v4 model binding, Fiori Elements integrations, QUnit/OPA5 tests, accessibility/security/performance improvements, or UI5 MCP-assisted scaffolding and API lookup.","type":"text"}]},{"type":"paragraph","content":[{"text":"Comprehensive skill for building enterprise applications with SAP UI5 framework.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Using MCP Tools (New in v2.0.0)","type":"text"}]},{"type":"paragraph","content":[{"text":"This skill integrates with the official ","type":"text"},{"text":"@ui5/mcp-server","type":"text","marks":[{"type":"strong"}]},{"text":" for live development tools:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Scaffolding","type":"text","marks":[{"type":"strong"}]},{"text":": Create projects with ","type":"text"},{"text":"ui5-app-scaffolder","type":"text","marks":[{"type":"code_inline"}]},{"text":" agent or ","type":"text"},{"text":"/ui5-scaffold","type":"text","marks":[{"type":"code_inline"}]},{"text":" command","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"API Reference","type":"text","marks":[{"type":"strong"}]},{"text":": Lookup controls with ","type":"text"},{"text":"ui5-api-explorer","type":"text","marks":[{"type":"code_inline"}]},{"text":" agent or ","type":"text"},{"text":"/ui5-api","type":"text","marks":[{"type":"code_inline"}]},{"text":" command","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Code Quality","type":"text","marks":[{"type":"strong"}]},{"text":": Run linter with ","type":"text"},{"text":"ui5-code-quality-advisor","type":"text","marks":[{"type":"code_inline"}]},{"text":" agent or ","type":"text"},{"text":"/ui5-lint","type":"text","marks":[{"type":"code_inline"}]},{"text":" command","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Migration","type":"text","marks":[{"type":"strong"}]},{"text":": Upgrade versions with ","type":"text"},{"text":"ui5-migration-specialist","type":"text","marks":[{"type":"code_inline"}]},{"text":" agent","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Version Info","type":"text","marks":[{"type":"strong"}]},{"text":": Check releases with ","type":"text"},{"text":"/ui5-version","type":"text","marks":[{"type":"code_inline"}]},{"text":" command","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Tool Catalog","type":"text","marks":[{"type":"strong"}]},{"text":": List all MCP tools with ","type":"text"},{"text":"/ui5-mcp-tools","type":"text","marks":[{"type":"code_inline"}]},{"text":" command","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"For setup and troubleshooting, see ","type":"text"},{"text":"references/mcp-integration.md","type":"text","marks":[{"type":"link","attrs":{"href":"references/mcp-integration.md","title":null}}]},{"text":". MCP package pins are governed by ","type":"text"},{"text":"sap-dependency-security","type":"text","marks":[{"type":"strong"}]},{"text":" and validated by ","type":"text"},{"text":"npm run validate:mcp-security","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]},{"type":"paragraph","content":[{"text":"Graceful Fallback","type":"text","marks":[{"type":"strong"}]},{"text":": All features work without MCP by using reference files and built-in templates.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Table of Contents","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Quick Start","type":"text","marks":[{"type":"link","attrs":{"href":"#quick-start","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Core Concepts","type":"text","marks":[{"type":"link","attrs":{"href":"#core-concepts","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"SAP Fiori Elements","type":"text","marks":[{"type":"link","attrs":{"href":"#sap-fiori-elements","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Metadata-Driven Controls (MDC)","type":"text","marks":[{"type":"link","attrs":{"href":"#metadata-driven-controls-mdc","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Testing","type":"text","marks":[{"type":"link","attrs":{"href":"#testing","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Best Practices","type":"text","marks":[{"type":"link","attrs":{"href":"#best-practices","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Common Patterns","type":"text","marks":[{"type":"link","attrs":{"href":"#common-patterns","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Troubleshooting","type":"text","marks":[{"type":"link","attrs":{"href":"#troubleshooting-common-issues","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Development Tools","type":"text","marks":[{"type":"link","attrs":{"href":"#development-tools","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Bundled Resources","type":"text","marks":[{"type":"link","attrs":{"href":"#bundled-reference-files","title":null}}]}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Quick Start","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Creating a Basic SAPUI5 App","type":"text"}]},{"type":"paragraph","content":[{"text":"Use UI5 Tooling (recommended) or SAP Business Application Studio:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Install UI5 CLI\nnpm install -g @ui5/cli\n\n# Create new project\nmkdir my-sapui5-app && cd my-sapui5-app\nnpm init -y\n\n# Initialize UI5 project\nui5 init\n\n# Add UI5 dependencies\nnpm install --save-dev @ui5/cli\n\n# Start development server\nui5 serve","type":"text"}]},{"type":"paragraph","content":[{"text":"Project Structure","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"my-sapui5-app/\n├── webapp/\n│ ├── Component.js\n│ ├── manifest.json\n│ ├── index.html\n│ ├── controller/\n│ │ └── Main.controller.js\n│ ├── view/\n│ │ └── Main.view.xml\n│ ├── model/\n│ │ └── formatter.js\n│ ├── i18n/\n│ │ └── i18n.properties\n│ ├── css/\n│ │ └── style.css\n│ └── test/\n│ ├── unit/\n│ └── integration/\n├── ui5.yaml\n└── package.json","type":"text"}]},{"type":"paragraph","content":[{"text":"Templates Available","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"templates/basic-component.js","type":"text","marks":[{"type":"code_inline"}]},{"text":": Component template","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"templates/manifest.json","type":"text","marks":[{"type":"code_inline"}]},{"text":": Application descriptor template","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"templates/xml-view.xml","type":"text","marks":[{"type":"code_inline"}]},{"text":": XML view with common patterns","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"templates/controller.js","type":"text","marks":[{"type":"code_inline"}]},{"text":": Controller with best practices","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"templates/formatter.js","type":"text","marks":[{"type":"code_inline"}]},{"text":": Common formatter functions","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Use templates","type":"text","marks":[{"type":"strong"}]},{"text":" by copying to your project and replacing placeholders (","type":"text"},{"text":"{{namespace}}","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"{{ControllerName}}","type":"text","marks":[{"type":"code_inline"}]},{"text":", etc.).","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Core Concepts","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"1. MVC Architecture","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Model","type":"text","marks":[{"type":"strong"}]},{"text":": Data layer (JSON, OData, XML, Resource models)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"View","type":"text","marks":[{"type":"strong"}]},{"text":": Presentation layer (XML, JavaScript, JSON, HTML)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Controller","type":"text","marks":[{"type":"strong"}]},{"text":": Business logic layer","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Binding","type":"text","marks":[{"type":"strong"}]},{"text":": Synchronizes model and view (One-way, Two-way, One-time)","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Reference","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"references/core-architecture.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" for detailed architecture concepts.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"2. Component & Manifest","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Component.js","type":"text","marks":[{"type":"strong"}]},{"text":": Entry point, initializes router and models","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"manifest.json","type":"text","marks":[{"type":"strong"}]},{"text":": Central configuration (models, routing, dependencies, data sources)","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Key manifest sections","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"sap.app","type":"text","marks":[{"type":"code_inline"}]},{"text":": Application metadata and data sources","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"sap.ui","type":"text","marks":[{"type":"code_inline"}]},{"text":": UI technology and device types","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"sap.ui5","type":"text","marks":[{"type":"code_inline"}]},{"text":": UI5-specific configuration (models, routing, dependencies)","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"3. Data Models","type":"text"}]},{"type":"paragraph","content":[{"text":"JSON Model","type":"text","marks":[{"type":"strong"}]},{"text":" (client-side):","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"var oModel = new JSONModel({\n products: [...]\n});\nthis.getView().setModel(oModel);","type":"text"}]},{"type":"paragraph","content":[{"text":"OData V2 Model","type":"text","marks":[{"type":"strong"}]},{"text":" (server-side):","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"\"\": {\n \"dataSource\": \"mainService\",\n \"settings\": {\n \"defaultBindingMode\": \"TwoWay\",\n \"useBatch\": true\n }\n}","type":"text"}]},{"type":"paragraph","content":[{"text":"Resource Model","type":"text","marks":[{"type":"strong"}]},{"text":" (i18n):","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"\"i18n\": {\n \"type\": \"sap.ui.model.resource.ResourceModel\",\n \"settings\": {\n \"bundleName\": \"my.app.i18n.i18n\"\n }\n}","type":"text"}]},{"type":"paragraph","content":[{"text":"Reference","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"references/data-binding-models.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" for comprehensive guide.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"4. Views & Controllers","type":"text"}]},{"type":"paragraph","content":[{"text":"XML View","type":"text","marks":[{"type":"strong"}]},{"text":" (recommended):","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"xml"},"content":[{"text":"\u003cmvc:View\n controllerName=\"my.app.controller.Main\"\n xmlns=\"sap.m\"\n xmlns:mvc=\"sap.ui.core.mvc\">\n \u003cPage title=\"{i18n>title}\">\n \u003cList items=\"{/products}\">\n \u003cStandardListItem title=\"{name}\" description=\"{price}\"/>\n \u003c/List>\n \u003c/Page>\n\u003c/mvc:View>","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"5. Routing & Navigation","type":"text"}]},{"type":"paragraph","content":[{"text":"Navigate programmatically","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"this.getOwnerComponent().getRouter().navTo(\"detail\", {\n objectId: sId\n});","type":"text"}]},{"type":"paragraph","content":[{"text":"Reference","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"references/routing-navigation.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" for routing patterns.","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"SAP Fiori Elements","type":"text"}]},{"type":"paragraph","content":[{"text":"Build applications without JavaScript UI code using OData annotations.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Application Types","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"List Report","type":"text","marks":[{"type":"strong"}]},{"text":": Searchable, filterable tables/charts","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Object Page","type":"text","marks":[{"type":"strong"}]},{"text":": Detailed view with sections and facets","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Analytical List Page","type":"text","marks":[{"type":"strong"}]},{"text":": Visual filters and analytics","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Overview Page","type":"text","marks":[{"type":"strong"}]},{"text":": Card-based dashboards","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Worklist","type":"text","marks":[{"type":"strong"}]},{"text":": Simplified list for tasks","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Quick Setup","type":"text"}]},{"type":"paragraph","content":[{"text":"manifest.json for List Report + Object Page","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"json"},"content":[{"text":"{\n \"sap.ui5\": {\n \"dependencies\": {\n \"libs\": {\n \"sap.fe.templates\": {}\n }\n },\n \"routing\": {\n \"targets\": {\n \"ProductsList\": {\n \"type\": \"Component\",\n \"name\": \"sap.fe.templates.ListReport\",\n \"options\": {\n \"settings\": {\n \"contextPath\": \"/Products\",\n \"variantManagement\": \"Page\"\n }\n }\n }\n }\n }\n }\n}","type":"text"}]},{"type":"paragraph","content":[{"text":"Key Annotations","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"@UI.LineItem","type":"text","marks":[{"type":"code_inline"}]},{"text":": Table columns","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"@UI.SelectionFields","type":"text","marks":[{"type":"code_inline"}]},{"text":": Filter bar fields","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"@UI.HeaderInfo","type":"text","marks":[{"type":"code_inline"}]},{"text":": Object page header","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"@UI.Facets","type":"text","marks":[{"type":"code_inline"}]},{"text":": Object page sections","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Reference","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"references/fiori-elements.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" for comprehensive guide.","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Metadata-Driven Controls (MDC)","type":"text"}]},{"type":"paragraph","content":[{"text":"The sap.ui.mdc library provides metadata-driven controls for building dynamic UIs at runtime.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Key Controls","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"MDC Table","type":"text","marks":[{"type":"strong"}]},{"text":": Data display with dynamic columns based on metadata","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"MDC FilterBar","type":"text","marks":[{"type":"strong"}]},{"text":": Complex filter conditions with PropertyInfo","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"MDC Value Help","type":"text","marks":[{"type":"strong"}]},{"text":": Assisted data input with suggestions","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Quick Example","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"xml"},"content":[{"text":"\u003cmdc:Table\n id=\"mdcTable\"\n delegate='{name: \"my/app/delegate/TableDelegate\", payload: {}}'\n p13nMode=\"Sort,Filter,Column\"\n type=\"ResponsiveTable\">\n \u003cmdc:columns>\n \u003cmdcTable:Column propertyKey=\"name\" header=\"Name\">\n \u003cText text=\"{name}\"/>\n \u003c/mdcTable:Column>\n \u003c/mdc:columns>\n\u003c/mdc:Table>","type":"text"}]},{"type":"paragraph","content":[{"text":"Reference","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"references/mdc-typescript-advanced.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" for comprehensive MDC guide with TypeScript.","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Testing","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Unit Tests (QUnit)","type":"text"}]},{"type":"paragraph","content":[{"text":"Test individual functions and modules:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"QUnit.module(\"Formatter Tests\");\nQUnit.test(\"Should format price correctly\", function(assert) {\n var fPrice = 123.456;\n var sResult = formatter.formatPrice(fPrice);\n assert.strictEqual(sResult, \"123.46 EUR\", \"Price formatted\");\n});","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Integration Tests (OPA5)","type":"text"}]},{"type":"paragraph","content":[{"text":"Test user interactions and flows:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"opaTest(\"Should navigate to detail page\", function(Given, When, Then) {\n Given.iStartMyApp();\n When.onTheWorklistPage.iPressOnTheFirstListItem();\n Then.onTheObjectPage.iShouldSeeTheObjectPage();\n Then.iTeardownMyApp();\n});","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Mock Server","type":"text"}]},{"type":"paragraph","content":[{"text":"Simulate OData backend:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"var oMockServer = new MockServer({\n rootUri: \"/sap/opu/odata/sap/SERVICE_SRV/\"\n});\noMockServer.simulate(\"localService/metadata.xml\", {\n sMockdataBaseUrl: \"localService/mockdata\"\n});\noMockServer.start();","type":"text"}]},{"type":"paragraph","content":[{"text":"Reference","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"references/testing.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" for comprehensive testing guide.","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Best Practices","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Always Use Async","type":"text","marks":[{"type":"strong"}]},{"text":" - sap.ui.define, async:true in manifests","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use XML Views","type":"text","marks":[{"type":"strong"}]},{"text":" - declarative and tooling-friendly","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Proper Namespacing","type":"text","marks":[{"type":"strong"}]},{"text":" - com.mycompany.myapp.controller.Main","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Internationalization","type":"text","marks":[{"type":"strong"}]},{"text":" - always use i18n for texts","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Data Binding Over Manual Updates","type":"text","marks":[{"type":"strong"}]},{"text":" - automatic XSS protection","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Security","type":"text","marks":[{"type":"strong"}]},{"text":" - enable CSP, validate input, use HTTPS","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Performance","type":"text","marks":[{"type":"strong"}]},{"text":" - component preload, lazy loading, batch requests","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Accessibility","type":"text","marks":[{"type":"strong"}]},{"text":" - semantic controls, labels, keyboard navigation","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Common Patterns","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"CRUD Operations","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"// Create\noModel.create(\"/Products\", oData, {success: function() {MessageToast.show(\"Created\");}});\n\n// Read\noModel.read(\"/Products\", {filters: [new Filter(\"Price\", FilterOperator.GT, 100)]});\n\n// Update\noModel.update(\"/Products(1)\", {Price: 200}, {success: function() {MessageToast.show(\"Updated\");}});\n\n// Delete\noModel.remove(\"/Products(1)\", {success: function() {MessageToast.show(\"Deleted\");}});","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Filtering & Sorting","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"var oBinding = this.byId(\"table\").getBinding(\"items\");\noBinding.filter([new Filter(\"price\", FilterOperator.GT, 100)]);\noBinding.sort([new Sorter(\"name\", false)]);","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Dialog Handling","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"if (!this.pDialog) {\n this.pDialog = this.loadFragment({\n name: \"my.app.view.fragments.MyDialog\"\n });\n}\nthis.pDialog.then(function(oDialog) {oDialog.open();});","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Troubleshooting Common Issues","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Binding not working","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Check model set on view/component","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Verify correct binding path","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Confirm data loaded","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Debug: ","type":"text"},{"text":"console.log(this.getView().getModel().getData())","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"OData call failing","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Verify service URL in manifest.json","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Check CORS configuration","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Test authentication","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use browser Network tab","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"View not displaying","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Check view registration in manifest.json","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Verify routing configuration","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Match controller name","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Check browser console for errors","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Performance problems","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Enable component preload","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use growing lists for large datasets","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Implement OData paging","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use one-way binding when possible","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Development Tools","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"UI5 Tooling","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"ui5 serve # Development server\nui5 build # Build for production\nnpm test # Run tests","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"UI5 Inspector","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Browser extension for debugging","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"View control tree and bindings","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Performance analysis","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Support Assistant","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Press ","type":"text"},{"text":"Ctrl+Alt+Shift+S","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Built-in quality checker","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Bundled Reference Files","type":"text"}]},{"type":"paragraph","content":[{"text":"This skill includes comprehensive reference documentation (15 files):","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/glossary.md","type":"text","marks":[{"type":"strong"}]},{"text":": Complete SAPUI5 terminology and concepts (100+ terms)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/core-architecture.md","type":"text","marks":[{"type":"strong"}]},{"text":": Framework architecture, components, MVC, bootstrapping","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/data-binding-models.md","type":"text","marks":[{"type":"strong"}]},{"text":": Data binding, models, filters, sorters","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/testing.md","type":"text","marks":[{"type":"strong"}]},{"text":": QUnit, OPA5, mock server, test automation","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/fiori-elements.md","type":"text","marks":[{"type":"strong"}]},{"text":": Fiori Elements templates, annotations, configuration","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/typescript-support.md","type":"text","marks":[{"type":"strong"}]},{"text":": TypeScript setup, configuration, migration","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/routing-navigation.md","type":"text","marks":[{"type":"strong"}]},{"text":": Routing, navigation, Flexible Column Layout","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/performance-optimization.md","type":"text","marks":[{"type":"strong"}]},{"text":": Performance best practices, optimization","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/accessibility.md","type":"text","marks":[{"type":"strong"}]},{"text":": WCAG 2.1 compliance, screen readers, ARIA","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/security.md","type":"text","marks":[{"type":"strong"}]},{"text":": XSS prevention, CSP, authentication, CSRF","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/mdc-typescript-advanced.md","type":"text","marks":[{"type":"strong"}]},{"text":": MDC controls, TypeScript control libraries","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/mcp-integration.md","type":"text","marks":[{"type":"strong"}]},{"text":": MCP setup, troubleshooting, and fallback behavior","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/code-quality-checklist.md","type":"text","marks":[{"type":"strong"}]},{"text":": Review checklist for UI5 projects","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/migration-patterns.md","type":"text","marks":[{"type":"strong"}]},{"text":": Upgrade and modernization patterns","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/scaffolding-templates.md","type":"text","marks":[{"type":"strong"}]},{"text":": Project scaffolding guidance","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Access these files","type":"text","marks":[{"type":"strong"}]},{"text":" for detailed information on specific topics while keeping the main skill concise.","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Templates Included","type":"text"}]},{"type":"paragraph","content":[{"text":"Ready-to-use templates in ","type":"text"},{"text":"templates/","type":"text","marks":[{"type":"code_inline"}]},{"text":" directory:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"basic-component.js","type":"text","marks":[{"type":"strong"}]},{"text":": Component.js template with best practices","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"manifest.json","type":"text","marks":[{"type":"strong"}]},{"text":": Complete application descriptor template","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"xml-view.xml","type":"text","marks":[{"type":"strong"}]},{"text":": XML view with common patterns","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"controller.js","type":"text","marks":[{"type":"strong"}]},{"text":": Controller template with lifecycle hooks","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"formatter.js","type":"text","marks":[{"type":"strong"}]},{"text":": Common formatter functions","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Instructions for Claude","type":"text"}]},{"type":"paragraph","content":[{"text":"When using this skill:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Always use async patterns","type":"text","marks":[{"type":"strong"}]},{"text":" - sap.ui.define, async:true","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Prefer XML views","type":"text","marks":[{"type":"strong"}]},{"text":" - more declarative and tooling-friendly","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use data binding","type":"text","marks":[{"type":"strong"}]},{"text":" - automatic XSS protection","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Refer to reference files","type":"text","marks":[{"type":"strong"}]},{"text":" - for detailed information","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use templates","type":"text","marks":[{"type":"strong"}]},{"text":" - copy from templates/ and replace placeholders","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Follow best practices","type":"text","marks":[{"type":"strong"}]},{"text":" - security, performance, accessibility","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Provide working examples","type":"text","marks":[{"type":"strong"}]},{"text":" - test code patterns before suggesting","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Bundled Resources","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Reference Documentation","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/accessibility.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Accessibility best practices","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/core-architecture.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Framework architecture and component patterns","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/data-binding-models.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Data binding and model usage","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/fiori-elements.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Fiori Elements templates and annotations","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/mdc-typescript-advanced.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" - MDC and TypeScript guidance","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/mcp-integration.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" - MCP setup and troubleshooting","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/migration-patterns.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Migration from older versions","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/performance-optimization.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Performance optimization techniques","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/testing.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Testing strategies and frameworks","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/security.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" - XSS, CSP, authentication, and CSRF guidance","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Templates","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"templates/basic-component.js","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Component development template","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"templates/controller.js","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Controller template","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"templates/xml-view.xml","type":"text","marks":[{"type":"code_inline"}]},{"text":" - XML view template","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"templates/formatter.js","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Formatter helper template","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"templates/manifest.json","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Application manifest template","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"paragraph","content":[{"text":"License","type":"text","marks":[{"type":"strong"}]},{"text":": GPL-3.0 ","type":"text"},{"text":"Next Review","type":"text","marks":[{"type":"strong"}]},{"text":": 2026-02-27 (Quarterly)","type":"text"}]}]},"metadata":{"date":"2026-11-02","name":"sapui5","tags":["sapui5","ui5","openui5","sap ui5","ui5 framework","ui5 application","ui5 development","ui5 tooling","ui5 cli","Component.js","manifest.json","xml view","data binding","odata v2","odata v4","fiori elements","sap.m controls","mvc pattern","routing","navigation","qunit","opa5","testing","mock server","sap.ui.mdc","mdc table","typescript","accessibility","security","performance","ui5 inspector"],"author":"@skillopedia","source":{"stars":487,"repo_name":"sap-skills","origin_url":"https://github.com/secondsky/sap-skills/blob/HEAD/plugins/sapui5/skills/sapui5/SKILL.md","repo_owner":"secondsky","body_sha256":"c5679c9724b5f62d5e3090e3b8d0ac691ddb5c10ef351153cc1bca97f348ffa9","cluster_key":"1a08e016d9ab496f6ddb029a296be0a474b5937289fc1305fb5e50af3402f1b1","clean_bundle":{"format":"clean-skill-bundle-v1","source":"secondsky/sap-skills/plugins/sapui5/skills/sapui5/SKILL.md","text_bytes":302134,"source_commit":"652a861d3ed422b71f01c8805f9ec03b14017cd8","skills_sh_rank":15693,"attachment_count":22,"text_attachments":22,"attachment_storage":"skill_attachments","binary_attachments":0},"cluster_size":1,"skill_md_path":"plugins/sapui5/skills/sapui5/SKILL.md","import_metadata":{"date":"2026-11-02","author":"@skillopedia","version":"v1","category":"web-development","category_label":"Web"},"exact_dupes_collapsed_into_this":0},"license":"GPL-3.0","version":"v1","category":"web-development","metadata":{"status":"production","version":"2.4.1","website":"https://sap-ai-skills.com","keywords":["sapui5","ui5","openui5","sap ui5","ui5 framework","ui5 application","ui5 development","ui5 tooling","ui5 cli","Component.js","manifest.json","xml view","data binding","odata v2","odata v4","fiori elements","sap.m controls","mvc pattern","routing","navigation","qunit","opa5","testing","mock server","sap.ui.mdc","mdc table","typescript","accessibility","security","performance","ui5 inspector"],"mcp_tools":9,"maintainer":"Eduard Jiglau","last_verified":"2026-05-31T00:00:00.000Z","slash_commands":5,"mcp_integration":true,"reference_files":15,"maintainer_email":"[email protected]","framework_version":"1.148.1 latest verified, 1.120.0+ baseline","specialized_agents":4,"documentation_source":"https://github.com/SAP-docs/sapui5","documentation_files_analyzed":1416},"import_tag":"skills-sh-top20k-2026-11","description":"This skill should be used when developing SAP UI5 applications, including creating freestyle apps, Fiori Elements apps, custom controls, testing, data binding, OData integration, routing, and troubleshooting. Use when building enterprise web applications with SAP UI5 framework, implementing MVC patterns, configuring manifest.json, creating XML views, writing controllers, setting up data models (JSON, OData v2/v4), implementing responsive UI with sap.m controls, building Fiori Elements apps, writing unit tests with QUnit, integration tests with OPA5, setting up mock servers, handling security (XSS, CSP), optimizing performance, implementing accessibility features, or debugging UI5 applications. Also covers sap.ui.mdc controls and TypeScript control libraries."}},"renderedAt":1793671521924}

SAPUI5 Development Skill Related Skills - sap-fiori-tools : Use for rapid Fiori application development, Page Editor configuration, and deployment automation - sap-cap-capire : Use for backend service integration, OData model binding, and CAP service consumption - sap-btp-cloud-platform : Use for deployment options, HTML5 Application Repository service, and BTP integration - sap-abap : Use when connecting to ABAP backends or consuming OData services from SAP systems - sap-api-style : Use when documenting UI5 application APIs or following REST/OData standards - sap-dependency-security : Use fo…