diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..8e3bfbd4f --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,129 @@ +# Repository Guide for AI Agents + +This repository ships five OpenUI5 tutorials rendered as a GitHub Pages site with a client-side JavaScript / TypeScript language toggle. Every tutorial step is a self-contained, runnable UI5 app under `packages//steps/NN/`. This document tells an agent enough about the layout, the toolchain, and the authoring conventions to be useful without re-deriving everything from source. + +## Layout + +- `packages/quickstart/` — 3 introductory steps. +- `packages/walkthrough/` — 38 steps; the canonical reference for content shape and toolchain. +- `packages/databinding/` — 15 steps on data binding (JSON & resource models, property/two-way/one-way/aggregation/element/expression binding, formatters, data types, validation). +- `packages/navigation/` — 17 steps on routing and navigation. +- `packages/odatav4/` — 11 steps on OData V4. +- `packages//README.md` — the tutorial's overview and step index. +- `packages//steps.json` — machine-readable index of the tutorial's steps (`n`, `id`, `title`, `description`, `previewUrl`, `zipTs`, `zipJs`). Regenerate with `node _/generate-steps-index.js` after adding, removing, or renaming steps. +- `packages//steps/NN/` — one runnable app per step. Contains `README.md`, `webapp/`, `package.json`, `ui5.yaml`, and (for TS steps) `tsconfig.json`. +- `assets/` — CSS and JS that power the site (language toggle, code-couple tab container, `.ts/.js` extension substitution). Ships as-is to `dist/`. +- `tools/builder/` — build orchestrator (`node tools/builder`) that produces `dist/` for GitHub Pages: transpiles TS steps to JS, packages ZIP downloads, renders READMEs. +- `tools/dev-server/` — dev server (`npm start`) that mirrors the built site with live reload. +- `_/` — **gitignored** folder for one-off migration and maintenance scripts. Add throwaway tooling here; do not check it in as permanent tooling. +- `_includes/head-custom.html` — Jekyll partial that injects `assets/css/custom.css` and `assets/js/custom.js` into every GitHub Pages README render. +- `dist/` — build output, gitignored. + +## Running the code + +The repo is an npm workspaces monorepo. Each `packages/*/steps/*` is a workspace. + +```sh +# one-time +npm install + +# build every tutorial step + assemble dist/ +npm run build + +# preview the rendered site with working local live-preview links +npm start # http://localhost:1337/packages/walkthrough/ + +# run one step directly +npm start -w ui5.tutorial.walkthrough.step07 +npm start -w ui5.tutorial.odatav4.step03 +# etc. + +# typecheck every TypeScript step +npm run typecheck +``` + +Rely on `npm start -w ` when you need to see a specific step in a browser — the root dev server is heavier and only needed for site-level checks. + +## Tutorial namespace and package naming + +All five tutorials share one convention: + +- App namespace: `ui5.tutorial.` (used in `manifest.json` `sap.app.id`, `Controller` names, XML `controllerName`, `index.html` resource-roots, and JSDoc `@namespace`). +- Per-step `package.json` name: `ui5.tutorial..stepNN` (zero-padded). +- `ui5.yaml` `metadata.name`: `ui5.tutorial.`. +- tsconfig `paths`: `"ui5/tutorial//*": ["./webapp/*"]`. + +If you touch any of these, keep the naming aligned across the file set. + +## Step README authoring conventions + +Step READMEs are markdown rendered through GitHub Pages + a small client-side runtime in [assets/js/custom.js](assets/js/custom.js). Three conventions matter: + +1. **Language-specific prose blocks.** Wrap TS-only content in `
` (mirror for `js-only`). The runtime converts these to `
` elements and CSS hides the inactive one. This is for *block-level* prose only — for a paired inline TS/JS link (e.g. a download-solution link), use `` instead (see point 5). +2. **Adjacent ts/js code fences.** Write the TypeScript snippet in a `` ```ts `` fence and the JavaScript snippet in a `` ```js `` fence directly below (only whitespace between them). The runtime wraps the pair in a tabbed container. +3. **First-line path comments.** Every ts/js fence pair whose content is real source code must start with a `// path/to/File.ts` comment on line 1 (mirror `.js`). This is what tells a human reader — and an AI indexing the corpus — which file the snippet belongs to. For XML fences use ``; for INI/properties use `# path`. +4. **File extensions in prose and headings.** Write `.ts/.js` where the current language should substitute. The runtime renders it as `.ts` in TS mode or `.js` in JS mode. Do not use the older `.?s` token — it was retired. +5. **Inline TS/JS link pairs.** For a paired link that differs only by language (the canonical case is the download-solution link), write two adjacent `` with the language tokens `(TS)` and `(JS)` inside a nested `` so both variants read plainly in raw markdown while the rendered site shows only one. See any tutorial-overview README for the current shape: + + ```md + [📥 Download Solution](…step-NN.zip) (TS)[📥 Download Solution](…step-NN-js.zip) (JS) + ``` + + `` is inline — do **not** add `markdown="1"` (that attribute is for block-level elements). The CSS in [assets/css/custom.css](assets/css/custom.css) hides `.ts-only` / `.js-only` on any element based on the active language, and `.lang-suffix { display: none }` drops the `(TS)` / `(JS)` marker on the rendered side. + +Full author docs are in [CONTRIBUTING.md](CONTRIBUTING.md). + +Other conventions: + +- Callouts use `> :note:`, `> :tip:`, `> :info:` (custom syntax, not GitHub's `[!NOTE]` alerts). +- Step navigation footer: `***` separator, then `**Next:** [Step N+1: ](../<NN+1>/README.md)` and `**Previous:** [Step N-1: <title>](../<NN-1>/README.md)`. +- Preview images: descriptive `alt` text (no bare `![](…)`), optional `"title"` for the hover tooltip. +- Do not add `<!-- loio… -->` markers or `<a name="loio…"/>` anchors — those are SAP-internal SDK artifacts. + +## Using the tutorials as a source for generated code + +The steps are *pedagogical*, not *canonical*. Step 01 of each tutorial is deliberately incomplete — it exists to introduce one concept at a time. If a user asks you to scaffold a new UI5 app or generate a snippet, anchor on the **last** step of the relevant tutorial, not the first. + +- **New freestyle UI5 app** → copy the shape of [packages/walkthrough/steps/38/](packages/walkthrough/steps/38/). It has the complete `manifest.json`, `Component`, router setup, i18n, tests, and `ui5.yaml` for a production build. Steps 01–09 omit the Component; steps 10–29 omit routing; earlier steps omit tests. +- **Routing / navigation questions** → [packages/navigation/steps/17/](packages/navigation/steps/17/) is the full picture. Walkthrough steps 30–32 are a routing subset (single-page → detail flow) and will mislead if the user needs deep-linking, unmatched routes, or target lifecycle. +- **OData V4** → [packages/odatav4/steps/11/](packages/odatav4/steps/11/) covers CRUD, batch groups, operations, list-detail, and `:n` navigation. **The tutorial's data layer is a mock server simulating TripPin — do not cargo-cult `webapp/localService/` into an answer about connecting to a real backend.** Point the user at a real OData destination and keep only the model/binding code from the tutorial. +- **First-15-minutes demo, not a project starter** → [packages/quickstart/](packages/quickstart/) is 3 steps and stops before Component/manifest wiring. Use it to *show* UI5, not to *scaffold* from. +- **Tooling handshake** ([npm install](packages/walkthrough/steps/01/README.md), `ui5 init`, `ui5 use OpenUI5`, `ui5 add sap.ui.core themelib_sap_horizon`, `tsconfig.json`) — always take these from [walkthrough/steps/01](packages/walkthrough/steps/01/README.md) + [walkthrough/steps/02](packages/walkthrough/steps/02/README.md). They are the only steps that document setup from scratch. + +Out of scope for this repo — do not invent code claiming these are covered here: CAP / CDS backends, Fiori elements, BTP / Cloud Foundry / ABAP deployment, `easy-ui5` / `generator-ui5-*` scaffolding, CI setup, real authentication. + +### Reading the markdown correctly + +When ingesting a step README, treat these as semantic — not decorative — markers: + +- Any element carrying the class `ts-only` or `js-only` — most often `<details class="ts-only" markdown="1">` for prose blocks or `<span class="ts-only">` for inline link pairs — is the JS/TS switch. In raw markdown both variants are present; filter by class before quoting, or you will emit TS content in a JS answer (or vice versa). +- Adjacent `` ```ts `` and `` ```js `` fences are the *same file* in two languages, not two different files. The first-line path comment inside each fence is authoritative for which file it is. +- The literal string `.ts/.js` in prose is a runtime substitution token, not a filename. Resolve it to `.ts` or `.js` based on the language you are generating for. +- The `(TS)` / `(JS)` marker inside a `<span class="lang-suffix">` is a raw-markdown readability aid; it is hidden on the rendered site. Do not treat it as part of the visible link text when you quote a snippet. + +## Propagation rules — changes are almost never local + +Tutorials in this repo are cumulative: step `NN+1` is built on top of step `NN`'s files. That makes almost every "small" change ripple. Before you consider a task done, check whether one of these applies: + +- **Editing a step's code or `README.md`** → the same change must be reflected in **every subsequent step of that tutorial** where the affected file still exists. If you rename `Foo.controller.ts` in step 10, steps 11–38 that carry `Foo.controller.ts` forward need the same rename. If you fix a bug in a snippet in step 15's README, later steps that quote or re-show that snippet need the same fix. Never leave the corpus in a state where the "diff between consecutive steps" contains changes the step's prose does not explain. +- **Editing an authoring convention** (fence syntax, callout syntax, path-comment format, `<details>` class names, step-footer shape, namespace pattern) → every existing step README and every AI-facing doc that describes the convention must be updated in the same change. Specifically: this file (`AGENTS.md`), [CONTRIBUTING.md](CONTRIBUTING.md), and any `README.md` under `packages/*/` that references the convention. A convention that is only half-migrated is worse than no convention. +- **Renaming, adding, or removing a step** → update `packages/<pkg>/README.md` (the step index prose), regenerate `packages/<pkg>/steps.json` via `node _/generate-steps-index.js`, fix the `**Next:** / **Previous:**` footer links in the neighboring steps, and re-check any cross-tutorial link that points at the old step number. +- **Editing repo-shape guidance for AI** (this file, the root [README.md](README.md), [CONTRIBUTING.md](CONTRIBUTING.md), a `packages/*/README.md`) → if the same fact is stated in more than one of these, update all of them in one commit. Divergence between AI docs and human docs is the failure mode most likely to mislead a future agent. + +Verify a rippling change with `git grep` before *and* after — the count of matches should change in a way you can explain. A cumulative-tutorial change that only touches one step is almost always incomplete. + +## When something needs to change across many files + +- One-off scripts go under `_/` (gitignored). Match the pattern of the migration scripts already there (`migrate-*.js`, `promote-*.js`, `add-*.js`) — small Node scripts, no dependencies, walk the tree with `fs.readdirSync`. +- Design scripts to be **idempotent**: re-running on already-migrated content should produce zero changes. This lets you split large migrations into review-and-apply passes. +- For content-shape changes that ripple across all five packages, verify with grep before and after (`git grep`) and touch one canary file first (usually [packages/walkthrough/steps/09/README.md](packages/walkthrough/steps/09/README.md) — it exercises every convention). + +## Verification + +- **Static:** `npm run typecheck` for TS integrity, `git grep` for corpus-wide cleanliness. +- **Rendered:** `npm start`, then load a step README in the browser (e.g. `http://localhost:1337/packages/walkthrough/steps/09/README.md`). Toggle the TS/JS button and confirm both languages render correctly. +- **Built:** `npm run build`, then `open dist/walkthrough/build/09/index-cdn.html` for the built app, or `dist/walkthrough/steps/09/README.html` for the built README. + +## Deploy + +GitHub Pages runs the `.github/workflows/deploy.yml` workflow: `npm ci` → `node tools/builder` → publish `dist/`. Jekyll is enabled on the artifact so `_includes/head-custom.html` and the front-matter emitted by `tools/builder/prepare-gh-pages.js` take effect. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8dca92657..94e336420 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -43,11 +43,11 @@ Readers of the tutorial can freely switch the programming language of code snipp Main features of the two-language support are: 1. Content meant only for *one* of the programming languages can be hidden in the other by enclosing it in a specific block. 2. Two adjacent code sections in different languages are automatically converted to a tab container which allows switching between languages. -3. File extensions writen as `.?s` appear automatically as `.js` or `.ts` depending on the current language. +3. File extensions writen as `.ts/.js` appear automatically as `.js` or `.ts` depending on the current language. ### Limitations -The feature 3. above does not work inside code blocks (yet). +Feature 3 above (`.ts/.js` extension substitution) does not apply inside code blocks. That is intentional: inside a code fence the fence itself already carries the language, and each ts/js fence pair also starts with a first-line path comment naming the concrete file (see *Path comments in code fences* below). No further substitution is needed there. ### Running the preview/dev server @@ -106,6 +106,25 @@ The `markdown="1"` part is required for markdown parsing within HTML and the `<s </details> +#### 1a. Inline TS/JS link pairs (`<span>`) + +The `<details>` pattern above is for *block-level* content (a paragraph, a list, a code block). For a paired inline element that differs only by language — the canonical case is the download-solution link — use adjacent `<span>` tags instead: + +```md +<span class="ts-only">[📥 Download Solution](…step-NN.zip)<span class="lang-suffix"> (TS)</span></span><span class="js-only">[📥 Download Solution](…step-NN-js.zip)<span class="lang-suffix"> (JS)</span></span> +``` + +Rules: + +- `<span>` is inline; do **not** add `markdown="1"` (that attribute is only meaningful on block-level HTML). +- The nested `<span class="lang-suffix"> (TS)</span>` / `<span class="lang-suffix"> (JS)</span>` sits *outside* the markdown link (`](…)`) but *inside* the outer `.ts-only` / `.js-only` span. Placing the suffix outside the link text avoids nested-HTML-in-link-text quirks with both kramdown (GitHub Pages) and showdown (dev server). +- The CSS in [assets/css/custom.css](assets/css/custom.css) hides `.ts-only` / `.js-only` on any element based on the active language, and `.lang-suffix { display: none }` drops the `(TS)` / `(JS)` marker on the rendered site. Result: both links visible in raw markdown with distinct labels; only one visible on the rendered site, without the language suffix. + +##### Resulting Appearance<span class="hidden"> in markdown view (both links visible; on the rendered site only the current-language one shows and without the (TS)/(JS) suffix)</span> + +<span class="ts-only">[📥 Download Solution](…step-NN.zip)<span class="lang-suffix"> (TS)</span></span><span class="js-only">[📥 Download Solution](…step-NN-js.zip)<span class="lang-suffix"> (JS)</span></span> + + #### 2. Switchable code blocks in both languages When a piece of code should be displayed in either JS or TS, whatever is current, then simply create two adjacent markdown-fenced code blocks. They are automatically recognized as language-specific alternatives. @@ -135,18 +154,39 @@ const i = 0; const i: number = 0; ``` +##### Path comments in code fences + +Every adjacent ts/js code-fence pair whose content is real source code should start with a first-line comment naming the concrete file. This makes the raw markdown legible to readers and to AI tools that index snippets independently of surrounding headings — each fence carries the filename it belongs to, without duplicating the surrounding prose. + +Use the comment syntax that matches the fence language: + +```md + + ```ts + // webapp/controller/App.controller.ts + … + ``` + + ```js + // webapp/controller/App.controller.js + … + ``` +``` + +For other fence languages that appear as ts/js pairs elsewhere: `<!-- path -->` for `xml` / `html`, `# path` for `ini` / `properties`. Fences used for folder-tree diagrams (`text` or unlabeled) are exempt. + #### 3. File Extensions (`.js/.ts`) -When the text or a section heading mentions the name of a file that will be JavaScript or TypeScript, depending on the language, then use the file extension `.\?s`. It will automatically be switched to the current language. +When the text or a section heading mentions the name of a file that will be JavaScript or TypeScript, depending on the language, then use the file extension `.\ts\/\.js`. It will automatically be switched to the current language. Example: ```md -In this step you create the file `Example.controller.\?s`. +In this step you create the file `Example.controller.\ts\/\.js`. ``` ##### Resulting Appearance<span class="hidden"> in markdown view (here the extension is not replaced, the replacement only happens in the dev server and in GitHub Pages)</span> -In this step you create the file `Example.controller.?s`. +In this step you create the file `Example.controller.ts/.js`. ### Converting the JS Code to TypeScript diff --git a/README.md b/README.md index 23f4cec04..764780415 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ We first introduce you to the basic development paradigms like *Model-View-Contr This repository contains following tutorials: - [Quickstart](./packages/quickstart/) - [Walkthrough](./packages/walkthrough/) +- [Data Binding](./packages/databinding/) - [Navigation and Routing](./packages/navigation/) - [OData V4](./packages/odatav4/) diff --git a/assets/css/custom.css b/assets/css/custom.css index 3fe4c6403..a6de3a12a 100644 --- a/assets/css/custom.css +++ b/assets/css/custom.css @@ -37,6 +37,14 @@ display: none; } +/* Suffix like "(TS)" / "(JS)" attached to language-paired inline links + (e.g. download-solution links). Visible in raw markdown for readability, + hidden on the rendered site — the surrounding .ts-only / .js-only span + already disambiguates the language once one of the pair is displayed. */ +.lang-suffix { + display: none; +} + /* tabbed switchable language area */ .code-couple-button { diff --git a/assets/js/custom.js b/assets/js/custom.js index d4a028300..b9ac21b4e 100644 --- a/assets/js/custom.js +++ b/assets/js/custom.js @@ -179,6 +179,13 @@ function replaceFileExtensions(lang) { const replacement = "<span class='ts-only'>.ts</span><span class='js-only'>.js</span>"; + // The `.ts/.js` toggle token only represents a *complete* file extension. + // Requiring a non-word boundary after `.js` prevents a mis-authored token + // that is really part of a longer extension (e.g. `manifest.ts/.js` + `on`, + // a leftover from tokenizing `.json`) from being swapped into a corrupt + // filename like `manifest.tson` when TypeScript is active. + const tokenPattern = /\.ts\/\.js(?![A-Za-z0-9])/g; + // select all text nodes in the body const walker = document.createTreeWalker( document.body, @@ -191,9 +198,9 @@ function replaceFileExtensions(lang) { // iterate over each text node while (node) { let nextNode = walker.nextNode(); - if (node.nodeValue.includes(".?s")) { + if (tokenPattern.test(node.nodeValue)) { const temp = document.createElement("div"); - temp.innerHTML = node.nodeValue.replace(/\.\?s/g, replacement); + temp.innerHTML = node.nodeValue.replace(tokenPattern, replacement); const fragment = document.createDocumentFragment(); while (temp.firstChild) { @@ -201,8 +208,8 @@ function replaceFileExtensions(lang) { } node.parentNode.replaceChild(fragment, node); - } else if (node.nodeValue.includes(".\\?s")) { - node.nodeValue = node.nodeValue.replace(/\.\\\?s/g, ".?s"); + } else if (node.nodeValue.includes(".\\ts\\/\\.js")) { + node.nodeValue = node.nodeValue.replace(/\.\\ts\\\/\\\.js/g, ".ts/.js"); } node = nextNode; } diff --git a/package-lock.json b/package-lock.json index 421f60275..89d725bc2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20007,6 +20007,66 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/ui5.tutorial.databinding.step01": { + "resolved": "packages/databinding/steps/01", + "link": true + }, + "node_modules/ui5.tutorial.databinding.step02": { + "resolved": "packages/databinding/steps/02", + "link": true + }, + "node_modules/ui5.tutorial.databinding.step03": { + "resolved": "packages/databinding/steps/03", + "link": true + }, + "node_modules/ui5.tutorial.databinding.step04": { + "resolved": "packages/databinding/steps/04", + "link": true + }, + "node_modules/ui5.tutorial.databinding.step05": { + "resolved": "packages/databinding/steps/05", + "link": true + }, + "node_modules/ui5.tutorial.databinding.step06": { + "resolved": "packages/databinding/steps/06", + "link": true + }, + "node_modules/ui5.tutorial.databinding.step07": { + "resolved": "packages/databinding/steps/07", + "link": true + }, + "node_modules/ui5.tutorial.databinding.step08": { + "resolved": "packages/databinding/steps/08", + "link": true + }, + "node_modules/ui5.tutorial.databinding.step09": { + "resolved": "packages/databinding/steps/09", + "link": true + }, + "node_modules/ui5.tutorial.databinding.step10": { + "resolved": "packages/databinding/steps/10", + "link": true + }, + "node_modules/ui5.tutorial.databinding.step11": { + "resolved": "packages/databinding/steps/11", + "link": true + }, + "node_modules/ui5.tutorial.databinding.step12": { + "resolved": "packages/databinding/steps/12", + "link": true + }, + "node_modules/ui5.tutorial.databinding.step13": { + "resolved": "packages/databinding/steps/13", + "link": true + }, + "node_modules/ui5.tutorial.databinding.step14": { + "resolved": "packages/databinding/steps/14", + "link": true + }, + "node_modules/ui5.tutorial.databinding.step15": { + "resolved": "packages/databinding/steps/15", + "link": true + }, "node_modules/ui5.tutorial.navigation.step01": { "resolved": "packages/navigation/steps/01", "link": true @@ -20851,6 +20911,186 @@ "url": "https://github.com/sponsors/colinhacks" } }, + "packages/databinding/steps/01": { + "name": "ui5.tutorial.databinding.step01", + "version": "1.0.0", + "devDependencies": { + "@openui5/types": "^1.148.1", + "@ui5/cli": "^4.0.53", + "typescript": "^6.0.3", + "ui5-middleware-livereload": "^3.3.1", + "ui5-middleware-serveframework": "^3.8.1", + "ui5-tooling-transpile": "^3.11.0" + } + }, + "packages/databinding/steps/02": { + "name": "ui5.tutorial.databinding.step02", + "version": "1.0.0", + "devDependencies": { + "@openui5/types": "^1.148.1", + "@ui5/cli": "^4.0.53", + "typescript": "^6.0.3", + "ui5-middleware-livereload": "^3.3.1", + "ui5-middleware-serveframework": "^3.8.1", + "ui5-tooling-transpile": "^3.11.0" + } + }, + "packages/databinding/steps/03": { + "name": "ui5.tutorial.databinding.step03", + "version": "1.0.0", + "devDependencies": { + "@openui5/types": "^1.148.1", + "@ui5/cli": "^4.0.53", + "typescript": "^6.0.3", + "ui5-middleware-livereload": "^3.3.1", + "ui5-middleware-serveframework": "^3.8.1", + "ui5-tooling-transpile": "^3.11.0" + } + }, + "packages/databinding/steps/04": { + "name": "ui5.tutorial.databinding.step04", + "version": "1.0.0", + "devDependencies": { + "@openui5/types": "^1.148.1", + "@ui5/cli": "^4.0.53", + "typescript": "^6.0.3", + "ui5-middleware-livereload": "^3.3.1", + "ui5-middleware-serveframework": "^3.8.1", + "ui5-tooling-transpile": "^3.11.0" + } + }, + "packages/databinding/steps/05": { + "name": "ui5.tutorial.databinding.step05", + "version": "1.0.0", + "devDependencies": { + "@openui5/types": "^1.148.1", + "@ui5/cli": "^4.0.53", + "typescript": "^6.0.3", + "ui5-middleware-livereload": "^3.3.1", + "ui5-middleware-serveframework": "^3.8.1", + "ui5-tooling-transpile": "^3.11.0" + } + }, + "packages/databinding/steps/06": { + "name": "ui5.tutorial.databinding.step06", + "version": "1.0.0", + "devDependencies": { + "@openui5/types": "^1.148.1", + "@ui5/cli": "^4.0.53", + "typescript": "^6.0.3", + "ui5-middleware-livereload": "^3.3.1", + "ui5-middleware-serveframework": "^3.8.1", + "ui5-tooling-transpile": "^3.11.0" + } + }, + "packages/databinding/steps/07": { + "name": "ui5.tutorial.databinding.step07", + "version": "1.0.0", + "devDependencies": { + "@openui5/types": "^1.148.1", + "@ui5/cli": "^4.0.53", + "typescript": "^6.0.3", + "ui5-middleware-livereload": "^3.3.1", + "ui5-middleware-serveframework": "^3.8.1", + "ui5-tooling-transpile": "^3.11.0" + } + }, + "packages/databinding/steps/08": { + "name": "ui5.tutorial.databinding.step08", + "version": "1.0.0", + "devDependencies": { + "@openui5/types": "^1.148.1", + "@ui5/cli": "^4.0.53", + "typescript": "^6.0.3", + "ui5-middleware-livereload": "^3.3.1", + "ui5-middleware-serveframework": "^3.8.1", + "ui5-tooling-transpile": "^3.11.0" + } + }, + "packages/databinding/steps/09": { + "name": "ui5.tutorial.databinding.step09", + "version": "1.0.0", + "devDependencies": { + "@openui5/types": "^1.148.1", + "@ui5/cli": "^4.0.53", + "typescript": "^6.0.3", + "ui5-middleware-livereload": "^3.3.1", + "ui5-middleware-serveframework": "^3.8.1", + "ui5-tooling-transpile": "^3.11.0" + } + }, + "packages/databinding/steps/10": { + "name": "ui5.tutorial.databinding.step10", + "version": "1.0.0", + "devDependencies": { + "@openui5/types": "^1.148.1", + "@ui5/cli": "^4.0.53", + "typescript": "^6.0.3", + "ui5-middleware-livereload": "^3.3.1", + "ui5-middleware-serveframework": "^3.8.1", + "ui5-tooling-transpile": "^3.11.0" + } + }, + "packages/databinding/steps/11": { + "name": "ui5.tutorial.databinding.step11", + "version": "1.0.0", + "devDependencies": { + "@openui5/types": "^1.148.1", + "@ui5/cli": "^4.0.53", + "typescript": "^6.0.3", + "ui5-middleware-livereload": "^3.3.1", + "ui5-middleware-serveframework": "^3.8.1", + "ui5-tooling-transpile": "^3.11.0" + } + }, + "packages/databinding/steps/12": { + "name": "ui5.tutorial.databinding.step12", + "version": "1.0.0", + "devDependencies": { + "@openui5/types": "^1.148.1", + "@ui5/cli": "^4.0.53", + "typescript": "^6.0.3", + "ui5-middleware-livereload": "^3.3.1", + "ui5-middleware-serveframework": "^3.8.1", + "ui5-tooling-transpile": "^3.11.0" + } + }, + "packages/databinding/steps/13": { + "name": "ui5.tutorial.databinding.step13", + "version": "1.0.0", + "devDependencies": { + "@openui5/types": "^1.148.1", + "@ui5/cli": "^4.0.53", + "typescript": "^6.0.3", + "ui5-middleware-livereload": "^3.3.1", + "ui5-middleware-serveframework": "^3.8.1", + "ui5-tooling-transpile": "^3.11.0" + } + }, + "packages/databinding/steps/14": { + "name": "ui5.tutorial.databinding.step14", + "version": "1.0.0", + "devDependencies": { + "@openui5/types": "^1.148.1", + "@ui5/cli": "^4.0.53", + "typescript": "^6.0.3", + "ui5-middleware-livereload": "^3.3.1", + "ui5-middleware-serveframework": "^3.8.1", + "ui5-tooling-transpile": "^3.11.0" + } + }, + "packages/databinding/steps/15": { + "name": "ui5.tutorial.databinding.step15", + "version": "1.0.0", + "devDependencies": { + "@openui5/types": "^1.148.1", + "@ui5/cli": "^4.0.53", + "typescript": "^6.0.3", + "ui5-middleware-livereload": "^3.3.1", + "ui5-middleware-serveframework": "^3.8.1", + "ui5-tooling-transpile": "^3.11.0" + } + }, "packages/navigation/steps/01": { "name": "ui5.tutorial.navigation.step01", "version": "1.0.0", diff --git a/packages/databinding/README.md b/packages/databinding/README.md new file mode 100644 index 000000000..3e47dd024 --- /dev/null +++ b/packages/databinding/README.md @@ -0,0 +1,54 @@ +<!-- Data Binding Tutorial overview --> + +# Data Binding Tutorial + +In this tutorial, we explain the concepts of data binding in OpenUI5. + +Data binding is used to bind UI elements to data sources. This keeps the data in sync and allows data editing on the UI. + +For data binding, you need a model and a binding instance: The model holds the data and provides methods to set the data or retrieve it from a server. It also provides a method for creating bindings to the data. When you call this method, a binding instance is created, which contains the binding information and provides an event, which is fired whenever the bound data changes. An element can listen to this event and update its visualization according to the new data. + +The UI uses data binding to bind controls to the model which holds the application data, so that the controls are updated automatically whenever application data changes. Data binding is also used in reverse, when changes in the control, for example data entered by the user, cause updates in the underlying application data. This is called two-way binding. + +## Preview + +![Preview of three panels of the OpenUI5 application that is going to be built in this tutorial. Contains three panels with name input fields, address details, and a product list.](assets/Tutorial_Data_Binding_896048e.png "The Data Binding sample app with panels for name, address, and a product list") + +> :tip: +> You don't have to do all tutorial steps sequentially, you can also jump directly to any step you want. Just download the code from the previous step, copy it to your workspace, and ensure that the application runs by calling the `webapp/index.html` file. + +*** + +### In this Tutorial + +The tutorial consists of the following steps. To start, just open the first link — you'll be guided from there. + +- **[Step 1: No Data Binding](./steps/01/README.md)** — In this step, we create a basic application and simply place some text on the screen using a standard sap.m.Text control. ([🔗 Live Preview](https://ui5.github.io/tutorials/databinding/build/01/index-cdn.html) \| <span class="ts-only">[📥 Download Solution](https://ui5.github.io/tutorials/databinding/databinding-step-01.zip)<span class="lang-suffix"> (TS)</span></span><span class="js-only">[📥 Download Solution](https://ui5.github.io/tutorials/databinding/databinding-step-01-js.zip)<span class="lang-suffix"> (JS)</span></span>) +- **[Step 2: Creating a Model](./steps/02/README.md)** — In this step, we create a model. It serves as a container for the data your application operates on. ([🔗 Live Preview](https://ui5.github.io/tutorials/databinding/build/02/index-cdn.html) \| <span class="ts-only">[📥 Download Solution](https://ui5.github.io/tutorials/databinding/databinding-step-02.zip)<span class="lang-suffix"> (TS)</span></span><span class="js-only">[📥 Download Solution](https://ui5.github.io/tutorials/databinding/databinding-step-02-js.zip)<span class="lang-suffix"> (JS)</span></span>) +- **[Step 3: Create Property Binding](./steps/03/README.md)** — Although there is no visible difference, the text on the screen is now derived from model data. ([🔗 Live Preview](https://ui5.github.io/tutorials/databinding/build/03/index-cdn.html) \| <span class="ts-only">[📥 Download Solution](https://ui5.github.io/tutorials/databinding/databinding-step-03.zip)<span class="lang-suffix"> (TS)</span></span><span class="js-only">[📥 Download Solution](https://ui5.github.io/tutorials/databinding/databinding-step-03-js.zip)<span class="lang-suffix"> (JS)</span></span>) +- **[Step 4: Two-Way Data Binding](./steps/04/README.md)** — We change the user interface to display first and last name fields using sap.m.Input fields and add a check box to enable or disable them. ([🔗 Live Preview](https://ui5.github.io/tutorials/databinding/build/04/index-cdn.html) \| <span class="ts-only">[📥 Download Solution](https://ui5.github.io/tutorials/databinding/databinding-step-04.zip)<span class="lang-suffix"> (TS)</span></span><span class="js-only">[📥 Download Solution](https://ui5.github.io/tutorials/databinding/databinding-step-04-js.zip)<span class="lang-suffix"> (JS)</span></span>) +- **[Step 5: One-Way Data Binding](./steps/05/README.md)** — Unlike two-way binding, one-way data binding lets data travel in one direction only: from the model to the consumer, but never back. ([🔗 Live Preview](https://ui5.github.io/tutorials/databinding/build/05/index-cdn.html) \| <span class="ts-only">[📥 Download Solution](https://ui5.github.io/tutorials/databinding/databinding-step-05.zip)<span class="lang-suffix"> (TS)</span></span><span class="js-only">[📥 Download Solution](https://ui5.github.io/tutorials/databinding/databinding-step-05-js.zip)<span class="lang-suffix"> (JS)</span></span>) +- **[Step 6: Resource Models](./steps/06/README.md)** — Business applications often require language-specific (translatable) text. We place all translatable texts into a resource bundle. ([🔗 Live Preview](https://ui5.github.io/tutorials/databinding/build/06/index-cdn.html) \| <span class="ts-only">[📥 Download Solution](https://ui5.github.io/tutorials/databinding/databinding-step-06.zip)<span class="lang-suffix"> (TS)</span></span><span class="js-only">[📥 Download Solution](https://ui5.github.io/tutorials/databinding/databinding-step-06-js.zip)<span class="lang-suffix"> (JS)</span></span>) +- **[Step 7: \(Optional\) Resource Bundles and Multiple Languages](./steps/07/README.md)** — Resource bundles enable an app to run in multiple languages without changing any code. We create a German version of the app. ([🔗 Live Preview](https://ui5.github.io/tutorials/databinding/build/07/index-cdn.html) \| <span class="ts-only">[📥 Download Solution](https://ui5.github.io/tutorials/databinding/databinding-step-07.zip)<span class="lang-suffix"> (TS)</span></span><span class="js-only">[📥 Download Solution](https://ui5.github.io/tutorials/databinding/databinding-step-07-js.zip)<span class="lang-suffix"> (JS)</span></span>) +- **[Step 8: Binding Paths: Accessing Properties in Hierarchically Structured Models](./steps/08/README.md)** — We explore how to reference fields in a hierarchically structured model object by adding a second panel with address data. ([🔗 Live Preview](https://ui5.github.io/tutorials/databinding/build/08/index-cdn.html) \| <span class="ts-only">[📥 Download Solution](https://ui5.github.io/tutorials/databinding/databinding-step-08.zip)<span class="lang-suffix"> (TS)</span></span><span class="js-only">[📥 Download Solution](https://ui5.github.io/tutorials/databinding/databinding-step-08-js.zip)<span class="lang-suffix"> (JS)</span></span>) +- **[Step 9: Formatting Values](./steps/09/README.md)** — We add a link that sends an e-mail to Harry Hawk, using a custom formatter function to convert the model data. ([🔗 Live Preview](https://ui5.github.io/tutorials/databinding/build/09/index-cdn.html) \| <span class="ts-only">[📥 Download Solution](https://ui5.github.io/tutorials/databinding/databinding-step-09.zip)<span class="lang-suffix"> (TS)</span></span><span class="js-only">[📥 Download Solution](https://ui5.github.io/tutorials/databinding/databinding-step-09-js.zip)<span class="lang-suffix"> (JS)</span></span>) +- **[Step 10: Property Formatting Using Data Types](./steps/10/README.md)** — We apply data types such as Currency to controls to ensure the value displayed on the screen is formatted correctly. ([🔗 Live Preview](https://ui5.github.io/tutorials/databinding/build/10/index-cdn.html) \| <span class="ts-only">[📥 Download Solution](https://ui5.github.io/tutorials/databinding/databinding-step-10.zip)<span class="lang-suffix"> (TS)</span></span><span class="js-only">[📥 Download Solution](https://ui5.github.io/tutorials/databinding/databinding-step-10-js.zip)<span class="lang-suffix"> (JS)</span></span>) +- **[Step 11: Validation Using `sap/ui/core/Messaging`](./steps/11/README.md)** — We enable validation for the entire app so that validation error messages are passed to Messaging and connected to the offending control. ([🔗 Live Preview](https://ui5.github.io/tutorials/databinding/build/11/index-cdn.html) \| <span class="ts-only">[📥 Download Solution](https://ui5.github.io/tutorials/databinding/databinding-step-11.zip)<span class="lang-suffix"> (TS)</span></span><span class="js-only">[📥 Download Solution](https://ui5.github.io/tutorials/databinding/databinding-step-11-js.zip)<span class="lang-suffix"> (JS)</span></span>) +- **[Step 12: Aggregation Binding Using Templates](./steps/12/README.md)** — Aggregation binding lets a control bind to a list within the model data. We add a third panel with a list of products. ([🔗 Live Preview](https://ui5.github.io/tutorials/databinding/build/12/index-cdn.html) \| <span class="ts-only">[📥 Download Solution](https://ui5.github.io/tutorials/databinding/databinding-step-12.zip)<span class="lang-suffix"> (TS)</span></span><span class="js-only">[📥 Download Solution](https://ui5.github.io/tutorials/databinding/databinding-step-12-js.zip)<span class="lang-suffix"> (JS)</span></span>) +- **[Step 13: Element Binding](./steps/13/README.md)** — We use a form with relatively bound controls and bind it to the selected list entity via element binding. ([🔗 Live Preview](https://ui5.github.io/tutorials/databinding/build/13/index-cdn.html) \| <span class="ts-only">[📥 Download Solution](https://ui5.github.io/tutorials/databinding/databinding-step-13.zip)<span class="lang-suffix"> (TS)</span></span><span class="js-only">[📥 Download Solution](https://ui5.github.io/tutorials/databinding/databinding-step-13-js.zip)<span class="lang-suffix"> (JS)</span></span>) +- **[Step 14: Expression Binding](./steps/14/README.md)** — An expression binding lets you display a calculated value on the screen, derived from values found in a model object. ([🔗 Live Preview](https://ui5.github.io/tutorials/databinding/build/14/index-cdn.html) \| <span class="ts-only">[📥 Download Solution](https://ui5.github.io/tutorials/databinding/databinding-step-14.zip)<span class="lang-suffix"> (TS)</span></span><span class="js-only">[📥 Download Solution](https://ui5.github.io/tutorials/databinding/databinding-step-14-js.zip)<span class="lang-suffix"> (JS)</span></span>) +- **[Step 15: Aggregation Binding Using a Factory Function](./steps/15/README.md)** — We use a factory function to generate different controls based on the data received at runtime. ([🔗 Live Preview](https://ui5.github.io/tutorials/databinding/build/15/index-cdn.html) \| <span class="ts-only">[📥 Download Solution](https://ui5.github.io/tutorials/databinding/databinding-step-15.zip)<span class="lang-suffix"> (TS)</span></span><span class="js-only">[📥 Download Solution](https://ui5.github.io/tutorials/databinding/databinding-step-15-js.zip)<span class="lang-suffix"> (JS)</span></span>) + +*** + +## Related Information + +[Data Binding](https://sdk.openui5.org/topic/68b9644a253741e8a4b9e4279a35c247.html "You use data binding to bind UI elements to data sources to keep the data in sync and allow data editing on the UI.") + +[Model View Controller \(MVC\)](https://sdk.openui5.org/topic/91f233476f4d1014b6dd926db0e91070.html "The Model View Controller (MVC) concept is used in OpenUI5 to separate the representation of information from the user interaction. This separation facilitates development and the changing of parts independently.") + +*** + +## License + +Copyright (c) 2026 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. diff --git a/packages/databinding/assets/Tutorial_Data_Binding_896048e.png b/packages/databinding/assets/Tutorial_Data_Binding_896048e.png new file mode 100644 index 000000000..2c020aa62 Binary files /dev/null and b/packages/databinding/assets/Tutorial_Data_Binding_896048e.png differ diff --git a/packages/databinding/steps.json b/packages/databinding/steps.json new file mode 100644 index 000000000..bcee0b845 --- /dev/null +++ b/packages/databinding/steps.json @@ -0,0 +1,141 @@ +{ + "tutorial": "databinding", + "namespace": "ui5.tutorial.databinding", + "steps": [ + { + "n": 1, + "id": "01", + "title": "No Data Binding", + "previewUrl": "https://ui5.github.io/tutorials/databinding/build/01/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/databinding/databinding-step-01.zip", + "zipJs": "https://ui5.github.io/tutorials/databinding/databinding-step-01-js.zip", + "description": "In this step, we create a basic application and simply place some text on the screen using a standard sap.m.Text control. The text in this control is a hard-coded part of the control's definition; therefore, this is not an example of data binding!" + }, + { + "n": 2, + "id": "02", + "title": "Creating a Model", + "previewUrl": "https://ui5.github.io/tutorials/databinding/build/02/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/databinding/databinding-step-02.zip", + "zipJs": "https://ui5.github.io/tutorials/databinding/databinding-step-02-js.zip", + "description": "In this step, we create a model. It serves as a container for the data your application operates on." + }, + { + "n": 3, + "id": "03", + "title": "Create Property Binding", + "previewUrl": "https://ui5.github.io/tutorials/databinding/build/03/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/databinding/databinding-step-03.zip", + "zipJs": "https://ui5.github.io/tutorials/databinding/databinding-step-03-js.zip", + "description": "Although there is no visible difference, the text on the screen is now derived from model data." + }, + { + "n": 4, + "id": "04", + "title": "Two-Way Data Binding", + "previewUrl": "https://ui5.github.io/tutorials/databinding/build/04/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/databinding/databinding-step-04.zip", + "zipJs": "https://ui5.github.io/tutorials/databinding/databinding-step-04-js.zip", + "description": "In the examples we've looked at so far, we've displayed the value of a model property using a read-only field. We'll now change the user interface to display first and last name fields using sap.m.Input fields. We're also adding a check box control to enable or disable both input fields. This setup illustrates a feature known as \"two-way data binding\". As the view now contains more controls, we're also moving the view definition into an XML file." + }, + { + "n": 5, + "id": "05", + "title": "One-Way Data Binding", + "previewUrl": "https://ui5.github.io/tutorials/databinding/build/05/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/databinding/databinding-step-05.zip", + "zipJs": "https://ui5.github.io/tutorials/databinding/databinding-step-05-js.zip", + "description": "Unlike the two-way binding behavior we've seen, one-way data binding is also possible. In this case, data travels in one direction only: from the model, through the binding instance, to the consumer \\(usually the property of a control\\), but never in the other direction. Let's modify the previous example to use one-way data binding. This shows how you can switch off the flow of data from the user interface back to the model if needed." + }, + { + "n": 6, + "id": "06", + "title": "Resource Models", + "previewUrl": "https://ui5.github.io/tutorials/databinding/build/06/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/databinding/databinding-step-06.zip", + "zipJs": "https://ui5.github.io/tutorials/databinding/databinding-step-06-js.zip", + "description": "Business applications often require language-specific \\(translatable\\) text used as labels and descriptions on the user interface." + }, + { + "n": 7, + "id": "07", + "title": "\\(Optional\\) Resource Bundles and Multiple Languages", + "previewUrl": "https://ui5.github.io/tutorials/databinding/build/07/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/databinding/databinding-step-07.zip", + "zipJs": "https://ui5.github.io/tutorials/databinding/databinding-step-07-js.zip", + "description": "Resource bundles exist to enable an app to run in multiple languages without the need to change any code. To demonstrate this feature, let's create a German version of the app – in fact, all we need to do is create a German version of the resource bundle file. In our code, we activate the German locale for the ResourceModel." + }, + { + "n": 8, + "id": "08", + "title": "Binding Paths: Accessing Properties in Hierarchically Structured Models", + "previewUrl": "https://ui5.github.io/tutorials/databinding/build/08/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/databinding/databinding-step-08.zip", + "zipJs": "https://ui5.github.io/tutorials/databinding/databinding-step-08-js.zip", + "description": "In Step 6 , we stated that the fields in a resource model are arranged in a flat structure; in other words, there is no hierarchy of properties. However, this is only true for resource models. The properties within JSON and OData models are usually arranged in a hierarchical structure. So, let's explore how to reference fields in a hierarchically structured model object." + }, + { + "n": 9, + "id": "09", + "title": "Formatting Values", + "previewUrl": "https://ui5.github.io/tutorials/databinding/build/09/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/databinding/databinding-step-09.zip", + "zipJs": "https://ui5.github.io/tutorials/databinding/databinding-step-09-js.zip", + "description": "We'd also like to provide our users with a way of contacting Harry Hawk, so we're adding a link that sends an e-mail to Harry. To do this, we convert our data in the model to match the sap.m.URLHelper.normalizeEmail API. As soon as the user changes the name, the e-mail also changes. We need a custom formatter function for this." + }, + { + "n": 10, + "id": "10", + "title": "Property Formatting Using Data Types", + "previewUrl": "https://ui5.github.io/tutorials/databinding/build/10/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/databinding/databinding-step-10.zip", + "zipJs": "https://ui5.github.io/tutorials/databinding/databinding-step-10-js.zip", + "description": "OpenUI5 offers a set of simple data types, including Boolean, Currency, Date and Float. You can apply these data types to controls to ensure that the value displayed on the screen is formatted correctly. If the field is open for input, this also ensures that the user input meets the requirements of that data type. Let's add a new field called Sales Amount of type Currency." + }, + { + "n": 11, + "id": "11", + "title": "Validation Using `sap/ui/core/Messaging`", + "previewUrl": "https://ui5.github.io/tutorials/databinding/build/11/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/databinding/databinding-step-11.zip", + "zipJs": "https://ui5.github.io/tutorials/databinding/databinding-step-11-js.zip", + "description": "Up to this point, we've created a currency field that formats itself correctly. The currency data type can also validate user input to ensure it meets currency requirements. However, OpenUI5 manages data type validation functions and doesn't have a built-in mechanism for reporting error messages back to the UI. We therefore need a way to report error messages from validation functions back to the user. In this step, we're enabling validation for the entire app with a feature known as \"Messaging\". Once this is set up, any validation error messages based on user input get passed to Messaging, which then connects them to the appropriate view and control that caused the error." + }, + { + "n": 12, + "id": "12", + "title": "Aggregation Binding Using Templates", + "previewUrl": "https://ui5.github.io/tutorials/databinding/build/12/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/databinding/databinding-step-12.zip", + "zipJs": "https://ui5.github.io/tutorials/databinding/databinding-step-12-js.zip", + "description": "Aggregation binding, also known as \"list binding\", lets a control bind to a list within the model data. This binding allows relative binding to the list entries by its child controls." + }, + { + "n": 13, + "id": "13", + "title": "Element Binding", + "previewUrl": "https://ui5.github.io/tutorials/databinding/build/13/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/databinding/databinding-step-13.zip", + "zipJs": "https://ui5.github.io/tutorials/databinding/databinding-step-13-js.zip", + "description": "Now, let's do something with that newly generated list. Typically, you use a list to allow selection of an item and then display the details of that item elsewhere. To accomplish this, we use a form with relatively bound controls and bind it to the selected entity via element binding." + }, + { + "n": 14, + "id": "14", + "title": "Expression Binding", + "previewUrl": "https://ui5.github.io/tutorials/databinding/build/14/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/databinding/databinding-step-14.zip", + "zipJs": "https://ui5.github.io/tutorials/databinding/databinding-step-14-js.zip", + "description": "An expression binding lets you display a calculated value on the screen, which is derived from values found in a model object. This feature allows you to insert simple formatting or calculations directly into the data binding string. In this example, we're changing the color of the price depending on whether it's above or below a certain threshold. The threshold value is stored in the JSON model." + }, + { + "n": 15, + "id": "15", + "title": "Aggregation Binding Using a Factory Function", + "previewUrl": "https://ui5.github.io/tutorials/databinding/build/15/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/databinding/databinding-step-15.zip", + "zipJs": "https://ui5.github.io/tutorials/databinding/databinding-step-15-js.zip", + "description": "Instead of using a single hard-coded template control, we now opt for a factory function to generate different controls based on the data received at runtime. This approach is much more flexible and allows for the display of complex or heterogeneous data." + } + ] +} diff --git a/packages/databinding/steps/01/README.md b/packages/databinding/steps/01/README.md new file mode 100644 index 000000000..baaae8fec --- /dev/null +++ b/packages/databinding/steps/01/README.md @@ -0,0 +1,226 @@ + +# Step 1: No Data Binding + +In this step, we create a basic application and simply place some text on the screen using a standard `sap.m.Text` control. The text in this control is a hard-coded part of the control's definition; therefore, this is not an example of data binding! + +## Preview + +![The browser shows the text "Hi, my name is Harry Hawk"](assets/Tutorial_Data_Binding_Step_1_6d391d5.png "The browser shows the text "Hi, my name is Harry Hawk"") + +You can view this step live: [🔗 Live Preview of Step 1](https://ui5.github.io/tutorials/databinding/build/01/index-cdn.html). + +## Setup + +Open a terminal and install UI5 Tooling globally on your machine by executing the following command: + +```sh +npm install --global @ui5/cli +``` + +## Coding + +You can download the solution for this step here: <span class="ts-only">[📥 Download step 1](https://ui5.github.io/tutorials/databinding/databinding-step-01.zip)<span class="lang-suffix"> (TS)</span></span><span class="js-only">[📥 Download step 1](https://ui5.github.io/tutorials/databinding/databinding-step-01-js.zip)<span class="lang-suffix"> (JS)</span></span>. + +### Folder structure for this step + +```text +webapp/ +├── view/ +│ └── App.view.xml +├── Component.ts/.js +├── index.html +└── manifest.json +``` + +1. Create a folder on your local machine which will contain all the sources of the app we're going to build. We'll refer to this folder as the “app root folder”. + +2. Create a new file called `package.json` which will enable you to execute commands and consume packages from the [npm registry](https://www.npmjs.com/) via the npm command line interface. Enter the following content: + +## package.json \(New\) + +```json +{ + "name": "ui5.tutorial.databinding", + "private": true, + "version": "1.0.0", + "author": "SAP SE", + "description": "UI5 Demo App - Data Binding Tutorial", + "scripts": { + "start": "ui5 serve -o index.html" + } +} +``` + +3. Create a new folder named `webapp` in the app root folder. It will contain all the sources that become available in the browser later. We'll refer to this folder as the "webapp folder". + +4. Create a new HTML file named `index.html` in your webapp folder and enter the following content: + +## webapp/index.html \(New\) + +```html +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Data Binding Tutorial + + + +
+ + +``` + +5. Create a new file named `manifest.json` in the webapp folder; it's also known as the "app descriptor". All application-specific configuration options which we'll introduce in this tutorial will be added to this file. Enter the following content: + +## webapp/manifest.json \(New\) + +```json +{ + "_version": "2.8.0", + "sap.app": { + "id": "ui5.tutorial.databinding", + "type": "application", + "applicationVersion": { + "version": "1.0.0" + }, + "title": "Data Binding Tutorial", + "description": "A simple app that explains how to use data binding features of OpenUI5" + }, + "sap.ui": { + "technology": "UI5", + "deviceTypes": { + "desktop": true, + "tablet": true, + "phone": true + } + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.148", + "libs": { + "sap.m": {}, + "sap.ui.core": {} + } + }, + "contentDensities": { + "compact": true, + "cozy": true + }, + "rootView": { + "viewName": "ui5.tutorial.databinding.view.App", + "type": "XML", + "id": "app" + } + } +} +``` + +6. Create a new file named `Component.ts/.js` in the webapp folder. Enter the following content: + +## `webapp/Component.ts/.js` \(New\) + +```ts +// webapp/Component.ts +import UIComponent from "sap/ui/core/UIComponent"; + +/** + * @namespace ui5.tutorial.databinding + */ +export default class Component extends UIComponent { + public static metadata = { + interfaces: ["sap.ui.core.IAsyncContentCreation"], + manifest: "json" + }; +} +``` + +```js +// webapp/Component.js +sap.ui.define(["sap/ui/core/UIComponent"], function (UIComponent) { + "use strict"; + + return UIComponent.extend("ui5.tutorial.databinding.Component", { + metadata: { + interfaces: ["sap.ui.core.IAsyncContentCreation"], + manifest: "json" + } + }); +}); +``` + +7. Create a new folder named `view` in the webapp folder. Then, create a new file `App.view.xml` within the `view` folder. We start by placing the `sap.m.Text` control into the XML view. Since the value of the control's text property is hard-coded, it doesn't relate to any data that might exist within a model object. Therefore, data binding is **not** used here. + +## webapp/view/App.view.xml \(New\) + +```xml + + + +``` + +8. Open a terminal in the app root folder and execute `npm i -D @ui5/cli` to install UI5 Tooling in the app root folder. + +9. Execute `ui5 init` in the app root folder. + +10. Execute `ui5 use OpenUI5` + +11. Execute `ui5 add sap.m sap.ui.core sap.ui.layout themelib_sap_horizon` + +12. Configure the tooling extensions we installed from npm in our UI5 Tooling setup, so we can use them in our project: + +## ui5.yaml + +```yaml +specVersion: "4.0" +metadata: + name: "ui5.tutorial.databinding" +type: application +framework: + name: OpenUI5 + version: "1.148.1" + libraries: + - name: sap.m + - name: sap.ui.core + - name: sap.ui.layout + - name: themelib_sap_horizon +builder: + customTasks: + - name: ui5-tooling-transpile-task + afterTask: replaceVersion +server: + customMiddleware: + - name: ui5-tooling-transpile-middleware + afterMiddleware: compression + - name: ui5-middleware-serveframework + afterMiddleware: compression + - name: ui5-middleware-livereload + afterMiddleware: compression +``` + +13. Execute `npm start` to start the web server and to open a new browser window hosting your newly created `index.html`. + +*** + +**Next:** [Step 2: Creating a Model](../02/README.md "In this step, we create a model. It serves as a container for the data your application operates on.") + +*** + +**Related Information** + +[Data Binding](https://sdk.openui5.org/topic/68b9644a253741e8a4b9e4279a35c247.html "You use data binding to bind UI elements to data sources to keep the data in sync and allow data editing on the UI.") + +[Model View Controller \(MVC\)](https://sdk.openui5.org/topic/91f233476f4d1014b6dd926db0e91070.html "The Model View Controller (MVC) concept is used in OpenUI5 to separate the representation of information from the user interaction. This separation facilitates development and the changing of parts independently.") diff --git a/packages/databinding/steps/01/assets/Tutorial_Data_Binding_Step_1_6d391d5.png b/packages/databinding/steps/01/assets/Tutorial_Data_Binding_Step_1_6d391d5.png new file mode 100644 index 000000000..aaf6180a0 Binary files /dev/null and b/packages/databinding/steps/01/assets/Tutorial_Data_Binding_Step_1_6d391d5.png differ diff --git a/packages/databinding/steps/01/package.json b/packages/databinding/steps/01/package.json new file mode 100644 index 000000000..9546aadb1 --- /dev/null +++ b/packages/databinding/steps/01/package.json @@ -0,0 +1,19 @@ +{ + "name": "ui5.tutorial.databinding.step01", + "private": true, + "version": "1.0.0", + "author": "SAP SE", + "description": "UI5 Demo App - Data Binding Tutorial", + "scripts": { + "start": "ui5 serve -o index.html", + "typecheck": "tsc --noEmit" + }, + "devDependencies": { + "@openui5/types": "^1.148.1", + "@ui5/cli": "^4.0.53", + "typescript": "^6.0.3", + "ui5-middleware-livereload": "^3.3.1", + "ui5-middleware-serveframework": "^3.8.1", + "ui5-tooling-transpile": "^3.11.0" + } +} diff --git a/packages/databinding/steps/01/tsconfig.json b/packages/databinding/steps/01/tsconfig.json new file mode 100644 index 000000000..0b7fa1ae7 --- /dev/null +++ b/packages/databinding/steps/01/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "es2023", + "types": [ + "@openui5/types" + ], + "skipLibCheck": true, + "allowJs": true, + "strictPropertyInitialization": false, + "rootDir": "./webapp", + "paths": { + "ui5/tutorial/databinding/*": [ + "./webapp/*" + ] + }, + "strict": false, + "strictNullChecks": false + }, + "exclude": [ + "./webapp/test/e2e/**/*" + ], + "include": [ + "./webapp/**/*" + ] +} diff --git a/packages/databinding/steps/01/ui5.yaml b/packages/databinding/steps/01/ui5.yaml new file mode 100644 index 000000000..af70be177 --- /dev/null +++ b/packages/databinding/steps/01/ui5.yaml @@ -0,0 +1,24 @@ +specVersion: "4.0" +metadata: + name: "ui5.tutorial.databinding" +type: application +framework: + name: OpenUI5 + version: "1.148.1" + libraries: + - name: sap.m + - name: sap.ui.core + - name: sap.ui.layout + - name: themelib_sap_horizon +builder: + customTasks: + - name: ui5-tooling-transpile-task + afterTask: replaceVersion +server: + customMiddleware: + - name: ui5-tooling-transpile-middleware + afterMiddleware: compression + - name: ui5-middleware-serveframework + afterMiddleware: compression + - name: ui5-middleware-livereload + afterMiddleware: compression diff --git a/packages/databinding/steps/01/webapp/Component.ts b/packages/databinding/steps/01/webapp/Component.ts new file mode 100644 index 000000000..3fc494bca --- /dev/null +++ b/packages/databinding/steps/01/webapp/Component.ts @@ -0,0 +1,11 @@ +import UIComponent from "sap/ui/core/UIComponent"; + +/** + * @namespace ui5.tutorial.databinding + */ +export default class Component extends UIComponent { + public static metadata = { + interfaces: ["sap.ui.core.IAsyncContentCreation"], + manifest: "json" + }; +} diff --git a/packages/databinding/steps/01/webapp/index-cdn.html b/packages/databinding/steps/01/webapp/index-cdn.html new file mode 100644 index 000000000..e2b2e4833 --- /dev/null +++ b/packages/databinding/steps/01/webapp/index-cdn.html @@ -0,0 +1,22 @@ + + + + + + Data Binding Tutorial + + + +
+ + diff --git a/packages/databinding/steps/01/webapp/index.html b/packages/databinding/steps/01/webapp/index.html new file mode 100644 index 000000000..42edae4b3 --- /dev/null +++ b/packages/databinding/steps/01/webapp/index.html @@ -0,0 +1,22 @@ + + + + + + Data Binding Tutorial + + + +
+ + diff --git a/packages/databinding/steps/01/webapp/manifest.json b/packages/databinding/steps/01/webapp/manifest.json new file mode 100644 index 000000000..463185964 --- /dev/null +++ b/packages/databinding/steps/01/webapp/manifest.json @@ -0,0 +1,38 @@ +{ + "_version": "2.8.0", + "sap.app": { + "id": "ui5.tutorial.databinding", + "type": "application", + "applicationVersion": { + "version": "1.0.0" + }, + "title": "Data Binding Tutorial", + "description": "A simple app that explains how to use data binding features of OpenUI5" + }, + "sap.ui": { + "technology": "UI5", + "deviceTypes": { + "desktop": true, + "tablet": true, + "phone": true + } + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.148", + "libs": { + "sap.m": {}, + "sap.ui.core": {} + } + }, + "contentDensities": { + "compact": true, + "cozy": true + }, + "rootView": { + "viewName": "ui5.tutorial.databinding.view.App", + "type": "XML", + "id": "app" + } + } +} diff --git a/packages/databinding/steps/01/webapp/view/App.view.xml b/packages/databinding/steps/01/webapp/view/App.view.xml new file mode 100644 index 000000000..470494168 --- /dev/null +++ b/packages/databinding/steps/01/webapp/view/App.view.xml @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/packages/databinding/steps/02/README.md b/packages/databinding/steps/02/README.md new file mode 100644 index 000000000..34a2f277b --- /dev/null +++ b/packages/databinding/steps/02/README.md @@ -0,0 +1,99 @@ + +# Step 2: Creating a Model + +In this step, we create a model. It serves as a container for the data your application operates on. + +You can define the business data within a model using various formats: + +- JavaScript Object Notation \(JSON\) + +- Extensible Markup Language \(XML\) + +- OData + +- Your own custom format \(not covered in this tutorial\) + +> :note: +> There's also a special type of model called a "resource model". This model type is used as a wrapper object around a resource bundle file. The names of such files must end with `.properties`. They're typically used for holding language-specific text. +> +> We'll use this in [Step 6: Resource Models](../06/README.md). + +When you create JSON, XML, and resource models, the data they contain is loaded in a single request \(either from a file stored locally on the client or by requesting it from a Web server\). In other words, after the model's data has been requested, the entire model is known to the application. These models are known as client-side models. Tasks such as filtering and sorting are performed locally on the client. + +An OData model, however, is a server-side model. This means that whenever an application needs data from the model, it must be requested from the server. Such a request almost never returns all the data in the model, typically because this would be far more data than the client application requires. Consequently, tasks such as sorting and filtering should always be delegated to the server. + +In this tutorial, we focus on JSON models since they're the simplest ones to work with. + +## Preview + +![The browser shows the text "Hi, my name is Harry Hawk"](assets/Tutorial_Data_Binding_Step_1_6d391d5.png "The browser shows the text "Hi, my name is Harry Hawk"") + +You can view this step live: [🔗 Live Preview of Step 2](https://ui5.github.io/tutorials/databinding/build/02/index-cdn.html). + +## Coding + +You can download the solution for this step here: [📥 Download step 2](https://ui5.github.io/tutorials/databinding/databinding-step-02.zip) (TS)[📥 Download step 2](https://ui5.github.io/tutorials/databinding/databinding-step-02-js.zip) (JS). + +### Folder structure for this step + +```text +webapp/ +├── model/ +│ └── data.json +├── view/ +│ └── App.view.xml +├── Component.ts/.js +├── index.html +└── manifest.json +``` + +1. Create a new folder named `model` in the `webapp` folder. In this folder, create a file called `data.json` with the following content: + +## webapp/model/data.json \(New\) + +```json +{ + "greetingText": "Hi, my name is Harry Hawk" +} +``` + +2. Create a new JSON model in the `manifest.json` and set its path via a URI. This binds the model object to the app component and makes it globally available to all controls used within the application. + +## webapp/manifest.json + +```json +{ + ... + "sap.ui5": { + ... + "models": { + "": { + "type": "sap.ui.model.json.JSONModel", + "uri": "./model/data.json" + } + }, + ... + } +} +``` + +Generally speaking, a model object holding business data should be bound to the app's `Component.ts/.js` or to the view that displays the data. For an example, see the *Walkthrough* tutorial, [Step 7: JSON Model](../../walkthrough/steps/07/README.md) \(binding to the View\) or [Step 9: Component Configuration](../../walkthrough/steps/09/README.md) \(binding to the Component\). + +The text that is displayed on the UI is still hard-coded and not taken from the model. We'll bind the property `greetingText` to our UI control in the next step. + +> :note: +> You can set models on every control by calling `setModel()`. The model is then propagated to all aggregated child controls \(and their children, and so on …\). All child controls then have access to that model. + +*** + +**Next:** [Step 3: Create Property Binding](../03/README.md "Although there is no visible difference, the text on the screen is now derived from model data.") + +**Previous:** [Step 1: No Data Binding](../01/README.md "In this step, we create a basic application and simply place some text on the screen using a standard sap.m.Text control.") + +*** + +**Related Information** + +[Models](https://sdk.openui5.org/topic/e1b625940c104b558e52f47afe5ddb4f.html "A model in the Model View Controller concept holds the data and provides methods to retrieve the data from the database and to set and update data.") + +[JSON Model](https://sdk.openui5.org/topic/96804e3315ff440aa0a50fd290805116.html#loio96804e3315ff440aa0a50fd290805116 "The JSON model can be used to bind controls to JavaScript object data, which is usually serialized in the JSON format.") diff --git a/packages/databinding/steps/02/assets/Tutorial_Data_Binding_Step_1_6d391d5.png b/packages/databinding/steps/02/assets/Tutorial_Data_Binding_Step_1_6d391d5.png new file mode 100644 index 000000000..aaf6180a0 Binary files /dev/null and b/packages/databinding/steps/02/assets/Tutorial_Data_Binding_Step_1_6d391d5.png differ diff --git a/packages/databinding/steps/02/package.json b/packages/databinding/steps/02/package.json new file mode 100644 index 000000000..e08510fc6 --- /dev/null +++ b/packages/databinding/steps/02/package.json @@ -0,0 +1,19 @@ +{ + "name": "ui5.tutorial.databinding.step02", + "private": true, + "version": "1.0.0", + "author": "SAP SE", + "description": "UI5 Demo App - Data Binding Tutorial", + "scripts": { + "start": "ui5 serve -o index.html", + "typecheck": "tsc --noEmit" + }, + "devDependencies": { + "@openui5/types": "^1.148.1", + "@ui5/cli": "^4.0.53", + "typescript": "^6.0.3", + "ui5-middleware-livereload": "^3.3.1", + "ui5-middleware-serveframework": "^3.8.1", + "ui5-tooling-transpile": "^3.11.0" + } +} diff --git a/packages/databinding/steps/02/tsconfig.json b/packages/databinding/steps/02/tsconfig.json new file mode 100644 index 000000000..0b7fa1ae7 --- /dev/null +++ b/packages/databinding/steps/02/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "es2023", + "types": [ + "@openui5/types" + ], + "skipLibCheck": true, + "allowJs": true, + "strictPropertyInitialization": false, + "rootDir": "./webapp", + "paths": { + "ui5/tutorial/databinding/*": [ + "./webapp/*" + ] + }, + "strict": false, + "strictNullChecks": false + }, + "exclude": [ + "./webapp/test/e2e/**/*" + ], + "include": [ + "./webapp/**/*" + ] +} diff --git a/packages/databinding/steps/02/ui5.yaml b/packages/databinding/steps/02/ui5.yaml new file mode 100644 index 000000000..af70be177 --- /dev/null +++ b/packages/databinding/steps/02/ui5.yaml @@ -0,0 +1,24 @@ +specVersion: "4.0" +metadata: + name: "ui5.tutorial.databinding" +type: application +framework: + name: OpenUI5 + version: "1.148.1" + libraries: + - name: sap.m + - name: sap.ui.core + - name: sap.ui.layout + - name: themelib_sap_horizon +builder: + customTasks: + - name: ui5-tooling-transpile-task + afterTask: replaceVersion +server: + customMiddleware: + - name: ui5-tooling-transpile-middleware + afterMiddleware: compression + - name: ui5-middleware-serveframework + afterMiddleware: compression + - name: ui5-middleware-livereload + afterMiddleware: compression diff --git a/packages/databinding/steps/02/webapp/Component.ts b/packages/databinding/steps/02/webapp/Component.ts new file mode 100644 index 000000000..3fc494bca --- /dev/null +++ b/packages/databinding/steps/02/webapp/Component.ts @@ -0,0 +1,11 @@ +import UIComponent from "sap/ui/core/UIComponent"; + +/** + * @namespace ui5.tutorial.databinding + */ +export default class Component extends UIComponent { + public static metadata = { + interfaces: ["sap.ui.core.IAsyncContentCreation"], + manifest: "json" + }; +} diff --git a/packages/databinding/steps/02/webapp/index-cdn.html b/packages/databinding/steps/02/webapp/index-cdn.html new file mode 100644 index 000000000..e2b2e4833 --- /dev/null +++ b/packages/databinding/steps/02/webapp/index-cdn.html @@ -0,0 +1,22 @@ + + + + + + Data Binding Tutorial + + + +
+ + diff --git a/packages/databinding/steps/02/webapp/index.html b/packages/databinding/steps/02/webapp/index.html new file mode 100644 index 000000000..42edae4b3 --- /dev/null +++ b/packages/databinding/steps/02/webapp/index.html @@ -0,0 +1,22 @@ + + + + + + Data Binding Tutorial + + + +
+ + diff --git a/packages/databinding/steps/02/webapp/manifest.json b/packages/databinding/steps/02/webapp/manifest.json new file mode 100644 index 000000000..c2727bd66 --- /dev/null +++ b/packages/databinding/steps/02/webapp/manifest.json @@ -0,0 +1,44 @@ +{ + "_version": "2.8.0", + "sap.app": { + "id": "ui5.tutorial.databinding", + "type": "application", + "applicationVersion": { + "version": "1.0.0" + }, + "title": "Data Binding Tutorial", + "description": "A simple app that explains how to use data binding features of OpenUI5" + }, + "sap.ui": { + "technology": "UI5", + "deviceTypes": { + "desktop": true, + "tablet": true, + "phone": true + } + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.148", + "libs": { + "sap.m": {}, + "sap.ui.core": {} + } + }, + "contentDensities": { + "compact": true, + "cozy": true + }, + "models": { + "": { + "type": "sap.ui.model.json.JSONModel", + "uri": "./model/data.json" + } + }, + "rootView": { + "viewName": "ui5.tutorial.databinding.view.App", + "type": "XML", + "id": "app" + } + } +} diff --git a/packages/databinding/steps/02/webapp/model/data.json b/packages/databinding/steps/02/webapp/model/data.json new file mode 100644 index 000000000..7b62fba1f --- /dev/null +++ b/packages/databinding/steps/02/webapp/model/data.json @@ -0,0 +1,3 @@ +{ + "greetingText": "Hi, my name is Harry Hawk" +} \ No newline at end of file diff --git a/packages/databinding/steps/02/webapp/view/App.view.xml b/packages/databinding/steps/02/webapp/view/App.view.xml new file mode 100644 index 000000000..470494168 --- /dev/null +++ b/packages/databinding/steps/02/webapp/view/App.view.xml @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/packages/databinding/steps/03/README.md b/packages/databinding/steps/03/README.md new file mode 100644 index 000000000..66644a6b2 --- /dev/null +++ b/packages/databinding/steps/03/README.md @@ -0,0 +1,53 @@ + +# Step 3: Create Property Binding + +Although there is no visible difference, the text on the screen is now derived from model data. + +## Preview + +![The browser shows the text "Hi, my name is Harry Hawk"](assets/Tutorial_Data_Binding_Step_1_6d391d5.png "The browser shows the text "Hi, my name is Harry Hawk"") + +You can view this step live: [🔗 Live Preview of Step 3](https://ui5.github.io/tutorials/databinding/build/03/index-cdn.html). + +## Coding + +You can download the solution for this step here: [📥 Download step 3](https://ui5.github.io/tutorials/databinding/databinding-step-03.zip) (TS)[📥 Download step 3](https://ui5.github.io/tutorials/databinding/databinding-step-03-js.zip) (JS). + +### Folder structure for this step + +```text +webapp/ +├── model/ +│ └── data.json +├── view/ +│ └── App.view.xml +├── Component.ts/.js +├── index.html +└── manifest.json +``` + +Assign the `text` property of the `sap.m.Text` control to the value `{/greetingText}`. The curly brackets enclosing a binding path \(binding syntax\) are automatically interpreted as a binding. These binding instances are called property bindings. In this scenario, the control's `text` property is bound to the `greetingText` property at the root of the default model. The slash \(`/`\) at the beginning of the binding path signifies an absolute binding path. + +## webapp/view/App.view.xml + +```xml + + + +``` + +*** + +**Next:** [Step 4: Two-Way Data Binding](../04/README.md "In the examples we've looked at so far, we've displayed the value of a model property using a read-only field.") + +**Previous:** [Step 2: Creating a Model](../02/README.md "In this step, we create a model.") + +*** + +**Related Information** + +[Binding Types](https://sdk.openui5.org/topic/91f0d8ab6f4d1014b6dd926db0e91070.html "Depending on the different use cases, you can use different binding types: Propety binding, context binding, and list binding.") + +[Property Binding](https://sdk.openui5.org/topic/91f0652b6f4d1014b6dd926db0e91070.html "With property binding, you can initialize properties of a control automatically and update them based on the data of the model.") diff --git a/packages/databinding/steps/03/assets/Tutorial_Data_Binding_Step_1_6d391d5.png b/packages/databinding/steps/03/assets/Tutorial_Data_Binding_Step_1_6d391d5.png new file mode 100644 index 000000000..aaf6180a0 Binary files /dev/null and b/packages/databinding/steps/03/assets/Tutorial_Data_Binding_Step_1_6d391d5.png differ diff --git a/packages/databinding/steps/03/package.json b/packages/databinding/steps/03/package.json new file mode 100644 index 000000000..82943f88f --- /dev/null +++ b/packages/databinding/steps/03/package.json @@ -0,0 +1,19 @@ +{ + "name": "ui5.tutorial.databinding.step03", + "private": true, + "version": "1.0.0", + "author": "SAP SE", + "description": "UI5 Demo App - Data Binding Tutorial", + "scripts": { + "start": "ui5 serve -o index.html", + "typecheck": "tsc --noEmit" + }, + "devDependencies": { + "@openui5/types": "^1.148.1", + "@ui5/cli": "^4.0.53", + "typescript": "^6.0.3", + "ui5-middleware-livereload": "^3.3.1", + "ui5-middleware-serveframework": "^3.8.1", + "ui5-tooling-transpile": "^3.11.0" + } +} diff --git a/packages/databinding/steps/03/tsconfig.json b/packages/databinding/steps/03/tsconfig.json new file mode 100644 index 000000000..0b7fa1ae7 --- /dev/null +++ b/packages/databinding/steps/03/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "es2023", + "types": [ + "@openui5/types" + ], + "skipLibCheck": true, + "allowJs": true, + "strictPropertyInitialization": false, + "rootDir": "./webapp", + "paths": { + "ui5/tutorial/databinding/*": [ + "./webapp/*" + ] + }, + "strict": false, + "strictNullChecks": false + }, + "exclude": [ + "./webapp/test/e2e/**/*" + ], + "include": [ + "./webapp/**/*" + ] +} diff --git a/packages/databinding/steps/03/ui5.yaml b/packages/databinding/steps/03/ui5.yaml new file mode 100644 index 000000000..af70be177 --- /dev/null +++ b/packages/databinding/steps/03/ui5.yaml @@ -0,0 +1,24 @@ +specVersion: "4.0" +metadata: + name: "ui5.tutorial.databinding" +type: application +framework: + name: OpenUI5 + version: "1.148.1" + libraries: + - name: sap.m + - name: sap.ui.core + - name: sap.ui.layout + - name: themelib_sap_horizon +builder: + customTasks: + - name: ui5-tooling-transpile-task + afterTask: replaceVersion +server: + customMiddleware: + - name: ui5-tooling-transpile-middleware + afterMiddleware: compression + - name: ui5-middleware-serveframework + afterMiddleware: compression + - name: ui5-middleware-livereload + afterMiddleware: compression diff --git a/packages/databinding/steps/03/webapp/Component.ts b/packages/databinding/steps/03/webapp/Component.ts new file mode 100644 index 000000000..3fc494bca --- /dev/null +++ b/packages/databinding/steps/03/webapp/Component.ts @@ -0,0 +1,11 @@ +import UIComponent from "sap/ui/core/UIComponent"; + +/** + * @namespace ui5.tutorial.databinding + */ +export default class Component extends UIComponent { + public static metadata = { + interfaces: ["sap.ui.core.IAsyncContentCreation"], + manifest: "json" + }; +} diff --git a/packages/databinding/steps/03/webapp/index-cdn.html b/packages/databinding/steps/03/webapp/index-cdn.html new file mode 100644 index 000000000..e2b2e4833 --- /dev/null +++ b/packages/databinding/steps/03/webapp/index-cdn.html @@ -0,0 +1,22 @@ + + + + + + Data Binding Tutorial + + + +
+ + diff --git a/packages/databinding/steps/03/webapp/index.html b/packages/databinding/steps/03/webapp/index.html new file mode 100644 index 000000000..42edae4b3 --- /dev/null +++ b/packages/databinding/steps/03/webapp/index.html @@ -0,0 +1,22 @@ + + + + + + Data Binding Tutorial + + + +
+ + diff --git a/packages/databinding/steps/03/webapp/manifest.json b/packages/databinding/steps/03/webapp/manifest.json new file mode 100644 index 000000000..c2727bd66 --- /dev/null +++ b/packages/databinding/steps/03/webapp/manifest.json @@ -0,0 +1,44 @@ +{ + "_version": "2.8.0", + "sap.app": { + "id": "ui5.tutorial.databinding", + "type": "application", + "applicationVersion": { + "version": "1.0.0" + }, + "title": "Data Binding Tutorial", + "description": "A simple app that explains how to use data binding features of OpenUI5" + }, + "sap.ui": { + "technology": "UI5", + "deviceTypes": { + "desktop": true, + "tablet": true, + "phone": true + } + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.148", + "libs": { + "sap.m": {}, + "sap.ui.core": {} + } + }, + "contentDensities": { + "compact": true, + "cozy": true + }, + "models": { + "": { + "type": "sap.ui.model.json.JSONModel", + "uri": "./model/data.json" + } + }, + "rootView": { + "viewName": "ui5.tutorial.databinding.view.App", + "type": "XML", + "id": "app" + } + } +} diff --git a/packages/databinding/steps/03/webapp/model/data.json b/packages/databinding/steps/03/webapp/model/data.json new file mode 100644 index 000000000..7b62fba1f --- /dev/null +++ b/packages/databinding/steps/03/webapp/model/data.json @@ -0,0 +1,3 @@ +{ + "greetingText": "Hi, my name is Harry Hawk" +} \ No newline at end of file diff --git a/packages/databinding/steps/03/webapp/view/App.view.xml b/packages/databinding/steps/03/webapp/view/App.view.xml new file mode 100644 index 000000000..ac3213a43 --- /dev/null +++ b/packages/databinding/steps/03/webapp/view/App.view.xml @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/packages/databinding/steps/04/README.md b/packages/databinding/steps/04/README.md new file mode 100644 index 000000000..1e420ec1f --- /dev/null +++ b/packages/databinding/steps/04/README.md @@ -0,0 +1,111 @@ + +# Step 4: Two-Way Data Binding + +In the examples we've looked at so far, we've displayed the value of a model property using a read-only field. We'll now change the user interface to display first and last name fields using `sap.m.Input` fields. We're also adding a check box control to enable or disable both input fields. This setup illustrates a feature known as "two-way data binding". As the view now contains more controls, we're also moving the view definition into an XML file. + +## Preview + +### Two input fields and a checkbox to enable or disable them + +![Two input fields and a checkbox to enable or disable them](assets/Tutorial_Data_Binding_Step_4_61d68f1.png "Two input fields and a checkbox to enable or disable them") + +You can view this step live: [🔗 Live Preview of Step 4](https://ui5.github.io/tutorials/databinding/build/04/index-cdn.html). + +## Coding + +You can download the solution for this step here: [📥 Download step 4](https://ui5.github.io/tutorials/databinding/databinding-step-04.zip) (TS)[📥 Download step 4](https://ui5.github.io/tutorials/databinding/databinding-step-04-js.zip) (JS). + +### Folder structure for this step + +```text +webapp/ +├── model/ +│ └── data.json +├── view/ +│ └── App.view.xml +├── Component.ts/.js +├── index.html +└── manifest.json +``` + +Replace the content of the `App.view.xml` file with the following content: + +## webapp/view/App.view.xml + +```xml + + + + + + +``` + +> :note: +> Requiring `sap/ui/layout/form/ColumnLayout` is needed because we use the `ColumnLayout` as `layout` for the `sap/ui/layout/form/SimpleForm`. +> The `sap/ui/layout/form/SimpleForm` requires the configured layout, in case it's not done by the consumer but this may cause an additional rendering cycle if rendering starts before the layout finished loading. + +Replace the content of the `data.json` file in the `model` folder with the following content: + +## webapp/model/data.json + +```json +{ + "firstName": "Harry", + "lastName": "Hawk", + "enabled": true, + "panelHeaderText": "Data Binding Basics" +} +``` + +After these changes, refresh the application preview and select or deselect the checkbox. You'll notice that the input fields are automatically enabled or disabled in response to the state of the checkbox. + +![Two input fields and a checked checkbox to enable or disable them](assets/Tutorial_Data_Binding_Step_4_61d68f1.png "Two input fields and a checked checkbox to enable or disable them")![Two input fields and a unchecked checkbox to enable or disable them](assets/Tutorial_Data_Binding_Step_4_2_6222561.png "Two input fields and a unchecked checkbox to enable or disable them") + +It is clear that we haven't written any code to transfer data between the user interface and the model, yet the `Input` controls are enabled or disabled according to the state of the checkbox. This behavior results from the fact that OData models and JSON models implement two-way data binding. For JSON models, two-way binding is the default behavior. For more information, see [Binding Modes](https://sdk.openui5.org/topic/68b9644a253741e8a4b9e4279a35c247.html#loio68b9644a253741e8a4b9e4279a35c247/section_BindingModes). + +Two things are happening here: + +- Data binding allows the property of a control to derive its value from any suitable property in a model. + +- OpenUI5 automatically handles the transport of data from the model to the controls and back from the controls to the model. This is called two-way binding. + +*** + +**Next:** [Step 5: One-Way Data Binding](../05/README.md "Unlike the two-way binding behavior we've seen, one-way data binding is also possible.") + +**Previous:** [Step 3: Create Property Binding](../03/README.md "Although there is no visible difference, the text on the screen is now derived from model data.") + +*** + +**Related Information** + +[Data Binding](https://sdk.openui5.org/topic/68b9644a253741e8a4b9e4279a35c247.html "You use data binding to bind UI elements to data sources to keep the data in sync and allow data editing on the UI.") diff --git a/packages/databinding/steps/04/assets/Tutorial_Data_Binding_Step_4_2_6222561.png b/packages/databinding/steps/04/assets/Tutorial_Data_Binding_Step_4_2_6222561.png new file mode 100644 index 000000000..a2c0a67a9 Binary files /dev/null and b/packages/databinding/steps/04/assets/Tutorial_Data_Binding_Step_4_2_6222561.png differ diff --git a/packages/databinding/steps/04/assets/Tutorial_Data_Binding_Step_4_61d68f1.png b/packages/databinding/steps/04/assets/Tutorial_Data_Binding_Step_4_61d68f1.png new file mode 100644 index 000000000..d701c3131 Binary files /dev/null and b/packages/databinding/steps/04/assets/Tutorial_Data_Binding_Step_4_61d68f1.png differ diff --git a/packages/databinding/steps/04/package.json b/packages/databinding/steps/04/package.json new file mode 100644 index 000000000..aabaff373 --- /dev/null +++ b/packages/databinding/steps/04/package.json @@ -0,0 +1,19 @@ +{ + "name": "ui5.tutorial.databinding.step04", + "private": true, + "version": "1.0.0", + "author": "SAP SE", + "description": "UI5 Demo App - Data Binding Tutorial", + "scripts": { + "start": "ui5 serve -o index.html", + "typecheck": "tsc --noEmit" + }, + "devDependencies": { + "@openui5/types": "^1.148.1", + "@ui5/cli": "^4.0.53", + "typescript": "^6.0.3", + "ui5-middleware-livereload": "^3.3.1", + "ui5-middleware-serveframework": "^3.8.1", + "ui5-tooling-transpile": "^3.11.0" + } +} diff --git a/packages/databinding/steps/04/tsconfig.json b/packages/databinding/steps/04/tsconfig.json new file mode 100644 index 000000000..0b7fa1ae7 --- /dev/null +++ b/packages/databinding/steps/04/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "es2023", + "types": [ + "@openui5/types" + ], + "skipLibCheck": true, + "allowJs": true, + "strictPropertyInitialization": false, + "rootDir": "./webapp", + "paths": { + "ui5/tutorial/databinding/*": [ + "./webapp/*" + ] + }, + "strict": false, + "strictNullChecks": false + }, + "exclude": [ + "./webapp/test/e2e/**/*" + ], + "include": [ + "./webapp/**/*" + ] +} diff --git a/packages/databinding/steps/04/ui5.yaml b/packages/databinding/steps/04/ui5.yaml new file mode 100644 index 000000000..af70be177 --- /dev/null +++ b/packages/databinding/steps/04/ui5.yaml @@ -0,0 +1,24 @@ +specVersion: "4.0" +metadata: + name: "ui5.tutorial.databinding" +type: application +framework: + name: OpenUI5 + version: "1.148.1" + libraries: + - name: sap.m + - name: sap.ui.core + - name: sap.ui.layout + - name: themelib_sap_horizon +builder: + customTasks: + - name: ui5-tooling-transpile-task + afterTask: replaceVersion +server: + customMiddleware: + - name: ui5-tooling-transpile-middleware + afterMiddleware: compression + - name: ui5-middleware-serveframework + afterMiddleware: compression + - name: ui5-middleware-livereload + afterMiddleware: compression diff --git a/packages/databinding/steps/04/webapp/Component.ts b/packages/databinding/steps/04/webapp/Component.ts new file mode 100644 index 000000000..3fc494bca --- /dev/null +++ b/packages/databinding/steps/04/webapp/Component.ts @@ -0,0 +1,11 @@ +import UIComponent from "sap/ui/core/UIComponent"; + +/** + * @namespace ui5.tutorial.databinding + */ +export default class Component extends UIComponent { + public static metadata = { + interfaces: ["sap.ui.core.IAsyncContentCreation"], + manifest: "json" + }; +} diff --git a/packages/databinding/steps/04/webapp/index-cdn.html b/packages/databinding/steps/04/webapp/index-cdn.html new file mode 100644 index 000000000..e2b2e4833 --- /dev/null +++ b/packages/databinding/steps/04/webapp/index-cdn.html @@ -0,0 +1,22 @@ + + + + + + Data Binding Tutorial + + + +
+ + diff --git a/packages/databinding/steps/04/webapp/index.html b/packages/databinding/steps/04/webapp/index.html new file mode 100644 index 000000000..42edae4b3 --- /dev/null +++ b/packages/databinding/steps/04/webapp/index.html @@ -0,0 +1,22 @@ + + + + + + Data Binding Tutorial + + + +
+ + diff --git a/packages/databinding/steps/04/webapp/manifest.json b/packages/databinding/steps/04/webapp/manifest.json new file mode 100644 index 000000000..5757104d5 --- /dev/null +++ b/packages/databinding/steps/04/webapp/manifest.json @@ -0,0 +1,45 @@ +{ + "_version": "2.8.0", + "sap.app": { + "id": "ui5.tutorial.databinding", + "type": "application", + "applicationVersion": { + "version": "1.0.0" + }, + "title": "Data Binding Tutorial", + "description": "A simple app that explains how to use data binding features of OpenUI5" + }, + "sap.ui": { + "technology": "UI5", + "deviceTypes": { + "desktop": true, + "tablet": true, + "phone": true + } + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.148", + "libs": { + "sap.m": {}, + "sap.ui.core": {}, + "sap.ui.layout": {} + } + }, + "contentDensities": { + "compact": true, + "cozy": true + }, + "models": { + "": { + "type": "sap.ui.model.json.JSONModel", + "uri": "./model/data.json" + } + }, + "rootView": { + "viewName": "ui5.tutorial.databinding.view.App", + "type": "XML", + "id": "app" + } + } +} diff --git a/packages/databinding/steps/04/webapp/model/data.json b/packages/databinding/steps/04/webapp/model/data.json new file mode 100644 index 000000000..5a0f07035 --- /dev/null +++ b/packages/databinding/steps/04/webapp/model/data.json @@ -0,0 +1,6 @@ +{ + "firstName": "Harry", + "lastName": "Hawk", + "enabled": true, + "panelHeaderText": "Data Binding Basics" +} \ No newline at end of file diff --git a/packages/databinding/steps/04/webapp/view/App.view.xml b/packages/databinding/steps/04/webapp/view/App.view.xml new file mode 100644 index 000000000..d90a308cf --- /dev/null +++ b/packages/databinding/steps/04/webapp/view/App.view.xml @@ -0,0 +1,34 @@ + + + + + + diff --git a/packages/databinding/steps/05/README.md b/packages/databinding/steps/05/README.md new file mode 100644 index 000000000..f2e0edab0 --- /dev/null +++ b/packages/databinding/steps/05/README.md @@ -0,0 +1,95 @@ + +# Step 5: One-Way Data Binding + +Unlike the two-way binding behavior we've seen, one-way data binding is also possible. In this case, data travels in one direction only: from the model, through the binding instance, to the consumer \(usually the property of a control\), but never in the other direction. Let's modify the previous example to use one-way data binding. This shows how you can switch off the flow of data from the user interface back to the model if needed. + +## Preview + +### Two input fields and a checkbox + +![Two input fields and a checkbox](assets/Tutorial_Data_Binding_Step_4_61d68f1.png "Two input fields and a checkbox") + +You can view this step live: [🔗 Live Preview of Step 5](https://ui5.github.io/tutorials/databinding/build/05/index-cdn.html). + +## Coding + +You can download the solution for this step here: [📥 Download step 5](https://ui5.github.io/tutorials/databinding/databinding-step-05.zip) (TS)[📥 Download step 5](https://ui5.github.io/tutorials/databinding/databinding-step-05-js.zip) (JS). + +### Folder structure for this step + +```text +webapp/ +├── model/ +│ └── data.json +├── view/ +│ └── App.view.xml +├── Component.ts/.js +├── index.html +└── manifest.json +``` + +Insert the highlighted code into the `Component.ts/.js` file. The `init` function calls the init function of its parent, retrieves the default model instance bound to the component, and sets the default binding mode to one-way data binding. + +## `webapp/Component.ts/.js` + +```ts +// webapp/Component.ts +import UIComponent from "sap/ui/core/UIComponent"; +import BindingMode from "sap/ui/model/BindingMode"; + +/** + * @namespace ui5.tutorial.databinding + */ +export default class Component extends UIComponent { + public static metadata = { + interfaces: ["sap.ui.core.IAsyncContentCreation"], + manifest: "json" + }; + + public init(): void { + super.init(); + this.getModel().setDefaultBindingMode(BindingMode.OneWay); + } +} +``` + +```js +// webapp/Component.js +sap.ui.define(["sap/ui/core/UIComponent", "sap/ui/model/BindingMode"], function (UIComponent, BindingMode) { + "use strict"; + + return UIComponent.extend("ui5.tutorial.databinding.Component", { + metadata: { + interfaces: ["sap.ui.core.IAsyncContentCreation"], + manifest: "json" + }, + init: function () { + UIComponent.prototype.init.call(this); + this.getModel().setDefaultBindingMode(BindingMode.OneWay); + } + }); +}); +``` + +Now, regardless of the state the checkbox is in, the input fields remain open for input, because one-way data binding ensures that data flows only from the model to the UI, but never in the other direction. + +The binding mode \(one-way or two-way\) is set on the model itself. Therefore, unless you specifically alter it, a binding instance will always be created using the model's default binding mode. + +If you wish to alter the binding mode, you've got two options: + +- Alter the model's default binding mode. This is the approach we used above. + +- Specify the data binding mode for a specific binding instance by using the `oBindingInfo.mode` parameter. This change only applies to this data binding instance. Any other binding instances will continue to use the model's default binding mode.For more information, see [API Reference: `sap.ui.base.ManagedObject.bindProperty`](https://sdk.openui5.org/#/api/sap.ui.base.ManagedObject/methods/bindProperty). + +> :note: +> There are two important points to understand about alterations to a model object's data binding mode: +> +> - If you alter the default binding mode of a model \(as in the example above\), **all** binding instances created after that point in time will use the altered binding mode, unless you explicitly say otherwise. +> +> - Altering a model's default binding mode doesn't affect already existing binding instances. + +*** + +**Next:** [Step 6: Resource Models](../06/README.md "Business applications often require language-specific \(translatable\) text used as labels and descriptions on the user interface.") + +**Previous:** [Step 4: Two-Way Data Binding](../04/README.md "In the examples we've looked at so far, we've displayed the value of a model property using a read-only field.") diff --git a/packages/databinding/steps/05/assets/Tutorial_Data_Binding_Step_4_61d68f1.png b/packages/databinding/steps/05/assets/Tutorial_Data_Binding_Step_4_61d68f1.png new file mode 100644 index 000000000..d701c3131 Binary files /dev/null and b/packages/databinding/steps/05/assets/Tutorial_Data_Binding_Step_4_61d68f1.png differ diff --git a/packages/databinding/steps/05/package.json b/packages/databinding/steps/05/package.json new file mode 100644 index 000000000..232aa477f --- /dev/null +++ b/packages/databinding/steps/05/package.json @@ -0,0 +1,19 @@ +{ + "name": "ui5.tutorial.databinding.step05", + "private": true, + "version": "1.0.0", + "author": "SAP SE", + "description": "UI5 Demo App - Data Binding Tutorial", + "scripts": { + "start": "ui5 serve -o index.html", + "typecheck": "tsc --noEmit" + }, + "devDependencies": { + "@openui5/types": "^1.148.1", + "@ui5/cli": "^4.0.53", + "typescript": "^6.0.3", + "ui5-middleware-livereload": "^3.3.1", + "ui5-middleware-serveframework": "^3.8.1", + "ui5-tooling-transpile": "^3.11.0" + } +} diff --git a/packages/databinding/steps/05/tsconfig.json b/packages/databinding/steps/05/tsconfig.json new file mode 100644 index 000000000..0b7fa1ae7 --- /dev/null +++ b/packages/databinding/steps/05/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "es2023", + "types": [ + "@openui5/types" + ], + "skipLibCheck": true, + "allowJs": true, + "strictPropertyInitialization": false, + "rootDir": "./webapp", + "paths": { + "ui5/tutorial/databinding/*": [ + "./webapp/*" + ] + }, + "strict": false, + "strictNullChecks": false + }, + "exclude": [ + "./webapp/test/e2e/**/*" + ], + "include": [ + "./webapp/**/*" + ] +} diff --git a/packages/databinding/steps/05/ui5.yaml b/packages/databinding/steps/05/ui5.yaml new file mode 100644 index 000000000..af70be177 --- /dev/null +++ b/packages/databinding/steps/05/ui5.yaml @@ -0,0 +1,24 @@ +specVersion: "4.0" +metadata: + name: "ui5.tutorial.databinding" +type: application +framework: + name: OpenUI5 + version: "1.148.1" + libraries: + - name: sap.m + - name: sap.ui.core + - name: sap.ui.layout + - name: themelib_sap_horizon +builder: + customTasks: + - name: ui5-tooling-transpile-task + afterTask: replaceVersion +server: + customMiddleware: + - name: ui5-tooling-transpile-middleware + afterMiddleware: compression + - name: ui5-middleware-serveframework + afterMiddleware: compression + - name: ui5-middleware-livereload + afterMiddleware: compression diff --git a/packages/databinding/steps/05/webapp/Component.ts b/packages/databinding/steps/05/webapp/Component.ts new file mode 100644 index 000000000..035c1884d --- /dev/null +++ b/packages/databinding/steps/05/webapp/Component.ts @@ -0,0 +1,17 @@ +import UIComponent from "sap/ui/core/UIComponent"; +import BindingMode from "sap/ui/model/BindingMode"; + +/** + * @namespace ui5.tutorial.databinding + */ +export default class Component extends UIComponent { + public static metadata = { + interfaces: ["sap.ui.core.IAsyncContentCreation"], + manifest: "json" + }; + + public init(): void { + super.init(); + this.getModel().setDefaultBindingMode(BindingMode.OneWay); + } +} diff --git a/packages/databinding/steps/05/webapp/index-cdn.html b/packages/databinding/steps/05/webapp/index-cdn.html new file mode 100644 index 000000000..e2b2e4833 --- /dev/null +++ b/packages/databinding/steps/05/webapp/index-cdn.html @@ -0,0 +1,22 @@ + + + + + + Data Binding Tutorial + + + +
+ + diff --git a/packages/databinding/steps/05/webapp/index.html b/packages/databinding/steps/05/webapp/index.html new file mode 100644 index 000000000..42edae4b3 --- /dev/null +++ b/packages/databinding/steps/05/webapp/index.html @@ -0,0 +1,22 @@ + + + + + + Data Binding Tutorial + + + +
+ + diff --git a/packages/databinding/steps/05/webapp/manifest.json b/packages/databinding/steps/05/webapp/manifest.json new file mode 100644 index 000000000..5757104d5 --- /dev/null +++ b/packages/databinding/steps/05/webapp/manifest.json @@ -0,0 +1,45 @@ +{ + "_version": "2.8.0", + "sap.app": { + "id": "ui5.tutorial.databinding", + "type": "application", + "applicationVersion": { + "version": "1.0.0" + }, + "title": "Data Binding Tutorial", + "description": "A simple app that explains how to use data binding features of OpenUI5" + }, + "sap.ui": { + "technology": "UI5", + "deviceTypes": { + "desktop": true, + "tablet": true, + "phone": true + } + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.148", + "libs": { + "sap.m": {}, + "sap.ui.core": {}, + "sap.ui.layout": {} + } + }, + "contentDensities": { + "compact": true, + "cozy": true + }, + "models": { + "": { + "type": "sap.ui.model.json.JSONModel", + "uri": "./model/data.json" + } + }, + "rootView": { + "viewName": "ui5.tutorial.databinding.view.App", + "type": "XML", + "id": "app" + } + } +} diff --git a/packages/databinding/steps/05/webapp/model/data.json b/packages/databinding/steps/05/webapp/model/data.json new file mode 100644 index 000000000..5a0f07035 --- /dev/null +++ b/packages/databinding/steps/05/webapp/model/data.json @@ -0,0 +1,6 @@ +{ + "firstName": "Harry", + "lastName": "Hawk", + "enabled": true, + "panelHeaderText": "Data Binding Basics" +} \ No newline at end of file diff --git a/packages/databinding/steps/05/webapp/view/App.view.xml b/packages/databinding/steps/05/webapp/view/App.view.xml new file mode 100644 index 000000000..d90a308cf --- /dev/null +++ b/packages/databinding/steps/05/webapp/view/App.view.xml @@ -0,0 +1,34 @@ + + + + + + diff --git a/packages/databinding/steps/06/README.md b/packages/databinding/steps/06/README.md new file mode 100644 index 000000000..17515fa9a --- /dev/null +++ b/packages/databinding/steps/06/README.md @@ -0,0 +1,210 @@ + +# Step 6: Resource Models + +Business applications often require language-specific \(translatable\) text used as labels and descriptions on the user interface. + +The example we used at the start of this tutorial was quite simplistic as we stored language-specific text directly in a JSON model object. Generally speaking, unless language-specific text comes directly from a back-end system, it's not considered good programming practice to put translatable texts directly into a model. So, let's fix this by placing all translatable texts \(such as field labels\) into a resource bundle. + +## Preview + +### The texts are now derived from a resource model \(No visual change to last step\) + +![The texts are now derived from a resource model (No visual change to last step)](assets/Tutorial_Data_Binding_Step_4_61d68f1.png "The texts are now derived from a resource model (No visual change to last step)") + +You can view this step live: [🔗 Live Preview of Step 6](https://ui5.github.io/tutorials/databinding/build/06/index-cdn.html). + +## Coding + +You can download the solution for this step here: [📥 Download step 6](https://ui5.github.io/tutorials/databinding/databinding-step-06.zip) (TS)[📥 Download step 6](https://ui5.github.io/tutorials/databinding/databinding-step-06-js.zip) (JS). + +### Folder structure for this step + +```text +webapp/ +├── i18n/ +│ └── i18n.properties +├── model/ +│ └── data.json +├── view/ +│ └── App.view.xml +├── Component.ts/.js +├── index.html +└── manifest.json +``` + +Create a new entry in the `manifest.json` file under the `models` entry as shown in the coding below. The resource model is set to the component using the model name `i18n`. The data comes from the `i18n.properties` file as specified in the `bundleName` entry in the settings. Since we're creating a resource model, the file name is assumed to have the extension `.properties`; this does not need to be stated explicitly. + +Also add the `i18n` property to the `sap.app` section and modify the `title` and `description` property to use the corresponding texts from the `i18n.properties` as shown below. + +## webapp/manifest.json + +```json +... + "sap.app": { + ... + "title": "{{appTitle}}", + "description": "{{appDescription}}", + "i18n": { + "bundleName": "ui5.tutorial.databinding.i18n.i18n", + "supportedLocales": [ + "", + "de" + ], + "fallbackLocale": "" + }, + ... + }, + ... + "sap.ui5": { + ... + "models": { + "": { + "type": "sap.ui.model.json.JSONModel", + "uri": "./model/data.json" + }, + "i18n": { + "type": "sap.ui.model.resource.ResourceModel", + "settings": { + "bundleName": "ui5.tutorial.databinding.i18n.i18n", + "supportedLocales": [ + "", + "de" + ], + "fallbackLocale": "" + } + } + }, + ... + } +... +``` + +> :note: +> The configured `supportedLocales` represent the following i18n files present \(see Step 7\): +> +> - `""` - `i18n/i18n.properties` +> - `"de"` - `i18n/i18n_de.properties` +> +> The configured `fallbackLocale` should represent one of these files. According to the fallback chain, the root bundle \(`""`\) is the last fallback. Configuring it explicitly avoids side effects when additional resource files are added. For more information, see [Supported Locales and Fallback Chain](https://sdk.openui5.org/topic/ec753bc539d748f689e3ac814e129563). + +Update the `i18n.properties` and add the code shown below. + +## webapp/i18n/i18n.properties \(New\) + +```properties +# App Descriptor +appTitle=Data Binding Tutorial +appDescription=A simple app that explains how to use data binding features of OpenUI5 + +# Field labels +firstName=First Name +lastName=Last Name +enabled=Enabled + +# Screen titles +panelHeaderText=Data Binding Basics +``` + +The `panelHeaderText` property has been moved from the JSON model into the `i18n` resource bundle. Also, the field labels are no longer hard-coded in the XML view. This is because all of these text fields need to be translated. + +Language-specific text stored in resource models obeys the Java convention for internationalization \(i18n\). + +Modify the data binding for the panel header and the labels in `App.view.xml` to include the model name. Note that a "greater than" character separates the model name and the property name. Also, i18n property names **must not** start with a slash character. + +## webapp/view/App.view.xml + +```xml + + + + + + +``` + +Remove the line `panelHeaderText : "Data Binding Basics"` from the model data in the `data.json` file. This text has now been moved to the resource model. + +## webapp/model/data.json + +```json +{ + "firstName": "Harry", + "lastName": "Hawk", + "enabled": true +} +``` + +Remove the `init` function and the import of `sap/ui/model/BindingMode` from `Component.ts/.js` as we do not want to set the one-way binding mode anymore. + +## `webapp/Component.ts/.js` + +```ts +// webapp/Component.ts +import UIComponent from "sap/ui/core/UIComponent"; + +/** + * @namespace ui5.tutorial.databinding + */ +export default class Component extends UIComponent { + public static metadata = { + interfaces: ["sap.ui.core.IAsyncContentCreation"], + manifest: "json" + }; +} +``` + +```js +// webapp/Component.js +sap.ui.define(["sap/ui/core/UIComponent"], function (UIComponent) { + "use strict"; + + return UIComponent.extend("ui5.tutorial.databinding.Component", { + metadata: { + interfaces: ["sap.ui.core.IAsyncContentCreation"], + manifest: "json" + } + }); +}); +``` + +You can use multiple model instances by using different model names. The model name can be set as the second parameter when using the `setModel(oResourceModel,"i18n")` method. The model is then propagated under this name to all aggregated child controls \(and their children, and so on …\). All these controls have access to this model under the name `i18n` as well as to the `JSONModel` \(default model, which has no name\). + +*** + +**Next:** [Step 7: \(Optional\) Resource Bundles and Multiple Languages](../07/README.md "Resource bundles exist to enable an app to run in multiple languages without the need to change any code.") + +**Previous:** [Step 5: One-Way Data Binding](../05/README.md "Unlike the two-way binding behavior we've seen, one-way data binding is also possible.") + +*** + +**Related Information** + +[Resource Model](https://sdk.openui5.org/topic/91f122a36f4d1014b6dd926db0e91070.html "The resource model is used as a wrapper for resource bundles. In data binding you use the resource model instance, for example, to bind texts of a control to language-dependent resource bundle properties.") diff --git a/packages/databinding/steps/06/assets/Tutorial_Data_Binding_Step_4_61d68f1.png b/packages/databinding/steps/06/assets/Tutorial_Data_Binding_Step_4_61d68f1.png new file mode 100644 index 000000000..d701c3131 Binary files /dev/null and b/packages/databinding/steps/06/assets/Tutorial_Data_Binding_Step_4_61d68f1.png differ diff --git a/packages/databinding/steps/06/package.json b/packages/databinding/steps/06/package.json new file mode 100644 index 000000000..2d6f9f472 --- /dev/null +++ b/packages/databinding/steps/06/package.json @@ -0,0 +1,19 @@ +{ + "name": "ui5.tutorial.databinding.step06", + "private": true, + "version": "1.0.0", + "author": "SAP SE", + "description": "UI5 Demo App - Data Binding Tutorial", + "scripts": { + "start": "ui5 serve -o index.html", + "typecheck": "tsc --noEmit" + }, + "devDependencies": { + "@openui5/types": "^1.148.1", + "@ui5/cli": "^4.0.53", + "typescript": "^6.0.3", + "ui5-middleware-livereload": "^3.3.1", + "ui5-middleware-serveframework": "^3.8.1", + "ui5-tooling-transpile": "^3.11.0" + } +} diff --git a/packages/databinding/steps/06/tsconfig.json b/packages/databinding/steps/06/tsconfig.json new file mode 100644 index 000000000..0b7fa1ae7 --- /dev/null +++ b/packages/databinding/steps/06/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "es2023", + "types": [ + "@openui5/types" + ], + "skipLibCheck": true, + "allowJs": true, + "strictPropertyInitialization": false, + "rootDir": "./webapp", + "paths": { + "ui5/tutorial/databinding/*": [ + "./webapp/*" + ] + }, + "strict": false, + "strictNullChecks": false + }, + "exclude": [ + "./webapp/test/e2e/**/*" + ], + "include": [ + "./webapp/**/*" + ] +} diff --git a/packages/databinding/steps/06/ui5.yaml b/packages/databinding/steps/06/ui5.yaml new file mode 100644 index 000000000..af70be177 --- /dev/null +++ b/packages/databinding/steps/06/ui5.yaml @@ -0,0 +1,24 @@ +specVersion: "4.0" +metadata: + name: "ui5.tutorial.databinding" +type: application +framework: + name: OpenUI5 + version: "1.148.1" + libraries: + - name: sap.m + - name: sap.ui.core + - name: sap.ui.layout + - name: themelib_sap_horizon +builder: + customTasks: + - name: ui5-tooling-transpile-task + afterTask: replaceVersion +server: + customMiddleware: + - name: ui5-tooling-transpile-middleware + afterMiddleware: compression + - name: ui5-middleware-serveframework + afterMiddleware: compression + - name: ui5-middleware-livereload + afterMiddleware: compression diff --git a/packages/databinding/steps/06/webapp/Component.ts b/packages/databinding/steps/06/webapp/Component.ts new file mode 100644 index 000000000..3fc494bca --- /dev/null +++ b/packages/databinding/steps/06/webapp/Component.ts @@ -0,0 +1,11 @@ +import UIComponent from "sap/ui/core/UIComponent"; + +/** + * @namespace ui5.tutorial.databinding + */ +export default class Component extends UIComponent { + public static metadata = { + interfaces: ["sap.ui.core.IAsyncContentCreation"], + manifest: "json" + }; +} diff --git a/packages/databinding/steps/06/webapp/i18n/i18n.properties b/packages/databinding/steps/06/webapp/i18n/i18n.properties new file mode 100644 index 000000000..761435352 --- /dev/null +++ b/packages/databinding/steps/06/webapp/i18n/i18n.properties @@ -0,0 +1,11 @@ +# App Descriptor +appTitle=Data Binding Tutorial +appDescription=A simple app that explains how to use data binding features of OpenUI5 + +# Field labels +firstName=First Name +lastName=Last Name +enabled=Enabled + +# Screen titles +panelHeaderText=Data Binding Basics diff --git a/packages/databinding/steps/06/webapp/index-cdn.html b/packages/databinding/steps/06/webapp/index-cdn.html new file mode 100644 index 000000000..e2b2e4833 --- /dev/null +++ b/packages/databinding/steps/06/webapp/index-cdn.html @@ -0,0 +1,22 @@ + + + + + + Data Binding Tutorial + + + +
+ + diff --git a/packages/databinding/steps/06/webapp/index.html b/packages/databinding/steps/06/webapp/index.html new file mode 100644 index 000000000..42edae4b3 --- /dev/null +++ b/packages/databinding/steps/06/webapp/index.html @@ -0,0 +1,22 @@ + + + + + + Data Binding Tutorial + + + +
+ + diff --git a/packages/databinding/steps/06/webapp/manifest.json b/packages/databinding/steps/06/webapp/manifest.json new file mode 100644 index 000000000..2738faec9 --- /dev/null +++ b/packages/databinding/steps/06/webapp/manifest.json @@ -0,0 +1,65 @@ +{ + "_version": "2.8.0", + "sap.app": { + "id": "ui5.tutorial.databinding", + "type": "application", + "applicationVersion": { + "version": "1.0.0" + }, + "title": "{{appTitle}}", + "description": "{{appDescription}}", + "i18n": { + "bundleName": "ui5.tutorial.databinding.i18n.i18n", + "supportedLocales": [ + "", + "de" + ], + "fallbackLocale": "" + } + }, + "sap.ui": { + "technology": "UI5", + "deviceTypes": { + "desktop": true, + "tablet": true, + "phone": true + } + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.148", + "libs": { + "sap.m": {}, + "sap.ui.core": {}, + "sap.ui.layout": {} + } + }, + "contentDensities": { + "compact": true, + "cozy": true + }, + "models": { + "": { + "type": "sap.ui.model.json.JSONModel", + "uri": "./model/data.json" + }, + "i18n": { + "type": "sap.ui.model.resource.ResourceModel", + "settings": { + "bundleName": "ui5.tutorial.databinding.i18n.i18n", + "supportedLocales": [ + "", + "de" + ], + "fallbackLocale": "", + "async": true + } + } + }, + "rootView": { + "viewName": "ui5.tutorial.databinding.view.App", + "type": "XML", + "id": "app" + } + } +} diff --git a/packages/databinding/steps/06/webapp/model/data.json b/packages/databinding/steps/06/webapp/model/data.json new file mode 100644 index 000000000..381adf692 --- /dev/null +++ b/packages/databinding/steps/06/webapp/model/data.json @@ -0,0 +1,5 @@ +{ + "firstName": "Harry", + "lastName": "Hawk", + "enabled": true +} \ No newline at end of file diff --git a/packages/databinding/steps/06/webapp/view/App.view.xml b/packages/databinding/steps/06/webapp/view/App.view.xml new file mode 100644 index 000000000..8a003efa7 --- /dev/null +++ b/packages/databinding/steps/06/webapp/view/App.view.xml @@ -0,0 +1,34 @@ + + + + + + diff --git a/packages/databinding/steps/07/README.md b/packages/databinding/steps/07/README.md new file mode 100644 index 000000000..2ccaf0b56 --- /dev/null +++ b/packages/databinding/steps/07/README.md @@ -0,0 +1,65 @@ + +# Step 7: \(Optional\) Resource Bundles and Multiple Languages + +Resource bundles exist to enable an app to run in multiple languages without the need to change any code. To demonstrate this feature, let's create a German version of the app – in fact, all we need to do is create a German version of the resource bundle file. In our code, we activate the German locale for the ResourceModel. + +## Preview + +### The texts are now adapted for the German locale + +![The texts are now adapted for the German locale](assets/Tutorial_Data_Binding_Step_7_d96cdf9.png "The texts are now adapted for the German locale") + +You can view this step live: [🔗 Live Preview of Step 7](https://ui5.github.io/tutorials/databinding/build/07/index-cdn.html). + +## Coding + +You can download the solution for this step here: [📥 Download step 7](https://ui5.github.io/tutorials/databinding/databinding-step-07.zip) (TS)[📥 Download step 7](https://ui5.github.io/tutorials/databinding/databinding-step-07-js.zip) (JS). + +### Folder structure for this step + +```text +webapp/ +├── i18n/ +│ ├── i18n.properties +│ └── i18n_de.properties +├── model/ +│ └── data.json +├── view/ +│ └── App.view.xml +├── Component.ts/.js +├── index.html +└── manifest.json +``` + +## webapp/i18n/i18n\_de.properties \(New\) + +In the `i18n` folder, duplicate the `i18n.properties` file and rename its copy to `i18n`**`_de`**`.properties`. Replace the English text with the German text provided below. The suffix `de` represents the locale for the German language. Since the `de` locale is already set in the `supportedLocales` configuration of the `manifest.json`, it will be taken into account. + +```properties +# App Descriptor +appTitle=Data Binding Tutorial +appDescription=Eine einfache Anwendung zur Erkl\u00e4rung der UI5 Data Binding Funktionen + +# Field labels +firstName=Vorname +lastName=Nachname +enabled=Aktiviert + +# Screen titles +panelHeaderText=Data Binding Grundlagen +``` + +To check the result, append the `sap-language=DE` URL parameter to the URL in your browser, for example `http://localhost:port/index.html?sap-language=DE`. Once you remove this parameter, your app reverts to your browser's default language. + +*** + +**Next:** [Step 8: Binding Paths: Accessing Properties in Hierarchically Structured Models](../08/README.md "Let's explore how to reference fields in a hierarchically structured model object.") + +**Previous:** [Step 6: Resource Models](../06/README.md "Business applications often require language-specific (translatable) text used as labels and descriptions on the user interface.") + +*** + +**Related Information** + +[Localization](https://sdk.openui5.org/topic/91f217c46f4d1014b6dd926db0e91070.html "The framework concepts for text localization in OpenUI5 are aligned with the general concepts of the Java platform.") + diff --git a/packages/databinding/steps/07/assets/Tutorial_Data_Binding_Step_7_d96cdf9.png b/packages/databinding/steps/07/assets/Tutorial_Data_Binding_Step_7_d96cdf9.png new file mode 100644 index 000000000..f46f49b16 Binary files /dev/null and b/packages/databinding/steps/07/assets/Tutorial_Data_Binding_Step_7_d96cdf9.png differ diff --git a/packages/databinding/steps/07/package.json b/packages/databinding/steps/07/package.json new file mode 100644 index 000000000..3a0810d04 --- /dev/null +++ b/packages/databinding/steps/07/package.json @@ -0,0 +1,19 @@ +{ + "name": "ui5.tutorial.databinding.step07", + "private": true, + "version": "1.0.0", + "author": "SAP SE", + "description": "UI5 Demo App - Data Binding Tutorial", + "scripts": { + "start": "ui5 serve -o index.html", + "typecheck": "tsc --noEmit" + }, + "devDependencies": { + "@openui5/types": "^1.148.1", + "@ui5/cli": "^4.0.53", + "typescript": "^6.0.3", + "ui5-middleware-livereload": "^3.3.1", + "ui5-middleware-serveframework": "^3.8.1", + "ui5-tooling-transpile": "^3.11.0" + } +} diff --git a/packages/databinding/steps/07/tsconfig.json b/packages/databinding/steps/07/tsconfig.json new file mode 100644 index 000000000..0b7fa1ae7 --- /dev/null +++ b/packages/databinding/steps/07/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "es2023", + "types": [ + "@openui5/types" + ], + "skipLibCheck": true, + "allowJs": true, + "strictPropertyInitialization": false, + "rootDir": "./webapp", + "paths": { + "ui5/tutorial/databinding/*": [ + "./webapp/*" + ] + }, + "strict": false, + "strictNullChecks": false + }, + "exclude": [ + "./webapp/test/e2e/**/*" + ], + "include": [ + "./webapp/**/*" + ] +} diff --git a/packages/databinding/steps/07/ui5.yaml b/packages/databinding/steps/07/ui5.yaml new file mode 100644 index 000000000..af70be177 --- /dev/null +++ b/packages/databinding/steps/07/ui5.yaml @@ -0,0 +1,24 @@ +specVersion: "4.0" +metadata: + name: "ui5.tutorial.databinding" +type: application +framework: + name: OpenUI5 + version: "1.148.1" + libraries: + - name: sap.m + - name: sap.ui.core + - name: sap.ui.layout + - name: themelib_sap_horizon +builder: + customTasks: + - name: ui5-tooling-transpile-task + afterTask: replaceVersion +server: + customMiddleware: + - name: ui5-tooling-transpile-middleware + afterMiddleware: compression + - name: ui5-middleware-serveframework + afterMiddleware: compression + - name: ui5-middleware-livereload + afterMiddleware: compression diff --git a/packages/databinding/steps/07/webapp/Component.ts b/packages/databinding/steps/07/webapp/Component.ts new file mode 100644 index 000000000..3fc494bca --- /dev/null +++ b/packages/databinding/steps/07/webapp/Component.ts @@ -0,0 +1,11 @@ +import UIComponent from "sap/ui/core/UIComponent"; + +/** + * @namespace ui5.tutorial.databinding + */ +export default class Component extends UIComponent { + public static metadata = { + interfaces: ["sap.ui.core.IAsyncContentCreation"], + manifest: "json" + }; +} diff --git a/packages/databinding/steps/07/webapp/i18n/i18n.properties b/packages/databinding/steps/07/webapp/i18n/i18n.properties new file mode 100644 index 000000000..761435352 --- /dev/null +++ b/packages/databinding/steps/07/webapp/i18n/i18n.properties @@ -0,0 +1,11 @@ +# App Descriptor +appTitle=Data Binding Tutorial +appDescription=A simple app that explains how to use data binding features of OpenUI5 + +# Field labels +firstName=First Name +lastName=Last Name +enabled=Enabled + +# Screen titles +panelHeaderText=Data Binding Basics diff --git a/packages/databinding/steps/07/webapp/i18n/i18n_de.properties b/packages/databinding/steps/07/webapp/i18n/i18n_de.properties new file mode 100644 index 000000000..b120815f9 --- /dev/null +++ b/packages/databinding/steps/07/webapp/i18n/i18n_de.properties @@ -0,0 +1,11 @@ +# App Descriptor +appTitle=Data Binding Tutorial +appDescription=Eine einfache Anwendung zur Erkl\u00e4rung der UI5 Data Binding Funktionen + +# Field labels +firstName=Vorname +lastName=Nachname +enabled=Aktiviert + +# Screen titles +panelHeaderText=Data Binding Grundlagen diff --git a/packages/databinding/steps/07/webapp/index-cdn.html b/packages/databinding/steps/07/webapp/index-cdn.html new file mode 100644 index 000000000..e2b2e4833 --- /dev/null +++ b/packages/databinding/steps/07/webapp/index-cdn.html @@ -0,0 +1,22 @@ + + + + + + Data Binding Tutorial + + + +
+ + diff --git a/packages/databinding/steps/07/webapp/index.html b/packages/databinding/steps/07/webapp/index.html new file mode 100644 index 000000000..42edae4b3 --- /dev/null +++ b/packages/databinding/steps/07/webapp/index.html @@ -0,0 +1,22 @@ + + + + + + Data Binding Tutorial + + + +
+ + diff --git a/packages/databinding/steps/07/webapp/manifest.json b/packages/databinding/steps/07/webapp/manifest.json new file mode 100644 index 000000000..2738faec9 --- /dev/null +++ b/packages/databinding/steps/07/webapp/manifest.json @@ -0,0 +1,65 @@ +{ + "_version": "2.8.0", + "sap.app": { + "id": "ui5.tutorial.databinding", + "type": "application", + "applicationVersion": { + "version": "1.0.0" + }, + "title": "{{appTitle}}", + "description": "{{appDescription}}", + "i18n": { + "bundleName": "ui5.tutorial.databinding.i18n.i18n", + "supportedLocales": [ + "", + "de" + ], + "fallbackLocale": "" + } + }, + "sap.ui": { + "technology": "UI5", + "deviceTypes": { + "desktop": true, + "tablet": true, + "phone": true + } + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.148", + "libs": { + "sap.m": {}, + "sap.ui.core": {}, + "sap.ui.layout": {} + } + }, + "contentDensities": { + "compact": true, + "cozy": true + }, + "models": { + "": { + "type": "sap.ui.model.json.JSONModel", + "uri": "./model/data.json" + }, + "i18n": { + "type": "sap.ui.model.resource.ResourceModel", + "settings": { + "bundleName": "ui5.tutorial.databinding.i18n.i18n", + "supportedLocales": [ + "", + "de" + ], + "fallbackLocale": "", + "async": true + } + } + }, + "rootView": { + "viewName": "ui5.tutorial.databinding.view.App", + "type": "XML", + "id": "app" + } + } +} diff --git a/packages/databinding/steps/07/webapp/model/data.json b/packages/databinding/steps/07/webapp/model/data.json new file mode 100644 index 000000000..381adf692 --- /dev/null +++ b/packages/databinding/steps/07/webapp/model/data.json @@ -0,0 +1,5 @@ +{ + "firstName": "Harry", + "lastName": "Hawk", + "enabled": true +} \ No newline at end of file diff --git a/packages/databinding/steps/07/webapp/view/App.view.xml b/packages/databinding/steps/07/webapp/view/App.view.xml new file mode 100644 index 000000000..8a003efa7 --- /dev/null +++ b/packages/databinding/steps/07/webapp/view/App.view.xml @@ -0,0 +1,34 @@ + + + + + + diff --git a/packages/databinding/steps/08/README.md b/packages/databinding/steps/08/README.md new file mode 100644 index 000000000..ed5baf970 --- /dev/null +++ b/packages/databinding/steps/08/README.md @@ -0,0 +1,135 @@ + +# Step 8: Binding Paths: Accessing Properties in Hierarchically Structured Models + +In Step 6 , we stated that the fields in a resource model are arranged in a flat structure; in other words, there is no hierarchy of properties. However, this is only true for resource models. The properties within JSON and OData models are usually arranged in a hierarchical structure. So, let's explore how to reference fields in a hierarchically structured model object. + +## Preview + +### A second panel with address data is added + +![A second panel with address data is added](assets/Tutorial_Data_Binding_Step_8_12705f5.png "A second panel with address data is added") + +You can view this step live: [🔗 Live Preview of Step 8](https://ui5.github.io/tutorials/databinding/build/08/index-cdn.html). + +## Coding + +You can download the solution for this step here: [📥 Download step 8](https://ui5.github.io/tutorials/databinding/databinding-step-08.zip) (TS)[📥 Download step 8](https://ui5.github.io/tutorials/databinding/databinding-step-08-js.zip) (JS). + +### Folder structure for this step + +```text +webapp/ +├── i18n/ +│ ├── i18n.properties +│ └── i18n_de.properties +├── model/ +│ └── data.json +├── view/ +│ └── App.view.xml +├── Component.ts/.js +├── index.html +└── manifest.json +``` + +In the `data.json` file, add an additional sub-object named `address`. This object has four properties: `street`, `city`, `zip`, and `country`. + +## webapp/model/data.json + +```json +{ + "firstName": "Harry", + "lastName": "Hawk", + "enabled": true, + "address": { + "street": "Dietmar-Hopp-Allee 16", + "city": "Walldorf", + "zip": "69190", + "country": "Germany" + } +} +``` + +Add a new panel to the `App.view.xml` with a new `Label` and `FormattedText` pair of elements. + +The `text` property of the `Label` element is bound to the i18n resource bundle field `address`. + +The `htmlText` property of the `FormattedText` element is bound to four JSON model properties: `/address/street`, `/address/zip`, `/address/city`, and `/address/country`. You can achieve the resulting address format by separating each one of these JSON model property references with a hard-coded newline character. Note that `zip` and `city` are separated by a space. + +## webapp/view/App.view.xml + +```xml + + + ... + + + + + + + + +``` + +Update the `i18n.properties` and `i18n_de.properties` files as shown below. + +## webapp/i18n/i18n.properties + +```properties +... + +# Screen titles +panel1HeaderText=Data Binding Basics +panel2HeaderText=Address Details +``` + +## webapp/i18n/i18n\_de.properties + +```properties +... + +# Screen titles +panel1HeaderText=Data Binding Grundlagen +panel2HeaderText=Adressdetails +``` + +> :note: +> The resource bundle files now contain new properties for the address and a new panel header text. Both panel properties are numbered. +> +> In the XML view, inside the curly brackets for the binding path of the `htmlText` element, you'll notice that the first character is a forward slash. This is necessary for binding paths that make absolute references to properties in JSON and OData models, but you must not use it for resource models. After the first forward slash character, the binding path syntax uses the object name and the property names separated by forward slash characters \(`{/address/street}`\). +> +> Remember, all binding path names are case-sensitive. + +*** + +**Next:** [Step 9: Formatting Values](../09/README.md "We'd also like to provide our users with a way of contacting Harry Hawk, so we're adding a link that sends an e-mail to Harry.") + +**Previous:** [Step 7: \(Optional\) Resource Bundles and Multiple Languages](../07/README.md "Resource bundles exist to enable an app to run in multiple languages without the need to change any code.") + +*** + +**Related Information** + +[JSON Model](https://sdk.openui5.org/topic/96804e3315ff440aa0a50fd290805116.html#loio96804e3315ff440aa0a50fd290805116 "The JSON model can be used to bind controls to JavaScript object data, which is usually serialized in the JSON format.") + diff --git a/packages/databinding/steps/08/assets/Tutorial_Data_Binding_Step_8_12705f5.png b/packages/databinding/steps/08/assets/Tutorial_Data_Binding_Step_8_12705f5.png new file mode 100644 index 000000000..e5c058bf4 Binary files /dev/null and b/packages/databinding/steps/08/assets/Tutorial_Data_Binding_Step_8_12705f5.png differ diff --git a/packages/databinding/steps/08/package.json b/packages/databinding/steps/08/package.json new file mode 100644 index 000000000..af466b0e1 --- /dev/null +++ b/packages/databinding/steps/08/package.json @@ -0,0 +1,19 @@ +{ + "name": "ui5.tutorial.databinding.step08", + "private": true, + "version": "1.0.0", + "author": "SAP SE", + "description": "UI5 Demo App - Data Binding Tutorial", + "scripts": { + "start": "ui5 serve -o index.html", + "typecheck": "tsc --noEmit" + }, + "devDependencies": { + "@openui5/types": "^1.148.1", + "@ui5/cli": "^4.0.53", + "typescript": "^6.0.3", + "ui5-middleware-livereload": "^3.3.1", + "ui5-middleware-serveframework": "^3.8.1", + "ui5-tooling-transpile": "^3.11.0" + } +} diff --git a/packages/databinding/steps/08/tsconfig.json b/packages/databinding/steps/08/tsconfig.json new file mode 100644 index 000000000..0b7fa1ae7 --- /dev/null +++ b/packages/databinding/steps/08/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "es2023", + "types": [ + "@openui5/types" + ], + "skipLibCheck": true, + "allowJs": true, + "strictPropertyInitialization": false, + "rootDir": "./webapp", + "paths": { + "ui5/tutorial/databinding/*": [ + "./webapp/*" + ] + }, + "strict": false, + "strictNullChecks": false + }, + "exclude": [ + "./webapp/test/e2e/**/*" + ], + "include": [ + "./webapp/**/*" + ] +} diff --git a/packages/databinding/steps/08/ui5.yaml b/packages/databinding/steps/08/ui5.yaml new file mode 100644 index 000000000..af70be177 --- /dev/null +++ b/packages/databinding/steps/08/ui5.yaml @@ -0,0 +1,24 @@ +specVersion: "4.0" +metadata: + name: "ui5.tutorial.databinding" +type: application +framework: + name: OpenUI5 + version: "1.148.1" + libraries: + - name: sap.m + - name: sap.ui.core + - name: sap.ui.layout + - name: themelib_sap_horizon +builder: + customTasks: + - name: ui5-tooling-transpile-task + afterTask: replaceVersion +server: + customMiddleware: + - name: ui5-tooling-transpile-middleware + afterMiddleware: compression + - name: ui5-middleware-serveframework + afterMiddleware: compression + - name: ui5-middleware-livereload + afterMiddleware: compression diff --git a/packages/databinding/steps/08/webapp/Component.ts b/packages/databinding/steps/08/webapp/Component.ts new file mode 100644 index 000000000..3fc494bca --- /dev/null +++ b/packages/databinding/steps/08/webapp/Component.ts @@ -0,0 +1,11 @@ +import UIComponent from "sap/ui/core/UIComponent"; + +/** + * @namespace ui5.tutorial.databinding + */ +export default class Component extends UIComponent { + public static metadata = { + interfaces: ["sap.ui.core.IAsyncContentCreation"], + manifest: "json" + }; +} diff --git a/packages/databinding/steps/08/webapp/i18n/i18n.properties b/packages/databinding/steps/08/webapp/i18n/i18n.properties new file mode 100644 index 000000000..23af9445c --- /dev/null +++ b/packages/databinding/steps/08/webapp/i18n/i18n.properties @@ -0,0 +1,13 @@ +# App Descriptor +appTitle=Data Binding Tutorial +appDescription=A simple app that explains how to use data binding features of OpenUI5 + +# Field labels +firstName=First Name +lastName=Last Name +enabled=Enabled +address=Address + +# Screen titles +panel1HeaderText=Data Binding Basics +panel2HeaderText=Address Details diff --git a/packages/databinding/steps/08/webapp/i18n/i18n_de.properties b/packages/databinding/steps/08/webapp/i18n/i18n_de.properties new file mode 100644 index 000000000..d087681a2 --- /dev/null +++ b/packages/databinding/steps/08/webapp/i18n/i18n_de.properties @@ -0,0 +1,13 @@ +# App Descriptor +appTitle=Data Binding Tutorial +appDescription=Eine einfache Anwendung zur Erkl\u00e4rung der UI5 Data Binding Funktionen + +# Field labels +firstName=Vorname +lastName=Nachname +enabled=Aktiviert +address=Adresse + +# Screen titles +panel1HeaderText=Data Binding Grundlagen +panel2HeaderText=Adressdetails diff --git a/packages/databinding/steps/08/webapp/index-cdn.html b/packages/databinding/steps/08/webapp/index-cdn.html new file mode 100644 index 000000000..e2b2e4833 --- /dev/null +++ b/packages/databinding/steps/08/webapp/index-cdn.html @@ -0,0 +1,22 @@ + + + + + + Data Binding Tutorial + + + +
+ + diff --git a/packages/databinding/steps/08/webapp/index.html b/packages/databinding/steps/08/webapp/index.html new file mode 100644 index 000000000..42edae4b3 --- /dev/null +++ b/packages/databinding/steps/08/webapp/index.html @@ -0,0 +1,22 @@ + + + + + + Data Binding Tutorial + + + +
+ + diff --git a/packages/databinding/steps/08/webapp/manifest.json b/packages/databinding/steps/08/webapp/manifest.json new file mode 100644 index 000000000..2738faec9 --- /dev/null +++ b/packages/databinding/steps/08/webapp/manifest.json @@ -0,0 +1,65 @@ +{ + "_version": "2.8.0", + "sap.app": { + "id": "ui5.tutorial.databinding", + "type": "application", + "applicationVersion": { + "version": "1.0.0" + }, + "title": "{{appTitle}}", + "description": "{{appDescription}}", + "i18n": { + "bundleName": "ui5.tutorial.databinding.i18n.i18n", + "supportedLocales": [ + "", + "de" + ], + "fallbackLocale": "" + } + }, + "sap.ui": { + "technology": "UI5", + "deviceTypes": { + "desktop": true, + "tablet": true, + "phone": true + } + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.148", + "libs": { + "sap.m": {}, + "sap.ui.core": {}, + "sap.ui.layout": {} + } + }, + "contentDensities": { + "compact": true, + "cozy": true + }, + "models": { + "": { + "type": "sap.ui.model.json.JSONModel", + "uri": "./model/data.json" + }, + "i18n": { + "type": "sap.ui.model.resource.ResourceModel", + "settings": { + "bundleName": "ui5.tutorial.databinding.i18n.i18n", + "supportedLocales": [ + "", + "de" + ], + "fallbackLocale": "", + "async": true + } + } + }, + "rootView": { + "viewName": "ui5.tutorial.databinding.view.App", + "type": "XML", + "id": "app" + } + } +} diff --git a/packages/databinding/steps/08/webapp/model/data.json b/packages/databinding/steps/08/webapp/model/data.json new file mode 100644 index 000000000..138a22fd4 --- /dev/null +++ b/packages/databinding/steps/08/webapp/model/data.json @@ -0,0 +1,11 @@ +{ + "firstName": "Harry", + "lastName": "Hawk", + "enabled": true, + "address": { + "street": "Dietmar-Hopp-Allee 16", + "city": "Walldorf", + "zip": "69190", + "country": "Germany" + } +} \ No newline at end of file diff --git a/packages/databinding/steps/08/webapp/view/App.view.xml b/packages/databinding/steps/08/webapp/view/App.view.xml new file mode 100644 index 000000000..e003ff4da --- /dev/null +++ b/packages/databinding/steps/08/webapp/view/App.view.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + diff --git a/packages/databinding/steps/09/README.md b/packages/databinding/steps/09/README.md new file mode 100644 index 000000000..f358eb7a8 --- /dev/null +++ b/packages/databinding/steps/09/README.md @@ -0,0 +1,172 @@ + +# Step 9: Formatting Values + +We'd also like to provide our users with a way of contacting Harry Hawk, so we're adding a link that sends an e-mail to Harry. To do this, we convert our data in the model to match the `sap.m.URLHelper.normalizeEmail` API. As soon as the user changes the name, the e-mail also changes. We need a custom formatter function for this. + +## Preview + +### An e-mail link is added to the address panel + +![An e-mail link is added to the address panel](assets/Tutorial_Data_Binding_Step_9_1161575.png "An e-mail link is added to the address panel") + +You can view this step live: [🔗 Live Preview of Step 9](https://ui5.github.io/tutorials/databinding/build/09/index-cdn.html). + +## Coding + +You can download the solution for this step here: [📥 Download step 9](https://ui5.github.io/tutorials/databinding/databinding-step-09.zip) (TS)[📥 Download step 9](https://ui5.github.io/tutorials/databinding/databinding-step-09-js.zip) (JS). + +### Folder structure for this step + +```text +webapp/ +├── controller/ +│ └── App.controller.ts/.js +├── i18n/ +│ ├── i18n.properties +│ └── i18n_de.properties +├── model/ +│ └── data.json +├── view/ +│ └── App.view.xml +├── Component.ts/.js +├── index.html +└── manifest.json +``` + +Create a new folder named `controller` within your `webapp` folder as a general location for all controller files for this app. Next, create a new file named `App.controller.ts/.js` with the following content: + +## `webapp/controller/App.controller.ts/.js` \(New\) + +```ts +// webapp/controller/App.controller.ts +import ResourceBundle from "sap/base/i18n/ResourceBundle"; +import { URLHelper } from "sap/m/library"; +import Controller from "sap/ui/core/mvc/Controller"; +import ResourceModel from "sap/ui/model/resource/ResourceModel"; + +/** + * @namespace ui5.tutorial.databinding.controller + */ +export default class App extends Controller { + public async formatMail(firstName: string, lastName: string): Promise { + const bundle: ResourceBundle = await (this.getView().getModel("i18n") as ResourceModel).getResourceBundle() as ResourceBundle; + + return URLHelper.normalizeEmail( + `${firstName}.${lastName}@example.com`, + bundle.getText("mailSubject", [firstName]), + bundle.getText("mailBody") + ); + } +} +``` + +```js +// webapp/controller/App.controller.js +sap.ui.define(["sap/ui/core/mvc/Controller", "sap/m/library"], function (Controller, mobileLibrary) { + "use strict"; + + return Controller.extend("ui5.tutorial.databinding.controller.App", { + async formatMail(firstName, lastName) { + const bundle = await this.getView().getModel("i18n").getResourceBundle(); + + return mobileLibrary.URLHelper.normalizeEmail( + `${firstName}.${lastName}@example.com`, + bundle.getText("mailSubject", [firstName]), + bundle.getText("mailBody") + ); + } + }); +}); +``` + +In our custom formatter, we set the first and last name currently in the model as function parameters. When a user changes the data in the model by entering a different name in the input fields, our formatter will be invoked automatically by the framework. This ensures that the UI stays in sync with the data model. + +In the `formatMail` function, we use the `sap.m.URLHelper.normalizeEmail` function that expects an e-mail address, a mail subject, and a text body. When a user follows the link, their default email client will open with these parameters. For more information, see [API Reference: `sap.m.URLHelper.normalizeEmail`](https://sdk.openui5.org/#/api/sap.m.URLHelper/methods/normalizeEmail). The `mailSubject` resource bundle text contains a placeholder for the recipient's first name \(see below\). Therefore, we provide the name with `[firstName]`. + +> :note: +> For a detailed description of the e-mail link format, see [MDN - Creating hyperlinks: Email links](https://developer.mozilla.org/de/docs/Web/Guide/HTML/Email_links). + +Enhance the `App.view.xml` file as shown below: + +## webapp/view/App.view.xml + +```xml + + ... + + + + ... + + + + + +``` + +For more complex bindings, we can't use the simple binding syntax with the curly braces anymore. The `href` property of the `Link` element now contains an entire object inside the string value. In this case, the object has two properties: + +- `parts` + + This is a JavaScript array in which each element is a string representing a `path` property. The number and order of the elements in this array correspond directly to the number and order of parameters expected by the `formatMail` function. + +- `formatter` + + This is a reference to the function that receives the parameters listed in the `parts` array. Whatever value the formatter function returns becomes the value set for the `href` property. The dot `formatMail`\) at the beginning of the formatter tells OpenUI5 to look for a `formatMail` function on the controller instance of the view. If you don't use the dot, the function will be resolved by looking into the global namespace. + +> :note: +> When using formatter functions, the binding automatically switches to "one-way". Therefore, you can’t use a formatter function for "two-way" scenarios. However, you can use data types \(which we explain in the following steps\). + +Add the `# E-mail` section to the `i18n.properties` and `i18n_de.properties` files as shown below. + +## webapp/i18n/i18n.properties + +```properties +... + +# E-mail +sendEmail=Send Mail +mailSubject=Hi {0}! +mailBody=How are you? +``` + +## webapp/i18n/i18n\_de.properties + +```properties +... + +# E-mail +sendEmail=E-mail versenden +mailSubject=Hallo {0}! +mailBody=Wie geht es dir? +``` + +*** + +**Next:** [Step 10: Property Formatting Using Data Types](../10/README.md "OpenUI5 offers a set of simple data types, including Boolean, Currency, Date and Float.") + +**Previous:** [Step 8: Binding Paths: Accessing Properties in Hierarchically Structured Models](../08/README.md "In Step 6, we stated that the fields in a resource model are arranged in a flat structure; in other words, there is no hierarchy of properties.") + +*** + +**Related Information** + +[Formatting, Parsing, and Validating Data](https://sdk.openui5.org/topic/07e4b920f5734fd78fdaa236f26236d8.html "Data that is presented on the UI often has to be converted so that is human readable and fits to the locale of the user. On the other hand, data entered by the user has to be parsed and validated to be understood by the data source. For this purpose, you use formatters and data types.") diff --git a/packages/databinding/steps/09/assets/Tutorial_Data_Binding_Step_9_1161575.png b/packages/databinding/steps/09/assets/Tutorial_Data_Binding_Step_9_1161575.png new file mode 100644 index 000000000..e184af3b6 Binary files /dev/null and b/packages/databinding/steps/09/assets/Tutorial_Data_Binding_Step_9_1161575.png differ diff --git a/packages/databinding/steps/09/package.json b/packages/databinding/steps/09/package.json new file mode 100644 index 000000000..e468ebe81 --- /dev/null +++ b/packages/databinding/steps/09/package.json @@ -0,0 +1,19 @@ +{ + "name": "ui5.tutorial.databinding.step09", + "private": true, + "version": "1.0.0", + "author": "SAP SE", + "description": "UI5 Demo App - Data Binding Tutorial", + "scripts": { + "start": "ui5 serve -o index.html", + "typecheck": "tsc --noEmit" + }, + "devDependencies": { + "@openui5/types": "^1.148.1", + "@ui5/cli": "^4.0.53", + "typescript": "^6.0.3", + "ui5-middleware-livereload": "^3.3.1", + "ui5-middleware-serveframework": "^3.8.1", + "ui5-tooling-transpile": "^3.11.0" + } +} diff --git a/packages/databinding/steps/09/tsconfig.json b/packages/databinding/steps/09/tsconfig.json new file mode 100644 index 000000000..0b7fa1ae7 --- /dev/null +++ b/packages/databinding/steps/09/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "es2023", + "types": [ + "@openui5/types" + ], + "skipLibCheck": true, + "allowJs": true, + "strictPropertyInitialization": false, + "rootDir": "./webapp", + "paths": { + "ui5/tutorial/databinding/*": [ + "./webapp/*" + ] + }, + "strict": false, + "strictNullChecks": false + }, + "exclude": [ + "./webapp/test/e2e/**/*" + ], + "include": [ + "./webapp/**/*" + ] +} diff --git a/packages/databinding/steps/09/ui5.yaml b/packages/databinding/steps/09/ui5.yaml new file mode 100644 index 000000000..af70be177 --- /dev/null +++ b/packages/databinding/steps/09/ui5.yaml @@ -0,0 +1,24 @@ +specVersion: "4.0" +metadata: + name: "ui5.tutorial.databinding" +type: application +framework: + name: OpenUI5 + version: "1.148.1" + libraries: + - name: sap.m + - name: sap.ui.core + - name: sap.ui.layout + - name: themelib_sap_horizon +builder: + customTasks: + - name: ui5-tooling-transpile-task + afterTask: replaceVersion +server: + customMiddleware: + - name: ui5-tooling-transpile-middleware + afterMiddleware: compression + - name: ui5-middleware-serveframework + afterMiddleware: compression + - name: ui5-middleware-livereload + afterMiddleware: compression diff --git a/packages/databinding/steps/09/webapp/Component.ts b/packages/databinding/steps/09/webapp/Component.ts new file mode 100644 index 000000000..3fc494bca --- /dev/null +++ b/packages/databinding/steps/09/webapp/Component.ts @@ -0,0 +1,11 @@ +import UIComponent from "sap/ui/core/UIComponent"; + +/** + * @namespace ui5.tutorial.databinding + */ +export default class Component extends UIComponent { + public static metadata = { + interfaces: ["sap.ui.core.IAsyncContentCreation"], + manifest: "json" + }; +} diff --git a/packages/databinding/steps/09/webapp/controller/App.controller.ts b/packages/databinding/steps/09/webapp/controller/App.controller.ts new file mode 100644 index 000000000..36a95910b --- /dev/null +++ b/packages/databinding/steps/09/webapp/controller/App.controller.ts @@ -0,0 +1,19 @@ +import ResourceBundle from "sap/base/i18n/ResourceBundle"; +import { URLHelper } from "sap/m/library"; +import Controller from "sap/ui/core/mvc/Controller"; +import ResourceModel from "sap/ui/model/resource/ResourceModel"; + +/** + * @namespace ui5.tutorial.databinding.controller + */ +export default class App extends Controller { + public async formatMail(firstName: string, lastName: string): Promise { + const bundle: ResourceBundle = await (this.getView().getModel("i18n") as ResourceModel).getResourceBundle() as ResourceBundle; + + return URLHelper.normalizeEmail( + `${firstName}.${lastName}@example.com`, + bundle.getText("mailSubject", [firstName]), + bundle.getText("mailBody") + ); + } +} diff --git a/packages/databinding/steps/09/webapp/i18n/i18n.properties b/packages/databinding/steps/09/webapp/i18n/i18n.properties new file mode 100644 index 000000000..18975b0b1 --- /dev/null +++ b/packages/databinding/steps/09/webapp/i18n/i18n.properties @@ -0,0 +1,18 @@ +# App Descriptor +appTitle=Data Binding Tutorial +appDescription=A simple app that explains how to use data binding features of OpenUI5 + +# Field labels +firstName=First Name +lastName=Last Name +enabled=Enabled +address=Address + +# Screen titles +panel1HeaderText=Data Binding Basics +panel2HeaderText=Address Details + +# Email +sendEmail=Send EMail +mailSubject=Hi {0}! +mailBody=How are you? diff --git a/packages/databinding/steps/09/webapp/i18n/i18n_de.properties b/packages/databinding/steps/09/webapp/i18n/i18n_de.properties new file mode 100644 index 000000000..df07d86e8 --- /dev/null +++ b/packages/databinding/steps/09/webapp/i18n/i18n_de.properties @@ -0,0 +1,18 @@ +# App Descriptor +appTitle=Data Binding Tutorial +appDescription=Eine einfache Anwendung zur Erkl\u00e4rung der UI5 Data Binding Funktionen + +# Field labels +firstName=Vorname +lastName=Nachname +enabled=Aktiviert +address=Adresse + +# Screen titles +panel1HeaderText=Data Binding Grundlagen +panel2HeaderText=Adressdetails + +# Email +sendEmail=E-Mail versenden +mailSubject=Hallo {0}! +mailBody=Wie geht es dir? diff --git a/packages/databinding/steps/09/webapp/index-cdn.html b/packages/databinding/steps/09/webapp/index-cdn.html new file mode 100644 index 000000000..e2b2e4833 --- /dev/null +++ b/packages/databinding/steps/09/webapp/index-cdn.html @@ -0,0 +1,22 @@ + + + + + + Data Binding Tutorial + + + +
+ + diff --git a/packages/databinding/steps/09/webapp/index.html b/packages/databinding/steps/09/webapp/index.html new file mode 100644 index 000000000..42edae4b3 --- /dev/null +++ b/packages/databinding/steps/09/webapp/index.html @@ -0,0 +1,22 @@ + + + + + + Data Binding Tutorial + + + +
+ + diff --git a/packages/databinding/steps/09/webapp/manifest.json b/packages/databinding/steps/09/webapp/manifest.json new file mode 100644 index 000000000..2738faec9 --- /dev/null +++ b/packages/databinding/steps/09/webapp/manifest.json @@ -0,0 +1,65 @@ +{ + "_version": "2.8.0", + "sap.app": { + "id": "ui5.tutorial.databinding", + "type": "application", + "applicationVersion": { + "version": "1.0.0" + }, + "title": "{{appTitle}}", + "description": "{{appDescription}}", + "i18n": { + "bundleName": "ui5.tutorial.databinding.i18n.i18n", + "supportedLocales": [ + "", + "de" + ], + "fallbackLocale": "" + } + }, + "sap.ui": { + "technology": "UI5", + "deviceTypes": { + "desktop": true, + "tablet": true, + "phone": true + } + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.148", + "libs": { + "sap.m": {}, + "sap.ui.core": {}, + "sap.ui.layout": {} + } + }, + "contentDensities": { + "compact": true, + "cozy": true + }, + "models": { + "": { + "type": "sap.ui.model.json.JSONModel", + "uri": "./model/data.json" + }, + "i18n": { + "type": "sap.ui.model.resource.ResourceModel", + "settings": { + "bundleName": "ui5.tutorial.databinding.i18n.i18n", + "supportedLocales": [ + "", + "de" + ], + "fallbackLocale": "", + "async": true + } + } + }, + "rootView": { + "viewName": "ui5.tutorial.databinding.view.App", + "type": "XML", + "id": "app" + } + } +} diff --git a/packages/databinding/steps/09/webapp/model/data.json b/packages/databinding/steps/09/webapp/model/data.json new file mode 100644 index 000000000..138a22fd4 --- /dev/null +++ b/packages/databinding/steps/09/webapp/model/data.json @@ -0,0 +1,11 @@ +{ + "firstName": "Harry", + "lastName": "Hawk", + "enabled": true, + "address": { + "street": "Dietmar-Hopp-Allee 16", + "city": "Walldorf", + "zip": "69190", + "country": "Germany" + } +} \ No newline at end of file diff --git a/packages/databinding/steps/09/webapp/view/App.view.xml b/packages/databinding/steps/09/webapp/view/App.view.xml new file mode 100644 index 000000000..48c8f4017 --- /dev/null +++ b/packages/databinding/steps/09/webapp/view/App.view.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + diff --git a/packages/databinding/steps/10/README.md b/packages/databinding/steps/10/README.md new file mode 100644 index 000000000..913e3faa6 --- /dev/null +++ b/packages/databinding/steps/10/README.md @@ -0,0 +1,144 @@ + +# Step 10: Property Formatting Using Data Types + +OpenUI5 offers a set of simple data types, including `Boolean`, `Currency`, `Date` and `Float`. You can apply these data types to controls to ensure that the value displayed on the screen is formatted correctly. If the field is open for input, this also ensures that the user input meets the requirements of that data type. Let's add a new field called *Sales Amount* of type `Currency`. + +## Preview + +### An input field for a currency amount is added to the second panel + +![An input field for a currency amount is added to the second panel](assets/Tutorial_Data_Binding_Step_10_d15f8bc.png "An input field for a currency amount is added to the second panel") + +You can view this step live: [🔗 Live Preview of Step 10](https://ui5.github.io/tutorials/databinding/build/10/index-cdn.html). + +## Coding + +You can download the solution for this step here: [📥 Download step 10](https://ui5.github.io/tutorials/databinding/databinding-step-10.zip) (TS)[📥 Download step 10](https://ui5.github.io/tutorials/databinding/databinding-step-10-js.zip) (JS). + +### Folder structure for this step + +```text +webapp/ +├── controller/ +│ └── App.controller.ts/.js +├── i18n/ +│ ├── i18n.properties +│ └── i18n_de.properties +├── model/ +│ └── data.json +├── view/ +│ └── App.view.xml +├── Component.ts/.js +├── index.html +└── manifest.json +``` + +Add two new JSON model properties, `salesAmount` and `currencyCode`, to the `data.json` file. + +## webapp/model/data.json + +```json +{ + "firstName": "Harry", + "lastName": "Hawk", + "enabled": true, + "address": { + "street": "Dietmar-Hopp-Allee 16", + "city": "Walldorf", + "zip": "69190", + "country": "Germany" + }, + "salesAmount": 12345.6789, + "currencyCode": "EUR" +} +``` + +Add a `sap.ui.layout.HorizontalLayout` to the content of the second `sap.m.Panel` within `App.view.xml` file. Move the existing `sap.ui.layout.VerticalLayout` to the default aggregation of the new `sap.ui.layout.HorizontalLayout`. Finally, add a second `sap.ui.layout.VerticalLayout`, containing a `sap.m.Label` and a `sap.m.Input` control, to the `sap.ui.layout.HorizontalLayout`. + +## webapp/view/App.view.xml + +```xml + + ... + + + + + ... + + + + + + + +``` + +We've created a new pair of `Label` and `Input` elements for the `salesAmount` model property. The description property of the `Input` element is bound to the `currencyCode` model property. The value property of the `Input` element is bound to the model properties `salesAmount` and `currencyCode`. The `{showMeasure: false}` parameter switches off the display of the currency symbol within the input field itself. This isn't necessary because the currency symbol is displayed using the `Input` element's description property. + +Add the highlighted texts to the `properties` files. Remember, you need to enter special characters \(non-Latin-1\) using Unicode escape characters. + +## webapp/i18n/i18n.properties + +```properties +... +# Field labels +firstName=Vorname +lastName=Nachname +enabled=Enabled +address=Address +salesAmount=Sales Amount +... +``` + +## webapp/i18n/i18n\_de.properties + +```properties +... +# Field labels +firstName=Vorname +lastName=Nachname +enabled=Aktiviert +address=Adresse +salesAmount=Verk\u00e4ufe bis zum heutigen Datum +... +``` + +*** + +**Next:** [Step 11: Validation Using `sap/ui/core/Messaging`](../11/README.md "Up to this point, we've created a currency field that formats itself correctly. The currency data type can also validate user input to ensure it meets currency requirements.") + +**Previous:** [Step 9: Formatting Values](../09/README.md "We'd also like to provide our users with a way of contacting Harry Hawk, so we're adding a link that sends an e-mail to Harry.") + +*** + +**Related Information** + +[Formatting, Parsing, and Validating Data](https://sdk.openui5.org/topic/07e4b920f5734fd78fdaa236f26236d8.html "Data that is presented on the UI often has to be converted so that is human readable and fits to the locale of the user. On the other hand, data entered by the user has to be parsed and validated to be understood by the data source. For this purpose, you use formatters and data types.") diff --git a/packages/databinding/steps/10/assets/Tutorial_Data_Binding_Step_10_d15f8bc.png b/packages/databinding/steps/10/assets/Tutorial_Data_Binding_Step_10_d15f8bc.png new file mode 100644 index 000000000..220e18c42 Binary files /dev/null and b/packages/databinding/steps/10/assets/Tutorial_Data_Binding_Step_10_d15f8bc.png differ diff --git a/packages/databinding/steps/10/package.json b/packages/databinding/steps/10/package.json new file mode 100644 index 000000000..a73a073f2 --- /dev/null +++ b/packages/databinding/steps/10/package.json @@ -0,0 +1,19 @@ +{ + "name": "ui5.tutorial.databinding.step10", + "private": true, + "version": "1.0.0", + "author": "SAP SE", + "description": "UI5 Demo App - Data Binding Tutorial", + "scripts": { + "start": "ui5 serve -o index.html", + "typecheck": "tsc --noEmit" + }, + "devDependencies": { + "@openui5/types": "^1.148.1", + "@ui5/cli": "^4.0.53", + "typescript": "^6.0.3", + "ui5-middleware-livereload": "^3.3.1", + "ui5-middleware-serveframework": "^3.8.1", + "ui5-tooling-transpile": "^3.11.0" + } +} diff --git a/packages/databinding/steps/10/tsconfig.json b/packages/databinding/steps/10/tsconfig.json new file mode 100644 index 000000000..0b7fa1ae7 --- /dev/null +++ b/packages/databinding/steps/10/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "es2023", + "types": [ + "@openui5/types" + ], + "skipLibCheck": true, + "allowJs": true, + "strictPropertyInitialization": false, + "rootDir": "./webapp", + "paths": { + "ui5/tutorial/databinding/*": [ + "./webapp/*" + ] + }, + "strict": false, + "strictNullChecks": false + }, + "exclude": [ + "./webapp/test/e2e/**/*" + ], + "include": [ + "./webapp/**/*" + ] +} diff --git a/packages/databinding/steps/10/ui5.yaml b/packages/databinding/steps/10/ui5.yaml new file mode 100644 index 000000000..af70be177 --- /dev/null +++ b/packages/databinding/steps/10/ui5.yaml @@ -0,0 +1,24 @@ +specVersion: "4.0" +metadata: + name: "ui5.tutorial.databinding" +type: application +framework: + name: OpenUI5 + version: "1.148.1" + libraries: + - name: sap.m + - name: sap.ui.core + - name: sap.ui.layout + - name: themelib_sap_horizon +builder: + customTasks: + - name: ui5-tooling-transpile-task + afterTask: replaceVersion +server: + customMiddleware: + - name: ui5-tooling-transpile-middleware + afterMiddleware: compression + - name: ui5-middleware-serveframework + afterMiddleware: compression + - name: ui5-middleware-livereload + afterMiddleware: compression diff --git a/packages/databinding/steps/10/webapp/Component.ts b/packages/databinding/steps/10/webapp/Component.ts new file mode 100644 index 000000000..3fc494bca --- /dev/null +++ b/packages/databinding/steps/10/webapp/Component.ts @@ -0,0 +1,11 @@ +import UIComponent from "sap/ui/core/UIComponent"; + +/** + * @namespace ui5.tutorial.databinding + */ +export default class Component extends UIComponent { + public static metadata = { + interfaces: ["sap.ui.core.IAsyncContentCreation"], + manifest: "json" + }; +} diff --git a/packages/databinding/steps/10/webapp/controller/App.controller.ts b/packages/databinding/steps/10/webapp/controller/App.controller.ts new file mode 100644 index 000000000..36a95910b --- /dev/null +++ b/packages/databinding/steps/10/webapp/controller/App.controller.ts @@ -0,0 +1,19 @@ +import ResourceBundle from "sap/base/i18n/ResourceBundle"; +import { URLHelper } from "sap/m/library"; +import Controller from "sap/ui/core/mvc/Controller"; +import ResourceModel from "sap/ui/model/resource/ResourceModel"; + +/** + * @namespace ui5.tutorial.databinding.controller + */ +export default class App extends Controller { + public async formatMail(firstName: string, lastName: string): Promise { + const bundle: ResourceBundle = await (this.getView().getModel("i18n") as ResourceModel).getResourceBundle() as ResourceBundle; + + return URLHelper.normalizeEmail( + `${firstName}.${lastName}@example.com`, + bundle.getText("mailSubject", [firstName]), + bundle.getText("mailBody") + ); + } +} diff --git a/packages/databinding/steps/10/webapp/i18n/i18n.properties b/packages/databinding/steps/10/webapp/i18n/i18n.properties new file mode 100644 index 000000000..bd88296b2 --- /dev/null +++ b/packages/databinding/steps/10/webapp/i18n/i18n.properties @@ -0,0 +1,19 @@ +# App Descriptor +appTitle=Data Binding Tutorial +appDescription=A simple app that explains how to use data binding features of OpenUI5 + +# Field labels +firstName=First Name +lastName=Last Name +enabled=Enabled +address=Address +salesAmount=Sales Amount + +# Screen titles +panel1HeaderText=Data Binding Basics +panel2HeaderText=Address Details + +# Email +sendEmail=Send EMail +mailSubject=Hi {0}! +mailBody=How are you? diff --git a/packages/databinding/steps/10/webapp/i18n/i18n_de.properties b/packages/databinding/steps/10/webapp/i18n/i18n_de.properties new file mode 100644 index 000000000..21988f6c1 --- /dev/null +++ b/packages/databinding/steps/10/webapp/i18n/i18n_de.properties @@ -0,0 +1,19 @@ +# App Descriptor +appTitle=Data Binding Tutorial +appDescription=Eine einfache Anwendung zur Erkl\u00e4rung der UI5 Data Binding Funktionen + +# Field labels +firstName=Vorname +lastName=Nachname +enabled=Aktiviert +address=Adresse +salesAmount=Verk\u00e4ufe bis zum heutigen Datum + +# Screen titles +panel1HeaderText=Data Binding Grundlagen +panel2HeaderText=Adressdetails + +# Email +sendEmail=E-Mail versenden +mailSubject=Hallo {0}! +mailBody=Wie geht es dir? diff --git a/packages/databinding/steps/10/webapp/index-cdn.html b/packages/databinding/steps/10/webapp/index-cdn.html new file mode 100644 index 000000000..e2b2e4833 --- /dev/null +++ b/packages/databinding/steps/10/webapp/index-cdn.html @@ -0,0 +1,22 @@ + + + + + + Data Binding Tutorial + + + +
+ + diff --git a/packages/databinding/steps/10/webapp/index.html b/packages/databinding/steps/10/webapp/index.html new file mode 100644 index 000000000..42edae4b3 --- /dev/null +++ b/packages/databinding/steps/10/webapp/index.html @@ -0,0 +1,22 @@ + + + + + + Data Binding Tutorial + + + +
+ + diff --git a/packages/databinding/steps/10/webapp/manifest.json b/packages/databinding/steps/10/webapp/manifest.json new file mode 100644 index 000000000..2738faec9 --- /dev/null +++ b/packages/databinding/steps/10/webapp/manifest.json @@ -0,0 +1,65 @@ +{ + "_version": "2.8.0", + "sap.app": { + "id": "ui5.tutorial.databinding", + "type": "application", + "applicationVersion": { + "version": "1.0.0" + }, + "title": "{{appTitle}}", + "description": "{{appDescription}}", + "i18n": { + "bundleName": "ui5.tutorial.databinding.i18n.i18n", + "supportedLocales": [ + "", + "de" + ], + "fallbackLocale": "" + } + }, + "sap.ui": { + "technology": "UI5", + "deviceTypes": { + "desktop": true, + "tablet": true, + "phone": true + } + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.148", + "libs": { + "sap.m": {}, + "sap.ui.core": {}, + "sap.ui.layout": {} + } + }, + "contentDensities": { + "compact": true, + "cozy": true + }, + "models": { + "": { + "type": "sap.ui.model.json.JSONModel", + "uri": "./model/data.json" + }, + "i18n": { + "type": "sap.ui.model.resource.ResourceModel", + "settings": { + "bundleName": "ui5.tutorial.databinding.i18n.i18n", + "supportedLocales": [ + "", + "de" + ], + "fallbackLocale": "", + "async": true + } + } + }, + "rootView": { + "viewName": "ui5.tutorial.databinding.view.App", + "type": "XML", + "id": "app" + } + } +} diff --git a/packages/databinding/steps/10/webapp/model/data.json b/packages/databinding/steps/10/webapp/model/data.json new file mode 100644 index 000000000..da676ea6b --- /dev/null +++ b/packages/databinding/steps/10/webapp/model/data.json @@ -0,0 +1,13 @@ +{ + "firstName": "Harry", + "lastName": "Hawk", + "enabled": true, + "address": { + "street": "Dietmar-Hopp-Allee 16", + "city": "Walldorf", + "zip": "69190", + "country": "Germany" + }, + "salesAmount": 12345.6789, + "currencyCode": "EUR" +} \ No newline at end of file diff --git a/packages/databinding/steps/10/webapp/view/App.view.xml b/packages/databinding/steps/10/webapp/view/App.view.xml new file mode 100644 index 000000000..639403174 --- /dev/null +++ b/packages/databinding/steps/10/webapp/view/App.view.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + diff --git a/packages/databinding/steps/11/README.md b/packages/databinding/steps/11/README.md new file mode 100644 index 000000000..8f7ebb708 --- /dev/null +++ b/packages/databinding/steps/11/README.md @@ -0,0 +1,71 @@ + +# Step 11: Validation Using `sap/ui/core/Messaging` + +Up to this point, we've created a currency field that formats itself correctly. The *currency* data type can also validate user input to ensure it meets currency requirements. However, OpenUI5 manages data type validation functions and doesn't have a built-in mechanism for reporting error messages back to the UI. We therefore need a way to report error messages from validation functions back to the user. In this step, we're enabling validation for the entire app with a feature known as "Messaging". Once this is set up, any validation error messages based on user input get passed to `Messaging`, which then connects them to the appropriate view and control that caused the error. + +## Preview + +### An error message is displayed upon entering text into the currency amount input field + +![An error message is displayed upon entering text into the currency amount input field](assets/Tutorial_Data_Binding_Step_11_3_e67207b.png "An error message is displayed upon entering text into the currency amount input field") + +You can view this step live: [🔗 Live Preview of Step 11](https://ui5.github.io/tutorials/databinding/build/11/index-cdn.html). + +## Coding + +You can download the solution for this step here: [📥 Download step 11](https://ui5.github.io/tutorials/databinding/databinding-step-11.zip) (TS)[📥 Download step 11](https://ui5.github.io/tutorials/databinding/databinding-step-11-js.zip) (JS). + +### Folder structure for this step + +```text +webapp/ +├── controller/ +│ └── App.controller.ts/.js +├── i18n/ +│ ├── i18n.properties +│ └── i18n_de.properties +├── model/ +│ └── data.json +├── view/ +│ └── App.view.xml +├── Component.ts/.js +├── index.html +└── manifest.json +``` + +## webapp/manifest.json + +To generally enable validation in the app, add the `handleValidation` property into the `sap.ui5` section of the `manifest.json` file as shown below. + +```json +... + "sap.ui5": { + "handleValidation": true, + ... + } +... +``` + +Now, try entering a non-numeric value into the *Sales Amount* field and either press [Enter\] or move the focus to a different UI control. This action triggers either the `onenter` or `onchange` event. OpenUI5 then executes the validation function for the `sap.ui.model.type.Currency` data type. + +With validation handling enabled in the manifest, any validation error messages will be picked up by `Messaging`. It checks its list of registered objects and passes the error message back to the correct view for display. + +You'll notice that the field in error has a red border: + +![Input field showing a validation error](assets/Tutorial_Data_Binding_Step_11_2_44db88e.png "Input field showing a validation error") + +However, the error message only displays when that particular field is in focus: + +![Input field showing a validation error](assets/Tutorial_Data_Binding_Step_11_3_e67207b.png "Input field showing a validation error") + +*** + +**Next:** [Step 12: Aggregation Binding Using Templates](../12/README.md "Aggregation binding, also known as list binding, lets a control bind to a list within the model data.") + +**Previous:** [Step 10: Property Formatting Using Data Types](../10/README.md "OpenUI5 offers a set of simple data types, including Boolean, Currency, Date and Float.") + +*** + +**Related Information** + +[Error, Warning, and Info Messages](https://sdk.openui5.org/topic/62b1481d3e084cb49dd30956d183c6a0.html "OpenUI5 provides a central place for storing and managing info, warning, and error messages.") diff --git a/packages/databinding/steps/11/assets/Tutorial_Data_Binding_Step_11_2_44db88e.png b/packages/databinding/steps/11/assets/Tutorial_Data_Binding_Step_11_2_44db88e.png new file mode 100644 index 000000000..0bad8bfbe Binary files /dev/null and b/packages/databinding/steps/11/assets/Tutorial_Data_Binding_Step_11_2_44db88e.png differ diff --git a/packages/databinding/steps/11/assets/Tutorial_Data_Binding_Step_11_3_e67207b.png b/packages/databinding/steps/11/assets/Tutorial_Data_Binding_Step_11_3_e67207b.png new file mode 100644 index 000000000..1a262c0fc Binary files /dev/null and b/packages/databinding/steps/11/assets/Tutorial_Data_Binding_Step_11_3_e67207b.png differ diff --git a/packages/databinding/steps/11/package.json b/packages/databinding/steps/11/package.json new file mode 100644 index 000000000..1cd5ae47b --- /dev/null +++ b/packages/databinding/steps/11/package.json @@ -0,0 +1,19 @@ +{ + "name": "ui5.tutorial.databinding.step11", + "private": true, + "version": "1.0.0", + "author": "SAP SE", + "description": "UI5 Demo App - Data Binding Tutorial", + "scripts": { + "start": "ui5 serve -o index.html", + "typecheck": "tsc --noEmit" + }, + "devDependencies": { + "@openui5/types": "^1.148.1", + "@ui5/cli": "^4.0.53", + "typescript": "^6.0.3", + "ui5-middleware-livereload": "^3.3.1", + "ui5-middleware-serveframework": "^3.8.1", + "ui5-tooling-transpile": "^3.11.0" + } +} diff --git a/packages/databinding/steps/11/tsconfig.json b/packages/databinding/steps/11/tsconfig.json new file mode 100644 index 000000000..0b7fa1ae7 --- /dev/null +++ b/packages/databinding/steps/11/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "es2023", + "types": [ + "@openui5/types" + ], + "skipLibCheck": true, + "allowJs": true, + "strictPropertyInitialization": false, + "rootDir": "./webapp", + "paths": { + "ui5/tutorial/databinding/*": [ + "./webapp/*" + ] + }, + "strict": false, + "strictNullChecks": false + }, + "exclude": [ + "./webapp/test/e2e/**/*" + ], + "include": [ + "./webapp/**/*" + ] +} diff --git a/packages/databinding/steps/11/ui5.yaml b/packages/databinding/steps/11/ui5.yaml new file mode 100644 index 000000000..af70be177 --- /dev/null +++ b/packages/databinding/steps/11/ui5.yaml @@ -0,0 +1,24 @@ +specVersion: "4.0" +metadata: + name: "ui5.tutorial.databinding" +type: application +framework: + name: OpenUI5 + version: "1.148.1" + libraries: + - name: sap.m + - name: sap.ui.core + - name: sap.ui.layout + - name: themelib_sap_horizon +builder: + customTasks: + - name: ui5-tooling-transpile-task + afterTask: replaceVersion +server: + customMiddleware: + - name: ui5-tooling-transpile-middleware + afterMiddleware: compression + - name: ui5-middleware-serveframework + afterMiddleware: compression + - name: ui5-middleware-livereload + afterMiddleware: compression diff --git a/packages/databinding/steps/11/webapp/Component.ts b/packages/databinding/steps/11/webapp/Component.ts new file mode 100644 index 000000000..3fc494bca --- /dev/null +++ b/packages/databinding/steps/11/webapp/Component.ts @@ -0,0 +1,11 @@ +import UIComponent from "sap/ui/core/UIComponent"; + +/** + * @namespace ui5.tutorial.databinding + */ +export default class Component extends UIComponent { + public static metadata = { + interfaces: ["sap.ui.core.IAsyncContentCreation"], + manifest: "json" + }; +} diff --git a/packages/databinding/steps/11/webapp/controller/App.controller.ts b/packages/databinding/steps/11/webapp/controller/App.controller.ts new file mode 100644 index 000000000..36a95910b --- /dev/null +++ b/packages/databinding/steps/11/webapp/controller/App.controller.ts @@ -0,0 +1,19 @@ +import ResourceBundle from "sap/base/i18n/ResourceBundle"; +import { URLHelper } from "sap/m/library"; +import Controller from "sap/ui/core/mvc/Controller"; +import ResourceModel from "sap/ui/model/resource/ResourceModel"; + +/** + * @namespace ui5.tutorial.databinding.controller + */ +export default class App extends Controller { + public async formatMail(firstName: string, lastName: string): Promise { + const bundle: ResourceBundle = await (this.getView().getModel("i18n") as ResourceModel).getResourceBundle() as ResourceBundle; + + return URLHelper.normalizeEmail( + `${firstName}.${lastName}@example.com`, + bundle.getText("mailSubject", [firstName]), + bundle.getText("mailBody") + ); + } +} diff --git a/packages/databinding/steps/11/webapp/i18n/i18n.properties b/packages/databinding/steps/11/webapp/i18n/i18n.properties new file mode 100644 index 000000000..bd88296b2 --- /dev/null +++ b/packages/databinding/steps/11/webapp/i18n/i18n.properties @@ -0,0 +1,19 @@ +# App Descriptor +appTitle=Data Binding Tutorial +appDescription=A simple app that explains how to use data binding features of OpenUI5 + +# Field labels +firstName=First Name +lastName=Last Name +enabled=Enabled +address=Address +salesAmount=Sales Amount + +# Screen titles +panel1HeaderText=Data Binding Basics +panel2HeaderText=Address Details + +# Email +sendEmail=Send EMail +mailSubject=Hi {0}! +mailBody=How are you? diff --git a/packages/databinding/steps/11/webapp/i18n/i18n_de.properties b/packages/databinding/steps/11/webapp/i18n/i18n_de.properties new file mode 100644 index 000000000..21988f6c1 --- /dev/null +++ b/packages/databinding/steps/11/webapp/i18n/i18n_de.properties @@ -0,0 +1,19 @@ +# App Descriptor +appTitle=Data Binding Tutorial +appDescription=Eine einfache Anwendung zur Erkl\u00e4rung der UI5 Data Binding Funktionen + +# Field labels +firstName=Vorname +lastName=Nachname +enabled=Aktiviert +address=Adresse +salesAmount=Verk\u00e4ufe bis zum heutigen Datum + +# Screen titles +panel1HeaderText=Data Binding Grundlagen +panel2HeaderText=Adressdetails + +# Email +sendEmail=E-Mail versenden +mailSubject=Hallo {0}! +mailBody=Wie geht es dir? diff --git a/packages/databinding/steps/11/webapp/index-cdn.html b/packages/databinding/steps/11/webapp/index-cdn.html new file mode 100644 index 000000000..e2b2e4833 --- /dev/null +++ b/packages/databinding/steps/11/webapp/index-cdn.html @@ -0,0 +1,22 @@ + + + + + + Data Binding Tutorial + + + +
+ + diff --git a/packages/databinding/steps/11/webapp/index.html b/packages/databinding/steps/11/webapp/index.html new file mode 100644 index 000000000..42edae4b3 --- /dev/null +++ b/packages/databinding/steps/11/webapp/index.html @@ -0,0 +1,22 @@ + + + + + + Data Binding Tutorial + + + +
+ + diff --git a/packages/databinding/steps/11/webapp/manifest.json b/packages/databinding/steps/11/webapp/manifest.json new file mode 100644 index 000000000..41eefc4e4 --- /dev/null +++ b/packages/databinding/steps/11/webapp/manifest.json @@ -0,0 +1,66 @@ +{ + "_version": "2.8.0", + "sap.app": { + "id": "ui5.tutorial.databinding", + "type": "application", + "applicationVersion": { + "version": "1.0.0" + }, + "title": "{{appTitle}}", + "description": "{{appDescription}}", + "i18n": { + "bundleName": "ui5.tutorial.databinding.i18n.i18n", + "supportedLocales": [ + "", + "de" + ], + "fallbackLocale": "" + } + }, + "sap.ui": { + "technology": "UI5", + "deviceTypes": { + "desktop": true, + "tablet": true, + "phone": true + } + }, + "sap.ui5": { + "handleValidation": true, + "dependencies": { + "minUI5Version": "1.148", + "libs": { + "sap.m": {}, + "sap.ui.core": {}, + "sap.ui.layout": {} + } + }, + "contentDensities": { + "compact": true, + "cozy": true + }, + "models": { + "": { + "type": "sap.ui.model.json.JSONModel", + "uri": "./model/data.json" + }, + "i18n": { + "type": "sap.ui.model.resource.ResourceModel", + "settings": { + "bundleName": "ui5.tutorial.databinding.i18n.i18n", + "supportedLocales": [ + "", + "de" + ], + "fallbackLocale": "", + "async": true + } + } + }, + "rootView": { + "viewName": "ui5.tutorial.databinding.view.App", + "type": "XML", + "id": "app" + } + } +} diff --git a/packages/databinding/steps/11/webapp/model/data.json b/packages/databinding/steps/11/webapp/model/data.json new file mode 100644 index 000000000..da676ea6b --- /dev/null +++ b/packages/databinding/steps/11/webapp/model/data.json @@ -0,0 +1,13 @@ +{ + "firstName": "Harry", + "lastName": "Hawk", + "enabled": true, + "address": { + "street": "Dietmar-Hopp-Allee 16", + "city": "Walldorf", + "zip": "69190", + "country": "Germany" + }, + "salesAmount": 12345.6789, + "currencyCode": "EUR" +} \ No newline at end of file diff --git a/packages/databinding/steps/11/webapp/view/App.view.xml b/packages/databinding/steps/11/webapp/view/App.view.xml new file mode 100644 index 000000000..639403174 --- /dev/null +++ b/packages/databinding/steps/11/webapp/view/App.view.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + diff --git a/packages/databinding/steps/12/README.md b/packages/databinding/steps/12/README.md new file mode 100644 index 000000000..2aa1bfd04 --- /dev/null +++ b/packages/databinding/steps/12/README.md @@ -0,0 +1,305 @@ + +# Step 12: Aggregation Binding Using Templates + +Aggregation binding, also known as "list binding", lets a control bind to a list within the model data. This binding allows relative binding to the list entries by its child controls. + +The system automatically creates as many child controls as are needed to display the data in the model using one of two approaches: + +- It clones a template control as many times as necessary to display the data. + +- It uses a factory function to generate the correct control for each bound list entry, based on the data received at runtime. + +## Preview + +### A third panel with a list of products is displayed + +![A third panel with a list of products is displayed](assets/Tutorial_Data_Binding_Step_12_1642433.png "A third panel with a list of products is displayed") + +You can view this step live: [🔗 Live Preview of Step 12](https://ui5.github.io/tutorials/databinding/build/12/index-cdn.html). + +## Coding + +You can download the solution for this step here: [📥 Download step 12](https://ui5.github.io/tutorials/databinding/databinding-step-12.zip) (TS)[📥 Download step 12](https://ui5.github.io/tutorials/databinding/databinding-step-12-js.zip) (JS). + +### Folder structure for this step + +```text +webapp/ +├── controller/ +│ └── App.controller.ts/.js +├── i18n/ +│ ├── i18n.properties +│ └── i18n_de.properties +├── model/ +│ ├── Products.json +│ └── data.json +├── view/ +│ └── App.view.xml +├── Component.ts/.js +├── index.html +└── manifest.json +``` + +Add a new entry named `products` to the `models` entry under `sap.ui5` in the `manifest.json` file: + +## webapp/manifest.json + +```json +{ + ... + "sap.ui5": { + ... + "models": { + "": { + "type": "sap.ui.model.json.JSONModel", + "uri": "./model/data.json" + }, + "products" : { + "type": "sap.ui.model.json.JSONModel", + "uri": "./model/Products.json" + }, + "i18n": { + "type": "sap.ui.model.resource.ResourceModel", + "settings": { + "bundleName": "ui5.tutorial.databinding.i18n.i18n", + "supportedLocales": [ + "", + "de" + ], + "fallbackLocale": "" + } + } + }, + ... + } + ... +} +``` + +Create a new file named `Products.json` in the `model` folder. Enter the data for the products: + +## webapp/model/Products.json \(New\) + +```json +{ + "Products": [ + { + "ProductID": 1, + "ProductName": "Chai", + "SupplierID": 1, + "CategoryID": 1, + "QuantityPerUnit": "10 boxes x 20 bags", + "UnitPrice": "18.0000", + "UnitsInStock": 39, + "UnitsOnOrder": 0, + "ReorderLevel": 10, + "Discontinued": false + }, + { + "ProductID": 2, + "ProductName": "Chang", + "SupplierID": 1, + "CategoryID": 1, + "QuantityPerUnit": "24 - 12 oz bottles", + "UnitPrice": "19.0000", + "UnitsInStock": 17, + "UnitsOnOrder": 40, + "ReorderLevel": 25, + "Discontinued": false + }, + { + "ProductID": 3, + "ProductName": "Aniseed Syrup", + "SupplierID": 1, + "CategoryID": 2, + "QuantityPerUnit": "12 - 550 ml bottles", + "UnitPrice": "10.0000", + "UnitsInStock": 13, + "UnitsOnOrder": 70, + "ReorderLevel": 25, + "Discontinued": false + }, + { + "ProductID": 4, + "ProductName": "Chef Anton's Cajun Seasoning", + "SupplierID": 2, + "CategoryID": 2, + "QuantityPerUnit": "48 - 6 oz jars", + "UnitPrice": "22.0000", + "UnitsInStock": 53, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": false + }, + { + "ProductID": 5, + "ProductName": "Chef Anton's Gumbo Mix", + "SupplierID": 2, + "CategoryID": 2, + "QuantityPerUnit": "36 boxes", + "UnitPrice": "21.3500", + "UnitsInStock": 0, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": true + } + ] +} +``` + +In the `App.view.xml` file, add a new panel with an `sap.m.List` control containing the `sap.m.ObjectListItem` template control as shown below. Note that the template control is only present once in the XML view. It's automatically cloned for each entry in the products' JSON model. + +## webapp/view/App.view.xml + +```xml + + ... + + + + + + + + + + + + + +``` + +Also, add another formatter to the `App.controller.ts/.js` file to calculate the value of the stock of each product. + +## `webapp/controller/App.controller.ts/.js` + +```ts +// webapp/controller/App.controller.ts +import ResourceBundle from "sap/base/i18n/ResourceBundle"; +import { URLHelper } from "sap/m/library"; +import Controller from "sap/ui/core/mvc/Controller"; +import ResourceModel from "sap/ui/model/resource/ResourceModel"; +import Currency from "sap/ui/model/type/Currency"; + +/** + * @namespace ui5.tutorial.databinding.controller + */ +export default class App extends Controller { + public async formatMail(firstName: string, lastName: string): Promise { + const bundle: ResourceBundle = await (this.getView().getModel("i18n") as ResourceModel).getResourceBundle() as ResourceBundle; + + return URLHelper.normalizeEmail( + `${firstName}.${lastName}@example.com`, + bundle.getText("mailSubject", [firstName]), + bundle.getText("mailBody") + ); + } + + public formatStockValue(unitPrice: number, stockLevel: number, currencyCode: string): string { + const currency: Currency = new Currency(); + return currency.formatValue([unitPrice * stockLevel, currencyCode], "string"); + } +} +``` + +```js +// webapp/controller/App.controller.js +sap.ui.define(["sap/ui/core/mvc/Controller", "sap/m/library", "sap/ui/model/type/Currency"], function (Controller, mobileLibrary, Currency) { + "use strict"; + + return Controller.extend("ui5.tutorial.databinding.controller.App", { + async formatMail(firstName, lastName) { + const bundle = await this.getView().getModel("i18n").getResourceBundle(); + + return mobileLibrary.URLHelper.normalizeEmail( + `${firstName}.${lastName}@example.com`, + bundle.getText("mailSubject", [firstName]), + bundle.getText("mailBody") + ); + }, + + formatStockValue(unitPrice, stockLevel, currencyCode) { + const currency = new Currency(); + return currency.formatValue([unitPrice * stockLevel, currencyCode], "string"); + } + }); +}); +``` + +Lastly, add the missing texts to the `i18n.properties` and `i18n_de.properties` files. These texts are used in the newly added UI elements. + +## webapp/i18n/i18n.properties + +```properties +... +# Screen titles +panel1HeaderText=Data Binding Basics +panel2HeaderText=Address Details +panel3HeaderText=Aggregation Binding + +... + +# Product list +productListTitle=Product List +stockValue=Current Stock Value +``` + +## webapp/i18n/i18n\_de.properties + +```properties +... +# Screen titles +panel1HeaderText=Data Binding Grundlagen +panel2HeaderText=Adressdetails +panel3HeaderText=Aggregation Binding + +... + +# Product list +productListTitle=Artikelliste +stockValue=Lagerbestand Wert +``` + +*** + +**Next:** [Step 13: Element Binding](../13/README.md "Now, let's do something with that newly generated list.") + +**Previous:** [Step 11: Validation Using `sap/ui/core/Messaging`](../11/README.md "Up to this point, we've created a currency field that formats itself correctly.") + +*** + +**Related Information** + +[List Binding \(Aggregation Binding\)](https://sdk.openui5.org/topic/91f057786f4d1014b6dd926db0e91070.html "List binding (or aggregation binding) is used to automatically create child controls according to model data.") diff --git a/packages/databinding/steps/12/assets/Tutorial_Data_Binding_Step_12_1642433.png b/packages/databinding/steps/12/assets/Tutorial_Data_Binding_Step_12_1642433.png new file mode 100644 index 000000000..2e6f27c35 Binary files /dev/null and b/packages/databinding/steps/12/assets/Tutorial_Data_Binding_Step_12_1642433.png differ diff --git a/packages/databinding/steps/12/package.json b/packages/databinding/steps/12/package.json new file mode 100644 index 000000000..be698bb50 --- /dev/null +++ b/packages/databinding/steps/12/package.json @@ -0,0 +1,19 @@ +{ + "name": "ui5.tutorial.databinding.step12", + "private": true, + "version": "1.0.0", + "author": "SAP SE", + "description": "UI5 Demo App - Data Binding Tutorial", + "scripts": { + "start": "ui5 serve -o index.html", + "typecheck": "tsc --noEmit" + }, + "devDependencies": { + "@openui5/types": "^1.148.1", + "@ui5/cli": "^4.0.53", + "typescript": "^6.0.3", + "ui5-middleware-livereload": "^3.3.1", + "ui5-middleware-serveframework": "^3.8.1", + "ui5-tooling-transpile": "^3.11.0" + } +} diff --git a/packages/databinding/steps/12/tsconfig.json b/packages/databinding/steps/12/tsconfig.json new file mode 100644 index 000000000..0b7fa1ae7 --- /dev/null +++ b/packages/databinding/steps/12/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "es2023", + "types": [ + "@openui5/types" + ], + "skipLibCheck": true, + "allowJs": true, + "strictPropertyInitialization": false, + "rootDir": "./webapp", + "paths": { + "ui5/tutorial/databinding/*": [ + "./webapp/*" + ] + }, + "strict": false, + "strictNullChecks": false + }, + "exclude": [ + "./webapp/test/e2e/**/*" + ], + "include": [ + "./webapp/**/*" + ] +} diff --git a/packages/databinding/steps/12/ui5.yaml b/packages/databinding/steps/12/ui5.yaml new file mode 100644 index 000000000..af70be177 --- /dev/null +++ b/packages/databinding/steps/12/ui5.yaml @@ -0,0 +1,24 @@ +specVersion: "4.0" +metadata: + name: "ui5.tutorial.databinding" +type: application +framework: + name: OpenUI5 + version: "1.148.1" + libraries: + - name: sap.m + - name: sap.ui.core + - name: sap.ui.layout + - name: themelib_sap_horizon +builder: + customTasks: + - name: ui5-tooling-transpile-task + afterTask: replaceVersion +server: + customMiddleware: + - name: ui5-tooling-transpile-middleware + afterMiddleware: compression + - name: ui5-middleware-serveframework + afterMiddleware: compression + - name: ui5-middleware-livereload + afterMiddleware: compression diff --git a/packages/databinding/steps/12/webapp/Component.ts b/packages/databinding/steps/12/webapp/Component.ts new file mode 100644 index 000000000..3fc494bca --- /dev/null +++ b/packages/databinding/steps/12/webapp/Component.ts @@ -0,0 +1,11 @@ +import UIComponent from "sap/ui/core/UIComponent"; + +/** + * @namespace ui5.tutorial.databinding + */ +export default class Component extends UIComponent { + public static metadata = { + interfaces: ["sap.ui.core.IAsyncContentCreation"], + manifest: "json" + }; +} diff --git a/packages/databinding/steps/12/webapp/controller/App.controller.ts b/packages/databinding/steps/12/webapp/controller/App.controller.ts new file mode 100644 index 000000000..6e71310b5 --- /dev/null +++ b/packages/databinding/steps/12/webapp/controller/App.controller.ts @@ -0,0 +1,25 @@ +import ResourceBundle from "sap/base/i18n/ResourceBundle"; +import { URLHelper } from "sap/m/library"; +import Controller from "sap/ui/core/mvc/Controller"; +import ResourceModel from "sap/ui/model/resource/ResourceModel"; +import Currency from "sap/ui/model/type/Currency"; + +/** + * @namespace ui5.tutorial.databinding.controller + */ +export default class App extends Controller { + public async formatMail(firstName: string, lastName: string): Promise { + const bundle: ResourceBundle = await (this.getView().getModel("i18n") as ResourceModel).getResourceBundle() as ResourceBundle; + + return URLHelper.normalizeEmail( + `${firstName}.${lastName}@example.com`, + bundle.getText("mailSubject", [firstName]), + bundle.getText("mailBody") + ); + } + + public formatStockValue(unitPrice: float, stockLevel: number, currencyCode: string): string { + const currency: Currency = new Currency(); + return currency.formatValue([unitPrice * stockLevel, currencyCode], "string"); + } +} diff --git a/packages/databinding/steps/12/webapp/i18n/i18n.properties b/packages/databinding/steps/12/webapp/i18n/i18n.properties new file mode 100644 index 000000000..c0907bcb2 --- /dev/null +++ b/packages/databinding/steps/12/webapp/i18n/i18n.properties @@ -0,0 +1,24 @@ +# App Descriptor +appTitle=Data Binding Tutorial +appDescription=A simple app that explains how to use data binding features of OpenUI5 + +# Field labels +firstName=First Name +lastName=Last Name +enabled=Enabled +address=Address +salesAmount=Sales Amount + +# Screen titles +panel1HeaderText=Data Binding Basics +panel2HeaderText=Address Details +panel3HeaderText=Aggregation Binding + +# Email +sendEmail=Send EMail +mailSubject=Hi {0}! +mailBody=How are you? + +# Product list +productListTitle=Product List +stockValue=Current Stock Value diff --git a/packages/databinding/steps/12/webapp/i18n/i18n_de.properties b/packages/databinding/steps/12/webapp/i18n/i18n_de.properties new file mode 100644 index 000000000..217e279b8 --- /dev/null +++ b/packages/databinding/steps/12/webapp/i18n/i18n_de.properties @@ -0,0 +1,24 @@ +# App Descriptor +appTitle=Data Binding Tutorial +appDescription=Eine einfache Anwendung zur Erkl\u00e4rung der UI5 Data Binding Funktionen + +# Field labels +firstName=Vorname +lastName=Nachname +enabled=Aktiviert +address=Adresse +salesAmount=Verk\u00e4ufe bis zum heutigen Datum + +# Screen titles +panel1HeaderText=Data Binding Grundlagen +panel2HeaderText=Adressdetails +panel3HeaderText=Aggregation Binding + +# Email +sendEmail=E-Mail versenden +mailSubject=Hallo {0}! +mailBody=Wie geht es dir? + +# Product list +productListTitle=Artikelliste +stockValue=Lagerbestand Wert diff --git a/packages/databinding/steps/12/webapp/index-cdn.html b/packages/databinding/steps/12/webapp/index-cdn.html new file mode 100644 index 000000000..e2b2e4833 --- /dev/null +++ b/packages/databinding/steps/12/webapp/index-cdn.html @@ -0,0 +1,22 @@ + + + + + + Data Binding Tutorial + + + +
+ + diff --git a/packages/databinding/steps/12/webapp/index.html b/packages/databinding/steps/12/webapp/index.html new file mode 100644 index 000000000..42edae4b3 --- /dev/null +++ b/packages/databinding/steps/12/webapp/index.html @@ -0,0 +1,22 @@ + + + + + + Data Binding Tutorial + + + +
+ + diff --git a/packages/databinding/steps/12/webapp/manifest.json b/packages/databinding/steps/12/webapp/manifest.json new file mode 100644 index 000000000..3852b19e3 --- /dev/null +++ b/packages/databinding/steps/12/webapp/manifest.json @@ -0,0 +1,70 @@ +{ + "_version": "2.8.0", + "sap.app": { + "id": "ui5.tutorial.databinding", + "type": "application", + "applicationVersion": { + "version": "1.0.0" + }, + "title": "{{appTitle}}", + "description": "{{appDescription}}", + "i18n": { + "bundleName": "ui5.tutorial.databinding.i18n.i18n", + "supportedLocales": [ + "", + "de" + ], + "fallbackLocale": "" + } + }, + "sap.ui": { + "technology": "UI5", + "deviceTypes": { + "desktop": true, + "tablet": true, + "phone": true + } + }, + "sap.ui5": { + "handleValidation": true, + "dependencies": { + "minUI5Version": "1.148", + "libs": { + "sap.m": {}, + "sap.ui.core": {}, + "sap.ui.layout": {} + } + }, + "contentDensities": { + "compact": true, + "cozy": true + }, + "models": { + "": { + "type": "sap.ui.model.json.JSONModel", + "uri": "./model/data.json" + }, + "products": { + "type": "sap.ui.model.json.JSONModel", + "uri": "./model/Products.json" + }, + "i18n": { + "type": "sap.ui.model.resource.ResourceModel", + "settings": { + "bundleName": "ui5.tutorial.databinding.i18n.i18n", + "supportedLocales": [ + "", + "de" + ], + "fallbackLocale": "", + "async": true + } + } + }, + "rootView": { + "viewName": "ui5.tutorial.databinding.view.App", + "type": "XML", + "id": "app" + } + } +} diff --git a/packages/databinding/steps/12/webapp/model/Products.json b/packages/databinding/steps/12/webapp/model/Products.json new file mode 100644 index 000000000..2b47ed742 --- /dev/null +++ b/packages/databinding/steps/12/webapp/model/Products.json @@ -0,0 +1,64 @@ +{ + "Products": [ + { + "ProductID": 1, + "ProductName": "Chai", + "SupplierID": 1, + "CategoryID": 1, + "QuantityPerUnit": "10 boxes x 20 bags", + "UnitPrice": "18.0000", + "UnitsInStock": 39, + "UnitsOnOrder": 0, + "ReorderLevel": 10, + "Discontinued": false + }, + { + "ProductID": 2, + "ProductName": "Chang", + "SupplierID": 1, + "CategoryID": 1, + "QuantityPerUnit": "24 - 12 oz bottles", + "UnitPrice": "19.0000", + "UnitsInStock": 17, + "UnitsOnOrder": 40, + "ReorderLevel": 25, + "Discontinued": false + }, + { + "ProductID": 3, + "ProductName": "Aniseed Syrup", + "SupplierID": 1, + "CategoryID": 2, + "QuantityPerUnit": "12 - 550 ml bottles", + "UnitPrice": "10.0000", + "UnitsInStock": 13, + "UnitsOnOrder": 70, + "ReorderLevel": 25, + "Discontinued": false + }, + { + "ProductID": 4, + "ProductName": "Chef Anton's Cajun Seasoning", + "SupplierID": 2, + "CategoryID": 2, + "QuantityPerUnit": "48 - 6 oz jars", + "UnitPrice": "22.0000", + "UnitsInStock": 53, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": false + }, + { + "ProductID": 5, + "ProductName": "Chef Anton's Gumbo Mix", + "SupplierID": 2, + "CategoryID": 2, + "QuantityPerUnit": "36 boxes", + "UnitPrice": "21.3500", + "UnitsInStock": 0, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": true + } + ] +} \ No newline at end of file diff --git a/packages/databinding/steps/12/webapp/model/data.json b/packages/databinding/steps/12/webapp/model/data.json new file mode 100644 index 000000000..da676ea6b --- /dev/null +++ b/packages/databinding/steps/12/webapp/model/data.json @@ -0,0 +1,13 @@ +{ + "firstName": "Harry", + "lastName": "Hawk", + "enabled": true, + "address": { + "street": "Dietmar-Hopp-Allee 16", + "city": "Walldorf", + "zip": "69190", + "country": "Germany" + }, + "salesAmount": 12345.6789, + "currencyCode": "EUR" +} \ No newline at end of file diff --git a/packages/databinding/steps/12/webapp/view/App.view.xml b/packages/databinding/steps/12/webapp/view/App.view.xml new file mode 100644 index 000000000..338bd67e0 --- /dev/null +++ b/packages/databinding/steps/12/webapp/view/App.view.xml @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/databinding/steps/13/README.md b/packages/databinding/steps/13/README.md new file mode 100644 index 000000000..17870b04f --- /dev/null +++ b/packages/databinding/steps/13/README.md @@ -0,0 +1,212 @@ + +# Step 13: Element Binding + +Now, let's do something with that newly generated list. Typically, you use a list to allow selection of an item and then display the details of that item elsewhere. To accomplish this, we use a form with relatively bound controls and bind it to the selected entity via element binding. + +## Preview + +### A fourth panel with details for a selected product is displayed + +![A fourth panel with details for a selected product is displayed](assets/Tutorial_Data_Binding_Step_13_872d2ed.png "A fourth panel with details for a selected product is displayed") + +You can view this step live: [🔗 Live Preview of Step 13](https://ui5.github.io/tutorials/databinding/build/13/index-cdn.html). + +## Coding + +You can download the solution for this step here: [📥 Download step 13](https://ui5.github.io/tutorials/databinding/databinding-step-13.zip) (TS)[📥 Download step 13](https://ui5.github.io/tutorials/databinding/databinding-step-13-js.zip) (JS). + +### Folder structure for this step + +```text +webapp/ +├── controller/ +│ └── App.controller.ts/.js +├── i18n/ +│ ├── i18n.properties +│ └── i18n_de.properties +├── model/ +│ ├── Products.json +│ └── data.json +├── view/ +│ └── App.view.xml +├── Component.ts/.js +├── index.html +└── manifest.json +``` + +In the `App.view.xml` file, add a `press` event handler to the items in the list. Below the panel with the list, add a new panel with an `sap.m.SimpleForm`. To populate the form with data, we bind the entire panel to the path of the element you clicked in the list. + +## webapp/view/App.view.xml + +```xml +... + + ... + + + + + + ... + + + + + + + + + + +``` + +In the controller, add a new function `onItemSelected`, which binds the newly created panel to the correct item whenever it's pressed. + +## `webapp/controller/App.controller.ts/.js` + +```ts +// webapp/controller/App.controller.ts +import ResourceBundle from "sap/base/i18n/ResourceBundle"; +import { URLHelper } from "sap/m/library"; +import { ListItemBase$DetailPressEvent } from "sap/m/ListItemBase"; +import ObjectListItem from "sap/m/ObjectListItem"; +import Controller from "sap/ui/core/mvc/Controller"; +import ResourceModel from "sap/ui/model/resource/ResourceModel"; +import Currency from "sap/ui/model/type/Currency"; + +/** + * @namespace ui5.tutorial.databinding.controller + */ +export default class App extends Controller { + ... + + public onItemSelected(event: ListItemBase$DetailPressEvent): void { + const bindingPath: string = (event.getSource() as ObjectListItem).getBindingContext("products").getPath(); + this.byId("productDetailsPanel")?.bindElement({ path: bindingPath, model: "products" }); + } +} +``` + +```js +// webapp/controller/App.controller.js +sap.ui.define(["sap/ui/core/mvc/Controller", "sap/m/library", "sap/ui/model/type/Currency"], function (Controller, mobileLibrary, Currency) { + "use strict"; + + return Controller.extend("ui5.tutorial.databinding.controller.App", { + ... + + onItemSelected(event) { + const bindingPath = event.getSource().getBindingContext("products").getPath(); + this.byId("productDetailsPanel")?.bindElement({ path: bindingPath, model: "products" }); + } + }); +}); +``` + +Lastly, add the new texts to the `i18n.properties` and `i18n_de.properties` files. + +## webapp/i18n/i18n.properties + +```properties +... +# Screen titles +panel1HeaderText=Data Binding Basics +panel2HeaderText=Address Details +panel3HeaderText=Aggregation Binding +panel4HeaderText=Product Details + +... + +# Product Details +ProductID=Product ID +ProductName=Product Name +QuantityPerUnit=Quantity per Unit +UnitPrice=Unit Price +UnitsInStock=Number of Units in Stock +Discontinued=Discontinued + +``` + +## webapp/i18n/i18n\_de.properties + +```properties +# Screen titles +panel1HeaderText=Data Binding Grundlagen +panel2HeaderText=Adressdetails +panel3HeaderText=Aggregation Binding +panel4HeaderText=Produktdetails + +... + +# Product Details +ProductID=Produkt-ID +ProductName=Produktname +QuantityPerUnit=Menge pro Einheit +UnitPrice=Preis pro Einheit +UnitsInStock=Lagerbestand +Discontinued=Eingestellt +``` + +Now, you can click on an element in the list and view its details in the panel below. You can even edit these details, and the changes are directly reflected in the list because we use two-way binding. + +> :note: +> Element bindings can also be relative to their parent context. + +*** + +**Next:** [Step 14: Expression Binding](../14/README.md "An expression binding lets you display a calculated value on the screen, which is derived from values found in a model object.") + +**Previous:** [Step 12: Aggregation Binding Using Templates](../12/README.md "Aggregation binding, also known as list binding, lets a control bind to a list within the model data.") + +*** + +**Related Information** + +[Context Binding \(Element Binding\)](https://sdk.openui5.org/topic/91f05e8b6f4d1014b6dd926db0e91070.html "Context binding (or element binding) allows you to bind elements to a specific object in the model data, which will create a binding context and allow relative binding within the control and all of its children. This is especially helpful in list-detail scenarios.") diff --git a/packages/databinding/steps/13/assets/Tutorial_Data_Binding_Step_13_872d2ed.png b/packages/databinding/steps/13/assets/Tutorial_Data_Binding_Step_13_872d2ed.png new file mode 100644 index 000000000..33c1216c0 Binary files /dev/null and b/packages/databinding/steps/13/assets/Tutorial_Data_Binding_Step_13_872d2ed.png differ diff --git a/packages/databinding/steps/13/package.json b/packages/databinding/steps/13/package.json new file mode 100644 index 000000000..981def262 --- /dev/null +++ b/packages/databinding/steps/13/package.json @@ -0,0 +1,19 @@ +{ + "name": "ui5.tutorial.databinding.step13", + "private": true, + "version": "1.0.0", + "author": "SAP SE", + "description": "UI5 Demo App - Data Binding Tutorial", + "scripts": { + "start": "ui5 serve -o index.html", + "typecheck": "tsc --noEmit" + }, + "devDependencies": { + "@openui5/types": "^1.148.1", + "@ui5/cli": "^4.0.53", + "typescript": "^6.0.3", + "ui5-middleware-livereload": "^3.3.1", + "ui5-middleware-serveframework": "^3.8.1", + "ui5-tooling-transpile": "^3.11.0" + } +} diff --git a/packages/databinding/steps/13/tsconfig.json b/packages/databinding/steps/13/tsconfig.json new file mode 100644 index 000000000..0b7fa1ae7 --- /dev/null +++ b/packages/databinding/steps/13/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "es2023", + "types": [ + "@openui5/types" + ], + "skipLibCheck": true, + "allowJs": true, + "strictPropertyInitialization": false, + "rootDir": "./webapp", + "paths": { + "ui5/tutorial/databinding/*": [ + "./webapp/*" + ] + }, + "strict": false, + "strictNullChecks": false + }, + "exclude": [ + "./webapp/test/e2e/**/*" + ], + "include": [ + "./webapp/**/*" + ] +} diff --git a/packages/databinding/steps/13/ui5.yaml b/packages/databinding/steps/13/ui5.yaml new file mode 100644 index 000000000..af70be177 --- /dev/null +++ b/packages/databinding/steps/13/ui5.yaml @@ -0,0 +1,24 @@ +specVersion: "4.0" +metadata: + name: "ui5.tutorial.databinding" +type: application +framework: + name: OpenUI5 + version: "1.148.1" + libraries: + - name: sap.m + - name: sap.ui.core + - name: sap.ui.layout + - name: themelib_sap_horizon +builder: + customTasks: + - name: ui5-tooling-transpile-task + afterTask: replaceVersion +server: + customMiddleware: + - name: ui5-tooling-transpile-middleware + afterMiddleware: compression + - name: ui5-middleware-serveframework + afterMiddleware: compression + - name: ui5-middleware-livereload + afterMiddleware: compression diff --git a/packages/databinding/steps/13/webapp/Component.ts b/packages/databinding/steps/13/webapp/Component.ts new file mode 100644 index 000000000..3fc494bca --- /dev/null +++ b/packages/databinding/steps/13/webapp/Component.ts @@ -0,0 +1,11 @@ +import UIComponent from "sap/ui/core/UIComponent"; + +/** + * @namespace ui5.tutorial.databinding + */ +export default class Component extends UIComponent { + public static metadata = { + interfaces: ["sap.ui.core.IAsyncContentCreation"], + manifest: "json" + }; +} diff --git a/packages/databinding/steps/13/webapp/controller/App.controller.ts b/packages/databinding/steps/13/webapp/controller/App.controller.ts new file mode 100644 index 000000000..97c2a9cb8 --- /dev/null +++ b/packages/databinding/steps/13/webapp/controller/App.controller.ts @@ -0,0 +1,32 @@ +import ResourceBundle from "sap/base/i18n/ResourceBundle"; +import { URLHelper } from "sap/m/library"; +import { ListItemBase$DetailPressEvent } from "sap/m/ListItemBase"; +import ObjectListItem from "sap/m/ObjectListItem"; +import Controller from "sap/ui/core/mvc/Controller"; +import ResourceModel from "sap/ui/model/resource/ResourceModel"; +import Currency from "sap/ui/model/type/Currency"; + +/** + * @namespace ui5.tutorial.databinding.controller + */ +export default class App extends Controller { + public async formatMail(firstName: string, lastName: string): Promise { + const bundle: ResourceBundle = await (this.getView().getModel("i18n") as ResourceModel).getResourceBundle() as ResourceBundle; + + return URLHelper.normalizeEmail( + `${firstName}.${lastName}@example.com`, + bundle.getText("mailSubject", [firstName]), + bundle.getText("mailBody") + ); + } + + public formatStockValue(unitPrice: number, stockLevel: number, currencyCode: string): string { + const currency: Currency = new Currency(); + return currency.formatValue([unitPrice * stockLevel, currencyCode], "string"); + } + + public onItemSelected(event: ListItemBase$DetailPressEvent): void { + const bindingPath: string = (event.getSource() as ObjectListItem).getBindingContext("products").getPath(); + this.byId("productDetailsPanel")?.bindElement({ path: bindingPath, model: "products" }); + } +} diff --git a/packages/databinding/steps/13/webapp/i18n/i18n.properties b/packages/databinding/steps/13/webapp/i18n/i18n.properties new file mode 100644 index 000000000..da1880d42 --- /dev/null +++ b/packages/databinding/steps/13/webapp/i18n/i18n.properties @@ -0,0 +1,33 @@ +# App Descriptor +appTitle=Data Binding Tutorial +appDescription=A simple app that explains how to use data binding features of OpenUI5 + +# Field labels +firstName=First Name +lastName=Last Name +enabled=Enabled +address=Address +salesAmount=Sales Amount + +# Screen titles +panel1HeaderText=Data Binding Basics +panel2HeaderText=Address Details +panel3HeaderText=Aggregation Binding +panel4HeaderText=Product Details + +# Email +sendEmail=Send EMail +mailSubject=Hi {0}! +mailBody=How are you? + +# Product list +productListTitle=Product List +stockValue=Current Stock Value + +# Product Details +ProductID=Product ID +ProductName=Product Name +QuantityPerUnit=Quantity per Unit +UnitPrice=Unit Price +UnitsInStock=Number of Units in Stock +Discontinued=Discontinued diff --git a/packages/databinding/steps/13/webapp/i18n/i18n_de.properties b/packages/databinding/steps/13/webapp/i18n/i18n_de.properties new file mode 100644 index 000000000..73b5af5d6 --- /dev/null +++ b/packages/databinding/steps/13/webapp/i18n/i18n_de.properties @@ -0,0 +1,33 @@ +# App Descriptor +appTitle=Data Binding Tutorial +appDescription=Eine einfache Anwendung zur Erkl\u00e4rung der UI5 Data Binding Funktionen + +# Field labels +firstName=Vorname +lastName=Nachname +enabled=Aktiviert +address=Adresse +salesAmount=Verk\u00e4ufe bis zum heutigen Datum + +# Screen titles +panel1HeaderText=Data Binding Grundlagen +panel2HeaderText=Adressdetails +panel3HeaderText=Aggregation Binding +panel4HeaderText=Produktdetails + +# Email +sendEmail=E-Mail versenden +mailSubject=Hallo {0}! +mailBody=Wie geht es dir? + +# Product list +productListTitle=Artikelliste +stockValue=Lagerbestand Wert + +# Product Details +ProductID=Produkt-ID +ProductName=Produktname +QuantityPerUnit=Menge pro Einheit +UnitPrice=Preis pro Einheit +UnitsInStock=Lagerbestand +Discontinued=Eingestellt diff --git a/packages/databinding/steps/13/webapp/index-cdn.html b/packages/databinding/steps/13/webapp/index-cdn.html new file mode 100644 index 000000000..e2b2e4833 --- /dev/null +++ b/packages/databinding/steps/13/webapp/index-cdn.html @@ -0,0 +1,22 @@ + + + + + + Data Binding Tutorial + + + +
+ + diff --git a/packages/databinding/steps/13/webapp/index.html b/packages/databinding/steps/13/webapp/index.html new file mode 100644 index 000000000..42edae4b3 --- /dev/null +++ b/packages/databinding/steps/13/webapp/index.html @@ -0,0 +1,22 @@ + + + + + + Data Binding Tutorial + + + +
+ + diff --git a/packages/databinding/steps/13/webapp/manifest.json b/packages/databinding/steps/13/webapp/manifest.json new file mode 100644 index 000000000..3852b19e3 --- /dev/null +++ b/packages/databinding/steps/13/webapp/manifest.json @@ -0,0 +1,70 @@ +{ + "_version": "2.8.0", + "sap.app": { + "id": "ui5.tutorial.databinding", + "type": "application", + "applicationVersion": { + "version": "1.0.0" + }, + "title": "{{appTitle}}", + "description": "{{appDescription}}", + "i18n": { + "bundleName": "ui5.tutorial.databinding.i18n.i18n", + "supportedLocales": [ + "", + "de" + ], + "fallbackLocale": "" + } + }, + "sap.ui": { + "technology": "UI5", + "deviceTypes": { + "desktop": true, + "tablet": true, + "phone": true + } + }, + "sap.ui5": { + "handleValidation": true, + "dependencies": { + "minUI5Version": "1.148", + "libs": { + "sap.m": {}, + "sap.ui.core": {}, + "sap.ui.layout": {} + } + }, + "contentDensities": { + "compact": true, + "cozy": true + }, + "models": { + "": { + "type": "sap.ui.model.json.JSONModel", + "uri": "./model/data.json" + }, + "products": { + "type": "sap.ui.model.json.JSONModel", + "uri": "./model/Products.json" + }, + "i18n": { + "type": "sap.ui.model.resource.ResourceModel", + "settings": { + "bundleName": "ui5.tutorial.databinding.i18n.i18n", + "supportedLocales": [ + "", + "de" + ], + "fallbackLocale": "", + "async": true + } + } + }, + "rootView": { + "viewName": "ui5.tutorial.databinding.view.App", + "type": "XML", + "id": "app" + } + } +} diff --git a/packages/databinding/steps/13/webapp/model/Products.json b/packages/databinding/steps/13/webapp/model/Products.json new file mode 100644 index 000000000..2b47ed742 --- /dev/null +++ b/packages/databinding/steps/13/webapp/model/Products.json @@ -0,0 +1,64 @@ +{ + "Products": [ + { + "ProductID": 1, + "ProductName": "Chai", + "SupplierID": 1, + "CategoryID": 1, + "QuantityPerUnit": "10 boxes x 20 bags", + "UnitPrice": "18.0000", + "UnitsInStock": 39, + "UnitsOnOrder": 0, + "ReorderLevel": 10, + "Discontinued": false + }, + { + "ProductID": 2, + "ProductName": "Chang", + "SupplierID": 1, + "CategoryID": 1, + "QuantityPerUnit": "24 - 12 oz bottles", + "UnitPrice": "19.0000", + "UnitsInStock": 17, + "UnitsOnOrder": 40, + "ReorderLevel": 25, + "Discontinued": false + }, + { + "ProductID": 3, + "ProductName": "Aniseed Syrup", + "SupplierID": 1, + "CategoryID": 2, + "QuantityPerUnit": "12 - 550 ml bottles", + "UnitPrice": "10.0000", + "UnitsInStock": 13, + "UnitsOnOrder": 70, + "ReorderLevel": 25, + "Discontinued": false + }, + { + "ProductID": 4, + "ProductName": "Chef Anton's Cajun Seasoning", + "SupplierID": 2, + "CategoryID": 2, + "QuantityPerUnit": "48 - 6 oz jars", + "UnitPrice": "22.0000", + "UnitsInStock": 53, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": false + }, + { + "ProductID": 5, + "ProductName": "Chef Anton's Gumbo Mix", + "SupplierID": 2, + "CategoryID": 2, + "QuantityPerUnit": "36 boxes", + "UnitPrice": "21.3500", + "UnitsInStock": 0, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": true + } + ] +} \ No newline at end of file diff --git a/packages/databinding/steps/13/webapp/model/data.json b/packages/databinding/steps/13/webapp/model/data.json new file mode 100644 index 000000000..da676ea6b --- /dev/null +++ b/packages/databinding/steps/13/webapp/model/data.json @@ -0,0 +1,13 @@ +{ + "firstName": "Harry", + "lastName": "Hawk", + "enabled": true, + "address": { + "street": "Dietmar-Hopp-Allee 16", + "city": "Walldorf", + "zip": "69190", + "country": "Germany" + }, + "salesAmount": 12345.6789, + "currencyCode": "EUR" +} \ No newline at end of file diff --git a/packages/databinding/steps/13/webapp/view/App.view.xml b/packages/databinding/steps/13/webapp/view/App.view.xml new file mode 100644 index 000000000..d3296cafb --- /dev/null +++ b/packages/databinding/steps/13/webapp/view/App.view.xml @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/databinding/steps/14/README.md b/packages/databinding/steps/14/README.md new file mode 100644 index 000000000..8c2cc0fae --- /dev/null +++ b/packages/databinding/steps/14/README.md @@ -0,0 +1,149 @@ + +# Step 14: Expression Binding + +An expression binding lets you display a calculated value on the screen, which is derived from values found in a model object. This feature allows you to insert simple formatting or calculations directly into the data binding string. In this example, we're changing the color of the price depending on whether it's above or below a certain threshold. The threshold value is stored in the JSON model. + +## Preview + +### Prices are color-coded depending on a selected threshold + +![Prices are color-coded depending on a selected threshold](assets/Tutorial_Data_Binding_Step_14_b9fb758.png "Prices are color-coded depending on a selected threshold") + +You can view this step live: [🔗 Live Preview of Step 14](https://ui5.github.io/tutorials/databinding/build/14/index-cdn.html). + +## Coding + +You can download the solution for this step here: [📥 Download step 14](https://ui5.github.io/tutorials/databinding/databinding-step-14.zip) (TS)[📥 Download step 14](https://ui5.github.io/tutorials/databinding/databinding-step-14-js.zip) (JS). + +### Folder structure for this step + +```text +webapp/ +├── controller/ +│ └── App.controller.ts/.js +├── i18n/ +│ ├── i18n.properties +│ └── i18n_de.properties +├── model/ +│ ├── Products.json +│ └── data.json +├── view/ +│ └── App.view.xml +├── Component.ts/.js +├── index.html +└── manifest.json +``` + +Add a new property called `priceThreshold` with a value of 20 to the `data.json` file. + +## webapp/model/data.json + +```json +{ + "firstName": "Harry", + "lastName": "Hawk", + "enabled": true, + "address": { + "street": "Dietmar-Hopp-Allee 16", + "city": "Walldorf", + "zip": "69190", + "country": "Germany" + }, + "salesAmount": 12345.6789, + "priceThreshold": 20, + "currencyCode": "EUR" +} +``` + +In the `App.view.xml` file, add a new `numberState` property to the `ObjectListItem` element within the `List`. The value of this property is an expression that gets evaluated for each item. The expression compares each invoice value against the price threshold and returns a number state based on the result. + +## webapp/view/App.view.xml + +```xml +... + + ... + + + + + + ... + + + + + + ... + +... +``` + +By binding an expression to the `numberState` property, the error status \(color\) of the price field changes depending on the invoice value. + +Consider the following two expressions: + +- `numberState="{= ${products>UnitPrice} > ${/priceThreshold} ? 'Error' : 'Success' }"` + +- `numberState="{= ${products>UnitPrice} <= ${/priceThreshold} ? 'Success' : 'Error' }"` + +Can you see why one of these expressions works, and the other doesn't? + +Logically, both expressions are identical. However, the first one works, and the second one doesn't: it only produces an empty screen and an "Invalid XML" message in the browser's console. So, what's happening here? + +To understand the situation, you need to know how XML files are parsed. + +When an XML file is parsed, certain characters have a special \(high-priority\) meaning to the XML parser. When these characters are encountered, they're **always** interpreted to be part of the XML definition itself, and not as part of any other content within the XML document. + +The XML parser always interprets one of these high-priority characters \(in this case, a less-than \(`<`\) character\) as the start of a new XML tag. This happens regardless of any other meaning that character might have within the context of the expression. This is known as a **syntax collision**. + +In our case, the collision occurs between the syntax of XML and the syntax of the JavaScript-like expression language used by OpenUI5. + +Therefore, this statement fails because the less-than character is interpreted as the start of an XML tag: `numberState="{= ${products>UnitPrice} <= ${/priceThreshold} ? 'Success' : 'Error' }"` + +You can avoid this problem in one of two ways: + +- Reverse the logic of the condition \(use "greater than or equal to" instead of "less than"\) + +- Use the escaped value for the less-than character: `numberState="{= ${products>UnitPrice} <= ${/priceThreshold} ? 'Success' : 'Error' }"` + +Since using an escaped character is not so easy to read, we recommend reversing the logic of the condition and using a greater-than character instead. + +The ampersand \(`&`\) character also has a high-priority meaning to the XML parser. This character always means "The start of an escaped character". So, if you'd like to use the Boolean `AND` operator \(`&&`\) in a condition, you must escape both ampersand characters \(`&&`\). + +*** + +**Next:** [Step 15: Aggregation Binding Using a Factory Function](../15/README.md "Instead of using a single hard-coded template control, we now opt for a factory function to generate different controls based on the data received at runtime.") + +**Previous:** [Step 13: Element Binding](../13/README.md "Now, let's do something with that newly generated list.") + +*** + +**Related Information** + +[Expression Binding](https://sdk.openui5.org/topic/daf6852a04b44d118963968a1239d2c0.html "Expression binding is an enhancement of the OpenUI5 binding syntax, which allows for providing expressions instead of custom formatter functions.") diff --git a/packages/databinding/steps/14/assets/Tutorial_Data_Binding_Step_14_b9fb758.png b/packages/databinding/steps/14/assets/Tutorial_Data_Binding_Step_14_b9fb758.png new file mode 100644 index 000000000..f07dea3fa Binary files /dev/null and b/packages/databinding/steps/14/assets/Tutorial_Data_Binding_Step_14_b9fb758.png differ diff --git a/packages/databinding/steps/14/package.json b/packages/databinding/steps/14/package.json new file mode 100644 index 000000000..90998b0be --- /dev/null +++ b/packages/databinding/steps/14/package.json @@ -0,0 +1,19 @@ +{ + "name": "ui5.tutorial.databinding.step14", + "private": true, + "version": "1.0.0", + "author": "SAP SE", + "description": "UI5 Demo App - Data Binding Tutorial", + "scripts": { + "start": "ui5 serve -o index.html", + "typecheck": "tsc --noEmit" + }, + "devDependencies": { + "@openui5/types": "^1.148.1", + "@ui5/cli": "^4.0.53", + "typescript": "^6.0.3", + "ui5-middleware-livereload": "^3.3.1", + "ui5-middleware-serveframework": "^3.8.1", + "ui5-tooling-transpile": "^3.11.0" + } +} diff --git a/packages/databinding/steps/14/tsconfig.json b/packages/databinding/steps/14/tsconfig.json new file mode 100644 index 000000000..0b7fa1ae7 --- /dev/null +++ b/packages/databinding/steps/14/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "es2023", + "types": [ + "@openui5/types" + ], + "skipLibCheck": true, + "allowJs": true, + "strictPropertyInitialization": false, + "rootDir": "./webapp", + "paths": { + "ui5/tutorial/databinding/*": [ + "./webapp/*" + ] + }, + "strict": false, + "strictNullChecks": false + }, + "exclude": [ + "./webapp/test/e2e/**/*" + ], + "include": [ + "./webapp/**/*" + ] +} diff --git a/packages/databinding/steps/14/ui5.yaml b/packages/databinding/steps/14/ui5.yaml new file mode 100644 index 000000000..af70be177 --- /dev/null +++ b/packages/databinding/steps/14/ui5.yaml @@ -0,0 +1,24 @@ +specVersion: "4.0" +metadata: + name: "ui5.tutorial.databinding" +type: application +framework: + name: OpenUI5 + version: "1.148.1" + libraries: + - name: sap.m + - name: sap.ui.core + - name: sap.ui.layout + - name: themelib_sap_horizon +builder: + customTasks: + - name: ui5-tooling-transpile-task + afterTask: replaceVersion +server: + customMiddleware: + - name: ui5-tooling-transpile-middleware + afterMiddleware: compression + - name: ui5-middleware-serveframework + afterMiddleware: compression + - name: ui5-middleware-livereload + afterMiddleware: compression diff --git a/packages/databinding/steps/14/webapp/Component.ts b/packages/databinding/steps/14/webapp/Component.ts new file mode 100644 index 000000000..3fc494bca --- /dev/null +++ b/packages/databinding/steps/14/webapp/Component.ts @@ -0,0 +1,11 @@ +import UIComponent from "sap/ui/core/UIComponent"; + +/** + * @namespace ui5.tutorial.databinding + */ +export default class Component extends UIComponent { + public static metadata = { + interfaces: ["sap.ui.core.IAsyncContentCreation"], + manifest: "json" + }; +} diff --git a/packages/databinding/steps/14/webapp/controller/App.controller.ts b/packages/databinding/steps/14/webapp/controller/App.controller.ts new file mode 100644 index 000000000..97c2a9cb8 --- /dev/null +++ b/packages/databinding/steps/14/webapp/controller/App.controller.ts @@ -0,0 +1,32 @@ +import ResourceBundle from "sap/base/i18n/ResourceBundle"; +import { URLHelper } from "sap/m/library"; +import { ListItemBase$DetailPressEvent } from "sap/m/ListItemBase"; +import ObjectListItem from "sap/m/ObjectListItem"; +import Controller from "sap/ui/core/mvc/Controller"; +import ResourceModel from "sap/ui/model/resource/ResourceModel"; +import Currency from "sap/ui/model/type/Currency"; + +/** + * @namespace ui5.tutorial.databinding.controller + */ +export default class App extends Controller { + public async formatMail(firstName: string, lastName: string): Promise { + const bundle: ResourceBundle = await (this.getView().getModel("i18n") as ResourceModel).getResourceBundle() as ResourceBundle; + + return URLHelper.normalizeEmail( + `${firstName}.${lastName}@example.com`, + bundle.getText("mailSubject", [firstName]), + bundle.getText("mailBody") + ); + } + + public formatStockValue(unitPrice: number, stockLevel: number, currencyCode: string): string { + const currency: Currency = new Currency(); + return currency.formatValue([unitPrice * stockLevel, currencyCode], "string"); + } + + public onItemSelected(event: ListItemBase$DetailPressEvent): void { + const bindingPath: string = (event.getSource() as ObjectListItem).getBindingContext("products").getPath(); + this.byId("productDetailsPanel")?.bindElement({ path: bindingPath, model: "products" }); + } +} diff --git a/packages/databinding/steps/14/webapp/i18n/i18n.properties b/packages/databinding/steps/14/webapp/i18n/i18n.properties new file mode 100644 index 000000000..da1880d42 --- /dev/null +++ b/packages/databinding/steps/14/webapp/i18n/i18n.properties @@ -0,0 +1,33 @@ +# App Descriptor +appTitle=Data Binding Tutorial +appDescription=A simple app that explains how to use data binding features of OpenUI5 + +# Field labels +firstName=First Name +lastName=Last Name +enabled=Enabled +address=Address +salesAmount=Sales Amount + +# Screen titles +panel1HeaderText=Data Binding Basics +panel2HeaderText=Address Details +panel3HeaderText=Aggregation Binding +panel4HeaderText=Product Details + +# Email +sendEmail=Send EMail +mailSubject=Hi {0}! +mailBody=How are you? + +# Product list +productListTitle=Product List +stockValue=Current Stock Value + +# Product Details +ProductID=Product ID +ProductName=Product Name +QuantityPerUnit=Quantity per Unit +UnitPrice=Unit Price +UnitsInStock=Number of Units in Stock +Discontinued=Discontinued diff --git a/packages/databinding/steps/14/webapp/i18n/i18n_de.properties b/packages/databinding/steps/14/webapp/i18n/i18n_de.properties new file mode 100644 index 000000000..73b5af5d6 --- /dev/null +++ b/packages/databinding/steps/14/webapp/i18n/i18n_de.properties @@ -0,0 +1,33 @@ +# App Descriptor +appTitle=Data Binding Tutorial +appDescription=Eine einfache Anwendung zur Erkl\u00e4rung der UI5 Data Binding Funktionen + +# Field labels +firstName=Vorname +lastName=Nachname +enabled=Aktiviert +address=Adresse +salesAmount=Verk\u00e4ufe bis zum heutigen Datum + +# Screen titles +panel1HeaderText=Data Binding Grundlagen +panel2HeaderText=Adressdetails +panel3HeaderText=Aggregation Binding +panel4HeaderText=Produktdetails + +# Email +sendEmail=E-Mail versenden +mailSubject=Hallo {0}! +mailBody=Wie geht es dir? + +# Product list +productListTitle=Artikelliste +stockValue=Lagerbestand Wert + +# Product Details +ProductID=Produkt-ID +ProductName=Produktname +QuantityPerUnit=Menge pro Einheit +UnitPrice=Preis pro Einheit +UnitsInStock=Lagerbestand +Discontinued=Eingestellt diff --git a/packages/databinding/steps/14/webapp/index-cdn.html b/packages/databinding/steps/14/webapp/index-cdn.html new file mode 100644 index 000000000..e2b2e4833 --- /dev/null +++ b/packages/databinding/steps/14/webapp/index-cdn.html @@ -0,0 +1,22 @@ + + + + + + Data Binding Tutorial + + + +
+ + diff --git a/packages/databinding/steps/14/webapp/index.html b/packages/databinding/steps/14/webapp/index.html new file mode 100644 index 000000000..42edae4b3 --- /dev/null +++ b/packages/databinding/steps/14/webapp/index.html @@ -0,0 +1,22 @@ + + + + + + Data Binding Tutorial + + + +
+ + diff --git a/packages/databinding/steps/14/webapp/manifest.json b/packages/databinding/steps/14/webapp/manifest.json new file mode 100644 index 000000000..3852b19e3 --- /dev/null +++ b/packages/databinding/steps/14/webapp/manifest.json @@ -0,0 +1,70 @@ +{ + "_version": "2.8.0", + "sap.app": { + "id": "ui5.tutorial.databinding", + "type": "application", + "applicationVersion": { + "version": "1.0.0" + }, + "title": "{{appTitle}}", + "description": "{{appDescription}}", + "i18n": { + "bundleName": "ui5.tutorial.databinding.i18n.i18n", + "supportedLocales": [ + "", + "de" + ], + "fallbackLocale": "" + } + }, + "sap.ui": { + "technology": "UI5", + "deviceTypes": { + "desktop": true, + "tablet": true, + "phone": true + } + }, + "sap.ui5": { + "handleValidation": true, + "dependencies": { + "minUI5Version": "1.148", + "libs": { + "sap.m": {}, + "sap.ui.core": {}, + "sap.ui.layout": {} + } + }, + "contentDensities": { + "compact": true, + "cozy": true + }, + "models": { + "": { + "type": "sap.ui.model.json.JSONModel", + "uri": "./model/data.json" + }, + "products": { + "type": "sap.ui.model.json.JSONModel", + "uri": "./model/Products.json" + }, + "i18n": { + "type": "sap.ui.model.resource.ResourceModel", + "settings": { + "bundleName": "ui5.tutorial.databinding.i18n.i18n", + "supportedLocales": [ + "", + "de" + ], + "fallbackLocale": "", + "async": true + } + } + }, + "rootView": { + "viewName": "ui5.tutorial.databinding.view.App", + "type": "XML", + "id": "app" + } + } +} diff --git a/packages/databinding/steps/14/webapp/model/Products.json b/packages/databinding/steps/14/webapp/model/Products.json new file mode 100644 index 000000000..2b47ed742 --- /dev/null +++ b/packages/databinding/steps/14/webapp/model/Products.json @@ -0,0 +1,64 @@ +{ + "Products": [ + { + "ProductID": 1, + "ProductName": "Chai", + "SupplierID": 1, + "CategoryID": 1, + "QuantityPerUnit": "10 boxes x 20 bags", + "UnitPrice": "18.0000", + "UnitsInStock": 39, + "UnitsOnOrder": 0, + "ReorderLevel": 10, + "Discontinued": false + }, + { + "ProductID": 2, + "ProductName": "Chang", + "SupplierID": 1, + "CategoryID": 1, + "QuantityPerUnit": "24 - 12 oz bottles", + "UnitPrice": "19.0000", + "UnitsInStock": 17, + "UnitsOnOrder": 40, + "ReorderLevel": 25, + "Discontinued": false + }, + { + "ProductID": 3, + "ProductName": "Aniseed Syrup", + "SupplierID": 1, + "CategoryID": 2, + "QuantityPerUnit": "12 - 550 ml bottles", + "UnitPrice": "10.0000", + "UnitsInStock": 13, + "UnitsOnOrder": 70, + "ReorderLevel": 25, + "Discontinued": false + }, + { + "ProductID": 4, + "ProductName": "Chef Anton's Cajun Seasoning", + "SupplierID": 2, + "CategoryID": 2, + "QuantityPerUnit": "48 - 6 oz jars", + "UnitPrice": "22.0000", + "UnitsInStock": 53, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": false + }, + { + "ProductID": 5, + "ProductName": "Chef Anton's Gumbo Mix", + "SupplierID": 2, + "CategoryID": 2, + "QuantityPerUnit": "36 boxes", + "UnitPrice": "21.3500", + "UnitsInStock": 0, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": true + } + ] +} \ No newline at end of file diff --git a/packages/databinding/steps/14/webapp/model/data.json b/packages/databinding/steps/14/webapp/model/data.json new file mode 100644 index 000000000..9682b2cd2 --- /dev/null +++ b/packages/databinding/steps/14/webapp/model/data.json @@ -0,0 +1,14 @@ +{ + "firstName": "Harry", + "lastName": "Hawk", + "enabled": true, + "address": { + "street": "Dietmar-Hopp-Allee 16", + "city": "Walldorf", + "zip": "69190", + "country": "Germany" + }, + "salesAmount": 12345.6789, + "priceThreshold": 20, + "currencyCode": "EUR" +} \ No newline at end of file diff --git a/packages/databinding/steps/14/webapp/view/App.view.xml b/packages/databinding/steps/14/webapp/view/App.view.xml new file mode 100644 index 000000000..664c62380 --- /dev/null +++ b/packages/databinding/steps/14/webapp/view/App.view.xml @@ -0,0 +1,151 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/databinding/steps/15/README.md b/packages/databinding/steps/15/README.md new file mode 100644 index 000000000..12a36f2d2 --- /dev/null +++ b/packages/databinding/steps/15/README.md @@ -0,0 +1,249 @@ + +# Step 15: Aggregation Binding Using a Factory Function + +Instead of using a single hard-coded template control, we now opt for a factory function to generate different controls based on the data received at runtime. This approach is much more flexible and allows for the display of complex or heterogeneous data. + +## Preview + +### A different type of list item is displayed for a discontinued product + +![A different type of list item is displayed for a discontinued product](assets/Tutorial_Data_Binding_Step_15_db27ba8.png "A different type of list item is displayed for a discontinued product") + +You can view this step live: [🔗 Live Preview of Step 15](https://ui5.github.io/tutorials/databinding/build/15/index-cdn.html). + +## Coding + +You can download the solution for this step here: [📥 Download step 15](https://ui5.github.io/tutorials/databinding/databinding-step-15.zip) (TS)[📥 Download step 15](https://ui5.github.io/tutorials/databinding/databinding-step-15-js.zip) (JS). + +### Folder structure for this step + +```text +webapp/ +├── controller/ +│ └── App.controller.ts/.js +├── i18n/ +│ ├── i18n.properties +│ └── i18n_de.properties +├── model/ +│ ├── Products.json +│ └── data.json +├── view/ +│ ├── App.view.xml +│ ├── ProductExtended.fragment.xml +│ └── ProductSimple.fragment.xml +├── Component.ts/.js +├── index.html +└── manifest.json +``` + +Create a `ProductSimple.fragment.xml` file in the `view` folder. Here, define an `sap.m.StandardListItem` that is used when the stock level is zero and the product is discontinued. In this simple use case, you only need to define a warning icon and a "Product Discontinued" message in the `info` property. + +## webapp/view/ProductSimple.fragment.xml \(New\) + +```xml + + + + +``` + +Create a new `ProductExtended.fragment.xml` file in the `view` folder. In this extended use case, you create an `ObjectListItem` to display more product details. The properties are bound to the fields of the current data binding context. This allows the use of types, formatters, and all handlers defined in the assigned controller. However, you can't define more complex logic declaratively in XML. Therefore, we add a single `sap.m.ObjectAttribute` in a factory function of the controller using JavaScript, which displays an "Out of Stock" message when the stock level is zero. + +## webapp/view/ProductExtended.fragment.xml \(New\) + +```xml + + + + +``` + +In the `App.view.xml` file, add an XML namespace for `sap.ui.core`. Then, remove the `items` aggregation from the `sap.m.List` XML element. Add an `id` attribute to the `sap.m.List` and include the factory function in the items' binding definition. Lastly, add the two newly created fragments as dependents to the `sap.m.List`. + +## webapp/view/App.view.xml + +```xml + + ... + + + + + + + + + ... + +``` + +The `sap.m.List` that previously held the product list is now just a named, but otherwise empty placeholder. Without a factory function to populate it, this `List` would always remain empty. As the fragments are declared as dependents, they also inherit the controller of the view. This means that the `onItemSelect` function of the `App.controller.ts/.js` can still be used in the `ProductExtended.fragment.xml`. + +In the `App.controller.ts/.js` file, add a new import for the `sap.m.ObjectAttribute` class and create a new function called `productListFactory`. This factory function returns a control for the associated binding context, similar to the XML templates we've defined in the previous steps. The controls returned by this factory function must suit the items aggregation of the `sap.m.List` object. In this case, it returns either an `sap.m.StandardListItem` or an `sap.m.ObjectListItem` based on the data stored in the context of the item to be created. + +## `webapp/controller/App.controller.ts/.js` + +```ts +// webapp/controller/App.controller.ts +import ResourceBundle from "sap/base/i18n/ResourceBundle"; +import { URLHelper } from "sap/m/library"; +import { ListItemBase$DetailPressEvent } from "sap/m/ListItemBase"; +import ObjectAttribute from "sap/m/ObjectAttribute"; +import ObjectListItem from "sap/m/ObjectListItem"; +import StandardListItem from "sap/m/StandardListItem"; +import Control from "sap/ui/core/Control"; +import Controller from "sap/ui/core/mvc/Controller"; +import Context from "sap/ui/model/Context"; +import ResourceModel from "sap/ui/model/resource/ResourceModel"; +import Currency from "sap/ui/model/type/Currency"; + +/** + * @namespace ui5.tutorial.databinding.controller + */ +export default class App extends Controller { + ... + + public productListFactory(id: string, context: Context): Control { + let uiControl; + // Decide based on the data which dependent to clone + if (context.getProperty("UnitsInStock") === 0 && context.getProperty("Discontinued")) { + // The item is discontinued, so use a StandardListItem + uiControl = (this.byId("productSimple") as StandardListItem).clone(id); + } else { + // The item is available, so we will create an ObjectListItem + uiControl = (this.byId("productExtended") as ObjectListItem).clone(id); + + // The item is temporarily out of stock, so we will add a status + if (context.getProperty("UnitsInStock") < 1) { + uiControl.addAttribute(new ObjectAttribute({ + text : { + path: "i18n>outOfStock" + } + })); + } + } + + return uiControl; + } +} +``` + +```js +// webapp/controller/App.controller.js +sap.ui.define(["sap/ui/core/mvc/Controller", "sap/m/library", "sap/m/ObjectAttribute", "sap/ui/model/type/Currency"], function (Controller, mobileLibrary, ObjectAttribute, Currency) { + "use strict"; + + return Controller.extend("ui5.tutorial.databinding.controller.App", { + ... + + productListFactory(id, context) { + let uiControl; + // Decide based on the data which dependent to clone + if (context.getProperty("UnitsInStock") === 0 && context.getProperty("Discontinued")) { + // The item is discontinued, so use a StandardListItem + uiControl = this.byId("productSimple").clone(id); + } else { + // The item is available, so we will create an ObjectListItem + uiControl = this.byId("productExtended").clone(id); + + // The item is temporarily out of stock, so we will add a status + if (context.getProperty("UnitsInStock") < 1) { + uiControl.addAttribute(new ObjectAttribute({ + text : { + path: "i18n>outOfStock" + } + })); + } + } + + return uiControl; + } + }); +}); +``` + +The function decides which type of control to return by checking the current stock level and whether the product is discontinued. For both options, it loads and clones the respective XML fragment, which allows the view logic to be defined dynamically. If the stock level is zero and the product is discontinued, the `ProductSimple` XML fragment is used. Otherwise, the `ProductExtended` XML fragment is used. + +For each item of the list, the corresponding control is cloned. This method creates a fresh copy of a control that can be bound to the context of the list item. Remember, in a factory function you are responsible for the life cycle of the control you create. + +If the product is not discontinued but the stock level is zero, we're temporarily out of stock. In this case, a single `sap.m.ObjectAttribute` is added to the cloned control. The "Out of Stock" message is bound to the `sap.m.ObjectAttribute`'s `text` property using JavaScript. Like declarative definitions in the XML view or fragments, you can bind properties using data binding syntax. Here, the text is bound to an entry in the resource bundle. Since the `sap.m.ObjectAttribute` is a child of the list item, it has access to all assigned models and the current binding context. + +Finally, the function returns the control that is then displayed inside the list. + +Lastly, add the new texts to the `i18n.properties` and `i18n_de.properties` files. + +## webapp/i18n/i18n.properties + +```properties +... +# Product Details +... +outOfStock=Out of Stock +``` + +## webapp/i18n/i18n\_de.properties + +```properties +... +# Product Details +... +outOfStock=Nicht vorr\u00e4tig +``` + +Congratulations! You've completed the Data Binding tutorial. + +*** + +**Previous:** [Step 14: Expression Binding](../14/README.md "An expression binding lets you display a calculated value on the screen, which is derived from values found in a model object.") + +*** + +**Related Information** + +[List Binding \(Aggregation Binding\)](https://sdk.openui5.org/topic/91f057786f4d1014b6dd926db0e91070.html "List binding (or aggregation binding) is used to automatically create child controls according to model data.") + +[XML Fragments](https://sdk.openui5.org/topic/2c677b574ea2486a8d5f5414d15e21c5.html "XML fragments are similar to XML view, but have no tag as root element. Instead, there is an OpenUI5 control.") + +[Using Factory Functions](https://sdk.openui5.org/topic/335848ac1174435c901baaa55f6d7819.html) diff --git a/packages/databinding/steps/15/assets/Tutorial_Data_Binding_Step_15_db27ba8.png b/packages/databinding/steps/15/assets/Tutorial_Data_Binding_Step_15_db27ba8.png new file mode 100644 index 000000000..c19c3e3de Binary files /dev/null and b/packages/databinding/steps/15/assets/Tutorial_Data_Binding_Step_15_db27ba8.png differ diff --git a/packages/databinding/steps/15/package.json b/packages/databinding/steps/15/package.json new file mode 100644 index 000000000..113c67bab --- /dev/null +++ b/packages/databinding/steps/15/package.json @@ -0,0 +1,19 @@ +{ + "name": "ui5.tutorial.databinding.step15", + "private": true, + "version": "1.0.0", + "author": "SAP SE", + "description": "UI5 Demo App - Data Binding Tutorial", + "scripts": { + "start": "ui5 serve -o index.html", + "typecheck": "tsc --noEmit" + }, + "devDependencies": { + "@openui5/types": "^1.148.1", + "@ui5/cli": "^4.0.53", + "typescript": "^6.0.3", + "ui5-middleware-livereload": "^3.3.1", + "ui5-middleware-serveframework": "^3.8.1", + "ui5-tooling-transpile": "^3.11.0" + } +} diff --git a/packages/databinding/steps/15/tsconfig.json b/packages/databinding/steps/15/tsconfig.json new file mode 100644 index 000000000..0b7fa1ae7 --- /dev/null +++ b/packages/databinding/steps/15/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "es2023", + "types": [ + "@openui5/types" + ], + "skipLibCheck": true, + "allowJs": true, + "strictPropertyInitialization": false, + "rootDir": "./webapp", + "paths": { + "ui5/tutorial/databinding/*": [ + "./webapp/*" + ] + }, + "strict": false, + "strictNullChecks": false + }, + "exclude": [ + "./webapp/test/e2e/**/*" + ], + "include": [ + "./webapp/**/*" + ] +} diff --git a/packages/databinding/steps/15/ui5.yaml b/packages/databinding/steps/15/ui5.yaml new file mode 100644 index 000000000..af70be177 --- /dev/null +++ b/packages/databinding/steps/15/ui5.yaml @@ -0,0 +1,24 @@ +specVersion: "4.0" +metadata: + name: "ui5.tutorial.databinding" +type: application +framework: + name: OpenUI5 + version: "1.148.1" + libraries: + - name: sap.m + - name: sap.ui.core + - name: sap.ui.layout + - name: themelib_sap_horizon +builder: + customTasks: + - name: ui5-tooling-transpile-task + afterTask: replaceVersion +server: + customMiddleware: + - name: ui5-tooling-transpile-middleware + afterMiddleware: compression + - name: ui5-middleware-serveframework + afterMiddleware: compression + - name: ui5-middleware-livereload + afterMiddleware: compression diff --git a/packages/databinding/steps/15/webapp/Component.ts b/packages/databinding/steps/15/webapp/Component.ts new file mode 100644 index 000000000..3fc494bca --- /dev/null +++ b/packages/databinding/steps/15/webapp/Component.ts @@ -0,0 +1,11 @@ +import UIComponent from "sap/ui/core/UIComponent"; + +/** + * @namespace ui5.tutorial.databinding + */ +export default class Component extends UIComponent { + public static metadata = { + interfaces: ["sap.ui.core.IAsyncContentCreation"], + manifest: "json" + }; +} diff --git a/packages/databinding/steps/15/webapp/controller/App.controller.ts b/packages/databinding/steps/15/webapp/controller/App.controller.ts new file mode 100644 index 000000000..c5ed75eee --- /dev/null +++ b/packages/databinding/steps/15/webapp/controller/App.controller.ts @@ -0,0 +1,59 @@ +import ResourceBundle from "sap/base/i18n/ResourceBundle"; +import { URLHelper } from "sap/m/library"; +import { ListItemBase$DetailPressEvent } from "sap/m/ListItemBase"; +import ObjectAttribute from "sap/m/ObjectAttribute"; +import ObjectListItem from "sap/m/ObjectListItem"; +import StandardListItem from "sap/m/StandardListItem"; +import Control from "sap/ui/core/Control"; +import Controller from "sap/ui/core/mvc/Controller"; +import Context from "sap/ui/model/Context"; +import ResourceModel from "sap/ui/model/resource/ResourceModel"; +import Currency from "sap/ui/model/type/Currency"; + +/** + * @namespace ui5.tutorial.databinding.controller + */ +export default class App extends Controller { + public async formatMail(firstName: string, lastName: string): Promise { + const bundle: ResourceBundle = await (this.getView().getModel("i18n") as ResourceModel).getResourceBundle() as ResourceBundle; + + return URLHelper.normalizeEmail( + `${firstName}.${lastName}@example.com`, + bundle.getText("mailSubject", [firstName]), + bundle.getText("mailBody") + ); + } + + public formatStockValue(unitPrice: number, stockLevel: number, currencyCode: string): string { + const currency: Currency = new Currency(); + return currency.formatValue([unitPrice * stockLevel, currencyCode], "string"); + } + + public onItemSelected(event: ListItemBase$DetailPressEvent): void { + const bindingPath: string = (event.getSource() as ObjectListItem).getBindingContext("products").getPath(); + this.byId("productDetailsPanel")?.bindElement({ path: bindingPath, model: "products" }); + } + + public productListFactory(id: string, context: Context): Control { + let uiControl; + // Decide based on the data which dependent to clone + if (context.getProperty("UnitsInStock") === 0 && context.getProperty("Discontinued")) { + // The item is discontinued, so use a StandardListItem + uiControl = (this.byId("productSimple") as StandardListItem).clone(id); + } else { + // The item is available, so we will create an ObjectListItem + uiControl = (this.byId("productExtended") as ObjectListItem).clone(id); + + // The item is temporarily out of stock, so we will add a status + if (context.getProperty("UnitsInStock") < 1) { + uiControl.addAttribute(new ObjectAttribute({ + text : { + path: "i18n>outOfStock" + } + })); + } + } + + return uiControl; + } +} diff --git a/packages/databinding/steps/15/webapp/i18n/i18n.properties b/packages/databinding/steps/15/webapp/i18n/i18n.properties new file mode 100644 index 000000000..d5dfa8b7f --- /dev/null +++ b/packages/databinding/steps/15/webapp/i18n/i18n.properties @@ -0,0 +1,34 @@ +# App Descriptor +appTitle=Data Binding Tutorial +appDescription=A simple app that explains how to use data binding features of OpenUI5 + +# Field labels +firstName=First Name +lastName=Last Name +enabled=Enabled +address=Address +salesAmount=Sales Amount + +# Screen titles +panel1HeaderText=Data Binding Basics +panel2HeaderText=Address Details +panel3HeaderText=Aggregation Binding +panel4HeaderText=Product Details + +# Email +sendEmail=Send EMail +mailSubject=Hi {0}! +mailBody=How are you? + +# Product list +productListTitle=Product List +stockValue=Current Stock Value + +# Product Details +ProductID=Product ID +ProductName=Product Name +QuantityPerUnit=Quantity per Unit +UnitPrice=Unit Price +UnitsInStock=Number of Units in Stock +Discontinued=Discontinued +outOfStock=Out of Stock diff --git a/packages/databinding/steps/15/webapp/i18n/i18n_de.properties b/packages/databinding/steps/15/webapp/i18n/i18n_de.properties new file mode 100644 index 000000000..c9d2da542 --- /dev/null +++ b/packages/databinding/steps/15/webapp/i18n/i18n_de.properties @@ -0,0 +1,34 @@ +# App Descriptor +appTitle=Data Binding Tutorial +appDescription=Eine einfache Anwendung zur Erkl\u00e4rung der UI5 Data Binding Funktionen + +# Field labels +firstName=Vorname +lastName=Nachname +enabled=Aktiviert +address=Adresse +salesAmount=Verk\u00e4ufe bis zum heutigen Datum + +# Screen titles +panel1HeaderText=Data Binding Grundlagen +panel2HeaderText=Adressdetails +panel3HeaderText=Aggregation Binding +panel4HeaderText=Produktdetails + +# Email +sendEmail=E-Mail versenden +mailSubject=Hallo {0}! +mailBody=Wie geht es dir? + +# Product list +productListTitle=Artikelliste +stockValue=Lagerbestand Wert + +# Product Details +ProductID=Produkt-ID +ProductName=Produktname +QuantityPerUnit=Menge pro Einheit +UnitPrice=Preis pro Einheit +UnitsInStock=Lagerbestand +Discontinued=Eingestellt +outOfStock=Nicht vorr\u00e4tig diff --git a/packages/databinding/steps/15/webapp/index-cdn.html b/packages/databinding/steps/15/webapp/index-cdn.html new file mode 100644 index 000000000..e2b2e4833 --- /dev/null +++ b/packages/databinding/steps/15/webapp/index-cdn.html @@ -0,0 +1,22 @@ + + + + + + Data Binding Tutorial + + + +
+ + diff --git a/packages/databinding/steps/15/webapp/index.html b/packages/databinding/steps/15/webapp/index.html new file mode 100644 index 000000000..42edae4b3 --- /dev/null +++ b/packages/databinding/steps/15/webapp/index.html @@ -0,0 +1,22 @@ + + + + + + Data Binding Tutorial + + + +
+ + diff --git a/packages/databinding/steps/15/webapp/manifest.json b/packages/databinding/steps/15/webapp/manifest.json new file mode 100644 index 000000000..3852b19e3 --- /dev/null +++ b/packages/databinding/steps/15/webapp/manifest.json @@ -0,0 +1,70 @@ +{ + "_version": "2.8.0", + "sap.app": { + "id": "ui5.tutorial.databinding", + "type": "application", + "applicationVersion": { + "version": "1.0.0" + }, + "title": "{{appTitle}}", + "description": "{{appDescription}}", + "i18n": { + "bundleName": "ui5.tutorial.databinding.i18n.i18n", + "supportedLocales": [ + "", + "de" + ], + "fallbackLocale": "" + } + }, + "sap.ui": { + "technology": "UI5", + "deviceTypes": { + "desktop": true, + "tablet": true, + "phone": true + } + }, + "sap.ui5": { + "handleValidation": true, + "dependencies": { + "minUI5Version": "1.148", + "libs": { + "sap.m": {}, + "sap.ui.core": {}, + "sap.ui.layout": {} + } + }, + "contentDensities": { + "compact": true, + "cozy": true + }, + "models": { + "": { + "type": "sap.ui.model.json.JSONModel", + "uri": "./model/data.json" + }, + "products": { + "type": "sap.ui.model.json.JSONModel", + "uri": "./model/Products.json" + }, + "i18n": { + "type": "sap.ui.model.resource.ResourceModel", + "settings": { + "bundleName": "ui5.tutorial.databinding.i18n.i18n", + "supportedLocales": [ + "", + "de" + ], + "fallbackLocale": "", + "async": true + } + } + }, + "rootView": { + "viewName": "ui5.tutorial.databinding.view.App", + "type": "XML", + "id": "app" + } + } +} diff --git a/packages/databinding/steps/15/webapp/model/Products.json b/packages/databinding/steps/15/webapp/model/Products.json new file mode 100644 index 000000000..8df5acf34 --- /dev/null +++ b/packages/databinding/steps/15/webapp/model/Products.json @@ -0,0 +1,64 @@ +{ + "Products": [ + { + "ProductID": 1, + "ProductName": "Chai", + "SupplierID": 1, + "CategoryID": 1, + "QuantityPerUnit": "10 boxes x 20 bags", + "UnitPrice": "18.0000", + "UnitsInStock": 39, + "UnitsOnOrder": 0, + "ReorderLevel": 10, + "Discontinued": false + }, + { + "ProductID": 2, + "ProductName": "Chang", + "SupplierID": 1, + "CategoryID": 1, + "QuantityPerUnit": "24 - 12 oz bottles", + "UnitPrice": "19.0000", + "UnitsInStock": 17, + "UnitsOnOrder": 40, + "ReorderLevel": 25, + "Discontinued": true + }, + { + "ProductID": 3, + "ProductName": "Aniseed Syrup", + "SupplierID": 1, + "CategoryID": 2, + "QuantityPerUnit": "12 - 550 ml bottles", + "UnitPrice": "10.0000", + "UnitsInStock": 0, + "UnitsOnOrder": 70, + "ReorderLevel": 25, + "Discontinued": false + }, + { + "ProductID": 4, + "ProductName": "Chef Anton's Cajun Seasoning", + "SupplierID": 2, + "CategoryID": 2, + "QuantityPerUnit": "48 - 6 oz jars", + "UnitPrice": "22.0000", + "UnitsInStock": 53, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": false + }, + { + "ProductID": 5, + "ProductName": "Chef Anton's Gumbo Mix", + "SupplierID": 2, + "CategoryID": 2, + "QuantityPerUnit": "36 boxes", + "UnitPrice": "21.3500", + "UnitsInStock": 0, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": true + } + ] +} \ No newline at end of file diff --git a/packages/databinding/steps/15/webapp/model/data.json b/packages/databinding/steps/15/webapp/model/data.json new file mode 100644 index 000000000..9682b2cd2 --- /dev/null +++ b/packages/databinding/steps/15/webapp/model/data.json @@ -0,0 +1,14 @@ +{ + "firstName": "Harry", + "lastName": "Hawk", + "enabled": true, + "address": { + "street": "Dietmar-Hopp-Allee 16", + "city": "Walldorf", + "zip": "69190", + "country": "Germany" + }, + "salesAmount": 12345.6789, + "priceThreshold": 20, + "currencyCode": "EUR" +} \ No newline at end of file diff --git a/packages/databinding/steps/15/webapp/view/App.view.xml b/packages/databinding/steps/15/webapp/view/App.view.xml new file mode 100644 index 000000000..971008de7 --- /dev/null +++ b/packages/databinding/steps/15/webapp/view/App.view.xml @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/databinding/steps/15/webapp/view/ProductExtended.fragment.xml b/packages/databinding/steps/15/webapp/view/ProductExtended.fragment.xml new file mode 100644 index 000000000..37e5613c6 --- /dev/null +++ b/packages/databinding/steps/15/webapp/view/ProductExtended.fragment.xml @@ -0,0 +1,21 @@ + + + + \ No newline at end of file diff --git a/packages/databinding/steps/15/webapp/view/ProductSimple.fragment.xml b/packages/databinding/steps/15/webapp/view/ProductSimple.fragment.xml new file mode 100644 index 000000000..adbd5c104 --- /dev/null +++ b/packages/databinding/steps/15/webapp/view/ProductSimple.fragment.xml @@ -0,0 +1,13 @@ + + + + \ No newline at end of file diff --git a/packages/navigation/README.md b/packages/navigation/README.md index 3a2cde4a6..6a8646500 100644 --- a/packages/navigation/README.md +++ b/packages/navigation/README.md @@ -1,8 +1,6 @@ - - # Navigation and Routing Tutorial -SAPUI5 comes with a powerful routing API that helps you control the state of your application efficiently. This tutorial will illustrate all major features and APIs related to navigation and routing in SAPUI5 apps by creating a simple and easy to understand mobile app. It represents a set of best practices for applying the navigation and routing features of SAPUI5 to your applications. +OpenUI5 comes with a powerful routing API that helps you control the state of your application efficiently. This tutorial will illustrate all major features and APIs related to navigation and routing in OpenUI5 apps by creating a simple and easy to understand mobile app. It represents a set of best practices for applying the navigation and routing features of OpenUI5 to your applications. In classical Web applications, the server determines which resource is requested based on the URL pattern of the request and serves it accordingly. The server-side logic controls how the requested resource or page is displayed in an appropriate way. @@ -42,23 +40,23 @@ Throughout this tutorial we will add features for navigating to pages and bookma The tutorial consists of the following steps. To start, just open the first link — you'll be guided from there. -- **[Step 1: Set Up the Initial App](./steps/01/README.md "We start by setting up a simple app for this tutorial. The app displays mock data only and mimics real OData back-end calls with the mock server as you have seen in the *Walkthrough* tutorial.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/navigation/build/01/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-01.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-01-js.zip)
) -- **[Step 2: Enable Routing](./steps/02/README.md "In this step we will modify the app and introduce routing. Instead of having the home page of the app hard coded we will configure a router to wire multiple views together when our app is called.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/navigation/build/02/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-02.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-02-js.zip)
) -- **[Step 3: Catch Invalid Hashes](./steps/03/README.md "Sometimes it is important to display an indication that the requested resource was not found.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/navigation/build/03/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-03.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-03-js.zip)
) -- **[Step 4: Add a *Back* Button to *Not Found* Page](./steps/04/README.md "When we are on the *Not Found* page because of an invalid hash, we want to get back to our app to select another page.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/navigation/build/04/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-04.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-04-js.zip)
) -- **[Step 5: Display a Target Without Changing the Hash](./steps/05/README.md "In this step, you will learn more about targets and how to display a target from the routing configuration manually.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/navigation/build/05/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-05.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-05-js.zip)
) -- **[Step 6: Navigate to Routes with Hard-Coded Patterns](./steps/06/README.md "In this step, we'll create a second button on the home page, with which we can navigate to a simple list of employees.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/navigation/build/06/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-06.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-06-js.zip)
) -- **[Step 7: Navigate to Routes with Mandatory Parameters](./steps/07/README.md "In this step, we implement a feature that allows the user to click on an employee in the list to see additional details of the employee.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/navigation/build/07/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-07.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-07-js.zip)
) -- **[Step 8: Navigate with Flip Transition](./steps/08/README.md "In this step, we want to illustrate how to navigate to a page with a custom transition animation. Both forward and backward navigation will use the “flip” transition but with a different direction.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/navigation/build/08/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-08.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-08-js.zip)
) -- **[Step 9: Allow Bookmarkable Tabs with Optional Query Parameters](./steps/09/README.md "The resume view contains four tabs as we have seen in the previous step of this tutorial. However, when the user navigates to the resume page, only the first tab is displayed initially.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/navigation/build/09/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-09.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-09-js.zip)
) -- **[Step 10: Implement “Lazy Loading”](./steps/10/README.md "In the previous steps, we have implemented a Resume view that uses tabs to display data. The complete content of all the tabs is loaded once, no matter which tab is currently displayed.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/navigation/build/10/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-10.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-10-js.zip)
) -- **[Step 11: Assign Multiple Targets](./steps/11/README.md "In this step, we will add a new button to the home page to illustrate the usage of multiple targets for a route.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/navigation/build/11/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-11.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-11-js.zip)
) -- **[Step 12: Make a Search Bookmarkable](./steps/12/README.md "In this step we will make the search bookmarkable. This allows users to search for employees in the *Employees* table and they can bookmark their search query or share the URL.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/navigation/build/12/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-12.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-12-js.zip)
) -- **[Step 13: Make Table Sorting Bookmarkable](./steps/13/README.md "In this step, we will create a button at the top of the table which will change the sorting of the table.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/navigation/build/13/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-13.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-13-js.zip)
) -- **[Step 14: Make Dialogs Bookmarkable](./steps/14/README.md "In this step, we want to allow bookmarking of the dialog box that is opened when the user clicks the *Sort* button.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/navigation/build/14/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-14.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-14-js.zip)
) -- **[Step 15: Reuse an Existing Route](./steps/15/README.md "The *Employees* table displays employee data. However, the resumes of the employees are not accessible from this view yet.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/navigation/build/15/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-15.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-15-js.zip)
) -- **[Step 16: Handle Invalid Hashes by Listening to Bypassed Events](./steps/16/README.md "So far we have created many useful routes in our app. In the very early steps we have also made sure that a *Not Found* page is displayed in case the app was called with an invalid hash.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/navigation/build/16/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-16.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-16-js.zip)
) -- **[Step 17: Listen to Matched Events of Any Route](./steps/17/README.md "In the previous step, we have listened for bypassed events to detect possible technical issues with our app.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/navigation/build/17/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-17.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-17-js.zip)
) +- **[Step 1: Set Up the Initial App](./steps/01/README.md)** — We start by setting up a simple app for this tutorial. The app displays mock data only and mimics real OData back-end calls with the mock server as you have seen in the *Walkthrough* tutorial. ([🔗 Live Preview](https://ui5.github.io/tutorials/navigation/build/01/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-01.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-01-js.zip) (JS)) +- **[Step 2: Enable Routing](./steps/02/README.md)** — In this step we will modify the app and introduce routing. Instead of having the home page of the app hard coded we will configure a router to wire multiple views together when our app is called. ([🔗 Live Preview](https://ui5.github.io/tutorials/navigation/build/02/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-02.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-02-js.zip) (JS)) +- **[Step 3: Catch Invalid Hashes](./steps/03/README.md)** — Sometimes it is important to display an indication that the requested resource was not found. ([🔗 Live Preview](https://ui5.github.io/tutorials/navigation/build/03/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-03.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-03-js.zip) (JS)) +- **[Step 4: Add a *Back* Button to *Not Found* Page](./steps/04/README.md)** — When we are on the *Not Found* page because of an invalid hash, we want to get back to our app to select another page. ([🔗 Live Preview](https://ui5.github.io/tutorials/navigation/build/04/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-04.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-04-js.zip) (JS)) +- **[Step 5: Display a Target Without Changing the Hash](./steps/05/README.md)** — In this step, you will learn more about targets and how to display a target from the routing configuration manually. ([🔗 Live Preview](https://ui5.github.io/tutorials/navigation/build/05/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-05.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-05-js.zip) (JS)) +- **[Step 6: Navigate to Routes with Hard-Coded Patterns](./steps/06/README.md)** — In this step, we'll create a second button on the home page, with which we can navigate to a simple list of employees. ([🔗 Live Preview](https://ui5.github.io/tutorials/navigation/build/06/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-06.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-06-js.zip) (JS)) +- **[Step 7: Navigate to Routes with Mandatory Parameters](./steps/07/README.md)** — In this step, we implement a feature that allows the user to click on an employee in the list to see additional details of the employee. ([🔗 Live Preview](https://ui5.github.io/tutorials/navigation/build/07/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-07.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-07-js.zip) (JS)) +- **[Step 8: Navigate with Flip Transition](./steps/08/README.md)** — In this step, we want to illustrate how to navigate to a page with a custom transition animation. Both forward and backward navigation will use the “flip” transition but with a different direction. ([🔗 Live Preview](https://ui5.github.io/tutorials/navigation/build/08/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-08.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-08-js.zip) (JS)) +- **[Step 9: Allow Bookmarkable Tabs with Optional Query Parameters](./steps/09/README.md)** — The resume view contains four tabs as we have seen in the previous step of this tutorial. However, when the user navigates to the resume page, only the first tab is displayed initially. ([🔗 Live Preview](https://ui5.github.io/tutorials/navigation/build/09/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-09.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-09-js.zip) (JS)) +- **[Step 10: Implement “Lazy Loading”](./steps/10/README.md)** — In the previous steps, we have implemented a Resume view that uses tabs to display data. The complete content of all the tabs is loaded once, no matter which tab is currently displayed. ([🔗 Live Preview](https://ui5.github.io/tutorials/navigation/build/10/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-10.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-10-js.zip) (JS)) +- **[Step 11: Assign Multiple Targets](./steps/11/README.md)** — In this step, we will add a new button to the home page to illustrate the usage of multiple targets for a route. ([🔗 Live Preview](https://ui5.github.io/tutorials/navigation/build/11/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-11.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-11-js.zip) (JS)) +- **[Step 12: Make a Search Bookmarkable](./steps/12/README.md)** — In this step we will make the search bookmarkable. This allows users to search for employees in the *Employees* table and they can bookmark their search query or share the URL. ([🔗 Live Preview](https://ui5.github.io/tutorials/navigation/build/12/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-12.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-12-js.zip) (JS)) +- **[Step 13: Make Table Sorting Bookmarkable](./steps/13/README.md)** — In this step, we will create a button at the top of the table which will change the sorting of the table. ([🔗 Live Preview](https://ui5.github.io/tutorials/navigation/build/13/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-13.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-13-js.zip) (JS)) +- **[Step 14: Make Dialogs Bookmarkable](./steps/14/README.md)** — In this step, we want to allow bookmarking of the dialog box that is opened when the user clicks the *Sort* button. ([🔗 Live Preview](https://ui5.github.io/tutorials/navigation/build/14/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-14.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-14-js.zip) (JS)) +- **[Step 15: Reuse an Existing Route](./steps/15/README.md)** — The *Employees* table displays employee data. However, the resumes of the employees are not accessible from this view yet. ([🔗 Live Preview](https://ui5.github.io/tutorials/navigation/build/15/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-15.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-15-js.zip) (JS)) +- **[Step 16: Handle Invalid Hashes by Listening to Bypassed Events](./steps/16/README.md)** — So far we have created many useful routes in our app. In the very early steps we have also made sure that a *Not Found* page is displayed in case the app was called with an invalid hash. ([🔗 Live Preview](https://ui5.github.io/tutorials/navigation/build/16/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-16.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-16-js.zip) (JS)) +- **[Step 17: Listen to Matched Events of Any Route](./steps/17/README.md)** — In the previous step, we have listened for bypassed events to detect possible technical issues with our app. ([🔗 Live Preview](https://ui5.github.io/tutorials/navigation/build/17/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-17.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/navigation/navigation-step-17-js.zip) (JS)) *** diff --git a/packages/navigation/steps.json b/packages/navigation/steps.json new file mode 100644 index 000000000..602210736 --- /dev/null +++ b/packages/navigation/steps.json @@ -0,0 +1,159 @@ +{ + "tutorial": "navigation", + "namespace": "ui5.tutorial.navigation", + "steps": [ + { + "n": 1, + "id": "01", + "title": "Set Up the Initial App", + "previewUrl": "https://ui5.github.io/tutorials/navigation/build/01/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/navigation/navigation-step-01.zip", + "zipJs": "https://ui5.github.io/tutorials/navigation/navigation-step-01-js.zip", + "description": "We start by setting up a simple app for this tutorial. The app displays mock data only and mimics real OData back-end calls with the mock server as you have seen in the Walkthrough tutorial." + }, + { + "n": 2, + "id": "02", + "title": "Enable Routing", + "previewUrl": "https://ui5.github.io/tutorials/navigation/build/02/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/navigation/navigation-step-02.zip", + "zipJs": "https://ui5.github.io/tutorials/navigation/navigation-step-02-js.zip", + "description": "In this step we will modify the app and introduce routing. Instead of having the home page of the app hard coded we will configure a router to wire multiple views together when our app is called. The routing configuration controls the application flow when the user triggers a navigation action or opens a link to the application directly." + }, + { + "n": 3, + "id": "03", + "title": "Catch Invalid Hashes", + "previewUrl": "https://ui5.github.io/tutorials/navigation/build/03/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/navigation/navigation-step-03.zip", + "zipJs": "https://ui5.github.io/tutorials/navigation/navigation-step-03-js.zip", + "description": "Sometimes it is important to display an indication that the requested resource was not found. To give you an example: If a user tries to access an invalid pattern which does not match any of the configured routes, the user is notified that something went wrong. You might also know this as a “404” or Not Found Page from traditional web pages. In this step, we will implement a feature that detects invalid hashes and visualizes this in a nice way." + }, + { + "n": 4, + "id": "04", + "title": "Add a *Back* Button to *Not Found* Page", + "previewUrl": "https://ui5.github.io/tutorials/navigation/build/04/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/navigation/navigation-step-04.zip", + "zipJs": "https://ui5.github.io/tutorials/navigation/navigation-step-04-js.zip", + "description": "When we are on the Not Found page because of an invalid hash, we want to get back to our app to select another page. Therefore, we will add a Back button to the NotFound view and make sure that the user gets redirected to either the previous page or the overview page when the Back button is pressed." + }, + { + "n": 5, + "id": "05", + "title": "Display a Target Without Changing the Hash", + "previewUrl": "https://ui5.github.io/tutorials/navigation/build/05/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/navigation/navigation-step-05.zip", + "zipJs": "https://ui5.github.io/tutorials/navigation/navigation-step-05-js.zip", + "description": "In this step, you will learn more about targets and how to display a target from the routing configuration manually." + }, + { + "n": 6, + "id": "06", + "title": "Navigate to Routes with Hard-Coded Patterns", + "previewUrl": "https://ui5.github.io/tutorials/navigation/build/06/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/navigation/navigation-step-06.zip", + "zipJs": "https://ui5.github.io/tutorials/navigation/navigation-step-06-js.zip", + "description": "In this step, we'll create a second button on the home page, with which we can navigate to a simple list of employees. This example illustrates how to navigate to a route that has a hard-coded pattern." + }, + { + "n": 7, + "id": "07", + "title": "Navigate to Routes with Mandatory Parameters", + "previewUrl": "https://ui5.github.io/tutorials/navigation/build/07/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/navigation/navigation-step-07.zip", + "zipJs": "https://ui5.github.io/tutorials/navigation/navigation-step-07-js.zip", + "description": "In this step, we implement a feature that allows the user to click on an employee in the list to see additional details of the employee. A route pattern can have one or more mandatory parameters to identify objects in an app." + }, + { + "n": 8, + "id": "08", + "title": "Navigate with Flip Transition", + "previewUrl": "https://ui5.github.io/tutorials/navigation/build/08/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/navigation/navigation-step-08.zip", + "zipJs": "https://ui5.github.io/tutorials/navigation/navigation-step-08-js.zip", + "description": "In this step, we want to illustrate how to navigate to a page with a custom transition animation. Both forward and backward navigation will use the “flip” transition but with a different direction. We will create a simple link on the Employee view that triggers a flip navigation to a page that displays the resume data of a certain employee. Pressing the Back button will navigate back to the Employee view with a reversed flip transition." + }, + { + "n": 9, + "id": "09", + "title": "Allow Bookmarkable Tabs with Optional Query Parameters", + "previewUrl": "https://ui5.github.io/tutorials/navigation/build/09/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/navigation/navigation-step-09.zip", + "zipJs": "https://ui5.github.io/tutorials/navigation/navigation-step-09-js.zip", + "description": "The resume view contains four tabs as we have seen in the previous step of this tutorial. However, when the user navigates to the resume page, only the first tab is displayed initially. Navigating directly to a specific tab or bookmarking a tab is not yet supported in our current app." + }, + { + "n": 10, + "id": "10", + "title": "Implement “Lazy Loading”", + "previewUrl": "https://ui5.github.io/tutorials/navigation/build/10/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/navigation/navigation-step-10.zip", + "zipJs": "https://ui5.github.io/tutorials/navigation/navigation-step-10-js.zip", + "description": "In the previous steps, we have implemented a Resume view that uses tabs to display data. The complete content of all the tabs is loaded once, no matter which tab is currently displayed. We can increase the performance of our app by avoiding to load content that is not visible. Therefore, we implement a “lazy loading” feature that only loads the view and data when requested by the user." + }, + { + "n": 11, + "id": "11", + "title": "Assign Multiple Targets", + "previewUrl": "https://ui5.github.io/tutorials/navigation/build/11/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/navigation/navigation-step-11.zip", + "zipJs": "https://ui5.github.io/tutorials/navigation/navigation-step-11-js.zip", + "description": "In this step, we will add a new button to the home page to illustrate the usage of multiple targets for a route. When the button is pressed, a new page opens that contains two parts: a header part at the top and a content part. The content part displays a table of employees that can be sorted and searched. We will use the array notation in the routing configuration to assign multiple targets to a route - a feature that we have not yet introduced." + }, + { + "n": 12, + "id": "12", + "title": "Make a Search Bookmarkable", + "previewUrl": "https://ui5.github.io/tutorials/navigation/build/12/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/navigation/navigation-step-12.zip", + "zipJs": "https://ui5.github.io/tutorials/navigation/navigation-step-12-js.zip", + "description": "In this step we will make the search bookmarkable. This allows users to search for employees in the Employees table and they can bookmark their search query or share the URL." + }, + { + "n": 13, + "id": "13", + "title": "Make Table Sorting Bookmarkable", + "previewUrl": "https://ui5.github.io/tutorials/navigation/build/13/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/navigation/navigation-step-13.zip", + "zipJs": "https://ui5.github.io/tutorials/navigation/navigation-step-13-js.zip", + "description": "In this step, we will create a button at the top of the table which will change the sorting of the table. When the current sorting state of the table is changed, the sorting state will be reflected in the URL. This illustrates how to make the table sorting bookmarkable." + }, + { + "n": 14, + "id": "14", + "title": "Make Dialogs Bookmarkable", + "previewUrl": "https://ui5.github.io/tutorials/navigation/build/14/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/navigation/navigation-step-14.zip", + "zipJs": "https://ui5.github.io/tutorials/navigation/navigation-step-14-js.zip", + "description": "In this step, we want to allow bookmarking of the dialog box that is opened when the user clicks the Sort button. The dialog should automatically open when the URL contains the query parameter showDialog." + }, + { + "n": 15, + "id": "15", + "title": "Reuse an Existing Route", + "previewUrl": "https://ui5.github.io/tutorials/navigation/build/15/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/navigation/navigation-step-15.zip", + "zipJs": "https://ui5.github.io/tutorials/navigation/navigation-step-15-js.zip", + "description": "The Employees table displays employee data. However, the resumes of the employees are not accessible from this view yet. We could create a new route and a new view to visualize the resume again, but we could also simply reuse an existing route to cross-link the resume of a certain employee. In this step, we will add a feature that allows users to directly navigate to the resume of a certain employee. We will reuse the Resume page that we have created in an earlier step. This example illustrates that there can be multiple navigation paths that direct to the same page." + }, + { + "n": 16, + "id": "16", + "title": "Handle Invalid Hashes by Listening to Bypassed Events", + "previewUrl": "https://ui5.github.io/tutorials/navigation/build/16/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/navigation/navigation-step-16.zip", + "zipJs": "https://ui5.github.io/tutorials/navigation/navigation-step-16-js.zip", + "description": "So far we have created many useful routes in our app. In the very early steps we have also made sure that a Not Found page is displayed in case the app was called with an invalid hash. Now, we proceed further and track invalid hashes to be able to detect and correct any invalid links or add new URL patterns that are often requested but not found. Therefore, we simply listen to the bypassed events" + }, + { + "n": 17, + "id": "17", + "title": "Listen to Matched Events of Any Route", + "previewUrl": "https://ui5.github.io/tutorials/navigation/build/17/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/navigation/navigation-step-17.zip", + "zipJs": "https://ui5.github.io/tutorials/navigation/navigation-step-17-js.zip", + "description": "In the previous step, we have listened for bypassed events to detect possible technical issues with our app. In this step, we want to improve the analysis use case even more by listening to any matched event of the route. We could use this information to measure how the app is used and how frequently the pages are called. Many Web analytic tools track page hits this way. The collected information can be used, for example to improve our app and its usability." + } + ] +} diff --git a/packages/navigation/steps/01/README.md b/packages/navigation/steps/01/README.md index 18b0aadc4..74ff2888a 100644 --- a/packages/navigation/steps/01/README.md +++ b/packages/navigation/steps/01/README.md @@ -1,10 +1,8 @@ - - # Step 1: Set Up the Initial App We start by setting up a simple app for this tutorial. The app displays mock data only and mimics real OData back-end calls with the mock server as you have seen in the *Walkthrough* tutorial. -The structure and data model created in this step will be used throughout the rest of this tutorial. The initial app created in this step will be extended in the subsequent steps to illustrate the navigation and routing features of SAPUI5. +The structure and data model created in this step will be used throughout the rest of this tutorial. The initial app created in this step will be extended in the subsequent steps to illustrate the navigation and routing features of OpenUI5. ## Preview @@ -14,13 +12,16 @@ The structure and data model created in this step will be used throughout the re ## Setup -1. To set up your project for this tutorial, download the files at [Navigation and Routing - Step 1](https://ui5.github.io/tutorials/navigation/navigation-step-01.zip). +The initial code for this step ships with the repository at [packages/navigation/steps/01/](./). Open it and run: -2. Extract the downloaded `.zip` file at the desired location on your local machine. +```sh +npm install +npm start +``` -3. Open a shell in the extracted folder and execute `npm install`. +This starts the local web server and opens a browser window hosting `index.html`. -4. Execute `npm start` to start the web server and to open a new browser window hosting your newly created `index.html`. +If you prefer to work outside the monorepo, download the standalone bundle at [Navigation and Routing - Step 1](https://ui5.github.io/tutorials/navigation/navigation-step-01.zip) (TS)[Navigation and Routing - Step 1](https://ui5.github.io/tutorials/navigation/navigation-step-01-js.zip) (JS), extract it, and run the same two commands from the extracted folder. You should have the same files as displayed in the following figure: @@ -28,20 +29,20 @@ You should have the same files as displayed in the following figure: ```text webapp/ -├── Component.?s +├── Component.ts/.js ├── controller/ -│ └── App.controller.?s +│ └── App.controller.ts/.js ├── i18n/ │ └── i18n.properties ├── index-cdn.html ├── index.html -├── initMockServer.?s +├── initMockServer.ts/.js ├── localService/ │ ├── metadata.xml │ ├── mockdata/ │ │ ├── Employees.json │ │ └── Resumes.json -│ └── mockserver.?s +│ └── mockserver.ts/.js ├── manifest.json └── view/ └── App.view.xml @@ -52,11 +53,11 @@ webapp/ ## The Initial App -With the downloaded coding, you have an initial app with recommended settings that provides the basic features of an SAPUI5 app: +With the downloaded coding, you have an initial app with recommended settings that provides the basic features of an OpenUI5 app: - **Home Page** - The home page of our app is defined in the `webapp/index.html` file. In this file we bootstrap SAPUI5 and tell the runtime where to find our custom resources. Furthermore, we initialize the `MockServer` to simulate back-end requests as we do not have a real back-end service throughout this tutorial. Finally, we instantiate the application component, assign it to a `sap.m.Shell` control, and place the shell into the body. The corresponding `Component.ts` file in the `webapp` folder will be extended throughout this tutorial. + The home page of our app is defined in the `webapp/index.html` file. In this file we bootstrap OpenUI5 and tell the runtime where to find our custom resources. Furthermore, we initialize the `MockServer` to simulate back-end requests as we do not have a real back-end service throughout this tutorial. Finally, we instantiate the application component, assign it to a `sap.m.Shell` control, and place the shell into the body. The corresponding `Component.ts` file in the `webapp` folder will be extended throughout this tutorial. - **Data** diff --git a/packages/navigation/steps/01/webapp/i18n/i18n.properties b/packages/navigation/steps/01/webapp/i18n/i18n.properties index be58132c8..32efd070c 100644 --- a/packages/navigation/steps/01/webapp/i18n/i18n.properties +++ b/packages/navigation/steps/01/webapp/i18n/i18n.properties @@ -1,6 +1,6 @@ # App Descriptor appTitle=Navigation & Routing Tutorial -appDescription=A simple app that explains how to use navigation and routing features of SAPUI5 +appDescription=A simple app that explains how to use navigation and routing features of OpenUI5 iWantToNavigate=I want to navigate homePageTitle=Home diff --git a/packages/navigation/steps/02/README.md b/packages/navigation/steps/02/README.md index 45aea849c..d7bba447d 100644 --- a/packages/navigation/steps/02/README.md +++ b/packages/navigation/steps/02/README.md @@ -1,5 +1,3 @@ - - # Step 2: Enable Routing In this step we will modify the app and introduce routing. Instead of having the home page of the app hard coded we will configure a router to wire multiple views together when our app is called. The routing configuration controls the application flow when the user triggers a navigation action or opens a link to the application directly. @@ -10,17 +8,19 @@ In this step we will modify the app and introduce routing. Instead of having the ![Views are wired together using the router](assets/Tutorial_Navigation_and_Routing_Step_02a.png "Views are wired together using the router") +You can view this step live: [🔗 Live Preview of Step 2](https://ui5.github.io/tutorials/navigation/build/02/index-cdn.html). + ## Coding -You can view this step live: [🔗 Live Preview of Step 2](https://ui5.github.io/tutorials/navigation/build/02/index-cdn.html). +You can download the solution for this step here: [📥 Download step 2](https://ui5.github.io/tutorials/navigation/navigation-step-02.zip) (TS)[📥 Download step 2](https://ui5.github.io/tutorials/navigation/navigation-step-02-js.zip) (JS). ### Folder structure for this step ```text webapp/ ├── controller/ -│ ├── App.controller.?s -│ └── Home.controller.?s +│ ├── App.controller.ts/.js +│ └── Home.controller.ts/.js ├── i18n/ │ └── i18n.properties ├── localService/ @@ -28,13 +28,13 @@ webapp/ │ │ ├── Employees.json │ │ └── Resumes.json │ ├── metadata.xml -│ └── mockserver.?s +│ └── mockserver.ts/.js ├── view/ │ ├── App.view.xml │ └── Home.view.xml -├── Component.?s +├── Component.ts/.js ├── index.html -├── initMockServer.?s +├── initMockServer.ts/.js └── manifest.json ``` @@ -90,7 +90,7 @@ webapp/ ``` -Single-page applications based on SAPUI5 can use a so-called “router” to dispatch hash-based URLs to one or more views of the app. Therefore, the router needs to know how to address and show the views. In SAPUI5, we can simply add a `routing` section to our existing `sap.ui5` section in the descriptor file to configure the router. There are three properties that can be used to configure the routing of your application: +Single-page applications based on OpenUI5 can use a so-called “router” to dispatch hash-based URLs to one or more views of the app. Therefore, the router needs to know how to address and show the views. In OpenUI5, we can simply add a `routing` section to our existing `sap.ui5` section in the descriptor file to configure the router. There are three properties that can be used to configure the routing of your application: - `config` @@ -110,11 +110,12 @@ Single-page applications based on SAPUI5 can use a so-called “router” to dis This basic routing configuration was easy enough. However, you can’t see it in action until you have initialized the router. > :note: -> As of SAPUI5 version 1.30, we recommend that you define the routing in the `manifest.json` descriptor file using routes and targets. In older versions of SAPUI5, the routing configuration had to be done directly in the metadata section of the component, and with different syntax. +> As of OpenUI5 version 1.30, we recommend that you define the routing in the `manifest.json` descriptor file using routes and targets. In older versions of OpenUI5, the routing configuration had to be done directly in the metadata section of the component, and with different syntax. -## `webapp/Component.?s` +## `webapp/Component.ts/.js` ```ts +// webapp/Component.ts import UIComponent from "sap/ui/core/UIComponent"; /** @@ -136,6 +137,7 @@ export default class Component extends UIComponent { ``` ```js +// webapp/Component.js sap.ui.define(["sap/ui/core/UIComponent"], function (UIComponent) { "use strict"; @@ -194,9 +196,10 @@ In the `App` view, we remove the content of `App` control. The pages will be add Create a file `Home.view.xml` in the `webapp/view` folder. The home view only contains a page control that displays a button. For illustration, we bind the title of the page to the `i18n>homePageTitle`, you can use data binding just the way you are used to it. -## `webapp/controller/Home.controller.?s` \(New\) +## `webapp/controller/Home.controller.ts/.js` \(New\) ```ts +// webapp/controller/Home.controller.ts import Controller from "sap/ui/core/mvc/Controller"; /** @@ -208,6 +211,7 @@ export default class Home extends Controller { ``` ```js +// webapp/controller/Home.controller.js sap.ui.define(["sap/ui/core/mvc/Controller"], function (Controller) { "use strict"; @@ -233,7 +237,6 @@ Create a file `Home.controller.ts` in the `webapp/controller` folder. The contro *** - *** **Next:** [Step 3: Catch Invalid Hashes](../03/README.md) diff --git a/packages/navigation/steps/02/webapp/i18n/i18n.properties b/packages/navigation/steps/02/webapp/i18n/i18n.properties index be58132c8..32efd070c 100644 --- a/packages/navigation/steps/02/webapp/i18n/i18n.properties +++ b/packages/navigation/steps/02/webapp/i18n/i18n.properties @@ -1,6 +1,6 @@ # App Descriptor appTitle=Navigation & Routing Tutorial -appDescription=A simple app that explains how to use navigation and routing features of SAPUI5 +appDescription=A simple app that explains how to use navigation and routing features of OpenUI5 iWantToNavigate=I want to navigate homePageTitle=Home diff --git a/packages/navigation/steps/03/README.md b/packages/navigation/steps/03/README.md index 80055b73e..3f8395045 100644 --- a/packages/navigation/steps/03/README.md +++ b/packages/navigation/steps/03/README.md @@ -1,5 +1,3 @@ - - # Step 3: Catch Invalid Hashes Sometimes it is important to display an indication that the requested resource was not found. To give you an example: If a user tries to access an invalid pattern which does not match any of the configured routes, the user is notified that something went wrong. You might also know this as a “404” or *Not Found Page* from traditional web pages. In this step, we will implement a feature that detects invalid hashes and visualizes this in a nice way. @@ -10,18 +8,20 @@ Sometimes it is important to display an indication that the requested resource w ![Not Found page](assets/Tutorial_Navigation_and_Routing_Step_03.png "Not Found page") +You can view this step live: [🔗 Live Preview of Step 3](https://ui5.github.io/tutorials/navigation/build/03/index-cdn.html). + ## Coding -You can view this step live: [🔗 Live Preview of Step 3](https://ui5.github.io/tutorials/navigation/build/03/index-cdn.html). +You can download the solution for this step here: [📥 Download step 3](https://ui5.github.io/tutorials/navigation/navigation-step-03.zip) (TS)[📥 Download step 3](https://ui5.github.io/tutorials/navigation/navigation-step-03-js.zip) (JS). ### Folder structure for this step ```text webapp/ ├── controller/ -│ ├── App.controller.?s -│ ├── Home.controller.?s -│ └── NotFound.controller.?s +│ ├── App.controller.ts/.js +│ ├── Home.controller.ts/.js +│ └── NotFound.controller.ts/.js ├── i18n/ │ └── i18n.properties ├── localService/ @@ -29,14 +29,14 @@ webapp/ │ │ ├── Employees.json │ │ └── Resumes.json │ ├── metadata.xml -│ └── mockserver.?s +│ └── mockserver.ts/.js ├── view/ │ ├── App.view.xml │ ├── Home.view.xml │ └── NotFound.view.xml -├── Component.?s +├── Component.ts/.js ├── index.html -├── initMockServer.?s +├── initMockServer.ts/.js └── manifest.json ``` @@ -108,9 +108,10 @@ Let’s extend the routing configuration in the descriptor by adding a `bypassed Now we create the view referenced above in a new file `NotFound.view.xml` within the `webapp/view` folder. It uses a `sap.m.Page` containing a `sap.m.IllustratedMessage` control to display an error message to the user. In a real app you might use a dynamic message matching the current error situation. Here, we simply display a preconfigured text from our resource bundle. -## `webapp/controller/NotFound.controller.?s` \(New\) +## `webapp/controller/NotFound.controller.ts/.js` \(New\) ```ts +// webapp/controller/NotFound.controller.ts import Controller from "sap/ui/core/mvc/Controller"; /** @@ -125,6 +126,7 @@ export default class NotFound extends Controller { ``` ```js +// webapp/controller/NotFound.controller.js sap.ui.define(["sap/ui/core/mvc/Controller"], function (Controller) { "use strict"; @@ -158,7 +160,6 @@ Open the URL `index.html#/thisIsInvalid` in your browser. From now on the user w *** - *** **Next:** [Step 4: Add a *Back* Button to *Not Found* Page](../04/README.md) diff --git a/packages/navigation/steps/03/webapp/i18n/i18n.properties b/packages/navigation/steps/03/webapp/i18n/i18n.properties index a5e577d0f..7a7cc5393 100644 --- a/packages/navigation/steps/03/webapp/i18n/i18n.properties +++ b/packages/navigation/steps/03/webapp/i18n/i18n.properties @@ -1,6 +1,6 @@ # App Descriptor appTitle=Navigation & Routing Tutorial -appDescription=A simple app that explains how to use navigation and routing features of SAPUI5 +appDescription=A simple app that explains how to use navigation and routing features of OpenUI5 iWantToNavigate=I want to navigate homePageTitle=Home diff --git a/packages/navigation/steps/04/README.md b/packages/navigation/steps/04/README.md index 6d449e2b0..bf0c69bb4 100644 --- a/packages/navigation/steps/04/README.md +++ b/packages/navigation/steps/04/README.md @@ -1,5 +1,3 @@ - - # Step 4: Add a *Back* Button to *Not Found* Page When we are on the *Not Found* page because of an invalid hash, we want to get back to our app to select another page. Therefore, we will add a *Back* button to the `NotFound` view and make sure that the user gets redirected to either the previous page or the overview page when the *Back* button is pressed. @@ -10,15 +8,17 @@ When we are on the *Not Found* page because of an invalid hash, we want to get b ![Not Found page with Back button](assets/Tutorial_Navigation_and_Routing_Step_04a.png) +You can view this step live: [🔗 Live Preview of Step 4](https://ui5.github.io/tutorials/navigation/build/04/index-cdn.html). + ### Folder structure for this step ```text webapp/ ├── controller/ -│ ├── App.controller.?s -│ ├── BaseController.?s -│ ├── Home.controller.?s -│ └── NotFound.controller.?s +│ ├── App.controller.ts/.js +│ ├── BaseController.ts/.js +│ ├── Home.controller.ts/.js +│ └── NotFound.controller.ts/.js ├── i18n/ │ └── i18n.properties ├── localService/ @@ -26,20 +26,20 @@ webapp/ │ │ ├── Employees.json │ │ └── Resumes.json │ ├── metadata.xml -│ └── mockserver.?s +│ └── mockserver.ts/.js ├── view/ │ ├── App.view.xml │ ├── Home.view.xml │ └── NotFound.view.xml -├── Component.?s +├── Component.ts/.js ├── index.html -├── initMockServer.?s +├── initMockServer.ts/.js └── manifest.json ``` ## Coding -You can view this step live: [🔗 Live Preview of Step 4](https://ui5.github.io/tutorials/navigation/build/04/index-cdn.html). +You can download the solution for this step here: [📥 Download step 4](https://ui5.github.io/tutorials/navigation/navigation-step-04.zip) (TS)[📥 Download step 4](https://ui5.github.io/tutorials/navigation/navigation-step-04-js.zip) (JS). ## webapp/view/NotFound.view.xml @@ -67,9 +67,10 @@ You can view this step live: [🔗 Live Preview of Step 4](https://ui5.github.io In the `NotFound` view, we set the property `showNavButton` of the `Page` control to `true` which automatically displays the *Back* button. We also add an event handler function `onNavBack` to the `navButtonPress` event of the control. The `onNavBack` function will handle the actual back navigation. We could directly add this function to the `NotFound` view’s controller. However, we are smart enough to anticipate that we might need the same handler function for different views. DRY \(“Don’t Repeat Yourself”\) is the right approach for us, so let’s create a `BaseController` from which all other controllers will inherit. -## `webapp/controller/BaseController.?s` \(New\) +## `webapp/controller/BaseController.ts/.js` \(New\) ```ts +// webapp/controller/BaseController.ts import Controller from "sap/ui/core/mvc/Controller"; import History from "sap/ui/core/routing/History"; import Router from "sap/ui/core/routing/Router"; @@ -98,6 +99,7 @@ export default class BaseController extends Controller { ``` ```js +// webapp/controller/BaseController.js sap.ui.define(["sap/ui/core/mvc/Controller", "sap/ui/core/routing/History", "sap/ui/core/UIComponent"], function (Controller, History, UIComponent) { "use strict"; @@ -126,11 +128,12 @@ The function checks if there is a previous hash value in the app history. If so, The third parameter of `navTo("appHome", {}, true /*no history*/);` has the value `true` and makes sure that the hash is replaced. With the line `sap.ui.core.UIComponent.getRouterFor(this)` you can easily access your component’s router throughout the app. To make it even more comfortable, we also add a handy shortcut `getRouter` to the base controller. This function is now available in each subclass as well. It is also used in the `onNavBack` handler to get a reference to the router before calling `navTo`. We now have to implement the reuse in all other controllers. > :note: -> In SAPUI5 there are multiple options to reuse code. We recommend to use a base controller for such helper methods because this allows us to decoratively use the `onNavBack` handler directly in any XML view without adding additional code to the controller. Our base controller is an abstract controller that will not be instantiated in any view. Therefore, the naming convention `*.controller.ts` does not apply, and we can just name the file `BaseController.ts`. By not using the naming convention `*.controller.ts` we can even prevent any unintentional usage in views. +> In OpenUI5 there are multiple options to reuse code. We recommend to use a base controller for such helper methods because this allows us to decoratively use the `onNavBack` handler directly in any XML view without adding additional code to the controller. Our base controller is an abstract controller that will not be instantiated in any view. Therefore, the naming convention `*.controller.ts` does not apply, and we can just name the file `BaseController.ts`. By not using the naming convention `*.controller.ts` we can even prevent any unintentional usage in views. -## `webapp/controller/NotFound.controller.?s` +## `webapp/controller/NotFound.controller.ts/.js` ```ts +// webapp/controller/NotFound.controller.ts import BaseController from "ui5/tutorial/navigation/controller/BaseController"; /** @@ -145,6 +148,7 @@ export default class NotFound extends BaseController { ``` ```js +// webapp/controller/NotFound.controller.js sap.ui.define(["ui5/tutorial/navigation/controller/BaseController"], function (BaseController) { "use strict"; @@ -159,9 +163,10 @@ In order to reuse the base controller implementation, we have to change the depe At this point you can open `index.html#/thisIsInvalid` in your browser and press the *Back* button to see what happens. You will be redirected to the app’s home page that is matched by the route `appHome` as you opened the *Not Found* page with an invalid hash. If you change the hash to something invalid when you are on the home page of the app, you will also go to the *Not Found* page but with a history entry. When you press back, you will get to the home page again, but this time with a native history navigation. -## `webapp/controller/App.controller.?s` +## `webapp/controller/App.controller.ts/.js` ```ts +// webapp/controller/App.controller.ts import BaseController from "ui5/tutorial/navigation/controller/BaseController"; /** @@ -176,6 +181,7 @@ export default class App extends BaseController { ``` ```js +// webapp/controller/App.controller.js sap.ui.define(["ui5/tutorial/navigation/controller/BaseController"], function (BaseController) { "use strict"; @@ -188,9 +194,10 @@ sap.ui.define(["ui5/tutorial/navigation/controller/BaseController"], function (B To be consistent, we will now adjust all of our controllers to extend to inherit from the `BaseController`. Change the `App` controller as described above. -## `webapp/controller/Home.controller.?s` +## `webapp/controller/Home.controller.ts/.js` ```ts +// webapp/controller/Home.controller.ts import BaseController from "ui5/tutorial/navigation/controller/BaseController"; /** @@ -202,6 +209,7 @@ export default class Home extends BaseController { ``` ```js +// webapp/controller/Home.controller.js sap.ui.define(["ui5/tutorial/navigation/controller/BaseController"], function (BaseController) { "use strict"; @@ -223,7 +231,6 @@ The same applies to our `Home` controller, we now also inherit from the `BaseCon *** - *** **Next:** [Step 5: Display a Target Without Changing the Hash](../05/README.md) diff --git a/packages/navigation/steps/04/webapp/i18n/i18n.properties b/packages/navigation/steps/04/webapp/i18n/i18n.properties index a5e577d0f..7a7cc5393 100644 --- a/packages/navigation/steps/04/webapp/i18n/i18n.properties +++ b/packages/navigation/steps/04/webapp/i18n/i18n.properties @@ -1,6 +1,6 @@ # App Descriptor appTitle=Navigation & Routing Tutorial -appDescription=A simple app that explains how to use navigation and routing features of SAPUI5 +appDescription=A simple app that explains how to use navigation and routing features of OpenUI5 iWantToNavigate=I want to navigate homePageTitle=Home diff --git a/packages/navigation/steps/05/README.md b/packages/navigation/steps/05/README.md index 5b802bac7..3557b07dc 100644 --- a/packages/navigation/steps/05/README.md +++ b/packages/navigation/steps/05/README.md @@ -1,5 +1,3 @@ - - # Step 5: Display a Target Without Changing the Hash In this step, you will learn more about targets and how to display a target from the routing configuration manually. @@ -14,9 +12,11 @@ Fortunately, we can extend our app and offer an easy solution. There are some us ![The new Home page with a navigation button](assets/Tutorial_Navigation_and_Routing_Step_05.png "The new Home page with a navigation button") +You can view this step live: [🔗 Live Preview of Step 5](https://ui5.github.io/tutorials/navigation/build/05/index-cdn.html). + ## Coding -You can view this step live: [🔗 Live Preview of Step 5](https://ui5.github.io/tutorials/navigation/build/05/index-cdn.html). +You can download the solution for this step here: [📥 Download step 5](https://ui5.github.io/tutorials/navigation/navigation-step-05.zip) (TS)[📥 Download step 5](https://ui5.github.io/tutorials/navigation/navigation-step-05-js.zip) (JS). ## webapp/view/Home.view.xml @@ -38,9 +38,10 @@ You can view this step live: [🔗 Live Preview of Step 5](https://ui5.github.io We start by changing the `Button` control from the home view. When the button is pressed, the `onDisplayNotFound` handler is called. -## `webapp/controller/Home.controller.?s` +## `webapp/controller/Home.controller.ts/.js` ```ts +// webapp/controller/Home.controller.ts import BaseController from "ui5/tutorial/navigation/controller/BaseController"; /** @@ -56,6 +57,7 @@ export default class Home extends BaseController { ``` ```js +// webapp/controller/Home.controller.js sap.ui.define(["ui5/tutorial/navigation/controller/BaseController"], function (BaseController) { "use strict"; @@ -82,9 +84,10 @@ If you now call the app and press the *Display Not Found* button you see that th When pressing the app’s *Back* button, the `onNavBack` from the previous step is called. It detects that there is no previous hash and therefore tries to navigate to the `appHome` route again. The router is smart enough to detect that the current hash did not change and therefore skips the navigation to the route. Fortunately, there is an easy workaround for us. However, we need to touch the `Home` controller again. -## `webapp/controller/Home.controller.?s` \(Changed Again\) +## `webapp/controller/Home.controller.ts/.js` \(Changed Again\) ```ts +// webapp/controller/Home.controller.ts import BaseController from "ui5/tutorial/navigation/controller/BaseController"; /** @@ -102,6 +105,7 @@ export default class Home extends BaseController { ``` ```js +// webapp/controller/Home.controller.js sap.ui.define(["ui5/tutorial/navigation/controller/BaseController"], function (BaseController) { "use strict"; @@ -119,9 +123,10 @@ sap.ui.define(["ui5/tutorial/navigation/controller/BaseController"], function (B This time we pass on a data object as the second parameter for the display method which contains the name of the current target; the one from which we navigate to the `notFound` target. We decide to choose the key `fromTarget` but since it is a custom configuration object any other key would be fine as well. -## `webapp/controller/NotFound.controller.?s` +## `webapp/controller/NotFound.controller.ts/.js` ```ts +// webapp/controller/NotFound.controller.ts import BaseController from "ui5/tutorial/navigation/controller/BaseController"; import Target, { Target$DisplayEvent } from "sap/ui/core/routing/Target"; @@ -159,6 +164,7 @@ export default class NotFound extends BaseController { ``` ```js +// webapp/controller/NotFound.controller.js sap.ui.define(["ui5/tutorial/navigation/controller/BaseController"], function (BaseController) { "use strict"; @@ -191,7 +197,7 @@ Next, we have to register an event handler to the `display` event of the `notFou From the router reference we can fetch a reference to the `notFound` target. Each target configuration will create a runtime object that can be accessed through the router. -Similar to SAPUI5 controls, targets define API methods and events that can be attached. We attach a display event handler and save the configuration that was received as the event parameter `data` in an internal controller variable `this.data`. This also includes the `fromTarget` information in case the caller passed it on. However, we now have to override the base controller’s `onNavBack` implementation to change the behavior a bit. We add a special case for our *Back* functionality in case the `fromTarget` property has been passed on. If specified, we simply display the target defined as `fromTarget` manually the same way we actually called the `notFound` target manually. Otherwise we just call the `onNavBack` implementation in the `BaseController`. +Similar to OpenUI5 controls, targets define API methods and events that can be attached. We attach a display event handler and save the configuration that was received as the event parameter `data` in an internal controller variable `this.data`. This also includes the `fromTarget` information in case the caller passed it on. However, we now have to override the base controller’s `onNavBack` implementation to change the behavior a bit. We add a special case for our *Back* functionality in case the `fromTarget` property has been passed on. If specified, we simply display the target defined as `fromTarget` manually the same way we actually called the `notFound` target manually. Otherwise we just call the `onNavBack` implementation in the `BaseController`. ## webapp/i18n/i18n.properties @@ -212,7 +218,6 @@ When we now click the *Back* button, it works as expected and brings us back to *** - *** **Next:** [Step 6: Navigate to Routes with Hard-Coded Patterns](../06/README.md) diff --git a/packages/navigation/steps/05/webapp/i18n/i18n.properties b/packages/navigation/steps/05/webapp/i18n/i18n.properties index 6a5deb0c6..44f8555f0 100644 --- a/packages/navigation/steps/05/webapp/i18n/i18n.properties +++ b/packages/navigation/steps/05/webapp/i18n/i18n.properties @@ -1,6 +1,6 @@ # App Descriptor appTitle=Navigation & Routing Tutorial -appDescription=A simple app that explains how to use navigation and routing features of SAPUI5 +appDescription=A simple app that explains how to use navigation and routing features of OpenUI5 iWantToNavigate=I want to navigate homePageTitle=Home diff --git a/packages/navigation/steps/06/README.md b/packages/navigation/steps/06/README.md index 976302cb0..c478e0c53 100644 --- a/packages/navigation/steps/06/README.md +++ b/packages/navigation/steps/06/README.md @@ -1,5 +1,3 @@ - - # Step 6: Navigate to Routes with Hard-Coded Patterns In this step, we'll create a second button on the home page, with which we can navigate to a simple list of employees. This example illustrates how to navigate to a route that has a hard-coded pattern. @@ -14,9 +12,11 @@ In this step, we'll create a second button on the home page, with which we can n ![Employee list with Back button](assets/Tutorial_Navigation_and_Routing_Step_06b.png "Employee list with Back button") +You can view this step live: [🔗 Live Preview of Step 6](https://ui5.github.io/tutorials/navigation/build/06/index-cdn.html). + ## Coding -You can view this step live: [🔗 Live Preview of Step 6](https://ui5.github.io/tutorials/navigation/build/06/index-cdn.html). +You can download the solution for this step here: [📥 Download step 6](https://ui5.github.io/tutorials/navigation/navigation-step-06.zip) (TS)[📥 Download step 6](https://ui5.github.io/tutorials/navigation/navigation-step-06-js.zip) (JS). ### Folder structure for this step @@ -24,11 +24,11 @@ You can view this step live: [🔗 Live Preview of Step 6](https://ui5.github.io webapp/ ├── controller/ │ ├── employee/ -│ │ └── EmployeeList.controller.?s -│ ├── App.controller.?s -│ ├── BaseController.?s -│ ├── Home.controller.?s -│ └── NotFound.controller.?s +│ │ └── EmployeeList.controller.ts/.js +│ ├── App.controller.ts/.js +│ ├── BaseController.ts/.js +│ ├── Home.controller.ts/.js +│ └── NotFound.controller.ts/.js ├── i18n/ │ └── i18n.properties ├── localService/ @@ -36,16 +36,16 @@ webapp/ │ │ ├── Employees.json │ │ └── Resumes.json │ ├── metadata.xml -│ └── mockserver.?s +│ └── mockserver.ts/.js ├── view/ │ ├── employee/ │ │ └── EmployeeList.view.xml │ ├── App.view.xml │ ├── Home.view.xml │ └── NotFound.view.xml -├── Component.?s +├── Component.ts/.js ├── index.html -├── initMockServer.?s +├── initMockServer.ts/.js └── manifest.json ``` @@ -70,9 +70,10 @@ webapp/ First, we change the `Home` view by adding the *Show Employee List* button. We register an event handler `onNavToEmployees` for the press event. -## `webapp/controller/Home.controller.?s` +## `webapp/controller/Home.controller.ts/.js` ```ts +// webapp/controller/Home.controller.ts import BaseController from "ui5/tutorial/navigation/controller/BaseController"; /** @@ -94,6 +95,7 @@ export default class Home extends BaseController { ``` ```js +// webapp/controller/Home.controller.js sap.ui.define(["ui5/tutorial/navigation/controller/BaseController"], function (BaseController) { "use strict"; @@ -222,9 +224,10 @@ In the view, we use a `sap.m.List` control and bind its items to the data from o This view can be referenced by `ui5.tutorial.navigation.view.employee.EmployeeList`. -## `webapp/controller/employee/EmployeeList.controller.?s` \(New\) +## `webapp/controller/employee/EmployeeList.controller.ts/.js` \(New\) ```ts +// webapp/controller/employee/EmployeeList.controller.ts import BaseController from "ui5/tutorial/navigation/controller/BaseController"; /** @@ -236,6 +239,7 @@ export default class EmployeeList extends BaseController { ``` ```js +// webapp/controller/employee/EmployeeList.controller.js sap.ui.define(["ui5/tutorial/navigation/controller/BaseController"], function (BaseController) { "use strict"; @@ -261,7 +265,6 @@ Now you can open the app and press the *Show Employee List* button to navigate t *** - *** **Next:** [Step 7: Navigate to Routes with Mandatory Parameters](../07/README.md) diff --git a/packages/navigation/steps/06/webapp/i18n/i18n.properties b/packages/navigation/steps/06/webapp/i18n/i18n.properties index 23e1a492e..fbefc39cb 100644 --- a/packages/navigation/steps/06/webapp/i18n/i18n.properties +++ b/packages/navigation/steps/06/webapp/i18n/i18n.properties @@ -1,6 +1,6 @@ # App Descriptor appTitle=Navigation & Routing Tutorial -appDescription=A simple app that explains how to use navigation and routing features of SAPUI5 +appDescription=A simple app that explains how to use navigation and routing features of OpenUI5 iWantToNavigate=I want to navigate homePageTitle=Home diff --git a/packages/navigation/steps/07/README.md b/packages/navigation/steps/07/README.md index dbc839971..d1f60cfda 100644 --- a/packages/navigation/steps/07/README.md +++ b/packages/navigation/steps/07/README.md @@ -1,5 +1,3 @@ - - # Step 7: Navigate to Routes with Mandatory Parameters In this step, we implement a feature that allows the user to click on an employee in the list to see additional details of the employee. A route pattern can have one or more mandatory parameters to identify objects in an app. @@ -20,9 +18,11 @@ The detail page has to read the ID of the employee from the URL to fetch and dis ![Not Found page for an invalid EmployeeID](assets/Tutorial_Navigation_and_Routing_Step_07c.png "Not Found page for an invalid EmployeeID") +You can view this step live: [🔗 Live Preview of Step 7](https://ui5.github.io/tutorials/navigation/build/07/index-cdn.html). + ## Coding -You can view this step live: [🔗 Live Preview of Step 7](https://ui5.github.io/tutorials/navigation/build/07/index-cdn.html). +You can download the solution for this step here: [📥 Download step 7](https://ui5.github.io/tutorials/navigation/navigation-step-07.zip) (TS)[📥 Download step 7](https://ui5.github.io/tutorials/navigation/navigation-step-07-js.zip) (JS). ### Folder structure for this step @@ -30,12 +30,12 @@ You can view this step live: [🔗 Live Preview of Step 7](https://ui5.github.io webapp/ ├── controller/ │ ├── employee/ -│ │ ├── Employee.controller.?s -│ │ └── EmployeeList.controller.?s -│ ├── App.controller.?s -│ ├── BaseController.?s -│ ├── Home.controller.?s -│ └── NotFound.controller.?s +│ │ ├── Employee.controller.ts/.js +│ │ └── EmployeeList.controller.ts/.js +│ ├── App.controller.ts/.js +│ ├── BaseController.ts/.js +│ ├── Home.controller.ts/.js +│ └── NotFound.controller.ts/.js ├── i18n/ │ └── i18n.properties ├── localService/ @@ -43,7 +43,7 @@ webapp/ │ │ ├── Employees.json │ │ └── Resumes.json │ ├── metadata.xml -│ └── mockserver.?s +│ └── mockserver.ts/.js ├── view/ │ ├── employee/ │ │ ├── Employee.view.xml @@ -51,9 +51,9 @@ webapp/ │ ├── App.view.xml │ ├── Home.view.xml │ └── NotFound.view.xml -├── Component.?s +├── Component.ts/.js ├── index.html -├── initMockServer.?s +├── initMockServer.ts/.js └── manifest.json ``` @@ -198,9 +198,10 @@ Create the file `Employee.view.xml` inside the `webapp/view/employee` folder. Th > Requiring `sap/ui/layout/form/ResponsiveGridLayout` is needed because we use the `ResponsiveGridLayout` as `layout` for the `sap/ui/layout/form/SimpleForm`. > The `sap/ui/layout/form/SimpleForm` requires the configured layout, in case it's not done by the consumer but this may cause an additional rendering cycle if rendering starts before the layout finished loading. -## `webapp/controller/employee/Employee.controller.?s` \(New\) +## `webapp/controller/employee/Employee.controller.ts/.js` \(New\) ```ts +// webapp/controller/employee/Employee.controller.ts import BaseController from "ui5/tutorial/navigation/controller/BaseController"; import { Route$MatchedEvent } from "sap/ui/core/routing/Route"; @@ -243,6 +244,7 @@ export default class Employee extends BaseController { ``` ```js +// webapp/controller/employee/Employee.controller.js sap.ui.define(["ui5/tutorial/navigation/controller/BaseController"], function (BaseController) { "use strict"; @@ -321,9 +323,10 @@ We also add an event handler to the `change` event as a private function `_onBin It’s time to change the `EmployeeList` view so that we can navigate to the new view. We set the attribute type of the `StandardListItem` template to `Navigation` to make the item clickable and indicate a navigation feature to the user. Additionally, we add an event handler for the `press` event that is called when the user clicks on an employee list item. -## `webapp/controller/employee/EmployeeList.controller.?s` +## `webapp/controller/employee/EmployeeList.controller.ts/.js` ```ts +// webapp/controller/employee/EmployeeList.controller.ts import StandardListItem from "sap/m/StandardListItem"; import BaseController from "ui5/tutorial/navigation/controller/BaseController"; import Event from "sap/ui/base/Event"; @@ -345,6 +348,7 @@ export default class EmployeeList extends BaseController { ``` ```js +// webapp/controller/employee/EmployeeList.controller.js sap.ui.define(["ui5/tutorial/navigation/controller/BaseController"], function (BaseController) { "use strict"; @@ -390,7 +394,6 @@ That’s it. You can go to `webapp/index.html#/employees` and click on any list *** - *** **Next:** [Step 8: Navigate with Flip Transition](../08/README.md) diff --git a/packages/navigation/steps/07/webapp/i18n/i18n.properties b/packages/navigation/steps/07/webapp/i18n/i18n.properties index 33c7e3a57..97b737152 100644 --- a/packages/navigation/steps/07/webapp/i18n/i18n.properties +++ b/packages/navigation/steps/07/webapp/i18n/i18n.properties @@ -1,6 +1,6 @@ # App Descriptor appTitle=Navigation & Routing Tutorial -appDescription=A simple app that explains how to use navigation and routing features of SAPUI5 +appDescription=A simple app that explains how to use navigation and routing features of OpenUI5 iWantToNavigate=I want to navigate homePageTitle=Home diff --git a/packages/navigation/steps/08/README.md b/packages/navigation/steps/08/README.md index 0b496a8fc..4fe7b2e45 100644 --- a/packages/navigation/steps/08/README.md +++ b/packages/navigation/steps/08/README.md @@ -1,5 +1,3 @@ - - # Step 8: Navigate with Flip Transition In this step, we want to illustrate how to navigate to a page with a custom transition animation. Both forward and backward navigation will use the “flip” transition but with a different direction. We will create a simple link on the `Employee` view that triggers a flip navigation to a page that displays the resume data of a certain employee. Pressing the *Back* button will navigate back to the `Employee` view with a reversed flip transition. @@ -18,9 +16,11 @@ In this step, we want to illustrate how to navigate to a page with a custom tran ![Not Found page for resume](assets/Tutorial_Navigation_and_Routing_Step_08c.png "Not Found page for resume") +You can view this step live: [🔗 Live Preview of Step 8](https://ui5.github.io/tutorials/navigation/build/08/index-cdn.html). + ## Coding -You can view this step live: [🔗 Live Preview of Step 8](https://ui5.github.io/tutorials/navigation/build/08/index-cdn.html). +You can download the solution for this step here: [📥 Download step 8](https://ui5.github.io/tutorials/navigation/navigation-step-08.zip) (TS)[📥 Download step 8](https://ui5.github.io/tutorials/navigation/navigation-step-08-js.zip) (JS). ### Folder structure for this step @@ -28,13 +28,13 @@ You can view this step live: [🔗 Live Preview of Step 8](https://ui5.github.io webapp/ ├── controller/ │ ├── employee/ -│ │ ├── Employee.controller.?s -│ │ ├── EmployeeList.controller.?s -│ │ └── Resume.controller.?s -│ ├── App.controller.?s -│ ├── BaseController.?s -│ ├── Home.controller.?s -│ └── NotFound.controller.?s +│ │ ├── Employee.controller.ts/.js +│ │ ├── EmployeeList.controller.ts/.js +│ │ └── Resume.controller.ts/.js +│ ├── App.controller.ts/.js +│ ├── BaseController.ts/.js +│ ├── Home.controller.ts/.js +│ └── NotFound.controller.ts/.js ├── i18n/ │ └── i18n.properties ├── localService/ @@ -42,7 +42,7 @@ webapp/ │ │ ├── Employees.json │ │ └── Resumes.json │ ├── metadata.xml -│ └── mockserver.?s +│ └── mockserver.ts/.js ├── view/ │ ├── employee/ │ │ ├── Employee.view.xml @@ -52,9 +52,9 @@ webapp/ │ ├── App.view.xml │ ├── Home.view.xml │ └── NotFound.view.xml -├── Component.?s +├── Component.ts/.js ├── index.html -├── initMockServer.?s +├── initMockServer.ts/.js └── manifest.json ``` @@ -99,9 +99,10 @@ webapp/ First we add the *Flip to Resume* link to the *Employee Details* view to trigger the navigation to the resume of the employee that is currently displayed. -## `webapp/controller/employee/Employee.controller.?s` +## `webapp/controller/employee/Employee.controller.ts/.js` ```ts +// webapp/controller/employee/Employee.controller.ts import BaseController from "ui5/tutorial/navigation/controller/BaseController"; import { Route$MatchedEvent } from "sap/ui/core/routing/Route"; @@ -128,6 +129,7 @@ export default class Employee extends BaseController { ``` ```js +// webapp/controller/employee/Employee.controller.js sap.ui.define(["ui5/tutorial/navigation/controller/BaseController"], function (BaseController) { "use strict"; @@ -309,11 +311,12 @@ The target `employeeResume` references the view `employee.Resume` that we are ab Create a file `Resume.view.xml` inside the `webapp/view/employee` folder. The view uses an `IconTabBar` to display the resume data. Therefore, its binding attribute is set to `{Resume}`. -In the `IconTabBar` we display four tabs. Three of them simply use a `Text` control to display the data from the service. The *Projects* tab uses a nested XML view to display the projects of the employee. SAPUI5 takes care of loading the XML view automatically when the user navigates to the *Resume* page. +In the `IconTabBar` we display four tabs. Three of them simply use a `Text` control to display the data from the service. The *Projects* tab uses a nested XML view to display the projects of the employee. OpenUI5 takes care of loading the XML view automatically when the user navigates to the *Resume* page. -## `webapp/controller/employee/Resume.controller.?s` \(New\) +## `webapp/controller/employee/Resume.controller.ts/.js` \(New\) ```ts +// webapp/controller/employee/Resume.controller.ts import BaseController from "ui5/tutorial/navigation/controller/BaseController"; import { Route$MatchedEvent } from "sap/ui/core/routing/Route"; @@ -356,6 +359,7 @@ export default class Resume extends BaseController { ``` ```js +// webapp/controller/employee/Resume.controller.js sap.ui.define(["ui5/tutorial/navigation/controller/BaseController"], function (BaseController) { "use strict"; @@ -401,10 +405,10 @@ Create a file `Resume.controller.ts` in the `webapp/controller/employee` folder. ``` -Create a file `ResumeProjects.view.xml` in the `webapp/view/employee` folder. This view does not have a controller as we don’t need it. It just displays a `Text` control with the projects text of the selected employee. It illustrates that using nested views works just fine in combination with navigation and routing in SAPUI5. +Create a file `ResumeProjects.view.xml` in the `webapp/view/employee` folder. This view does not have a controller as we don’t need it. It just displays a `Text` control with the projects text of the selected employee. It illustrates that using nested views works just fine in combination with navigation and routing in OpenUI5. > :note: -> For more complex applications, the performance is significantly increased if parts of the UI are only loaded when the user is actively selecting it. In this example, the view is always loaded even though the user never decided to display the project information. In the next steps, we will extend the UI so that the content is loaded “lazy” by SAPUI5 only when the filter item is clicked. The back-end service will fetch the data only on request and the UI will only have to be updated with the selected data instead of loading all data. +> For more complex applications, the performance is significantly increased if parts of the UI are only loaded when the user is actively selecting it. In this example, the view is always loaded even though the user never decided to display the project information. In the next steps, we will extend the UI so that the content is loaded “lazy” by OpenUI5 only when the filter item is clicked. The back-end service will fetch the data only on request and the UI will only have to be updated with the selected data instead of loading all data. ## webapp/i18n/i18n.properties @@ -425,7 +429,6 @@ You can go to `webapp/index.html#/employees/3` and click on the *Flip to Resume* *** - *** **Next:** [Step 9: Allow Bookmarkable Tabs with Optional Query Parameters](../09/README.md) diff --git a/packages/navigation/steps/08/webapp/i18n/i18n.properties b/packages/navigation/steps/08/webapp/i18n/i18n.properties index 4f8b74451..1e8ba095c 100644 --- a/packages/navigation/steps/08/webapp/i18n/i18n.properties +++ b/packages/navigation/steps/08/webapp/i18n/i18n.properties @@ -1,6 +1,6 @@ # App Descriptor appTitle=Navigation & Routing Tutorial -appDescription=A simple app that explains how to use navigation and routing features of SAPUI5 +appDescription=A simple app that explains how to use navigation and routing features of OpenUI5 iWantToNavigate=I want to navigate homePageTitle=Home diff --git a/packages/navigation/steps/09/README.md b/packages/navigation/steps/09/README.md index 524762927..69ddcdc06 100644 --- a/packages/navigation/steps/09/README.md +++ b/packages/navigation/steps/09/README.md @@ -1,5 +1,3 @@ - - # Step 9: Allow Bookmarkable Tabs with Optional Query Parameters The `resume` view contains four tabs as we have seen in the previous step of this tutorial. However, when the user navigates to the `resume` page, only the first tab is displayed initially. Navigating directly to a specific tab or bookmarking a tab is not yet supported in our current app. @@ -12,9 +10,11 @@ In this step, we implement a bookmarking feature by enabling deep linking to tab ![Deep link to allow bookmarkable tabs](assets/Tutorial_Navigation_and_Routing_Step_09.png "Deep link to allow bookmarkable tabs") +You can view this step live: [🔗 Live Preview of Step 9](https://ui5.github.io/tutorials/navigation/build/09/index-cdn.html). + ## Coding -You can view this step live: [🔗 Live Preview of Step 9](https://ui5.github.io/tutorials/navigation/build/09/index-cdn.html). +You can download the solution for this step here: [📥 Download step 9](https://ui5.github.io/tutorials/navigation/navigation-step-09.zip) (TS)[📥 Download step 9](https://ui5.github.io/tutorials/navigation/navigation-step-09-js.zip) (JS). ## webapp/manifest.json @@ -116,9 +116,10 @@ The `:?query:` parameter starts and ends with `:`, which means that it is option To update the currently selected tab in the URL we listen to the select event of the `IconTabBar` by setting `select=".onTabSelect"` in the resume view. The `selectedKey` is bound to a view model. This allows to easily change the `selectedKey` according to the selected tab in the URL. -## `webapp/controller/employee/Resume.controller.?s` +## `webapp/controller/employee/Resume.controller.ts/.js` ```ts +// webapp/controller/employee/Resume.controller.ts import { IconTabBar$SelectEvent } from "sap/m/IconTabBar"; import BaseController from "ui5/tutorial/navigation/controller/BaseController"; import { Route$MatchedEvent } from "sap/ui/core/routing/Route"; @@ -195,6 +196,7 @@ export default class Resume extends BaseController { ``` ```js +// webapp/controller/employee/Resume.controller.js sap.ui.define(["ui5/tutorial/navigation/controller/BaseController", "sap/ui/model/json/JSONModel"], function (BaseController, JSONModel) { "use strict"; @@ -286,7 +288,6 @@ When you click on any tab you will see that the hash in the URL changes immediat *** - *** **Next:** [Step 10: Implement “Lazy Loading”](../10/README.md) diff --git a/packages/navigation/steps/09/webapp/i18n/i18n.properties b/packages/navigation/steps/09/webapp/i18n/i18n.properties index 4f8b74451..1e8ba095c 100644 --- a/packages/navigation/steps/09/webapp/i18n/i18n.properties +++ b/packages/navigation/steps/09/webapp/i18n/i18n.properties @@ -1,6 +1,6 @@ # App Descriptor appTitle=Navigation & Routing Tutorial -appDescription=A simple app that explains how to use navigation and routing features of SAPUI5 +appDescription=A simple app that explains how to use navigation and routing features of OpenUI5 iWantToNavigate=I want to navigate homePageTitle=Home diff --git a/packages/navigation/steps/10/README.md b/packages/navigation/steps/10/README.md index ef04bd853..fcbc7b32b 100644 --- a/packages/navigation/steps/10/README.md +++ b/packages/navigation/steps/10/README.md @@ -1,5 +1,3 @@ - - # Step 10: Implement “Lazy Loading” In the previous steps, we have implemented a `Resume` view that uses tabs to display data. The complete content of all the tabs is loaded once, no matter which tab is currently displayed. We can increase the performance of our app by avoiding to load content that is not visible. Therefore, we implement a “lazy loading” feature that only loads the view and data when requested by the user. @@ -10,9 +8,11 @@ In the previous steps, we have implemented a `Resume` view that uses tabs to dis ![Tabs with lazy loading](assets/Tutorial_Navigation_and_Routing_Step_10a.png "Tabs with lazy loading") +You can view this step live: [🔗 Live Preview of Step 10](https://ui5.github.io/tutorials/navigation/build/10/index-cdn.html). + ## Coding -You can view this step live: [🔗 Live Preview of Step 10](https://ui5.github.io/tutorials/navigation/build/10/index-cdn.html). +You can download the solution for this step here: [📥 Download step 10](https://ui5.github.io/tutorials/navigation/navigation-step-10.zip) (TS)[📥 Download step 10](https://ui5.github.io/tutorials/navigation/navigation-step-10-js.zip) (JS). ### Folder Structure for this Step @@ -83,9 +83,10 @@ Create the file `ResumeHobbies.view.xml` in the `webapp/view/employee` folder. M Create the file `ResumeNotes.view.xml` in the `webapp/view/employee` folder similar to the *Hobbies* view to transform this tab to a separate view as well. -## `webapp/controller/employee/Resume.controller.?s` +## `webapp/controller/employee/Resume.controller.ts/.js` ```ts +// webapp/controller/employee/Resume.controller.ts import { IconTabBar$SelectEvent } from "sap/m/IconTabBar"; import BaseController from "ui5/tutorial/navigation/controller/BaseController"; import { Route$MatchedEvent } from "sap/ui/core/routing/Route"; @@ -139,6 +140,7 @@ export default class Resume extends BaseController { ``` ```js +// webapp/controller/employee/Resume.controller.js sap.ui.define(["ui5/tutorial/navigation/controller/BaseController", "sap/ui/model/json/JSONModel"], function (BaseController, JSONModel) { "use strict"; @@ -280,7 +282,7 @@ We add the `resumeTabHobbies` and `resumeTabNotes` targets to the descriptor fil The `resumeTabHobbies` target sets the parent property to `employeeResume`. The parent property expects the name of another target. In our case, this makes sure that the view from the parent target `employeeResume` is loaded before the target `resumeTabHobbies` is displayed. This can be considered as a “view dependency”. By setting the `controlId` and `controlAggregation` properties the router places the view `ResumeHobbies` into the `content` aggregation of the `IconTabFilter` control with ID `hobbiesTab`. We also set a parameter `id` to a custom ID to illustrate how you could overrule a hard-coded ID inside a view. > :note: -> Each target can define only one parent with its parent property. This is similar to the SAPUI5 control tree where each control can have only one parent control \(accessed with the method `getParent()` of `sap.ui.base.ManagedObject`\). The `controlId` property always references a control inside the parent view that is specified with the `parent` target. +> Each target can define only one parent with its parent property. This is similar to the OpenUI5 control tree where each control can have only one parent control \(accessed with the method `getParent()` of `sap.ui.base.ManagedObject`\). The `controlId` property always references a control inside the parent view that is specified with the `parent` target. Now we add the `resumeTabNotes` target similar to the `Hobbies` target. The `resumeTabNotes` target defines the parent target `employeeResume` as well, because they share the same parent view. We place the `ResumeNotes` view into the `content` aggregation of the `IconTabFilter` control with ID `notesTab`. @@ -294,7 +296,6 @@ Try it out yourself: Open the *Network* tab of your browser's developer tools an *** - *** **Next:** [Step 11: Assign Multiple Targets](../11/README.md) diff --git a/packages/navigation/steps/10/webapp/i18n/i18n.properties b/packages/navigation/steps/10/webapp/i18n/i18n.properties index 4f8b74451..1e8ba095c 100644 --- a/packages/navigation/steps/10/webapp/i18n/i18n.properties +++ b/packages/navigation/steps/10/webapp/i18n/i18n.properties @@ -1,6 +1,6 @@ # App Descriptor appTitle=Navigation & Routing Tutorial -appDescription=A simple app that explains how to use navigation and routing features of SAPUI5 +appDescription=A simple app that explains how to use navigation and routing features of OpenUI5 iWantToNavigate=I want to navigate homePageTitle=Home diff --git a/packages/navigation/steps/11/README.md b/packages/navigation/steps/11/README.md index 4e22c8bef..ad275e703 100644 --- a/packages/navigation/steps/11/README.md +++ b/packages/navigation/steps/11/README.md @@ -1,5 +1,3 @@ - - # Step 11: Assign Multiple Targets In this step, we will add a new button to the home page to illustrate the usage of multiple targets for a route. When the button is pressed, a new page opens that contains two parts: a header part at the top and a content part. The content part displays a table of employees that can be sorted and searched. We will use the array notation in the routing configuration to assign multiple targets to a route - a feature that we have not yet introduced. @@ -18,9 +16,11 @@ In this step, we will add a new button to the home page to illustrate the usage ![Sort options for the Employee Overview](assets/Tutorial_Navigation_and_Routing_Step_11c.png "Sort options for the Employee Overview") +You can view this step live: [🔗 Live Preview of Step 11](https://ui5.github.io/tutorials/navigation/build/11/index-cdn.html). + ## Coding -You can view this step live: [🔗 Live Preview of Step 11](https://ui5.github.io/tutorials/navigation/build/11/index-cdn.html). +You can download the solution for this step here: [📥 Download step 11](https://ui5.github.io/tutorials/navigation/navigation-step-11.zip) (TS)[📥 Download step 11](https://ui5.github.io/tutorials/navigation/navigation-step-11-js.zip) (JS). ### Folder Structure for this Step @@ -48,9 +48,10 @@ You can view this step live: [🔗 Live Preview of Step 11](https://ui5.github.i First we add a new button to the `Home` view and add an event handler for the `press` event. -## `webapp/controller/Home.controller.?s` +## `webapp/controller/Home.controller.ts/.js` ```ts +// webapp/controller/Home.controller.ts import BaseController from "ui5/tutorial/navigation/controller/BaseController"; /** @@ -69,6 +70,7 @@ export default class Home extends BaseController { ``` ```js +// webapp/controller/Home.controller.js sap.ui.define(["ui5/tutorial/navigation/controller/BaseController"], function (BaseController) { "use strict"; @@ -218,9 +220,10 @@ The router makes sure that the parent view is loaded in addition to the target v First we create the parent view by creating the folder `overview` under `webapp/view/employee` and placing the file `EmployeeOverview.view.xml` into that folder. This view contains a `Page` control that is referenced from the targets in our `manifest.json` descriptor file. The content aggregation of the page will be filled by the router with the top and content part when the corresponding route has been hit. -## `webapp/controller/employee/overview/EmployeeOverview.controller.?s` \(New\) +## `webapp/controller/employee/overview/EmployeeOverview.controller.ts/.js` \(New\) ```ts +// webapp/controller/employee/overview/EmployeeOverview.controller.ts import BaseController from "ui5/tutorial/navigation/controller/BaseController"; /** @@ -232,6 +235,7 @@ export default class EmployeeOverview extends BaseController { ``` ```js +// webapp/controller/employee/overview/EmployeeOverview.controller.js sap.ui.define(["ui5/tutorial/navigation/controller/BaseController"], function (BaseController) { "use strict"; @@ -301,9 +305,10 @@ Create the file `EmployeeOverviewTop.view.xml` and place it in the `webapp/view/ Create the file EmployeeOverviewContent.view.xml in the `webapp/view/employee/overview` folder. This view displays a responsive table with several columns containing employee data like *Employee ID*, *First Name*, *Last Name* and so on. In the `headerToolbar`, we add the `SearchField` and a `Button`. The `SearchField` in the header area allows to search in the table. The `Button` next to it opens a dialog to adjust the sorting of the table. -## `webapp/controller/employee/overview/EmployeeOverviewContent.controller.?s` \(New\) +## `webapp/controller/employee/overview/EmployeeOverviewContent.controller.ts/.js` \(New\) ```ts +// webapp/controller/employee/overview/EmployeeOverviewContent.controller.ts import SearchField, { SearchField$SearchEvent } from "sap/m/SearchField"; import Table from "sap/m/Table"; import ViewSettingsDialog, { ViewSettingsDialog$ConfirmEvent } from "sap/m/ViewSettingsDialog"; @@ -438,6 +443,7 @@ export default class EmployeeOverviewContent extends BaseController { ``` ```js +// webapp/controller/employee/overview/EmployeeOverviewContent.controller.js sap.ui.define(["sap/m/ViewSettingsDialog", "sap/m/ViewSettingsItem", "ui5/tutorial/navigation/controller/BaseController", "sap/ui/model/Filter", "sap/ui/model/FilterOperator", "sap/ui/model/Sorter"], function (ViewSettingsDialog, ViewSettingsItem, BaseController, Filter, FilterOperator, Sorter) { "use strict"; @@ -583,7 +589,6 @@ Add the new texts to the `properties` file. *** - *** **Next:** [Step 12: Make a Search Bookmarkable](../12/README.md) diff --git a/packages/navigation/steps/11/webapp/i18n/i18n.properties b/packages/navigation/steps/11/webapp/i18n/i18n.properties index 1e9ffae14..5e3d07bb7 100644 --- a/packages/navigation/steps/11/webapp/i18n/i18n.properties +++ b/packages/navigation/steps/11/webapp/i18n/i18n.properties @@ -1,6 +1,6 @@ # App Descriptor appTitle=Navigation & Routing Tutorial -appDescription=A simple app that explains how to use navigation and routing features of SAPUI5 +appDescription=A simple app that explains how to use navigation and routing features of OpenUI5 iWantToNavigate=I want to navigate homePageTitle=Home diff --git a/packages/navigation/steps/12/README.md b/packages/navigation/steps/12/README.md index 99b7acbac..f86358cf3 100644 --- a/packages/navigation/steps/12/README.md +++ b/packages/navigation/steps/12/README.md @@ -1,5 +1,3 @@ - - # Step 12: Make a Search Bookmarkable In this step we will make the search bookmarkable. This allows users to search for employees in the *Employees* table and they can bookmark their search query or share the URL. @@ -10,9 +8,11 @@ In this step we will make the search bookmarkable. This allows users to search f ![Search and sorting bookmarkable](assets/Tutorial_Navigation_and_Routing_Step_12.png "Search and sorting bookmarkable") +You can view this step live: [🔗 Live Preview of Step 12](https://ui5.github.io/tutorials/navigation/build/12/index-cdn.html). + ## Coding -You can view this step live: [🔗 Live Preview of Step 12](https://ui5.github.io/tutorials/navigation/build/12/index-cdn.html). +You can download the solution for this step here: [📥 Download step 12](https://ui5.github.io/tutorials/navigation/navigation-step-12.zip) (TS)[📥 Download step 12](https://ui5.github.io/tutorials/navigation/navigation-step-12-js.zip) (JS). ## webapp/manifest.json @@ -72,9 +72,10 @@ You can view this step live: [🔗 Live Preview of Step 12](https://ui5.github.i In order to make the search bookmarkable we have to think about how the pattern of the corresponding route should match the bookmark. We decide to allow `/#/employees/overview?search=mySearchQueryString` in order to bookmark a search. Therefore, we simply extend our routing configuration a little. We add the optional `:?query:` parameter to the route `employeeOverview`. We keep in mind that we want to use `search` as the URL parameter for the search term that was entered in the search field. -## `webapp/controller/employee/overview/EmployeeOverviewContent.controller.?s` +## `webapp/controller/employee/overview/EmployeeOverviewContent.controller.ts/.js` ```ts +// webapp/controller/employee/overview/EmployeeOverviewContent.controller.ts import SearchField, { SearchField$SearchEvent } from "sap/m/SearchField"; import Table from "sap/m/Table"; import ViewSettingsDialog, { ViewSettingsDialog$ConfirmEvent } from "sap/m/ViewSettingsDialog"; @@ -134,6 +135,7 @@ export default class EmployeeOverviewContent extends BaseController { ``` ```js +// webapp/controller/employee/overview/EmployeeOverviewContent.controller.js sap.ui.define(["sap/m/ViewSettingsDialog", "sap/m/ViewSettingsItem", "ui5/tutorial/navigation/controller/BaseController", "sap/ui/model/Filter", "sap/ui/model/FilterOperator", "sap/ui/model/Sorter"], function (ViewSettingsDialog, ViewSettingsItem, BaseController, Filter, FilterOperator, Sorter) { "use strict"; @@ -282,7 +284,6 @@ When you change the value in the search field, you see that the hash updates acc *** - *** **Next:** [Step 13: Make Table Sorting Bookmarkable](../13/README.md) diff --git a/packages/navigation/steps/12/webapp/i18n/i18n.properties b/packages/navigation/steps/12/webapp/i18n/i18n.properties index 1e9ffae14..5e3d07bb7 100644 --- a/packages/navigation/steps/12/webapp/i18n/i18n.properties +++ b/packages/navigation/steps/12/webapp/i18n/i18n.properties @@ -1,6 +1,6 @@ # App Descriptor appTitle=Navigation & Routing Tutorial -appDescription=A simple app that explains how to use navigation and routing features of SAPUI5 +appDescription=A simple app that explains how to use navigation and routing features of OpenUI5 iWantToNavigate=I want to navigate homePageTitle=Home diff --git a/packages/navigation/steps/13/README.md b/packages/navigation/steps/13/README.md index c0a8bbdee..5fd526559 100644 --- a/packages/navigation/steps/13/README.md +++ b/packages/navigation/steps/13/README.md @@ -1,5 +1,3 @@ - - # Step 13: Make Table Sorting Bookmarkable In this step, we will create a button at the top of the table which will change the sorting of the table. When the current sorting state of the table is changed, the sorting state will be reflected in the URL. This illustrates how to make the table sorting bookmarkable. @@ -10,13 +8,16 @@ In this step, we will create a button at the top of the table which will change ![Bookmarkable search and sorting](assets/Tutorial_Navigation_and_Routing_Step_13.png "Bookmarkable search and sorting") +You can view this step live: [🔗 Live Preview of Step 13](https://ui5.github.io/tutorials/navigation/build/13/index-cdn.html). + ## Coding -You can view this step live: [🔗 Live Preview of Step 13](https://ui5.github.io/tutorials/navigation/build/13/index-cdn.html). +You can download the solution for this step here: [📥 Download step 13](https://ui5.github.io/tutorials/navigation/navigation-step-13.zip) (TS)[📥 Download step 13](https://ui5.github.io/tutorials/navigation/navigation-step-13-js.zip) (JS). -## `webapp/controller/employee/overview/EmployeeOverviewContent.controller.?s` +## `webapp/controller/employee/overview/EmployeeOverviewContent.controller.ts/.js` ```ts +// webapp/controller/employee/overview/EmployeeOverviewContent.controller.ts import SearchField, { SearchField$SearchEvent } from "sap/m/SearchField"; import Table from "sap/m/Table"; import ViewSettingsDialog, { ViewSettingsDialog$ConfirmEvent } from "sap/m/ViewSettingsDialog"; @@ -66,6 +67,7 @@ export default class EmployeeOverviewContent extends BaseController { ``` ```js +// webapp/controller/employee/overview/EmployeeOverviewContent.controller.js sap.ui.define(["sap/m/ViewSettingsDialog", "sap/m/ViewSettingsItem", "ui5/tutorial/navigation/controller/BaseController", "sap/ui/model/Filter", "sap/ui/model/FilterOperator", "sap/ui/model/Sorter"], function (ViewSettingsDialog, ViewSettingsItem, BaseController, Filter, FilterOperator, Sorter) { "use strict"; @@ -220,7 +222,6 @@ When changing the table’s sorting options, you will see that the hash updates *** - *** **Next:** [Step 14: Make Dialogs Bookmarkable](../14/README.md) diff --git a/packages/navigation/steps/13/webapp/i18n/i18n.properties b/packages/navigation/steps/13/webapp/i18n/i18n.properties index 1e9ffae14..5e3d07bb7 100644 --- a/packages/navigation/steps/13/webapp/i18n/i18n.properties +++ b/packages/navigation/steps/13/webapp/i18n/i18n.properties @@ -1,6 +1,6 @@ # App Descriptor appTitle=Navigation & Routing Tutorial -appDescription=A simple app that explains how to use navigation and routing features of SAPUI5 +appDescription=A simple app that explains how to use navigation and routing features of OpenUI5 iWantToNavigate=I want to navigate homePageTitle=Home diff --git a/packages/navigation/steps/14/README.md b/packages/navigation/steps/14/README.md index c90077a8f..8426b8fdf 100644 --- a/packages/navigation/steps/14/README.md +++ b/packages/navigation/steps/14/README.md @@ -1,5 +1,3 @@ - - # Step 14: Make Dialogs Bookmarkable In this step, we want to allow bookmarking of the dialog box that is opened when the user clicks the *Sort* button. The dialog should automatically open when the URL contains the query parameter `showDialog`. @@ -10,13 +8,16 @@ In this step, we want to allow bookmarking of the dialog box that is opened when ![Bookmark for a dialog](assets/Tutorial_Navigation_and_Routing_Step_14.png "Bookmark for a dialog") +You can view this step live: [🔗 Live Preview of Step 14](https://ui5.github.io/tutorials/navigation/build/14/index-cdn.html). + ## Coding -You can view this step live: [🔗 Live Preview of Step 14](https://ui5.github.io/tutorials/navigation/build/14/index-cdn.html). +You can download the solution for this step here: [📥 Download step 14](https://ui5.github.io/tutorials/navigation/navigation-step-14.zip) (TS)[📥 Download step 14](https://ui5.github.io/tutorials/navigation/navigation-step-14-js.zip) (JS). ## /controller/employee/overview/EmployeeOverviewContent.controller.ts ```ts +// /controller/employee/overview/EmployeeOverviewContent.controller.ts import SearchField, { SearchField$SearchEvent } from "sap/m/SearchField"; import Table from "sap/m/Table"; import ViewSettingsDialog, { ViewSettingsDialog$ConfirmEvent, ViewSettingsDialog$CancelEvent } from "sap/m/ViewSettingsDialog"; @@ -82,6 +83,7 @@ export default class EmployeeOverviewContent extends BaseController { ``` ```js +// /controller/employee/overview/EmployeeOverviewContent.controller.js sap.ui.define(["sap/m/ViewSettingsDialog", "sap/m/ViewSettingsItem", "ui5/tutorial/navigation/controller/BaseController", "sap/ui/model/Filter", "sap/ui/model/FilterOperator", "sap/ui/model/Sorter"], function (ViewSettingsDialog, ViewSettingsItem, BaseController, Filter, FilterOperator, Sorter) { "use strict"; @@ -248,7 +250,6 @@ As you can see, the dialog opens automatically if the parameter `showDialog=1` i *** - *** **Next:** [Step 15: Reuse an Existing Route](../15/README.md) diff --git a/packages/navigation/steps/14/webapp/i18n/i18n.properties b/packages/navigation/steps/14/webapp/i18n/i18n.properties index 1e9ffae14..5e3d07bb7 100644 --- a/packages/navigation/steps/14/webapp/i18n/i18n.properties +++ b/packages/navigation/steps/14/webapp/i18n/i18n.properties @@ -1,6 +1,6 @@ # App Descriptor appTitle=Navigation & Routing Tutorial -appDescription=A simple app that explains how to use navigation and routing features of SAPUI5 +appDescription=A simple app that explains how to use navigation and routing features of OpenUI5 iWantToNavigate=I want to navigate homePageTitle=Home diff --git a/packages/navigation/steps/15/README.md b/packages/navigation/steps/15/README.md index 84ff89a08..b48d040de 100644 --- a/packages/navigation/steps/15/README.md +++ b/packages/navigation/steps/15/README.md @@ -1,5 +1,3 @@ - - # Step 15: Reuse an Existing Route The *Employees* table displays employee data. However, the resumes of the employees are not accessible from this view yet. We could create a new route and a new view to visualize the resume again, but we could also simply reuse an existing route to cross-link the resume of a certain employee. In this step, we will add a feature that allows users to directly navigate to the resume of a certain employee. We will reuse the *Resume* page that we have created in an earlier step. This example illustrates that there can be multiple navigation paths that direct to the same page. @@ -10,9 +8,11 @@ The *Employees* table displays employee data. However, the resumes of the employ ![Navigation to an existing route from a table item](assets/Tutorial_Navigation_and_Routing_Step_15.png "Navigation to an existing route from a table item") +You can view this step live: [🔗 Live Preview of Step 15](https://ui5.github.io/tutorials/navigation/build/15/index-cdn.html). + ## Coding -You can view this step live: [🔗 Live Preview of Step 15](https://ui5.github.io/tutorials/navigation/build/15/index-cdn.html). +You can download the solution for this step here: [📥 Download step 15](https://ui5.github.io/tutorials/navigation/navigation-step-15.zip) (TS)[📥 Download step 15](https://ui5.github.io/tutorials/navigation/navigation-step-15-js.zip) (JS). ## webapp/view/employee/overview/EmployeeOverviewContent.view.xml @@ -43,9 +43,10 @@ You can view this step live: [🔗 Live Preview of Step 15](https://ui5.github.i In the `EmployeeOverviewContent` view we register an event handler for the `itemPress` event and set the type attribute of the `ColumnListItem` to `Active` so that we can choose an item and trigger the navigation. -## `webapp/controller/employee/overview/EmployeeOverviewContent.controller.?s` +## `webapp/controller/employee/overview/EmployeeOverviewContent.controller.ts/.js` ```ts +// webapp/controller/employee/overview/EmployeeOverviewContent.controller.ts import { ListBase$ItemPressEvent } from "sap/m/ListBase"; import SearchField, { SearchField$SearchEvent } from "sap/m/SearchField"; import Table from "sap/m/Table"; @@ -79,6 +80,7 @@ export default class EmployeeOverviewContent extends BaseController { ``` ```js +// webapp/controller/employee/overview/EmployeeOverviewContent.controller.js sap.ui.define(["sap/m/ViewSettingsDialog", "sap/m/ViewSettingsItem", "ui5/tutorial/navigation/controller/BaseController", "sap/ui/model/Filter", "sap/ui/model/FilterOperator", "sap/ui/model/Sorter"], function (ViewSettingsDialog, ViewSettingsItem, BaseController, Filter, FilterOperator, Sorter) { "use strict"; @@ -245,7 +247,6 @@ Next we add the `itemPress` handler `.onItemPressed` to the `EmployeeOverviewCon *** - *** **Next:** [Step 16: Handle Invalid Hashes by Listening to Bypassed Events](../16/README.md) diff --git a/packages/navigation/steps/15/webapp/i18n/i18n.properties b/packages/navigation/steps/15/webapp/i18n/i18n.properties index 1e9ffae14..5e3d07bb7 100644 --- a/packages/navigation/steps/15/webapp/i18n/i18n.properties +++ b/packages/navigation/steps/15/webapp/i18n/i18n.properties @@ -1,6 +1,6 @@ # App Descriptor appTitle=Navigation & Routing Tutorial -appDescription=A simple app that explains how to use navigation and routing features of SAPUI5 +appDescription=A simple app that explains how to use navigation and routing features of OpenUI5 iWantToNavigate=I want to navigate homePageTitle=Home diff --git a/packages/navigation/steps/16/README.md b/packages/navigation/steps/16/README.md index 8e94e3cc3..620994555 100644 --- a/packages/navigation/steps/16/README.md +++ b/packages/navigation/steps/16/README.md @@ -1,5 +1,3 @@ - - # Step 16: Handle Invalid Hashes by Listening to Bypassed Events So far we have created many useful routes in our app. In the very early steps we have also made sure that a *Not Found* page is displayed in case the app was called with an invalid hash. Now, we proceed further and track invalid hashes to be able to detect and correct any invalid links or add new URL patterns that are often requested but not found. Therefore, we simply listen to the bypassed events @@ -10,13 +8,16 @@ So far we have created many useful routes in our app. In the very early steps we ![Console output for invalid hashes when listening to bypassed events](assets/Tutorial_Navigation_and_Routing_Step_16.png "Console output for invalid hashes when listening to bypassed events") +You can view this step live: [🔗 Live Preview of Step 16](https://ui5.github.io/tutorials/navigation/build/16/index-cdn.html). + ## Coding -You can view this step live: [🔗 Live Preview of Step 16](https://ui5.github.io/tutorials/navigation/build/16/index-cdn.html). +You can download the solution for this step here: [📥 Download step 16](https://ui5.github.io/tutorials/navigation/navigation-step-16.zip) (TS)[📥 Download step 16](https://ui5.github.io/tutorials/navigation/navigation-step-16-js.zip) (JS). -## `webapp/controller/App.controller.?s` +## `webapp/controller/App.controller.ts/.js` ```ts +// webapp/controller/App.controller.ts import BaseController from "ui5/tutorial/navigation/controller/BaseController"; import Log from "sap/base/Log"; import { Router$BypassedEvent } from "sap/ui/core/routing/Router"; @@ -47,6 +48,7 @@ export default class App extends BaseController { ``` ```js +// webapp/controller/App.controller.js sap.ui.define(["ui5/tutorial/navigation/controller/BaseController", "sap/base/Log"], function (BaseController, Log) { "use strict"; @@ -80,7 +82,6 @@ Now try to access `webapp/index.html#/thisIsInvalid` while you have your browser *** - *** **Next:** [Step 17: Listen to Matched Events of Any Route](../17/README.md) diff --git a/packages/navigation/steps/16/webapp/i18n/i18n.properties b/packages/navigation/steps/16/webapp/i18n/i18n.properties index 1e9ffae14..5e3d07bb7 100644 --- a/packages/navigation/steps/16/webapp/i18n/i18n.properties +++ b/packages/navigation/steps/16/webapp/i18n/i18n.properties @@ -1,6 +1,6 @@ # App Descriptor appTitle=Navigation & Routing Tutorial -appDescription=A simple app that explains how to use navigation and routing features of SAPUI5 +appDescription=A simple app that explains how to use navigation and routing features of OpenUI5 iWantToNavigate=I want to navigate homePageTitle=Home diff --git a/packages/navigation/steps/17/README.md b/packages/navigation/steps/17/README.md index b671ae77d..5e4b7d68f 100644 --- a/packages/navigation/steps/17/README.md +++ b/packages/navigation/steps/17/README.md @@ -1,5 +1,3 @@ - - # Step 17: Listen to Matched Events of Any Route In the previous step, we have listened for bypassed events to detect possible technical issues with our app. In this step, we want to improve the analysis use case even more by listening to any matched event of the route. We could use this information to measure how the app is used and how frequently the pages are called. Many Web analytic tools track page hits this way. The collected information can be used, for example to improve our app and its usability. @@ -10,13 +8,16 @@ In the previous step, we have listened for bypassed events to detect possible te ![Console output for routes matched by listening to routeMatched events](assets/Tutorial_Navigation_and_Routing_Step_17.png "Console output for routes matched by listening to routeMatched events") +You can view this step live: [🔗 Live Preview of Step 17](https://ui5.github.io/tutorials/navigation/build/17/index-cdn.html). + ## Coding -You can view this step live: [🔗 Live Preview of Step 17](https://ui5.github.io/tutorials/navigation/build/17/index-cdn.html). +You can download the solution for this step here: [📥 Download step 17](https://ui5.github.io/tutorials/navigation/navigation-step-17.zip) (TS)[📥 Download step 17](https://ui5.github.io/tutorials/navigation/navigation-step-17-js.zip) (JS). -## `webapp/controller/App.controller.?s` +## `webapp/controller/App.controller.ts/.js` ```ts +// webapp/controller/App.controller.ts import BaseController from "ui5/tutorial/navigation/controller/BaseController"; import Log from "sap/base/Log"; import { Router$BypassedEvent, Router$RouteMatchedEvent } from "sap/ui/core/routing/Router"; @@ -43,7 +44,6 @@ export default class App extends BaseController { Log.info(`Sorry, but the hash '${hash}' is invalid.`, "The resource was not found."); }); - router.attachRouteMatched((event: Router$RouteMatchedEvent) => { const sRouteName = event.getParameter("name"); // do something, i.e. send usage statistics to backend @@ -55,6 +55,7 @@ export default class App extends BaseController { ``` ```js +// webapp/controller/App.controller.js sap.ui.define(["ui5/tutorial/navigation/controller/BaseController", "sap/base/Log"], function (BaseController, Log) { "use strict"; @@ -89,7 +90,6 @@ We extend the `App` controller again and listen to the `routeMatched` event. The Now you can access, for example, `webapp/index.html#/employees` while you have the console of the browser open. As you can see, there is a message logged for each navigation step that you do within the app. - *** **Previous:** [Step 16: Handle Invalid Hashes by Listening to Bypassed Events](../16/README.md) diff --git a/packages/navigation/steps/17/webapp/i18n/i18n.properties b/packages/navigation/steps/17/webapp/i18n/i18n.properties index 1e9ffae14..5e3d07bb7 100644 --- a/packages/navigation/steps/17/webapp/i18n/i18n.properties +++ b/packages/navigation/steps/17/webapp/i18n/i18n.properties @@ -1,6 +1,6 @@ # App Descriptor appTitle=Navigation & Routing Tutorial -appDescription=A simple app that explains how to use navigation and routing features of SAPUI5 +appDescription=A simple app that explains how to use navigation and routing features of OpenUI5 iWantToNavigate=I want to navigate homePageTitle=Home diff --git a/packages/odatav4/README.md b/packages/odatav4/README.md index a82e957ef..08cf9f9fc 100644 --- a/packages/odatav4/README.md +++ b/packages/odatav4/README.md @@ -23,17 +23,17 @@ We start with an initial app that simply retrieves data from an OData V4 service The tutorial consists of the following steps. To start, just open the first link — you'll be guided from there. -- **[Step 1: The Initial App](./steps/01/README.md "We start by setting up a simple app that loads data from an OData service and displays it in a table. We use a mock server to simulate requests to and responses from the service.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/odatav4/build/01/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/odatav4/odatav4-step-01.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/odatav4/odatav4-step-01-js.zip)
) -- **[Step 2: Data Access and Client-Server Communication](./steps/02/README.md "In this step, we see how the Table that is bound to the People entity set initially requests its data, and how the data can be refreshed.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/odatav4/build/02/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/odatav4/odatav4-step-02.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/odatav4/odatav4-step-02-js.zip)
) -- **[Step 3: Automatic Data Type Detection](./steps/03/README.md "In this step, we use the automatic data type detection of the OData V4 model to parse, validate, and format user entries.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/odatav4/build/03/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/odatav4/odatav4-step-03.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/odatav4/odatav4-step-03-js.zip)
) -- **[Step 4: Filtering, Sorting, and Counting](./steps/04/README.md "In this step, we add features to filter, sort, and count the user data by using the OData V4 model API to apply OData system query options $filter, $orderby, and $count.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/odatav4/build/04/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/odatav4/odatav4-step-04.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/odatav4/odatav4-step-04-js.zip)
) -- **[Step 5: Batch Groups](./steps/05/README.md "In this step, we have a closer look at batch groups. Batch groups are used to group multiple requests into one server request to improve the overall performance.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/odatav4/build/05/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/odatav4/odatav4-step-05.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/odatav4/odatav4-step-05-js.zip)
) -- **[Step 6: Create and Edit](./steps/06/README.md "In this step, we will make it possible to create and edit (update) user data from the user interface and send the data to the back end.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/odatav4/build/06/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/odatav4/odatav4-step-06.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/odatav4/odatav4-step-06-js.zip)
) -- **[Step 7: Delete](./steps/07/README.md "In this step, we make it possible to delete user data.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/odatav4/build/07/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/odatav4/odatav4-step-07.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/odatav4/odatav4-step-07-js.zip)
) -- **[Step 8: OData Operations](./steps/08/README.md "Our OData service provides one OData operation: the ResetDataSource action. In this step, we add a button that resets all data changes we made during the tutorial to their original state using this action.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/odatav4/build/08/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/odatav4/odatav4-step-08.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/odatav4/odatav4-step-08-js.zip)
) -- **[Step 9: List-Detail Scenario](./steps/09/README.md "In this step we add a detail area with additional information.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/odatav4/build/09/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/odatav4/odatav4-step-09.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/odatav4/odatav4-step-09-js.zip)
) -- **[Step 10: Enable Data Reuse](./steps/10/README.md "In this step we avoid unnecessary back-end requests by preventing the destruction of data shown in the detail area when sorting or filtering the list.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/odatav4/build/10/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/odatav4/odatav4-step-10.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/odatav4/odatav4-step-10-js.zip)
) -- **[Step 11: Add Table with :n Navigation to Detail Area](./steps/11/README.md "In this step we add a table with additional information to the detail area.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/odatav4/build/11/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/odatav4/odatav4-step-11.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/odatav4/odatav4-step-11-js.zip)
) +- **[Step 1: The Initial App](./steps/01/README.md)** — We start by setting up a simple app that loads data from an OData service and displays it in a table. We use a mock server to simulate requests to and responses from the service. ([🔗 Live Preview](https://ui5.github.io/tutorials/odatav4/build/01/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/odatav4/odatav4-step-01.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/odatav4/odatav4-step-01-js.zip) (JS)) +- **[Step 2: Data Access and Client-Server Communication](./steps/02/README.md)** — In this step, we see how the Table that is bound to the People entity set initially requests its data, and how the data can be refreshed. ([🔗 Live Preview](https://ui5.github.io/tutorials/odatav4/build/02/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/odatav4/odatav4-step-02.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/odatav4/odatav4-step-02-js.zip) (JS)) +- **[Step 3: Automatic Data Type Detection](./steps/03/README.md)** — In this step, we use the automatic data type detection of the OData V4 model to parse, validate, and format user entries. ([🔗 Live Preview](https://ui5.github.io/tutorials/odatav4/build/03/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/odatav4/odatav4-step-03.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/odatav4/odatav4-step-03-js.zip) (JS)) +- **[Step 4: Filtering, Sorting, and Counting](./steps/04/README.md)** — In this step, we add features to filter, sort, and count the user data by using the OData V4 model API to apply OData system query options $filter, $orderby, and $count. ([🔗 Live Preview](https://ui5.github.io/tutorials/odatav4/build/04/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/odatav4/odatav4-step-04.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/odatav4/odatav4-step-04-js.zip) (JS)) +- **[Step 5: Batch Groups](./steps/05/README.md)** — In this step, we have a closer look at batch groups. Batch groups are used to group multiple requests into one server request to improve the overall performance. ([🔗 Live Preview](https://ui5.github.io/tutorials/odatav4/build/05/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/odatav4/odatav4-step-05.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/odatav4/odatav4-step-05-js.zip) (JS)) +- **[Step 6: Create and Edit](./steps/06/README.md)** — In this step, we will make it possible to create and edit (update) user data from the user interface and send the data to the back end. ([🔗 Live Preview](https://ui5.github.io/tutorials/odatav4/build/06/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/odatav4/odatav4-step-06.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/odatav4/odatav4-step-06-js.zip) (JS)) +- **[Step 7: Delete](./steps/07/README.md)** — In this step, we make it possible to delete user data. ([🔗 Live Preview](https://ui5.github.io/tutorials/odatav4/build/07/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/odatav4/odatav4-step-07.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/odatav4/odatav4-step-07-js.zip) (JS)) +- **[Step 8: OData Operations](./steps/08/README.md)** — Our OData service provides one OData operation: the ResetDataSource action. In this step, we add a button that resets all data changes we made during the tutorial to their original state using this action. ([🔗 Live Preview](https://ui5.github.io/tutorials/odatav4/build/08/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/odatav4/odatav4-step-08.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/odatav4/odatav4-step-08-js.zip) (JS)) +- **[Step 9: List-Detail Scenario](./steps/09/README.md)** — In this step we add a detail area with additional information. ([🔗 Live Preview](https://ui5.github.io/tutorials/odatav4/build/09/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/odatav4/odatav4-step-09.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/odatav4/odatav4-step-09-js.zip) (JS)) +- **[Step 10: Enable Data Reuse](./steps/10/README.md)** — In this step we avoid unnecessary back-end requests by preventing the destruction of data shown in the detail area when sorting or filtering the list. ([🔗 Live Preview](https://ui5.github.io/tutorials/odatav4/build/10/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/odatav4/odatav4-step-10.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/odatav4/odatav4-step-10-js.zip) (JS)) +- **[Step 11: Add Table with :n Navigation to Detail Area](./steps/11/README.md)** — In this step we add a table with additional information to the detail area. ([🔗 Live Preview](https://ui5.github.io/tutorials/odatav4/build/11/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/odatav4/odatav4-step-11.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/odatav4/odatav4-step-11-js.zip) (JS)) *** diff --git a/packages/odatav4/steps.json b/packages/odatav4/steps.json new file mode 100644 index 000000000..ac10ddad4 --- /dev/null +++ b/packages/odatav4/steps.json @@ -0,0 +1,105 @@ +{ + "tutorial": "odatav4", + "namespace": "ui5.tutorial.odatav4", + "steps": [ + { + "n": 1, + "id": "01", + "title": "The Initial App", + "previewUrl": "https://ui5.github.io/tutorials/odatav4/build/01/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/odatav4/odatav4-step-01.zip", + "zipJs": "https://ui5.github.io/tutorials/odatav4/odatav4-step-01-js.zip", + "description": "We start by setting up a simple app that loads data from an OData service and displays it in a table. We use a mock server to simulate requests to and responses from the service." + }, + { + "n": 2, + "id": "02", + "title": "Data Access and Client-Server Communication", + "previewUrl": "https://ui5.github.io/tutorials/odatav4/build/02/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/odatav4/odatav4-step-02.zip", + "zipJs": "https://ui5.github.io/tutorials/odatav4/odatav4-step-02-js.zip", + "description": "In this step, we see how the Table that is bound to the People entity set initially requests its data, and how the data can be refreshed. We use the Console tab in the browser developer tools to monitor the communication between the browser and the server. We see the initial request as well as the requests for refreshing the data." + }, + { + "n": 3, + "id": "03", + "title": "Automatic Data Type Detection", + "previewUrl": "https://ui5.github.io/tutorials/odatav4/build/03/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/odatav4/odatav4-step-03.zip", + "zipJs": "https://ui5.github.io/tutorials/odatav4/odatav4-step-03-js.zip", + "description": "In this step, we use the automatic data type detection of the OData V4 model to parse, validate, and format user entries. The service metadata contains type information for the properties of each entity." + }, + { + "n": 4, + "id": "04", + "title": "Filtering, Sorting, and Counting", + "previewUrl": "https://ui5.github.io/tutorials/odatav4/build/04/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/odatav4/odatav4-step-04.zip", + "zipJs": "https://ui5.github.io/tutorials/odatav4/odatav4-step-04-js.zip", + "description": "In this step, we add features to filter, sort, and count the user data by using the OData V4 model API to apply OData system query options $filter, $orderby, and $count." + }, + { + "n": 5, + "id": "05", + "title": "Batch Groups", + "previewUrl": "https://ui5.github.io/tutorials/odatav4/build/05/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/odatav4/odatav4-step-05.zip", + "zipJs": "https://ui5.github.io/tutorials/odatav4/odatav4-step-05-js.zip", + "description": "In this step, we have a closer look at batch groups. Batch groups are used to group multiple requests into one server request to improve the overall performance." + }, + { + "n": 6, + "id": "06", + "title": "Create and Edit", + "previewUrl": "https://ui5.github.io/tutorials/odatav4/build/06/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/odatav4/odatav4-step-06.zip", + "zipJs": "https://ui5.github.io/tutorials/odatav4/odatav4-step-06-js.zip", + "description": "In this step, we will make it possible to create and edit \\(update\\) user data from the user interface and send the data to the back end." + }, + { + "n": 7, + "id": "07", + "title": "Delete", + "previewUrl": "https://ui5.github.io/tutorials/odatav4/build/07/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/odatav4/odatav4-step-07.zip", + "zipJs": "https://ui5.github.io/tutorials/odatav4/odatav4-step-07-js.zip", + "description": "In this step, we make it possible to delete user data." + }, + { + "n": 8, + "id": "08", + "title": "OData Operations", + "previewUrl": "https://ui5.github.io/tutorials/odatav4/build/08/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/odatav4/odatav4-step-08.zip", + "zipJs": "https://ui5.github.io/tutorials/odatav4/odatav4-step-08-js.zip", + "description": "Our OData service provides one OData operation: the ResetDataSource action. In this step, we add a button that resets all data changes we made during the tutorial to their original state using this action." + }, + { + "n": 9, + "id": "09", + "title": "List-Detail Scenario", + "previewUrl": "https://ui5.github.io/tutorials/odatav4/build/09/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/odatav4/odatav4-step-09.zip", + "zipJs": "https://ui5.github.io/tutorials/odatav4/odatav4-step-09-js.zip", + "description": "In this step we add a detail area with additional information." + }, + { + "n": 10, + "id": "10", + "title": "Enable Data Reuse", + "previewUrl": "https://ui5.github.io/tutorials/odatav4/build/10/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/odatav4/odatav4-step-10.zip", + "zipJs": "https://ui5.github.io/tutorials/odatav4/odatav4-step-10-js.zip", + "description": "In this step we avoid unnecessary back-end requests by preventing the destruction of data shown in the detail area when sorting or filtering the list." + }, + { + "n": 11, + "id": "11", + "title": "Add Table with :n Navigation to Detail Area", + "previewUrl": "https://ui5.github.io/tutorials/odatav4/build/11/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/odatav4/odatav4-step-11.zip", + "zipJs": "https://ui5.github.io/tutorials/odatav4/odatav4-step-11-js.zip", + "description": "In this step we add a table with additional information to the detail area." + } + ] +} diff --git a/packages/odatav4/steps/01/README.md b/packages/odatav4/steps/01/README.md index 3ac1be673..c61a68f24 100644 --- a/packages/odatav4/steps/01/README.md +++ b/packages/odatav4/steps/01/README.md @@ -2,35 +2,28 @@ We start by setting up a simple app that loads data from an OData service and displays it in a table. We use a mock server to simulate requests to and responses from the service. -The structure and data model created in this step will be used throughout this tutorial to illustrate the OData V4 features in SAPUI5. +The structure and data model created in this step will be used throughout this tutorial to illustrate the OData V4 features in OpenUI5. ## Preview **Initial app with a simple table** -![](assets/Tutorial_OData_V4_Step1_Preview_9d0182f.png "Initial app with a simple table") - -
- -You can download the solution for this step here: [📥 Download step 1](https://ui5.github.io/tutorials/odatav4/odatav4-step-01.zip). - -
- -
- -You can download the solution for this step here: [📥 Download step 1](https://ui5.github.io/tutorials/odatav4/odatav4-step-01-js.zip). - -
+![Initial app with a simple table](assets/Tutorial_OData_V4_Step1_Preview_9d0182f.png "Initial app with a simple table") *** ## Setup -1. To set up your project for this tutorial, download the files at [OData V4 - Step 1](https://ui5.github.io/tutorials/odatav4/odatav4-step-01.zip). +The initial code for this step ships with the repository at [packages/odatav4/steps/01/](./). Open it and run: + +```sh +npm install +npm start +``` + +This starts the local web server and opens a browser window hosting `index.html`. -2. Extract the downloaded `.zip` file at the desired location on your local machine. -3. Open a shell in the extracted folder and run `npm install`. -4. Run `npm start` to start the web server and to open a new browser window hosting your newly created `index.html`. +If you prefer to work outside the monorepo, download the standalone bundle at [OData V4 - Step 1](https://ui5.github.io/tutorials/odatav4/odatav4-step-01.zip) (TS)[OData V4 - Step 1](https://ui5.github.io/tutorials/odatav4/odatav4-step-01-js.zip) (JS), extract it, and run the same two commands from the extracted folder. You should now have the following files: @@ -38,22 +31,22 @@ You should now have the following files: ```text webapp/ -├── Component.?s +├── Component.ts/.js ├── controller/ -│ └── App.controller.?s +│ └── App.controller.ts/.js ├── i18n/ │ └── i18n.properties ├── index-cdn.html ├── index.html -├── initMockServer.?s +├── initMockServer.ts/.js ├── localService/ │ ├── metadata.xml │ ├── mockdata/ │ │ └── people.json -│ └── mockserver.?s +│ └── mockserver.ts/.js ├── manifest.json ├── model/ -│ └── models.?s +│ └── models.ts/.js └── view/ └── App.view.xml ``` @@ -92,7 +85,7 @@ The `manifest.json` descriptor file contains the app configuration. In the `sap. > :note: > The mock server included in this tutorial is only meant to support the features needed in this tutorial. Currently, there is no "general-purpose mock server" for application development available with OData V4 \(like there is for OData V2\). -The `mockserver.?s` file contains the implementation of the mock server. It is quite simple since the mock server is only used to simulate certain types of requests to the *TripPin* service. +The `mockserver.ts/.js` file contains the implementation of the mock server. It is quite simple since the mock server is only used to simulate certain types of requests to the *TripPin* service. The `metadata.xml` file contains the service metadata that includes, for example, entity types and entity sets. Those define the possible requests as well as the structure of responses. @@ -104,9 +97,9 @@ In this tutorial, we only use the entity type `Person` of the *TripPin* service. [*OData* Reference Services including *TripPin*](http://www.odata.org/odata-services/) -[Bootstrapping: Loading and Initializing](../04_Essentials/bootstrapping-loading-and-initializing-a04b0d1.md "To use SAPUI5 features in your HTML page, you have to load and initialize the SAPUI5 library.") +[Bootstrapping: Loading and Initializing](https://sdk.openui5.org/topic/a04b0d10fb494d1cb722b9e341b584ba.html "To use OpenUI5 features in your HTML page, you have to load and initialize the OpenUI5 library.") -[Descriptor for Applications, Components, and Libraries \(manifest.json\)](../04_Essentials/descriptor-for-applications-components-and-libraries-manifest-json-be0cf40.md "The descriptor for applications, components, and libraries (in short: app descriptor) is inspired by the WebApplication Manifest concept introduced by the W3C. The descriptor provides a central, machine-readable, and easy-to-access location for storing metadata associated with an application, an application component, or a library.") +[Descriptor for Applications, Components, and Libraries \(manifest.json\)](https://sdk.openui5.org/topic/be0cf40f61184b358b5faedaec98b2da.html "The descriptor for applications, components, and libraries (in short: app descriptor) is inspired by the WebApplication Manifest concept introduced by the W3C. The descriptor provides a central, machine-readable, and easy-to-access location for storing metadata associated with an application, an application component, or a library.") *** diff --git a/packages/odatav4/steps/02/README.md b/packages/odatav4/steps/02/README.md index 40098f4b6..d8fe08866 100644 --- a/packages/odatav4/steps/02/README.md +++ b/packages/odatav4/steps/02/README.md @@ -1,33 +1,25 @@ # Step 2: Data Access and Client-Server Communication -
- -You can download the solution for this step here: [📥 Download step 2](https://ui5.github.io/tutorials/odatav4/odatav4-step-02.zip). - -
- -
- -You can download the solution for this step here: [📥 Download step 2](https://ui5.github.io/tutorials/odatav4/odatav4-step-02-js.zip). - -
- In this step, we see how the `Table` that is bound to the `People` entity set initially requests its data, and how the data can be refreshed. We use the *Console* tab in the browser developer tools to monitor the communication between the browser and the server. We see the initial request as well as the requests for refreshing the data. ## Preview **App with a toolbar that contains a Refresh button** -![](assets/Tutorial_OData_V4_Step_2_0abcbb6.png "App with a toolbar that contains a Refresh +![App with a toolbar that contains a Refresh + button](assets/Tutorial_OData_V4_Step_2_0abcbb6.png "App with a toolbar that contains a Refresh button") +You can view this step live: [🔗 Live Preview of Step 2](https://ui5.github.io/tutorials/odatav4/build/02/index-cdn.html). + ## Coding -You can view this step live: [🔗 Live Preview of Step 2](https://ui5.github.io/tutorials/odatav4/build/02/index-cdn.html). +You can download the solution for this step here: [📥 Download step 2](https://ui5.github.io/tutorials/odatav4/odatav4-step-02.zip) (TS)[📥 Download step 2](https://ui5.github.io/tutorials/odatav4/odatav4-step-02-js.zip) (JS). -## `webapp/controller/App.controller.?s` +## `webapp/controller/App.controller.ts/.js` ```ts +// webapp/controller/App.controller.ts import Controller from "sap/ui/core/mvc/Controller"; import MessageToast from "sap/m/MessageToast"; import MessageBox from "sap/m/MessageBox"; @@ -91,6 +83,7 @@ export default class App extends Controller { ``` ```js +// webapp/controller/App.controller.js sap.ui.define(["sap/ui/core/mvc/Controller", "sap/m/MessageToast", "sap/m/MessageBox", "sap/ui/model/json/JSONModel"], function (Controller, MessageToast, MessageBox, JSONModel) { "use strict"; @@ -234,13 +227,13 @@ We search for the following mock server requests: **Related Information** -[Bindings](../04_Essentials/bindings-54e0ddf.md "Bindings connect SAPUI5 view elements to model data, allowing changes in the model to be reflected in the view element and vice versa.") +[Bindings](https://sdk.openui5.org/topic/54e0ddf695af4a6c978472cecb01c64d "Bindings connect OpenUI5 view elements to model data, allowing changes in the model to be reflected in the view element and vice versa.") -[API Reference: `sap.ui.model.odata.v4.ODataMetaModel`](https://ui5.sap.com/#/api/sap.ui.model.odata.v4.ODataMetaModel) +[API Reference: `sap.ui.model.odata.v4.ODataMetaModel`](https://sdk.openui5.org/#/api/sap.ui.model.odata.v4.ODataMetaModel) -[API Reference: `sap.ui.model.odata.v4.ODataListBinding.refresh`](https://ui5.sap.com/#/api/sap.ui.model.odata.v4.ODataListBinding/methods/refresh) +[API Reference: `sap.ui.model.odata.v4.ODataListBinding.refresh`](https://sdk.openui5.org/#/api/sap.ui.model.odata.v4.ODataListBinding/methods/refresh) -[Troubleshooting Tutorial Step 1: Browser Developer Tools](step-1-browser-developer-tools-eadd60a.md "In this step, you will learn how to use your browser's developers tools to troubleshoot your SAPUI5 app.") +[Troubleshooting Tutorial Step 1: Browser Developer Tools](step-1-browser-developer-tools-eadd60a.md "In this step, you will learn how to use your browser's developers tools to troubleshoot your OpenUI5 app.") *** diff --git a/packages/odatav4/steps/03/README.md b/packages/odatav4/steps/03/README.md index 6fd58301c..147814c30 100644 --- a/packages/odatav4/steps/03/README.md +++ b/packages/odatav4/steps/03/README.md @@ -1,32 +1,22 @@ # Step 3: Automatic Data Type Detection -
- -You can download the solution for this step here: [📥 Download step 3](https://ui5.github.io/tutorials/odatav4/odatav4-step-03.zip). - -
- -
- -You can download the solution for this step here: [📥 Download step 3](https://ui5.github.io/tutorials/odatav4/odatav4-step-03-js.zip). - -
- In this step, we use the automatic data type detection of the OData V4 model to parse, validate, and format user entries. The service metadata contains type information for the properties of each entity. -The OData V4 Model utilizes this information to compute the corresponding SAPUI5 type, including constraints, and sets this type to the SAPUI5 property binding for the entity property. For example, for `` the SAPUI5 type `Int64` is used, which corresponds to the OData type `Edm.Int64`. +The OData V4 Model utilizes this information to compute the corresponding OpenUI5 type, including constraints, and sets this type to the OpenUI5 property binding for the entity property. For example, for `` the OpenUI5 type `Int64` is used, which corresponds to the OData type `Edm.Int64`. ## Preview **Input does not match the underlying data type** -![](assets/Tutorial_OData_V4_Step_3_8320fcf.png "Input does not match the underlying data type") +![Input does not match the underlying data type](assets/Tutorial_OData_V4_Step_3_8320fcf.png "Input does not match the underlying data type") + +You can view this step live: [🔗 Live Preview of Step 3](https://ui5.github.io/tutorials/odatav4/build/03/index-cdn.html). ## Coding -You can view this step live: [🔗 Live Preview of Step 3](https://ui5.github.io/tutorials/odatav4/build/03/index-cdn.html). +You can download the solution for this step here: [📥 Download step 3](https://ui5.github.io/tutorials/odatav4/odatav4-step-03.zip) (TS)[📥 Download step 3](https://ui5.github.io/tutorials/odatav4/odatav4-step-03-js.zip) (JS). -## `webapp/manifest.?s`on +## `webapp/manifest.json` ```json { @@ -61,9 +51,9 @@ You can view this step live: [🔗 Live Preview of Step 3](https://ui5.github.io ``` -In the `manifest.json` descriptor file, we add the `"handleValidation": true` setting. This makes sure that any validation errors that are detected by the SAPUI5 types are shown on the UI using the message manager. +In the `manifest.json` descriptor file, we add the `"handleValidation": true` setting. This makes sure that any validation errors that are detected by the OpenUI5 types are shown on the UI using the message manager. -We now run the app using the `index.html` file and enter values that don't match the type and constraints given in the metadata file. For example, enter the string value `Young at Heart` in field *Age*, which requires an integer input \(SAPUI5 type `sap.ui.model.odata.type.Int64`, corresponding to OData type `Edm.Int64`\), or remove an entry from the *User Name* or *First Name* fields, which are mandatory. Fields with incorrect entries are highlighted and an error message is displayed. +We now run the app using the `index.html` file and enter values that don't match the type and constraints given in the metadata file. For example, enter the string value `Young at Heart` in field *Age*, which requires an integer input \(OpenUI5 type `sap.ui.model.odata.type.Int64`, corresponding to OData type `Edm.Int64`\), or remove an entry from the *User Name* or *First Name* fields, which are mandatory. Fields with incorrect entries are highlighted and an error message is displayed. > :note: > If you explicitly define a type in the binding info of a control, the automatic type detection for that binding will be turned off. For example, if you change the `Input` for `Age` in the view to ` - -You can download the solution for this step here: [📥 Download step 4](https://ui5.github.io/tutorials/odatav4/odatav4-step-04.zip). - - - -
- -You can download the solution for this step here: [📥 Download step 4](https://ui5.github.io/tutorials/odatav4/odatav4-step-04-js.zip). - -
- In this step, we add features to filter, sort, and count the user data by using the OData V4 model API to apply OData system query options `$filter`, `$orderby`, and `$count`. ## Preview **App now has a search field, the entries can be sorted, and you can see how many entities are loaded and how many more are available** -![](assets/Tutorial_OData_V4_Step_4_3ac4fcc.png "App now has a search field, the entries can be sorted, and you can see how +![App now has a search field, the entries can be sorted, and you can see how + many entities are loaded and how many more are available](assets/Tutorial_OData_V4_Step_4_3ac4fcc.png "App now has a search field, the entries can be sorted, and you can see how many entities are loaded and how many more are available") +You can view this step live: [🔗 Live Preview of Step 4](https://ui5.github.io/tutorials/odatav4/build/04/index-cdn.html). + ## Coding -You can view this step live: [🔗 Live Preview of Step 4](https://ui5.github.io/tutorials/odatav4/build/04/index-cdn.html). +You can download the solution for this step here: [📥 Download step 4](https://ui5.github.io/tutorials/odatav4/odatav4-step-04.zip) (TS)[📥 Download step 4](https://ui5.github.io/tutorials/odatav4/odatav4-step-04-js.zip) (JS). -## `webapp/controller/App.controller.?s` +## `webapp/controller/App.controller.ts/.js` ```ts +// webapp/controller/App.controller.ts import Controller from "sap/ui/core/mvc/Controller"; import MessageToast from "sap/m/MessageToast"; import MessageBox from "sap/m/MessageBox"; @@ -84,6 +76,7 @@ import JSONModel from "sap/ui/model/json/JSONModel"; ``` ```js +// webapp/controller/App.controller.js sap.ui.define([ "sap/ui/core/mvc/Controller", "sap/m/MessageToast", @@ -286,9 +279,9 @@ We add the missing texts to the properties file. **Related Information** -[Filtering](../04_Essentials/filtering-5338bd1.md "The OData V4 Model supports server side filtering on lists.") +[Filtering](https://sdk.openui5.org/topic/5338bd1f9afb45fb8b2af957c3530e8f "The OData V4 Model supports server side filtering on lists.") -[Sorting](../04_Essentials/sorting-d2ce3f5.md "The OData V4 model supports server side sorting on lists.") +[Sorting](https://sdk.openui5.org/topic/d2ce3f51e5e34198b0c1a7f6ddd98def "The OData V4 model supports server side sorting on lists.") [Query Options under *Querying Data* in the Basic Tutorial on the OData home page](http://www.odata.org/getting-started/basic-tutorial/#queryData) diff --git a/packages/odatav4/steps/05/README.md b/packages/odatav4/steps/05/README.md index 029b790a9..5f736c3ea 100644 --- a/packages/odatav4/steps/05/README.md +++ b/packages/odatav4/steps/05/README.md @@ -1,30 +1,20 @@ # Step 5: Batch Groups -
- -You can download the solution for this step here: [📥 Download step 5](https://ui5.github.io/tutorials/odatav4/odatav4-step-05.zip). - -
- -
- -You can download the solution for this step here: [📥 Download step 5](https://ui5.github.io/tutorials/odatav4/odatav4-step-05-js.zip). - -
- In this step, we have a closer look at batch groups. Batch groups are used to group multiple requests into one server request to improve the overall performance. ## Preview **No visual change compared to the last step** -![](assets/Tutorial_OData_V4_Step_4_3ac4fcc.png "No visual change compared to the last step") +![No visual change compared to the last step](assets/Tutorial_OData_V4_Step_4_3ac4fcc.png "No visual change compared to the last step") + +You can view this step live: [🔗 Live Preview of Step 5](https://ui5.github.io/tutorials/odatav4/build/05/index-cdn.html). ## Coding -You can view this step live: [🔗 Live Preview of Step 5](https://ui5.github.io/tutorials/odatav4/build/05/index-cdn.html). +You can download the solution for this step here: [📥 Download step 5](https://ui5.github.io/tutorials/odatav4/odatav4-step-05.zip) (TS)[📥 Download step 5](https://ui5.github.io/tutorials/odatav4/odatav4-step-05-js.zip) (JS). -## `webapp/manifest.?s`on +## `webapp/manifest.json` ```json ... @@ -55,9 +45,9 @@ A `$batch` request uses multipart MIME to put several requests into one. This ma **Related Information** -[Batch Control](../04_Essentials/batch-control-74142a3.md "OData V4 allows you to group multiple operations into a single HTTP request payload, as described in the official OData V4 specification Part 1, Batch Requests (see the link under Related Information for more details).") +[Batch Control](https://sdk.openui5.org/topic/74142a38e3d4467c8d6a70b28764048f "OData V4 allows you to group multiple operations into a single HTTP request payload, as described in the official OData V4 specification Part 1, Batch Requests (see the link under Related Information for more details).") -[Performance Aspects](../04_Essentials/performance-aspects-5a0d286.md "The OData V4 model offers the features described below which influence performance.") +[Performance Aspects](https://sdk.openui5.org/topic/5a0d286c5606424b8e0d663c87445733 "The OData V4 model offers the features described below which influence performance.") *** diff --git a/packages/odatav4/steps/06/README.md b/packages/odatav4/steps/06/README.md index 89ae78a27..fc4104188 100644 --- a/packages/odatav4/steps/06/README.md +++ b/packages/odatav4/steps/06/README.md @@ -1,32 +1,23 @@ # Step 6: Create and Edit -
- -You can download the solution for this step here: [📥 Download step 6](https://ui5.github.io/tutorials/odatav4/odatav4-step-06.zip). - -
- -
- -You can download the solution for this step here: [📥 Download step 6](https://ui5.github.io/tutorials/odatav4/odatav4-step-06-js.zip). - -
- In this step, we will make it possible to create and edit \(update\) user data from the user interface and send the data to the back end. ## Preview **Data can now be edited and added.** -![](assets/Tutorial_OData_V4_Step_6_baf7417.png "Data can now be edited and added.") +![Data can now be edited and added.](assets/Tutorial_OData_V4_Step_6_baf7417.png "Data can now be edited and added.") + +You can view this step live: [🔗 Live Preview of Step 6](https://ui5.github.io/tutorials/odatav4/build/06/index-cdn.html). ## Coding -You can view this step live: [🔗 Live Preview of Step 6](https://ui5.github.io/tutorials/odatav4/build/06/index-cdn.html). +You can download the solution for this step here: [📥 Download step 6](https://ui5.github.io/tutorials/odatav4/odatav4-step-06.zip) (TS)[📥 Download step 6](https://ui5.github.io/tutorials/odatav4/odatav4-step-06-js.zip) (JS). -## `webapp/controller/App.controller.?s` +## `webapp/controller/App.controller.ts/.js` ```ts +// webapp/controller/App.controller.ts ... onInit() { const oMessageManager = sap.ui.getCore().getMessageManager(), @@ -48,6 +39,7 @@ You can view this step live: [🔗 Live Preview of Step 6](https://ui5.github.io ``` ```js +// webapp/controller/App.controller.js ... onInit : function () { var oMessageManager = sap.ui.getCore().getMessageManager(), @@ -72,6 +64,7 @@ You can view this step live: [🔗 Live Preview of Step 6](https://ui5.github.io We change the `onInit` method: The `appView` model receives two additional properties, which we will use to control whether certain controls in the view are enabled or visible during user entries. We also make the `MessageModel` available to the view and add a `ListBinding`. When the OData service reports errors while writing data, the OData Model adds them to the `MessageModel` as technical messages. Therefore we apply a filter to the `ListBinding`. We register our own handler to the `change` event of that `ListBinding` in order to capture any errors. ```ts +// webapp/controller/App.controller.ts ... onSort() { ... @@ -94,6 +87,7 @@ We change the `onInit` method: The `appView` model receives two additional prope ``` ```js +// webapp/controller/App.controller.js ... onSort : function () { ... @@ -119,6 +113,7 @@ We change the `onInit` method: The `appView` model receives two additional prope We add the `_setUIChanges` private method that lets us set the property `hasUIChanges` of the `appView` model. Unless there are currently technical messages in the `MessageModel` or it is called with a given value for its `bHasUIChanges` parameter, the method uses `ODataModel.hasPendingChanges`. That method returns `true` if there are any changes that have not yet been written to the service. ```ts +// webapp/controller/App.controller.ts ... onInit() { ... @@ -147,6 +142,7 @@ We add the `_setUIChanges` private method that lets us set the property `hasUICh ``` ```js +// webapp/controller/App.controller.js ... onInit: function () { ... @@ -181,6 +177,7 @@ We add the `onCreate` event handler that responds to the `press` event of the *A We also use the binding context returned by the `create` method to focus and select the new row in which the new data can be entered. ```ts +// webapp/controller/App.controller.ts ... onRefresh() { ... @@ -217,6 +214,7 @@ We also use the binding context returned by the `create` method to focus and sel ``` ```js +// webapp/controller/App.controller.js ... onRefresh: function () { ... @@ -260,6 +258,7 @@ The `submitBatch` method returns a `Promise` that is rejected only if the batch We also define a `_setBusy` private function to lock the whole UI while the data is submitted to the back end. ```ts +// webapp/controller/App.controller.ts ... onSort() { ... @@ -294,6 +293,7 @@ We also define a `_setBusy` private function to lock the whole UI while the data ``` ```js +// webapp/controller/App.controller.js ... onSort : function () { ... @@ -331,6 +331,7 @@ We also define a `_setBusy` private function to lock the whole UI while the data We implement the event handler for the `change` event of the `ListBinding` to the `MessageModel`. We created the `ListBinding` with a filter to only include technical messages. That means that the `change` event will be fired with every change but only technical messages will have a binding context. In case of technical messages, we get the first one and display it as an error. We also make sure that the toolbar for saving or discarding changes stays visible. We delete the technical messages so that they do not accumulate. ```ts +// webapp/controller/App.controller.ts ... onRefresh() { ... @@ -347,6 +348,7 @@ We implement the event handler for the `change` event of the `ListBinding` to th ``` ```js +// webapp/controller/App.controller.js ... onRefresh: function () { ... @@ -365,6 +367,7 @@ We implement the event handler for the `change` event of the `ListBinding` to th The `onResetChanges` method handles discarding pending changes. It uses the `resetChanges` method of the `ODataListBinding` API to remove any such changes. Then it calls the `_setUIChanges` private method to enable the elements of the header toolbar again and hide the footer. ```ts +// webapp/controller/App.controller.ts ... onCreate() { ... @@ -386,6 +389,7 @@ The `onResetChanges` method handles discarding pending changes. It uses the `res ``` ```js +// webapp/controller/App.controller.js ... onCreate: function () { ... @@ -538,9 +542,9 @@ We add the `$$updateGroupId: 'peopleGroup'` parameter to the table. This means t We add a new *Add User* button to the overflow toolbar in the table header, and define a footer toolbar that contains *Save* and *Cancel* buttons that we can display or hide through the `appView` model. We can disable the *Save* button separately, for example when a user enters invalid data. -Finally, we add the `liveChange="onInputChange"` event handler to the table cells to make it possible to react to user input. In addition, we set the `valueLiveUpdate` properties for the fields for `UserName` and `Age`. That makes sure that the SAPUI5 types validate the field content with each keystroke. +Finally, we add the `liveChange="onInputChange"` event handler to the table cells to make it possible to react to user input. In addition, we set the `valueLiveUpdate` properties for the fields for `UserName` and `Age`. That makes sure that the OpenUI5 types validate the field content with each keystroke. -Creation via a form is demonstrated in our [Sales Orders sample app](https://ui5.sap.com/#/entity/sap.ui.model.odata.v4.ODataModel/sample/sap.ui.core.sample.odata.v4.SalesOrders). +Creation via a form is demonstrated in our [Sales Orders sample app](https://sdk.openui5.org/#/entity/sap.ui.model.odata.v4.ODataModel/sample/sap.ui.core.sample.odata.v4.SalesOrders). ## webapp/i18n/i18n.properties @@ -568,17 +572,17 @@ We add the new message texts. **Related Information** -[Model Instantiation and Data Access](../04_Essentials/model-instantiation-and-data-access-9613f1f.md "One OData V4 model instance can only cover one OData service. This section describes the creation of a model instance in more detail.") +[Model Instantiation and Data Access](https://sdk.openui5.org/topic/9613f1f2d88747cab21896f7216afdac "One OData V4 model instance can only cover one OData service. This section describes the creation of a model instance in more detail.") -[Batch Control](../04_Essentials/batch-control-74142a3.md "OData V4 allows you to group multiple operations into a single HTTP request payload, as described in the official OData V4 specification Part 1, Batch Requests (see the link under Related Information for more details).") +[Batch Control](https://sdk.openui5.org/topic/74142a38e3d4467c8d6a70b28764048f "OData V4 allows you to group multiple operations into a single HTTP request payload, as described in the official OData V4 specification Part 1, Batch Requests (see the link under Related Information for more details).") -[OData Operations](../04_Essentials/odata-operations-b54f789.md "The OData V4 model supports OData operations (ActionImport, FunctionImport, bound Actions and bound Functions). Unbound parameters are limited to primitive values.") +[OData Operations](https://sdk.openui5.org/topic/b54f7895b7594c61a83fa7257fa9d13f "The OData V4 model supports OData operations (ActionImport, FunctionImport, bound Actions and bound Functions). Unbound parameters are limited to primitive values.") -[Creating an Entity](../04_Essentials/creating-an-entity-c9723f8.md "The sap.ui.model.odata.v4.ODataListBinding#create method creates a new entity. Users can interact with a newly created entity even before it has been sent to the server.") +[Creating an Entity](https://sdk.openui5.org/topic/c9723f8265f644af91c0ed941e114d46 "The sap.ui.model.odata.v4.ODataListBinding#create method creates a new entity. Users can interact with a newly created entity even before it has been sent to the server.") -[Message Model](../04_Essentials/message-model-8956f0a.md "The message model contains all messages and is used to bind to the messages to display them.") +[Message Model](https://sdk.openui5.org/topic/8956f0a223284d729900ebad4ca88356 "The message model contains all messages and is used to bind to the messages to display them.") -[API Reference: `sap.ui.model.odata.v4.ODataContextBinding`](https://ui5.sap.com/#/api/sap.ui.model.odata.v4.ODataContextBinding) +[API Reference: `sap.ui.model.odata.v4.ODataContextBinding`](https://sdk.openui5.org/#/api/sap.ui.model.odata.v4.ODataContextBinding) *** diff --git a/packages/odatav4/steps/07/README.md b/packages/odatav4/steps/07/README.md index d2511133f..a4044f2df 100644 --- a/packages/odatav4/steps/07/README.md +++ b/packages/odatav4/steps/07/README.md @@ -1,30 +1,20 @@ # Step 7: Delete -
- -You can download the solution for this step here: [📥 Download step 7](https://ui5.github.io/tutorials/odatav4/odatav4-step-07.zip). - -
- -
- -You can download the solution for this step here: [📥 Download step 7](https://ui5.github.io/tutorials/odatav4/odatav4-step-07-js.zip). - -
- In this step, we make it possible to delete user data. ## Preview **A new Delete User button is added** -![](assets/Tutorial_OData_V4_Step_7_32509f4.png "A new Delete User button is added") +![A new Delete User button is added](assets/Tutorial_OData_V4_Step_7_32509f4.png "A new Delete User button is added") + +You can view this step live: [🔗 Live Preview of Step 7](https://ui5.github.io/tutorials/odatav4/build/07/index-cdn.html). ## Coding -You can view this step live: [🔗 Live Preview of Step 7](https://ui5.github.io/tutorials/odatav4/build/07/index-cdn.html). +You can download the solution for this step here: [📥 Download step 7](https://ui5.github.io/tutorials/odatav4/odatav4-step-07.zip) (TS)[📥 Download step 7](https://ui5.github.io/tutorials/odatav4/odatav4-step-07-js.zip) (JS). -## `webapp/App.controller.?s` +## `webapp/App.controller.ts/.js` ``` ... @@ -192,7 +182,7 @@ We add the missing texts to the properties file. **Related Information** -[Deleting an Entity](../04_Essentials/deleting-an-entity-2613ebc.md "The v4.Context.delete method deletes an entity on the server and updates the user interface accordingly.") +[Deleting an Entity](https://sdk.openui5.org/topic/2613ebc835764abd9aefd2e6fa8b7392 "The v4.Context.delete method deletes an entity on the server and updates the user interface accordingly.") *** diff --git a/packages/odatav4/steps/08/README.md b/packages/odatav4/steps/08/README.md index 9d0649974..3dc4f2009 100644 --- a/packages/odatav4/steps/08/README.md +++ b/packages/odatav4/steps/08/README.md @@ -1,32 +1,23 @@ # Step 8: OData Operations -
- -You can download the solution for this step here: [📥 Download step 8](https://ui5.github.io/tutorials/odatav4/odatav4-step-08.zip). - -
- -
- -You can download the solution for this step here: [📥 Download step 8](https://ui5.github.io/tutorials/odatav4/odatav4-step-08-js.zip). - -
- Our OData service provides one OData operation: the `ResetDataSource` action. In this step, we add a button that resets all data changes we made during the tutorial to their original state using this action. ## Preview **A Restart Tutorial button is added** -![](assets/Tutorial_OData_V4_Step_8_e518deb.png "A Restart Tutorial button is added") +![A Restart Tutorial button is added](assets/Tutorial_OData_V4_Step_8_e518deb.png "A Restart Tutorial button is added") + +You can view this step live: [🔗 Live Preview of Step 8](https://ui5.github.io/tutorials/odatav4/build/08/index-cdn.html). ## Coding -You can view this step live: [🔗 Live Preview of Step 8](https://ui5.github.io/tutorials/odatav4/build/08/index-cdn.html). +You can download the solution for this step here: [📥 Download step 8](https://ui5.github.io/tutorials/odatav4/odatav4-step-08.zip) (TS)[📥 Download step 8](https://ui5.github.io/tutorials/odatav4/odatav4-step-08-js.zip) (JS). -## `webapp/controller/App.controller.?s` +## `webapp/controller/App.controller.ts/.js` ```ts +// webapp/controller/App.controller.ts ... onResetChanges() { this.byId("peopleList").getBinding("items").resetChanges(); @@ -51,6 +42,7 @@ You can view this step live: [🔗 Live Preview of Step 8](https://ui5.github.io ``` ```js +// webapp/controller/App.controller.js ... onResetChanges : function () { this.byId("peopleList").getBinding("items").resetChanges(); @@ -82,7 +74,7 @@ We call that action by first creating a deferred operation binding on the model. The invocation is asynchronous; the `invoke` method therefore returns a `Promise`. We attach simple success and error handlers to that `Promise` by calling its `then` method. > :note: -> Many of the methods in the OData V4 API of SAPUI5 return a `Promise` to manage asynchronous processing +> Many of the methods in the OData V4 API of OpenUI5 return a `Promise` to manage asynchronous processing ## webapp/view/App.view.xml @@ -131,9 +123,9 @@ And now we are done! We built a simple application with user data from an OData **Related Information** -[Bindings](../04_Essentials/bindings-54e0ddf.md "Bindings connect SAPUI5 view elements to model data, allowing changes in the model to be reflected in the view element and vice versa.") +[Bindings](https://sdk.openui5.org/topic/54e0ddf695af4a6c978472cecb01c64d "Bindings connect OpenUI5 view elements to model data, allowing changes in the model to be reflected in the view element and vice versa.") -[OData Operations](../04_Essentials/odata-operations-b54f789.md "The OData V4 model supports OData operations (ActionImport, FunctionImport, bound Actions and bound Functions). Unbound parameters are limited to primitive values.") +[OData Operations](https://sdk.openui5.org/topic/b54f7895b7594c61a83fa7257fa9d13f "The OData V4 model supports OData operations (ActionImport, FunctionImport, bound Actions and bound Functions). Unbound parameters are limited to primitive values.") *** diff --git a/packages/odatav4/steps/09/README.md b/packages/odatav4/steps/09/README.md index 87bcf975e..4350817f7 100644 --- a/packages/odatav4/steps/09/README.md +++ b/packages/odatav4/steps/09/README.md @@ -1,17 +1,5 @@ # Step 9: List-Detail Scenario -
- -You can download the solution for this step here: [📥 Download step 9](https://ui5.github.io/tutorials/odatav4/odatav4-step-09.zip). - -
- -
- -You can download the solution for this step here: [📥 Download step 9](https://ui5.github.io/tutorials/odatav4/odatav4-step-09-js.zip). - -
- In this step we add a detail area with additional information. ## Preview @@ -20,13 +8,16 @@ In this step we add a detail area with additional information. ![A list of users with an added detail area](assets/Tut_OD4_Step_9_6e9025b.png "A detail area containing information about the selected user is added") +You can view this step live: [🔗 Live Preview of Step 9](https://ui5.github.io/tutorials/odatav4/build/09/index-cdn.html). + ## Coding -You can view this step live: [🔗 Live Preview of Step 9](https://ui5.github.io/tutorials/odatav4/build/09/index-cdn.html). +You can download the solution for this step here: [📥 Download step 9](https://ui5.github.io/tutorials/odatav4/odatav4-step-09.zip) (TS)[📥 Download step 9](https://ui5.github.io/tutorials/odatav4/odatav4-step-09-js.zip) (JS). -## `webapp/controller/App.controller.?s` +## `webapp/controller/App.controller.ts/.js` ```ts +// webapp/controller/App.controller.ts ... onDelete() { const oContext, @@ -84,6 +75,7 @@ You can view this step live: [🔗 Live Preview of Step 9](https://ui5.github.io ``` ```js +// webapp/controller/App.controller.js ... onDelete : function () { var oContext, @@ -355,7 +347,7 @@ We add the missing texts to the properties file. **Related Information** -[Data Reuse](../04_Essentials/data-reuse-648e360.md "The OData V4 model keeps data with respect to bindings, which allows different views on the same data, but also means that data is not automatically shared between bindings. There are mechanisms for sharing data to avoid redundant requests and to keep the same data in different controls in sync.") +[Data Reuse](https://sdk.openui5.org/topic/648e360fa22d46248ca783dc6eb44531 "The OData V4 model keeps data with respect to bindings, which allows different views on the same data, but also means that data is not automatically shared between bindings. There are mechanisms for sharing data to avoid redundant requests and to keep the same data in different controls in sync.") *** diff --git a/packages/odatav4/steps/10/README.md b/packages/odatav4/steps/10/README.md index 5c9061d30..0f9c66096 100644 --- a/packages/odatav4/steps/10/README.md +++ b/packages/odatav4/steps/10/README.md @@ -1,17 +1,5 @@ # Step 10: Enable Data Reuse -
- -You can download the solution for this step here: [📥 Download step 10](https://ui5.github.io/tutorials/odatav4/odatav4-step-10.zip). - -
- -
- -You can download the solution for this step here: [📥 Download step 10](https://ui5.github.io/tutorials/odatav4/odatav4-step-10-js.zip). - -
- In this step we avoid unnecessary back-end requests by preventing the destruction of data shown in the detail area when sorting or filtering the list. ## Preview @@ -20,13 +8,16 @@ In this step we avoid unnecessary back-end requests by preventing the destructio ![A list of users with an added detail area](assets/Tut_OD4_Step_9_6e9025b.png "No visual change compared to the last step") +You can view this step live: [🔗 Live Preview of Step 10](https://ui5.github.io/tutorials/odatav4/build/10/index-cdn.html). + ## Coding -You can view this step live: [🔗 Live Preview of Step 10](https://ui5.github.io/tutorials/odatav4/build/10/index-cdn.html). +You can download the solution for this step here: [📥 Download step 10](https://ui5.github.io/tutorials/odatav4/odatav4-step-10.zip) (TS)[📥 Download step 10](https://ui5.github.io/tutorials/odatav4/odatav4-step-10-js.zip) (JS). -## `webapp/controller/App.controller.?s` +## `webapp/controller/App.controller.ts/.js` ```ts +// webapp/controller/App.controller.ts ... onMessageBindingChange(oEvent) { ... @@ -72,6 +63,7 @@ You can view this step live: [🔗 Live Preview of Step 10](https://ui5.github.i ``` ```js +// webapp/controller/App.controller.js ... onMessageBindingChange : function (oEvent) { ... @@ -124,7 +116,7 @@ You can use the `Context#setKeepAlive` method to prevent the destruction of info **Related Information** -[Extending the Lifetime of a Context that is not Used Exclusively by a Table Collection](../04_Essentials/data-reuse-648e360.md#loio648e360fa22d46248ca783dc6eb44531__section_ELC) +[Extending the Lifetime of a Context that is not Used Exclusively by a Table Collection](https://sdk.openui5.org/topic/648e360fa22d46248ca783dc6eb44531.html#loio648e360fa22d46248ca783dc6eb44531/section_ELC) *** diff --git a/packages/odatav4/steps/11/README.md b/packages/odatav4/steps/11/README.md index 0363e192b..ab0602a00 100644 --- a/packages/odatav4/steps/11/README.md +++ b/packages/odatav4/steps/11/README.md @@ -1,28 +1,18 @@ # Step 11: Add Table with :n Navigation to Detail Area -
- -You can download the solution for this step here: [📥 Download step 11](https://ui5.github.io/tutorials/odatav4/odatav4-step-11.zip). - -
- -
- -You can download the solution for this step here: [📥 Download step 11](https://ui5.github.io/tutorials/odatav4/odatav4-step-11-js.zip). - -
- In this step we add a table with additional information to the detail area. ## Preview **A table containing information about friends of the selected user is added** -![](assets/Tut_OD4_Step_11_45abd62.png "A table containing information about friends of the selected user is added") +![A table containing information about friends of the selected user is added](assets/Tut_OD4_Step_11_45abd62.png "A table containing information about friends of the selected user is added") + +You can view this step live: [🔗 Live Preview of Step 11](https://ui5.github.io/tutorials/odatav4/build/11/index-cdn.html). ## Coding -You can view this step live: [🔗 Live Preview of Step 11](https://ui5.github.io/tutorials/odatav4/build/11/index-cdn.html). +You can download the solution for this step here: [📥 Download step 11](https://ui5.github.io/tutorials/odatav4/odatav4-step-11.zip) (TS)[📥 Download step 11](https://ui5.github.io/tutorials/odatav4/odatav4-step-11-js.zip) (JS). ## webapp/view/App.view.xml diff --git a/packages/quickstart/README.md b/packages/quickstart/README.md index 4b4c339d6..618b4f173 100644 --- a/packages/quickstart/README.md +++ b/packages/quickstart/README.md @@ -20,9 +20,9 @@ We first introduce you to the basic development paradigms like *Model-View-Contr The tutorial consists of the following steps. To start, just open the first link - you`ll be guided from there. -- **[Step 1: Ready...](steps/01/README.md "Let's get you ready for your journey! We bootstrap OpenUI5 in an HTML page and implement a simple "Hello World" example.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/quickstart/build/01/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/quickstart/quickstart-step-01.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/quickstart/quickstart-step-01-js.zip)
) -- **[Step 2: Steady...](steps/02/README.md "Now we extend our minimalist HTML page to a basic app with a view and a controller.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/quickstart/build/02/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/quickstart/quickstart-step-02.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/quickstart/quickstart-step-02-js.zip)
) -- **[Step 3: Go!](steps/03/README.md "Now it is time to build our first little UI by replacing the "Hello World" text in the HTML body by the OpenUI5 control sap/m/Text. In the beginning, we will use the JavaScript control interface to set up the UI, the control instance is then placed into the HTML body. ")** ([🔗 Live Preview](https://ui5.github.io/tutorials/quickstart/build/03/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/quickstart/quickstart-step-03.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/quickstart/quickstart-step-03-js.zip)
) +- **[Step 1: Ready...](steps/01/README.md)** — Let's get you ready for your journey! We bootstrap OpenUI5 in an HTML page and implement a simple "Hello World" example. ([🔗 Live Preview](https://ui5.github.io/tutorials/quickstart/build/01/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/quickstart/quickstart-step-01.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/quickstart/quickstart-step-01-js.zip) (JS) ) +- **[Step 2: Steady...](steps/02/README.md)** — Now we extend our minimalist HTML page to a basic app with a view and a controller. ([🔗 Live Preview](https://ui5.github.io/tutorials/quickstart/build/02/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/quickstart/quickstart-step-02.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/quickstart/quickstart-step-02-js.zip) (JS) ) +- **[Step 3: Go!](steps/03/README.md)** — Now it is time to build our first little UI by replacing the "Hello World" text in the HTML body by the OpenUI5 control sap/m/Text. In the beginning, we will use the JavaScript control interface to set up the UI, the control instance is then placed into the HTML body. ([🔗 Live Preview](https://ui5.github.io/tutorials/quickstart/build/03/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/quickstart/quickstart-step-03.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/quickstart/quickstart-step-03-js.zip) (JS) ) ## License diff --git a/packages/quickstart/steps.json b/packages/quickstart/steps.json new file mode 100644 index 000000000..2d43da8a6 --- /dev/null +++ b/packages/quickstart/steps.json @@ -0,0 +1,33 @@ +{ + "tutorial": "quickstart", + "namespace": "ui5.tutorial.quickstart", + "steps": [ + { + "n": 1, + "id": "01", + "title": "Ready...", + "previewUrl": "https://ui5.github.io/tutorials/quickstart/build/01/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/quickstart/quickstart-step-01.zip", + "zipJs": "https://ui5.github.io/tutorials/quickstart/quickstart-step-01-js.zip", + "description": "Let's get you ready for your journey! We bootstrap OpenUI5 in an HTML page and implement a simple \"Hello World\" example." + }, + { + "n": 2, + "id": "02", + "title": "Steady...", + "previewUrl": "https://ui5.github.io/tutorials/quickstart/build/02/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/quickstart/quickstart-step-02.zip", + "zipJs": "https://ui5.github.io/tutorials/quickstart/quickstart-step-02-js.zip", + "description": "Now we extend our minimalist HTML page to a basic app with a view and a controller." + }, + { + "n": 3, + "id": "03", + "title": "Go!", + "previewUrl": "https://ui5.github.io/tutorials/quickstart/build/03/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/quickstart/quickstart-step-03.zip", + "zipJs": "https://ui5.github.io/tutorials/quickstart/quickstart-step-03-js.zip", + "description": "Finally, we add a second page to our app showcasing some of the key OpenUI5 concepts." + } + ] +} diff --git a/packages/quickstart/steps/01/README.md b/packages/quickstart/steps/01/README.md index 87a3402a1..47e8795a1 100644 --- a/packages/quickstart/steps/01/README.md +++ b/packages/quickstart/steps/01/README.md @@ -9,7 +9,7 @@ Let's get you ready for your journey! We bootstrap OpenUI5 in an HTML page and i ### Preview -![](assets/loio9c157e9764b846fea7de519d141c33ac_LowRes.png "The browser shows a "Ready" button that triggers a "Hello World" message") +![The browser shows a "Ready" button that triggers a "Hello World" message](assets/loio9c157e9764b846fea7de519d141c33ac_LowRes.png "The browser shows a Ready button that triggers a Hello World message") *The browser shows a "Ready" button that triggers a "Hello World" message* @@ -19,17 +19,7 @@ You can access the live preview by clicking on this link: [🔗 Live Preview of ### Coding -
- -You can download the solution for this step here: [📥 Download step 1](https://ui5.github.io/tutorials/quickstart-step-01.zip). - -
- -
- -You can download the solution for this step here: [📥 Download step 1](https://ui5.github.io/tutorials/quickstart-step-01-js.zip). - -
+You can download the solution for this step here: [📥 Download step 1](https://ui5.github.io/tutorials/quickstart-step-01.zip) (TS)[📥 Download step 1](https://ui5.github.io/tutorials/quickstart-step-01-js.zip) (JS). *** @@ -75,11 +65,12 @@ With the `script` tag, we load and initialize OpenUI5 with typical bootstrap par *** -### webapp/index.?s \(New\) +### webapp/index.ts/.js \(New\) In your `webapp` folder, create a new file `index.js` that will be called as soon as OpenUI5 is loaded and initialized. We load two UI controls - a button and a message toast - and place the button in the element with the `content` ID. The button is defined with a `text` property and a callback attached to its `press` event. ```ts +// webapp/index.ts import Button from "sap/m/Button"; import MessageToast from "sap/m/MessageToast"; @@ -92,6 +83,7 @@ new Button({ ``` ```js +// webapp/index.js sap.ui.define([ "sap/m/Button", "sap/m/MessageToast" diff --git a/packages/quickstart/steps/02/README.md b/packages/quickstart/steps/02/README.md index d6f5dbfb6..a2f511546 100644 --- a/packages/quickstart/steps/02/README.md +++ b/packages/quickstart/steps/02/README.md @@ -9,7 +9,7 @@ Now we extend our minimalist HTML page to a basic app with a view and a controll ### Preview -![](assets/loio240ef5357d7f4d36955092cdaf1884a2_LowRes.png "The browser shows a Steady button in an app") +![The browser shows a Steady button in an app](assets/loio240ef5357d7f4d36955092cdaf1884a2_LowRes.png "The browser shows a Steady button in an app") *The browser shows a Steady button in an app* @@ -19,27 +19,18 @@ You can access the live preview by clicking on this link: [🔗 Live Preview of ### Coding -
- -You can download the solution for this step here: [📥 Download step 2](https://ui5.github.io/tutorials/quickstart-step-02.zip). - -
- -
- -You can download the solution for this step here: [📥 Download step 2](https://ui5.github.io/tutorials/quickstart-step-02-js.zip). - -
+You can download the solution for this step here: [📥 Download step 2](https://ui5.github.io/tutorials/quickstart-step-02.zip) (TS)[📥 Download step 2](https://ui5.github.io/tutorials/quickstart-step-02-js.zip) (JS). *** -### webapp/index.?s +### webapp/index.ts/.js Now we replace most of the code in this file: We remove the inline button from the previous step, and introduce a proper XML view to separate the presentation from the controller logic. We prefix the view name `ui5.tutorial.quickstart.App` with our newly defined namespace. The view is loaded asynchronously. Similar to the step before, the view is placed in the element with the `content` ID after it has finished loading. ```ts +// webapp/index.ts import XMLView from "sap/ui/core/mvc/XMLView"; XMLView.create({ @@ -47,6 +38,7 @@ XMLView.create({ }).then((oView) => oView.placeAt("content")); ``` ```js +// webapp/index.js sap.ui.define([ "sap/ui/core/mvc/XMLView" ], (XMLView) => { @@ -89,6 +81,7 @@ We outsource the controller logic to an app controller. The `.onPress` event now In our controller, we load the `Controller` base class and extend it to define the behavior of our app. We also add the event handler for our button. The `MessageToast` is also loaded as a dependency. When the button is pressed, we now display a "Hello App" message. ```ts +// webapp/App.controller.ts import Controller from "sap/ui/core/mvc/Controller"; import MessageToast from "sap/m/MessageToast"; @@ -99,6 +92,7 @@ export default class App extends Controller { } ``` ```js +// webapp/App.controller.js sap.ui.define([ "sap/ui/core/mvc/Controller", "sap/m/MessageToast" @@ -123,6 +117,6 @@ You can see a title bar and a blue button that reacts to your input. Congratulat **Related Information** -[XML View](https://sdk.openui5.org/#/topic/1409791afe4747319a3b23a1e2fc7064 "The XML view type is defined in an XML file, with a file name ending in .view.xml. The file name and the folder structure together specify the name of the view that equals the OpenUI5 module name.") +[XML View](https://sdk.openui5.org/topic/1409791afe4747319a3b23a1e2fc7064 "The XML view type is defined in an XML file, with a file name ending in .view.xml. The file name and the folder structure together specify the name of the view that equals the OpenUI5 module name.") -[Controller](https://sdk.openui5.org/#/topic/121b8e6337d147af9819129e428f1f75 "A controller contains methods that define how models and views interact.") +[Controller](https://sdk.openui5.org/topic/121b8e6337d147af9819129e428f1f75 "A controller contains methods that define how models and views interact.") diff --git a/packages/quickstart/steps/03/README.md b/packages/quickstart/steps/03/README.md index cd1e665b7..bc5874c70 100644 --- a/packages/quickstart/steps/03/README.md +++ b/packages/quickstart/steps/03/README.md @@ -9,7 +9,7 @@ Finally, we add a second page to our app showcasing some of the key OpenUI5 conc ### Preview -![](assets/loio79e1157d948c488c9717ef840fa9b396_LowRes.png "The second page shows plenty of UI controls and concepts to explore") +![The second page shows plenty of UI controls and concepts to explore](assets/loio79e1157d948c488c9717ef840fa9b396_LowRes.png "The second page shows plenty of UI controls and concepts to explore") *The second page shows plenty of UI controls and concepts to explore* @@ -19,17 +19,7 @@ You can access the live preview by clicking on this link: [🔗 Live Preview of ### Coding -
- -You can download the solution for this step here: [📥 Download step 3](https://ui5.github.io/tutorials/quickstart-step-03.zip). - -
- -
- -You can download the solution for this step here: [📥 Download step 3](https://ui5.github.io/tutorials/quickstart-step-03-js.zip). - -
+You can download the solution for this step here: [📥 Download step 3](https://ui5.github.io/tutorials/quickstart-step-03.zip) (TS)[📥 Download step 3](https://ui5.github.io/tutorials/quickstart-step-03-js.zip) (JS). *** @@ -135,6 +125,7 @@ We display these texts on the second page using data binding. The `InfoLabel` ta Finally, we make the Panel in the lower part of the view interactive by attaching an `onChange` event to the switch defined there. OpenUI5 comes with a large set of feature-rich controls that you can combine as you need. ```ts +// webapp/App.controller.ts import Controller from "sap/ui/core/mvc/Controller"; import MessageToast from "sap/m/MessageToast"; import JSONModel from "sap/ui/model/json/JSONModel"; @@ -169,6 +160,7 @@ export default class AppController extends Controller { ``` ```js +// webapp/App.controller.js sap.ui.define([ "sap/ui/core/mvc/Controller", "sap/m/MessageToast", @@ -213,8 +205,8 @@ Have fun with OpenUI5! **Related Information** -[Working with Controls](https://sdk.openui5.org/#/topic/91f0a22d6f4d1014b6dd926db0e91070 "Controls are used to define the appearance and behavior of screen areas.") +[Working with Controls](https://sdk.openui5.org/topic/91f0a22d6f4d1014b6dd926db0e91070 "Controls are used to define the appearance and behavior of screen areas.") -[Data Binding](https://sdk.openui5.org/#/topic/68b9644a253741e8a4b9e4279a35c247 "You use data binding to bind UI elements to data sources to keep the data in sync and allow data editing on the UI.") +[Data Binding](https://sdk.openui5.org/topic/68b9644a253741e8a4b9e4279a35c247 "You use data binding to bind UI elements to data sources to keep the data in sync and allow data editing on the UI.") -[Routing and Navigation](https://sdk.openui5.org/#/topic/3d18f20bd2294228acb6910d8e8a5fb5 "OpenUI5 offers hash-based navigation, which allows you to build single-page apps where the navigation is done by changing the hash. In this way the browser does not have to reload the page; instead there is a callback to which the app and especially the affected view can react. A hash string is parsed and matched against patterns which will then inform the handlers.") +[Routing and Navigation](https://sdk.openui5.org/topic/3d18f20bd2294228acb6910d8e8a5fb5 "OpenUI5 offers hash-based navigation, which allows you to build single-page apps where the navigation is done by changing the hash. In this way the browser does not have to reload the page; instead there is a callback to which the app and especially the affected view can react. A hash string is parsed and matched against patterns which will then inform the handlers.") diff --git a/packages/walkthrough/README.md b/packages/walkthrough/README.md index 63a7ec7f5..dfabe1918 100644 --- a/packages/walkthrough/README.md +++ b/packages/walkthrough/README.md @@ -20,44 +20,44 @@ We first introduce you to the basic development paradigms like *Model-View-Contr The tutorial consists of the following steps. To start, just open the first link - you`ll be guided from there. -- **[Step 1: Hello World!](steps/01/README.md "As you know OpenUI5 is all about HTML5. Let’s get started with building a first "Hello World" with only HTML.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/01/index.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-01.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-01-js.zip)
) -- **[Step 2: Bootstrap](steps/02/README.md "Before we can do something with OpenUI5, we need to load and initialize it. This process of loading and initializing OpenUI5 is called bootstrapping. Once this bootstrapping is finished, we simply display an alert.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/02/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-02.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-02-js.zip)
) -- **[Step 3: Controls](steps/03/README.md "Now it is time to build our first little UI by replacing the "Hello World" text in the HTML body by the OpenUI5 control sap/m/Text. In the beginning, we will use the JavaScript control interface to set up the UI, the control instance is then placed into the HTML body. ")** ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/03/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-03.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-03-js.zip)
) -- **[Step 4: XML Views](steps/04/README.md "Putting all our UI into the index.ts file will very soon result in a messy setup, and there is quite a bit of work ahead of us. So let’s do a first modularization by putting the sap/m/Text control into a dedicated view.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/04/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-04.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-04-js.zip)
) -- **[Step 5: Controllers](steps/05/README.md "In this step, we replace the text with a button and show the "Hello World" message when the button is pressed. The handling of the button's press event is implemented in the controller of the view.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/05/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-05.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-05-js.zip)
) -- **[Step 6: Modules](steps/06/README.md "In OpenUI5, resources are often referred to as modules. In this step, we replace the alert from the last exercise with a proper Message Toast from the sap.m library.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/06/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-06.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-06-js.zip)
) -- **[Step 7: JSON Model](steps/07/README.md "Now that we have set up the view and controller, it’s about time to think about the M in MVC.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/07/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-07.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-07-js.zip)
) -- **[Step 8: Translatable Texts](steps/08/README.md "In this step we move the texts of our UI to a separate resource file.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/08/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-08.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-08-js.zip)
) -- **[Step 9: Component Configuration](steps/09/README.md "After we have introduced all three parts of the Model-View-Controller /(MVC/) concept, we now come to another important structural aspect of OpenUI5. ")** ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/09/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-09.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-09-js.zip)
) -- **[Step 10: Descriptor for Applications](steps/10/README.md "All application-specific configuration settings will now further be put in a separate descriptor file called manifest.json. This clearly separates the application coding from the configuration settings and makes our app even more flexible. For example, all SAP Fiori applications are realized as components and come with a descriptor file in order to be hosted in the SAP Fiori launchpad.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/10/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-10.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-10-js.zip)
) -- **[Step 11: Pages and Panels](steps/11/README.md "After all the work on the app structure it’s time to improve the look of our app. We will use two controls from the sap.m library to add a bit more "bling" to our UI. You will also learn about control aggregations in this step.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/11/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-11.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-11-js.zip)
) -- **[Step 12: Shell Control as Container](steps/12/README.md "Now we use a shell control as container for our app and use it as our new root element. The shell takes care of visual adaptation of the application to the device’s screen size by introducing a so-called letterbox on desktop screens.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/12/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-12.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-12-js.zip)
) -- **[Step 13: Margins and Paddings](steps/13/README.md "Our app content is still glued to the corners of the letterbox. To fine-tune our layout, we can add margins and paddings to the controls that we added in the previous step. ")** ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/13/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-13.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-13-js.zip)
) -- **[Step 14: Custom CSS and Theme Colors](steps/14/README.md "Sometimes we need to define some more fine-granular layouts and this is when we can use the flexibility of CSS by adding custom style classes to controls and style them as we like. ")** ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/14/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-14.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-14-js.zip)
) -- **[Step 15: Nested Views](steps/15/README.md "Our panel content is getting more and more complex and now it is time to move the panel content to a separate view. With that approach, the application structure is much easier to understand, and the individual parts of the app can be reused.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/15/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-15.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-15-js.zip)
) -- **[Step 16: Dialogs and Fragments](steps/16/README.md "In this step, we will take a closer look at another element which can be used to assemble views: the fragment. ")** ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/16/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-16.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-16-js.zip)
) -- **[Step 17: Fragment Callbacks](steps/17/README.md "Now that we have integrated the dialog, it's time to add some user interaction. The user will definitely want to close the dialog again at some point, so we add a button to close the dialog and assign an event handler.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/17/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-17.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-17-js.zip)
) -- **[Step 18: Icons](steps/18/README.md "Our dialog is still pretty much empty. Since OpenUI5 is shipped with a large icon font that contains more than 500 icons, we will add an icon to greet our users when the dialog is opened.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/18/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-18.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-18-js.zip)
) -- **[Step 19: Aggregation Binding](steps/19/README.md "Now that we have established a good structure for our app, it's time to add some more functionality. We start exploring more features of data binding by adding some invoice data in JSON format that we display in a list below the panel.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/19/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-19.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-19-js.zip)
) -- **[Step 20: Data Types](steps/20/README.md "The list of invoices is already looking nice, but what is an invoice without a price assigned? Typically prices are stored in a technical format and with a /'./' delimiter in the data model. For example, our invoice for pineapples has the calculated price 87.2 without a currency. We are going to use the OpenUI5 data types to format the price properly, with a locale-dependent decimal separator and two digits after the separator.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/20/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-20.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-20-js.zip)
) -- **[Step 21: Expression Binding](steps/21/README.md "Sometimes the predefined types of OpenUI5 are not flexible enough and you want to do a simple calculation or formatting in the view - that is where expressions are really helpful. We use them to format our price according to the current number in the data model.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/21/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-21.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-21-js.zip)
) -- **[Step 22: Custom Formatters](steps/22/README.md "If we want to do a more complex logic for formatting properties of our data model, we can also write a custom formatting function. We will now add a localized status with a custom formatter, because the status in our data model is in a rather technical format.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/22/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-22.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-22-js.zip)
) -- **[Step 23: Filtering](steps/23/README.md "In this step, we add a search field for our product list and define a filter that represents the search term. When searching, the list is automatically updated to show only the items that match the search term.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/23/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-23.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-23-js.zip)
) -- **[Step 24: Sorting and Grouping](steps/24/README.md "To make our list of invoices even more user-friendly, we sort it alphabetically instead of just showing the order from the data model. Additionally, we introduce groups and add the company that ships the products so that the data is easier to consume.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/24/index-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-24.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-24-js.zip)
) -- **[Step 25: Remote OData Service](steps/25/README.md "So far we have worked with local JSON data, but now we will access a real OData service to visualize remote data.")** (🔗 Live Preview *unfeasible* \|
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-25.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-25-js.zip)
) -- **[Step 26: Mock Server Configuration](steps/26/README.md "We just ran our app against a real service, but for developing and testing our app we do not want to rely on the availability of the “real” service or put additional load on the system where the data service is located.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/26/test/mockServer-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-26.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-26-js.zip)
) -- **[Step 27: Unit Test with QUnit](steps/27/README.md "Now that we have a test folder in the app, we can start to increase our test coverage. ")** ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/27/test/Test.cdn.qunit.html?testsuite=test-resources/ui5/tutorial/walkthrough/testsuite.cdn.qunit&test=unit/unitTests) \|
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-27.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-27-js.zip)
) -- **[Step 28: Integration Test with OPA](steps/28/README.md "If we want to test interaction patterns or more visual features of our app, we can also write an integration test. ")** ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/28/test/Test.cdn.qunit.html?testsuite=test-resources/ui5/tutorial/walkthrough/testsuite.cdn.qunit&test=integration/opaTests) \|
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-28.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-28-js.zip)
) -- **[Step 29: Debugging Tools](steps/29/README.md "Even though we have added a basic test coverage in the previous steps, it seems like we accidentally broke our app, because it does not display prices to our invoices anymore. We need to debug the issue and fix it before someone finds out.")** (*code remains unchanged from the previous step*) -- **[Step 30: Routing and Navigation](steps/30/README.md "So far, we have put all app content on one single page. As we add more and more features, we want to split the content and put it on separate pages.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/30/test/mockServer-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-30.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-30-js.zip)
) -- **[Step 31: Routing with Parameters](steps/31/README.md "We can now navigate between the overview and the detail page, but the actual item that we selected in the overview is not displayed on the detail page yet. A typical use case for our app is to show additional information for the selected item on the detail page. ")** ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/31/test/mockServer-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-31.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-31-js.zip)
) -- **[Step 32: Routing Back and History](steps/32/README.md "Now we can navigate to our detail page and display an invoice, but we cannot go back to the overview page yet. We'll add a back button to the detail page and implement a function that shows our overview page again.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/32/test/mockServer-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-32.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-32-js.zip)
) -- **[Step 33: Custom Controls](steps/33/README.md "In this step, we are going to extend the functionality of OpenUI5 with a custom control. We want to rate the product shown on the detail page, so we create a composition of multiple standard controls using the OpenUI5 extension mechanism and add some glue code to make them work nicely together. This way, we can reuse the control across the app and keep all related functionality in one module.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/33/test/mockServer-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-33.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-33-js.zip)
) -- **[Step 34: Responsiveness](steps/34/README.md "In this step, we improve the responsiveness of our app. OpenUI5 applications can be run on phone, tablet, and desktop devices and we can configure the application to make best use of the screen estate for each scenario. Fortunately, OpenUI5 controls like the sap.m.Table already deliver a lot of features that we can use.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/34/test/mockServer-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-34.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-34-js.zip)
) -- **[Step 35: Device Adaptation](steps/35/README.md "We now configure the visibility and properties of controls based on the device that we run the application on. By making use of the sap.ui.Device API and defining a device model we will make the app look great on many devices.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/35/test/mockServer-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-35.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-35-js.zip)
) -- **[Step 36: Content Density](steps/36/README.md "In this step of our Walkthrough tutorial, we adjust the content density based on the user’s device. OpenUI5 contains different content densities allowing you to display larger controls for touch-enabled devices and a smaller, more compact design for devices that are operated by mouse. In our app, we will detect the device and adjust the density accordingly.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/36/test/mockServer-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-36.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-36-js.zip)
) -- **[Step 37: Accessibility](steps/37/README.md "In this step we're going to improve the accessibility of our app.")** ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/37/test/mockServer-cdn.html) \|
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-37.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-37-js.zip)
) -- **[Step 38: Build Your Application](steps/38/README.md "In this step we're going to build our application and consume the speed of a built OpenUI5 application.")** (
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-38.zip)
[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-38-js.zip)
) +- **[Step 1: Hello World!](steps/01/README.md)** — As you know OpenUI5 is all about HTML5. Let’s get started with building a first "Hello World" with only HTML. ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/01/index.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-01.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-01-js.zip) (JS) ) +- **[Step 2: Bootstrap](steps/02/README.md)** — Before we can do something with OpenUI5, we need to load and initialize it. This process of loading and initializing OpenUI5 is called bootstrapping. Once this bootstrapping is finished, we simply display an alert. ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/02/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-02.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-02-js.zip) (JS) ) +- **[Step 3: Controls](steps/03/README.md)** — Now it is time to build our first little UI by replacing the "Hello World" text in the HTML body by the OpenUI5 control sap/m/Text. In the beginning, we will use the JavaScript control interface to set up the UI, the control instance is then placed into the HTML body. ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/03/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-03.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-03-js.zip) (JS) ) +- **[Step 4: XML Views](steps/04/README.md)** — Putting all our UI into the index.ts file will very soon result in a messy setup, and there is quite a bit of work ahead of us. So let’s do a first modularization by putting the sap/m/Text control into a dedicated view. ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/04/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-04.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-04-js.zip) (JS)) +- **[Step 5: Controllers](steps/05/README.md)** — In this step, we replace the text with a button and show the "Hello World" message when the button is pressed. The handling of the button's press event is implemented in the controller of the view. ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/05/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-05.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-05-js.zip) (JS)) +- **[Step 6: Modules](steps/06/README.md)** — In OpenUI5, resources are often referred to as modules. In this step, we replace the alert from the last exercise with a proper Message Toast from the sap.m library. ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/06/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-06.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-06-js.zip) (JS)) +- **[Step 7: JSON Model](steps/07/README.md)** — Now that we have set up the view and controller, it’s about time to think about the M in MVC. ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/07/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-07.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-07-js.zip) (JS)) +- **[Step 8: Translatable Texts](steps/08/README.md)** — In this step we move the texts of our UI to a separate resource file. ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/08/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-08.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-08-js.zip) (JS)) +- **[Step 9: Component Configuration](steps/09/README.md)** — After we have introduced all three parts of the Model-View-Controller /(MVC/) concept, we now come to another important structural aspect of OpenUI5. ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/09/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-09.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-09-js.zip) (JS)) +- **[Step 10: Descriptor for Applications](steps/10/README.md)** — All application-specific configuration settings will now further be put in a separate descriptor file called manifest.json. This clearly separates the application coding from the configuration settings and makes our app even more flexible. For example, all SAP Fiori applications are realized as components and come with a descriptor file in order to be hosted in the SAP Fiori launchpad. ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/10/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-10.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-10-js.zip) (JS)) +- **[Step 11: Pages and Panels](steps/11/README.md)** — After all the work on the app structure it’s time to improve the look of our app. We will use two controls from the sap.m library to add a bit more "bling" to our UI. You will also learn about control aggregations in this step. ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/11/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-11.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-11-js.zip) (JS)) +- **[Step 12: Shell Control as Container](steps/12/README.md)** — Now we use a shell control as container for our app and use it as our new root element. The shell takes care of visual adaptation of the application to the device’s screen size by introducing a so-called letterbox on desktop screens. ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/12/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-12.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-12-js.zip) (JS)) +- **[Step 13: Margins and Paddings](steps/13/README.md)** — Our app content is still glued to the corners of the letterbox. To fine-tune our layout, we can add margins and paddings to the controls that we added in the previous step. ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/13/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-13.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-13-js.zip) (JS)) +- **[Step 14: Custom CSS and Theme Colors](steps/14/README.md)** — Sometimes we need to define some more fine-granular layouts and this is when we can use the flexibility of CSS by adding custom style classes to controls and style them as we like. ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/14/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-14.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-14-js.zip) (JS)) +- **[Step 15: Nested Views](steps/15/README.md)** — Our panel content is getting more and more complex and now it is time to move the panel content to a separate view. With that approach, the application structure is much easier to understand, and the individual parts of the app can be reused. ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/15/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-15.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-15-js.zip) (JS)) +- **[Step 16: Dialogs and Fragments](steps/16/README.md)** — In this step, we will take a closer look at another element which can be used to assemble views: the fragment. ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/16/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-16.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-16-js.zip) (JS)) +- **[Step 17: Fragment Callbacks](steps/17/README.md)** — Now that we have integrated the dialog, it's time to add some user interaction. The user will definitely want to close the dialog again at some point, so we add a button to close the dialog and assign an event handler. ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/17/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-17.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-17-js.zip) (JS)) +- **[Step 18: Icons](steps/18/README.md)** — Our dialog is still pretty much empty. Since OpenUI5 is shipped with a large icon font that contains more than 500 icons, we will add an icon to greet our users when the dialog is opened. ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/18/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-18.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-18-js.zip) (JS)) +- **[Step 19: Aggregation Binding](steps/19/README.md)** — Now that we have established a good structure for our app, it's time to add some more functionality. We start exploring more features of data binding by adding some invoice data in JSON format that we display in a list below the panel. ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/19/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-19.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-19-js.zip) (JS)) +- **[Step 20: Data Types](steps/20/README.md)** — The list of invoices is already looking nice, but what is an invoice without a price assigned? Typically prices are stored in a technical format and with a /'./' delimiter in the data model. For example, our invoice for pineapples has the calculated price 87.2 without a currency. We are going to use the OpenUI5 data types to format the price properly, with a locale-dependent decimal separator and two digits after the separator. ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/20/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-20.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-20-js.zip) (JS)) +- **[Step 21: Expression Binding](steps/21/README.md)** — Sometimes the predefined types of OpenUI5 are not flexible enough and you want to do a simple calculation or formatting in the view - that is where expressions are really helpful. We use them to format our price according to the current number in the data model. ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/21/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-21.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-21-js.zip) (JS)) +- **[Step 22: Custom Formatters](steps/22/README.md)** — If we want to do a more complex logic for formatting properties of our data model, we can also write a custom formatting function. We will now add a localized status with a custom formatter, because the status in our data model is in a rather technical format. ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/22/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-22.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-22-js.zip) (JS)) +- **[Step 23: Filtering](steps/23/README.md)** — In this step, we add a search field for our product list and define a filter that represents the search term. When searching, the list is automatically updated to show only the items that match the search term. ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/23/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-23.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-23-js.zip) (JS)) +- **[Step 24: Sorting and Grouping](steps/24/README.md)** — To make our list of invoices even more user-friendly, we sort it alphabetically instead of just showing the order from the data model. Additionally, we introduce groups and add the company that ships the products so that the data is easier to consume. ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/24/index-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-24.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-24-js.zip) (JS)) +- **[Step 25: Remote OData Service](steps/25/README.md)** — So far we have worked with local JSON data, but now we will access a real OData service to visualize remote data. (🔗 Live Preview *unfeasible* \| [📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-25.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-25-js.zip) (JS)) +- **[Step 26: Mock Server Configuration](steps/26/README.md)** — We just ran our app against a real service, but for developing and testing our app we do not want to rely on the availability of the “real” service or put additional load on the system where the data service is located. ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/26/test/mockServer-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-26.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-26-js.zip) (JS)) +- **[Step 27: Unit Test with QUnit](steps/27/README.md)** — Now that we have a test folder in the app, we can start to increase our test coverage. ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/27/test/Test.cdn.qunit.html?testsuite=test-resources/ui5/tutorial/walkthrough/testsuite.cdn.qunit&test=unit/unitTests) \| [📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-27.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-27-js.zip) (JS)) +- **[Step 28: Integration Test with OPA](steps/28/README.md)** — If we want to test interaction patterns or more visual features of our app, we can also write an integration test. ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/28/test/Test.cdn.qunit.html?testsuite=test-resources/ui5/tutorial/walkthrough/testsuite.cdn.qunit&test=integration/opaTests) \| [📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-28.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-28-js.zip) (JS)) +- **[Step 29: Debugging Tools](steps/29/README.md)** — Even though we have added a basic test coverage in the previous steps, it seems like we accidentally broke our app, because it does not display prices to our invoices anymore. We need to debug the issue and fix it before someone finds out. (*code remains unchanged from the previous step*) +- **[Step 30: Routing and Navigation](steps/30/README.md)** — So far, we have put all app content on one single page. As we add more and more features, we want to split the content and put it on separate pages. ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/30/test/mockServer-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-30.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-30-js.zip) (JS)) +- **[Step 31: Routing with Parameters](steps/31/README.md)** — We can now navigate between the overview and the detail page, but the actual item that we selected in the overview is not displayed on the detail page yet. A typical use case for our app is to show additional information for the selected item on the detail page. ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/31/test/mockServer-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-31.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-31-js.zip) (JS)) +- **[Step 32: Routing Back and History](steps/32/README.md)** — Now we can navigate to our detail page and display an invoice, but we cannot go back to the overview page yet. We'll add a back button to the detail page and implement a function that shows our overview page again. ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/32/test/mockServer-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-32.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-32-js.zip) (JS)) +- **[Step 33: Custom Controls](steps/33/README.md)** — In this step, we are going to extend the functionality of OpenUI5 with a custom control. We want to rate the product shown on the detail page, so we create a composition of multiple standard controls using the OpenUI5 extension mechanism and add some glue code to make them work nicely together. This way, we can reuse the control across the app and keep all related functionality in one module. ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/33/test/mockServer-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-33.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-33-js.zip) (JS)) +- **[Step 34: Responsiveness](steps/34/README.md)** — In this step, we improve the responsiveness of our app. OpenUI5 applications can be run on phone, tablet, and desktop devices and we can configure the application to make best use of the screen estate for each scenario. Fortunately, OpenUI5 controls like the sap.m.Table already deliver a lot of features that we can use. ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/34/test/mockServer-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-34.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-34-js.zip) (JS)) +- **[Step 35: Device Adaptation](steps/35/README.md)** — We now configure the visibility and properties of controls based on the device that we run the application on. By making use of the sap.ui.Device API and defining a device model we will make the app look great on many devices. ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/35/test/mockServer-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-35.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-35-js.zip) (JS)) +- **[Step 36: Content Density](steps/36/README.md)** — In this step of our Walkthrough tutorial, we adjust the content density based on the user’s device. OpenUI5 contains different content densities allowing you to display larger controls for touch-enabled devices and a smaller, more compact design for devices that are operated by mouse. In our app, we will detect the device and adjust the density accordingly. ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/36/test/mockServer-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-36.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-36-js.zip) (JS)) +- **[Step 37: Accessibility](steps/37/README.md)** — In this step we're going to improve the accessibility of our app. ([🔗 Live Preview](https://ui5.github.io/tutorials/walkthrough/build/37/test/mockServer-cdn.html) \| [📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-37.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-37-js.zip) (JS)) +- **[Step 38: Build Your Application](steps/38/README.md)** — In this step we're going to build our application and consume the speed of a built OpenUI5 application. ([📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-38.zip) (TS)[📥 Download Solution](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-38-js.zip) (JS)) ## License diff --git a/packages/walkthrough/steps.json b/packages/walkthrough/steps.json new file mode 100644 index 000000000..8bb23338f --- /dev/null +++ b/packages/walkthrough/steps.json @@ -0,0 +1,348 @@ +{ + "tutorial": "walkthrough", + "namespace": "ui5.tutorial.walkthrough", + "steps": [ + { + "n": 1, + "id": "01", + "title": "Hello World!", + "previewUrl": "https://ui5.github.io/tutorials/walkthrough/build/01/index.html", + "zipTs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-01.zip", + "zipJs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-01-js.zip", + "description": "As you know, OpenUI5 is all about HTML5. Let's get started with building a first \"Hello World\" with only HTML. In addition we'll initialize the UI5 CLI, so we can benefit from it from the beginning." + }, + { + "n": 2, + "id": "02", + "title": "Bootstrap", + "previewUrl": "https://ui5.github.io/tutorials/walkthrough/build/02/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-02.zip", + "zipJs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-02-js.zip", + "description": "Before we can do something with OpenUI5, we need to load and initialize it. This process of loading and initializing OpenUI5 is called bootstrapping. Once this bootstrapping is finished, we simply display an alert." + }, + { + "n": 3, + "id": "03", + "title": "Controls", + "previewUrl": "https://ui5.github.io/tutorials/walkthrough/build/03/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-03.zip", + "zipJs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-03-js.zip", + "description": "Now it is time to build our first little UI by replacing the “Hello World” text in the HTML body by the OpenUI5 control sap/m/Text. In the beginning, we will create an OpenUI5 control instance and place into the HTML body." + }, + { + "n": 4, + "id": "04", + "title": "XML Views", + "previewUrl": "https://ui5.github.io/tutorials/walkthrough/build/04/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-04.zip", + "zipJs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-04-js.zip", + "description": "Putting all our UI into the index.ts file will very soon result in a messy setup and there is quite a bit of work ahead of us. So let’s do a first modularization by putting the sap/m/Text control into a dedicated view." + }, + { + "n": 5, + "id": "05", + "title": "Controllers", + "previewUrl": "https://ui5.github.io/tutorials/walkthrough/build/05/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-05.zip", + "zipJs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-05-js.zip", + "description": "In this step, we replace the text with a button and show the “Hello World” message when the button is pressed. The handling of the button's press event is implemented in the controller of the view." + }, + { + "n": 6, + "id": "06", + "title": "Modules", + "previewUrl": "https://ui5.github.io/tutorials/walkthrough/build/06/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-06.zip", + "zipJs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-06-js.zip", + "description": "In OpenUI5, resources are often referred to as modules. In this step, we replace the alert from the last exercise with a proper Message Toast from the sap.m library." + }, + { + "n": 7, + "id": "07", + "title": "JSON Model", + "previewUrl": "https://ui5.github.io/tutorials/walkthrough/build/07/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-07.zip", + "zipJs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-07-js.zip", + "description": "Now that we have set up the view and controller, it’s about time to think about the M in MVC." + }, + { + "n": 8, + "id": "08", + "title": "Translatable Texts", + "previewUrl": "https://ui5.github.io/tutorials/walkthrough/build/08/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-08.zip", + "zipJs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-08-js.zip", + "description": "In this step we move the texts of our UI to a separate resource file." + }, + { + "n": 9, + "id": "09", + "title": "Component Configuration", + "previewUrl": "https://ui5.github.io/tutorials/walkthrough/build/09/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-09.zip", + "zipJs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-09-js.zip", + "description": "After we have introduced all three parts of the Model-View-Controller \\(MVC\\) concept, we now come to another important structural aspect of OpenUI5." + }, + { + "n": 10, + "id": "10", + "title": "Descriptor for Applications", + "previewUrl": "https://ui5.github.io/tutorials/walkthrough/build/10/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-10.zip", + "zipJs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-10-js.zip", + "description": "All application-specific configuration settings will now be put into the manifest. This clearly separates the application coding from the configuration settings and makes our app even more flexible." + }, + { + "n": 11, + "id": "11", + "title": "Pages and Panels", + "previewUrl": "https://ui5.github.io/tutorials/walkthrough/build/11/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-11.zip", + "zipJs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-11-js.zip", + "description": "After all the work on the app structure it’s time to improve the look of our app. We will use two controls from the sap.m library to add a bit more \"bling\" to our UI. You will also learn about control aggregations in this step." + }, + { + "n": 12, + "id": "12", + "title": "Shell Control as Container", + "previewUrl": "https://ui5.github.io/tutorials/walkthrough/build/12/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-12.zip", + "zipJs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-12-js.zip", + "description": "Now we use a shell control as container for our app and use it as our new root element. The shell takes care of visual adaptation of the application to the device’s screen size by introducing a so-called letterbox on desktop screens." + }, + { + "n": 13, + "id": "13", + "title": "Margins and Paddings", + "previewUrl": "https://ui5.github.io/tutorials/walkthrough/build/13/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-13.zip", + "zipJs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-13-js.zip", + "description": "Our app content is still glued to the corners of the letterbox. To fine-tune our layout, we can add margins and paddings to the controls that we added in the previous step." + }, + { + "n": 14, + "id": "14", + "title": "Custom CSS and Theme Colors", + "previewUrl": "https://ui5.github.io/tutorials/walkthrough/build/14/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-14.zip", + "zipJs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-14-js.zip", + "description": "Sometimes we need to define some more fine-granular layouts and this is when we can use the flexibility of CSS by adding custom style classes to controls and style them as we like." + }, + { + "n": 15, + "id": "15", + "title": "Nested Views", + "previewUrl": "https://ui5.github.io/tutorials/walkthrough/build/15/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-15.zip", + "zipJs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-15-js.zip", + "description": "Our panel content is getting more and more complex and now it is time to move the panel content to a separate view. With that approach, the application structure is much easier to understand, and the individual parts of the app can be reused." + }, + { + "n": 16, + "id": "16", + "title": "Dialogs and Fragments", + "previewUrl": "https://ui5.github.io/tutorials/walkthrough/build/16/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-16.zip", + "zipJs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-16-js.zip", + "description": "In this step, we will take a closer look at another element which can be used to assemble views: the fragment." + }, + { + "n": 17, + "id": "17", + "title": "Fragment Callbacks", + "previewUrl": "https://ui5.github.io/tutorials/walkthrough/build/17/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-17.zip", + "zipJs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-17-js.zip", + "description": "Now that we have integrated the dialog, it's time to add some user interaction. The user will definitely want to close the dialog again at some point, so we add a button to close the dialog and assign an event handler." + }, + { + "n": 18, + "id": "18", + "title": "Icons", + "previewUrl": "https://ui5.github.io/tutorials/walkthrough/build/18/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-18.zip", + "zipJs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-18-js.zip", + "description": "Our dialog is still pretty much empty. Since OpenUI5 is shipped with a large icon font that contains more than 500 icons, we will add an icon to greet our users when the dialog is opened." + }, + { + "n": 19, + "id": "19", + "title": "Aggregation Binding", + "previewUrl": "https://ui5.github.io/tutorials/walkthrough/build/19/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-19.zip", + "zipJs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-19-js.zip", + "description": "Now that we have established a good structure for our app, it's time to add some more functionality. We start exploring more features of data binding by adding some invoice data in JSON format that we display in a list below the panel." + }, + { + "n": 20, + "id": "20", + "title": "Data Types", + "previewUrl": "https://ui5.github.io/tutorials/walkthrough/build/20/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-20.zip", + "zipJs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-20-js.zip", + "description": "The list of invoices is already looking nice, but what is an invoice without a price assigned? Typically prices are stored in a technical format and with a '.' delimiter in the data model. For example, our invoice for pineapples has the calculated price 87.2 without a currency. We are going to use the OpenUI5 data types to format the price properly, with a locale-dependent decimal separator and two digits after the separator." + }, + { + "n": 21, + "id": "21", + "title": "Expression Binding", + "previewUrl": "https://ui5.github.io/tutorials/walkthrough/build/21/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-21.zip", + "zipJs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-21-js.zip", + "description": "Sometimes the predefined types of OpenUI5 are not flexible enough and you want to do a simple calculation or formatting in the view - that is where expressions are really helpful. We use them to format our price according to the current number in the data model." + }, + { + "n": 22, + "id": "22", + "title": "Custom Formatters", + "previewUrl": "https://ui5.github.io/tutorials/walkthrough/build/22/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-22.zip", + "zipJs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-22-js.zip", + "description": "If we want to do a more complex logic for formatting properties of our data model, we can also write a custom formatting function. We will now add a localized status with a custom formatter, because the status in our data model is in a rather technical format." + }, + { + "n": 23, + "id": "23", + "title": "Filtering", + "previewUrl": "https://ui5.github.io/tutorials/walkthrough/build/23/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-23.zip", + "zipJs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-23-js.zip", + "description": "In this step, we add a search field for our product list and define a filter that represents the search term. When searching, the list is automatically updated to show only the items that match the search term." + }, + { + "n": 24, + "id": "24", + "title": "Sorting and Grouping", + "previewUrl": "https://ui5.github.io/tutorials/walkthrough/build/24/index-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-24.zip", + "zipJs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-24-js.zip", + "description": "To make our list of invoices even more user-friendly, we sort it alphabetically instead of just showing the order from the data model. Additionally, we introduce groups and add the company that ships the products so that the data is easier to consume." + }, + { + "n": 25, + "id": "25", + "title": "Remote OData Service", + "previewUrl": null, + "zipTs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-25.zip", + "zipJs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-25-js.zip", + "description": "So far we have worked with local JSON data, but now we will access a real OData service to visualize remote data." + }, + { + "n": 26, + "id": "26", + "title": "Mock Server Configuration", + "previewUrl": "https://ui5.github.io/tutorials/walkthrough/build/26/test/mockServer-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-26.zip", + "zipJs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-26-js.zip", + "description": "We just ran our app against a real service, but for developing and testing our app we do not want to rely on the availability of the “real” service or put additional load on the system where the data service is located." + }, + { + "n": 27, + "id": "27", + "title": "Unit Test with QUnit", + "previewUrl": "https://ui5.github.io/tutorials/walkthrough/build/27/test/Test.cdn.qunit.html?testsuite=test-resources/ui5/tutorial/walkthrough/testsuite.cdn.qunit&test=unit/unitTests", + "zipTs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-27.zip", + "zipJs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-27-js.zip", + "description": "Now that we have a test folder in the app, we can start to increase our test coverage." + }, + { + "n": 28, + "id": "28", + "title": "Integration Test with OPA", + "previewUrl": "https://ui5.github.io/tutorials/walkthrough/build/28/test/Test.cdn.qunit.html?testsuite=test-resources/ui5/tutorial/walkthrough/testsuite.cdn.qunit&test=integration/opaTests", + "zipTs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-28.zip", + "zipJs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-28-js.zip", + "description": "If we want to test interaction patterns or more visual features of our app, we can also write an integration test." + }, + { + "n": 29, + "id": "29", + "title": "Debugging Tools", + "previewUrl": null, + "zipTs": null, + "zipJs": null, + "description": "Even though we have added a basic test coverage in the previous steps, it seems like we accidentally broke our app, because it does not display prices to our invoices anymore. We need to debug the issue and fix it before someone finds out." + }, + { + "n": 30, + "id": "30", + "title": "Routing and Navigation", + "previewUrl": "https://ui5.github.io/tutorials/walkthrough/build/30/test/mockServer-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-30.zip", + "zipJs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-30-js.zip", + "description": "So far, we have put all app content on one single page. As we add more and more features, we want to split the content and put it on separate pages." + }, + { + "n": 31, + "id": "31", + "title": "Routing with Parameters", + "previewUrl": "https://ui5.github.io/tutorials/walkthrough/build/31/test/mockServer-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-31.zip", + "zipJs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-31-js.zip", + "description": "We can now navigate between the overview and the detail page, but the actual item that we selected in the overview is not displayed on the detail page yet. A typical use case for our app is to show additional information for the selected item on the detail page." + }, + { + "n": 32, + "id": "32", + "title": "Routing Back and History", + "previewUrl": "https://ui5.github.io/tutorials/walkthrough/build/32/test/mockServer-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-32.zip", + "zipJs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-32-js.zip", + "description": "Now we can navigate to our detail page and display an invoice, but we cannot go back to the overview page yet. We'll add a back button to the detail page and implement a function that shows our overview page again." + }, + { + "n": 33, + "id": "33", + "title": "Custom Controls", + "previewUrl": "https://ui5.github.io/tutorials/walkthrough/build/33/test/mockServer-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-33.zip", + "zipJs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-33-js.zip", + "description": "In this step, we are going to extend the functionality of OpenUI5 with a custom control. We want to rate the product shown on the detail page, so we create a composition of multiple standard controls using the OpenUI5 extension mechanism and add some glue code to make them work nicely together. This way, we can reuse the control across the app and keep all related functionality in one module." + }, + { + "n": 34, + "id": "34", + "title": "Responsiveness", + "previewUrl": "https://ui5.github.io/tutorials/walkthrough/build/34/test/mockServer-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-34.zip", + "zipJs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-34-js.zip", + "description": "In this step, we improve the responsiveness of our app. OpenUI5 applications can be run on phone, tablet, and desktop devices and we can configure the application to make best use of the screen estate for each scenario. Fortunately, OpenUI5 controls like the sap.m.Table already deliver a lot of features that we can use." + }, + { + "n": 35, + "id": "35", + "title": "Device Adaptation", + "previewUrl": "https://ui5.github.io/tutorials/walkthrough/build/35/test/mockServer-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-35.zip", + "zipJs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-35-js.zip", + "description": "We now configure the visibility and properties of controls based on the device that we run the application on. By making use of the sap.ui.Device API and defining a device model we will make the app look great on many devices." + }, + { + "n": 36, + "id": "36", + "title": "Content Density", + "previewUrl": "https://ui5.github.io/tutorials/walkthrough/build/36/test/mockServer-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-36.zip", + "zipJs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-36-js.zip", + "description": "In this step of our Walkthrough tutorial, we adjust the content density based on the user’s device. Content density refers to the spacing and sizing of the UI controls and elements within your application. OpenUI5 contains different content densities allowing you to display larger controls for touch-enabled devices and a smaller, more compact design for devices that are operated by mouse. In our app, we will detect the device and adjust the density accordingly." + }, + { + "n": 37, + "id": "37", + "title": "Accessibility", + "previewUrl": "https://ui5.github.io/tutorials/walkthrough/build/37/test/mockServer-cdn.html", + "zipTs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-37.zip", + "zipJs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-37-js.zip", + "description": "In this step we're going to improve the accessibility of our app." + }, + { + "n": 38, + "id": "38", + "title": "Build Your Application", + "previewUrl": null, + "zipTs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-38.zip", + "zipJs": "https://ui5.github.io/tutorials/walkthrough/walkthrough-step-38-js.zip", + "description": "In this step we're going to build our application and consume the speed of a built OpenUI5 application." + } + ] +} diff --git a/packages/walkthrough/steps/01/README.md b/packages/walkthrough/steps/01/README.md index 9073c3713..f3c9e5fe9 100644 --- a/packages/walkthrough/steps/01/README.md +++ b/packages/walkthrough/steps/01/README.md @@ -9,7 +9,7 @@ As you know, OpenUI5 is all about HTML5. Let's get started with building a first ### Preview -![](assets/loio1dd456361379431aab7e5bcdaaeff00f_LowRes.png "The browser shows the text "Hello World"") +![The browser shows the text "Hello World"](assets/loio1dd456361379431aab7e5bcdaaeff00f_LowRes.png "The browser shows the text "Hello World"") *The browser shows the text "Hello World"* @@ -19,17 +19,7 @@ You can access the live preview by clicking on this link: [🔗 Live Preview of ### Coding -
- -You can download the solution for this step here: [📥 Download step 1](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-01.zip). - -
- -
- -You can download the solution for this step here: [📥 Download step 1](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-01-js.zip). - -
+You can download the solution for this step here: [📥 Download step 1](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-01.zip) (TS)[📥 Download step 1](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-01-js.zip) (JS). *** ### webapp \(New\) diff --git a/packages/walkthrough/steps/02/README.md b/packages/walkthrough/steps/02/README.md index a6bb3391b..f1b6ce750 100644 --- a/packages/walkthrough/steps/02/README.md +++ b/packages/walkthrough/steps/02/README.md @@ -9,7 +9,7 @@ Before we can do something with OpenUI5, we need to load and initialize it. This ### Preview -![](assets/loio0f6b6b9dc46a474da9287c382c8d3456_LowRes.png "An alert "UI5 is ready" is displayed") +![An alert "UI5 is ready" is displayed](assets/loio0f6b6b9dc46a474da9287c382c8d3456_LowRes.png "An alert "UI5 is ready" is displayed") *An alert "UI5 is ready" is displayed* @@ -18,17 +18,7 @@ You can access the live preview by clicking on this link: [🔗 Live Preview of *** ### Coding -
- -You can download the solution for this step here: [📥 Download step 2](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-02.zip). - -
- -
- -You can download the solution for this step here: [📥 Download step 2](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-02-js.zip). - -
+You can download the solution for this step here: [📥 Download step 2](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-02.zip) (TS)[📥 Download step 2](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-02-js.zip) (JS). *** ### Development Environment @@ -110,11 +100,12 @@ Let's go through the compiler options specified in the file: -### webapp/index.?s \(New\) +### webapp/index.ts/.js \(New\) -Now let's move on to the UI work. We create a new `index.?s` script in the webapp folder. In this script, we add a native `alert()` method with the message "UI5 is ready". +Now let's move on to the UI work. We create a new `index.ts/.js` script in the webapp folder. In this script, we add a native `alert()` method with the message "UI5 is ready". ```js +// webapp/index.js alert("UI5 is ready"); ``` diff --git a/packages/walkthrough/steps/03/README.md b/packages/walkthrough/steps/03/README.md index 837978c08..1a66bac0e 100644 --- a/packages/walkthrough/steps/03/README.md +++ b/packages/walkthrough/steps/03/README.md @@ -8,7 +8,7 @@ Now it is time to build our first little UI by replacing the “Hello World” t ### Preview -![](assets/loio30a42d381b9e4388bf7fdc0b941e5381_LowRes.png "The "Hello World" text is now displayed by a OpenUI5 control") +![The "Hello World" text is now displayed by a OpenUI5 control](assets/loio30a42d381b9e4388bf7fdc0b941e5381_LowRes.png "The "Hello World" text is now displayed by a OpenUI5 control") *The "Hello World" text is now displayed by an OpenUI5 control* @@ -17,17 +17,7 @@ You can access the live preview by clicking on this link: [🔗 Live Preview of *** ### Coding -
- -You can download the solution for this step here: [📥 Download step 3](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-03.zip). - -
- -
- -You can download the solution for this step here: [📥 Download step 3](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-03-js.zip). - -
+You can download the solution for this step here: [📥 Download step 3](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-03.zip) (TS)[📥 Download step 3](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-03-js.zip) (JS). ***
@@ -44,7 +34,7 @@ npm install @openui5/types --save-dev
-### webapp/index.?s +### webapp/index.ts/.js
@@ -70,6 +60,7 @@ To place the text control to our HTML document, we chain the constructor call of
```ts +// webapp/index.ts import Text from "sap/m/Text"; new Text({ @@ -78,6 +69,7 @@ new Text({ ``` ```js +// webapp/index.js sap.ui.define(["sap/m/Text"], function (Text) { "use strict"; @@ -152,15 +144,15 @@ ui5 add sap.m [Working with Controls](https://sdk.openui5.org/topic/91f0a22d6f4d1014b6dd926db0e91070.html "Controls are used to define the appearance and behavior of screen areas.") -[API Reference: `sap.m.Text`](https://sdk.openui5.orgapi/sap.m.Text) +[API Reference: `sap.m.Text`](https://sdk.openui5.org/#/api/sap.m.Text) -[Samples: `sap.m.Text` ](https://sdk.openui5.orgentity/sap.m.Text) +[Samples: `sap.m.Text` ](https://sdk.openui5.org/#/entity/sap.m.Text) -[API Reference: `sap.ui.core.Control`](https://sdk.openui5.orgapi/sap.ui.core.Control) +[API Reference: `sap.ui.core.Control`](https://sdk.openui5.org/#/api/sap.ui.core.Control) -[API Reference: `sap.ui.core.Element`](https://sdk.openui5.orgapi/sap.ui.core.Element) +[API Reference: `sap.ui.core.Element`](https://sdk.openui5.org/#/api/sap.ui.core.Element) -[API Reference: `sap.ui.base.ManagedObject`](https://sdk.openui5.orgapi/sap.ui.base.ManagedObject) +[API Reference: `sap.ui.base.ManagedObject`](https://sdk.openui5.org/#/api/sap.ui.base.ManagedObject)
diff --git a/packages/walkthrough/steps/04/README.md b/packages/walkthrough/steps/04/README.md index eeada18cc..7f21458d1 100644 --- a/packages/walkthrough/steps/04/README.md +++ b/packages/walkthrough/steps/04/README.md @@ -10,7 +10,7 @@ When working with OpenUI5, we recommend the use of XML views, as this produces t ### Preview -![](assets/loio05f6775a39d3409ea673f4acc3812142_LowRes.png "The "Hello World" text is now displayed by a OpenUI5 control \(No visual changes to last step\)") +![The "Hello World" text is now displayed by a OpenUI5 control \(No visual changes to last step\)](assets/loio05f6775a39d3409ea673f4acc3812142_LowRes.png "The "Hello World" text is now displayed by a OpenUI5 control \(No visual changes to last step\)") *The "Hello World" text is now displayed by an OpenUI5 control \(No visual changes to last step\)* @@ -20,17 +20,7 @@ You can access the live preview by clicking on this link: [🔗 Live Preview of ### Coding -
- -You can download the solution for this step here: [📥 Download step 4](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-04.zip). - -
- -
- -You can download the solution for this step here: [📥 Download step 4](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-04-js.zip). - -
+You can download the solution for this step here: [📥 Download step 4](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-04.zip) (TS)[📥 Download step 4](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-04-js.zip) (JS). *** ### webapp/view/App.view.xml \(New\) @@ -57,11 +47,12 @@ We have created an XML view that displays a text control with the text "Hello Wo *** -### webapp/index.?s +### webapp/index.ts/.js -As a next step, we are going to replace the `sap/m/Text` control in our `index.?s` file with the app view that we've just created. To do this, we utilize the `XMLView.create` function, which is a part of the `sap/ui/core/mvc/View` module. This function needs a `viewName` property, which indicates the resource that needs to be loaded. The `viewName` is a combination of the namespace defined in the bootstrap and the path to the app view, but without the ".view.xml" extension. In addition, we set the `id` property to "app". Providing a stable ID is beneficial as it offers an easy and consistent way to identify and refer to specific views and elements in your code, thus helping to keep your code organized. +As a next step, we are going to replace the `sap/m/Text` control in our `index.ts/.js` file with the app view that we've just created. To do this, we utilize the `XMLView.create` function, which is a part of the `sap/ui/core/mvc/View` module. This function needs a `viewName` property, which indicates the resource that needs to be loaded. The `viewName` is a combination of the namespace defined in the bootstrap and the path to the app view, but without the ".view.xml" extension. In addition, we set the `id` property to "app". Providing a stable ID is beneficial as it offers an easy and consistent way to identify and refer to specific views and elements in your code, thus helping to keep your code organized. ```ts +// webapp/index.ts import XMLView from "sap/ui/core/mvc/XMLView"; XMLView.create({ @@ -74,6 +65,7 @@ XMLView.create({ ``` ```js +// webapp/index.js sap.ui.define(["sap/ui/core/mvc/XMLView"], function (XMLView) { "use strict"; diff --git a/packages/walkthrough/steps/05/README.md b/packages/walkthrough/steps/05/README.md index 4b4975ca1..d0a569fd1 100644 --- a/packages/walkthrough/steps/05/README.md +++ b/packages/walkthrough/steps/05/README.md @@ -9,7 +9,7 @@ In this step, we replace the text with a button and show the “Hello World” m ### Preview -![](assets/loiocedfdf89b30643ddbfcab1fe50bfa892_LowRes.png "A Say Hello button is added") +![A Say Hello button is added](assets/loiocedfdf89b30643ddbfcab1fe50bfa892_LowRes.png "A Say Hello button is added") *A Say Hello button is added* @@ -19,26 +19,17 @@ You can access the live preview by clicking on this link: [🔗 Live Preview of ### Coding -
- -You can download the solution for this step here: [📥 Download step 5](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-05.zip). - -
- -
- -You can download the solution for this step here: [📥 download step 5](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-05-js.zip). - -
+You can download the solution for this step here: [📥 Download step 5](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-05.zip) (TS)[📥 Download step 5](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-05-js.zip) (JS). *** -### webapp/controller/App.controller.?s \(New\) +### webapp/controller/App.controller.ts/.js \(New\) First of all, we need a controller for our app view that defines how the view should react to user inputs, such as a button press event. -We create a new folder called `controller` inside the `webapp` folder. This folder will hold all our controller files. Inside the `controller` folder, we create a new file called `App.controller.?s`. We define the app controller in its own file by extending the OpenUI5-provided `sap/ui/core/mvc/Controller`. In the beginning, it holds only a single function called `onShowHello` that shows an alert with the static text "Hello World". +We create a new folder called `controller` inside the `webapp` folder. This folder will hold all our controller files. Inside the `controller` folder, we create a new file called `App.controller.ts/.js`. We define the app controller in its own file by extending the OpenUI5-provided `sap/ui/core/mvc/Controller`. In the beginning, it holds only a single function called `onShowHello` that shows an alert with the static text "Hello World". ```ts +// webapp/controller/App.controller.ts import Controller from "sap/ui/core/mvc/Controller"; /** @@ -54,6 +45,7 @@ export default class AppController extends Controller { ``` ```js +// webapp/controller/App.controller.js sap.ui.define(["sap/ui/core/mvc/Controller"], function (Controller) { "use strict"; @@ -108,7 +100,7 @@ A view does not necessarily need an explicitly assigned controller. You do not h - Event handlers are prefixed with `on` -- Controller names always end with `*.controller.?s` +- Controller names always end with `*.controller.ts/.js`   diff --git a/packages/walkthrough/steps/06/README.md b/packages/walkthrough/steps/06/README.md index 29b18b4a2..eca32c429 100644 --- a/packages/walkthrough/steps/06/README.md +++ b/packages/walkthrough/steps/06/README.md @@ -8,7 +8,7 @@ In OpenUI5, resources are often referred to as modules. In this step, we replace ### Preview -![](assets/loio2f629a95211f49afa367b60d233fb390_LowRes.png "A message toast displays the "Hello World" message") +![A message toast displays the "Hello World" message](assets/loio2f629a95211f49afa367b60d233fb390_LowRes.png "A message toast displays the "Hello World" message") *A message toast displays the "Hello World" message* @@ -18,25 +18,16 @@ You can access the live preview by clicking on this link: [🔗 Live Preview of ### Coding -
- -You can download the solution for this step here: [📥 Download step 6](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-06.zip). - -
- -
- -You can download the solution for this step here: [📥 Download step 6](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-06-js.zip). - -
+You can download the solution for this step here: [📥 Download step 6](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-06.zip) (TS)[📥 Download step 6](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-06-js.zip) (JS). *** -### webapp/controller/App.controller.?s +### webapp/controller/App.controller.ts/.js We now replace the native `alert` function with the `show` method of the `sap.m.MessageToast` control of OpenUI5. ```ts +// webapp/controller/App.controller.ts import MessageToast from "sap/m/MessageToast"; import Controller from "sap/ui/core/mvc/Controller"; @@ -52,6 +43,7 @@ export default class AppController extends Controller { ``` ```js +// webapp/controller/App.controller.js sap.ui.define(["sap/m/MessageToast", "sap/ui/core/mvc/Controller"], function (MessageToast, Controller) { "use strict"; diff --git a/packages/walkthrough/steps/07/README.md b/packages/walkthrough/steps/07/README.md index 6d7dcc312..bb485b224 100644 --- a/packages/walkthrough/steps/07/README.md +++ b/packages/walkthrough/steps/07/README.md @@ -10,7 +10,7 @@ We'll create a view model in our controller, add an input field to our app, bind ### Preview -![](assets/loioafc105517a644407bd90662e3d94ea01_LowRes.png "An input field and a description displaying the value of the input field") +![An input field and a description displaying the value of the input field](assets/loioafc105517a644407bd90662e3d94ea01_LowRes.png "An input field and a description displaying the value of the input field") *An input field and a description displaying the value of the input field* @@ -20,20 +20,10 @@ You can access the live preview by clicking on this link: [🔗 Live Preview of ### Coding -
- -You can download the solution for this step here: [📥 Download step 7](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-07.zip). - -
- -
- -You can download the solution for this step here: [📥 Download step 7](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-07-js.zip). - -
+You can download the solution for this step here: [📥 Download step 7](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-07.zip) (TS)[📥 Download step 7](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-07-js.zip) (JS). *** -### webapp/controller/App.controller.?s +### webapp/controller/App.controller.ts/.js In the controller, we'll create a new data model and link it to the view that is related to the controller. The best time to create a model is during the `onInit` method. This is a special method in the Controller class that is automatically invoked by the framework when the controller is first set up. @@ -43,6 +33,7 @@ Next, we need to link this data model to our view. We do this by first obtaining ```ts +// webapp/controller/App.controller.ts import MessageToast from "sap/m/MessageToast"; import Controller from "sap/ui/core/mvc/Controller"; import JSONModel from "sap/ui/model/json/JSONModel"; @@ -70,6 +61,7 @@ export default class AppController extends Controller { ``` ```js +// webapp/controller/App.controller.js sap.ui.define(["sap/m/MessageToast", "sap/ui/core/mvc/Controller", "sap/ui/model/json/JSONModel"], function (MessageToast, Controller, JSONModel) { "use strict"; @@ -150,6 +142,6 @@ In addition to this, we create a greeting message. We combine the static text "H [Property Binding](https://sdk.openui5.org/topic/91f0652b6f4d1014b6dd926db0e91070.html "With property binding, you can initialize properties of a control automatically and update them based on the data of the model.") -[Binding Path](https://ui5.sap.com/#/topic/2888af49635949eca14fa326d04833b9.html "Binding paths address the different properties and lists in a model and define how a node in the hierarchical data tree can be found.") +[Binding Path](https://sdk.openui5.org/topic/2888af49635949eca14fa326d04833b9.html "Binding paths address the different properties and lists in a model and define how a node in the hierarchical data tree can be found.") [API Reference: `sap.ui.base.ManagedObject.PropertyBindingInfo`](https://sdk.openui5.org/api/sap.ui.base.ManagedObject.PropertyBindingInfo "Configuration for the binding of a managed property.") diff --git a/packages/walkthrough/steps/08/README.md b/packages/walkthrough/steps/08/README.md index 28b47b796..3c3671374 100644 --- a/packages/walkthrough/steps/08/README.md +++ b/packages/walkthrough/steps/08/README.md @@ -10,7 +10,7 @@ This way, they are all in a central place and can be easily translated into othe ### Preview -![](assets/loio0eb579e2f2a64c5a9894086322c7faa0_LowRes.png "An input field and a description displaying the value of the input field \(No visual changes to last step\(") +![An input field and a description displaying the value of the input field \(No visual changes to last step\(](assets/loio0eb579e2f2a64c5a9894086322c7faa0_LowRes.png "An input field and a description displaying the value of the input field \(No visual changes to last step\(") *An input field and a description displaying the value of the input field \(No visual changes to last step\)* @@ -19,17 +19,7 @@ You can access the live preview by clicking on this link: [🔗 Live Preview of *** ### Coding -
- -You can download the solution for this step here: [📥 Download step 8](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-08.zip). - -
- -
- -You can download the solution for this step here: [📥 Download step 8](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-08-js.zip). - -
+You can download the solution for this step here: [📥 Download step 8](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-08.zip) (TS)[📥 Download step 8](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-08-js.zip) (JS). *** ### webapp/i18n/i18n.properties \(New\) @@ -47,7 +37,7 @@ In this tutorial we'll only have one properties file. However, in real-world pro *** -### webapp/controller/App.controller.?s +### webapp/controller/App.controller.ts/.js In the controller, we'll create a new resource model that refers to our resource bundle file (`i18n.properties`) and link it to the view associated with the controller. This allows us to bind control properties in the view to translatable texts. We'll also modify the `onShowHello` event handler function to replace the static "Hello World" text with a dynamic greeting text. @@ -57,6 +47,7 @@ In the `onShowHello` event handler function, we first get access to the data mod ```ts +// webapp/controller/App.controller.ts import MessageToast from "sap/m/MessageToast"; import Controller from "sap/ui/core/mvc/Controller"; import JSONModel from "sap/ui/model/json/JSONModel"; @@ -97,6 +88,7 @@ export default class AppController extends Controller { ``` ```js +// webapp/controller/App.controller.js sap.ui.define(["sap/m/MessageToast", "sap/ui/core/mvc/Controller", "sap/ui/model/json/JSONModel", "sap/ui/model/resource/ResourceModel"], function (MessageToast, Controller, JSONModel, ResourceModel) { "use strict"; diff --git a/packages/walkthrough/steps/09/README.md b/packages/walkthrough/steps/09/README.md index 6b0e3b431..242f39100 100644 --- a/packages/walkthrough/steps/09/README.md +++ b/packages/walkthrough/steps/09/README.md @@ -13,45 +13,36 @@ By encapsulating our application as a component, we can seamlessly integrate it ### Preview -![](assets/loiocac9bcfa902c44c496d115acd7ee7376_LowRes.png "An input field and a description displaying the value of the input field (No visual changes to last step)") +![An input field and a description displaying the value of the input field (No visual changes to last step)](assets/loiocac9bcfa902c44c496d115acd7ee7376_LowRes.png "An input field and a description displaying the value of the input field (No visual changes to last step)") *An input field and a description displaying the value of the input field \(No visual changes to last step\)* You can access the live preview by clicking on this link: [🔗 Live Preview of Step 9](https://ui5.github.io/tutorials/walkthrough/build/09/index-cdn.html). -After this step your project structure will look like the figure below. We will create the `Component.?s` file now and modify the related files in the app. +After this step your project structure will look like the figure below. We will create the `Component.ts/.js` file now and modify the related files in the app. -![](assets/loio1e237a36972a44ac8522dd1a540ac062_LowRes.png "Folder Structure for this Step") +![Folder Structure for this Step](assets/loio1e237a36972a44ac8522dd1a540ac062_LowRes.png "Folder Structure for this Step") *Folder Structure for this Step* *** ### Coding -
- -You can download the solution for this step here: [📥 Download step 9](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-09.zip). - -
- -
- -You can download the solution for this step here: [📥 Download step 9](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-09-js.zip). - -
+You can download the solution for this step here: [📥 Download step 9](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-09.zip) (TS)[📥 Download step 9](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-09-js.zip) (JS). *** -### webapp/Component.?s \(New\) +### webapp/Component.ts/.js \(New\) -We navigate to the `webapp` folder and place the `Component.?s` file to it. This file is commonly referred to as the component controller. A component is organized in a unique namespace \(which is synonymous with the application namespace\). All required and optional resources of the component have to be organized in the namespace of the component. +We navigate to the `webapp` folder and place the `Component.ts/.js` file to it. This file is commonly referred to as the component controller. A component is organized in a unique namespace \(which is synonymous with the application namespace\). All required and optional resources of the component have to be organized in the namespace of the component. We define the component by extending `sap/ui/core/UIComponent` and supplement the component with additional metadata. Within the `interfaces` settings, we specify that the component should implement the `sap/ui/core/IAsyncContentCreation` interface. This allows the component to be generated asynchronously, which in turn sets the component's rootView and router configuration to async. When the component is instantiated, OpenUI5 automatically calls the `init` function of the component. It's obligatory to make the super call to the `init` function of the base class in the overridden `init` method. In this section, we also instantiate our data model and the `i18n` model, similar to what we did earlier in the `onInit` function of our app controller (*Hint: besides the `bundleName` we also specifiy the `supportedLocales` with an empty string as value in an array and an empty `fallbackLocale` to ensure that just the default `messagebundle.properties` is loaded without a language suffix to avoid 404s during development!*). -Finally we call the `createContent` hook method of the component. This method creates the content \(UI Control Tree\) of this component. Here, we create the view as we did in the `index.?s` file to set our app view as the root view of the component. +Finally we call the `createContent` hook method of the component. This method creates the content \(UI Control Tree\) of this component. Here, we create the view as we did in the `index.ts/.js` file to set our app view as the root view of the component. ```ts +// webapp/Component.ts import Control from "sap/ui/core/Control"; import XMLView from "sap/ui/core/mvc/XMLView"; import JSONModel from "sap/ui/model/json/JSONModel"; @@ -96,6 +87,7 @@ export default class Component extends UIComponent { ``` ```js +// webapp/Component.js sap.ui.define(["sap/ui/core/UIComponent", "sap/ui/core/mvc/XMLView", "sap/ui/model/json/JSONModel", "sap/ui/model/resource/ResourceModel"], function (UIComponent, XMLView, JSONModel, ResourceModel) { "use strict"; @@ -139,11 +131,12 @@ Be aware that the models are set directly on the component and not on the root v *** -### webapp/controller/App.controller.?s +### webapp/controller/App.controller.ts/.js We delete the `onInit` function from the app controller; this is now done in the component controller. ```ts +// webapp/controller/App.controller.ts import MessageToast from "sap/m/MessageToast"; import Controller from "sap/ui/core/mvc/Controller"; import JSONModel from "sap/ui/model/json/JSONModel"; @@ -167,6 +160,7 @@ export default class AppController extends Controller { ``` ```js +// webapp/controller/App.controller.js sap.ui.define(["sap/m/MessageToast", "sap/ui/core/mvc/Controller"], function (MessageToast, Controller) { "use strict"; @@ -191,7 +185,7 @@ sap.ui.define(["sap/m/MessageToast", "sap/ui/core/mvc/Controller"], function (Me ``` *** -#### webapp/index.?s +#### webapp/index.ts/.js We'll replace the view with a UI component. To do this, we use a control called `ComponentContainer`. This control allows us to wrap a UI Component and place it in our HTML document. We configure this instance by providing the following options: @@ -208,6 +202,7 @@ We'll replace the view with a UI component. To do this, we use a control called Finally, we position our newly created `ComponentContainer` control within the HTML element with the id `content`. ```ts +// webapp/index.ts import ComponentContainer from "sap/ui/core/ComponentContainer"; new ComponentContainer({ @@ -223,6 +218,7 @@ new ComponentContainer({ ``` ```js +// webapp/index.js sap.ui.define(["sap/ui/core/ComponentContainer"], function (ComponentContainer) { "use strict"; @@ -242,7 +238,7 @@ sap.ui.define(["sap/ui/core/ComponentContainer"], function (ComponentContainer) ### Conventions -- The component is named `Component.?s`. +- The component is named `Component.ts/.js`. - Together with all UI assets of the app, the component is located in the `webapp` folder. diff --git a/packages/walkthrough/steps/10/README.md b/packages/walkthrough/steps/10/README.md index b6f1e6e4d..ab68c8043 100644 --- a/packages/walkthrough/steps/10/README.md +++ b/packages/walkthrough/steps/10/README.md @@ -9,7 +9,7 @@ Instead of relying on a local HTML file for the bootstrap, the manifest is parse *** ### Preview -![](assets/loio7b2aef85c016485da4a31c087bf4c0f0_LowRes.png "An input field and a description displaying the value of the input field \(No visual changes to last step\)") +![An input field and a description displaying the value of the input field \(No visual changes to last step\)](assets/loio7b2aef85c016485da4a31c087bf4c0f0_LowRes.png "An input field and a description displaying the value of the input field \(No visual changes to last step\)") *An input field and a description displaying the value of the input field \(No visual changes to last step\)* @@ -19,17 +19,7 @@ You can access the live preview by clicking on this link: [🔗 Live Preview of ### Coding -
- -You can download the solution for this step here: [📥 Download step 10](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-10.zip). - -
- -
- -You can download the solution for this step here: [📥 Download step 10](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-10-js.zip). - -
+You can download the solution for this step here: [📥 Download step 10](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-10.zip) (TS)[📥 Download step 10](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-10-js.zip) (JS). *** ### webapp/i18n/i18n.properties @@ -161,13 +151,14 @@ In our current scenario, we only have one model called `i18n`, which is a resour *** -### webapp/Component.?s +### webapp/Component.ts/.js To apply the settings specified in the manifest to the component, we need to include the manifest in the component's metadata. To do this, we add a `manifest` property to the `metadata` section of the component and set it to "json". This property acts as a reference to the `manifest.json` file, which will be loaded and used. Now that the resource model is automatically instantiated based on the configuration in the manifest, we can safely remove the corresponding code block from the `init` method in our component controller. This also means that we can remove the import statement for the `ResourceModel` module from `sap/ui/model/resource/ResourceModel`, as it is no longer needed. Additionally, we can remove the `createContent` call since the configuration of the rootView is specified in the manifest and therefore makes the implementation in this method unnecessary. ```ts +// webapp/Component.ts import UIComponent from "sap/ui/core/UIComponent"; import JSONModel from "sap/ui/model/json/JSONModel"; @@ -197,6 +188,7 @@ export default class Component extends UIComponent { ``` ```js +// webapp/Component.js sap.ui.define(["sap/ui/core/UIComponent", "sap/ui/model/json/JSONModel"], function (UIComponent, JSONModel) { "use strict"; @@ -259,7 +251,7 @@ It's worth noting that the `ComponentSupport` module enforces asynchronous loadi ``` -We can now delete our `index.?s` file, because our component is now initiated directly in the HTML markup. +We can now delete our `index.ts/.js` file, because our component is now initiated directly in the HTML markup. *** diff --git a/packages/walkthrough/steps/11/README.md b/packages/walkthrough/steps/11/README.md index 9a61b5cd4..49c487ce9 100644 --- a/packages/walkthrough/steps/11/README.md +++ b/packages/walkthrough/steps/11/README.md @@ -8,7 +8,7 @@ After all the work on the app structure it’s time to improve the look of our a ### Preview -![](assets/loio97feb5417c89462ead5b4259f3ecfd47_LowRes.png "A panel is now displaying the controls from the previous steps") +![A panel is now displaying the controls from the previous steps](assets/loio97feb5417c89462ead5b4259f3ecfd47_LowRes.png "A panel is now displaying the controls from the previous steps") *A panel is now displaying the controls from the previous steps* @@ -19,17 +19,7 @@ You can access the live preview by clicking on this link: [🔗 Live Preview of ### Coding -
- -You can download the solution for this step here: [📥 Download step 11](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-11.zip). - -
- -
- -You can download the solution for this step here: [📥 Download step 11](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-11-js.zip). - -
+You can download the solution for this step here: [📥 Download step 11](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-11.zip) (TS)[📥 Download step 11](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-11-js.zip) (JS). *** ### webapp/i18n/i18n.properties diff --git a/packages/walkthrough/steps/12/README.md b/packages/walkthrough/steps/12/README.md index ecdd05ad8..efaf345e0 100644 --- a/packages/walkthrough/steps/12/README.md +++ b/packages/walkthrough/steps/12/README.md @@ -8,7 +8,7 @@ Now we use a shell control as container for our app and use it as our new root e ### Preview -![](assets/loio0becf3ee81f5486a864e3b39ba036402_LowRes.png "The app is now run in a shall that limits the app width") +![The app is now run in a shall that limits the app width](assets/loio0becf3ee81f5486a864e3b39ba036402_LowRes.png "The app is now run in a shall that limits the app width") *The app is now run in a shell that limits the app width* @@ -18,17 +18,7 @@ You can access the live preview by clicking on this link: [🔗 Live Preview of ### Coding -
- -You can download the solution for this step here: [📥 Download step 12](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-12.zip). - -
- -
- -You can download the solution for this step here: [📥 Download step 12](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-12-js.zip). - -
+You can download the solution for this step here: [📥 Download step 12](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-12.zip) (TS)[📥 Download step 12](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-12-js.zip) (JS). *** ### webapp/view/App.view.xml diff --git a/packages/walkthrough/steps/13/README.md b/packages/walkthrough/steps/13/README.md index 6b57edd76..a48ffca7e 100644 --- a/packages/walkthrough/steps/13/README.md +++ b/packages/walkthrough/steps/13/README.md @@ -10,7 +10,7 @@ Instead of manually adding CSS to the controls, we will use the standard classes ### Preview -![](assets/loio0becf3ee81f5486a864e3b39ba036402_LowRes.png "The layout of the panel and its content now has margins and padding") +![The layout of the panel and its content now has margins and padding](assets/loio0becf3ee81f5486a864e3b39ba036402_LowRes.png "The layout of the panel and its content now has margins and padding") *The layout of the panel and its content now has margins and padding* @@ -20,17 +20,7 @@ You can access the live preview by clicking on this link: [🔗 Live Preview of ### Coding -
- -You can download the solution for this step here: [📥 Download step 13](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-13.zip). - -
- -
- -You can download the solution for this step here: [📥 Download step 13](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-13-js.zip). - -
+You can download the solution for this step here: [📥 Download step 13](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-13.zip) (TS)[📥 Download step 13](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-13-js.zip) (JS). *** ### webapp/view/App.view.xml diff --git a/packages/walkthrough/steps/14/README.md b/packages/walkthrough/steps/14/README.md index 92bf4f26f..36647909f 100644 --- a/packages/walkthrough/steps/14/README.md +++ b/packages/walkthrough/steps/14/README.md @@ -11,7 +11,7 @@ Sometimes we need to define some more fine-granular layouts and this is when we ### Preview -![](assets/loiod9a40e539b7c49c485be821efbd3821f_LowRes.png "The space between the button and the input field is now smaller and the output text is bold") +![The space between the button and the input field is now smaller and the output text is bold](assets/loiod9a40e539b7c49c485be821efbd3821f_LowRes.png "The space between the button and the input field is now smaller and the output text is bold") *The space between the button and the input field is now smaller and the output text is bold* @@ -22,17 +22,7 @@ You can access the live preview by clicking on this link: [🔗 Live Preview of ### Coding -
- -You can download the solution for this step here: [📥 Download step 14](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-14.zip). - -
- -
- -You can download the solution for this step here: [📥 Download step 14](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-14-js.zip). - -
+You can download the solution for this step here: [📥 Download step 14](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-14.zip) (TS)[📥 Download step 14](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-14-js.zip) (JS). *** ### webapp/css/style.css \(New\) diff --git a/packages/walkthrough/steps/15/README.md b/packages/walkthrough/steps/15/README.md index 4c391b99f..9855b5f28 100644 --- a/packages/walkthrough/steps/15/README.md +++ b/packages/walkthrough/steps/15/README.md @@ -8,7 +8,7 @@ Our panel content is getting more and more complex and now it is time to move th ### Preview -![](assets/loiof3724d2f97e94a78b27d8ab01ff9c37d_LowRes.png "The panel content is now refactored to a separate view \(No visual changes to last step\)") +![The panel content is now refactored to a separate view \(No visual changes to last step\)](assets/loiof3724d2f97e94a78b27d8ab01ff9c37d_LowRes.png "The panel content is now refactored to a separate view \(No visual changes to last step\)") *The panel content is now refactored to a separate view \(No visual changes to last step\)* @@ -19,24 +19,15 @@ You can access the live preview by clicking on this link: [🔗 Live Preview of ### Coding -
- -You can download the solution for this step here: [📥 Download step 15](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-15.zip). - -
- -
- -You can download the solution for this step here: [📥 Download step 15](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-15-js.zip). - -
+You can download the solution for this step here: [📥 Download step 15](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-15.zip) (TS)[📥 Download step 15](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-15-js.zip) (JS). *** -### webapp/controller/HelloPanel.controller.?s \(New\) +### webapp/controller/HelloPanel.controller.ts/.js \(New\) -In folder `webapp/controller` we create a new `HelloPanel.controller.?s` file and move the method `onShowHello` of the app controller to it, so we get a reusable asset. +In folder `webapp/controller` we create a new `HelloPanel.controller.ts/.js` file and move the method `onShowHello` of the app controller to it, so we get a reusable asset. ```ts +// webapp/controller/HelloPanel.controller.ts import Controller from "sap/ui/core/mvc/Controller"; import MessageToast from "sap/m/MessageToast"; import JSONModel from "sap/ui/model/json/JSONModel"; @@ -62,6 +53,7 @@ export default class HelloPanel extends Controller { ``` ```js +// webapp/controller/HelloPanel.controller.js sap.ui.define(["sap/ui/core/mvc/Controller", "sap/m/MessageToast"], function (Controller, MessageToast) { "use strict"; @@ -135,11 +127,12 @@ In the App view, we remove the panel control and its content and put the `XMLVie ``` -### webapp/controller/App.controller.?s +### webapp/controller/App.controller.ts/.js We remove the `onShowHello` method from the App controller, as this is not needed anymore. ```ts +// webapp/controller/App.controller.ts import Controller from "sap/ui/core/mvc/Controller"; /** * @namespace ui5.tutorial.walkthrough.controller @@ -151,6 +144,7 @@ export default class App extends Controller { ``` ```js +// webapp/controller/App.controller.js sap.ui.define(["sap/ui/core/mvc/Controller"], function (Controller) { "use strict"; diff --git a/packages/walkthrough/steps/16/README.md b/packages/walkthrough/steps/16/README.md index ae1a167ee..d9f1e7d53 100644 --- a/packages/walkthrough/steps/16/README.md +++ b/packages/walkthrough/steps/16/README.md @@ -14,7 +14,7 @@ We will now add a dialog to our app. Dialogs are special, because they open on t ### Preview -![](assets/loio0916080895e144ed8b31963bfb18e17f_LowRes.png "A dialog opens when the new "Say Hello With Dialog" button is clicked") +![A dialog opens when the new "Say Hello With Dialog" button is clicked](assets/loio0916080895e144ed8b31963bfb18e17f_LowRes.png "A dialog opens when the new "Say Hello With Dialog" button is clicked") *A dialog opens when the new “Say Hello With Dialog” button is clicked* @@ -25,17 +25,7 @@ You can access the live preview by clicking on this link: [🔗 Live Preview of ### Coding -
- -You can download the solution for this step here: [📥 Download step 16](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-16.zip). - -
- -
- -You can download the solution for this step here: [📥 Download step 16](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-16-js.zip). - -
+You can download the solution for this step here: [📥 Download step 16](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-16.zip) (TS)[📥 Download step 16](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-16-js.zip) (JS). *** ### webapp/view/HelloDialog.fragment.xml \(New\) @@ -54,7 +44,7 @@ We add a new XML file to declaratively define our dialog in a fragment. The Frag   The syntax is similar to a view, but since fragments do not have a controller this attribute is missing. Also, the fragment does not have any footprint in the DOM tree of the app, and there is no control instance of the fragment itself (only the contained controls). It is simply a container for a set of reuse controls. -### webapp/controller/HelloPanel.controller.?s +### webapp/controller/HelloPanel.controller.ts/.js In the HelloPanel controller, we define a new event handler function `onOpenDialog` which calls the dialog in the HelloDialog fragment when triggered. To do so we need the `sap.m.Dialog` module. @@ -63,6 +53,7 @@ Using async/await, we handle the opening of the dialog asynchronously whenever t If the dialog fragment does not exist yet, the fragment is instantiated by calling the `loadFragment` API. We then store the dialog on the controller instance. This allows us to reuse the dialog every time the event is triggered again. ```ts +// webapp/controller/HelloPanel.controller.ts import Controller from "sap/ui/core/mvc/Controller"; import MessageToast from "sap/m/MessageToast"; import JSONModel from "sap/ui/model/json/JSONModel"; @@ -89,6 +80,7 @@ export default class HelloPanel extends Controller { ``` ```js +// webapp/controller/HelloPanel.controller.js sap.ui.define(["sap/ui/core/mvc/Controller", "sap/m/MessageToast"], function (Controller, MessageToast) { "use strict"; diff --git a/packages/walkthrough/steps/17/README.md b/packages/walkthrough/steps/17/README.md index 357059633..ddb300017 100644 --- a/packages/walkthrough/steps/17/README.md +++ b/packages/walkthrough/steps/17/README.md @@ -8,7 +8,7 @@ Now that we have integrated the dialog, it's time to add some user interaction. ### Preview -![](assets/loioc351bbd078824c43bf1758b0c3679cbd_LowRes.png "The dialog now has an "OK" button") +![The dialog now has an "OK" button](assets/loioc351bbd078824c43bf1758b0c3679cbd_LowRes.png "The dialog now has an "OK" button") *The dialog now has an "OK" button to close the dialog* @@ -17,24 +17,15 @@ You can access the live preview by clicking on this link: [🔗 Live Preview of ### Coding -
- -You can download the solution for this step here: [📥 Download step 17](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-17.zip). - -
- -
- -You can download the solution for this step here: [📥 Download step 17](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-17-js.zip). - -
+You can download the solution for this step here: [📥 Download step 17](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-17.zip) (TS)[📥 Download step 17](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-17-js.zip) (JS). *** -### webapp/controller/HelloPanel.controller.?s +### webapp/controller/HelloPanel.controller.ts/.js We add an `onCloseDialog` event handler function into the HelloPanel controller file that closes the dialog when triggered. To get the dialog instance we use the `byId` function and then call the `close` function of the dialog. ```ts +// webapp/controller/HelloPanel.controller.ts import Controller from "sap/ui/core/mvc/Controller"; import MessageToast from "sap/m/MessageToast"; import JSONModel from "sap/ui/model/json/JSONModel"; @@ -69,6 +60,7 @@ export default class HelloPanel extends Controller { ``` ```js +// webapp/controller/HelloPanel.controller.js sap.ui.define(["sap/ui/core/mvc/Controller", "sap/m/MessageToast"], function (Controller, MessageToast) { "use strict"; diff --git a/packages/walkthrough/steps/18/README.md b/packages/walkthrough/steps/18/README.md index d21eaa02c..21fa68209 100644 --- a/packages/walkthrough/steps/18/README.md +++ b/packages/walkthrough/steps/18/README.md @@ -8,7 +8,7 @@ Our dialog is still pretty much empty. Since OpenUI5 is shipped with a large ico ### Preview -![](assets/loiofbc48e23cc7d45e393cc95bbbfc6e0a3_LowRes.png "An icon is now displayed in the dialog box") +![An icon is now displayed in the dialog box](assets/loiofbc48e23cc7d45e393cc95bbbfc6e0a3_LowRes.png "An icon is now displayed in the dialog box") *An icon is now displayed in the dialog box* @@ -17,17 +17,7 @@ You can access the live preview by clicking on this link: [🔗 Live Preview of ### Coding -
- -You can download the solution for this step here: [📥 Download step 18](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-18.zip). - -
- -
- -You can download the solution for this step here: [📥 Download step 18](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-18-js.zip). - -
+You can download the solution for this step here: [📥 Download step 18](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-18.zip) (TS)[📥 Download step 18](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-18-js.zip) (JS). *** ### webapp/view/HelloPanel.view.xml diff --git a/packages/walkthrough/steps/19/README.md b/packages/walkthrough/steps/19/README.md index 689ce0c50..7796ac74b 100644 --- a/packages/walkthrough/steps/19/README.md +++ b/packages/walkthrough/steps/19/README.md @@ -7,7 +7,7 @@ Now that we have established a good structure for our app, it's time to add some ### Preview -![](assets/loiob05bdb47393b4abda3e1b54498959c38_LowRes.png "A list of invoices is displayed below the panel") +![A list of invoices is displayed below the panel](assets/loiob05bdb47393b4abda3e1b54498959c38_LowRes.png "A list of invoices is displayed below the panel") *A list of invoices is displayed below the panel* @@ -17,17 +17,7 @@ You can access the live preview by clicking on this link: [🔗 Live Preview of ### Coding -
- -You can download the solution for this step here: [📥 Download step 19](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-19.zip). - -
- -
- -You can download the solution for this step here: [📥 Download step 19](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-19-js.zip). - -
+You can download the solution for this step here: [📥 Download step 19](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-19.zip) (TS)[📥 Download step 19](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-19-js.zip) (JS). *** ### webapp/model/localInvoices.json \(New\) @@ -212,9 +202,9 @@ In the app view we add a second view and assign it to our newly created InvoiceL [Folder Structure: Where to Put Your Files](https://sdk.openui5.org/topic/003f755d46d34dd1bbce9ffe08c8d46a.html "The details described here represent a best practice for structuring an application that features one component, one OData service and less than 20 views. If you're building an app that has more components, OData services and views, you may have to introduce more folder levels than described here.") -[Lists](https://sdk.openui5.org/#/topic/1da158152f644ba1ad408a3e982fd3df.html "Lists have properties and events and they contain list items that inherit from sap.m.ListItemBase, which provides navigation, selection and event features. The list item type determines the way the list item interacts by providing additional features.") +[Lists](https://sdk.openui5.org/topic/1da158152f644ba1ad408a3e982fd3df.html "Lists have properties and events and they contain list items that inherit from sap.m.ListItemBase, which provides navigation, selection and event features. The list item type determines the way the list item interacts by providing additional features.") -[List Binding (Aggregation Binding)](https://sdk.openui5.org/#/topic/91f057786f4d1014b6dd926db0e91070.html "List binding (or aggregation binding) is used to automatically create child controls according to model data.") +[List Binding (Aggregation Binding)](https://sdk.openui5.org/topic/91f057786f4d1014b6dd926db0e91070.html "List binding (or aggregation binding) is used to automatically create child controls according to model data.") [API Reference: `sap.ui.base.ManagedObject.AggregationBindingInfo`](https://sdk.openui5.org/api/sap.ui.base.ManagedObject.AggregationBindingInfo) diff --git a/packages/walkthrough/steps/20/README.md b/packages/walkthrough/steps/20/README.md index b6b176b75..84d405454 100644 --- a/packages/walkthrough/steps/20/README.md +++ b/packages/walkthrough/steps/20/README.md @@ -8,7 +8,7 @@ The list of invoices is already looking nice, but what is an invoice without a p ### Preview -![](assets/loiodc9e919119564ddab78b8d0550ecfa9b_LowRes.png "The list of invoices with prices and number units") +![The list of invoices with prices and number units](assets/loiodc9e919119564ddab78b8d0550ecfa9b_LowRes.png "The list of invoices with prices and number units") *The list of invoices with prices and number units* @@ -17,26 +17,17 @@ You can access the live preview by clicking on this link: [🔗 Live Preview of ### Coding -
- -You can download the solution for this step here: [📥 Download step 20](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-20.zip). - -
- -
- -You can download the solution for this step here: [📥 Download step 20](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-20-js.zip). - -
+You can download the solution for this step here: [📥 Download step 20](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-20.zip) (TS)[📥 Download step 20](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-20-js.zip) (JS). *** -### webapp/controller/InvoiceList.controller.?s \(New\) +### webapp/controller/InvoiceList.controller.ts/.js \(New\) We want to display in our list view the price in Euro. Since currency information isn't available in our backend data model, we'll handle the currency formatting within the application. We'll create a controller for the InvoiceList view and use a JSON model (`sap/ui/model/json/JSONModel`) to store the currency code. This model will contain a single property, `currency: "EUR"`, which will be used for formatting the prices in the view. ```ts +// webapp/controller/InvoiceList.controller.ts import Controller from "sap/ui/core/mvc/Controller"; import JSONModel from "sap/ui/model/json/JSONModel"; @@ -56,6 +47,7 @@ export default class App extends Controller { ``` ```js +// webapp/controller/InvoiceList.controller.js sap.ui.define(["sap/ui/core/mvc/Controller", "sap/ui/model/json/JSONModel"], function (Controller, JSONModel) { "use strict"; @@ -143,7 +135,7 @@ As you can see above, the example uses a special binding syntax for the `number` [API Reference: sap.ui.base.ManagedObject.PropertyBindingInfo](https://sdk.openui5.org/api/sap.ui.base.ManagedObject.PropertyBindingInfo) -[API Reference: `sap.ui.model.type`](https://ui5.sap.com/#/api/sap.ui.model.type) +[API Reference: `sap.ui.model.type`](https://sdk.openui5.org/#/api/sap.ui.model.type) [API Reference: sap.ui.model.type.Currency](https://sdk.openui5.org/api/sap.ui.model.type.Currency) diff --git a/packages/walkthrough/steps/21/README.md b/packages/walkthrough/steps/21/README.md index 88668940a..82c4c9bbb 100644 --- a/packages/walkthrough/steps/21/README.md +++ b/packages/walkthrough/steps/21/README.md @@ -8,7 +8,7 @@ Sometimes the predefined types of OpenUI5 are not flexible enough and you want t ### Preview -![](assets/loio636b7008113442c8a4765bb710dd8ea9_LowRes.png "The price is now formatted according to its number") +![The price is now formatted according to its number](assets/loio636b7008113442c8a4765bb710dd8ea9_LowRes.png "The price is now formatted according to its number") *The price is now formatted according to its number* @@ -18,17 +18,7 @@ You can access the live preview by clicking on this link: [🔗 Live Preview of ### Coding -
- -You can download the solution for this step here: [📥 Download step 21](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-21.zip). - -
- -
- -You can download the solution for this step here: [📥 Download step 21](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-21-js.zip). - -
+You can download the solution for this step here: [📥 Download step 21](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-21.zip) (TS)[📥 Download step 21](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-21-js.zip) (JS). *** ### webapp/view/InvoiceList.view.xml diff --git a/packages/walkthrough/steps/22/README.md b/packages/walkthrough/steps/22/README.md index 1d44fe235..57924029c 100644 --- a/packages/walkthrough/steps/22/README.md +++ b/packages/walkthrough/steps/22/README.md @@ -8,7 +8,7 @@ If we want to do a more complex logic for formatting properties of our data mode ### Preview -![](assets/loio7aa185a90dd7495cb6ec30c96bc80a54_LowRes.png "A status is now displayed with a custom formatter") +![A status is now displayed with a custom formatter](assets/loio7aa185a90dd7495cb6ec30c96bc80a54_LowRes.png "A status is now displayed with a custom formatter") *A status is now displayed with a custom formatter* @@ -18,17 +18,7 @@ You can access the live preview by clicking on this link: [🔗 Live Preview of ### Coding -
- -You can download the solution for this step here: [📥 Download step 22](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-22.zip). - -
- -
- -You can download the solution for this step here: [📥 Download step 22](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-22-js.zip). - -
+You can download the solution for this step here: [📥 Download step 22](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-22.zip) (TS)[📥 Download step 22](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-22-js.zip) (JS). *** ### webapp/i18n/i18n.properties @@ -47,13 +37,14 @@ invoiceStatusB=In Progress invoiceStatusC=Done ``` -### webapp/model/formatter.?s \(New\) +### webapp/model/formatter.ts/.js \(New\) We will create a formatter function to transform status codes into user-friendly text labels. -We create a file named `formatter.?s` within the `model` folder. This module contains the `statusText` function which takes a status code as input, retrieves the corresponding descriptive text from the resource bundle, and returns it. If no matching text is found in the resource bundle, or if the resource bundle can't be found, the function returns the original status code itself. +We create a file named `formatter.ts/.js` within the `model` folder. This module contains the `statusText` function which takes a status code as input, retrieves the corresponding descriptive text from the resource bundle, and returns it. If no matching text is found in the resource bundle, or if the resource bundle can't be found, the function returns the original status code itself. ```ts +// webapp/model/formatter.ts import ResourceBundle from "sap/base/i18n/ResourceBundle"; import Controller from "sap/ui/core/mvc/Controller"; import ResourceModel from "sap/ui/model/resource/ResourceModel"; @@ -77,6 +68,7 @@ export default { ``` ```js +// webapp/model/formatter.js sap.ui.define([], function () { "use strict"; diff --git a/packages/walkthrough/steps/23/README.md b/packages/walkthrough/steps/23/README.md index 96f186500..d161b4250 100644 --- a/packages/walkthrough/steps/23/README.md +++ b/packages/walkthrough/steps/23/README.md @@ -8,7 +8,7 @@ In this step, we add a search field for our product list and define a filter tha ### Preview -![](assets/loio472ab6bf88674c23ba103efd97163133_LowRes.png "A search field is displayed above the list") +![A search field is displayed above the list](assets/loio472ab6bf88674c23ba103efd97163133_LowRes.png "A search field is displayed above the list") *A search field is displayed above the list* @@ -18,20 +18,10 @@ You can access the live preview by clicking on this link: [🔗 Live Preview of ### Coding -
- -You can download the solution for this step here: [📥 Download step 23](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-23.zip). - -
- -
- -You can download the solution for this step here: [📥 Download step 23](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-23-js.zip). - -
+You can download the solution for this step here: [📥 Download step 23](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-23.zip) (TS)[📥 Download step 23](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-23-js.zip) (JS). *** -### webapp/controller/InvoiceList.controller.?s +### webapp/controller/InvoiceList.controller.ts/.js We will implement a new `onFilterInvoices` event handler function to our controller. This function will enable users to filter the invoice list based on a search term entered in a `sap/m/SearchField` control. @@ -42,6 +32,7 @@ If the query is empty, we filter the binding with an empty array. This will make Finally we apply the filter to the items binding of the invoice list in our view, updating the displayed items. ```ts +// webapp/controller/InvoiceList.controller.ts import Controller from "sap/ui/core/mvc/Controller"; import JSONModel from "sap/ui/model/json/JSONModel"; import { SearchField$SearchEvent } from "sap/m/SearchField"; @@ -78,6 +69,7 @@ export default class App extends Controller { ``` ```js +// webapp/controller/InvoiceList.controller.js sap.ui.define(["sap/ui/core/mvc/Controller", "sap/ui/model/json/JSONModel", "sap/ui/model/Filter", "sap/ui/model/FilterOperator"], function (Controller, JSONModel, Filter, FilterOperator) { "use strict"; diff --git a/packages/walkthrough/steps/24/README.md b/packages/walkthrough/steps/24/README.md index 10b896498..ad0b37cb7 100644 --- a/packages/walkthrough/steps/24/README.md +++ b/packages/walkthrough/steps/24/README.md @@ -9,7 +9,7 @@ To make our list of invoices even more user-friendly, we sort it alphabetically ### Preview -![](assets/loio33f71b44bb644d1fa2a0ab14f1fcc02a_LowRes.png "The list is now sorted and grouped by the shipping company") +![The list is now sorted and grouped by the shipping company](assets/loio33f71b44bb644d1fa2a0ab14f1fcc02a_LowRes.png "The list is now sorted and grouped by the shipping company") *The list is now sorted and grouped by the shipping company* @@ -19,17 +19,7 @@ You can access the live preview by clicking on this link: [🔗 Live Preview of ### Coding -
- -You can download the solution for this step here: [📥 Download step 24](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-24.zip). - -
- -
- -You can download the solution for this step here: [📥 Download step 24](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-24-js.zip). - -
+You can download the solution for this step here: [📥 Download step 24](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-24.zip) (TS)[📥 Download step 24](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-24-js.zip) (JS). *** ### webapp/view/InvoiceList.view.xml diff --git a/packages/walkthrough/steps/25/README.md b/packages/walkthrough/steps/25/README.md index 48cc1ab59..2b85edbe5 100644 --- a/packages/walkthrough/steps/25/README.md +++ b/packages/walkthrough/steps/25/README.md @@ -12,7 +12,7 @@ In the real world, data often resides on remote servers and is accessed via an O ### Preview -![](assets/loio5b76bb4b15eb44e1862d0b6c1c802571_LowRes.png "Products from the OData invoices test service are now shown within our app") +![Products from the OData invoices test service are now shown within our app](assets/loio5b76bb4b15eb44e1862d0b6c1c802571_LowRes.png "Products from the OData invoices test service are now shown within our app") *Products from the OData invoices test service are now shown within our app* @@ -22,17 +22,7 @@ In the real world, data often resides on remote servers and is accessed via an O ### Coding -
- -You can download the solution for this step here: [📥 Download step 25](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-25.zip). - -
- -
- -You can download the solution for this step here: [📥 Download step 25](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-25-js.zip). - -
+You can download the solution for this step here: [📥 Download step 25](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-25.zip) (TS)[📥 Download step 25](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-25-js.zip) (JS). *** ### Development Environment diff --git a/packages/walkthrough/steps/26/README.md b/packages/walkthrough/steps/26/README.md index 3c3327a97..f13d4e2a6 100644 --- a/packages/walkthrough/steps/26/README.md +++ b/packages/walkthrough/steps/26/README.md @@ -11,7 +11,7 @@ This system is the so-called back-end system that we will now simulate with anOp ### Preview -![](assets/loiofe1403346ce9499f8bb102beaa4986d5_LowRes.png "The list of invoices is now served by the Mock Server") +![The list of invoices is now served by the Mock Server](assets/loiofe1403346ce9499f8bb102beaa4986d5_LowRes.png "The list of invoices is now served by the Mock Server") *The list of invoices is now served by the Mock Server* @@ -19,7 +19,7 @@ The folder structure of our app project is clearly separating test and productiv The new `localService` folder contains a `metadata.xml` service description file for OData, the `mockserver.js` file that simulates a real service with local data, and the `mockdata` subfolder that contains the local test data \(`Invoices.json`\). -![](assets/loio7a5e2b02d72d40d388f5e601d7de74df_LowRes.png "Folder Structure for this Step") +![Folder Structure for this Step](assets/loio7a5e2b02d72d40d388f5e601d7de74df_LowRes.png "Folder Structure for this Step") *Folder Structure for this Step* @@ -28,17 +28,7 @@ You can access the live preview by clicking on this link: [🔗 Live Preview of *** ### Coding -
- -You can download the solution for this step here: [📥 Download step 26](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-26.zip). - -
- -
- -You can download the solution for this step here: [📥 Download step 26](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-26-js.zip). - -
+You can download the solution for this step here: [📥 Download step 26](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-26.zip) (TS)[📥 Download step 26](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-26-js.zip) (JS). *** ### webapp/localService/metadata.xml \(New\) @@ -137,9 +127,9 @@ In folder `localService` we create the new folder `mockdata`. The mock server de This file will automatically be found and read by the mock server. *** -### webapp/localService/mockserver.?s \(New\) +### webapp/localService/mockserver.ts/.js \(New\) -Now we can write the code to initialize the mock server which will then simulate any OData request to the real Northwind server. For this we add a new file `mockserver.?s` to the `localService` folder. +Now we can write the code to initialize the mock server which will then simulate any OData request to the real Northwind server. For this we add a new file `mockserver.ts/.js` to the `localService` folder. We import the standard OpenUI5 `MockServer` module and create a helper object that defines an `init` method to start the server. The `init` method creates a `MockServer` instance with the same URL as the real service calls. The URL in the `rootUri` configuration parameter has to point to the same URL as defined in the `uri` property of the data source in the `manifest.json` descriptor file. In the `manifest.json`, OpenUI5 automatically interprets a relative URL as being relative to the application namespace. In the TypeScript code, you can ensure this by using the `sap.ui.require.toUrl` method. The `sap/ui/core/util/MockServer` then catches every request to the real service and returns a response. @@ -150,6 +140,7 @@ To simulate a service, we can simply call the `simulate` method on the `MockServ Finally, we call the `start` method on the `MockServer`. From this point, each request to the URL pattern `rootUri` will be processed by the `MockServer`. ```ts +// webapp/localService/mockserver.ts import MockServer from "sap/ui/core/util/MockServer"; export default { @@ -179,6 +170,7 @@ export default { ``` ```js +// webapp/localService/mockserver.js sap.ui.define(["sap/ui/core/util/MockServer"], function (MockServer) { "use strict"; @@ -208,13 +200,14 @@ sap.ui.define(["sap/ui/core/util/MockServer"], function (MockServer) { ``` -### webapp/test/initMockServer.?s \(New\) +### webapp/test/initMockServer.ts/.js \(New\) -As a next step, we create a module that initializes our local `mockserver`. For this, we add the new `test` folder to our App folder where we place the new `initmockServer.?s` file. +As a next step, we create a module that initializes our local `mockserver`. For this, we add the new `test` folder to our App folder where we place the new `initmockServer.ts/.js` file. First, we call the `init` method of our local `mockserver`, then we initialize the app component. ```ts +// webapp/test/initMockServer.ts import mockserver from "../localService/mockserver"; // initialize the mock server @@ -226,6 +219,7 @@ import("sap/ui/core/ComponentSupport"); ``` ```js +// webapp/test/initMockServer.js sap.ui.define(["../localService/mockserver"], function (mockserver) { "use strict"; diff --git a/packages/walkthrough/steps/27/README.md b/packages/walkthrough/steps/27/README.md index ba7e2e99e..66b00a53e 100644 --- a/packages/walkthrough/steps/27/README.md +++ b/packages/walkthrough/steps/27/README.md @@ -13,13 +13,13 @@ Actually, every feature that we added to the app so far, would require a separat ### Preview -![](assets/loio0d29491d96574cfe8d8158d60a0a32e2_LowRes.png "A unit test for our formatters is now available") +![A unit test for our formatters is now available](assets/loio0d29491d96574cfe8d8158d60a0a32e2_LowRes.png "A unit test for our formatters is now available") *A unit test for our formatters is now available* We add a new folder `unit` under the `test` folder and a `model` subfolder where we will place our formatter unit test. The folder structure matches the app structure to easily find the corresponding unit tests. -![](assets/loio1b5613ac3ab94757af2c7823039222a9_LowRes.png "Folder Structure for this Step") +![Folder Structure for this Step](assets/loio1b5613ac3ab94757af2c7823039222a9_LowRes.png "Folder Structure for this Step") *Folder Structure for this Step* You can access the live preview by clicking on this link: [🔗 Live Preview of Step 27](https://ui5.github.io/tutorials/walkthrough/build/27/test/Test.cdn.qunit.html?testsuite=test-resources/ui5/tutorial/walkthrough/testsuite.cdn.qunit&test=unit/unitTests). @@ -28,22 +28,12 @@ You can access the live preview by clicking on this link: [🔗 Live Preview of ### Coding -
- -You can download the solution for this step here: [📥 Download step 27](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-27.zip). - -
- -
- -You can download the solution for this step here: [📥 Download step 27](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-27-js.zip). - -
+You can download the solution for this step here: [📥 Download step 27](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-27.zip) (TS)[📥 Download step 27](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-27-js.zip) (JS). *** -### webapp/test/unit/model/formatter.?s \(New\) +### webapp/test/unit/model/formatter.ts/.js \(New\) -We create a new `formatter.?s` file under `webapp/test/unit/model` where the unit test for the custom formatter is implemented. The formatter function that we want to test is from the `formatter.ts` file located in the `webapp/model` folder. +We create a new `formatter.ts/.js` file under `webapp/test/unit/model` where the unit test for the custom formatter is implemented. The formatter function that we want to test is from the `formatter.ts` file located in the `webapp/model` folder. The new formatter file just contains one QUnit module for our formatter function and one unit test for the formatter function. In the implementation of the `statusText` function that we created in Step 23, we use the translated texts when calling the formatter. As we do not want to test the OpenUI5 binding functionality, we just use text in the test instead of a `ResourceBundle`. @@ -53,6 +43,7 @@ Finally, we perform our assertions. We check each branch of the formatter logic > Test code needs to import the modules under test (i.e. productive code) using their full namespace (in our case `ui5/tutorial/walkthrough/`), rather than using relative paths. This is because the test code uses a different namespace (`test-resources/ui5/tutorial/walkthrough/`). ```ts +// webapp/test/unit/model/formatter.ts import ResourceModel from "sap/ui/model/resource/ResourceModel"; import Controller from "sap/ui/core/mvc/Controller"; import formatter from "ui5/tutorial/walkthrough/model/formatter"; @@ -91,6 +82,7 @@ QUnit.test("Should return the translated texts", (assert) => { ``` ```js +// webapp/test/unit/model/formatter.js sap.ui.define(["sap/ui/model/resource/ResourceModel", "ui5/tutorial/walkthrough/model/formatter"], function (ResourceModel, formatter) { "use strict"; @@ -124,19 +116,21 @@ sap.ui.define(["sap/ui/model/resource/ResourceModel", "ui5/tutorial/walkthrough/ ``` -### webapp/test/unit/unitTests.qunit.?s \(New\) +### webapp/test/unit/unitTests.qunit.ts/.js \(New\) -We create a new `unitTests.qunit.?s` file under `webapp/test/unit/`. +We create a new `unitTests.qunit.ts/.js` file under `webapp/test/unit/`. This module will serve as the entry point for all our unit tests. It will be referenced in the test suite that we will set up later on. -Inside the `unitTests.qunit.?s` file, we import the unit test for the custom formatter. This ensures that any tests related to the custom formatter functionality will be included when running our unit tests. +Inside the `unitTests.qunit.ts/.js` file, we import the unit test for the custom formatter. This ensures that any tests related to the custom formatter functionality will be included when running our unit tests. ```ts +// webapp/test/unit/unitTests.qunit.ts import "./model/formatter"; ``` ```js +// webapp/test/unit/unitTests.qunit.js sap.ui.define(["./model/formatter"], function (___model_formatter) { "use strict"; }); @@ -184,9 +178,10 @@ The test suite serves as the entry point for all tests within our project such a The previously created generic `Test.qunit.html` file is referenced as the test `page` and configured with query parameters so that individual tests can be run. The placeholders `{suite}` and `{name}` are replaced with the suite and test names respectively. -For more information, read [Test Starter - Concept and Basic Setup](https://sdk.openui5.org/#/topic/22f50c0f0b104bf3ba84620880793d3f). +For more information, read [Test Starter - Concept and Basic Setup](https://sdk.openui5.org/topic/22f50c0f0b104bf3ba84620880793d3f). ```ts +// webapp/test/testsuite.qunit.ts import type {SuiteConfiguration} from "sap/ui/test/starter/config"; export default { name: "QUnit test suite for UI5 TypeScript Walkthrough", @@ -214,6 +209,7 @@ export default { ``` ```js +// webapp/test/testsuite.qunit.js sap.ui.define([], function () { "use strict"; diff --git a/packages/walkthrough/steps/28/README.md b/packages/walkthrough/steps/28/README.md index a9bd254a4..e6bb559a7 100644 --- a/packages/walkthrough/steps/28/README.md +++ b/packages/walkthrough/steps/28/README.md @@ -13,13 +13,13 @@ We haven’t thought about testing our interaction with the app yet, so in this ### Preview -![](assets/loio250d5b92921d44a4b432cc0fade88cc9_LowRes.png "An OPA test opens the "Hello" dialog from step 16") +![An OPA test opens the "Hello" dialog from step 16](assets/loio250d5b92921d44a4b432cc0fade88cc9_LowRes.png "An OPA test opens the "Hello" dialog from step 16") *An OPA test opens the "Hello" dialog from step 16* We add a new folder `integration` below the `test` folder, where we put our new test cases. Page objects that help structuring such integration tests are put in the `pages` subfolder that we also create now. -![](assets/loio27e84d5bd72a485498564b92894869b5_LowRes.png "Folder Structure for this Step") +![Folder Structure for this Step](assets/loio27e84d5bd72a485498564b92894869b5_LowRes.png "Folder Structure for this Step") *Folder Structure for this Step* You can access the live preview by clicking on this link: [🔗 Live Preview of Step 28](https://ui5.github.io/tutorials/walkthrough/build/28/test/Test.cdn.qunit.html?testsuite=test-resources/ui5/tutorial/walkthrough/testsuite.cdn.qunit&test=integration/opaTests). @@ -29,20 +29,10 @@ You can access the live preview by clicking on this link: [🔗 Live Preview of ### Coding -
- -You can download the solution for this step here: [📥 Download step 28](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-28.zip). - -
- -
- -You can download the solution for this step here: [📥 Download step 28](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-28-js.zip). - -
+You can download the solution for this step here: [📥 Download step 28](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-28.zip) (TS)[📥 Download step 28](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-28-js.zip) (JS). *** -### webapp/test/integration/pages/HelloPanelPage.?s \(New\) +### webapp/test/integration/pages/HelloPanelPage.ts/.js \(New\) We create a new `HelloPanelPage.ts` file under `webapp/test/integration/pages`. @@ -53,6 +43,7 @@ In the actions section of the page object we define a function to click the "Hel In the assertions section we define a `waitFor` statement that checks if a `sap.m.Dialog` control is existing in the DOM of the app. When the dialog has been found, the test is successful and we can immediately confirm by calling an `ok` statement with a meaningful message. ```ts +// webapp/test/integration/pages/HelloPanelPage.ts import Opa5 from "sap/ui/test/Opa5"; import Press from "sap/ui/test/actions/Press"; @@ -85,6 +76,7 @@ export default class HelloPanelPage extends Opa5 { ``` ```js +// webapp/test/integration/pages/HelloPanelPage.js sap.ui.define(["sap/ui/test/Opa5", "sap/ui/test/actions/Press"], function (Opa5, Press) { "use strict"; @@ -118,7 +110,7 @@ sap.ui.define(["sap/ui/test/Opa5", "sap/ui/test/actions/Press"], function (Opa5, ``` -### webapp/test/integration/NavigationJourney.?s \(New\) +### webapp/test/integration/NavigationJourney.ts/.js \(New\) We create a new `NavigationJourney` file under `webapp/test/integration/`. @@ -138,6 +130,7 @@ The function `opaTest` is the main aspect for defining integration tests with OP In our journey, we create a very simple test that starts the `MainPage` and load our app. Then, we carry out the the actions we defined in our `MainPage` and expect that they will be executed successfully. Finally, we shut down the page again by calling function `iTeardownMyApp` on the `MainPage`. ```ts +// webapp/test/integration/NavigationJourney.ts import opaTest from "sap/ui/test/opaQunit"; import HelloPanelPage from "./pages/HelloPanelPage"; @@ -167,6 +160,7 @@ opaTest("Should open the Hello dialog", function () { ``` ```js +// webapp/test/integration/NavigationJourney.js sap.ui.define(["sap/ui/test/opaQunit", "./pages/HelloPanelPage"], function (opaTest, HelloPanelPage) { "use strict"; @@ -195,28 +189,31 @@ sap.ui.define(["sap/ui/test/opaQunit", "./pages/HelloPanelPage"], function (opaT   As you can see, the test case reads like a user story, we actually do not need the implementation of the methods yet to understand the meaning of the test case. This approach is called "Behavior Driven Development" or simply BDD and is popular in "Agile Software Development". -### webapp/test/integration/opaTests.qunit.?s \(New\) +### webapp/test/integration/opaTests.qunit.ts/.js \(New\) -We create a new `opaTests.qunit.?s` file under `webapp/test/integration/`. +We create a new `opaTests.qunit.ts/.js` file under `webapp/test/integration/`. This module imports our `NavigationJourney` and is the entrypoint for all integration tests in the project. ```ts +// webapp/test/integration/opaTests.qunit.ts import "./NavigationJourney"; ``` ```js +// webapp/test/integration/opaTests.qunit.js sap.ui.define(["./NavigationJourney"], function (___NavigationJourney) { "use strict"; }); ``` -### webapp/test/testsuite.qunit.?s +### webapp/test/testsuite.qunit.ts/.js -Finally we reference the new `integration/opaTests.qunit.?s` in the `testsuite.qunit.?s` file. The `.qunit.?s` extension is omitted and will be added automatically during runtime. +Finally we reference the new `integration/opaTests.qunit.ts/.js` in the `testsuite.qunit.ts/.js` file. The `.qunit.ts/.js` extension is omitted and will be added automatically during runtime. ```ts +// webapp/test/testsuite.qunit.ts import type {SuiteConfiguration} from "sap/ui/test/starter/config"; export default { // ... @@ -233,6 +230,7 @@ export default { ``` ```js +// webapp/test/testsuite.qunit.js sap.ui.define([], function () { "use strict"; //... diff --git a/packages/walkthrough/steps/29/README.md b/packages/walkthrough/steps/29/README.md index 8e847c68f..04c0e36fe 100644 --- a/packages/walkthrough/steps/29/README.md +++ b/packages/walkthrough/steps/29/README.md @@ -11,7 +11,7 @@ Luckily, OpenUI5 provides a couple of debugging tools that we can use within the ### Preview -![](assets/loio930de31b311f43ffa9df9261ca760da0_LowRes.png "The diagnostics window") +![The diagnostics window](assets/loio930de31b311f43ffa9df9261ca760da0_LowRes.png "The diagnostics window") *The diagnostics window* @@ -21,17 +21,7 @@ Luckily, OpenUI5 provides a couple of debugging tools that we can use within the ### Coding -
- -You can download the solution for this step here: [📥 Download step 29](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-29.zip). - -
- -
- -You can download the solution for this step here: [📥 Download step 29](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-29-js.zip). - -
+You can download the solution for this step here: [📥 Download step 29](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-29.zip) (TS)[📥 Download step 29](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-29-js.zip) (JS). *** @@ -112,7 +102,7 @@ Sometimes errors are not as easy to spot and you actually need to debug the Java > This makes debugging harder, because the code is a lot less readable. You can load the debug sources by adding the URL parameter `sap-ui-debug=true` or by pressing [Ctrl\] + [Shift\] + [Alt\] /[Option\] + [P\] and selecting *Use Debug Sources* in the dialog box that is displayed. After reloading the page, you can see in the *Network* tab of the browser’s developer tools that now a lot of files are loaded that have a `–dbg` suffix. These are the source code files that include comments and the uncompressed code of the app and the OpenUI5 artifacts. -![](assets/loio34c4b02c74eb4848b8b720d86042bfdc_LowRes.png "Technical information dialog ") +![Technical information dialog ](assets/loio34c4b02c74eb4848b8b720d86042bfdc_LowRes.png "Technical information dialog ") *Technical information dialog* diff --git a/packages/walkthrough/steps/30/README.md b/packages/walkthrough/steps/30/README.md index 86777b093..6859f780b 100644 --- a/packages/walkthrough/steps/30/README.md +++ b/packages/walkthrough/steps/30/README.md @@ -11,7 +11,7 @@ In this step, we will use the OpenUI5 navigation features to load and show a sep ### Preview -![](assets/loio94152a595fe24d45b12223e0abcccb9c_LowRes.png "A second page is added to display the invoice") +![A second page is added to display the invoice](assets/loio94152a595fe24d45b12223e0abcccb9c_LowRes.png "A second page is added to display the invoice") *A second page is added to display the invoice* @@ -22,17 +22,7 @@ You can access the live preview by clicking on this link: [🔗 Live Preview of ### Coding -
- -You can download the solution for this step here: [📥 Download step 30](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-30.zip). - -
- -
- -You can download the solution for this step here: [📥 Download step 30](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-30-js.zip). - -
+You can download the solution for this step here: [📥 Download step 30](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-30.zip) (TS)[📥 Download step 30](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-30-js.zip) (JS). *** ### webapp/i18n/i18n.properties @@ -181,11 +171,12 @@ The overview view is always shown when the hash is empty. The detail view is sho > :info: > The sequence of the routes in the routes definition is important. As soon as a pattern is matched, the following patterns are ignored. To prevent this for a specific route, you use the `greedy` parameter. If set to `true`, the route is always taken into account. -### webapp/Component.?s +### webapp/Component.ts/.js In the component initialization method, we now add a call to initialize the router. ```ts +// webapp/Component.ts import UIComponent from "sap/ui/core/UIComponent"; import JSONModel from "sap/ui/model/json/JSONModel"; @@ -218,6 +209,7 @@ export default class Component extends UIComponent { ``` ```js +// webapp/Component.js sap.ui.define(["sap/ui/core/UIComponent", "sap/ui/model/json/JSONModel"], function (UIComponent, JSONModel) { "use strict"; @@ -255,11 +247,12 @@ Initializing the router will evaluate the current URL and load the corresponding *** -### webapp/controller/InvoiceList.controller.?s +### webapp/controller/InvoiceList.controller.ts/.js What is still missing is the event handler that performs a navigation to the detail page by clicking an item in the invoice list: To access the router instance for our app use the static method `getRouterFor()` on the `UIComponent` module. On the router we call the `navTo` method passing the pattern name we defined in our app descriptor for routing to the details page. ```ts +// webapp/controller/InvoiceList.controller.ts import Controller from "sap/ui/core/mvc/Controller"; import JSONModel from "sap/ui/model/json/JSONModel"; import { SearchField$SearchEvent } from "sap/m/SearchField"; @@ -283,6 +276,7 @@ export default class App extends Controller { ``` ```js +// webapp/controller/InvoiceList.controller.js sap.ui.define(["sap/ui/core/mvc/Controller", "sap/ui/model/json/JSONModel", "sap/ui/model/Filter", "sap/ui/model/FilterOperator", "sap/ui/core/UIComponent"], function (Controller, JSONModel, Filter, FilterOperator, UIComponent) { "use strict"; diff --git a/packages/walkthrough/steps/31/README.md b/packages/walkthrough/steps/31/README.md index 5602523a5..071ca3093 100644 --- a/packages/walkthrough/steps/31/README.md +++ b/packages/walkthrough/steps/31/README.md @@ -11,7 +11,7 @@ To make this work, we have to pass over the information which item has been sele ### Preview -![](assets/loio31da9d48ae204c36a991146b90648c21_LowRes.png "The selected invoice details are now shown in the details page") +![The selected invoice details are now shown in the details page](assets/loio31da9d48ae204c36a991146b90648c21_LowRes.png "The selected invoice details are now shown in the details page") *The selected invoice details are now shown in the details page* @@ -21,17 +21,7 @@ You can access the live preview by clicking on this link: [🔗 Live Preview of ### Coding -
- -You can download the solution for this step here: [📥 Download step 31](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-31.zip). - -
- -
- -You can download the solution for this step here: [📥 Download step 31](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-31-js.zip). - -
+You can download the solution for this step here: [📥 Download step 31](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-31.zip) (TS)[📥 Download step 31](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-31-js.zip) (JS). *** @@ -83,7 +73,7 @@ We want to hand over the information for the selected item when navigating to th } ``` -### webapp/controller/InvoiceList.controller.?s +### webapp/controller/InvoiceList.controller.ts/.js In the controller for the invoice list view, we extend the `onPress` event handler in such a way, it not only triggers the navigation to the detail view but also passes the selected item to the routing. @@ -95,6 +85,7 @@ Typically we woudl use the key of the item in the back-end system to identify th ```ts +// webapp/controller/InvoiceList.controller.ts import Controller from "sap/ui/core/mvc/Controller"; import JSONModel from "sap/ui/model/json/JSONModel"; import { SearchField$SearchEvent } from "sap/m/SearchField"; @@ -125,6 +116,7 @@ export default class App extends Controller { ``` ```js +// webapp/controller/InvoiceList.controller.js sap.ui.define(["sap/ui/core/mvc/Controller", "sap/ui/model/json/JSONModel", "sap/ui/model/Filter", "sap/ui/model/FilterOperator", "sap/ui/core/UIComponent"], function (Controller, JSONModel, Filter, FilterOperator, UIComponent) { "use strict"; @@ -146,7 +138,7 @@ sap.ui.define(["sap/ui/core/mvc/Controller", "sap/ui/model/json/JSONModel", "sap ``` -### webapp/controller/Detail.controller.?s \(New\) +### webapp/controller/Detail.controller.ts/.js \(New\) Now we need to create a new detail controller to set the content we passed in with the URL parameter `invoicePath` on the detail view. This will allow us to access the data of the selected item and display them on the view. @@ -160,6 +152,7 @@ The `bindElement` function is creating a binding context for a OpenUI5 control a ```ts +// webapp/controller/Detail.controller.ts import Controller from "sap/ui/core/mvc/Controller"; import UIComponent from "sap/ui/core/UIComponent"; import Route, { Route$PatternMatchedEvent } from "sap/ui/core/routing/Route"; @@ -185,6 +178,7 @@ export default class Detail extends Controller { ``` ```js +// webapp/controller/Detail.controller.js sap.ui.define(["sap/ui/core/mvc/Controller", "sap/ui/core/UIComponent"], function (Controller, UIComponent) { "use strict"; diff --git a/packages/walkthrough/steps/32/README.md b/packages/walkthrough/steps/32/README.md index 8574b0346..5f9624eb9 100644 --- a/packages/walkthrough/steps/32/README.md +++ b/packages/walkthrough/steps/32/README.md @@ -9,7 +9,7 @@ Now we can navigate to our detail page and display an invoice, but we cannot go ### Preview -![](assets/loio33a8341077bb458685274c64d2317f6b_LowRes.png "A back button is now displayed on the detail page") +![A back button is now displayed on the detail page](assets/loio33a8341077bb458685274c64d2317f6b_LowRes.png "A back button is now displayed on the detail page") *A back button is now displayed on the detail page* @@ -19,21 +19,11 @@ You can access the live preview by clicking on this link: [🔗 Live Preview of ### Coding -
- -You can download the solution for this step here: [📥 Download step 32](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-32.zip). - -
- -
- -You can download the solution for this step here: [📥 Download step 32](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-32-js.zip). - -
+You can download the solution for this step here: [📥 Download step 32](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-32.zip) (TS)[📥 Download step 32](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-32-js.zip) (JS). *** -### webapp/controller/Detail.controller.?s +### webapp/controller/Detail.controller.ts/.js To be able to navigate from the detail view back to the view we came from we implement a new event handler function in the controller of the detail view. For a start we load a new class called `History` from the `sap.ui.core.routing` namespace. This class provides methods for navigating through the history of the application. @@ -43,6 +33,7 @@ In the event handler we access the navigation history and try to determine the p If no navigation has happened before, we get a reference to the router and use the `navTo` method to navigate to the "overview" route. As a second parameter we specify an empty array \(`{}`\) as we do not pass any additional parameters to the route. The third parameter is set to `true`. This tells the router to replace the current history state with the new one since we actually do a back navigation by ourselves and we do not want to have an entry in the browser history. ```ts +// webapp/controller/Detail.controller.ts import Controller from "sap/ui/core/mvc/Controller"; import { Route$PatternMatchedEvent } from "sap/ui/core/routing/Route"; import History from "sap/ui/core/routing/History"; @@ -71,6 +62,7 @@ export default class Detail extends Controller { ``` ```js +// webapp/controller/Detail.controller.js sap.ui.define(["sap/ui/core/mvc/Controller", "sap/ui/core/routing/History", "sap/ui/core/UIComponent"], function (Controller, History, UIComponent) { "use strict"; diff --git a/packages/walkthrough/steps/33/README.md b/packages/walkthrough/steps/33/README.md index 3b9a47f58..dede2ccd8 100644 --- a/packages/walkthrough/steps/33/README.md +++ b/packages/walkthrough/steps/33/README.md @@ -8,7 +8,7 @@ In this step, we are going to extend the functionality of OpenUI5 with a custom ### Preview -![](assets/loio21dd14c37b67473b817c8865f168f668_LowRes.png "A custom product rating control is added to the detail page") +![A custom product rating control is added to the detail page](assets/loio21dd14c37b67473b817c8865f168f668_LowRes.png "A custom product rating control is added to the detail page") *A custom product rating control is added to the detail page* @@ -17,17 +17,7 @@ You can access the live preview by clicking on this link: [🔗 Live Preview of *** ### Coding -
- -You can download the solution for this step here: [📥 Download step 33](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-33.zip). - -
- -
- -You can download the solution for this step here: [📥 Download step 33](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-33-js.zip). - -
+You can download the solution for this step here: [📥 Download step 33](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-33.zip) (TS)[📥 Download step 33](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-33-js.zip) (JS). *** ### webapp/i18n/i18n.properties @@ -73,7 +63,7 @@ html[dir="rtl"] .myAppDemoWT .myCustomButton.sapMBtn { We could also do this with more HTML in the renderer but this is the simplest way and it will only be applied inside our custom control. However, please be aware that the custom control is in your app and might have to be adjusted when the inner controls change in future versions of OpenUI5. -### webapp/control/ProductRating.?s \(New\) +### webapp/control/ProductRating.ts/.js \(New\) Custom controls are small reuse components that can be created within an application very easily. Due to their nature, they are sometimes also referred to as "notepad” or “on the fly” controls. A custom control is an object that has two special sections \(`metadata` and `renderer`\) and various methods that determine the control's functionality. @@ -95,6 +85,7 @@ The static `renderer` property expects an object that defines how the control is > When developing a custom control, it is crucial to specify the appropriate apiVersion for the control's renderer. This ensures that your control can leverage the latest rendering features and improvements available in the RenderManager. ```ts +// webapp/control/ProductRating.ts import Control from "sap/ui/core/Control"; import RenderManager from "sap/ui/core/RenderManager"; @@ -120,6 +111,7 @@ export default class ProductRating extends Control { ``` ```js +// webapp/control/ProductRating.js sap.ui.define(["sap/ui/core/Control"], function (Control) { "use strict"; @@ -181,6 +173,7 @@ Next, we have the `press` handler for the rating button that submits our rating. We define the `reset` method to be able to revert the state of the control on the UI to its initial state so that the user can again submit a rating. ```ts +// webapp/control/ProductRating.ts import Control from "sap/ui/core/Control"; import RenderManager from "sap/ui/core/RenderManager"; import { MetadataOptions } from "sap/ui/core/Element"; @@ -305,6 +298,7 @@ export default class ProductRating extends Control { ``` ```js +// webapp/control/ProductRating.js sap.ui.define(["sap/ui/core/Control", "sap/m/Label", "sap/m/Button", "sap/m/RatingIndicator"], function (Control, Label, Button, RatingIndicator) { "use strict"; @@ -429,7 +423,7 @@ You can now stop the interface generator again, as no further control API change
-### webapp/controller/Detail.controller.?s +### webapp/controller/Detail.controller.ts/.js In the `Detail` controller we implement a new `onRatingChange` event that reads the value of our coustom change event that is fired when a rating has been submitted. This requires to import our new control, as well as the `ProductRating$ChangeEvent` type we just defined to the detail controller. To keep the sample simple we only display a message message instead of sending the rating to the backend. We therefore load the `MessageToast` module from the `sap.m` namespace to our script. In addition we need the `ResourceBundle` module from the `sap/base/i18n` namespace as well as the `ResourceModel` module from the `sap/ui/model/resource` namespace as we want to display the confirmation message we specified in our resource bundle in the message toast. @@ -438,6 +432,7 @@ In the `onRatingChange` the event handler we extract the value of our custom cha In the `onObjectMatched` method, we call the `reset` method to make it possible to submit another rating as soon as the detail view is displayed for a different item. ```ts +// webapp/controller/Detail.controller.ts import Controller from "sap/ui/core/mvc/Controller"; import Route, { Route$PatternMatchedEvent } from "sap/ui/core/routing/Route"; import History from "sap/ui/core/routing/History"; @@ -489,6 +484,7 @@ export default class Detail extends Controller { ``` ```js +// webapp/controller/Detail.controller.js sap.ui.define(["sap/ui/core/mvc/Controller", "sap/ui/core/routing/History", "sap/m/MessageToast", "sap/ui/core/UIComponent"], function (Controller, History, MessageToast, UIComponent) { "use strict"; @@ -574,6 +570,7 @@ constructor(id?: string, settings?: $ProductRatingSettings) { super(id, settings To complete the setup of the generated interface, we follow the instructions and add the block between the BEGIN and END line into the `ProductRating` class body in the file `webapp/control/ProductRating.ts`. ```ts +// webapp/control/ProductRating.ts import Control from "sap/ui/core/Control"; import RenderManager from "sap/ui/core/RenderManager"; import { MetadataOptions } from "sap/ui/core/Element"; diff --git a/packages/walkthrough/steps/34/README.md b/packages/walkthrough/steps/34/README.md index b836aaf2b..44c0d2525 100644 --- a/packages/walkthrough/steps/34/README.md +++ b/packages/walkthrough/steps/34/README.md @@ -9,7 +9,7 @@ In this step, we improve the responsiveness of our app. OpenUI5 applications can ### Preview -![](assets/loiocc3f2e0d8ac6471288af6495836c2f07_LowRes.png "A responsive table is hiding some of the columns on small devices") +![A responsive table is hiding some of the columns on small devices](assets/loiocc3f2e0d8ac6471288af6495836c2f07_LowRes.png "A responsive table is hiding some of the columns on small devices") *A responsive table is hiding some of the columns on small devices* @@ -19,17 +19,7 @@ You can access the live preview by clicking on this link: [🔗 Live Preview of *** ### Coding -
- -You can download the solution for this step here: [📥 Download step 34](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-34.zip). - -
- -
- -You can download the solution for this step here: [📥 Download step 34](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-34-js.zip). - -
+You can download the solution for this step here: [📥 Download step 34](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-34.zip) (TS)[📥 Download step 34](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-34-js.zip) (JS). *** ### webapp/i18n/i18n.properties diff --git a/packages/walkthrough/steps/35/README.md b/packages/walkthrough/steps/35/README.md index c9554a845..f8b0539fc 100644 --- a/packages/walkthrough/steps/35/README.md +++ b/packages/walkthrough/steps/35/README.md @@ -8,7 +8,7 @@ We now configure the visibility and properties of controls based on the device t ### Preview -![](assets/loio0b0d57e04e574d7fbc4b10395e6cb260_LowRes.png "On phone devices, the panel is collapsed to save screen space and a button is hidden") +![On phone devices, the panel is collapsed to save screen space and a button is hidden](assets/loio0b0d57e04e574d7fbc4b10395e6cb260_LowRes.png "On phone devices, the panel is collapsed to save screen space and a button is hidden") *On phone devices, the panel is collapsed to save screen space and a button is hidden* @@ -17,20 +17,10 @@ You can access the live preview by clicking on this link: [🔗 Live Preview of *** ### Coding -
- -You can download the solution for this step here: [📥 Download step 35](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-35.zip). - -
- -
- -You can download the solution for this step here: [📥 Download step 35](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-35-js.zip). - -
+You can download the solution for this step here: [📥 Download step 35](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-35.zip) (TS)[📥 Download step 35](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-35-js.zip) (JS). *** -### webapp/Component.?s +### webapp/Component.ts/.js In the `app` component we import the `Device` module from the `sap.ui` namespace and initialize the device model in the `init` method. We can simply pass the loaded dependency `Device` to the constructor function of the JSONModel. This will make most properties of the OpenUI5 device API available as a JSON model. The model is then set on the component as a named model so that we can reference it in data binding. @@ -38,6 +28,7 @@ In the `app` component we import the `Device` module from the `sap.ui` namespace > We have to set the binding mode to `OneWay` as the device model is read-only and we want to avoid changing the model accidentally when we bind properties of a control to it. By default, models in OpenUI5 are bidirectional \(`TwoWay`\). When the property changes, the bound model value is updated as well. ```ts +// webapp/Component.ts import UIComponent from "sap/ui/core/UIComponent"; import JSONModel from "sap/ui/model/json/JSONModel"; import Device from "sap/ui/Device"; @@ -76,6 +67,7 @@ export default class Component extends UIComponent { ``` ```js +// webapp/Component.js sap.ui.define(["sap/ui/core/UIComponent", "sap/ui/model/json/JSONModel", "sap/ui/Device"], function (UIComponent, JSONModel, Device) { "use strict"; @@ -157,11 +149,12 @@ The device API of OpenUI5 offers more functionality to detect various device-spe > :info: > The `sap.ui.Device` API detects the device type \(Phone, Tablet, Desktop\) based on the user agent and many other properties of the device. Therefore simply reducing the screen size will not change the device type. To test this feature, you will have to enable device emulation in your browser or open it on a real device. -### webapp/controller/Detail.controller.?s +### webapp/controller/Detail.controller.ts/.js In the `Detail` controller we simply add the view model with our currency definition to display the number properly. It is the same code as in the `InvoiceList` controller file. ```ts +// webapp/controller/Detail.controller.ts import Controller from "sap/ui/core/mvc/Controller"; import Route, { Route$PatternMatchedEvent } from "sap/ui/core/routing/Route"; import History from "sap/ui/core/routing/History"; @@ -192,6 +185,7 @@ export default class Detail extends Controller { ``` ```js +// webapp/controller/Detail.controller.js sap.ui.define(["sap/ui/core/mvc/Controller", "sap/ui/core/routing/History", "sap/m/MessageToast", "sap/ui/model/json/JSONModel", "sap/ui/core/UIComponent"], function (Controller, History, MessageToast, JSONModel, UIComponent) { "use strict"; diff --git a/packages/walkthrough/steps/36/README.md b/packages/walkthrough/steps/36/README.md index 3d699afda..c4075a976 100644 --- a/packages/walkthrough/steps/36/README.md +++ b/packages/walkthrough/steps/36/README.md @@ -8,7 +8,7 @@ In this step of our Walkthrough tutorial, we adjust the content density based on ### Preview -![](assets/loiof216b131c492448d8a1df25db2b9a26d_LowRes.png "The content density is compact on desktop devices and cozy on touch-enabled devices") +![The content density is compact on desktop devices and cozy on touch-enabled devices](assets/loiof216b131c492448d8a1df25db2b9a26d_LowRes.png "The content density is compact on desktop devices and cozy on touch-enabled devices") *The content density is compact on devices without a touch screen and cozy on touch-enabled devices* @@ -17,26 +17,17 @@ You can access the live preview by clicking on this link: [🔗 Live Preview of *** ### Coding -
- -You can download the solution for this step here: [📥 Download step 36](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-36.zip). - -
- -
- -You can download the solution for this step here: [📥 Download step 36](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-36-js.zip). - -
+You can download the solution for this step here: [📥 Download step 36](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-36.zip) (TS)[📥 Download step 36](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-36-js.zip) (JS). *** -### webapp/Component.?s +### webapp/Component.ts/.js To prepare the content density feature we add a helper method `getContentDensityClass` to the app component. OpenUI5 controls can be displayed in multiple sizes, for example in a `compact` size that is optimized for desktop and non-touch devices, and in a `cozy` mode that is optimized for touch interaction. The controls look for a specific CSS class in the HTML structure of the application to adjust their size. This helper method queries the `Device` API directly for touch support of the client and returns the CSS class `sapUiSizeCompact` if touch interaction is not supported and `sapUiSizeCozy` for all other cases. We will use it throughout the application coding to set the proper content density CSS class. ```ts +// webapp/Component.ts import UIComponent from "sap/ui/core/UIComponent"; import JSONModel from "sap/ui/model/json/JSONModel"; import Device from "sap/ui/Device"; @@ -62,6 +53,7 @@ export default class Component extends UIComponent { ``` ```js +// webapp/Component.js sap.ui.define(["sap/ui/core/UIComponent", "sap/ui/model/json/JSONModel", "sap/ui/Device"], function (UIComponent, JSONModel, Device) { "use strict"; @@ -84,11 +76,12 @@ sap.ui.define(["sap/ui/core/UIComponent", "sap/ui/model/json/JSONModel", "sap/ui ``` -### webapp/controller/App.controller.?s +### webapp/controller/App.controller.ts/.js We add the `onInit` method to the app controller that is called when the app view is instantiated. There, we query the helper function that we defined on the app component in order to set the corresponding style class on the app view. All controls inside the app view will now automatically adjust to either the compact or the cozy size, as defined by the style. ```ts +// webapp/controller/App.controller.ts import Controller from "sap/ui/core/mvc/Controller"; import Component from "../Component"; @@ -104,6 +97,7 @@ export default class App extends Controller { ``` ```js +// webapp/controller/App.controller.js sap.ui.define(["sap/ui/core/mvc/Controller"], function (Controller) { "use strict"; diff --git a/packages/walkthrough/steps/37/README.md b/packages/walkthrough/steps/37/README.md index 74c3df495..2ece95fd6 100644 --- a/packages/walkthrough/steps/37/README.md +++ b/packages/walkthrough/steps/37/README.md @@ -15,7 +15,7 @@ One part of the ARIA attribute set are the so-called landmarks. You can compare ### Preview -![](assets/loiob35deda1ebe1433fbf0ff066f6e3fc4b_LowRes.png "Landmarks in our app") +![Landmarks in our app](assets/loiob35deda1ebe1433fbf0ff066f6e3fc4b_LowRes.png "Landmarks in our app") *Landmarks in our app* @@ -25,17 +25,7 @@ You can access the live preview by clicking on this link: [🔗 Live Preview of ### Coding -
- -You can download the solution for this step here: [📥 Download step 37](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-37.zip). - -
- -
- -You can download the solution for this step here: [📥 Download step 37](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-37-js.zip). - -
+You can download the solution for this step here: [📥 Download step 37](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-37.zip) (TS)[📥 Download step 37](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-37-js.zip) (JS). *** ### webapp/i18n/i18n.properties diff --git a/packages/walkthrough/steps/38/README.md b/packages/walkthrough/steps/38/README.md index 2c6437d38..5541ff1e2 100644 --- a/packages/walkthrough/steps/38/README.md +++ b/packages/walkthrough/steps/38/README.md @@ -9,24 +9,14 @@ In this step we're going to build our application and consume the speed of a bui ### Preview -![](assets/loiofb12cea5ac9b45bb9007aac5a1a8689f_LowRes.png "The UI5 application is built and served") +![The UI5 application is built and served](assets/loiofb12cea5ac9b45bb9007aac5a1a8689f_LowRes.png "The UI5 application is built and served") *The OpenUI5 application is built and served* *** ### Coding -
- -You can download the solution for this step here: [📥 Download step 38](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-38.zip). - -
- -
- -You can download the solution for this step here: [📥 Download step 38](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-38-js.zip). - -
+You can download the solution for this step here: [📥 Download step 38](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-38.zip) (TS)[📥 Download step 38](https://ui5.github.io/tutorials/walkthrough/walkthrough-step-38-js.zip) (JS). *** ### package.json diff --git a/tools/builder/prepare-gh-pages.js b/tools/builder/prepare-gh-pages.js index 7ec90cf11..e0b467ba3 100644 --- a/tools/builder/prepare-gh-pages.js +++ b/tools/builder/prepare-gh-pages.js @@ -135,6 +135,10 @@ function removeTSfromUI5YAML(ui5yaml) { rewriteLinks(join(distTutorialDir, "index.md")); } + if (existsSync(join(tutorialDir, "steps.json"))) { + copyFileSync(join(tutorialDir, "steps.json"), join(distTutorialDir, "steps.json")); + } + console.log(` 👉 Zipping TypeScript sources...`); await Promise.all(steps.map((step) => { return zipDirectory(