├── .editorconfig ├── .gitignore ├── .markdownlint.json ├── LICENSE ├── LICENSES └── Apache-2.0.txt ├── README.md ├── REUSE.toml ├── exercises ├── ex0 │ └── README.md ├── ex1 │ ├── README.md │ └── com.myorg.myapp │ │ ├── .editorconfig │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .yo-rc.json │ │ ├── LICENSE │ │ ├── README.md │ │ ├── karma-ci-cov.conf.js │ │ ├── karma-ci.conf.js │ │ ├── karma.conf.js │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── tsconfig.json │ │ ├── ui5-dist.yaml │ │ ├── ui5.yaml │ │ └── webapp │ │ ├── Component.ts │ │ ├── controller │ │ ├── App.controller.ts │ │ ├── BaseController.ts │ │ └── Main.controller.ts │ │ ├── i18n │ │ ├── i18n.properties │ │ ├── i18n_de.properties │ │ └── i18n_en.properties │ │ ├── index-cdn.html │ │ ├── index.html │ │ ├── manifest.json │ │ ├── model │ │ ├── formatter.ts │ │ └── models.ts │ │ ├── test │ │ ├── Test.qunit.html │ │ ├── integration │ │ │ ├── HelloJourney.ts │ │ │ ├── opaTests.qunit.ts │ │ │ └── pages │ │ │ │ └── MainPage.ts │ │ ├── testsuite.qunit.html │ │ ├── testsuite.qunit.ts │ │ └── unit │ │ │ ├── controller │ │ │ └── Main.qunit.ts │ │ │ └── unitTests.qunit.ts │ │ └── view │ │ ├── App.view.xml │ │ └── Main.view.xml ├── ex2 │ ├── README.md │ ├── com.myorg.myapp │ │ ├── .editorconfig │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .yo-rc.json │ │ ├── LICENSE │ │ ├── README.md │ │ ├── karma-ci-cov.conf.js │ │ ├── karma-ci.conf.js │ │ ├── karma.conf.js │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── tsconfig.json │ │ ├── ui5-dist.yaml │ │ ├── ui5.yaml │ │ └── webapp │ │ │ ├── Component.ts │ │ │ ├── controller │ │ │ ├── App.controller.ts │ │ │ ├── BaseController.ts │ │ │ └── Main.controller.ts │ │ │ ├── i18n │ │ │ ├── i18n.properties │ │ │ ├── i18n_de.properties │ │ │ └── i18n_en.properties │ │ │ ├── index-cdn.html │ │ │ ├── index.html │ │ │ ├── manifest.json │ │ │ ├── model │ │ │ ├── formatter.ts │ │ │ └── models.ts │ │ │ ├── test │ │ │ ├── Test.qunit.html │ │ │ ├── integration │ │ │ │ ├── HelloJourney.ts │ │ │ │ ├── opaTests.qunit.ts │ │ │ │ └── pages │ │ │ │ │ └── MainPage.ts │ │ │ ├── testsuite.qunit.html │ │ │ ├── testsuite.qunit.ts │ │ │ └── unit │ │ │ │ ├── controller │ │ │ │ └── Main.qunit.ts │ │ │ │ └── unitTests.qunit.ts │ │ │ └── view │ │ │ ├── App.view.xml │ │ │ └── Main.view.xml │ └── images │ │ ├── codeassist_jsonmodel.png │ │ ├── create_onInit.png │ │ ├── ex2.png │ │ ├── hover_init.png │ │ ├── jsonmodel_error.png │ │ └── jsonmodel_quickfix.png ├── ex3 │ ├── README.md │ ├── com.myorg.myapp │ │ ├── .editorconfig │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .yo-rc.json │ │ ├── LICENSE │ │ ├── README.md │ │ ├── karma-ci-cov.conf.js │ │ ├── karma-ci.conf.js │ │ ├── karma.conf.js │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── tsconfig.json │ │ ├── ui5-dist.yaml │ │ ├── ui5.yaml │ │ └── webapp │ │ │ ├── Component.ts │ │ │ ├── controller │ │ │ ├── App.controller.ts │ │ │ ├── BaseController.ts │ │ │ └── Main.controller.ts │ │ │ ├── i18n │ │ │ ├── i18n.properties │ │ │ ├── i18n_de.properties │ │ │ └── i18n_en.properties │ │ │ ├── index-cdn.html │ │ │ ├── index.html │ │ │ ├── manifest.json │ │ │ ├── model │ │ │ ├── formatter.ts │ │ │ └── models.ts │ │ │ ├── test │ │ │ ├── Test.qunit.html │ │ │ ├── integration │ │ │ │ ├── HelloJourney.ts │ │ │ │ ├── opaTests.qunit.ts │ │ │ │ └── pages │ │ │ │ │ └── MainPage.ts │ │ │ ├── testsuite.qunit.html │ │ │ ├── testsuite.qunit.ts │ │ │ └── unit │ │ │ │ ├── controller │ │ │ │ └── Main.qunit.ts │ │ │ │ └── unitTests.qunit.ts │ │ │ └── view │ │ │ ├── App.view.xml │ │ │ └── Main.view.xml │ └── images │ │ ├── event_parameter_any_error.png │ │ ├── event_parameters_content_assist.png │ │ ├── ex3.png │ │ └── wrong_event.png ├── ex4 │ ├── README.md │ ├── com.myorg.myapp │ │ ├── .editorconfig │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .yo-rc.json │ │ ├── LICENSE │ │ ├── README.md │ │ ├── karma-ci-cov.conf.js │ │ ├── karma-ci.conf.js │ │ ├── karma.conf.js │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── tsconfig.json │ │ ├── ui5-dist.yaml │ │ ├── ui5.yaml │ │ └── webapp │ │ │ ├── Component.ts │ │ │ ├── controller │ │ │ ├── App.controller.ts │ │ │ ├── BaseController.ts │ │ │ └── Main.controller.ts │ │ │ ├── i18n │ │ │ ├── i18n.properties │ │ │ ├── i18n_de.properties │ │ │ └── i18n_en.properties │ │ │ ├── index-cdn.html │ │ │ ├── index.html │ │ │ ├── manifest.json │ │ │ ├── model │ │ │ ├── formatter.ts │ │ │ └── models.ts │ │ │ ├── test │ │ │ ├── Test.qunit.html │ │ │ ├── integration │ │ │ │ ├── HelloJourney.ts │ │ │ │ ├── opaTests.qunit.ts │ │ │ │ └── pages │ │ │ │ │ └── MainPage.ts │ │ │ ├── testsuite.qunit.html │ │ │ ├── testsuite.qunit.ts │ │ │ └── unit │ │ │ │ ├── controller │ │ │ │ └── Main.qunit.ts │ │ │ │ └── unitTests.qunit.ts │ │ │ └── view │ │ │ ├── App.view.xml │ │ │ └── Main.view.xml │ └── images │ │ ├── ex4.png │ │ ├── nominatim_createClient.png │ │ └── placeName_error.png ├── ex5 │ ├── README.md │ ├── com.myorg.myapp │ │ ├── .editorconfig │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .yo-rc.json │ │ ├── LICENSE │ │ ├── README.md │ │ ├── karma-ci-cov.conf.js │ │ ├── karma-ci.conf.js │ │ ├── karma.conf.js │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── tsconfig.json │ │ ├── ui5-dist.yaml │ │ ├── ui5.yaml │ │ └── webapp │ │ │ ├── Component.ts │ │ │ ├── control │ │ │ ├── WindDirection.gen.d.ts │ │ │ └── WindDirection.ts │ │ │ ├── controller │ │ │ ├── App.controller.ts │ │ │ ├── BaseController.ts │ │ │ └── Main.controller.ts │ │ │ ├── i18n │ │ │ ├── i18n.properties │ │ │ ├── i18n_de.properties │ │ │ └── i18n_en.properties │ │ │ ├── index-cdn.html │ │ │ ├── index.html │ │ │ ├── manifest.json │ │ │ ├── model │ │ │ ├── formatter.ts │ │ │ └── models.ts │ │ │ ├── test │ │ │ ├── Test.qunit.html │ │ │ ├── integration │ │ │ │ ├── HelloJourney.ts │ │ │ │ ├── opaTests.qunit.ts │ │ │ │ └── pages │ │ │ │ │ └── MainPage.ts │ │ │ ├── testsuite.qunit.html │ │ │ ├── testsuite.qunit.ts │ │ │ └── unit │ │ │ │ ├── controller │ │ │ │ └── Main.qunit.ts │ │ │ │ └── unitTests.qunit.ts │ │ │ └── view │ │ │ ├── App.view.xml │ │ │ └── Main.view.xml │ └── images │ │ ├── Constructor_Suggestions.png │ │ └── getDirection_error.png └── ex6 │ ├── README.md │ └── com.myorg.myapp │ ├── .editorconfig │ ├── .eslintrc.js │ ├── .gitignore │ ├── .yo-rc.json │ ├── LICENSE │ ├── README.md │ ├── karma-ci-cov.conf.js │ ├── karma-ci.conf.js │ ├── karma.conf.js │ ├── package-lock.json │ ├── package.json │ ├── tsconfig.json │ ├── ui5-dist.yaml │ ├── ui5.yaml │ └── webapp │ ├── Component.ts │ ├── control │ ├── WindDirection.gen.d.ts │ └── WindDirection.ts │ ├── controller │ ├── App.controller.ts │ ├── BaseController.ts │ └── Main.controller.ts │ ├── i18n │ ├── i18n.properties │ ├── i18n_de.properties │ └── i18n_en.properties │ ├── index-cdn.html │ ├── index.html │ ├── manifest.json │ ├── model │ ├── formatter.ts │ └── models.ts │ ├── test │ ├── Test.qunit.html │ ├── integration │ │ ├── HelloJourney.ts │ │ ├── opaTests.qunit.ts │ │ └── pages │ │ │ └── MainPage.ts │ ├── testsuite.qunit.html │ ├── testsuite.qunit.ts │ └── unit │ │ ├── control │ │ └── WindDirection.qunit.ts │ │ ├── controller │ │ └── Main.qunit.ts │ │ └── unitTests.qunit.ts │ └── view │ ├── App.view.xml │ └── Main.view.xml └── generator ├── .editorconfig ├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── .lintstagedrc.json ├── .prettierignore ├── .prettierrc.json ├── LICENSE ├── README.md ├── generators └── app │ ├── index.js │ └── templates │ ├── LICENSE │ ├── README.md │ ├── _.editorconfig │ ├── _.eslintrc.js │ ├── _.gitignore │ ├── karma-ci-cov.conf.js │ ├── karma-ci.conf.js │ ├── karma.conf.js │ ├── package.json │ ├── tsconfig.json │ ├── ui5-dist.yaml │ ├── ui5.yaml │ └── webapp │ ├── Component.ts │ ├── controller │ ├── App.controller.ts │ ├── BaseController.ts │ └── Main.controller.ts │ ├── i18n │ ├── i18n.properties │ ├── i18n_de.properties │ └── i18n_en.properties │ ├── index-cdn.html │ ├── index.html │ ├── manifest.json │ ├── model │ ├── formatter.ts │ └── models.ts │ ├── test │ ├── Test.qunit.html │ ├── integration │ │ ├── HelloJourney.ts │ │ ├── opaTests.qunit.ts │ │ └── pages │ │ │ └── MainPage.ts │ ├── testsuite.qunit.html │ ├── testsuite.qunit.ts │ └── unit │ │ ├── controller │ │ └── Main.qunit.ts │ │ └── unitTests.qunit.ts │ └── view │ ├── App.view.xml │ └── Main.view.xml ├── package-lock.json └── package.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | 7 | [*.{css,html,ts,js,xml,less,txt}] 8 | trim_trailing_whitespace = true 9 | end_of_line = lf 10 | tab_width = 4 11 | 12 | [*.{json,yaml}] 13 | indent_style = space 14 | indent_size = 2 15 | 16 | [*.md] 17 | trim_trailing_whitespace = false 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | node_modules 3 | 4 | .DS_Store 5 | 2022 -------------------------------------------------------------------------------- /.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "MD010": { 3 | "code_blocks": false 4 | }, 5 | "MD013": false 6 | } -------------------------------------------------------------------------------- /REUSE.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | SPDX-PackageName = "ui5-typescript-tutorial" 3 | SPDX-PackageSupplier = "OpenUI5 Team (openui5@sap.com)" 4 | SPDX-PackageDownloadLocation = "https://github.com/SAP-samples/ui5-typescript-tutorial" 5 | SPDX-PackageComment = "The code in this project may include calls to APIs (“API Calls”) of\n SAP or third-party products or services developed outside of this project\n (“External Products”).\n “APIs” means application programming interfaces, as well as their respective\n specifications and implementing code that allows software to communicate with\n other software.\n API Calls to External Products are not licensed under the open source license\n that governs this project. The use of such API Calls and related External\n Products are subject to applicable additional agreements with the relevant\n provider of the External Products. In no event shall the open source license\n that governs this project grant any rights in or to any External Products,or\n alter, expand or supersede any terms of the applicable additional agreements.\n If you have a valid license agreement with SAP for the use of a particular SAP\n External Product, then you may make use of any API Calls included in this\n project’s code for that SAP External Product, subject to the terms of such\n license agreement. If you do not have a valid license agreement for the use of\n a particular SAP External Product, then you may only make use of any API Calls\n in this project for that SAP External Product for your internal, non-productive\n and non-commercial test and evaluation of such API Calls. Nothing herein grants\n you any rights to use or access any SAP External Product, or provide any third\n parties the right to use of access any SAP External Product, through API Calls." 6 | 7 | [[annotations]] 8 | path = "**" 9 | precedence = "aggregate" 10 | SPDX-FileCopyrightText = "2023 SAP SE or an SAP affiliate company and ui5-typescript-tutorial contributors" 11 | SPDX-License-Identifier = "Apache-2.0" 12 | -------------------------------------------------------------------------------- /exercises/ex0/README.md: -------------------------------------------------------------------------------- 1 | # Exercise 0 - Get Prepared 2 | 3 | To get started, you need to ensure that you have a recent [Node.js](https://nodejs.org/de/download) version (recommended: 18.x or 20.x) installed on your machine. 4 | 5 | ## Install Yeoman And Easy UI5 6 | 7 | Next to [Node.js](https://nodejs.org/de/download) you need to install [Yeoman](https://yeoman.io/) and the [Easy UI5 Generator](https://github.com/SAP/generator-easy-ui5). Both NPM packages will be installed globally by entering the following command in your console: 8 | 9 | ```sh 10 | npm install -g yo generator-easy-ui5 11 | ``` 12 | 13 | Please verify your installation to see if Yeoman has been installed correctly and the Easy UI5 Generator is available by executing the following command in your console: 14 | 15 | ```sh 16 | yo --generators 17 | ``` 18 | 19 | Make sure that `easy-ui5` is listed. 20 | 21 | To verify the version of the installed generator-easy-ui5 you can run the following command: 22 | 23 | ```sh 24 | npm list -g "generator-easy-ui5" 25 | ``` 26 | 27 | > :warning: **Remark:** The version must be at least **```3.6.2```** to be able to consume the latest template from this repository available [here](https://github.com/SAP-samples/ui5-typescript-tutorial/tree/main/generator)!
28 | > When using Node.js 21.x or higher, you need at least version 3.7.0 of the Easy UI5 Generator.
29 | > For the tutorial we used the Easy UI5 Generator 3.6.2. You can also explicitly install this version via `npm install -g generator-easy-ui5@3.6.2`. 30 | 31 | ## Summary 32 | 33 | Now that you have prepared your computer you can go ahead and create your first UI5 TypeScript application. 34 | 35 | Continue to - [Exercise 1 - Create And Run Your Application](../ex1/README.md) 36 | -------------------------------------------------------------------------------- /exercises/ex1/com.myorg.myapp/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | # We recommend you to keep these unchanged 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | 14 | # Change these settings to your own preference 15 | indent_style = tab 16 | indent_size = 2 17 | 18 | [*.{yaml,yml}] 19 | indent_style = space 20 | 21 | [*.md] 22 | indent_style = unset 23 | trim_trailing_whitespace = false 24 | -------------------------------------------------------------------------------- /exercises/ex1/com.myorg.myapp/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "root": true, 3 | "env": { 4 | "browser": true, 5 | "es6": true, 6 | "node": true 7 | }, 8 | "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:@typescript-eslint/recommended-requiring-type-checking"], 9 | "parser": "@typescript-eslint/parser", 10 | "parserOptions": { 11 | "project": [`${__dirname}/tsconfig.json`], 12 | "sourceType": "module" 13 | }, 14 | "plugins": ["@typescript-eslint"], 15 | "ignorePatterns": [".eslintrc.js"] 16 | }; 17 | -------------------------------------------------------------------------------- /exercises/ex1/com.myorg.myapp/.gitignore: -------------------------------------------------------------------------------- 1 | # build results 2 | dist 3 | coverage 4 | 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | 12 | # Dependency directories 13 | node_modules/ 14 | 15 | .DS_Store 16 | .env 17 | -------------------------------------------------------------------------------- /exercises/ex1/com.myorg.myapp/.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-ui5-ts-app": { 3 | "namespace": "com.myorg.myapp", 4 | "framework": "OpenUI5", 5 | "frameworkVersion": "1.115.1", 6 | "author": "OpenUI5 Team", 7 | "initrepo": false, 8 | "tstypes": "@openui5/types", 9 | "tstypesVersion": "1.115.1", 10 | "appId": "com.myorg.myapp", 11 | "appURI": "com/myorg/myapp", 12 | "cdnDomain": "sdk.openui5.org", 13 | "defaultTheme": "sap_horizon", 14 | "gte11150": true, 15 | "setupCompleted": true 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /exercises/ex1/com.myorg.myapp/README.md: -------------------------------------------------------------------------------- 1 | # UI5 Application com.myorg.myapp 2 | 3 | Insert the purpose of this project and some interesting info here... 4 | 5 | ## Description 6 | 7 | This app demonstrates a TypeScript setup for developing UI5 applications. The central entry point for all information about using TypeScript with UI5 is at [https://sap.github.io/ui5-typescript](https://sap.github.io/ui5-typescript). 8 | 9 | **The template is inspired by the [`SAP-samples/ui5-typescript-helloworld`](https://github.com/SAP-samples/ui5-typescript-helloworld) project which also contains [a detailed step-by-step guide](https://github.com/SAP-samples/ui5-typescript-helloworld/blob/main/step-by-step.md). It explains how this setup is created and how all the bits and pieces fit together.** 10 | 11 | ## Requirements 12 | 13 | Either [npm](https://www.npmjs.com/) or [yarn](https://yarnpkg.com/) for dependency management. 14 | 15 | ## Preparation 16 | 17 | Use `npm` (or `yarn`) to install the dependencies: 18 | 19 | ```sh 20 | npm install 21 | ``` 22 | 23 | (To use yarn, just do `yarn` instead.) 24 | 25 | ## Run the App 26 | 27 | Execute the following command to run the app locally for development in watch mode (the browser reloads the app automatically when there are changes in the source code): 28 | 29 | ```sh 30 | npm start 31 | ``` 32 | 33 | As shown in the terminal after executing this command, the app is then running on http://localhost:8080/index.html. A browser window with this URL should automatically open. 34 | 35 | (When using yarn, do `yarn start` instead.) 36 | 37 | ## Debug the App 38 | 39 | In the browser, you can directly debug the original TypeScript code, which is supplied via sourcemaps (need to be enabled in the browser's developer console if it does not work straight away). If the browser doesn't automatically jump to the TypeScript code when setting breakpoints, use e.g. `Ctrl`/`Cmd` + `P` in Chrome to open the `*.ts` file you want to debug. 40 | 41 | ## Build the App 42 | 43 | ### Unoptimized (but quick) 44 | 45 | Execute the following command to build the project and get an app that can be deployed: 46 | 47 | ```sh 48 | npm run build 49 | ``` 50 | 51 | The result is placed into the `dist` folder. To start the generated package, just run 52 | 53 | ```sh 54 | npm run start:dist 55 | ``` 56 | 57 | Note that `index.html` still loads the UI5 framework from the relative URL `resources/...`, which does not physically exist, but is only provided dynamically by the UI5 tooling. So for an actual deployment you should change this URL to either [the CDN](https://sdk.openui5.org/#/topic/2d3eb2f322ea4a82983c1c62a33ec4ae) or your local deployment of UI5. 58 | 59 | (When using yarn, do `yarn build` and `yarn start:dist` instead.) 60 | 61 | ### Optimized 62 | 63 | For an optimized self-contained build (takes longer because the UI5 resources are built, too), do: 64 | 65 | ```sh 66 | npm run build:opt 67 | ``` 68 | 69 | To start the generated package, again just run: 70 | 71 | ```sh 72 | npm run start:dist 73 | ``` 74 | 75 | In this case, all UI5 framework resources are also available within the `dist` folder, so the folder can be deployed as-is to any static web server, without changing the bootstrap URL. 76 | 77 | With the self-contained build, the bootstrap URL in `index.html` has already been modified to load the newly created `sap-ui-custom.js` for bootstrapping, which contains all app resources as well as all needed UI5 JavaScript resources. Most UI5 resources inside the `dist` folder are for this reason actually **not** needed to run the app. Only the non-JS-files, like translation texts and CSS files, are used and must also be deployed. (Only when for some reason JS files are missing from the optimized self-contained bundle, they are also loaded separately.) 78 | 79 | (When using yarn, do `yarn build:opt` and `yarn start:dist` instead.) 80 | 81 | ## Check the Code 82 | 83 | Do the following to run a TypeScript check: 84 | 85 | ```sh 86 | npm run ts-typecheck 87 | ``` 88 | 89 | This checks the application code for any type errors (but will also complain in case of fundamental syntax issues which break the parsing). 90 | 91 | To lint the TypeScript code, do: 92 | 93 | ```sh 94 | npm run lint 95 | ``` 96 | 97 | (Again, when using yarn, do `yarn ts-typecheck` and `yarn lint` instead.) 98 | 99 | ## License 100 | 101 | This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](LICENSE) file. 102 | -------------------------------------------------------------------------------- /exercises/ex1/com.myorg.myapp/karma-ci-cov.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function (config) { 2 | require("./karma-ci.conf")(config); 3 | config.set({ 4 | reporters: ["progress", "coverage"], 5 | preprocessors: { 6 | "webapp/**/*.ts": ["ui5-transpile"] 7 | }, 8 | coverageReporter: { 9 | dir: "coverage", 10 | reporters: [ 11 | { type: "html", subdir: "report-html" }, 12 | { type: "cobertura", subdir: ".", file: "cobertura.txt" }, 13 | { type: "lcovonly", subdir: ".", file: "report-lcovonly.txt" }, 14 | { type: "text-summary" } 15 | ] 16 | } 17 | }); 18 | }; 19 | -------------------------------------------------------------------------------- /exercises/ex1/com.myorg.myapp/karma-ci.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function (config) { 2 | require("./karma.conf")(config); 3 | config.set({ 4 | browsers: ["ChromeHeadless"], 5 | singleRun: true 6 | }); 7 | }; 8 | -------------------------------------------------------------------------------- /exercises/ex1/com.myorg.myapp/karma.conf.js: -------------------------------------------------------------------------------- 1 | // karma-ui5 usage: https://github.com/SAP/karma-ui5 2 | module.exports = function (config) { 3 | config.set({ 4 | frameworks: ["ui5"], 5 | browsers: ["Chrome"] 6 | }); 7 | }; 8 | -------------------------------------------------------------------------------- /exercises/ex1/com.myorg.myapp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.myorg.myapp", 3 | "version": "1.0.0", 4 | "description": "UI5 Application: com.myorg.myapp", 5 | "author": "OpenUI5 Team", 6 | "license": "Apache-2.0", 7 | "scripts": { 8 | "clean": "rimraf dist coverage", 9 | "build": "ui5 build --clean-dest", 10 | "build:opt": "ui5 build self-contained --clean-dest --all", 11 | "start": "ui5 serve --port 8080 -o index.html", 12 | "start-cdn": "ui5 serve --port 8080 -o index-cdn.html", 13 | "start:dist": "npm start -- --config ui5-dist.yaml", 14 | "start:dist-cdn": "npm run start-cdn -- --config ui5-dist.yaml", 15 | "ts-typecheck": "tsc --noEmit", 16 | "lint": "eslint webapp", 17 | "karma": "karma start", 18 | "karma-ci": "karma start karma-ci.conf.js", 19 | "karma-ci-cov": "karma start karma-ci-cov.conf.js", 20 | "test": "npm run lint && npm run karma-ci-cov" 21 | }, 22 | "devDependencies": { 23 | "@openui5/types": "1.131.1", 24 | "@typescript-eslint/eslint-plugin": "^5.61.0", 25 | "@typescript-eslint/parser": "^5.61.0", 26 | "@ui5/cli": "^4.0.11", 27 | "eslint": "^8.44.0", 28 | "karma": "^6.4.2", 29 | "karma-chrome-launcher": "^3.2.0", 30 | "karma-coverage": "^2.2.1", 31 | "karma-ui5": "^3.0.3", 32 | "karma-ui5-transpile": "^0.3.22", 33 | "rimraf": "^5.0.1", 34 | "typescript": "^5.1.6", 35 | "ui5-middleware-livereload": "^0.8.4", 36 | "ui5-tooling-transpile": "^0.7.17" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /exercises/ex1/com.myorg.myapp/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", "@types/qunit"], 13 | "paths": { 14 | "com/myorg/myapp/*": ["./webapp/*"], 15 | "unit/*": ["./webapp/test/unit/*"], 16 | "integration/*": ["./webapp/test/integration/*"] 17 | } 18 | }, 19 | "include": ["./webapp/**/*"], 20 | "exclude": ["./webapp/coverage/**/*"] 21 | } 22 | -------------------------------------------------------------------------------- /exercises/ex1/com.myorg.myapp/ui5-dist.yaml: -------------------------------------------------------------------------------- 1 | specVersion: "3.0" 2 | metadata: 3 | name: com.myorg.myapp 4 | type: application 5 | resources: 6 | configuration: 7 | paths: 8 | webapp: dist 9 | framework: 10 | name: OpenUI5 11 | version: "1.131.1" 12 | libraries: 13 | - name: sap.m 14 | - name: sap.ui.core 15 | - name: themelib_sap_horizon 16 | -------------------------------------------------------------------------------- /exercises/ex1/com.myorg.myapp/ui5.yaml: -------------------------------------------------------------------------------- 1 | specVersion: "3.0" 2 | metadata: 3 | name: com.myorg.myapp 4 | type: application 5 | framework: 6 | name: OpenUI5 7 | version: "1.131.1" 8 | libraries: 9 | - name: sap.m 10 | - name: sap.ui.core 11 | - name: themelib_sap_horizon 12 | builder: 13 | customTasks: 14 | - name: ui5-tooling-transpile-task 15 | afterTask: replaceVersion 16 | server: 17 | customMiddleware: 18 | - name: ui5-tooling-transpile-middleware 19 | afterMiddleware: compression 20 | - name: ui5-middleware-livereload 21 | afterMiddleware: compression 22 | -------------------------------------------------------------------------------- /exercises/ex1/com.myorg.myapp/webapp/Component.ts: -------------------------------------------------------------------------------- 1 | import UIComponent from "sap/ui/core/UIComponent"; 2 | import models from "./model/models"; 3 | import Device from "sap/ui/Device"; 4 | 5 | /** 6 | * @namespace com.myorg.myapp 7 | */ 8 | export default class Component extends UIComponent { 9 | public static metadata = { 10 | manifest: "json", 11 | }; 12 | 13 | private contentDensityClass: string; 14 | 15 | public init(): void { 16 | // call the base component's init function 17 | super.init(); 18 | 19 | // create the device model 20 | this.setModel(models.createDeviceModel(), "device"); 21 | 22 | // create the views based on the url/hash 23 | this.getRouter().initialize(); 24 | } 25 | 26 | /** 27 | * This method can be called to determine whether the sapUiSizeCompact or sapUiSizeCozy 28 | * design mode class should be set, which influences the size appearance of some controls. 29 | * @public 30 | * @returns css class, either 'sapUiSizeCompact' or 'sapUiSizeCozy' - or an empty string if no css class should be set 31 | */ 32 | public getContentDensityClass(): string { 33 | if (this.contentDensityClass === undefined) { 34 | // check whether FLP has already set the content density class; do nothing in this case 35 | if (document.body.classList.contains("sapUiSizeCozy") || document.body.classList.contains("sapUiSizeCompact")) { 36 | this.contentDensityClass = ""; 37 | } else if (!Device.support.touch) { 38 | // apply "compact" mode if touch is not supported 39 | this.contentDensityClass = "sapUiSizeCompact"; 40 | } else { 41 | // "cozy" in case of touch support; default for most sap.m controls, but needed for desktop-first controls like sap.ui.table.Table 42 | this.contentDensityClass = "sapUiSizeCozy"; 43 | } 44 | } 45 | return this.contentDensityClass; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /exercises/ex1/com.myorg.myapp/webapp/controller/App.controller.ts: -------------------------------------------------------------------------------- 1 | import BaseController from "./BaseController"; 2 | 3 | /** 4 | * @namespace com.myorg.myapp.controller 5 | */ 6 | export default class App extends BaseController { 7 | public onInit(): void { 8 | // apply content density mode to root view 9 | this.getView().addStyleClass(this.getOwnerComponent().getContentDensityClass()); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /exercises/ex1/com.myorg.myapp/webapp/controller/BaseController.ts: -------------------------------------------------------------------------------- 1 | import Controller from "sap/ui/core/mvc/Controller"; 2 | import UIComponent from "sap/ui/core/UIComponent"; 3 | import AppComponent from "../Component"; 4 | import Model from "sap/ui/model/Model"; 5 | import ResourceModel from "sap/ui/model/resource/ResourceModel"; 6 | import ResourceBundle from "sap/base/i18n/ResourceBundle"; 7 | import Router from "sap/ui/core/routing/Router"; 8 | import History from "sap/ui/core/routing/History"; 9 | 10 | /** 11 | * @namespace com.myorg.myapp.controller 12 | */ 13 | export default abstract class BaseController extends Controller { 14 | /** 15 | * Convenience method for accessing the component of the controller's view. 16 | * @returns The component of the controller's view 17 | */ 18 | public getOwnerComponent(): AppComponent { 19 | return super.getOwnerComponent() as AppComponent; 20 | } 21 | 22 | /** 23 | * Convenience method to get the components' router instance. 24 | * @returns The router instance 25 | */ 26 | public getRouter(): Router { 27 | return UIComponent.getRouterFor(this); 28 | } 29 | 30 | /** 31 | * Convenience method for getting the i18n resource bundle of the component. 32 | * @returns The i18n resource bundle of the component 33 | */ 34 | public getResourceBundle(): ResourceBundle | Promise { 35 | const oModel = this.getOwnerComponent().getModel("i18n") as ResourceModel; 36 | return oModel.getResourceBundle(); 37 | } 38 | 39 | /** 40 | * Convenience method for getting the view model by name in every controller of the application. 41 | * @param [sName] The model name 42 | * @returns The model instance 43 | */ 44 | public getModel(sName?: string): Model { 45 | return this.getView().getModel(sName); 46 | } 47 | 48 | /** 49 | * Convenience method for setting the view model in every controller of the application. 50 | * @param oModel The model instance 51 | * @param [sName] The model name 52 | * @returns The current base controller instance 53 | */ 54 | public setModel(oModel: Model, sName?: string): BaseController { 55 | this.getView().setModel(oModel, sName); 56 | return this; 57 | } 58 | 59 | /** 60 | * Convenience method for triggering the navigation to a specific target. 61 | * @public 62 | * @param sName Target name 63 | * @param [oParameters] Navigation parameters 64 | * @param [bReplace] Defines if the hash should be replaced (no browser history entry) or set (browser history entry) 65 | */ 66 | public navTo(sName: string, oParameters?: object, bReplace?: boolean): void { 67 | this.getRouter().navTo(sName, oParameters, undefined, bReplace); 68 | } 69 | 70 | /** 71 | * Convenience event handler for navigating back. 72 | * It there is a history entry we go one step back in the browser history 73 | * If not, it will replace the current entry of the browser history with the main route. 74 | */ 75 | public onNavBack(): void { 76 | const sPreviousHash = History.getInstance().getPreviousHash(); 77 | if (sPreviousHash !== undefined) { 78 | window.history.go(-1); 79 | } else { 80 | this.getRouter().navTo("main", {}, undefined, true); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /exercises/ex1/com.myorg.myapp/webapp/controller/Main.controller.ts: -------------------------------------------------------------------------------- 1 | import MessageBox from "sap/m/MessageBox"; 2 | import BaseController from "./BaseController"; 3 | 4 | /** 5 | * @namespace com.myorg.myapp.controller 6 | */ 7 | export default class Main extends BaseController { 8 | public sayHello(): void { 9 | MessageBox.show("Hello World!"); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /exercises/ex1/com.myorg.myapp/webapp/i18n/i18n.properties: -------------------------------------------------------------------------------- 1 | appTitle=com.myorg.myapp 2 | appDescription=UI5 Application com.myorg.myapp 3 | btnText=Say Hello 4 | -------------------------------------------------------------------------------- /exercises/ex1/com.myorg.myapp/webapp/i18n/i18n_de.properties: -------------------------------------------------------------------------------- 1 | appTitle=com.myorg.myapp 2 | appDescription=UI5 Application com.myorg.myapp 3 | btnText=Sag Hallo 4 | -------------------------------------------------------------------------------- /exercises/ex1/com.myorg.myapp/webapp/i18n/i18n_en.properties: -------------------------------------------------------------------------------- 1 | appTitle=com.myorg.myapp 2 | appDescription=UI5 Application com.myorg.myapp 3 | btnText=Say Hello 4 | -------------------------------------------------------------------------------- /exercises/ex1/com.myorg.myapp/webapp/index-cdn.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | UI5 Application: com.myorg.myapp 11 | 12 | 26 | 27 | 28 | 29 |
30 | 31 | 32 | -------------------------------------------------------------------------------- /exercises/ex1/com.myorg.myapp/webapp/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | UI5 Application: com.myorg.myapp 10 | 11 | 25 | 26 | 27 | 28 |
29 | 30 | 31 | -------------------------------------------------------------------------------- /exercises/ex1/com.myorg.myapp/webapp/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "_version": "1.12.0", 3 | 4 | "sap.app": { 5 | "id": "com.myorg.myapp", 6 | "type": "application", 7 | "i18n": "i18n/i18n.properties", 8 | "title": "{{appTitle}}", 9 | "description": "{{appDescription}}", 10 | "applicationVersion": { 11 | "version": "1.0.0" 12 | } 13 | }, 14 | 15 | "sap.ui": { 16 | "technology": "UI5", 17 | "icons": {}, 18 | "deviceTypes": { 19 | "desktop": true, 20 | "tablet": true, 21 | "phone": true 22 | } 23 | }, 24 | 25 | "sap.ui5": { 26 | "rootView": { 27 | "viewName": "com.myorg.myapp.view.App", 28 | "type": "XML", 29 | "async": true, 30 | "id": "app" 31 | }, 32 | 33 | "dependencies": { 34 | "minUI5Version": "1.115.1", 35 | "libs": { 36 | "sap.ui.core": {}, 37 | "sap.m": {} 38 | } 39 | }, 40 | 41 | "handleValidation": true, 42 | 43 | "contentDensities": { 44 | "compact": true, 45 | "cozy": true 46 | }, 47 | 48 | "models": { 49 | "i18n": { 50 | "type": "sap.ui.model.resource.ResourceModel", 51 | "settings": { 52 | "bundleName": "com.myorg.myapp.i18n.i18n" 53 | } 54 | } 55 | }, 56 | 57 | "routing": { 58 | "config": { 59 | "routerClass": "sap.m.routing.Router", 60 | "viewType": "XML", 61 | "viewPath": "com.myorg.myapp.view", 62 | "controlId": "app", 63 | "controlAggregation": "pages", 64 | "async": true 65 | }, 66 | "routes": [ 67 | { 68 | "pattern": "", 69 | "name": "main", 70 | "target": "main" 71 | } 72 | ], 73 | "targets": { 74 | "main": { 75 | "viewId": "main", 76 | "viewName": "Main" 77 | } 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /exercises/ex1/com.myorg.myapp/webapp/model/formatter.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | formatValue: (value: string) => { 3 | return value?.toUpperCase(); 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /exercises/ex1/com.myorg.myapp/webapp/model/models.ts: -------------------------------------------------------------------------------- 1 | import JSONModel from "sap/ui/model/json/JSONModel"; 2 | import BindingMode from "sap/ui/model/BindingMode"; 3 | 4 | import Device from "sap/ui/Device"; 5 | 6 | 7 | export default { 8 | createDeviceModel: () => { 9 | const oModel = new JSONModel(Device); 10 | oModel.setDefaultBindingMode(BindingMode.OneWay); 11 | return oModel; 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /exercises/ex1/com.myorg.myapp/webapp/test/Test.qunit.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 11 | 12 | 13 |
14 |
15 | 16 | 17 | -------------------------------------------------------------------------------- /exercises/ex1/com.myorg.myapp/webapp/test/integration/HelloJourney.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-floating-promises */ 2 | import opaTest from "sap/ui/test/opaQunit"; 3 | import MainPage from "./pages/MainPage"; 4 | 5 | const onTheMainPage = new MainPage(); 6 | 7 | QUnit.module("Sample Hello Journey"); 8 | 9 | opaTest("Should open the Hello dialog", function () { 10 | // Arrangements 11 | onTheMainPage.iStartMyUIComponent({ 12 | componentConfig: { 13 | name: "com.myorg.myapp" 14 | } 15 | }); 16 | 17 | // Actions 18 | onTheMainPage.iPressTheSayHelloWithDialogButton(); 19 | 20 | // Assertions 21 | onTheMainPage.iShouldSeeTheHelloDialog(); 22 | 23 | // Actions 24 | onTheMainPage.iPressTheOkButtonInTheDialog(); 25 | 26 | // Assertions 27 | onTheMainPage.iShouldNotSeeTheHelloDialog(); 28 | 29 | // Cleanup 30 | onTheMainPage.iTeardownMyApp(); 31 | }); 32 | 33 | opaTest("Should close the Hello dialog", function () { 34 | // Arrangements 35 | onTheMainPage.iStartMyUIComponent({ 36 | componentConfig: { 37 | name: "com.myorg.myapp" 38 | } 39 | }); 40 | 41 | // Actions 42 | onTheMainPage.iPressTheSayHelloWithDialogButton(); 43 | onTheMainPage.iPressTheOkButtonInTheDialog(); 44 | 45 | // Assertions 46 | onTheMainPage.iShouldNotSeeTheHelloDialog(); 47 | 48 | // Cleanup 49 | onTheMainPage.iTeardownMyApp(); 50 | }); 51 | -------------------------------------------------------------------------------- /exercises/ex1/com.myorg.myapp/webapp/test/integration/opaTests.qunit.ts: -------------------------------------------------------------------------------- 1 | import "./HelloJourney"; 2 | -------------------------------------------------------------------------------- /exercises/ex1/com.myorg.myapp/webapp/test/integration/pages/MainPage.ts: -------------------------------------------------------------------------------- 1 | import Opa5 from "sap/ui/test/Opa5"; 2 | import Press from "sap/ui/test/actions/Press"; 3 | 4 | const viewName = "com.myorg.myapp.view.Main"; 5 | 6 | export default class MainPage extends Opa5 { 7 | // Actions 8 | iPressTheSayHelloWithDialogButton() { 9 | this.waitFor({ 10 | id: "helloButton", 11 | viewName, 12 | actions: new Press(), 13 | errorMessage: "Did not find the 'Say Hello With Dialog' button on the Main view" 14 | }); 15 | } 16 | 17 | iPressTheOkButtonInTheDialog() { 18 | this.waitFor({ 19 | controlType: "sap.m.Button", 20 | searchOpenDialogs: true, 21 | viewName, 22 | actions: new Press(), 23 | errorMessage: "Did not find the 'OK' button in the Dialog" 24 | }); 25 | } 26 | 27 | // Assertions 28 | iShouldSeeTheHelloDialog() { 29 | this.waitFor({ 30 | controlType: "sap.m.Dialog", 31 | success: function () { 32 | // we set the view busy, so we need to query the parent of the app 33 | Opa5.assert.ok(true, "The dialog is open"); 34 | }, 35 | errorMessage: "Did not find the dialog control" 36 | }); 37 | } 38 | 39 | iShouldNotSeeTheHelloDialog() { 40 | this.waitFor({ 41 | controlType: "sap.m.App", // dummy, I just want a check function, where I can search the DOM. Probably there is a better way for a NEGATIVE test (NO dialog). 42 | check: function () { 43 | return document.querySelectorAll(".sapMDialog").length === 0; 44 | }, 45 | success: function () { 46 | Opa5.assert.ok(true, "No dialog is open"); 47 | } 48 | }); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /exercises/ex1/com.myorg.myapp/webapp/test/testsuite.qunit.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /exercises/ex1/com.myorg.myapp/webapp/test/testsuite.qunit.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | name: "Unit test suite for the UI5 Application: com.myorg.myapp", 3 | defaults: { 4 | page: "ui5://test-resources/com/myorg/myapp/Test.qunit.html?testsuite={suite}&test={name}", 5 | qunit: { 6 | version: 2 7 | }, 8 | ui5: { 9 | theme: "sap_horizon" 10 | }, 11 | loader: { 12 | paths: { 13 | "com/myorg/myapp": "../" 14 | } 15 | } 16 | }, 17 | tests: { 18 | "unit/unitTests": { 19 | title: "Unit tests for the UI5 Application: com.myorg.myapp" 20 | }, 21 | "integration/opaTests": { 22 | title: "Integration tests for the UI5 Application: com.myorg.myapp" 23 | } 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /exercises/ex1/com.myorg.myapp/webapp/test/unit/controller/Main.qunit.ts: -------------------------------------------------------------------------------- 1 | import Main from "com/myorg/myapp/controller/Main.controller"; 2 | 3 | QUnit.module("Sample Main controller test"); 4 | 5 | QUnit.test("The Main controller class has a sayHello method", function (assert) { 6 | // as a very basic test example just check the presence of the "sayHello" method 7 | assert.strictEqual(typeof Main.prototype.sayHello, "function"); 8 | }); 9 | -------------------------------------------------------------------------------- /exercises/ex1/com.myorg.myapp/webapp/test/unit/unitTests.qunit.ts: -------------------------------------------------------------------------------- 1 | import "./controller/Main.qunit"; 2 | -------------------------------------------------------------------------------- /exercises/ex1/com.myorg.myapp/webapp/view/App.view.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /exercises/ex1/com.myorg.myapp/webapp/view/Main.view.xml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 17 | 18 |