├── u1 ├── ex0 │ ├── .gitignore │ ├── ex0.png │ ├── ex0_folder.png │ ├── webapp │ │ ├── Component.ts │ │ ├── view │ │ │ └── Mountains.view.xml │ │ ├── index.html │ │ ├── manifest.json │ │ └── model │ │ │ └── metadata │ │ │ └── JSONPropertyInfo.ts │ ├── tsconfig.json │ ├── ui5.yaml │ ├── package.json │ └── readme.md ├── ex1 │ ├── .gitignore │ ├── ex1.png │ ├── webapp │ │ ├── Component.ts │ │ ├── index.html │ │ ├── manifest.json │ │ ├── delegate │ │ │ └── JSONTableDelegate.ts │ │ ├── model │ │ │ └── metadata │ │ │ │ └── JSONPropertyInfo.ts │ │ └── view │ │ │ └── Mountains.view.xml │ ├── tsconfig.json │ ├── ui5.yaml │ ├── package.json │ └── readme.md ├── ex2 │ ├── .gitignore │ ├── ex2.png │ ├── webapp │ │ ├── Component.ts │ │ ├── index.html │ │ ├── view │ │ │ ├── fragment │ │ │ │ ├── RangeValueHelp.fragment.xml │ │ │ │ └── NameValueHelp.fragment.xml │ │ │ └── Mountains.view.xml │ │ ├── manifest.json │ │ ├── delegate │ │ │ ├── JSONFilterBarDelegate.ts │ │ │ └── JSONTableDelegate.ts │ │ └── model │ │ │ └── metadata │ │ │ └── JSONPropertyInfo.ts │ ├── tsconfig.json │ ├── ui5.yaml │ ├── package.json │ └── readme.md ├── ex3 │ ├── .gitignore │ ├── ex3.png │ ├── webapp │ │ ├── Component.ts │ │ ├── index.html │ │ ├── view │ │ │ ├── fragment │ │ │ │ ├── RangeValueHelp.fragment.xml │ │ │ │ └── NameValueHelp.fragment.xml │ │ │ └── Mountains.view.xml │ │ ├── manifest.json │ │ ├── model │ │ │ └── metadata │ │ │ │ └── JSONPropertyInfo.ts │ │ └── delegate │ │ │ ├── JSONFilterBarDelegate.ts │ │ │ └── JSONTableDelegate.ts │ ├── tsconfig.json │ ├── ui5.yaml │ ├── package.json │ └── readme.md ├── ex4 │ ├── .gitignore │ ├── ex4.png │ ├── webapp │ │ ├── Component.ts │ │ ├── delegate │ │ │ ├── JSONBaseDelegate.ts │ │ │ ├── JSONFilterBarDelegate.ts │ │ │ └── JSONTableDelegate.ts │ │ ├── model │ │ │ ├── type │ │ │ │ ├── LengthMeter.ts │ │ │ │ └── TypeMap.ts │ │ │ └── metadata │ │ │ │ └── JSONPropertyInfo.ts │ │ ├── index.html │ │ ├── view │ │ │ ├── fragment │ │ │ │ ├── RangeValueHelp.fragment.xml │ │ │ │ └── NameValueHelp.fragment.xml │ │ │ └── Mountains.view.xml │ │ └── manifest.json │ ├── tsconfig.json │ ├── ui5.yaml │ ├── package.json │ └── readme.md └── ex5 │ ├── .gitignore │ ├── ex5.png │ ├── webapp │ ├── Component.ts │ ├── delegate │ │ ├── JSONBaseDelegate.ts │ │ ├── JSONFilterBarDelegate.ts │ │ └── JSONTableDelegate.ts │ ├── model │ │ ├── type │ │ │ ├── LengthMeter.ts │ │ │ └── TypeMap.ts │ │ └── metadata │ │ │ └── JSONPropertyInfo.ts │ ├── index.html │ ├── view │ │ ├── fragment │ │ │ ├── RangeValueHelp.fragment.xml │ │ │ └── NameValueHelp.fragment.xml │ │ └── Mountains.view.xml │ └── manifest.json │ ├── tsconfig.json │ ├── ui5.yaml │ ├── package.json │ └── readme.md ├── u2 └── ex1 │ ├── .gitignore │ ├── ex1.png │ ├── webapp │ ├── delegate │ │ ├── JSONBaseDelegate.ts │ │ └── JSONGeomapDelegate.ts │ ├── worker.js │ ├── model │ │ ├── type │ │ │ ├── Geometry.ts │ │ │ ├── LengthMeter.ts │ │ │ └── TypeMap.ts │ │ ├── osm.json │ │ └── metadata │ │ │ └── JSONPropertyInfo.ts │ ├── Component.ts │ ├── controller │ │ └── Mountains.controller.ts │ ├── index.html │ ├── manifest.json │ └── view │ │ └── Mountains.view.xml │ ├── tsconfig.json │ ├── ui5.yaml │ ├── package.json │ └── readme.md ├── .github └── workflows │ └── on-commit.yaml ├── REUSE.toml ├── README.md ├── LICENSE └── LICENSES └── Apache-2.0.txt /u1/ex0/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ -------------------------------------------------------------------------------- /u1/ex1/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ -------------------------------------------------------------------------------- /u1/ex2/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ -------------------------------------------------------------------------------- /u1/ex3/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ -------------------------------------------------------------------------------- /u1/ex4/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ -------------------------------------------------------------------------------- /u1/ex5/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ -------------------------------------------------------------------------------- /u2/ex1/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ -------------------------------------------------------------------------------- /u1/ex0/ex0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP-samples/ui5-mdc-json-tutorial/HEAD/u1/ex0/ex0.png -------------------------------------------------------------------------------- /u1/ex1/ex1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP-samples/ui5-mdc-json-tutorial/HEAD/u1/ex1/ex1.png -------------------------------------------------------------------------------- /u1/ex2/ex2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP-samples/ui5-mdc-json-tutorial/HEAD/u1/ex2/ex2.png -------------------------------------------------------------------------------- /u1/ex3/ex3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP-samples/ui5-mdc-json-tutorial/HEAD/u1/ex3/ex3.png -------------------------------------------------------------------------------- /u1/ex4/ex4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP-samples/ui5-mdc-json-tutorial/HEAD/u1/ex4/ex4.png -------------------------------------------------------------------------------- /u1/ex5/ex5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP-samples/ui5-mdc-json-tutorial/HEAD/u1/ex5/ex5.png -------------------------------------------------------------------------------- /u2/ex1/ex1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP-samples/ui5-mdc-json-tutorial/HEAD/u2/ex1/ex1.png -------------------------------------------------------------------------------- /u1/ex0/ex0_folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP-samples/ui5-mdc-json-tutorial/HEAD/u1/ex0/ex0_folder.png -------------------------------------------------------------------------------- /u1/ex0/webapp/Component.ts: -------------------------------------------------------------------------------- 1 | import UIComponent from "sap/ui/core/UIComponent" 2 | 3 | /** 4 | * @namespace mdc.tutorial 5 | */ 6 | export default class Component extends UIComponent {} -------------------------------------------------------------------------------- /u1/ex1/webapp/Component.ts: -------------------------------------------------------------------------------- 1 | import UIComponent from "sap/ui/core/UIComponent" 2 | 3 | /** 4 | * @namespace mdc.tutorial 5 | */ 6 | export default class Component extends UIComponent {} -------------------------------------------------------------------------------- /u1/ex2/webapp/Component.ts: -------------------------------------------------------------------------------- 1 | import UIComponent from "sap/ui/core/UIComponent"; 2 | 3 | /** 4 | * @namespace mdc.tutorial 5 | */ 6 | export default class Component extends UIComponent {} -------------------------------------------------------------------------------- /u1/ex3/webapp/Component.ts: -------------------------------------------------------------------------------- 1 | import UIComponent from "sap/ui/core/UIComponent" 2 | 3 | /** 4 | * @namespace mdc.tutorial 5 | */ 6 | export default class Component extends UIComponent {} -------------------------------------------------------------------------------- /u1/ex4/webapp/Component.ts: -------------------------------------------------------------------------------- 1 | import UIComponent from "sap/ui/core/UIComponent" 2 | 3 | /** 4 | * @namespace mdc.tutorial 5 | */ 6 | export default class Component extends UIComponent {} -------------------------------------------------------------------------------- /u1/ex5/webapp/Component.ts: -------------------------------------------------------------------------------- 1 | import UIComponent from "sap/ui/core/UIComponent" 2 | 3 | /** 4 | * @namespace mdc.tutorial 5 | */ 6 | export default class Component extends UIComponent {} -------------------------------------------------------------------------------- /u1/ex4/webapp/delegate/JSONBaseDelegate.ts: -------------------------------------------------------------------------------- 1 | import TypeMap from "mdc/tutorial/model/type/TypeMap" 2 | 3 | export default { 4 | getTypeMap: function() { 5 | return TypeMap; 6 | } 7 | } -------------------------------------------------------------------------------- /u1/ex5/webapp/delegate/JSONBaseDelegate.ts: -------------------------------------------------------------------------------- 1 | import TypeMap from "mdc/tutorial/model/type/TypeMap" 2 | 3 | export default { 4 | getTypeMap: function() { 5 | return TypeMap; 6 | } 7 | } -------------------------------------------------------------------------------- /u2/ex1/webapp/delegate/JSONBaseDelegate.ts: -------------------------------------------------------------------------------- 1 | import TypeMap from "mdc/tutorial/model/type/TypeMap" 2 | 3 | export default { 4 | getTypeMap: function() { 5 | return TypeMap; 6 | } 7 | } -------------------------------------------------------------------------------- /u2/ex1/webapp/worker.js: -------------------------------------------------------------------------------- 1 | // importScripts("/resources/sap/ui/geomap/thirdparty/maplibre-gl-csp-worker.js?commonjs-es-import"); 2 | importScripts(new URLSearchParams(self.location.search).get("worker")); -------------------------------------------------------------------------------- /u1/ex4/webapp/model/type/LengthMeter.ts: -------------------------------------------------------------------------------- 1 | import Integer from "sap/ui/model/type/Integer" 2 | import NumberFormat from "sap/ui/core/format/NumberFormat" 3 | 4 | export default class LengthMeter extends Integer { 5 | formatValue(height: number) { 6 | const unitFormat = NumberFormat.getUnitInstance() 7 | return unitFormat.format(height, "length-meter") 8 | } 9 | } -------------------------------------------------------------------------------- /u1/ex5/webapp/model/type/LengthMeter.ts: -------------------------------------------------------------------------------- 1 | import Integer from "sap/ui/model/type/Integer" 2 | import NumberFormat from "sap/ui/core/format/NumberFormat" 3 | 4 | export default class LengthMeter extends Integer { 5 | formatValue(height: number) { 6 | const unitFormat = NumberFormat.getUnitInstance() 7 | return unitFormat.format(height, "length-meter") 8 | } 9 | } -------------------------------------------------------------------------------- /u2/ex1/webapp/model/type/Geometry.ts: -------------------------------------------------------------------------------- 1 | import Float from "sap/ui/model/type/Float" 2 | 3 | type GeometryType = { 4 | type: string, 5 | coordinates: Number[] 6 | } 7 | 8 | export default class Geometry extends Float { 9 | formatValue(oObject: GeometryType) { 10 | const coordinates = oObject.coordinates; 11 | 12 | return coordinates; 13 | } 14 | } -------------------------------------------------------------------------------- /u2/ex1/webapp/model/type/LengthMeter.ts: -------------------------------------------------------------------------------- 1 | import Integer from "sap/ui/model/type/Integer" 2 | import NumberFormat from "sap/ui/core/format/NumberFormat" 3 | 4 | export default class LengthMeter extends Integer { 5 | formatValue(height: number) { 6 | const unitFormat = NumberFormat.getUnitInstance() 7 | return unitFormat.format(height, "length-meter") 8 | } 9 | } -------------------------------------------------------------------------------- /u2/ex1/webapp/model/osm.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 8, 3 | "name": "OSM Raster", 4 | "sources": { 5 | "osm-tiles": { 6 | "type": "raster", 7 | "tiles": ["https://tile.openstreetmap.org/{z}/{x}/{y}.png"], 8 | "tileSize": 256 9 | } 10 | }, 11 | "layers": [ 12 | { 13 | "id": "osm-tiles-layer", 14 | "type": "raster", 15 | "source": "osm-tiles" 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /u2/ex1/webapp/Component.ts: -------------------------------------------------------------------------------- 1 | import UIComponent from "sap/ui/core/UIComponent" 2 | 3 | /** 4 | * @namespace mdc.tutorial 5 | */ 6 | export default class Component extends UIComponent { 7 | public static metadata = { 8 | manifest: "json" 9 | }; 10 | 11 | public init(): void { 12 | // call the base component's init function 13 | super.init(); 14 | }; 15 | } -------------------------------------------------------------------------------- /u1/ex4/webapp/model/type/TypeMap.ts: -------------------------------------------------------------------------------- 1 | import DefaultTypeMap from "sap/ui/mdc/DefaultTypeMap" 2 | import BaseType from "sap/ui/mdc/enums/BaseType" 3 | import LengthMeter from "mdc/tutorial/model/type/LengthMeter" 4 | 5 | const TypeMap = Object.assign({}, DefaultTypeMap) 6 | TypeMap.import(DefaultTypeMap) 7 | TypeMap.set("mdc.tutorial.model.type.LengthMeter", BaseType.Numeric) 8 | TypeMap.freeze() 9 | 10 | export default TypeMap -------------------------------------------------------------------------------- /u1/ex5/webapp/model/type/TypeMap.ts: -------------------------------------------------------------------------------- 1 | import DefaultTypeMap from "sap/ui/mdc/DefaultTypeMap" 2 | import BaseType from "sap/ui/mdc/enums/BaseType" 3 | import LengthMeter from "mdc/tutorial/model/type/LengthMeter" 4 | 5 | const TypeMap = Object.assign({}, DefaultTypeMap) 6 | TypeMap.import(DefaultTypeMap) 7 | TypeMap.set("mdc.tutorial.model.type.LengthMeter", BaseType.Numeric) 8 | TypeMap.freeze() 9 | 10 | export default TypeMap -------------------------------------------------------------------------------- /u1/ex0/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2022", 4 | "module": "es2022", 5 | "moduleResolution": "node", 6 | "skipLibCheck": true, 7 | "allowJs": true, 8 | "strict": true, 9 | "strictNullChecks": false, 10 | "strictPropertyInitialization": false, 11 | "rootDir": "./webapp", 12 | "types": ["@openui5/types"], 13 | "paths": { 14 | "mdc/tutorial/*": ["./webapp/*"] 15 | } 16 | }, 17 | "include": ["./webapp/**/*"] 18 | } -------------------------------------------------------------------------------- /u1/ex1/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2022", 4 | "module": "es2022", 5 | "moduleResolution": "node", 6 | "skipLibCheck": true, 7 | "allowJs": true, 8 | "strict": true, 9 | "strictNullChecks": false, 10 | "strictPropertyInitialization": false, 11 | "rootDir": "./webapp", 12 | "types": ["@openui5/types"], 13 | "paths": { 14 | "mdc/tutorial/*": ["./webapp/*"] 15 | } 16 | }, 17 | "include": ["./webapp/**/*"] 18 | } -------------------------------------------------------------------------------- /u1/ex2/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2022", 4 | "module": "es2022", 5 | "moduleResolution": "node", 6 | "skipLibCheck": true, 7 | "allowJs": true, 8 | "strict": true, 9 | "strictNullChecks": false, 10 | "strictPropertyInitialization": false, 11 | "rootDir": "./webapp", 12 | "types": ["@openui5/types"], 13 | "paths": { 14 | "mdc/tutorial/*": ["./webapp/*"] 15 | } 16 | }, 17 | "include": ["./webapp/**/*"] 18 | } -------------------------------------------------------------------------------- /u1/ex3/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2022", 4 | "module": "es2022", 5 | "moduleResolution": "node", 6 | "skipLibCheck": true, 7 | "allowJs": true, 8 | "strict": true, 9 | "strictNullChecks": false, 10 | "strictPropertyInitialization": false, 11 | "rootDir": "./webapp", 12 | "types": ["@openui5/types"], 13 | "paths": { 14 | "mdc/tutorial/*": ["./webapp/*"] 15 | } 16 | }, 17 | "include": ["./webapp/**/*"] 18 | } -------------------------------------------------------------------------------- /u1/ex4/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2022", 4 | "module": "es2022", 5 | "moduleResolution": "node", 6 | "skipLibCheck": true, 7 | "allowJs": true, 8 | "strict": true, 9 | "strictNullChecks": false, 10 | "strictPropertyInitialization": false, 11 | "rootDir": "./webapp", 12 | "types": ["@openui5/types"], 13 | "paths": { 14 | "mdc/tutorial/*": ["./webapp/*"] 15 | } 16 | }, 17 | "include": ["./webapp/**/*"] 18 | } -------------------------------------------------------------------------------- /u1/ex5/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2022", 4 | "module": "es2022", 5 | "moduleResolution": "node", 6 | "skipLibCheck": true, 7 | "allowJs": true, 8 | "strict": true, 9 | "strictNullChecks": false, 10 | "strictPropertyInitialization": false, 11 | "rootDir": "./webapp", 12 | "types": ["@openui5/types"], 13 | "paths": { 14 | "mdc/tutorial/*": ["./webapp/*"] 15 | } 16 | }, 17 | "include": ["./webapp/**/*"] 18 | } -------------------------------------------------------------------------------- /u2/ex1/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2022", 4 | "module": "es2022", 5 | "moduleResolution": "node", 6 | "skipLibCheck": true, 7 | "allowJs": true, 8 | "strict": true, 9 | "strictNullChecks": false, 10 | "strictPropertyInitialization": false, 11 | "rootDir": "./webapp", 12 | "types": ["@openui5/types"], 13 | "paths": { 14 | "mdc/tutorial/*": ["./webapp/*"] 15 | } 16 | }, 17 | "include": ["./webapp/**/*"] 18 | } -------------------------------------------------------------------------------- /u1/ex0/ui5.yaml: -------------------------------------------------------------------------------- 1 | specVersion: '3.2' 2 | type: application 3 | metadata: 4 | name: mdc-json-tutorial 5 | framework: 6 | name: OpenUI5 7 | version: '1.136.0' 8 | builder: 9 | customTasks: 10 | - name: ui5-tooling-transpile-task 11 | afterTask: replaceVersion 12 | server: 13 | customMiddleware: 14 | - name: ui5-tooling-transpile-middleware 15 | afterMiddleware: compression 16 | - name: ui5-middleware-livereload 17 | afterMiddleware: compression -------------------------------------------------------------------------------- /u1/ex1/ui5.yaml: -------------------------------------------------------------------------------- 1 | specVersion: '3.2' 2 | type: application 3 | metadata: 4 | name: mdc-json-tutorial 5 | framework: 6 | name: OpenUI5 7 | version: '1.136.0' 8 | builder: 9 | customTasks: 10 | - name: ui5-tooling-transpile-task 11 | afterTask: replaceVersion 12 | server: 13 | customMiddleware: 14 | - name: ui5-tooling-transpile-middleware 15 | afterMiddleware: compression 16 | - name: ui5-middleware-livereload 17 | afterMiddleware: compression -------------------------------------------------------------------------------- /u1/ex2/ui5.yaml: -------------------------------------------------------------------------------- 1 | specVersion: '3.2' 2 | type: application 3 | metadata: 4 | name: mdc-json-tutorial 5 | framework: 6 | name: OpenUI5 7 | version: '1.136.0' 8 | builder: 9 | customTasks: 10 | - name: ui5-tooling-transpile-task 11 | afterTask: replaceVersion 12 | server: 13 | customMiddleware: 14 | - name: ui5-tooling-transpile-middleware 15 | afterMiddleware: compression 16 | - name: ui5-middleware-livereload 17 | afterMiddleware: compression -------------------------------------------------------------------------------- /u1/ex3/ui5.yaml: -------------------------------------------------------------------------------- 1 | specVersion: '3.2' 2 | type: application 3 | metadata: 4 | name: mdc-json-tutorial 5 | framework: 6 | name: OpenUI5 7 | version: '1.136.0' 8 | builder: 9 | customTasks: 10 | - name: ui5-tooling-transpile-task 11 | afterTask: replaceVersion 12 | server: 13 | customMiddleware: 14 | - name: ui5-tooling-transpile-middleware 15 | afterMiddleware: compression 16 | - name: ui5-middleware-livereload 17 | afterMiddleware: compression -------------------------------------------------------------------------------- /u1/ex4/ui5.yaml: -------------------------------------------------------------------------------- 1 | specVersion: '3.2' 2 | type: application 3 | metadata: 4 | name: mdc-json-tutorial 5 | framework: 6 | name: OpenUI5 7 | version: '1.136.0' 8 | builder: 9 | customTasks: 10 | - name: ui5-tooling-transpile-task 11 | afterTask: replaceVersion 12 | server: 13 | customMiddleware: 14 | - name: ui5-tooling-transpile-middleware 15 | afterMiddleware: compression 16 | - name: ui5-middleware-livereload 17 | afterMiddleware: compression -------------------------------------------------------------------------------- /u1/ex5/ui5.yaml: -------------------------------------------------------------------------------- 1 | specVersion: '3.2' 2 | type: application 3 | metadata: 4 | name: mdc-json-tutorial 5 | framework: 6 | name: OpenUI5 7 | version: '1.136.0' 8 | builder: 9 | customTasks: 10 | - name: ui5-tooling-transpile-task 11 | afterTask: replaceVersion 12 | server: 13 | customMiddleware: 14 | - name: ui5-tooling-transpile-middleware 15 | afterMiddleware: compression 16 | - name: ui5-middleware-livereload 17 | afterMiddleware: compression -------------------------------------------------------------------------------- /u2/ex1/ui5.yaml: -------------------------------------------------------------------------------- 1 | specVersion: '3.2' 2 | type: application 3 | metadata: 4 | name: mdc-json-tutorial 5 | framework: 6 | name: SAPUI5 7 | version: '1.142.0' 8 | builder: 9 | customTasks: 10 | - name: ui5-tooling-transpile-task 11 | afterTask: replaceVersion 12 | server: 13 | customMiddleware: 14 | - name: ui5-tooling-transpile-middleware 15 | afterMiddleware: compression 16 | - name: ui5-middleware-livereload 17 | afterMiddleware: compression -------------------------------------------------------------------------------- /u2/ex1/webapp/model/type/TypeMap.ts: -------------------------------------------------------------------------------- 1 | import DefaultTypeMap from "sap/ui/mdc/DefaultTypeMap" 2 | import BaseType from "sap/ui/mdc/enums/BaseType" 3 | import LengthMeter from "mdc/tutorial/model/type/LengthMeter" 4 | import Geometry from "mdc/tutorial/model/type/Geometry" 5 | 6 | const TypeMap = Object.assign({}, DefaultTypeMap) 7 | const a = Geometry; // workaround for the build to iclude Geometry Type 8 | TypeMap.import(DefaultTypeMap) 9 | TypeMap.set("mdc.tutorial.model.type.LengthMeter", BaseType.Numeric) 10 | TypeMap.set("mdc.tutorial.model.type.Geometry", BaseType.Numeric) 11 | TypeMap.freeze() 12 | 13 | export default TypeMap -------------------------------------------------------------------------------- /u2/ex1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ui5-mdc-json-tutorial", 3 | "version": "1.0.0", 4 | "author": "SAP SE", 5 | "description": "Sample: Geomap with JSON model", 6 | "scripts": { 7 | "start": "ui5 serve", 8 | "build": "ui5 build", 9 | "ts-typecheck": "tsc --noEmit" 10 | }, 11 | "devDependencies": { 12 | "@openui5/types": "^1.124.0", 13 | "@typescript-eslint/eslint-plugin": "^5.62.0", 14 | "@typescript-eslint/parser": "^5.62.0", 15 | "@ui5/cli": "^3.8.0", 16 | "typescript": "^5.2.2", 17 | "ui5-middleware-livereload": "^3.0.2", 18 | "ui5-tooling-transpile": "^3.3.2" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /u2/ex1/webapp/controller/Mountains.controller.ts: -------------------------------------------------------------------------------- 1 | import Controller from "sap/ui/core/mvc/Controller" 2 | import JSONModel from "sap/ui/model/json/JSONModel" 3 | 4 | /** 5 | * @namespace Mountains.controller 6 | */ 7 | export default class Mountains extends Controller { 8 | public onInit(): void { 9 | const oDataModel = this.createDataModel(); 10 | this.getView().setModel(oDataModel, "data"); 11 | this.getView().byId("geomap").setModel(oDataModel); 12 | } 13 | createDataModel(): JSONModel { 14 | return new JSONModel(sap.ui.require.toUrl("mdc/tutorial/model/mountains.json")); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /u1/ex0/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ui5-mdc-json-tutorial", 3 | "version": "1.0.0", 4 | "author": "SAP SE", 5 | "description": "Sample: Table with FilterBar and JSON model", 6 | "scripts": { 7 | "start": "ui5 serve", 8 | "build": "ui5 build", 9 | "ts-typecheck": "tsc --noEmit" 10 | }, 11 | "devDependencies": { 12 | "@openui5/types": "^1.124.0", 13 | "@typescript-eslint/eslint-plugin": "^5.62.0", 14 | "@typescript-eslint/parser": "^5.62.0", 15 | "@ui5/cli": "^3.8.0", 16 | "typescript": "^5.2.2", 17 | "ui5-middleware-livereload": "^3.0.2", 18 | "ui5-tooling-transpile": "^3.3.2" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /u1/ex1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ui5-mdc-json-tutorial", 3 | "version": "1.0.0", 4 | "author": "SAP SE", 5 | "description": "Sample: Table with FilterBar and JSON model", 6 | "scripts": { 7 | "start": "ui5 serve", 8 | "build": "ui5 build", 9 | "ts-typecheck": "tsc --noEmit" 10 | }, 11 | "devDependencies": { 12 | "@openui5/types": "^1.124.0", 13 | "@typescript-eslint/eslint-plugin": "^5.62.0", 14 | "@typescript-eslint/parser": "^5.62.0", 15 | "@ui5/cli": "^3.8.0", 16 | "typescript": "^5.2.2", 17 | "ui5-middleware-livereload": "^3.0.2", 18 | "ui5-tooling-transpile": "^3.3.2" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /u1/ex2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ui5-mdc-json-tutorial", 3 | "version": "1.0.0", 4 | "author": "SAP SE", 5 | "description": "Sample: Table with FilterBar and JSON model", 6 | "scripts": { 7 | "start": "ui5 serve", 8 | "build": "ui5 build", 9 | "ts-typecheck": "tsc --noEmit" 10 | }, 11 | "devDependencies": { 12 | "@openui5/types": "^1.124.0", 13 | "@typescript-eslint/eslint-plugin": "^5.62.0", 14 | "@typescript-eslint/parser": "^5.62.0", 15 | "@ui5/cli": "^3.8.0", 16 | "typescript": "^5.2.2", 17 | "ui5-middleware-livereload": "^3.0.2", 18 | "ui5-tooling-transpile": "^3.3.2" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /u1/ex3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ui5-mdc-json-tutorial", 3 | "version": "1.0.0", 4 | "author": "SAP SE", 5 | "description": "Sample: Table with FilterBar and JSON model", 6 | "scripts": { 7 | "start": "ui5 serve", 8 | "build": "ui5 build", 9 | "ts-typecheck": "tsc --noEmit" 10 | }, 11 | "devDependencies": { 12 | "@openui5/types": "^1.124.0", 13 | "@typescript-eslint/eslint-plugin": "^5.62.0", 14 | "@typescript-eslint/parser": "^5.62.0", 15 | "@ui5/cli": "^3.8.0", 16 | "typescript": "^5.2.2", 17 | "ui5-middleware-livereload": "^3.0.2", 18 | "ui5-tooling-transpile": "^3.3.2" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /u1/ex4/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ui5-mdc-json-tutorial", 3 | "version": "1.0.0", 4 | "author": "SAP SE", 5 | "description": "Sample: Table with FilterBar and JSON model", 6 | "scripts": { 7 | "start": "ui5 serve", 8 | "build": "ui5 build", 9 | "ts-typecheck": "tsc --noEmit" 10 | }, 11 | "devDependencies": { 12 | "@openui5/types": "^1.124.0", 13 | "@typescript-eslint/eslint-plugin": "^5.62.0", 14 | "@typescript-eslint/parser": "^5.62.0", 15 | "@ui5/cli": "^3.8.0", 16 | "typescript": "^5.2.2", 17 | "ui5-middleware-livereload": "^3.0.2", 18 | "ui5-tooling-transpile": "^3.3.2" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /u1/ex5/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ui5-mdc-json-tutorial", 3 | "version": "1.0.0", 4 | "author": "SAP SE", 5 | "description": "Sample: Table with FilterBar and JSON model", 6 | "scripts": { 7 | "start": "ui5 serve", 8 | "build": "ui5 build", 9 | "ts-typecheck": "tsc --noEmit" 10 | }, 11 | "devDependencies": { 12 | "@openui5/types": "^1.124.0", 13 | "@typescript-eslint/eslint-plugin": "^5.62.0", 14 | "@typescript-eslint/parser": "^5.62.0", 15 | "@ui5/cli": "^3.11.13", 16 | "typescript": "^5.2.2", 17 | "ui5-middleware-livereload": "^3.0.2", 18 | "ui5-tooling-transpile": "^3.3.2" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /u1/ex0/webapp/view/Mountains.view.xml: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | </f:heading> 18 | </f:DynamicPageTitle> 19 | </f:title> 20 | <f:header> 21 | <f:DynamicPageHeader pinnable="true"> 22 | 23 | </f:DynamicPageHeader> 24 | </f:header> 25 | <f:content> 26 | 27 | </f:content> 28 | </f:DynamicPage> 29 | 30 | </mvc:View> -------------------------------------------------------------------------------- /u1/ex0/webapp/index.html: -------------------------------------------------------------------------------- 1 | <!DOCTYPE html> 2 | <html> 3 | <head> 4 | <meta charset="utf-8"> 5 | <title>MDC JSON Tutorial 6 | 16 | 17 | 18 |
20 |
21 | 22 | -------------------------------------------------------------------------------- /u1/ex1/webapp/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MDC JSON Tutorial 6 | 16 | 17 | 18 |
20 |
21 | 22 | -------------------------------------------------------------------------------- /u1/ex2/webapp/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MDC JSON Tutorial 6 | 16 | 17 | 18 |
20 |
21 | 22 | -------------------------------------------------------------------------------- /u1/ex4/webapp/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MDC JSON Tutorial 6 | 16 | 17 | 18 |
20 |
21 | 22 | -------------------------------------------------------------------------------- /u1/ex5/webapp/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MDC JSON Tutorial 6 | 16 | 17 | 18 |
20 |
21 | 22 | -------------------------------------------------------------------------------- /u2/ex1/webapp/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MDC JSON Tutorial 6 | 17 | 18 | 19 |
21 |
22 | 23 | -------------------------------------------------------------------------------- /u1/ex3/webapp/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | MDC JSON Tutorial 7 | 8 | 18 | 19 | 20 | 21 | 22 |
23 |
24 | 25 | -------------------------------------------------------------------------------- /.github/workflows/on-commit.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | jobs: 6 | deploy-pages: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v4 10 | - uses: actions/setup-node@v4 11 | with: 12 | cache: 'npm' 13 | cache-dependency-path: 'u1/ex0/package-lock.json' 14 | 15 | - name: Install and build exercises 16 | run: | 17 | mkdir -p gh-pages/u1 18 | zip -r gh-pages/mdc.tutorial.zip u1/ex0 -x 'readme.md' -x '*.png' 19 | 20 | for dir in ./u1/ex*/; do 21 | mkdir -p gh-pages/u1/$(basename "$dir") 22 | pushd "$dir" 23 | npm install 24 | npm run build 25 | mv dist ../../gh-pages/u1/$(basename "$dir") 26 | popd 27 | done 28 | 29 | - name: Deploy 🚀 30 | uses: JamesIves/github-pages-deploy-action@v4 31 | with: 32 | folder: gh-pages -------------------------------------------------------------------------------- /u1/ex2/webapp/view/fragment/RangeValueHelp.fragment.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 |
19 |
20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 |
30 |
31 |
32 |
33 | 34 |
-------------------------------------------------------------------------------- /u1/ex3/webapp/view/fragment/RangeValueHelp.fragment.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 |
19 |
20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 |
30 |
31 |
32 |
33 | 34 |
-------------------------------------------------------------------------------- /u1/ex4/webapp/view/fragment/RangeValueHelp.fragment.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 |
19 |
20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 |
30 |
31 |
32 |
33 | 34 |
-------------------------------------------------------------------------------- /u1/ex5/webapp/view/fragment/RangeValueHelp.fragment.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 |
19 |
20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 |
30 |
31 |
32 |
33 | 34 |
-------------------------------------------------------------------------------- /u1/ex0/webapp/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "sap.app": { 3 | "id": "mdc.tutorial", 4 | "type": "application", 5 | "applicationVersion": { 6 | "version": "1.0.0" 7 | }, 8 | "title": "MDC JSON Tutorial", 9 | "description": "MDC JSON Tutorial featuring Table and Filterbar with VM", 10 | "dataSources": { 11 | "mountains" : { 12 | "uri": "model/mountains.json", 13 | "type": "JSON" 14 | } 15 | } 16 | }, 17 | "sap.ui": { 18 | "technology": "UI5", 19 | "deviceTypes": { 20 | "desktop": true, 21 | "tablet": true, 22 | "phone": true 23 | } 24 | }, 25 | "sap.ui5": { 26 | "flexEnabled": false, 27 | "dependencies": { 28 | "minUI5Version": "1.118.0", 29 | "libs": { 30 | "sap.ui.core": {}, 31 | "sap.m": {}, 32 | "sap.f": {}, 33 | "sap.ui.mdc": {}, 34 | "sap.ui.fl": {} 35 | } 36 | }, 37 | "contentDensities": { 38 | "compact": true, 39 | "cozy": true 40 | }, 41 | "handleValidation": true, 42 | "models": { 43 | "mountains": { 44 | "type": "sap.ui.model.json.JSONModel", 45 | "dataSource": "mountains" 46 | } 47 | }, 48 | "rootView": { 49 | "viewName": "mdc.tutorial.view.Mountains", 50 | "type": "XML", 51 | "async": true, 52 | "id": "sample" 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /u1/ex1/webapp/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "sap.app": { 3 | "id": "mdc.tutorial", 4 | "type": "application", 5 | "applicationVersion": { 6 | "version": "1.0.0" 7 | }, 8 | "title": "MDC JSON Tutorial", 9 | "description": "MDC JSON Tutorial featuring Table and Filterbar with VM", 10 | "dataSources": { 11 | "mountains" : { 12 | "uri": "model/mountains.json", 13 | "type": "JSON" 14 | } 15 | } 16 | }, 17 | "sap.ui": { 18 | "technology": "UI5", 19 | "deviceTypes": { 20 | "desktop": true, 21 | "tablet": true, 22 | "phone": true 23 | } 24 | }, 25 | "sap.ui5": { 26 | "flexEnabled": false, 27 | "dependencies": { 28 | "minUI5Version": "1.118.0", 29 | "libs": { 30 | "sap.ui.core": {}, 31 | "sap.m": {}, 32 | "sap.f": {}, 33 | "sap.ui.mdc": {}, 34 | "sap.ui.fl": {} 35 | } 36 | }, 37 | "contentDensities": { 38 | "compact": true, 39 | "cozy": true 40 | }, 41 | "handleValidation": true, 42 | "models": { 43 | "mountains": { 44 | "type": "sap.ui.model.json.JSONModel", 45 | "dataSource": "mountains" 46 | } 47 | }, 48 | "rootView": { 49 | "viewName": "mdc.tutorial.view.Mountains", 50 | "type": "XML", 51 | "async": true, 52 | "id": "sample" 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /u1/ex2/webapp/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "sap.app": { 3 | "id": "mdc.tutorial", 4 | "type": "application", 5 | "applicationVersion": { 6 | "version": "1.0.0" 7 | }, 8 | "title": "MDC JSON Tutorial", 9 | "description": "MDC JSON Tutorial featuring Table and Filterbar with VM", 10 | "dataSources": { 11 | "mountains" : { 12 | "uri": "model/mountains.json", 13 | "type": "JSON" 14 | } 15 | } 16 | }, 17 | "sap.ui": { 18 | "technology": "UI5", 19 | "deviceTypes": { 20 | "desktop": true, 21 | "tablet": true, 22 | "phone": true 23 | } 24 | }, 25 | "sap.ui5": { 26 | "flexEnabled": false, 27 | "dependencies": { 28 | "minUI5Version": "1.118.0", 29 | "libs": { 30 | "sap.ui.core": {}, 31 | "sap.m": {}, 32 | "sap.f": {}, 33 | "sap.ui.mdc": {}, 34 | "sap.ui.fl": {} 35 | } 36 | }, 37 | "contentDensities": { 38 | "compact": true, 39 | "cozy": true 40 | }, 41 | "handleValidation": true, 42 | "models": { 43 | "mountains": { 44 | "type": "sap.ui.model.json.JSONModel", 45 | "dataSource": "mountains" 46 | } 47 | }, 48 | "rootView": { 49 | "viewName": "mdc.tutorial.view.Mountains", 50 | "type": "XML", 51 | "async": true, 52 | "id": "sample" 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /u1/ex3/webapp/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "sap.app": { 3 | "id": "mdc.tutorial", 4 | "type": "application", 5 | "applicationVersion": { 6 | "version": "1.0.0" 7 | }, 8 | "title": "MDC JSON Tutorial", 9 | "description": "MDC JSON Tutorial featuring Table and Filterbar with VM", 10 | "dataSources": { 11 | "mountains" : { 12 | "uri": "model/mountains.json", 13 | "type": "JSON" 14 | } 15 | } 16 | }, 17 | "sap.ui": { 18 | "technology": "UI5", 19 | "deviceTypes": { 20 | "desktop": true, 21 | "tablet": true, 22 | "phone": true 23 | } 24 | }, 25 | "sap.ui5": { 26 | "flexEnabled": false, 27 | "dependencies": { 28 | "minUI5Version": "1.118.0", 29 | "libs": { 30 | "sap.ui.core": {}, 31 | "sap.m": {}, 32 | "sap.f": {}, 33 | "sap.ui.mdc": {}, 34 | "sap.ui.fl": {} 35 | } 36 | }, 37 | "contentDensities": { 38 | "compact": true, 39 | "cozy": true 40 | }, 41 | "handleValidation": true, 42 | "models": { 43 | "mountains": { 44 | "type": "sap.ui.model.json.JSONModel", 45 | "dataSource": "mountains" 46 | } 47 | }, 48 | "rootView": { 49 | "viewName": "mdc.tutorial.view.Mountains", 50 | "type": "XML", 51 | "async": true, 52 | "id": "sample" 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /u1/ex4/webapp/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "sap.app": { 3 | "id": "mdc.tutorial", 4 | "type": "application", 5 | "applicationVersion": { 6 | "version": "1.0.0" 7 | }, 8 | "title": "MDC JSON Tutorial", 9 | "description": "MDC JSON Tutorial featuring Table and Filterbar with VM", 10 | "dataSources": { 11 | "mountains" : { 12 | "uri": "model/mountains.json", 13 | "type": "JSON" 14 | } 15 | } 16 | }, 17 | "sap.ui": { 18 | "technology": "UI5", 19 | "deviceTypes": { 20 | "desktop": true, 21 | "tablet": true, 22 | "phone": true 23 | } 24 | }, 25 | "sap.ui5": { 26 | "flexEnabled": false, 27 | "dependencies": { 28 | "minUI5Version": "1.118.0", 29 | "libs": { 30 | "sap.ui.core": {}, 31 | "sap.m": {}, 32 | "sap.f": {}, 33 | "sap.ui.mdc": {}, 34 | "sap.ui.fl": {} 35 | } 36 | }, 37 | "contentDensities": { 38 | "compact": true, 39 | "cozy": true 40 | }, 41 | "handleValidation": true, 42 | "models": { 43 | "mountains": { 44 | "type": "sap.ui.model.json.JSONModel", 45 | "dataSource": "mountains" 46 | } 47 | }, 48 | "rootView": { 49 | "viewName": "mdc.tutorial.view.Mountains", 50 | "type": "XML", 51 | "async": true, 52 | "id": "sample" 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /u1/ex5/webapp/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "sap.app": { 3 | "id": "mdc.tutorial", 4 | "type": "application", 5 | "applicationVersion": { 6 | "version": "1.0.0" 7 | }, 8 | "title": "MDC JSON Tutorial", 9 | "description": "MDC JSON Tutorial featuring Table and Filterbar with VM", 10 | "dataSources": { 11 | "mountains" : { 12 | "uri": "model/mountains.json", 13 | "type": "JSON" 14 | } 15 | } 16 | }, 17 | "sap.ui": { 18 | "technology": "UI5", 19 | "deviceTypes": { 20 | "desktop": true, 21 | "tablet": true, 22 | "phone": true 23 | } 24 | }, 25 | "sap.ui5": { 26 | "flexEnabled": false, 27 | "dependencies": { 28 | "minUI5Version": "1.118.0", 29 | "libs": { 30 | "sap.ui.core": {}, 31 | "sap.m": {}, 32 | "sap.f": {}, 33 | "sap.ui.mdc": {}, 34 | "sap.ui.fl": {} 35 | } 36 | }, 37 | "contentDensities": { 38 | "compact": true, 39 | "cozy": true 40 | }, 41 | "handleValidation": true, 42 | "models": { 43 | "mountains": { 44 | "type": "sap.ui.model.json.JSONModel", 45 | "dataSource": "mountains" 46 | } 47 | }, 48 | "rootView": { 49 | "viewName": "mdc.tutorial.view.Mountains", 50 | "type": "XML", 51 | "async": true, 52 | "id": "sample" 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /u2/ex1/webapp/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "sap.app": { 3 | "id": "mdc.tutorial", 4 | "type": "application", 5 | "applicationVersion": { 6 | "version": "1.0.0" 7 | }, 8 | "title": "MDC JSON Tutorial", 9 | "description": "MDC JSON Tutorial featuring Geomap", 10 | "dataSources": { 11 | "mountains" : { 12 | "uri": "model/mountains.json", 13 | "type": "JSON" 14 | } 15 | } 16 | }, 17 | "sap.ui": { 18 | "technology": "UI5", 19 | "deviceTypes": { 20 | "desktop": true, 21 | "tablet": true, 22 | "phone": true 23 | } 24 | }, 25 | "sap.ui5": { 26 | "flexEnabled": false, 27 | "dependencies": { 28 | "minUI5Version": "1.118.0", 29 | "libs": { 30 | "sap.ui.core": {}, 31 | "sap.m": {}, 32 | "sap.f": {}, 33 | "sap.ui.mdc": {}, 34 | "sap.ui.geomap": {}, 35 | "sap.ui.fl": {} 36 | } 37 | }, 38 | "contentDensities": { 39 | "compact": true, 40 | "cozy": true 41 | }, 42 | "handleValidation": true, 43 | "models": { 44 | "mountains": { 45 | "type": "sap.ui.model.json.JSONModel", 46 | "dataSource": "mountains" 47 | } 48 | }, 49 | "rootView": { 50 | "viewName": "mdc.tutorial.view.Mountains", 51 | "type": "XML", 52 | "async": true, 53 | "id": "sample" 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /u2/ex1/webapp/model/metadata/JSONPropertyInfo.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @namespace mdc.tutorial.model.metadata 3 | * 4 | * Property Example: 5 | * { 6 | * "coordinates": "27°59'17''N 86°55'31''E", 7 | * "parent_mountain": "-", 8 | * "first_ascent": 1953, 9 | * "countries": "Nepal, China", 10 | * "geometry": { 11 | * "type": "Point", 12 | * "coordinates": [39.7972, 47.115], 13 | * "properties": { 14 | * "rank": 1, 15 | * "name": "Mount Everest", 16 | * "height": 8848, 17 | * "prominence": 8848, 18 | * "range": "Mahalangur Himalaya", 19 | * } 20 | * }, 21 | * } 22 | */ 23 | export default [ 24 | { 25 | key: "coordinates", 26 | label: "Coordinates", 27 | visible: true, 28 | path: "coordinates", 29 | dataType: "sap.ui.model.type.String" 30 | },{ 31 | key: "parent_mountain", 32 | label: "Parent Mountain", 33 | visible: true, 34 | path: "parent_mountain", 35 | dataType: "sap.ui.model.type.String" 36 | },{ 37 | key: "first_ascent", 38 | label: "First Ascent", 39 | visible: true, 40 | path: "first_ascent", 41 | dataType: "sap.ui.model.type.Integer" 42 | },{ 43 | key: "geometry", 44 | label: "Geometry", 45 | visible: true, 46 | path: "geometry", 47 | dataType: "mdc.tutorial.model.type.Geometry" 48 | },{ 49 | key: "$search", 50 | label: "Search", 51 | visible: true, 52 | maxConditions: 1, 53 | dataType: "sap.ui.model.type.String" 54 | } 55 | ] -------------------------------------------------------------------------------- /u1/ex2/webapp/delegate/JSONFilterBarDelegate.ts: -------------------------------------------------------------------------------- 1 | import FilterBarDelegate from "sap/ui/mdc/FilterBarDelegate" 2 | import JSONPropertyInfo from "mdc/tutorial/model/metadata/JSONPropertyInfo" 3 | import FilterField from "sap/ui/mdc/FilterField" 4 | import Element from "sap/ui/core/Element" 5 | import {default as FilterBar, PropertyInfo as FilterBarPropertyInfo} from "sap/ui/mdc/FilterBar" 6 | 7 | var JSONFilterBarDelegate = Object.assign({}, FilterBarDelegate) 8 | 9 | JSONFilterBarDelegate.fetchProperties = async () => JSONPropertyInfo 10 | 11 | const _createFilterField = (id:string, property:FilterBarPropertyInfo, filterBar:FilterBar) => { 12 | const propertyKey = property.key 13 | const filterField = new FilterField(id, { 14 | dataType: property.dataType, 15 | conditions: `{$filters>/conditions/${propertyKey}}`, 16 | propertyKey: propertyKey, 17 | required: property.required, 18 | label: property.label, 19 | maxConditions: property.maxConditions, 20 | delegate: {name: "sap/ui/mdc/field/FieldBaseDelegate", payload: {}} 21 | }) 22 | return filterField 23 | } 24 | 25 | JSONFilterBarDelegate.addItem = async (filterBar:FilterBar, propertyKey:string) => { 26 | const property = JSONPropertyInfo.find((p) => p.key === propertyKey) as FilterBarPropertyInfo 27 | const id = `${filterBar.getId()}--filter--${propertyKey}` 28 | const filterField = Element.getElementById(id) as FilterField 29 | return filterField ?? _createFilterField(id, property, filterBar) 30 | } 31 | 32 | export default JSONFilterBarDelegate -------------------------------------------------------------------------------- /u1/ex1/webapp/delegate/JSONTableDelegate.ts: -------------------------------------------------------------------------------- 1 | import TableDelegate from "sap/ui/mdc/TableDelegate" 2 | import Text from "sap/m/Text" 3 | import Element from "sap/ui/core/Element" 4 | import JSONPropertyInfo from "mdc/tutorial/model/metadata/JSONPropertyInfo" 5 | import {default as Table, PropertyInfo as TablePropertyInfo} from "sap/ui/mdc/Table" 6 | import Column from "sap/ui/mdc/table/Column" 7 | 8 | interface TablePayload { 9 | bindingPath: string 10 | } 11 | 12 | const JSONTableDelegate = Object.assign({}, TableDelegate) 13 | 14 | JSONTableDelegate.fetchProperties = async () => { 15 | return JSONPropertyInfo.filter((p) => p.key !== "$search") 16 | } 17 | 18 | const _createColumn = (propertyInfo:TablePropertyInfo, table:Table) => { 19 | const name = propertyInfo.key 20 | const id = table.getId() + "---col-" + name 21 | const column = Element.getElementById(id) as Column 22 | return column ?? new Column(id, { 23 | propertyKey: name, 24 | header: propertyInfo.label, 25 | template: new Text({ 26 | text: { 27 | path: "mountains>" + name, 28 | type: propertyInfo.dataType 29 | } 30 | }) 31 | }) 32 | } 33 | 34 | JSONTableDelegate.addItem = async (table:Table, propertyKey:string) => { 35 | const propertyInfo = JSONPropertyInfo.find((p) => p.key === propertyKey) 36 | return _createColumn(propertyInfo, table) 37 | } 38 | 39 | JSONTableDelegate.updateBindingInfo = (table, bindingInfo) => { 40 | TableDelegate.updateBindingInfo.call(JSONTableDelegate, table, bindingInfo) 41 | bindingInfo.path = (table.getPayload() as TablePayload).bindingPath 42 | bindingInfo.templateShareable = true 43 | } 44 | 45 | export default JSONTableDelegate -------------------------------------------------------------------------------- /u2/ex1/webapp/view/Mountains.view.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 13 | 37 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /REUSE.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | SPDX-PackageName = "ui5-mdc-json-tutorial" 3 | SPDX-PackageSupplier = "ospo@sap.com" 4 | SPDX-PackageDownloadLocation = " 11 | 12 | 13 | 14 | 15 | 16 | 17 | </f:heading> 18 | </f:DynamicPageTitle> 19 | </f:title> 20 | <f:header> 21 | <f:DynamicPageHeader pinnable="true"> 22 | 23 | </f:DynamicPageHeader> 24 | </f:header> 25 | <f:content> 26 | <mdc:Table 27 | id="table" 28 | header="Mountains" 29 | p13nMode="Sort,Column" 30 | type="ResponsiveTable" 31 | threshold="100" 32 | filter="filterbar" 33 | showRowCount="false" 34 | delegate="{ 35 | name: 'mdc/tutorial/delegate/JSONTableDelegate', 36 | payload: { 37 | bindingPath: 'mountains>/mountains' 38 | } 39 | }"> 40 | <mdct:Column 41 | propertyKey="name" 42 | header="Name"> 43 | <Text text="{mountains>name}"/> 44 | </mdct:Column> 45 | <mdct:Column 46 | propertyKey="height" 47 | header="Height"> 48 | <Text text="{path: 'mountains>height'}"/> 49 | </mdct:Column> 50 | <mdct:Column 51 | propertyKey="range" 52 | header="Range"> 53 | <Text text="{mountains>range}"/> 54 | </mdct:Column> 55 | <mdct:Column 56 | propertyKey="first_ascent" 57 | header="First Ascent"> 58 | <Text text="{mountains>first_ascent}"/> 59 | </mdct:Column> 60 | <mdct:Column 61 | propertyKey="countries" 62 | header="Countries"> 63 | <Text text="{mountains>countries}"/> 64 | </mdct:Column> 65 | <mdct:Column 66 | propertyKey="parent_mountain" 67 | header="Parent Mountain"> 68 | <Text text="{mountains>parent_mountain}"/> 69 | </mdct:Column> 70 | </mdc:Table> 71 | </f:content> 72 | </f:DynamicPage> 73 | 74 | </mvc:View> -------------------------------------------------------------------------------- /u1/ex5/readme.md: -------------------------------------------------------------------------------- 1 | [![solution](https://flat.badgen.net/badge/solution/available/green?icon=github)](webapp) 2 | [![demo](https://flat.badgen.net/badge/demo/deployed/blue?icon=github)](https://sap-samples.github.io/ui5-mdc-json-tutorial/u1/ex5/dist) 3 | # Exercise 5: How to Enable VariantManagement 4 | Now that we've activated all of the remarkable features highlighted in the previous exercises, there's just one more step to complete. We want our users to have the ability to save the settings that they've applied in the sap.ui.mdc controls. To achieve this, we'll incorporate a [`VariantManagement`](https://sdk.openui5.org/api/sap.ui.fl.variants.VariantManagement) control. 5 | 6 | ## Step 1: Incorporate VariantManagement 7 | We'll utilize the `sap/ui/fl/variants/VariantManagement` control. This does not only provide persistence for user settings but also offers support for key user adaptations. The implementation is straightforward, replace the Title control as follows: 8 | ###### view/Mountains.view.xml 9 | ```xml 10 | <f:DynamicPageTitle> 11 | <f:heading> 12 | <vm:VariantManagement id="variants" for="filterbar, table"/> 13 | </f:heading> 14 | </f:DynamicPageTitle> 15 | ``` 16 | Try it and create your favorite variant! How easy was that? 😎 The following image shows a custom variant named "Very High Mountains" that adds a FilterField for the `Height` property. 17 | 18 | ![Exercise 5 Result](u1/ex5.png) 19 | 20 | >ℹ️ For this tutorial we use a local storage persistency, which does not protect data by default and is not meant for productive use. On BTP, SAP S/4 HANA or ABAP platform we recommend SAPUI5 flexibility services. For more information, see [Features and Availability](https://help.sap.com/docs/UI5_FLEXIBILITY/430e2c1a4ff241bc8162df4bf51e0730/41ada93054994698ab9067855bb85fe1.html). 21 | ## Conclusion 22 | Bravo! We've successfully built a fully customizable list report application using sap.ui.mdc. We've learned how to manipulate the controls with the relevant delegates and discovered methods to customize and extend them. Armed with this toolkit, we're now equipped to develop metadata-driven applications for any UI5 model and protocol! Congratulations! 🎉 -------------------------------------------------------------------------------- /u1/ex2/webapp/view/fragment/NameValueHelp.fragment.xml: -------------------------------------------------------------------------------- 1 | <core:FragmentDefinition 2 | xmlns="sap.m" 3 | xmlns:core="sap.ui.core" 4 | xmlns:mdc="sap.ui.mdc" 5 | xmlns:vh="sap.ui.mdc.valuehelp" 6 | xmlns:vhc="sap.ui.mdc.valuehelp.content" 7 | xmlns:vhfb="sap.ui.mdc.filterbar.vh"> 8 | 9 | <mdc:ValueHelp id="name-vh" delegate="{name: 'sap/ui/mdc/ValueHelpDelegate', payload: {}}"> 10 | <mdc:typeahead> 11 | <vh:Popover title="Name"> 12 | <vhc:MTable keyPath="name" filterFields="*name*"> 13 | <Table id="name-vht-table" items='{path : "mountains>/mountains", length: 10}' 14 | width="30rem"> 15 | <columns> 16 | <Column> 17 | <header> 18 | <Text text="Name" /> 19 | </header> 20 | </Column> 21 | </columns> 22 | <items> 23 | <ColumnListItem type="Active"> 24 | <cells> 25 | <Text text="{mountains>name}" /> 26 | </cells> 27 | </ColumnListItem> 28 | </items> 29 | </Table> 30 | </vhc:MTable> 31 | </vh:Popover> 32 | </mdc:typeahead> 33 | <mdc:dialog> 34 | <vh:Dialog title="Name"> 35 | <vhc:MDCTable keyPath="name"> 36 | <vhc:filterBar> 37 | <vhfb:FilterBar id="name-vhd-fb" delegate="{name: 'mdc/tutorial/delegate/JSONFilterBarDelegate'}"> 38 | <vhfb:basicSearchField> 39 | <mdc:FilterField delegate="{name: 'sap/ui/mdc/field/FieldBaseDelegate'}" 40 | dataType="sap.ui.model.type.String" 41 | placeholder= "Search Mountains" 42 | conditions="{$filters>/conditions/$search}" 43 | maxConditions="1"/> 44 | </vhfb:basicSearchField> 45 | </vhfb:FilterBar> 46 | </vhc:filterBar> 47 | <mdc:Table id="name-vhd-table" 48 | type="ResponsiveTable" 49 | selectionMode="Multi" 50 | delegate="{ 51 | name: 'mdc/tutorial/delegate/JSONTableDelegate', 52 | payload: { 53 | bindingPath: 'mountains>/mountains' 54 | } 55 | }" 56 | filter="name-vhd-fb"> 57 | <mdc:columns> 58 | <mdc:table.Column 59 | header="Name" 60 | dataProperty="name"> 61 | <Text text="{mountains>name}"/> 62 | </mdc:table.Column> 63 | </mdc:columns> 64 | </mdc:Table> 65 | </vhc:MDCTable> 66 | <vhc:Conditions label="Name"/> 67 | </vh:Dialog> 68 | </mdc:dialog> 69 | </mdc:ValueHelp> 70 | 71 | </core:FragmentDefinition> -------------------------------------------------------------------------------- /u1/ex3/webapp/view/fragment/NameValueHelp.fragment.xml: -------------------------------------------------------------------------------- 1 | <core:FragmentDefinition 2 | xmlns="sap.m" 3 | xmlns:core="sap.ui.core" 4 | xmlns:mdc="sap.ui.mdc" 5 | xmlns:vh="sap.ui.mdc.valuehelp" 6 | xmlns:vhc="sap.ui.mdc.valuehelp.content" 7 | xmlns:vhfb="sap.ui.mdc.filterbar.vh"> 8 | 9 | <mdc:ValueHelp id="name-vh" delegate="{name: 'sap/ui/mdc/ValueHelpDelegate', payload: {}}"> 10 | <mdc:typeahead> 11 | <vh:Popover title="Name"> 12 | <vhc:MTable keyPath="name" filterFields="*name*"> 13 | <Table id="name-vht-table" items='{path : "mountains>/mountains", length: 10}' 14 | width="30rem"> 15 | <columns> 16 | <Column> 17 | <header> 18 | <Text text="Name" /> 19 | </header> 20 | </Column> 21 | </columns> 22 | <items> 23 | <ColumnListItem type="Active"> 24 | <cells> 25 | <Text text="{mountains>name}" /> 26 | </cells> 27 | </ColumnListItem> 28 | </items> 29 | </Table> 30 | </vhc:MTable> 31 | </vh:Popover> 32 | </mdc:typeahead> 33 | <mdc:dialog> 34 | <vh:Dialog title="Name"> 35 | <vhc:MDCTable keyPath="name"> 36 | <vhc:filterBar> 37 | <vhfb:FilterBar id="name-vhd-fb" delegate="{name: 'mdc/tutorial/delegate/JSONFilterBarDelegate'}"> 38 | <vhfb:basicSearchField> 39 | <mdc:FilterField delegate="{name: 'sap/ui/mdc/field/FieldBaseDelegate'}" 40 | dataType="sap.ui.model.type.String" 41 | placeholder= "Search Mountains" 42 | conditions="{$filters>/conditions/$search}" 43 | maxConditions="1"/> 44 | </vhfb:basicSearchField> 45 | </vhfb:FilterBar> 46 | </vhc:filterBar> 47 | <mdc:Table id="name-vhd-table" 48 | type="ResponsiveTable" 49 | selectionMode="Multi" 50 | delegate="{ 51 | name: 'mdc/tutorial/delegate/JSONTableDelegate', 52 | payload: { 53 | bindingPath: 'mountains>/mountains' 54 | } 55 | }" 56 | filter="name-vhd-fb"> 57 | <mdc:columns> 58 | <mdc:table.Column 59 | header="Name" 60 | dataProperty="name"> 61 | <Text text="{mountains>name}"/> 62 | </mdc:table.Column> 63 | </mdc:columns> 64 | </mdc:Table> 65 | </vhc:MDCTable> 66 | <vhc:Conditions label="Name"/> 67 | </vh:Dialog> 68 | </mdc:dialog> 69 | </mdc:ValueHelp> 70 | 71 | </core:FragmentDefinition> -------------------------------------------------------------------------------- /u1/ex4/webapp/view/fragment/NameValueHelp.fragment.xml: -------------------------------------------------------------------------------- 1 | <core:FragmentDefinition 2 | xmlns="sap.m" 3 | xmlns:core="sap.ui.core" 4 | xmlns:mdc="sap.ui.mdc" 5 | xmlns:vh="sap.ui.mdc.valuehelp" 6 | xmlns:vhc="sap.ui.mdc.valuehelp.content" 7 | xmlns:vhfb="sap.ui.mdc.filterbar.vh"> 8 | 9 | <mdc:ValueHelp id="name-vh" delegate="{name: 'sap/ui/mdc/ValueHelpDelegate', payload: {}}"> 10 | <mdc:typeahead> 11 | <vh:Popover title="Name"> 12 | <vhc:MTable keyPath="name" filterFields="*name*"> 13 | <Table id="name-vht-table" items='{path : "mountains>/mountains", length: 10}' 14 | width="30rem"> 15 | <columns> 16 | <Column> 17 | <header> 18 | <Text text="Name" /> 19 | </header> 20 | </Column> 21 | </columns> 22 | <items> 23 | <ColumnListItem type="Active"> 24 | <cells> 25 | <Text text="{mountains>name}" /> 26 | </cells> 27 | </ColumnListItem> 28 | </items> 29 | </Table> 30 | </vhc:MTable> 31 | </vh:Popover> 32 | </mdc:typeahead> 33 | <mdc:dialog> 34 | <vh:Dialog title="Name"> 35 | <vhc:MDCTable keyPath="name"> 36 | <vhc:filterBar> 37 | <vhfb:FilterBar id="name-vhd-fb" delegate="{name: 'mdc/tutorial/delegate/JSONFilterBarDelegate'}"> 38 | <vhfb:basicSearchField> 39 | <mdc:FilterField delegate="{name: 'sap/ui/mdc/field/FieldBaseDelegate'}" 40 | dataType="sap.ui.model.type.String" 41 | placeholder= "Search Mountains" 42 | conditions="{$filters>/conditions/$search}" 43 | maxConditions="1"/> 44 | </vhfb:basicSearchField> 45 | </vhfb:FilterBar> 46 | </vhc:filterBar> 47 | <mdc:Table id="name-vhd-table" 48 | type="ResponsiveTable" 49 | selectionMode="Multi" 50 | delegate="{ 51 | name: 'mdc/tutorial/delegate/JSONTableDelegate', 52 | payload: { 53 | bindingPath: 'mountains>/mountains' 54 | } 55 | }" 56 | filter="name-vhd-fb"> 57 | <mdc:columns> 58 | <mdc:table.Column 59 | header="Name" 60 | dataProperty="name"> 61 | <Text text="{mountains>name}"/> 62 | </mdc:table.Column> 63 | </mdc:columns> 64 | </mdc:Table> 65 | </vhc:MDCTable> 66 | <vhc:Conditions label="Name"/> 67 | </vh:Dialog> 68 | </mdc:dialog> 69 | </mdc:ValueHelp> 70 | 71 | </core:FragmentDefinition> -------------------------------------------------------------------------------- /u1/ex5/webapp/view/fragment/NameValueHelp.fragment.xml: -------------------------------------------------------------------------------- 1 | <core:FragmentDefinition 2 | xmlns="sap.m" 3 | xmlns:core="sap.ui.core" 4 | xmlns:mdc="sap.ui.mdc" 5 | xmlns:vh="sap.ui.mdc.valuehelp" 6 | xmlns:vhc="sap.ui.mdc.valuehelp.content" 7 | xmlns:vhfb="sap.ui.mdc.filterbar.vh"> 8 | 9 | <mdc:ValueHelp id="name-vh" delegate="{name: 'sap/ui/mdc/ValueHelpDelegate', payload: {}}"> 10 | <mdc:typeahead> 11 | <vh:Popover title="Name"> 12 | <vhc:MTable keyPath="name" filterFields="*name*"> 13 | <Table id="name-vht-table" items='{path : "mountains>/mountains", length: 10}' 14 | width="30rem"> 15 | <columns> 16 | <Column> 17 | <header> 18 | <Text text="Name" /> 19 | </header> 20 | </Column> 21 | </columns> 22 | <items> 23 | <ColumnListItem type="Active"> 24 | <cells> 25 | <Text text="{mountains>name}" /> 26 | </cells> 27 | </ColumnListItem> 28 | </items> 29 | </Table> 30 | </vhc:MTable> 31 | </vh:Popover> 32 | </mdc:typeahead> 33 | <mdc:dialog> 34 | <vh:Dialog title="Name"> 35 | <vhc:MDCTable keyPath="name"> 36 | <vhc:filterBar> 37 | <vhfb:FilterBar id="name-vhd-fb" delegate="{name: 'mdc/tutorial/delegate/JSONFilterBarDelegate'}"> 38 | <vhfb:basicSearchField> 39 | <mdc:FilterField delegate="{name: 'sap/ui/mdc/field/FieldBaseDelegate'}" 40 | dataType="sap.ui.model.type.String" 41 | placeholder= "Search Mountains" 42 | conditions="{$filters>/conditions/$search}" 43 | maxConditions="1"/> 44 | </vhfb:basicSearchField> 45 | </vhfb:FilterBar> 46 | </vhc:filterBar> 47 | <mdc:Table id="name-vhd-table" 48 | type="ResponsiveTable" 49 | selectionMode="Multi" 50 | delegate="{ 51 | name: 'mdc/tutorial/delegate/JSONTableDelegate', 52 | payload: { 53 | bindingPath: 'mountains>/mountains' 54 | } 55 | }" 56 | filter="name-vhd-fb"> 57 | <mdc:columns> 58 | <mdc:table.Column 59 | header="Name" 60 | dataProperty="name"> 61 | <Text text="{mountains>name}"/> 62 | </mdc:table.Column> 63 | </mdc:columns> 64 | </mdc:Table> 65 | </vhc:MDCTable> 66 | <vhc:Conditions label="Name"/> 67 | </vh:Dialog> 68 | </mdc:dialog> 69 | </mdc:ValueHelp> 70 | 71 | </core:FragmentDefinition> -------------------------------------------------------------------------------- /u1/ex3/webapp/delegate/JSONFilterBarDelegate.ts: -------------------------------------------------------------------------------- 1 | import FilterBarDelegate from "sap/ui/mdc/FilterBarDelegate" 2 | import JSONPropertyInfo from "mdc/tutorial/model/metadata/JSONPropertyInfo" 3 | import FilterField from "sap/ui/mdc/FilterField" 4 | import Element from "sap/ui/core/Element" 5 | import {default as FilterBar, PropertyInfo as FilterBarPropertyInfo} from "sap/ui/mdc/FilterBar" 6 | import Fragment from "sap/ui/core/Fragment" 7 | import ValueHelp from "sap/ui/mdc/ValueHelp" 8 | 9 | interface FilterBarPayload { 10 | valueHelp: { 11 | [key:string]: string 12 | } 13 | } 14 | 15 | var JSONFilterBarDelegate = Object.assign({}, FilterBarDelegate) 16 | 17 | JSONFilterBarDelegate.fetchProperties = async () => JSONPropertyInfo 18 | 19 | const _createValueHelp = async (filterBar:FilterBar, propertyKey:string) => { 20 | const path = "mdc.tutorial.view.fragment." 21 | const valueHelp = await Fragment.load({ 22 | name: path + (filterBar.getPayload() as FilterBarPayload).valueHelp[propertyKey] 23 | }) as unknown as ValueHelp 24 | filterBar.addDependent(valueHelp) 25 | return valueHelp 26 | } 27 | 28 | const _createFilterField = async (id:string, property:FilterBarPropertyInfo, filterBar:FilterBar) => { 29 | const propertyKey = property.key 30 | const filterField = new FilterField(id, { 31 | dataType: property.dataType, 32 | conditions: `{$filters>/conditions/${propertyKey}}`, 33 | propertyKey: propertyKey, 34 | required: property.required, 35 | label: property.label, 36 | maxConditions: property.maxConditions, 37 | delegate: {name: "sap/ui/mdc/field/FieldBaseDelegate", payload: {}} 38 | }) 39 | if ((filterBar.getPayload() as FilterBarPayload).valueHelp[propertyKey] ) { 40 | const dependents = filterBar.getDependents() 41 | let valueHelp = dependents.find((dependent) => dependent.getId().includes(propertyKey)) as ValueHelp 42 | valueHelp ??= await _createValueHelp(filterBar, propertyKey) 43 | filterField.setValueHelp(valueHelp) 44 | } 45 | return filterField 46 | } 47 | 48 | JSONFilterBarDelegate.addItem = async (filterBar:FilterBar, propertyKey:string) => { 49 | const property = JSONPropertyInfo.find((p) => p.key === propertyKey) as FilterBarPropertyInfo 50 | const id = `${filterBar.getId()}--filter--${propertyKey}` 51 | const filterField = Element.getElementById(id) as FilterField 52 | return filterField ?? _createFilterField(id, property, filterBar) 53 | } 54 | 55 | export default JSONFilterBarDelegate -------------------------------------------------------------------------------- /u1/ex2/webapp/delegate/JSONTableDelegate.ts: -------------------------------------------------------------------------------- 1 | import TableDelegate from "sap/ui/mdc/TableDelegate" 2 | import Text from "sap/m/Text" 3 | import Element from "sap/ui/core/Element" 4 | import JSONPropertyInfo from "mdc/tutorial/model/metadata/JSONPropertyInfo" 5 | import {default as Table, PropertyInfo as TablePropertyInfo} from "sap/ui/mdc/Table" 6 | import Column from "sap/ui/mdc/table/Column" 7 | import Filter from "sap/ui/model/Filter" 8 | import FilterOperator from "sap/ui/model/FilterOperator" 9 | import FilterBar from "sap/ui/mdc/FilterBar" 10 | interface TablePayload { 11 | bindingPath: string 12 | searchKeys: string[] 13 | } 14 | 15 | const JSONTableDelegate = Object.assign({}, TableDelegate) 16 | 17 | JSONTableDelegate.fetchProperties = async () => { 18 | return JSONPropertyInfo.filter((p) => p.key !== "$search") 19 | } 20 | 21 | const _createColumn = (propertyInfo:TablePropertyInfo, table:Table) => { 22 | const name = propertyInfo.key 23 | const id = table.getId() + "---col-" + name 24 | const column = Element.getElementById(id) as Column 25 | return column ?? new Column(id, { 26 | propertyKey: name, 27 | header: propertyInfo.label, 28 | template: new Text({ 29 | text: { 30 | path: "mountains>" + name, 31 | type: propertyInfo.dataType 32 | } 33 | }) 34 | }) 35 | } 36 | 37 | JSONTableDelegate.addItem = async (table:Table, propertyKey:string) => { 38 | const propertyInfo = JSONPropertyInfo.find((p) => p.key === propertyKey) 39 | return _createColumn(propertyInfo, table) 40 | } 41 | 42 | JSONTableDelegate.updateBindingInfo = (table, bindingInfo) => { 43 | TableDelegate.updateBindingInfo.call(JSONTableDelegate, table, bindingInfo) 44 | bindingInfo.path = (table.getPayload() as TablePayload).bindingPath 45 | bindingInfo.templateShareable = true 46 | } 47 | 48 | const _createSearchFilters = (search:string, keys:string[]) => { 49 | const filters = keys.map((key) => new Filter({ 50 | path: key, 51 | operator: FilterOperator.Contains, 52 | value1: search 53 | })) 54 | return [new Filter(filters, false)] 55 | } 56 | 57 | JSONTableDelegate.getFilters = (table) => { 58 | const search = (Element.getElementById(table.getFilter()) as FilterBar).getSearch() 59 | const keys = (table.getPayload() as TablePayload).searchKeys 60 | let filters = TableDelegate.getFilters(table) 61 | if (search && keys) { 62 | filters = filters.concat(_createSearchFilters(search, keys)) 63 | } 64 | return filters 65 | } 66 | 67 | export default JSONTableDelegate -------------------------------------------------------------------------------- /u1/ex3/webapp/delegate/JSONTableDelegate.ts: -------------------------------------------------------------------------------- 1 | import TableDelegate from "sap/ui/mdc/TableDelegate" 2 | import Text from "sap/m/Text" 3 | import Element from "sap/ui/core/Element" 4 | import JSONPropertyInfo from "mdc/tutorial/model/metadata/JSONPropertyInfo" 5 | import {default as Table, PropertyInfo as TablePropertyInfo} from "sap/ui/mdc/Table" 6 | import Column from "sap/ui/mdc/table/Column" 7 | import Filter from "sap/ui/model/Filter" 8 | import FilterOperator from "sap/ui/model/FilterOperator" 9 | import FilterBar from "sap/ui/mdc/FilterBar" 10 | interface TablePayload { 11 | bindingPath: string 12 | searchKeys: string[] 13 | } 14 | 15 | const JSONTableDelegate = Object.assign({}, TableDelegate) 16 | 17 | JSONTableDelegate.fetchProperties = async () => { 18 | return JSONPropertyInfo.filter((p) => p.key !== "$search") 19 | } 20 | 21 | const _createColumn = (propertyInfo:TablePropertyInfo, table:Table) => { 22 | const name = propertyInfo.key 23 | const id = table.getId() + "---col-" + name 24 | const column = Element.getElementById(id) as Column 25 | return column ?? new Column(id, { 26 | propertyKey: name, 27 | header: propertyInfo.label, 28 | template: new Text({ 29 | text: { 30 | path: "mountains>" + name, 31 | type: propertyInfo.dataType 32 | } 33 | }) 34 | }) 35 | } 36 | 37 | JSONTableDelegate.addItem = async (table:Table, propertyKey:string) => { 38 | const propertyInfo = JSONPropertyInfo.find((p) => p.key === propertyKey) 39 | return _createColumn(propertyInfo, table) 40 | } 41 | 42 | JSONTableDelegate.updateBindingInfo = (table, bindingInfo) => { 43 | TableDelegate.updateBindingInfo.call(JSONTableDelegate, table, bindingInfo) 44 | bindingInfo.path = (table.getPayload() as TablePayload).bindingPath 45 | bindingInfo.templateShareable = true 46 | } 47 | 48 | const _createSearchFilters = (search:string, keys:string[]) => { 49 | const filters = keys.map((key) => new Filter({ 50 | path: key, 51 | operator: FilterOperator.Contains, 52 | value1: search 53 | })) 54 | return [new Filter(filters, false)] 55 | } 56 | 57 | JSONTableDelegate.getFilters = (table) => { 58 | const search = (Element.getElementById(table.getFilter()) as FilterBar).getSearch() 59 | const keys = (table.getPayload() as TablePayload).searchKeys 60 | let filters = TableDelegate.getFilters(table) 61 | if (search && keys) { 62 | filters = filters.concat(_createSearchFilters(search, keys)) 63 | } 64 | return filters 65 | } 66 | 67 | export default JSONTableDelegate -------------------------------------------------------------------------------- /u1/ex4/webapp/delegate/JSONFilterBarDelegate.ts: -------------------------------------------------------------------------------- 1 | import FilterBarDelegate from "sap/ui/mdc/FilterBarDelegate" 2 | import JSONPropertyInfo from "mdc/tutorial/model/metadata/JSONPropertyInfo" 3 | import FilterField from "sap/ui/mdc/FilterField" 4 | import Element from "sap/ui/core/Element" 5 | import {default as FilterBar, PropertyInfo as FilterBarPropertyInfo} from "sap/ui/mdc/FilterBar" 6 | import Fragment from "sap/ui/core/Fragment" 7 | import ValueHelp from "sap/ui/mdc/ValueHelp" 8 | import JSONBaseDelegate from "./JSONBaseDelegate" 9 | 10 | interface FilterBarPayload { 11 | valueHelp: { 12 | [key:string]: string 13 | } 14 | } 15 | 16 | var JSONFilterBarDelegate = Object.assign({}, FilterBarDelegate, JSONBaseDelegate) 17 | 18 | JSONFilterBarDelegate.fetchProperties = async () => JSONPropertyInfo 19 | 20 | const _createValueHelp = async (filterBar:FilterBar, propertyKey:string) => { 21 | const path = "mdc.tutorial.view.fragment." 22 | const valueHelp = await Fragment.load({ 23 | name: path + (filterBar.getPayload() as FilterBarPayload).valueHelp[propertyKey] 24 | }) as unknown as ValueHelp 25 | filterBar.addDependent(valueHelp) 26 | return valueHelp 27 | } 28 | 29 | const _createFilterField = async (id:string, property:FilterBarPropertyInfo, filterBar:FilterBar) => { 30 | const propertyKey = property.key 31 | const filterField = new FilterField(id, { 32 | dataType: property.dataType, 33 | conditions: `{$filters>/conditions/${propertyKey}}`, 34 | propertyKey: propertyKey, 35 | required: property.required, 36 | label: property.label, 37 | maxConditions: property.maxConditions, 38 | delegate: {name: "sap/ui/mdc/field/FieldBaseDelegate", payload: {}} 39 | }) 40 | if ((filterBar.getPayload() as FilterBarPayload).valueHelp[propertyKey] ) { 41 | const dependents = filterBar.getDependents() 42 | let valueHelp = dependents.find((dependent) => dependent.getId().includes(propertyKey)) as ValueHelp 43 | valueHelp ??= await _createValueHelp(filterBar, propertyKey) 44 | filterField.setValueHelp(valueHelp) 45 | } 46 | return filterField 47 | } 48 | 49 | JSONFilterBarDelegate.addItem = async (filterBar:FilterBar, propertyKey:string) => { 50 | const property = JSONPropertyInfo.find((p) => p.key === propertyKey) as FilterBarPropertyInfo 51 | const id = `${filterBar.getId()}--filter--${propertyKey}` 52 | const filterField = Element.getElementById(id) as FilterField 53 | return filterField ?? _createFilterField(id, property, filterBar) 54 | } 55 | 56 | export default JSONFilterBarDelegate -------------------------------------------------------------------------------- /u1/ex5/webapp/delegate/JSONFilterBarDelegate.ts: -------------------------------------------------------------------------------- 1 | import FilterBarDelegate from "sap/ui/mdc/FilterBarDelegate" 2 | import JSONPropertyInfo from "mdc/tutorial/model/metadata/JSONPropertyInfo" 3 | import FilterField from "sap/ui/mdc/FilterField" 4 | import Element from "sap/ui/core/Element" 5 | import {default as FilterBar, PropertyInfo as FilterBarPropertyInfo} from "sap/ui/mdc/FilterBar" 6 | import Fragment from "sap/ui/core/Fragment" 7 | import ValueHelp from "sap/ui/mdc/ValueHelp" 8 | import JSONBaseDelegate from "./JSONBaseDelegate" 9 | 10 | interface FilterBarPayload { 11 | valueHelp: { 12 | [key:string]: string 13 | } 14 | } 15 | 16 | var JSONFilterBarDelegate = Object.assign({}, FilterBarDelegate, JSONBaseDelegate) 17 | 18 | JSONFilterBarDelegate.fetchProperties = async () => JSONPropertyInfo 19 | 20 | const _createValueHelp = async (filterBar:FilterBar, propertyKey:string) => { 21 | const path = "mdc.tutorial.view.fragment." 22 | const valueHelp = await Fragment.load({ 23 | name: path + (filterBar.getPayload() as FilterBarPayload).valueHelp[propertyKey] 24 | }) as unknown as ValueHelp 25 | filterBar.addDependent(valueHelp) 26 | return valueHelp 27 | } 28 | 29 | const _createFilterField = async (id:string, property:FilterBarPropertyInfo, filterBar:FilterBar) => { 30 | const propertyKey = property.key 31 | const filterField = new FilterField(id, { 32 | dataType: property.dataType, 33 | conditions: `{$filters>/conditions/${propertyKey}}`, 34 | propertyKey: propertyKey, 35 | required: property.required, 36 | label: property.label, 37 | maxConditions: property.maxConditions, 38 | delegate: {name: "sap/ui/mdc/field/FieldBaseDelegate", payload: {}} 39 | }) 40 | if ((filterBar.getPayload() as FilterBarPayload).valueHelp[propertyKey] ) { 41 | const dependents = filterBar.getDependents() 42 | let valueHelp = dependents.find((dependent) => dependent.getId().includes(propertyKey)) as ValueHelp 43 | valueHelp ??= await _createValueHelp(filterBar, propertyKey) 44 | filterField.setValueHelp(valueHelp) 45 | } 46 | return filterField 47 | } 48 | 49 | JSONFilterBarDelegate.addItem = async (filterBar:FilterBar, propertyKey:string) => { 50 | const property = JSONPropertyInfo.find((p) => p.key === propertyKey) as FilterBarPropertyInfo 51 | const id = `${filterBar.getId()}--filter--${propertyKey}` 52 | const filterField = Element.getElementById(id) as FilterField 53 | return filterField ?? _createFilterField(id, property, filterBar) 54 | } 55 | 56 | export default JSONFilterBarDelegate -------------------------------------------------------------------------------- /u1/ex4/webapp/delegate/JSONTableDelegate.ts: -------------------------------------------------------------------------------- 1 | import TableDelegate from "sap/ui/mdc/TableDelegate" 2 | import Text from "sap/m/Text" 3 | import Element from "sap/ui/core/Element" 4 | import JSONPropertyInfo from "mdc/tutorial/model/metadata/JSONPropertyInfo" 5 | import {default as Table, PropertyInfo as TablePropertyInfo} from "sap/ui/mdc/Table" 6 | import Column from "sap/ui/mdc/table/Column" 7 | import Filter from "sap/ui/model/Filter" 8 | import FilterOperator from "sap/ui/model/FilterOperator" 9 | import FilterBar from "sap/ui/mdc/FilterBar" 10 | import JSONBaseDelegate from "./JSONBaseDelegate" 11 | 12 | interface TablePayload { 13 | bindingPath: string 14 | searchKeys: string[] 15 | } 16 | 17 | const JSONTableDelegate = Object.assign({}, TableDelegate, JSONBaseDelegate) 18 | 19 | JSONTableDelegate.fetchProperties = async () => { 20 | return JSONPropertyInfo.filter((p) => p.key !== "$search") 21 | } 22 | 23 | const _createColumn = (propertyInfo:TablePropertyInfo, table:Table) => { 24 | const name = propertyInfo.key 25 | const id = table.getId() + "---col-" + name 26 | const column = Element.getElementById(id) as Column 27 | return column ?? new Column(id, { 28 | propertyKey: name, 29 | header: propertyInfo.label, 30 | template: new Text({ 31 | text: { 32 | path: "mountains>" + name, 33 | type: propertyInfo.dataType 34 | } 35 | }) 36 | }) 37 | } 38 | 39 | JSONTableDelegate.addItem = async (table:Table, propertyKey:string) => { 40 | const propertyInfo = JSONPropertyInfo.find((p) => p.key === propertyKey) 41 | return _createColumn(propertyInfo, table) 42 | } 43 | 44 | JSONTableDelegate.updateBindingInfo = (table, bindingInfo) => { 45 | TableDelegate.updateBindingInfo.call(JSONTableDelegate, table, bindingInfo) 46 | bindingInfo.path = (table.getPayload() as TablePayload).bindingPath 47 | bindingInfo.templateShareable = true 48 | } 49 | 50 | const _createSearchFilters = (search:string, keys:string[]) => { 51 | const filters = keys.map((key) => new Filter({ 52 | path: key, 53 | operator: FilterOperator.Contains, 54 | value1: search 55 | })) 56 | return [new Filter(filters, false)] 57 | } 58 | 59 | JSONTableDelegate.getFilters = (table) => { 60 | const search = (Element.getElementById(table.getFilter()) as FilterBar).getSearch() 61 | const keys = (table.getPayload() as TablePayload).searchKeys 62 | let filters = TableDelegate.getFilters(table) 63 | if (search && keys) { 64 | filters = filters.concat(_createSearchFilters(search, keys)) 65 | } 66 | return filters 67 | } 68 | 69 | export default JSONTableDelegate -------------------------------------------------------------------------------- /u1/ex5/webapp/delegate/JSONTableDelegate.ts: -------------------------------------------------------------------------------- 1 | import TableDelegate from "sap/ui/mdc/TableDelegate" 2 | import Text from "sap/m/Text" 3 | import Element from "sap/ui/core/Element" 4 | import JSONPropertyInfo from "mdc/tutorial/model/metadata/JSONPropertyInfo" 5 | import {default as Table, PropertyInfo as TablePropertyInfo} from "sap/ui/mdc/Table" 6 | import Column from "sap/ui/mdc/table/Column" 7 | import Filter from "sap/ui/model/Filter" 8 | import FilterOperator from "sap/ui/model/FilterOperator" 9 | import FilterBar from "sap/ui/mdc/FilterBar" 10 | import JSONBaseDelegate from "./JSONBaseDelegate" 11 | 12 | interface TablePayload { 13 | bindingPath: string 14 | searchKeys: string[] 15 | } 16 | 17 | const JSONTableDelegate = Object.assign({}, TableDelegate, JSONBaseDelegate) 18 | 19 | JSONTableDelegate.fetchProperties = async () => { 20 | return JSONPropertyInfo.filter((p) => p.key !== "$search") 21 | } 22 | 23 | const _createColumn = (propertyInfo:TablePropertyInfo, table:Table) => { 24 | const name = propertyInfo.key 25 | const id = table.getId() + "---col-" + name 26 | const column = Element.getElementById(id) as Column 27 | return column ?? new Column(id, { 28 | propertyKey: name, 29 | header: propertyInfo.label, 30 | template: new Text({ 31 | text: { 32 | path: "mountains>" + name, 33 | type: propertyInfo.dataType 34 | } 35 | }) 36 | }) 37 | } 38 | 39 | JSONTableDelegate.addItem = async (table:Table, propertyKey:string) => { 40 | const propertyInfo = JSONPropertyInfo.find((p) => p.key === propertyKey) 41 | return _createColumn(propertyInfo, table) 42 | } 43 | 44 | JSONTableDelegate.updateBindingInfo = (table, bindingInfo) => { 45 | TableDelegate.updateBindingInfo.call(JSONTableDelegate, table, bindingInfo) 46 | bindingInfo.path = (table.getPayload() as TablePayload).bindingPath 47 | bindingInfo.templateShareable = true 48 | } 49 | 50 | const _createSearchFilters = (search:string, keys:string[]) => { 51 | const filters = keys.map((key) => new Filter({ 52 | path: key, 53 | operator: FilterOperator.Contains, 54 | value1: search 55 | })) 56 | return [new Filter(filters, false)] 57 | } 58 | 59 | JSONTableDelegate.getFilters = (table) => { 60 | const search = (Element.getElementById(table.getFilter()) as FilterBar).getSearch() 61 | const keys = (table.getPayload() as TablePayload).searchKeys 62 | let filters = TableDelegate.getFilters(table) 63 | if (search && keys) { 64 | filters = filters.concat(_createSearchFilters(search, keys)) 65 | } 66 | return filters 67 | } 68 | 69 | export default JSONTableDelegate -------------------------------------------------------------------------------- /u1/ex0/readme.md: -------------------------------------------------------------------------------- 1 | [![solution](https://flat.badgen.net/badge/solution/available/green?icon=github)](webapp) 2 | [![demo](https://flat.badgen.net/badge/demo/deployed/blue?icon=github)](https://sap-samples.github.io/ui5-mdc-json-tutorial/u1/ex0/dist) 3 | # Exercise 0: Setup the Project on Your Machine 4 | 5 | This exercise will guide you through the process of setting up a development environment. We will install Node.js, download and extract a zip file for a project, install project dependencies using npm, and serve the project using the UI5 tooling. 6 | 7 | ## Step 1: Install Node.js 8 | 9 | If it is not already available, the first step is to download and install it from https://nodejs.org/. 10 | 11 | We can confirm the installation by opening our terminal or command prompt and typing: 12 | 13 | ```bash 14 | node -v 15 | ``` 16 | 17 | This command should display the installed version of Node.js. 18 | 19 | ## Step 2: Download and Extract Project Files 20 | 21 | Next, we will download the project files contained in a .zip file. 22 | - Download the project files from [mdc.tutorial.zip](https://sap-samples.github.io/ui5-mdc-json-tutorial/mdc.tutorial.zip). 23 | - Once the download is complete, navigate to the download location and extract the .zip file. 24 | 25 | ## Step 3: Open the Project Folder 26 | 27 | Now, we will open the project folder in our code editor. 28 | 29 | - Navigate to the extracted project folder. 30 | - Open the folder in our preferred code editor. 31 | 32 | The structure should look like this: 33 | 34 | ![Alt text](u1/ex0_folder.png) 35 | 36 | ## Step 4: Install Project Dependencies 37 | 38 | Once the project is open in our code editor, we will install the project dependencies using npm, the Node Package Manager, which was installed alongside Node.js. 39 | 40 | - Open a terminal or command prompt in our project's root directory. 41 | - Run the following command: 42 | 43 | ```bash 44 | npm install 45 | ``` 46 | 47 | This command will read the `package.json` file in our project and install the necessary dependencies. 48 | 49 | ## Step 5: Serve the Project 50 | 51 | Finally, we will serve the project using ui5. 52 | 53 | - In the terminal or command prompt at our project's root directory, run the following command: 54 | 55 | ```bash 56 | npm start 57 | ``` 58 | 59 | This command will start a development server for our project. You can access the development server at http://localhost:8080/index.html and check out the running application! 🚀 60 | 61 | ![Exercise 0 Result](u1/ex0.png) 62 | 63 | ## Summary 64 | Congratulations, we have successfully set up our development environment and served our project! 65 | 66 | Continue to - [Exercise 1](../u1/ex1/readme.md) 67 | -------------------------------------------------------------------------------- /u1/ex2/webapp/view/Mountains.view.xml: -------------------------------------------------------------------------------- 1 | <mvc:View 2 | height="100%" 3 | displayBlock="true" 4 | xmlns="sap.m" 5 | xmlns:mvc="sap.ui.core.mvc" 6 | xmlns:f="sap.f" 7 | xmlns:core="sap.ui.core" 8 | xmlns:mdc="sap.ui.mdc" 9 | xmlns:mdct="sap.ui.mdc.table" 10 | xmlns:vm="sap.ui.fl.variants"> 11 | 12 | <f:DynamicPage id="page"> 13 | <f:title> 14 | <f:DynamicPageTitle> 15 | <f:heading> 16 | <Title text="Mountains"/> 17 | </f:heading> 18 | </f:DynamicPageTitle> 19 | </f:title> 20 | <f:header> 21 | <f:DynamicPageHeader pinnable="true"> 22 | <mdc:FilterBar id="filterbar" delegate="{name: 'mdc/tutorial/delegate/JSONFilterBarDelegate'}" 23 | p13nMode = "Item,Value"> 24 | <mdc:basicSearchField> 25 | <mdc:FilterField delegate="{name: 'sap/ui/mdc/field/FieldBaseDelegate'}" 26 | dataType="sap.ui.model.type.String" 27 | placeholder= "Search Mountains" 28 | conditions="{$filters>/conditions/$search}" 29 | maxConditions="1"/> 30 | </mdc:basicSearchField> 31 | <mdc:filterItems> 32 | <mdc:FilterField 33 | label="Name" 34 | propertyKey="name" 35 | dataType="sap.ui.model.type.String" 36 | conditions="{$filters>/conditions/name}" 37 | delegate="{name: 'sap/ui/mdc/field/FieldBaseDelegate'}"/> 38 | </mdc:filterItems> 39 | <mdc:dependents> 40 | 41 | </mdc:dependents> 42 | </mdc:FilterBar> 43 | </f:DynamicPageHeader> 44 | </f:header> 45 | <f:content> 46 | <mdc:Table 47 | id="table" 48 | header="Mountains" 49 | p13nMode="Sort,Column" 50 | type="ResponsiveTable" 51 | threshold="100" 52 | filter="filterbar" 53 | showRowCount="false" 54 | delegate="{ 55 | name: 'mdc/tutorial/delegate/JSONTableDelegate', 56 | payload: { 57 | bindingPath: 'mountains>/mountains', 58 | searchKeys: ['name', 'range', 'parent_mountain', 'countries'] 59 | } 60 | }"> 61 | <mdct:Column 62 | propertyKey="name" 63 | header="Name"> 64 | <Text text="{mountains>name}"/> 65 | </mdct:Column> 66 | <mdct:Column 67 | propertyKey="height" 68 | header="Height"> 69 | <Text text="{path: 'mountains>height'}"/> 70 | </mdct:Column> 71 | <mdct:Column 72 | propertyKey="range" 73 | header="Range"> 74 | <Text text="{mountains>range}"/> 75 | </mdct:Column> 76 | <mdct:Column 77 | propertyKey="first_ascent" 78 | header="First Ascent"> 79 | <Text text="{mountains>first_ascent}"/> 80 | </mdct:Column> 81 | <mdct:Column 82 | propertyKey="countries" 83 | header="Countries"> 84 | <Text text="{mountains>countries}"/> 85 | </mdct:Column> 86 | <mdct:Column 87 | propertyKey="parent_mountain" 88 | header="Parent Mountain"> 89 | <Text text="{mountains>parent_mountain}"/> 90 | </mdct:Column> 91 | </mdc:Table> 92 | </f:content> 93 | </f:DynamicPage> 94 | 95 | </mvc:View> -------------------------------------------------------------------------------- /u1/ex3/webapp/view/Mountains.view.xml: -------------------------------------------------------------------------------- 1 | <mvc:View 2 | height="100%" 3 | displayBlock="true" 4 | xmlns="sap.m" 5 | xmlns:mvc="sap.ui.core.mvc" 6 | xmlns:f="sap.f" 7 | xmlns:core="sap.ui.core" 8 | xmlns:mdc="sap.ui.mdc" 9 | xmlns:mdct="sap.ui.mdc.table" 10 | xmlns:vm="sap.ui.fl.variants"> 11 | 12 | <f:DynamicPage id="page"> 13 | <f:title> 14 | <f:DynamicPageTitle> 15 | <f:heading> 16 | <Title text="Mountains"/> 17 | </f:heading> 18 | </f:DynamicPageTitle> 19 | </f:title> 20 | <f:header> 21 | <f:DynamicPageHeader pinnable="true"> 22 | <mdc:FilterBar id="filterbar" delegate="{ 23 | name: 'mdc/tutorial/delegate/JSONFilterBarDelegate', 24 | payload: { 25 | valueHelp: { 26 | name: 'NameValueHelp', 27 | range: 'RangeValueHelp' 28 | } 29 | } 30 | }" 31 | p13nMode = "Item,Value"> 32 | <mdc:basicSearchField> 33 | <mdc:FilterField delegate="{name: 'sap/ui/mdc/field/FieldBaseDelegate'}" 34 | dataType="sap.ui.model.type.String" 35 | placeholder= "Search Mountains" 36 | conditions="{$filters>/conditions/$search}" 37 | maxConditions="1"/> 38 | </mdc:basicSearchField> 39 | <mdc:filterItems> 40 | <mdc:FilterField 41 | label="Name" 42 | propertyKey="name" 43 | dataType="sap.ui.model.type.String" 44 | conditions="{$filters>/conditions/name}" 45 | valueHelp="name-vh" 46 | delegate="{name: 'sap/ui/mdc/field/FieldBaseDelegate'}"/> 47 | </mdc:filterItems> 48 | <mdc:dependents> 49 | <core:Fragment fragmentName="mdc.tutorial.view.fragment.NameValueHelp" type="XML"/> 50 | </mdc:dependents> 51 | </mdc:FilterBar> 52 | </f:DynamicPageHeader> 53 | </f:header> 54 | <f:content> 55 | <mdc:Table 56 | id="table" 57 | header="Mountains" 58 | p13nMode="Sort,Column" 59 | type="ResponsiveTable" 60 | threshold="100" 61 | filter="filterbar" 62 | showRowCount="false" 63 | delegate="{ 64 | name: 'mdc/tutorial/delegate/JSONTableDelegate', 65 | payload: { 66 | bindingPath: 'mountains>/mountains', 67 | searchKeys: ['name', 'range', 'parent_mountain', 'countries'] 68 | } 69 | }"> 70 | <mdct:Column 71 | propertyKey="name" 72 | header="Name"> 73 | <Text text="{mountains>name}"/> 74 | </mdct:Column> 75 | <mdct:Column 76 | propertyKey="height" 77 | header="Height"> 78 | <Text text="{path: 'mountains>height'}"/> 79 | </mdct:Column> 80 | <mdct:Column 81 | propertyKey="range" 82 | header="Range"> 83 | <Text text="{mountains>range}"/> 84 | </mdct:Column> 85 | <mdct:Column 86 | propertyKey="first_ascent" 87 | header="First Ascent"> 88 | <Text text="{mountains>first_ascent}"/> 89 | </mdct:Column> 90 | <mdct:Column 91 | propertyKey="countries" 92 | header="Countries"> 93 | <Text text="{mountains>countries}"/> 94 | </mdct:Column> 95 | <mdct:Column 96 | propertyKey="parent_mountain" 97 | header="Parent Mountain"> 98 | <Text text="{mountains>parent_mountain}"/> 99 | </mdct:Column> 100 | </mdc:Table> 101 | </f:content> 102 | </f:DynamicPage> 103 | 104 | </mvc:View> -------------------------------------------------------------------------------- /u1/ex4/webapp/view/Mountains.view.xml: -------------------------------------------------------------------------------- 1 | <mvc:View 2 | height="100%" 3 | displayBlock="true" 4 | xmlns="sap.m" 5 | xmlns:mvc="sap.ui.core.mvc" 6 | xmlns:f="sap.f" 7 | xmlns:core="sap.ui.core" 8 | xmlns:mdc="sap.ui.mdc" 9 | xmlns:mdct="sap.ui.mdc.table" 10 | xmlns:vm="sap.ui.fl.variants"> 11 | 12 | <f:DynamicPage id="page"> 13 | <f:title> 14 | <f:DynamicPageTitle> 15 | <f:heading> 16 | <Title text="Mountains"/> 17 | </f:heading> 18 | </f:DynamicPageTitle> 19 | </f:title> 20 | <f:header> 21 | <f:DynamicPageHeader pinnable="true"> 22 | <mdc:FilterBar id="filterbar" delegate="{ 23 | name: 'mdc/tutorial/delegate/JSONFilterBarDelegate', 24 | payload: { 25 | valueHelp: { 26 | name: 'NameValueHelp', 27 | range: 'RangeValueHelp' 28 | } 29 | } 30 | }" 31 | p13nMode = "Item,Value"> 32 | <mdc:basicSearchField> 33 | <mdc:FilterField delegate="{name: 'sap/ui/mdc/field/FieldBaseDelegate'}" 34 | dataType="sap.ui.model.type.String" 35 | placeholder= "Search Mountains" 36 | conditions="{$filters>/conditions/$search}" 37 | maxConditions="1"/> 38 | </mdc:basicSearchField> 39 | <mdc:filterItems> 40 | <mdc:FilterField 41 | label="Name" 42 | propertyKey="name" 43 | dataType="sap.ui.model.type.String" 44 | conditions="{$filters>/conditions/name}" 45 | valueHelp="name-vh" 46 | delegate="{name: 'sap/ui/mdc/field/FieldBaseDelegate'}"/> 47 | </mdc:filterItems> 48 | <mdc:dependents> 49 | <core:Fragment fragmentName="mdc.tutorial.view.fragment.NameValueHelp" type="XML"/> 50 | </mdc:dependents> 51 | </mdc:FilterBar> 52 | </f:DynamicPageHeader> 53 | </f:header> 54 | <f:content> 55 | <mdc:Table 56 | id="table" 57 | header="Mountains" 58 | p13nMode="Sort,Column" 59 | type="ResponsiveTable" 60 | threshold="100" 61 | filter="filterbar" 62 | showRowCount="false" 63 | delegate="{ 64 | name: 'mdc/tutorial/delegate/JSONTableDelegate', 65 | payload: { 66 | bindingPath: 'mountains>/mountains', 67 | searchKeys: ['name', 'range', 'parent_mountain', 'countries'] 68 | } 69 | }"> 70 | <mdct:Column 71 | propertyKey="name" 72 | header="Name"> 73 | <Text text="{mountains>name}"/> 74 | </mdct:Column> 75 | <mdct:Column 76 | propertyKey="height" 77 | header="Height"> 78 | <Text text="{path: 'mountains>height' , type: 'mdc.tutorial.model.type.LengthMeter'}"/> 79 | </mdct:Column> 80 | <mdct:Column 81 | propertyKey="range" 82 | header="Range"> 83 | <Text text="{mountains>range}"/> 84 | </mdct:Column> 85 | <mdct:Column 86 | propertyKey="first_ascent" 87 | header="First Ascent"> 88 | <Text text="{mountains>first_ascent}"/> 89 | </mdct:Column> 90 | <mdct:Column 91 | propertyKey="countries" 92 | header="Countries"> 93 | <Text text="{mountains>countries}"/> 94 | </mdct:Column> 95 | <mdct:Column 96 | propertyKey="parent_mountain" 97 | header="Parent Mountain"> 98 | <Text text="{mountains>parent_mountain}"/> 99 | </mdct:Column> 100 | </mdc:Table> 101 | </f:content> 102 | </f:DynamicPage> 103 | 104 | </mvc:View> -------------------------------------------------------------------------------- /u1/ex5/webapp/view/Mountains.view.xml: -------------------------------------------------------------------------------- 1 | <mvc:View 2 | height="100%" 3 | displayBlock="true" 4 | xmlns="sap.m" 5 | xmlns:mvc="sap.ui.core.mvc" 6 | xmlns:f="sap.f" 7 | xmlns:core="sap.ui.core" 8 | xmlns:mdc="sap.ui.mdc" 9 | xmlns:mdct="sap.ui.mdc.table" 10 | xmlns:vm="sap.ui.fl.variants"> 11 | 12 | <f:DynamicPage id="page"> 13 | <f:title> 14 | <f:DynamicPageTitle> 15 | <f:heading> 16 | <vm:VariantManagement id="variants" for="filterbar, table"/> 17 | </f:heading> 18 | </f:DynamicPageTitle> 19 | </f:title> 20 | <f:header> 21 | <f:DynamicPageHeader pinnable="true"> 22 | <mdc:FilterBar id="filterbar" delegate="{ 23 | name: 'mdc/tutorial/delegate/JSONFilterBarDelegate', 24 | payload: { 25 | valueHelp: { 26 | name: 'NameValueHelp', 27 | range: 'RangeValueHelp' 28 | } 29 | } 30 | }" 31 | p13nMode = "Item,Value"> 32 | <mdc:basicSearchField> 33 | <mdc:FilterField delegate="{name: 'sap/ui/mdc/field/FieldBaseDelegate'}" 34 | dataType="sap.ui.model.type.String" 35 | placeholder= "Search Mountains" 36 | conditions="{$filters>/conditions/$search}" 37 | maxConditions="1"/> 38 | </mdc:basicSearchField> 39 | <mdc:filterItems> 40 | <mdc:FilterField 41 | label="Name" 42 | propertyKey="name" 43 | dataType="sap.ui.model.type.String" 44 | conditions="{$filters>/conditions/name}" 45 | valueHelp="name-vh" 46 | delegate="{name: 'sap/ui/mdc/field/FieldBaseDelegate'}"/> 47 | </mdc:filterItems> 48 | <mdc:dependents> 49 | <core:Fragment fragmentName="mdc.tutorial.view.fragment.NameValueHelp" type="XML"/> 50 | </mdc:dependents> 51 | </mdc:FilterBar> 52 | </f:DynamicPageHeader> 53 | </f:header> 54 | <f:content> 55 | <mdc:Table 56 | id="table" 57 | header="Mountains" 58 | p13nMode="Sort,Column" 59 | type="ResponsiveTable" 60 | threshold="100" 61 | filter="filterbar" 62 | showRowCount="false" 63 | delegate="{ 64 | name: 'mdc/tutorial/delegate/JSONTableDelegate', 65 | payload: { 66 | bindingPath: 'mountains>/mountains', 67 | searchKeys: ['name', 'range', 'parent_mountain', 'countries'] 68 | } 69 | }"> 70 | <mdct:Column 71 | propertyKey="name" 72 | header="Name"> 73 | <Text text="{mountains>name}"/> 74 | </mdct:Column> 75 | <mdct:Column 76 | propertyKey="height" 77 | header="Height"> 78 | <Text text="{path: 'mountains>height' , type: 'mdc.tutorial.model.type.LengthMeter'}"/> 79 | </mdct:Column> 80 | <mdct:Column 81 | propertyKey="range" 82 | header="Range"> 83 | <Text text="{mountains>range}"/> 84 | </mdct:Column> 85 | <mdct:Column 86 | propertyKey="first_ascent" 87 | header="First Ascent"> 88 | <Text text="{mountains>first_ascent}"/> 89 | </mdct:Column> 90 | <mdct:Column 91 | propertyKey="countries" 92 | header="Countries"> 93 | <Text text="{mountains>countries}"/> 94 | </mdct:Column> 95 | <mdct:Column 96 | propertyKey="parent_mountain" 97 | header="Parent Mountain"> 98 | <Text text="{mountains>parent_mountain}"/> 99 | </mdct:Column> 100 | </mdc:Table> 101 | </f:content> 102 | </f:DynamicPage> 103 | 104 | </mvc:View> -------------------------------------------------------------------------------- /u1/ex4/readme.md: -------------------------------------------------------------------------------- 1 | [![solution](https://flat.badgen.net/badge/solution/available/green?icon=github)](webapp) 2 | [![demo](https://flat.badgen.net/badge/demo/deployed/blue?icon=github)](https://sap-samples.github.io/ui5-mdc-json-tutorial/u1/ex4/dist) 3 | # Exercise 4: How to Add Custom Types 4 | At this point the application can display the height of the mountains. However, it lacks the corresponding unit, "meters", which would provide a more accurate description. This exercise will illustrate how to add specific types and integrate them with the sap.ui.mdc controls. 5 | 6 | ## Step 1: Add a New Type 7 | Begin by creating a new folder named `type` within the `model` folder of the application. Inside this folder, create a file called `LengthMeter.ts`. While the name might seem unusual, it's derived from the corresponding cldr unit. 8 | ###### model/type/LengthMeter.ts 9 | ```typescript 10 | import Integer from "sap/ui/model/type/Integer" 11 | import NumberFormat from "sap/ui/core/format/NumberFormat" 12 | 13 | export default class LengthMeter extends Integer { 14 | formatValue(height: number) { 15 | const unitFormat = NumberFormat.getUnitInstance() 16 | return unitFormat.format(height, "length-meter") 17 | } 18 | } 19 | ``` 20 | >ℹ️ This is only an exemplary type, which makes not much sense and if we look very carefully we might find some issues with it. For a complete implementation of a custom type, see the corresponding article in the [UI5 Documentation](https://sdk.openui5.org/topic/07e4b920f5734fd78fdaa236f26236d8). 21 | ## Step 2: Add the TypeMap 22 | In the same `type` folder, create a file named `TypeMap.ts`. This file will define a module that extends the `sap/ui/mdc/DefaultTypeMap`. This extension allows us to supplement the default set of type mappings with our specific type `LengthMeter`. Remember to import and freeze our custom TypeMap as indicated in the following snippet. 23 | ###### model/type/TypeMap.ts 24 | ```typescript 25 | import DefaultTypeMap from "sap/ui/mdc/DefaultTypeMap" 26 | import BaseType from "sap/ui/mdc/enums/BaseType" 27 | import LengthMeter from "mdc/tutorial/model/type/LengthMeter" 28 | 29 | const TypeMap = Object.assign({}, DefaultTypeMap) 30 | TypeMap.import(DefaultTypeMap) 31 | TypeMap.set("mdc.tutorial.model.type.LengthMeter", BaseType.Numeric) 32 | TypeMap.freeze() 33 | 34 | export default TypeMap 35 | ``` 36 | >ℹ️ We have to require the type here, as there is no library where we could declare them and ensure, that they will be loaded in our application. A small change to improve this is currently under consideration. 37 | ## Step 3: Create BaseDelegate & Use It 38 | Since our controls need to utilize the new TypeMap, the delegates are equipped with a special `getTypeMap` hook. Let's create a basic delegate in the `delegate` folder and name it `JSONBaseDelegate.ts`. This delegate can be reused, eliminating the need for all delegates to implement it. 39 | ###### delegate/JSONBaseDelegate.ts 40 | ```typescript 41 | import TypeMap from "mdc/tutorial/model/type/TypeMap" 42 | 43 | export default { 44 | getTypeMap: function() { 45 | return TypeMap; 46 | } 47 | } 48 | ``` 49 | In both `JSONTableDelegate` and `JSONFilterBarDelegate` files, import and add the new `JSONBaseDelegate` to the `assign` call as a third argument. 50 | ###### delegate/JSONTableDelegate.ts 51 | ```typescript 52 | import JSONBaseDelegate from "./JSONBaseDelegate" 53 | 54 | const JSONTableDelegate = Object.assign({}, TableDelegate, JSONBaseDelegate) 55 | ``` 56 | ###### delegate/JSONFilterBarDelegate.ts 57 | ```typescript 58 | import JSONBaseDelegate from "./JSONBaseDelegate" 59 | 60 | var JSONFilterBarDelegate = Object.assign({}, FilterBarDelegate, JSONBaseDelegate) 61 | ``` 62 | ## Step 4: Add Type Definition 63 | The final step involves using the type for the `height` property in our `Mountains.view.xml` file and adding it to the `PropertyInfo.ts` file, from where it will be assigned automatically to all other columns and FilterFields via the delegates. 64 | ###### view/Mountains.view.xml 65 | ```xml 66 | <mdct:Column 67 | propertyKey="height" 68 | header="Height"> 69 | <Text text="{path: 'mountains>height' , type: 'mdc.tutorial.model.type.LengthMeter'}"/> 70 | </mdct:Column> 71 | ``` 72 | ###### model/metadata/JSONPropertyInfo.ts 73 | ```js 74 | },{ 75 | key: "height", 76 | label: "Height", 77 | visible: true, 78 | path: "height", 79 | dataType: "mdc.tutorial.model.type.LengthMeter" 80 | },{ 81 | key: "prominence", 82 | label: "Prominence", 83 | visible: true, 84 | path: "prominence", 85 | dataType: "mdc.tutorial.model.type.LengthMeter" 86 | },{ 87 | ``` 88 | Check if the application now shows the meters properly in FilterFields and columns! 🏔️ 89 | 90 | ![Exercise 4 Result](u1/ex4.png) 91 | 92 | ## Summary 93 | In this exercise, we've learned how to add a specific type, "LengthMeter", to the sap.ui.mdc controls in our application. This included creating a new file for the type, extending the default TypeMap, creating a base delegate, and adding the type definition to the view and property info files. 94 | 95 | Proceed to - [Exercise 5](../u1/ex5/readme.md) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UI5 MDC Tutorial 2 | [![REUSE status](https://api.reuse.software/badge/github.com/SAP-samples/ui5-mdc-json-tutorial)](https://api.reuse.software/info/github.com/SAP-samples/ui5-mdc-json-tutorial) 3 | ## Description 4 | In this tutorial, we will cover some key concepts involving metadata-driven controls (MDCs). This includes understanding control delegates, PropertyInfo, table, Geomap, FilterBar, value help, TypeMap, and VariantManagement. The implementation will be based on a JSON model and is based on our official [Demokit Sample](https://sdk.openui5.org/entity/sap.ui.mdc/sample/sap.ui.mdc.demokit.sample.TableFilterBarJson). After completion, we should be able to leverage the full potential of the MDC concepts in our own projects. 5 | 6 | >ℹ️ This version of the tutorial uses TypeScript. You can still find the JavaScript version [here](https://github.com/SAP-samples/ui5-mdc-json-tutorial/tree/javascript), but that one will not receive any more updates or additions. We recommend using sap.ui.mdc with TypeScript. 7 | ### Metadata-Driven Controls 8 | Metadata-Driven Controls or MDCs are a powerful tool that allows us to create user interfaces dynamically at runtime. The controls are driven by metadata, which gives a description of the data and its characteristics. This way, we don't have to explicitly define every control in our UI, but we can configure and modify them based on the provided metadata. Find more details in the UI5 [Documentation](https://sdk.openui5.org/topic/1dd2aa91115d43409452a271d11be95b) and [API Reference](https://sdk.openui5.org/api/sap.ui.mdc). 9 | ### Delegates 10 | Control Delegates are used to implement service or application-specific behavior for our MDCs. They allow developers to customize the default behavior of controls depending on the specific needs of their service or application. This can include things like custom control creation, metadata provision, or data binding. 11 | ### PropertyInfo 12 | PropertyInfo is used to define the necessary metadata that should be provided to the controls. This can include information like the control visibility, the data type, or other control-specific settings. PropertyInfo is essential in letting our MDCs know how they should behave. 13 | ### Used Controls 14 | These are some specific types of MDCs that we can use in our applications: 15 | - **Table**: This control lets us display data in a tabular format. We can define columns and rows based on the provided metadata. 16 | - **FilterBar**: This control provides a user interface for creating complex filter conditions. It can be used in conjunction with a Table control to filter the displayed data. 17 | - **Value Help**: This control is used to assist the user in inputting data. It can provide suggestions or a list of possible values to choose from based on the provided metadata. 18 | ### Custom Types 19 | The TypeMap is used for defining custom types in our MDCs. If the standard types provided are not sufficient for our needs, we can add our own using the TypeMap. This gives us even more flexibility in customizing our controls. 20 | ### VariantManagement and Personalization 21 | VariantManagement is a feature that allows users to save their personalization settings. This can include things like the layout of a table, filter conditions in a FilterBar, or others. These settings can then be loaded later, allowing users to customize their experience and increase their productivity. 22 | ## Exercises 23 | ### Unit 1: 24 | 0. [Setup the Project on Your Machine](u1/ex0/) (*[browse sources](u1/ex0/webapp) - [run app](https://sap-samples.github.io/ui5-mdc-json-tutorial/u1/ex0/dist)*) 25 | 1. [How to Use the MDC Table](u1/ex1/) (*[browse sources](u1/ex1/webapp) - [run app](https://sap-samples.github.io/ui5-mdc-json-tutorial/u1/ex1/dist)*) 26 | 1. [How to Use the MDC FilterBar](u1/ex2/) (*[browse sources](u1/ex2/webapp) - [run app](https://sap-samples.github.io/ui5-mdc-json-tutorial/u1/ex2/dist)*) 27 | 1. [How to Build Advanced Value Helps](u1/ex3/) (*[browse sources](u1/ex3/webapp) - [run app](https://sap-samples.github.io/ui5-mdc-json-tutorial/u1/ex3/dist)*) 28 | 1. [How to Add Custom Types](u1/ex4/) (*[browse sources](u1/ex4/webapp) - [run app](https://sap-samples.github.io/ui5-mdc-json-tutorial/u1/ex4/dist)*) 29 | 1. [How to Enable VariantManagement](u1/ex5/) (*[browse sources](u1/ex5/webapp) - [run app](https://sap-samples.github.io/ui5-mdc-json-tutorial/u1/ex5/dist)*) 30 | 31 | ### Unit 2: 32 | 1. [How to Use the MDC Geomap](u2/ex1) (*[browse sources](u2/ex1/webapp)*) 33 | 34 | > [!CAUTION] 35 | > [How to Use the MDC Geomap](u2/ex1) - This package is provided under the terms of the [SAP Developer License Agreement](https://tools.hana.ondemand.com/developer-license.txt). 36 | 37 | 38 | ## Requirements 39 | ### Technical Requirements 40 | * A current version of [Node.js](https://nodejs.org/) (preferably 18+) 41 | * A code editor supporting TypeScript development 42 | ### Required Knowledge 43 | * TypeScript knowledge to avoid blind copy and paste without knowing what's going on. 44 | * UI5 knowledge, as this tutorial focuses on the MDC concepts. 45 | ## Known Issues 46 | No known issues. 47 | ## How to obtain support 48 | This repository is provided as-is, without any support guarantees. However, you are welcome to report issues via the [Issues](../../issues) tab and we'll see what we can do to fix them. 49 | ## Contributing 50 | If you wish to contribute code, offer fixes or improvements, please send a pull request. Due to legal reasons, contributors will be asked to accept a DCO when they create the first pull request to this project. This happens in an automated fashion during the submission process. SAP uses [the standard DCO text of the Linux Foundation](https://developercertificate.org/). 51 | ## License 52 | Copyright (c) 2023 SAP SE or an SAP affiliate company. All rights reserved. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](LICENSE) file. 53 | -------------------------------------------------------------------------------- /u1/ex2/readme.md: -------------------------------------------------------------------------------- 1 | [![solution](https://flat.badgen.net/badge/solution/available/green?icon=github)](webapp) 2 | [![demo](https://flat.badgen.net/badge/demo/deployed/blue?icon=github)](https://sap-samples.github.io/ui5-mdc-json-tutorial/u1/ex2/dist) 3 | # Exercise 2: How to Use the MDC FilterBar 4 | In this exercise, we will create a FilterBarDelegate, add a FilterBar to the XML view, use the filter association of the table and implement the search feature as a combination of filters. 5 | ## Step 1: Create a FilterBarDelegate 6 | This delegate is responsible for managing the filtering logic for an application. This includes creating and managing FilterFields, as well as providing the corresponding PropertyInfo for FilterFields. 7 | ###### delegate/JSONFilterBarDelegate.ts 8 | ```typescript 9 | import FilterBarDelegate from "sap/ui/mdc/FilterBarDelegate" 10 | import JSONPropertyInfo from "mdc/tutorial/model/metadata/JSONPropertyInfo" 11 | import FilterField from "sap/ui/mdc/FilterField" 12 | import Element from "sap/ui/core/Element" 13 | import {default as FilterBar, PropertyInfo as FilterBarPropertyInfo} from "sap/ui/mdc/FilterBar" 14 | 15 | var JSONFilterBarDelegate = Object.assign({}, FilterBarDelegate) 16 | 17 | JSONFilterBarDelegate.fetchProperties = async () => JSONPropertyInfo 18 | 19 | const _createFilterField = (id:string, property:FilterBarPropertyInfo, filterBar:FilterBar) => { 20 | const propertyKey = property.key 21 | const filterField = new FilterField(id, { 22 | dataType: property.dataType, 23 | conditions: `{$filters>/conditions/${propertyKey}}`, 24 | propertyKey: propertyKey, 25 | required: property.required, 26 | label: property.label, 27 | maxConditions: property.maxConditions, 28 | delegate: {name: "sap/ui/mdc/field/FieldBaseDelegate", payload: {}} 29 | }) 30 | return filterField 31 | } 32 | 33 | JSONFilterBarDelegate.addItem = async (filterBar:FilterBar, propertyKey:string) => { 34 | const property = JSONPropertyInfo.find((p) => p.key === propertyKey) as FilterBarPropertyInfo 35 | const id = `${filterBar.getId()}--filter--${propertyKey}` 36 | const filterField = Element.getElementById(id) as FilterField 37 | return filterField ?? _createFilterField(id, property, filterBar) 38 | } 39 | 40 | export default JSONFilterBarDelegate 41 | ``` 42 | 43 | ## Step 2: Use the MDC FilterBar 44 | To add a FilterBar to the XML view, we can use the [`sap.ui.mdc.FilterBar`](https://sdk.openui5.org/api/sap.ui.mdc.FilterBar) control. Setting the previously created delegate makes sure, that the FilterBar can deal with the specific JSON data we are facing. Place the FilterBar inside of the DynamicPageHeader. 45 | ###### view/Mountains.view.xml 46 | ```xml 47 | <f:DynamicPageHeader pinnable="true"> 48 | <mdc:FilterBar id="filterbar" delegate="{name: 'mdc/tutorial/delegate/JSONFilterBarDelegate'}" 49 | p13nMode = "Item,Value"> 50 | <mdc:basicSearchField> 51 | <mdc:FilterField delegate="{name: 'sap/ui/mdc/field/FieldBaseDelegate'}" 52 | dataType="sap.ui.model.type.String" 53 | placeholder= "Search Mountains" 54 | conditions="{$filters>/conditions/$search}" 55 | maxConditions="1"/> 56 | </mdc:basicSearchField> 57 | <mdc:filterItems> 58 | <mdc:FilterField 59 | label="Name" 60 | propertyKey="name" 61 | dataType="sap.ui.model.type.String" 62 | conditions="{$filters>/conditions/name}" 63 | delegate="{name: 'sap/ui/mdc/field/FieldBaseDelegate'}"/> 64 | </mdc:filterItems> 65 | <mdc:dependents> 66 | 67 | </mdc:dependents> 68 | </mdc:FilterBar> 69 | </f:DynamicPageHeader> 70 | ``` 71 | 72 | >⚠️ Like columns in the MDC table, the filter items are used for UI adaptation functionalities. Hence, do not change them, manually or dynamically, or use bindings to prevent undesired effects. 73 | 74 | Use the filter association of the table to connect it to the FilterBar and add the fields we would like to search in the payload. For this, add the `searchKeys` property to the delegate payload. 75 | ###### view/Mountains.view.xml 76 | ```xml 77 | <mdc:Table 78 | id="table" 79 | header="Mountains" 80 | p13nMode="Sort,Column" 81 | type="ResponsiveTable" 82 | threshold="100" 83 | filter="filterbar" 84 | showRowCount="false" 85 | delegate="{ 86 | name: 'mdc/tutorial/delegate/JSONTableDelegate', 87 | payload: { 88 | bindingPath: 'mountains>/mountains', 89 | searchKeys: ['name', 'range', 'parent_mountain', 'countries'] 90 | } 91 | }"> 92 | ``` 93 | 94 | ## Step 3: Enable the Search in the JSONTableDelegate 95 | To implement the search feature, we need to extend the `JSONTableDelegate` and override the `getFilters` method. We implement a simple search feature by combining several filters and appending them to the regular filter set, which is prepared by the `TableDelegate`. Furthermore, we will extend the interface of the `TablePayload`, which then should also contain the information about the `searchKeys`. At this point, you might need to add missing imports for `Filter`, `FilterOperator`, and `FilterBar`, which you can either do manually or use the "Quick Fix" feature of e.g. Visual Studio Code. 96 | 97 | ###### delegate/JSONTableDelegate.ts 98 | ```typescript 99 | interface TablePayload { 100 | bindingPath: string 101 | searchKeys: string[] 102 | } 103 | 104 | const _createSearchFilters = (search:string, keys:string[]) => { 105 | const filters = keys.map((key) => new Filter({ 106 | path: key, 107 | operator: FilterOperator.Contains, 108 | value1: search 109 | })) 110 | return [new Filter(filters, false)] 111 | } 112 | 113 | JSONTableDelegate.getFilters = (table) => { 114 | const search = (Element.getElementById(table.getFilter()) as FilterBar).getSearch() 115 | const keys = (table.getPayload() as TablePayload).searchKeys 116 | let filters = TableDelegate.getFilters(table) 117 | if (search && keys) { 118 | filters = filters.concat(_createSearchFilters(search, keys)) 119 | } 120 | return filters 121 | } 122 | ``` 123 | Now go and try out the filter and search functionality in our application. The table should display only the filtered items! 🙌 124 | 125 | ![Exercise 2 Result](u1/ex2.png) 126 | 127 | ## Summary 128 | In this exercise, we have extended the functionality of our application by adding a FilterBarDelegate to handle filtering operations, and a JSONTableDelegate to handle search operations. We have also learned how to use the filter association of the table to connect the FilterBar to the Table. This allows us to create a more interactive and dynamic user interface, where the user can filter and search the data in the table based on their needs. 129 | 130 | Continue to - [Exercise 3](../u1/ex3/readme.md) -------------------------------------------------------------------------------- /u1/ex1/readme.md: -------------------------------------------------------------------------------- 1 | [![solution](https://flat.badgen.net/badge/solution/available/green?icon=github)](webapp) 2 | [![demo](https://flat.badgen.net/badge/demo/deployed/blue?icon=github)](https://sap-samples.github.io/ui5-mdc-json-tutorial/u1/ex1/dist) 3 | # Exercise 1: How to Use the MDC Table 4 | In this exercise we will learn how to create a JSONTableDelegate for an MDC Table in an XMLView. These elements are crucial when building an MDC application that interacts with JSON data. 5 | 6 | ## Step 1: Create a JSONTableDelegate 7 | 8 | Firstly, let's create a new directory named `delegate` within the `webapp` directory. Also, add a JavaScript file named `JSONTableDelegate.ts` inside the `delegate` directory. 9 | 10 | This file serves as a delegate for a UI5 table. Delegates offer a method to customize the behavior of a control without modifying the control itself. In this example, it contains the logic of how the table interacts with the sample JSON data. 11 | 12 | Below is the code for the delegate. It extends the [`sap/ui/mdc/TableDelegate`](https://sdk.openui5.org/api/module:sap/ui/mdc/TableDelegate) and includes functions to extract properties from the JSON metadata provided in `JSONPropertyInfo.ts` in the model folder, add items to the table, delete items from the table, and revise the table's binding information. 13 | 14 | Thanks to TypeScript we can provide a delegate-specific interface for the payload, which clearly defines what content can be provided. In this case, the `bindingPath` is specified, so that the table knows from there to get its data. Take a look at the implementation! 15 | ###### delegate/JSONTableDelegate.ts 16 | ```typescript 17 | import TableDelegate from "sap/ui/mdc/TableDelegate" 18 | import Text from "sap/m/Text" 19 | import Element from "sap/ui/core/Element" 20 | import JSONPropertyInfo from "mdc/tutorial/model/metadata/JSONPropertyInfo" 21 | import {default as Table, PropertyInfo as TablePropertyInfo} from "sap/ui/mdc/Table" 22 | import Column from "sap/ui/mdc/table/Column" 23 | 24 | interface TablePayload { 25 | bindingPath: string 26 | } 27 | 28 | const JSONTableDelegate = Object.assign({}, TableDelegate) 29 | 30 | JSONTableDelegate.fetchProperties = async () => { 31 | return JSONPropertyInfo.filter((p) => p.key !== "$search") 32 | } 33 | 34 | const _createColumn = (propertyInfo:TablePropertyInfo, table:Table) => { 35 | const name = propertyInfo.key 36 | const id = table.getId() + "---col-" + name 37 | const column = Element.getElementById(id) as Column 38 | return column ?? new Column(id, { 39 | propertyKey: name, 40 | header: propertyInfo.label, 41 | template: new Text({ 42 | text: { 43 | path: "mountains>" + name, 44 | type: propertyInfo.dataType 45 | } 46 | }) 47 | }) 48 | } 49 | 50 | JSONTableDelegate.addItem = async (table:Table, propertyKey:string) => { 51 | const propertyInfo = JSONPropertyInfo.find((p) => p.key === propertyKey) 52 | return _createColumn(propertyInfo, table) 53 | } 54 | 55 | JSONTableDelegate.updateBindingInfo = (table, bindingInfo) => { 56 | TableDelegate.updateBindingInfo.call(JSONTableDelegate, table, bindingInfo) 57 | bindingInfo.path = (table.getPayload() as TablePayload).bindingPath 58 | bindingInfo.templateShareable = true 59 | } 60 | 61 | export default JSONTableDelegate 62 | ``` 63 | 64 | >⚠️ The `fetchProperties` is a special function as its return value is used for further UI adaptation functionalities. Due to this, the result of this function must be kept stable throughout the lifecycle of your application. Any changes of the returned values might result in undesired effects. As we're using a PropertyInfo at this point, be aware to keep it stable throughout the lifecycle of your application. 65 | 66 | The PropertyInfo provides all necessary metadata for the MDC Table to function. Take a look at this excerpt of the `JSONPropertyInfo.ts` file to understand how the two properties, `name` and `height` are defined. 67 | ###### model/metadata/JSONPropertInfo.ts 68 | ```js 69 | { 70 | key: "name", 71 | label: "Name", 72 | visible: true, 73 | path: "name", 74 | dataType: "sap.ui.model.type.String" 75 | },{ 76 | key: "height", 77 | label: "Height", 78 | visible: true, 79 | path: "height", 80 | dataType: "sap.ui.model.type.Integer" 81 | } 82 | ``` 83 | >ℹ️ For a comprehensive description of what information should be contained within `PropertyInfo` objects, see the [API Reference](https://sdk.openui5.org/api/sap.ui.mdc.table.PropertyInfo). In real-life scenarios we might retrieve this metadata from the data service and we would have to translate it into the PropertyInfo format, easy to digest for the controls. 84 | ## Step 2: Use the MDC Table 85 | 86 | Next, let's enhance the XML view named `Mountains.view.xml` in the `view` directory. 87 | 88 | This XML view defines the user interface for a screen in our UI5 application. The view comprises a DynamicPage with a Table control in its content area. The table is set up to use our custom JSONTableDelegate. 89 | 90 | Below is the code we can add to the content aggregation of the DynamicPage in the XML view. It includes a table with columns for name, height, range, first ascent, countries, and parent mountain, along with the data bindings. The corresponding model is automatically generated based on our sample data via the `manifest.json`. 91 | ###### view/Mountains.view.xml 92 | ```xml 93 | <f:content> 94 | <mdc:Table 95 | id="table" 96 | header="Mountains" 97 | p13nMode="Sort,Column" 98 | type="ResponsiveTable" 99 | threshold="100" 100 | filter="filterbar" 101 | showRowCount="false" 102 | delegate="{ 103 | name: 'mdc/tutorial/delegate/JSONTableDelegate', 104 | payload: { 105 | bindingPath: 'mountains>/mountains' 106 | } 107 | }"> 108 | <mdct:Column 109 | propertyKey="name" 110 | header="Name"> 111 | <Text text="{mountains>name}" /> 112 | </mdct:Column> 113 | <mdct:Column 114 | propertyKey="height" 115 | header="Height"> 116 | <Text text="{path: 'mountains>height'}" /> 117 | </mdct:Column> 118 | <mdct:Column 119 | propertyKey="range" 120 | header="Range"> 121 | <Text text="{mountains>range}" /> 122 | </mdct:Column> 123 | <mdct:Column 124 | propertyKey="first_ascent" 125 | header="First Ascent"> 126 | <Text text="{mountains>first_ascent}" /> 127 | </mdct:Column> 128 | <mdct:Column 129 | propertyKey="countries" 130 | header="Countries"> 131 | <Text text="{mountains>countries}" /> 132 | </mdct:Column> 133 | <mdct:Column 134 | propertyKey="parent_mountain" 135 | header="Parent Mountain"> 136 | <Text text="{mountains>parent_mountain}" /> 137 | </mdct:Column> 138 | </mdc:Table> 139 | </f:content> 140 | ``` 141 | > ℹ️ Pay attention to how the controls are specified. All the MDCs included in the XML view will initially appear on the screen without any additional personalization. While this may seem superfluous when also providing the control creation method in the delegate, it allows us to establish a default without any hassle. Alternatively, we could opt to not provide any controls here and add them later through personalization. 142 | 143 | >⚠️ The columns we define in our table are used for UI adaptation functionalities like the aforementioned `fetchProperties` function. Hence, the values must again be kept stable throughout the lifecycle of your application to prevent undesired effects. For this reason, using bindings or changing the aggregation's value dynamically through controller code is not advised. 144 | 145 | Run the application and see how, with just a few lines of code we added, we get a personalizable table that shows the properties of our JSON data! 😱 146 | 147 | ![Exercise 1 Result](u1/ex1.png) 148 | ## Summary 149 | 150 | The main takeaway is that delegates offer a potent mechanism to adapt the behavior of sap.ui.mdc controls without altering the controls themselves. With a custom delegate, we can customize a control to handle a specific type of data, such as JSON data. Furthermore, XML views provide declarative means to define the user interface for a screen in a UI5 application. 151 | 152 | Proceed to - [Exercise 2](../u1/ex2/readme.md) -------------------------------------------------------------------------------- /u1/ex3/readme.md: -------------------------------------------------------------------------------- 1 | [![solution](https://flat.badgen.net/badge/solution/available/green?icon=github)](webapp) 2 | [![demo](https://flat.badgen.net/badge/demo/deployed/blue?icon=github)](https://sap-samples.github.io/ui5-mdc-json-tutorial/u1/ex3/dist) 3 | # Exercise 3: How to Build Advanced Value Helps 4 | In this exercise, we will build advanced value helps and integrate them into the FilterFields. This enhancement will improve the user's filtering experience and expedite the search for desired results. 5 | 6 | ## Step 1: Build Value Helps as Fragments 7 | Let's begin by creating a subfolder in the `view` folder of our web application, and name it `fragment`. Here, we will build two value helps of varying complexity. These ValueHelps are defined in conjunction with the table wrappers, [`MTable`](https://sdk.openui5.org/api/sap.ui.mdc.valuehelp.content.MTable) and [`MDCTable`](https://sdk.openui5.org/api/sap.ui.mdc.valuehelp.content.MDCTable), which manage the orchestration of the associated table and the communication with the ValueHelp dialog. 8 | 9 | Firstly, create `RangeValueHelp.fragment.xml` and insert the following value help template. It includes a simple suggestion (type-ahead) for the specific mountain range. 10 | ###### view/fragment/RangeValueHelp.fragment.xml 11 | ```xml 12 | <core:FragmentDefinition 13 | xmlns="sap.m" 14 | xmlns:core="sap.ui.core" 15 | xmlns:mdc="sap.ui.mdc" 16 | xmlns:mdct="sap.ui.mdc.table" 17 | xmlns:vh="sap.ui.mdc.valuehelp" 18 | xmlns:vhc="sap.ui.mdc.valuehelp.content" 19 | xmlns:vhfb="sap.ui.mdc.filterbar.vh"> 20 | 21 | <mdc:ValueHelp id="range-vh" delegate="{name: 'sap/ui/mdc/ValueHelpDelegate', payload: {}}"> 22 | <mdc:typeahead> 23 | <vh:Popover title="Range"> 24 | <vhc:MTable keyPath="range" filterFields="*range*"> 25 | <Table id="range-vh-table" items='{path : "mountains>/ranges"}' width="30rem"> 26 | <columns> 27 | <Column> 28 | <header> 29 | <Text text="Range" /> 30 | </header> 31 | </Column> 32 | </columns> 33 | <items> 34 | <ColumnListItem type="Active"> 35 | <cells> 36 | <Text text="{mountains>range}" /> 37 | </cells> 38 | </ColumnListItem> 39 | </items> 40 | </Table> 41 | </vhc:MTable> 42 | </vh:Popover> 43 | </mdc:typeahead> 44 | </mdc:ValueHelp> 45 | 46 | </core:FragmentDefinition> 47 | ``` 48 | Next, construct `NameValueHelp.fragment.xml` with the template provided below. This time we are incorporating a comprehensive value help dialog with a selection table. The dialog will also host the default ValueHelp condition tab. 49 | ###### view/fragment/NameValueHelp.fragment.xml 50 | ```xml 51 | <core:FragmentDefinition 52 | xmlns="sap.m" 53 | xmlns:core="sap.ui.core" 54 | xmlns:mdc="sap.ui.mdc" 55 | xmlns:mdct="sap.ui.mdc.table" 56 | xmlns:vh="sap.ui.mdc.valuehelp" 57 | xmlns:vhc="sap.ui.mdc.valuehelp.content" 58 | xmlns:vhfb="sap.ui.mdc.filterbar.vh"> 59 | 60 | <mdc:ValueHelp id="name-vh" delegate="{name: 'sap/ui/mdc/ValueHelpDelegate', payload: {}}"> 61 | <mdc:typeahead> 62 | <vh:Popover title="Name"> 63 | <vhc:MTable keyPath="name" filterFields="*name*"> 64 | <Table id="name-vht-table" items='{path : "mountains>/mountains", length: 10}' 65 | width="30rem"> 66 | <columns> 67 | <Column> 68 | <header> 69 | <Text text="Name" /> 70 | </header> 71 | </Column> 72 | </columns> 73 | <items> 74 | <ColumnListItem type="Active"> 75 | <cells> 76 | <Text text="{mountains>name}" /> 77 | </cells> 78 | </ColumnListItem> 79 | </items> 80 | </Table> 81 | </vhc:MTable> 82 | </vh:Popover> 83 | </mdc:typeahead> 84 | <mdc:dialog> 85 | <vh:Dialog title="Name"> 86 | <vhc:MDCTable keyPath="name"> 87 | <vhc:filterBar> 88 | <vhfb:FilterBar id="name-vhd-fb" delegate="{name: 'mdc/tutorial/delegate/JSONFilterBarDelegate'}"> 89 | <vhfb:basicSearchField> 90 | <mdc:FilterField delegate="{name: 'sap/ui/mdc/field/FieldBaseDelegate'}" 91 | dataType="sap.ui.model.type.String" 92 | placeholder= "Search Mountains" 93 | conditions="{$filters>/conditions/$search}" 94 | maxConditions="1"/> 95 | </vhfb:basicSearchField> 96 | </vhfb:FilterBar> 97 | </vhc:filterBar> 98 | <mdc:Table id="name-vhd-table" 99 | type="ResponsiveTable" 100 | selectionMode="Multi" 101 | delegate="{ 102 | name: 'mdc/tutorial/delegate/JSONTableDelegate', 103 | payload: { 104 | bindingPath: 'mountains>/mountains', 105 | searchKeys: ['name'] 106 | } 107 | }" 108 | filter="name-vhd-fb"> 109 | <mdc:columns> 110 | <mdct:Column 111 | header="Name" 112 | propertyKey="name"> 113 | <Text text="{mountains>name}"/> 114 | </mdct:Column> 115 | </mdc:columns> 116 | </mdc:Table> 117 | </vhc:MDCTable> 118 | <vhc:Conditions label="Name"/> 119 | </vh:Dialog> 120 | </mdc:dialog> 121 | </mdc:ValueHelp> 122 | 123 | </core:FragmentDefinition> 124 | ``` 125 | >ℹ️ To simplify matters, we are using the same data set here as in our table. However, in a real scenario, we may want to consider using a ValueHelp from a different set of data to find the values, which is entirely possible. 126 | ## Step 2: Link ValueHelps with FilterFields 127 | To utilize the advanced ValueHelp in our view, we need to attach it as a dependent of our FilterBar. This ensures its lifecycle is tied to the FilterBar and will be terminated along with it, for instance, when the application is closed or navigated away from. 128 | ###### view/Mountains.view.xml 129 | ```xml 130 | <mdc:dependents> 131 | <core:Fragment fragmentName="mdc.tutorial.view.fragment.NameValueHelp" type="XML"/> 132 | </mdc:dependents> 133 | ``` 134 | Subsequently, it is crucial to connect the ValueHelp to the corresponding FilterField by setting the `valueHelp` association. For the other ValueHelp, we are going to use a delegate-based method in the next step. 135 | ###### view/Mountains.view.xml 136 | ```xml 137 | <mdc:FilterField 138 | label="Name" 139 | propertyKey="name" 140 | dataType="sap.ui.model.type.String" 141 | conditions="{$filters>/conditions/name}" 142 | valueHelp="name-vh" 143 | delegate="{name: 'sap/ui/mdc/field/FieldBaseDelegate'}"/> 144 | ``` 145 | ## Step 3: Incorporate Payload 146 | To determine which properties should be associated with a ValueHelp in our delegate, we can define a delegate-specific payload for this particular table instance. The payload can be added as follows: 147 | ###### view/Mountains.view.xml 148 | ```xml 149 | <mdc:FilterBar id="filterbar" delegate="{ 150 | name: 'mdc/tutorial/delegate/JSONFilterBarDelegate', 151 | payload: { 152 | valueHelp: { 153 | name: 'NameValueHelp', 154 | range: 'RangeValueHelp' 155 | } 156 | } 157 | }" 158 | p13nMode = "Item,Value"> 159 | ``` 160 | ## Step 4: Add ValueHelp Creation in Delegate 161 | Accessing the payload allows us to identify if a specific FilterField requires a ValueHelp if created by the delegate. Using this information, we can tie the FilterField to the ValueHelp and attach it as a dependent to the FilterBar, but this time within the appropriate callback. Replace the old implementation of `_createFilterField` as follows and add an interface for the `FilterBarPayload`, which defines its content: 162 | ###### delegate/JSONFilterBarDelegate.ts 163 | ```typescript 164 | interface FilterBarPayload { 165 | valueHelp: { 166 | [key:string]: string 167 | } 168 | } 169 | 170 | const _createValueHelp = async (filterBar:FilterBar, propertyKey:string) => { 171 | const path = "mdc.tutorial.view.fragment." 172 | const valueHelp = await Fragment.load({ 173 | name: path + (filterBar.getPayload() as FilterBarPayload).valueHelp[propertyKey] 174 | }) as unknown as ValueHelp 175 | filterBar.addDependent(valueHelp) 176 | return valueHelp 177 | } 178 | 179 | const _createFilterField = async (id:string, property:FilterBarPropertyInfo, filterBar:FilterBar) => { 180 | const propertyKey = property.key 181 | const filterField = new FilterField(id, { 182 | dataType: property.dataType, 183 | conditions: `{$filters>/conditions/${propertyKey}}`, 184 | propertyKey: propertyKey, 185 | required: property.required, 186 | label: property.label, 187 | maxConditions: property.maxConditions, 188 | delegate: {name: "sap/ui/mdc/field/FieldBaseDelegate", payload: {}} 189 | }) 190 | if ((filterBar.getPayload() as FilterBarPayload).valueHelp[propertyKey] ) { 191 | const dependents = filterBar.getDependents() 192 | let valueHelp = dependents.find((dependent) => dependent.getId().includes(propertyKey)) as ValueHelp 193 | valueHelp ??= await _createValueHelp(filterBar, propertyKey) 194 | filterField.setValueHelp(valueHelp) 195 | } 196 | return filterField 197 | } 198 | ``` 199 | Check that our ValueHelps work by using the suggestion and the ValueHelp dialog of the corresponding fields! 🤓 200 | 201 | ![Exercise 3 Result](u1/ex3.png) 202 | ## Summary 203 | In this exercise, we learned how to construct ValueHelps as fragments and improve the user's filtering experience. These ValueHelps were linked with FilterFields and added as dependents to the FilterBar. Additionally, a delegate-specific payload was incorporated into the delegate to specify which properties should have a ValueHelp attached. 204 | 205 | Proceed to - [Exercise 4](../u1/ex4/readme.md) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSES/Apache-2.0.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /u2/ex1/webapp/delegate/JSONGeomapDelegate.ts: -------------------------------------------------------------------------------- 1 | import GeomapDelegate from "sap/ui/mdc/GeomapDelegate" 2 | import Text from "sap/m/Text" 3 | import JSONPropertyInfo from "mdc/tutorial/model/metadata/JSONPropertyInfo" 4 | import {Geomap as MDCGeomap, PropertyInfo as GeomapPropertyInfo} from "sap/ui/mdc/Geomap" 5 | import JSONBaseDelegate from "./JSONBaseDelegate" 6 | import Geomap from 'sap/ui/geomap/Geomap' 7 | import GeomapProvider from 'sap/ui/geomap/GeomapProvider' 8 | import GeomapSpot from 'sap/ui/geomap/GeomapSpot' 9 | import GeomapNavigationControl from 'sap/ui/geomap/GeomapNavigationControl' 10 | import GeomapSelectionControl from 'sap/ui/geomap/GeomapSelectionControl' 11 | import GeomapScaleControl from 'sap/ui/geomap/GeomapScaleControl' 12 | import GeomapCopyrightControl from 'sap/ui/geomap/GeomapCopyrightControl' 13 | import GeomapFullscreenControl from 'sap/ui/geomap/GeomapFullscreenControl' 14 | import GeomapControlPosition from "sap/ui/mdc/enums/GeomapControlPosition" 15 | import GeomapPolygon from "sap/ui/geomap/GeomapPolygon" 16 | import GeomapPoint from "sap/ui/geomap/GeomapPoint" 17 | import GeomapLine from "sap/ui/geomap/GeomapLine" 18 | import JSONModel from "sap/ui/model/json/JSONModel" 19 | import Popover from "sap/m/Popover" 20 | import List from "sap/m/List" 21 | import StandardListItem from "sap/m/StandardListItem" 22 | import Log from "sap/base/Log" 23 | import Link from "sap/m/Link" 24 | 25 | interface GeomapPayload { 26 | bindingPath: string 27 | } 28 | 29 | const JSONGeomapDelegate = Object.assign({}, GeomapDelegate, JSONBaseDelegate) 30 | 31 | const mStateMap = new window.WeakMap(); 32 | 33 | JSONGeomapDelegate._getState = function(oGeomap: MDCGeomap) { 34 | if (mStateMap.has(oGeomap)) { 35 | return mStateMap.get(oGeomap); 36 | } 37 | 38 | return {}; 39 | }; 40 | 41 | JSONGeomapDelegate._setState = function(oGeomap: MDCGeomap, oState: any) { 42 | mStateMap.set(oGeomap, oState); 43 | }; 44 | 45 | JSONGeomapDelegate._getMetadataInfo = (oGeomap: MDCGeomap) => { 46 | return oGeomap.getDelegate().payload; 47 | }; 48 | 49 | JSONGeomapDelegate.fetchProperties = function (oGeomap: MDCGeomap) { 50 | const aProperties = JSONPropertyInfo.filter((oPI) => oPI.key !== "$search"); 51 | 52 | oGeomap.awaitPropertyHelper().then(function(oPropertyHelper: any) { 53 | oPropertyHelper.setProperties(aProperties); 54 | }); 55 | 56 | oGeomap.setPropertyInfo(aProperties); 57 | 58 | return aProperties; 59 | }; 60 | 61 | JSONGeomapDelegate._getModel = function (oGeomap: MDCGeomap) { 62 | const oMetadataInfo = oGeomap.getDelegate().payload; 63 | return oGeomap.getModel(oMetadataInfo.collectionPath); 64 | }; 65 | 66 | JSONGeomapDelegate.initializeGeomap = function (oGeomap: MDCGeomap) { 67 | return JSONGeomapDelegate._createContentFromPropertyInfos(oGeomap); 68 | }; 69 | 70 | JSONGeomapDelegate._createContentFromPropertyInfos = function (oGeomap: MDCGeomap, bForceRebind: boolean) { 71 | return new Promise(function (resolve, reject) { 72 | 73 | const aMapControls = []; 74 | 75 | const oControlPositions = JSONGeomapDelegate.getControlPositions().controlPositions; 76 | aMapControls.push(new GeomapProvider({ 77 | id: "mapProvider", 78 | styleUrl: JSONGeomapDelegate.getProvider() 79 | })); 80 | 81 | if (oGeomap.getEnableNavigationControl()) { 82 | aMapControls.push(new GeomapNavigationControl({ 83 | position: oControlPositions?.navigation ?? GeomapControlPosition.TopLeft 84 | })); 85 | } 86 | 87 | if (oGeomap.getEnableSelectionControl()) { 88 | aMapControls.push(new GeomapSelectionControl({ 89 | position: oControlPositions?.selection ?? GeomapControlPosition.TopRight 90 | })); 91 | } 92 | 93 | if (oGeomap.getEnableFullscreenControl()) { 94 | aMapControls.push(new GeomapFullscreenControl({ 95 | position: oControlPositions?.fullscreen ?? GeomapControlPosition.TopRight 96 | })); 97 | } 98 | 99 | if (oGeomap.getEnableScaleControl()) { 100 | aMapControls.push(new GeomapScaleControl({ 101 | position: oControlPositions?.scale ?? GeomapControlPosition.BottomLeft 102 | })); 103 | } 104 | 105 | if (oGeomap.getEnableCopyrightControl()) { 106 | 107 | const oText = new Text({text: "Map data from "}); 108 | oText.addStyleClass("copyright"); 109 | const oLink = new Link({ 110 | text: "OpenStreetMap", 111 | href: "https://www.openstreetmap.org/copyright", 112 | target: "_blank" 113 | }); 114 | oLink.addStyleClass("sapUiTinyMarginBegin") 115 | aMapControls.push(new GeomapCopyrightControl({ 116 | position: GeomapControlPosition.BottomRight, 117 | content: [ 118 | oText, 119 | oLink 120 | ] 121 | })); 122 | } 123 | 124 | const geomapInstance = new Geomap({ 125 | centerLat: oGeomap.getCenterLat(), 126 | centerLng: oGeomap.getCenterLng(), 127 | zoom: oGeomap.getZoom(), 128 | height: oGeomap.getWidth(), 129 | width: oGeomap.getHeight(), 130 | mapControls: aMapControls, 131 | items: [] 132 | }); 133 | 134 | geomapInstance.setModel(oGeomap.getModel()); 135 | 136 | const oModel = oGeomap.getModel(); 137 | if (bForceRebind) { 138 | oModel.fireRequestCompleted(); 139 | } 140 | if (oModel) { 141 | const oData = oModel.getData(); 142 | const aCollection = oData[oGeomap.getDelegate().payload.collectionName]; 143 | 144 | const aFlattened: any[] = []; 145 | 146 | const aInitiallyVisibleProperties = JSONGeomapDelegate.getVisibleProperties(oGeomap); 147 | 148 | aCollection.forEach(function (oFeature: any) { 149 | const aPropertyInfos = JSONGeomapDelegate.fetchProperties(oGeomap); 150 | aPropertyInfos.forEach((oPropertyInfo: any) => { 151 | 152 | if (oPropertyInfo.key && 153 | oPropertyInfo.dataType.toLowerCase().includes("geom") && 154 | !( 155 | aInitiallyVisibleProperties.length > 0 && 156 | !aInitiallyVisibleProperties.includes(oPropertyInfo.key) 157 | ) 158 | ) { 159 | if (oFeature[oPropertyInfo.path]) { 160 | aFlattened.push({ 161 | GeometryType: oPropertyInfo.dataType, 162 | path: oPropertyInfo.path, 163 | key: oPropertyInfo.key, 164 | geometry: oFeature[oPropertyInfo.path], 165 | properties: oFeature.properties || {} 166 | }); 167 | } 168 | } 169 | }); 170 | }); 171 | 172 | // Create flattened JSONModel 173 | const oFlatModel = new JSONModel(aFlattened); 174 | // Set the flattened model to the geomap instance 175 | geomapInstance.setModel(oFlatModel); 176 | geomapInstance.bindAggregation("items", { 177 | path: "/", 178 | factory: JSONGeomapDelegate.createItemTemplateFactory.bind(this, oGeomap), 179 | templateShareable: false 180 | }); 181 | } 182 | 183 | geomapInstance.attachMapClick((event: any) => { 184 | const mParams = event.getParameters(); 185 | Log.info(`Map clicked at ${mParams.lng}, ${mParams.lat}`); 186 | }); 187 | 188 | oGeomap.setAggregation("_geomap", geomapInstance); 189 | oGeomap._applyConfigurations(); 190 | 191 | const oState = JSONGeomapDelegate._getState(oGeomap); 192 | oState.innerGeomap = geomapInstance; 193 | JSONGeomapDelegate._setState(oGeomap, oState); 194 | 195 | resolve(oGeomap); 196 | }); 197 | }; 198 | 199 | JSONGeomapDelegate.createItemTemplateFactory = (oGeomap: MDCGeomap, sId: string, oContext: any) => { 200 | const oContextObject = oContext.getObject(); 201 | let oTemplate; 202 | switch (oContextObject.geometry.type) { 203 | case "Point": { 204 | oTemplate = _createSpotObject("", oContextObject, oGeomap); 205 | break; 206 | } 207 | case "Polygon": { 208 | oTemplate = _createPolygonObject("", oContextObject, oGeomap); 209 | break; 210 | } 211 | case "LineString": { 212 | oTemplate = _createLineObject("", oContextObject, oGeomap); 213 | break; 214 | } 215 | } 216 | return oTemplate; 217 | }; 218 | 219 | /** 220 | * "Forwards" the initial content (items) for the geomap 221 | * @param oGeomap 222 | */ 223 | JSONGeomapDelegate.createInitialGeomapContent = function (oGeomap: MDCGeomap) { 224 | const oGeomapInstance = oGeomap.getAggregation("_geomap"); 225 | 226 | // if in future we need to pass some initial content, we can do it here 227 | 228 | const oState = JSONGeomapDelegate._getState(oGeomap); 229 | oState.innerGeomap = oGeomapInstance; 230 | JSONGeomapDelegate._setState(oGeomap, oState); 231 | }; 232 | 233 | const _createSpotObject = (sId: string, oContext: any, oGeomap: MDCGeomap) => { 234 | let color = "#000000"; 235 | let width = "1rem"; 236 | const icon = "map"; 237 | 238 | if (oContext.geometry.properties.height > 8000) { 239 | color = "#e70a29"; 240 | } else if (oGeomap.getDelegate().payload?.spotConfig?.color) { 241 | color = oGeomap.getDelegate().payload?.spotConfig?.color; 242 | } 243 | if (oContext.geometry.properties.height > 5000) { 244 | width = "2rem"; 245 | } else if (oContext.geometry.properties.height > 2000) { 246 | width = "1.5rem"; 247 | } 248 | 249 | const oSpot = new GeomapSpot({ 250 | lng: oContext.geometry.coordinates[0], 251 | lat: oContext.geometry.coordinates[1], 252 | height: oGeomap.getDelegate().payload?.spotConfig?.height, 253 | color: color, 254 | icon: icon, 255 | width: width 256 | }); 257 | 258 | oSpot.attachClick((e: any) => { 259 | const oTarget = e.getSource(); 260 | const oPopover = new Popover({ 261 | title: oContext.geometry.properties.name, 262 | content: [ 263 | new List({ 264 | items: [ 265 | new StandardListItem({ 266 | title: "Height: " + oContext.geometry.properties.height 267 | }), 268 | new StandardListItem({ 269 | title: "Rank: " + oContext.geometry.properties.rank 270 | }), 271 | new StandardListItem({ 272 | title: "Prominence: " + oContext.geometry.properties.prominence 273 | }), 274 | new StandardListItem({ 275 | title: "Range: " + oContext.geometry.properties.range 276 | }), 277 | new StandardListItem({ 278 | title: "Countries: " + oContext.geometry.properties.countries 279 | }) 280 | ] 281 | }) 282 | ] 283 | }); 284 | oTarget.addDependent(oPopover); 285 | oPopover.openBy(oTarget); 286 | }); 287 | 288 | return oSpot; 289 | }; 290 | 291 | const _createPolygonObject = (sId: string, oContext: any, oGeomap: MDCGeomap) => { 292 | const oPolygon = new GeomapPolygon({ 293 | points: oContext.geometry.coordinates[0].map((aPoint: any[]) => { 294 | return new GeomapPoint({ 295 | lng: aPoint[0], 296 | lat: aPoint[1] 297 | }); 298 | }) 299 | }); 300 | return oPolygon; 301 | }; 302 | const _createLineObject = (sId: string, oContext: any, oGeomap: MDCGeomap) => { 303 | const oLine = new GeomapLine({ 304 | width: 10, 305 | points: oContext.geometry.coordinates.map((aPoint: any[]) => { 306 | return new GeomapPoint({ 307 | lng: aPoint[0], 308 | lat: aPoint[1] 309 | }); 310 | }) 311 | }); 312 | return oLine; 313 | }; 314 | 315 | /** 316 | * Updates the binding info 317 | * @param oGeomap 318 | * @param oBindingInfo 319 | */ 320 | JSONGeomapDelegate.updateBindingInfo = function (oGeomap: MDCGeomap, oBindingInfo: any) { 321 | JSONGeomapDelegate.updateBindingInfo.call(JSONGeomapDelegate, oGeomap, oBindingInfo); 322 | oBindingInfo.path = "/" + oGeomap.getPayload().collectionName; 323 | }; 324 | 325 | /** 326 | * Propagates the changes from the control to the inner Geomap instance (webc) 327 | * @param oGeomap 328 | * @param oChange 329 | */ 330 | JSONGeomapDelegate.propagateItemChangeToGeomap = function (oGeomap: MDCGeomap, oChange: any) { 331 | if (oChange.mutation === "insert") { 332 | if (oChange.child) { 333 | Log.info("Inserting item to geomap: " + oChange); 334 | } 335 | 336 | JSONGeomapDelegate.rebind(oGeomap, JSONGeomapDelegate.getBindingInfo(oGeomap)); 337 | } 338 | }; 339 | 340 | /** 341 | * Returns the inner Geomap instance (webc) 342 | * @param oGeomap 343 | * @returns {sap.ui.geomap.Geomap} 344 | * @private 345 | */ 346 | JSONGeomapDelegate._getInnerGeomap = function (oGeomap: MDCGeomap) { 347 | return oGeomap.getAggregation("_geomap"); 348 | }; 349 | 350 | /** 351 | * Returns the binding info for given geomap. 352 | * If no binding info exists yet, a new one will be created. 353 | * @param {sap.ui.mdc.Geomap} oGeomap Reference to the MDC geomap 354 | * @returns {object} BindingInfo object 355 | * 356 | * @experimental 357 | * @private 358 | * @ui5-restricted sap.fe, sap.ui.mdc 359 | */ 360 | JSONGeomapDelegate.getBindingInfo = function (oGeomap: MDCGeomap) { 361 | 362 | const sEntitySetPath = "/" + JSONGeomapDelegate._getMetadataInfo(oGeomap).collectionName; 363 | const oBindingInfo = { 364 | path: sEntitySetPath 365 | }; 366 | return oBindingInfo; 367 | }; 368 | 369 | /** 370 | * Returns the current zoom level of the inner Geomap instance (webc) 371 | * @param oGeomap 372 | * @returns {number} 373 | */ 374 | JSONGeomapDelegate.getZoomLevel = function(oGeomap: MDCGeomap) { 375 | oGeomap.getControlDelegate()._getInnerGeomap(oGeomap).getZoom(); 376 | }; 377 | 378 | /** 379 | * Sets the zoom level of the inner Geomap instance (webc) 380 | * @param oGeomap 381 | * @param iZoomLevel 382 | */ 383 | JSONGeomapDelegate.zoomIn = function(oGeomap: MDCGeomap) { 384 | oGeomap.getAggregation("_geomap").setZoom(oGeomap.getAggregation("_geomap").getZoom() + 1); 385 | }; 386 | 387 | /** 388 | * Sets the zoom level of the inner Geomap instance (webc) 389 | * @param oGeomap 390 | * @param iZoomLevel 391 | */ 392 | JSONGeomapDelegate.zoomOut = function(oGeomap: MDCGeomap) { 393 | oGeomap.getAggregation("_geomap").setZoom(oGeomap.getAggregation("_geomap").getZoom() - 1); 394 | }; 395 | 396 | JSONGeomapDelegate.getGeomapBound = function(oGeomap: MDCGeomap) { 397 | const oState = this._getState(oGeomap); 398 | return !!oState?.innerGeomap; 399 | }; 400 | 401 | JSONGeomapDelegate.getControlPositions = function() { 402 | return { 403 | controlPositions: { 404 | navigation: GeomapControlPosition.TopLeft, 405 | selection: GeomapControlPosition.TopRight, 406 | fullscreen: GeomapControlPosition.TopRight, 407 | scale: GeomapControlPosition.BottomLeft 408 | } 409 | }; 410 | }; 411 | 412 | JSONGeomapDelegate.getProvider = function() { 413 | const osm = sap.ui.require.toUrl("mdc/tutorial/model/osm.json"); 414 | return `${window.location.origin}/${osm}`; 415 | }; 416 | 417 | JSONGeomapDelegate._setBindingInfoForState = function(oGeomap: MDCGeomap, oBindingInfo: any) { 418 | if (mStateMap.has(oGeomap)) { 419 | mStateMap.get(oGeomap).bindingInfo = oBindingInfo; 420 | } else { 421 | mStateMap.set(oGeomap, { bindingInfo: oBindingInfo }); 422 | } 423 | }; 424 | 425 | JSONGeomapDelegate.rebind = function(oGeomap: MDCGeomap, oBindingInfo: any) { 426 | if (oGeomap && oBindingInfo && this._getInnerGeomap(oGeomap)) { 427 | 428 | if (oBindingInfo.binding) { 429 | oBindingInfo.binding.bHasAnalyticalInfo = true; 430 | } 431 | 432 | JSONGeomapDelegate._createContentFromPropertyInfos(oGeomap, true); 433 | 434 | this._setBindingInfoForState(oGeomap, oBindingInfo); 435 | } 436 | }; 437 | 438 | export default JSONGeomapDelegate -------------------------------------------------------------------------------- /u2/ex1/readme.md: -------------------------------------------------------------------------------- 1 | [![solution](https://flat.badgen.net/badge/solution/available/green?icon=github)](webapp) 2 | 3 | # Exercise 6: How to Use the MDC Geomap 4 | In this exercise we will learn how to create a JSONGeomapDelegate for an MDC Geomap in an XMLView. These elements are crucial when building an MDC application that interacts with JSON data. 5 | 6 | ## Step 1: Create a JSONGeomapDelegate 7 | 8 | Firstly, let's create a new directory named `delegate` within the `webapp` directory. Also, add a JavaScript file named `JSONGeomapDelegate.ts` inside the `delegate` directory. 9 | 10 | This file serves as a delegate for a UI5 geomap. Delegates offer a method to customize the behavior of a control without modifying the control itself. In this example, it contains the logic of how the geomap interacts with the sample JSON data. 11 | 12 | Below is the code for the delegate. It extends the [`sap/ui/mdc/GeomapDelegate`](https://sdk.openui5.org/api/module:sap/ui/mdc/GeomapDelegate) and includes functions to extract properties from the JSON metadata provided in `JSONPropertyInfo.ts` in the model folder, add items to the geomap, delete items from the geomap, and revise the geomap's binding information. 13 | 14 | Thanks to TypeScript we can provide a delegate-specific interface for the payload, which clearly defines what content can be provided. In this case, the `bindingPath` is specified, so that the geomap knows from where to get its data. Take a look at the implementation! 15 | (We separate it in parts with some explanations to bring more clarity.) 16 | 17 | ### Create the delegate extending the base one 18 | 19 | In the sample code below, we create a new delegate we will later use for our Geomap control. 20 | 21 | ###### delegate/JSONGeomapDelegate.ts 22 | ```typescript 23 | import GeomapDelegate from "sap/ui/mdc/GeomapDelegate" 24 | import JSONPropertyInfo from "mdc/tutorial/model/metadata/JSONPropertyInfo" 25 | import {Geomap as MDCGeomap, PropertyInfo as GeomapPropertyInfo} from "sap/ui/mdc/Geomap" 26 | import JSONBaseDelegate from "./JSONBaseDelegate" 27 | 28 | interface GeomapPayload { 29 | bindingPath: string 30 | } 31 | 32 | const JSONGeomapDelegate = Object.assign({}, GeomapDelegate, JSONBaseDelegate) 33 | 34 | export default JSONGeomapDelegate 35 | ``` 36 | 37 | ### Implement PropertyInfo handling 38 | 39 | The next part adds some methods responsible for working with the `propertyInfo` object. 40 | 41 | ###### delegate/JSONGeomapDelegate.ts: propertyInfo 42 | ```typescript 43 | JSONGeomapDelegate.fetchProperties = function (oGeomap: MDCGeomap) { 44 | const aProperties = JSONPropertyInfo.filter((oPI) => oPI.key !== "$search"); 45 | 46 | oGeomap.awaitPropertyHelper().then(function(oPropertyHelper: any) { 47 | oPropertyHelper.setProperties(aProperties); 48 | }); 49 | 50 | oGeomap.setPropertyInfo(aProperties); 51 | 52 | return aProperties; 53 | }; 54 | 55 | JSONGeomapDelegate._getModel = function (oGeomap: MDCGeomap) { 56 | const oMetadataInfo = oGeomap.getDelegate().payload; 57 | return oGeomap.getModel(oMetadataInfo.collectionPath); 58 | }; 59 | 60 | JSONGeomapDelegate.initializeGeomap = function (oGeomap: MDCGeomap) { 61 | return JSONGeomapDelegate._createContentFromPropertyInfos(oGeomap); 62 | }; 63 | 64 | JSONGeomapDelegate.updateBindingInfo = function (oGeomap: MDCGeomap, oBindingInfo: any) { 65 | JSONGeomapDelegate.updateBindingInfo.call(JSONGeomapDelegate, oGeomap, oBindingInfo); 66 | oBindingInfo.path = "/" + oGeomap.getPayload().collectionName; 67 | }; 68 | 69 | JSONGeomapDelegate._getInnerGeomap = function (oGeomap: MDCGeomap) { 70 | return oGeomap.getAggregation("_geomap"); 71 | }; 72 | 73 | JSONGeomapDelegate.getBindingInfo = function (oGeomap: MDCGeomap) { 74 | 75 | const sEntitySetPath = "/" + JSONGeomapDelegate._getMetadataInfo(oGeomap).collectionName; 76 | const oBindingInfo = { 77 | path: sEntitySetPath 78 | }; 79 | return oBindingInfo; 80 | }; 81 | 82 | ``` 83 | 84 | >⚠️ The `fetchProperties` is a special function as its return value is used for further UI adaptation functionalities. Due to this, the result of this function must be kept stable throughout the lifecycle of your application. Any changes of the returned values might result in undesired effects. As we're using a PropertyInfo at this point, be aware to keep it stable throughout the lifecycle of your application. 85 | 86 | `PropertyInfo` provides all necessary metadata for the MDC Geomap to function. Take a look at this excerpt of the `JSONPropertyInfo.ts` file to understand how the `geometry` property is defined. 87 | ###### model/metadata/JSONPropertInfo.ts 88 | ```js 89 | { 90 | key: "geometry", 91 | label: "Geometry", 92 | visible: true, 93 | path: "geometry", 94 | dataType: "mdc.tutorial.model.type.Geometry" 95 | } 96 | ``` 97 | >ℹ️ For a comprehensive description of what information `PropertyInfo` objects should contain, see the [API Reference](https://sdk.openui5.org/api/sap.ui.mdc.geomap.PropertyInfo). In real-life scenarios, we may retrieve this metadata from the data service and we would have to translate it into the format of the `PropertyInfo` object - easy to digest for the controls.<br/> 98 | >ℹ️ As the `mdc.tutorial.model.type.Geometry` is a custom type, you may need to check [How to Add Custom Types](../u1/ex4/). 99 | 100 | ### Working with `sap.ui.geomap`. 101 | In this tutorial, we're using a `sap.ui.geomap` library for an internal map. It is part of SAPUI5 and requires [SAP Developer License Agreement](https://tools.hana.ondemand.com/developer-license.txt) 102 | 103 | ###### delegate/JSONGeomapDelegate.ts: import `sap.ui.geomap` 104 | ```typescript 105 | import Geomap from 'sap/ui/geomap/Geomap' 106 | import GeomapProvider from 'sap/ui/geomap/GeomapProvider' 107 | import GeomapSpot from 'sap/ui/geomap/GeomapSpot' 108 | import GeomapNavigationControl from 'sap/ui/geomap/GeomapNavigationControl' 109 | import GeomapSelectionControl from 'sap/ui/geomap/GeomapSelectionControl' 110 | import GeomapScaleControl from 'sap/ui/geomap/GeomapScaleControl' 111 | import GeomapCopyrightControl from 'sap/ui/geomap/GeomapCopyrightControl' 112 | import GeomapFullscreenControl from 'sap/ui/geomap/GeomapFullscreenControl' 113 | import GeomapControlPosition from "sap/ui/mdc/enums/GeomapControlPosition" 114 | import GeomapPolygon from "sap/ui/geomap/GeomapPolygon" 115 | import GeomapPoint from "sap/ui/geomap/GeomapPoint" 116 | import GeomapLine from "sap/ui/geomap/GeomapLine" 117 | import JSONModel from "sap/ui/model/json/JSONModel" 118 | ``` 119 | 120 | ###### delegate/JSONGeomapDelegate.ts: using of `sap.ui.geomap` 121 | ```typescript 122 | JSONGeomapDelegate._createContentFromPropertyInfos = function (oGeomap: MDCGeomap, bForceRebind: boolean) { 123 | return new Promise(function (resolve, reject) { 124 | 125 | const aMapControls = []; 126 | 127 | const oControlPositions = JSONGeomapDelegate.getControlPositions().controlPositions; 128 | aMapControls.push(new GeomapProvider({ 129 | id: "mapProvider", 130 | styleUrl: JSONGeomapDelegate.getProvider() 131 | })); 132 | 133 | if (oGeomap.getEnableNavigationControl()) { 134 | aMapControls.push(new GeomapNavigationControl({ 135 | position: oControlPositions?.navigation ?? GeomapControlPosition.TopLeft 136 | })); 137 | } 138 | 139 | if (oGeomap.getEnableSelectionControl()) { 140 | aMapControls.push(new GeomapSelectionControl({ 141 | position: oControlPositions?.selection ?? GeomapControlPosition.TopRight 142 | })); 143 | } 144 | 145 | if (oGeomap.getEnableFullscreenControl()) { 146 | aMapControls.push(new GeomapFullscreenControl({ 147 | position: oControlPositions?.fullscreen ?? GeomapControlPosition.TopRight 148 | })); 149 | } 150 | 151 | if (oGeomap.getEnableScaleControl()) { 152 | aMapControls.push(new GeomapScaleControl({ 153 | position: oControlPositions?.scale ?? GeomapControlPosition.BottomLeft 154 | })); 155 | } 156 | 157 | if (oGeomap.getEnableCopyrightControl()) { 158 | aMapControls.push(new GeomapCopyrightControl({ 159 | position: GeomapControlPosition.BottomRight 160 | })); 161 | } 162 | 163 | const geomapInstance = new Geomap({ 164 | centerLat: oGeomap.getCenterLat(), 165 | centerLng: oGeomap.getCenterLng(), 166 | zoom: oGeomap.getZoom(), 167 | height: oGeomap.getWidth(), 168 | width: oGeomap.getHeight(), 169 | mapControls: aMapControls, 170 | items: [] 171 | }); 172 | 173 | geomapInstance.setModel(oGeomap.getModel()); 174 | 175 | // ... see next snippet 176 | 177 | geomapInstance.bindAggregation("items", { 178 | path: "/", 179 | factory: JSONGeomapDelegate.createItemTemplateFactory.bind(this, oGeomap), 180 | templateShareable: false 181 | }); 182 | 183 | oGeomap.setAggregation("_geomap", geomapInstance); 184 | oGeomap._applyConfigurations(); 185 | 186 | resolve(oGeomap); 187 | }); 188 | }; 189 | 190 | JSONGeomapDelegate.getControlPositions = function() { 191 | return { 192 | controlPositions: { 193 | navigation: GeomapControlPosition.TopLeft, 194 | selection: GeomapControlPosition.TopRight, 195 | fullscreen: GeomapControlPosition.TopRight, 196 | scale: GeomapControlPosition.BottomLeft 197 | } 198 | }; 199 | }; 200 | 201 | JSONGeomapDelegate.createItemTemplateFactory = (oGeomap: MDCGeomap, sId: string, oContext: any) => { 202 | const oContextObject = oContext.getObject(); 203 | let oTemplate; 204 | switch (oContextObject.geometry.type) { 205 | case "Point": { 206 | oTemplate = _createSpotObject("", oContextObject, oGeomap); 207 | break; 208 | } 209 | // ... handling for any other geo object types 210 | } 211 | return oTemplate; 212 | }; 213 | ``` 214 | 215 | The `sap.ui.geomap` control is a wrapper for the `Sap Geomap Web Component`, which aggregates various geo objects (spots, circles, polygons, etc.) into a single aggregation. However, a database allows having multiple lines with multiple columns containing data for different types of geo objects. Therefore, you must handle this mapping manually: 216 | 217 | ###### delegate/JSONGeomapDelegate.ts: model handling 218 | ```typescript 219 | JSONGeomapDelegate._createContentFromPropertyInfos = function (oGeomap: MDCGeomap, bForceRebind: boolean) { 220 | return new Promise(function (resolve, reject) { 221 | 222 | // ... see previous snippet 223 | 224 | // flatten the model 225 | const oModel = oGeomap.getModel(); 226 | if (bForceRebind) { 227 | oModel.fireRequestCompleted(); 228 | } 229 | if (oModel) { 230 | const oData = oModel.getData(); 231 | const aCollection = oData[oGeomap.getDelegate().payload.collectionName]; 232 | 233 | const aFlattened: any[] = []; 234 | 235 | const aInitiallyVisibleProperties = JSONGeomapDelegate.getVisibleProperties(oGeomap); 236 | 237 | aCollection.forEach(function (oFeature: any) { 238 | const aPropertyInfos = JSONGeomapDelegate.fetchProperties(oGeomap); 239 | aPropertyInfos.forEach((oPropertyInfo: any) => { 240 | 241 | if (oPropertyInfo.key && 242 | oPropertyInfo.dataType.toLowerCase().includes("geom") && 243 | !( 244 | aInitiallyVisibleProperties.length > 0 && 245 | !aInitiallyVisibleProperties.includes(oPropertyInfo.key) 246 | ) 247 | ) { 248 | if (oFeature[oPropertyInfo.path]) { 249 | aFlattened.push({ 250 | GeometryType: oPropertyInfo.dataType, 251 | path: oPropertyInfo.path, 252 | key: oPropertyInfo.key, 253 | geometry: oFeature[oPropertyInfo.path], 254 | properties: oFeature.properties || {} 255 | }); 256 | } 257 | } 258 | }); 259 | }); 260 | 261 | // Create flattened JSONModel 262 | const oFlatModel = new JSONModel(aFlattened); 263 | // Set the flattened model to the geomap instance 264 | geomapInstance.setModel(oFlatModel); 265 | geomapInstance.bindAggregation("items", { 266 | path: "/", 267 | factory: JSONGeomapDelegate.createItemTemplateFactory.bind(this, oGeomap), 268 | templateShareable: false 269 | }); 270 | } 271 | 272 | // ... see previous snippet 273 | 274 | resolve(oGeomap); 275 | }); 276 | }; 277 | ``` 278 | 279 | ###### delegate/JSONGeomapDelegate.ts: geo object factories 280 | ```typescript 281 | const _createSpotObject = (sId: string, oContext: any, oGeomap: MDCGeomap) => { 282 | const oSpot = new GeomapSpot({ 283 | lng: oContext.geometry.coordinates[0], 284 | lat: oContext.geometry.coordinates[1], 285 | height: oGeomap.getDelegate().payload?.spotConfig?.height, 286 | color: "#000000", 287 | icon: "map", 288 | width: "1rem" 289 | }); 290 | 291 | return oSpot; 292 | }; 293 | ``` 294 | 295 | The map requires a provider that shall be used for fetching map data such as vectors/raster tiles.<br/> 296 | This usually requires license/subscription. For this tutorial we will use OpenStreetMap.<br/> 297 | The following code snippet shows how this can be achieved by loading `osm.json` located in the model folder of this tutorial: 298 | 299 | ###### delegate/JSONGeomapDelegate.ts: the provider 300 | ```typescript 301 | JSONGeomapDelegate.getProvider = function() { 302 | const osm = sap.ui.require.toUrl("mdc/tutorial/model/osm.json"); 303 | return `${window.location.origin}/${osm}`; 304 | }; 305 | ``` 306 | 307 | >ℹ️ Full version of the JSONGeomapDelegate used in this sample is available [here](../webapp/delegate/JSONGeomapDelegate). 308 | 309 | ## Step 2: Prepare a bridge for worker 310 | As MDC Geomap uses the `sap.ui.geomap` library — built on top of the SAP Geomap Web Component and requiring a Web Worker — and because strict CSP policies apply, we need to create a "bridge" to enable the Web Worker 311 | This is achievable by simply creating a "worker.js" in the webapp root with the following: 312 | ```js 313 | importScripts(new URLSearchParams(self.location.search).get("worker")); 314 | ``` 315 | 316 | To elaborate a bit - since Geomap uses Web Workers internally, in environments with a strict Content Security Policy (CSP) the worker must be loaded by the application. 317 | We create a "bridge" between the library and the application so the SAP Geomap Web Component can automatically discover and use the worker URL. 318 | 319 | The library provides the path of the worker to be loaded and passes it to "worker.js" on app side which simply loads it using `importScripts`. 320 | 321 | ## Step 3: Use the MDC Geomap 322 | 323 | Next, let's enhance the XML view named `Mountains.view.xml` in the `view` directory. 324 | 325 | This XML view defines the user interface for a screen in our UI5 application. The view comprises a DynamicPage with a Geomap control in its content area. The geomap is set up to use our custom JSONGeomapDelegate. 326 | 327 | Below is the code we can add to the content aggregation of the DynamicPage in the XML view. It includes a geomap with some properties configurations. The corresponding model is automatically generated based on our sample data via the `manifest.json`. 328 | ###### view/Mountains.view.xml 329 | ```xml 330 | <f:content> 331 | <mdc:Geomap 332 | id="geomap" 333 | delegate="{ 334 | 'name': 'mdc/tutorial/delegate/JSONGeomapDelegate', 335 | 'payload': { 336 | bindingPath: 'mountains>/mountains', 337 | collectionName: 'mountains', 338 | infomodel: 'propertyinfos', 339 | spotConfig: { 340 | width: '3rem', 341 | height: '3rem', 342 | color: '#0057D2' 343 | } 344 | } 345 | }" 346 | centerLat="35.88138888888889" 347 | centerLng="76.51333333333334" 348 | zoom="4.5" 349 | width="700px" 350 | height="600px" 351 | enableSelectionControl="true" 352 | enableCopyrightControl="true" 353 | > 354 | </mdc:Geomap> 355 | </f:content> 356 | ``` 357 | Run the application and see how, with just a few lines of code we added, we get a geomap that shows the properties of our JSON data! 😱 358 | 359 | ![Exercise 6 Result](ex1.png) 360 | ## Summary 361 | 362 | The main takeaway is that delegates offer a potent mechanism to adapt the behavior of sap.ui.mdc controls without altering the controls themselves. With a custom delegate, we can customize a control to handle a specific type of data, such as JSON data. Furthermore, XML views provide declarative means to define the user interface for a screen in a UI5 application. 363 | 364 | ## License 365 | This package is provided under the terms of the [SAP Developer License Agreement](https://tools.hana.ondemand.com/developer-license.txt). --------------------------------------------------------------------------------