diff --git a/README.md b/README.md
index bfe12e9f..e5205aef 100644
--- a/README.md
+++ b/README.md
@@ -2,64 +2,11 @@
# SAP CodeJam - UI5
-This repository contains the material for SAP CodeJam events on UI5.
+This branch of the repository contains [instructions](/instructions.md) on how to improve the performance of the bookshop application that can be found in the [main branch](https://github.com/SAP-samples/ui5-exercises-codejam/tree/main).
-Please check the [prerequisites](/prerequisites.md) before the event an make sure you meet them.
+Furthermore, this branch was used for the corresponding session at Devtoberfest 2022: [Improving the Performance of UI5 Applications](https://groups.community.sap.com/t5/devtoberfest/improving-the-performance-of-ui5-applications/ec-p/9012#M42)
-## Overview
-
-The material in this repository introduces you to the core principles of UI5, an enterprise-ready web development framework used to build apps that follow the Fiori design guidelines. This repository is a step-by-step guide explaining how to build a frontend web application using UI5. The finished app is a bookshop app, where users can browse and order books. The app sits on top of the well-known [bookshop](https://github.com/SAP-samples/cloud-cap-samples/tree/main/bookshop) backend application built with the Node.js flavour of the [SAP Cloud Application Programming Model (CAP)](https://cap.cloud.sap/docs/).
-
-
-
-The finished UI5 bookshop app already exists in the [bookshop/app/finished-webapp](/bookshop/app/finished-webapp/) directory, but we want to rebuild it from scratch step by step. You can compare the finished app with your version in case you have issues along the way.
-
-After reading all chapters and following the instructions, you will be able to build your own UI5 applications leveraging the official [SAPUI5 API Reference](https://sapui5.hana.ondemand.com/#/api).
-
-## Previous Knowledge
-
-The material in this repository aims to be beginner friendly. If you have never built a (UI5) web app before, you will still be able to follow along. No prior knowledge is required, although it certainly helps to have experience in (web) development.
-
-The material includes additional explanations in collapsable sections (see example below), whenever a concept is used that web developers are probably already familiar with, but beginners might not be. You can decide for yourself whether you want to read or skip them.
-
-See this example:
-
-
-What is SAPUI5? 💬
-
-
-
-> SAPUI5 is an HTML5 framework for creating cross-platform, enterprise-grade web applications in an efficient way.
->
-> See this [blog post](https://blogs.sap.com/2021/08/23/what-is-sapui5/) for more information.
-
-
-
-## Material Organization
-
-The material consists of a series of chapters. The chapters build on top of each other and are meant to be completed in the given order. Each of the [chapters](/chapters/) has its own 'readme' file with explanations, instructions, code samples and screen shots.
-
-## Chapters
-
-0. [Preparing the Development Environment](/chapters/chapter00)
-1. [Scaffolding the App - Our First View](/chapters/chapter01)
-1. [Creating and Consuming Our First Model](/chapters/chapter02)
-1. [Creating and Extending Our First Controller](/chapters/chapter03)
-1. [Adding an 'Order' Feature to Our Bookshop](/chapters/chapter04)
-1. [Adding a 'Search' Feature to Our Bookshop](/chapters/chapter05)
-1. [Adding Custom Formatting](/chapters/chapter06)
-1. [Adding i18n Features](/chapters/chapter07)
-1. [Adding Custom CSS](/chapters/chapter08)
-1. [Deploying Our App](/chapters/chapter09) (Optional)
-1. [Further Improvements and Learning Material](/chapters/chapter10)
-
-## SAPUI5 vs. OpenUI5
-
-You will often read about either SAPUI5 or OpenUI5 when working with the framework. The main difference between the two is the license. Whereas SAPUI5 requires a license and is integrated into a lot of SAP products, OpenUI5 is open source and generally available under an Apache 2.0 license. SAPUI5 includes more libraries than OpenUI5, but the latter still contains all central functionality and most commonly used control libraries are identical in both deliveries.
-
-The material in this repository would work with both deliveries, but uses OpenUI5. For the sake of simplicity and to indicate that the material would work with SAPUI5, too, the material simply refers to the framework as 'UI5'.
-
-You can find more information about this in the [SAPUI5 Documentation](https://sapui5.hana.ondemand.com/#/topic/5982a9734748474aa8d4af9c3d8f31c0).
+
## Support
diff --git a/bookshop/app/performance-webapp/package.json b/bookshop/app/performance-webapp/package.json
new file mode 100644
index 00000000..8d849872
--- /dev/null
+++ b/bookshop/app/performance-webapp/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "performance-webapp",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "start:dist": "serve dist"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "devDependencies": {
+ "@ui5/cli": "^2.14.13",
+ "serve": "^14.0.1"
+ }
+}
diff --git a/bookshop/app/performance-webapp/ui5.yaml b/bookshop/app/performance-webapp/ui5.yaml
new file mode 100644
index 00000000..100e5797
--- /dev/null
+++ b/bookshop/app/performance-webapp/ui5.yaml
@@ -0,0 +1,11 @@
+specVersion: '2.6'
+metadata:
+ name: performance-webapp
+type: application
+framework:
+ name: OpenUI5
+ version: "1.107.1"
+ libraries:
+ - name: sap.m
+ - name: sap.ui.core
+ - name: themelib_sap_horizon
diff --git a/bookshop/app/performance-webapp/webapp/Component.js b/bookshop/app/performance-webapp/webapp/Component.js
new file mode 100644
index 00000000..466523dc
--- /dev/null
+++ b/bookshop/app/performance-webapp/webapp/Component.js
@@ -0,0 +1,27 @@
+sap.ui.define([
+ "sap/ui/core/UIComponent",
+ "sap/ui/model/json/JSONModel",
+ "sap/ui/core/date/Gregorian",
+ "sap/ui/core/ComponentSupport"
+], function (UIComponent, JSONModel) {
+ "use strict"
+ return UIComponent.extend(
+ "sap.codejam.Component", {
+ metadata : {
+ "interfaces": [
+ "sap.ui.core.IAsyncContentCreation"
+ ],
+ manifest: "json"
+ },
+ init : function () {
+ this.setModel(new JSONModel({"@odata.context":"$metadata#Books(genre())","value":[{"createdAt":"2022-10-18T12:25:17.907Z","modifiedAt":"2022-10-18T12:25:17.907Z","ID":201,"title":"Wuthering Heights","descr":"Wuthering Heights, Emily Brontë's only novel, was published in 1847 under the pseudonym \"Ellis Bell\". It was written between October 1845 and June 1846. Wuthering Heights and Anne Brontë's Agnes Grey were accepted by publisher Thomas Newby before the success of their sister Charlotte's novel Jane Eyre. After Emily's death, Charlotte edited the manuscript of Wuthering Heights and arranged for the edited version to be published as a posthumous second edition in 1850.","author":"Emily Brontë","genre_ID":11,"stock":12,"price":11.11,"currency_code":"GBP","image@odata.mediaContentType":"image/png","genre":{"name":"Drama","descr":null,"ID":11,"parent_ID":10}},{"createdAt":"2022-10-18T12:25:17.907Z","modifiedAt":"2022-10-18T12:25:17.907Z","ID":207,"title":"Jane Eyre","descr":"Jane Eyre /ɛər/ (originally published as Jane Eyre: An Autobiography) is a novel by English writer Charlotte Brontë, published under the pen name \"Currer Bell\", on 16 October 1847, by Smith, Elder & Co. of London. The first American edition was published the following year by Harper & Brothers of New York. Primarily a bildungsroman, Jane Eyre follows the experiences of its eponymous heroine, including her growth to adulthood and her love for Mr. Rochester, the brooding master of Thornfield Hall. The novel revolutionised prose fiction in that the focus on Jane's moral and spiritual development is told through an intimate, first-person narrative, where actions and events are coloured by a psychological intensity. The book contains elements of social criticism, with a strong sense of Christian morality at its core and is considered by many to be ahead of its time because of Jane's individualistic character and how the novel approaches the topics of class, sexuality, religion and feminism.","author":"Charlotte Brontë","genre_ID":11,"stock":11,"price":12.34,"currency_code":"GBP","image@odata.mediaContentType":"image/png","genre":{"name":"Drama","descr":null,"ID":11,"parent_ID":10}},{"createdAt":"2022-10-18T12:25:17.907Z","modifiedAt":"2022-10-18T12:25:17.907Z","ID":251,"title":"The Raven","descr":"\"The Raven\" is a narrative poem by American writer Edgar Allan Poe. First published in January 1845, the poem is often noted for its musicality, stylized language, and supernatural atmosphere. It tells of a talking raven's mysterious visit to a distraught lover, tracing the man's slow fall into madness. The lover, often identified as being a student, is lamenting the loss of his love, Lenore. Sitting on a bust of Pallas, the raven seems to further distress the protagonist with its constant repetition of the word \"Nevermore\". The poem makes use of folk, mythological, religious, and classical references.","author":"Edgar Allen Poe","genre_ID":16,"stock":333,"price":13.13,"currency_code":"USD","image@odata.mediaContentType":"image/png","genre":{"name":"Mystery","descr":null,"ID":16,"parent_ID":10}},{"createdAt":"2022-10-18T12:25:17.907Z","modifiedAt":"2022-10-18T12:25:17.907Z","ID":252,"title":"Eleonora","descr":"\"Eleonora\" is a short story by Edgar Allan Poe, first published in 1842 in Philadelphia in the literary annual The Gift. It is often regarded as somewhat autobiographical and has a relatively \"happy\" ending.","author":"Edgar Allen Poe","genre_ID":16,"stock":555,"price":14,"currency_code":"USD","image@odata.mediaContentType":"image/png","genre":{"name":"Mystery","descr":null,"ID":16,"parent_ID":10}},{"createdAt":"2022-10-18T12:25:17.907Z","modifiedAt":"2022-10-18T12:25:17.907Z","ID":271,"title":"Catweazle","descr":"Catweazle is a British fantasy television series, starring Geoffrey Bayldon in the title role, and created by Richard Carpenter for London Weekend Television. The first series, produced and directed by Quentin Lawrence, was screened in the UK on ITV in 1970. The second series, directed by David Reid and David Lane, was shown in 1971. Each series had thirteen episodes, most but not all written by Carpenter, who also published two books based on the scripts.","author":"Richard Carpenter","genre_ID":13,"stock":22,"price":150,"currency_code":"JPY","image@odata.mediaContentType":"image/png","genre":{"name":"Fantasy","descr":null,"ID":13,"parent_ID":10}}]}))
+ UIComponent.prototype.init.apply(
+ this,
+ arguments
+ )
+ },
+ onAfterRendering: function() {
+ document.body.classList.remove("loading");
+ }
+ })
+ })
\ No newline at end of file
diff --git a/bookshop/app/performance-webapp/webapp/boot.js b/bookshop/app/performance-webapp/webapp/boot.js
new file mode 100644
index 00000000..5ac13bee
--- /dev/null
+++ b/bookshop/app/performance-webapp/webapp/boot.js
@@ -0,0 +1,19 @@
+// configuration object
+window["sap-ui-config"] = {
+ "compatVersion": "edge",
+ "async": true,
+ "language": "en",
+ "resourceroots": {
+ "sap.codejam": "./"
+ },
+ "onInit": "module:sap/ui/core/ComponentSupport",
+ "theme": (function () {
+ // determine the proper theme for UI5 from current color scheme
+ try {
+ return window.matchMedia("(prefers-color-scheme: dark)").matches ? "sap_horizon_dark" : "sap_horizon";
+ } catch (ex) {
+ console.warn("window.matchMedia not supported - keep default theme");
+ return "sap_horizon";
+ }
+ })()
+};
\ No newline at end of file
diff --git a/bookshop/app/performance-webapp/webapp/controller/App.controller.js b/bookshop/app/performance-webapp/webapp/controller/App.controller.js
new file mode 100644
index 00000000..8177166e
--- /dev/null
+++ b/bookshop/app/performance-webapp/webapp/controller/App.controller.js
@@ -0,0 +1,83 @@
+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";
+ return Controller.extend("sap.codejam.controller.App", {
+ onInit: function () {
+ this.getView().setModel(new JSONModel({
+ itemSelected: false,
+ selectedQuantity: 1
+ }), "userSelection")
+ },
+ onSelect: function (oEvent) {
+ let oModel = this.getView().getModel("userSelection")
+ let selectedModelPath = oEvent.getSource().getBindingContext().sPath
+ let selectedModelData = oEvent.getSource().getModel().getProperty(selectedModelPath)
+ oModel.setProperty("/selectedItemPath", selectedModelPath)
+ oModel.setProperty("/selectedItemData", selectedModelData)
+
+ oModel.setProperty("/selectedQuantity", 1)
+ this.getView().byId("orderStatus").setText("")
+ oModel.setProperty("/itemSelected", true)
+ },
+ onSubmitOrder: function () {
+ let oView = this.getView()
+ let userSelectionData = oView.getModel("userSelection").getData()
+
+ let reqSettings = {
+ "url": "/browse/submitOrder",
+ "method": "POST",
+ "timeout": 0,
+ "headers": {
+ "Content-Type": "application/json"
+ },
+ "data": JSON.stringify({
+ "book": userSelectionData.selectedItemData.ID,
+ "quantity": userSelectionData.selectedQuantity
+ }),
+ }
+
+ let i18nModel = oView.getModel("i18n")
+ jQuery.ajax(reqSettings)
+ .done(function (response) {
+ console.log(response)
+ oView.byId("orderStatus")
+ .setText(
+ `${i18nModel.getProperty("orderSuccessful")}
+ (${userSelectionData.selectedItemData.title},
+ ${userSelectionData.selectedQuantity}
+ ${i18nModel.getProperty("pieces")})`
+ )
+ oView.byId("orderStatus").setState("Success")
+
+ let userSelectedPath = oView.getModel("userSelection").getProperty("/selectedItemPath")
+ oView.getModel().setProperty(userSelectedPath + "/stock", response.stock)
+ oView.getModel("userSelection").setProperty("/selectedItemData/stock", response.stock)
+ })
+ .fail(function(response) {
+ console.log(response)
+ oView.byId("orderStatus").setText(`${i18nModel.getProperty("Error")}`)
+ oView.byId("orderStatus").setState("Error")
+ })
+ },
+ onSearch: function (oEvent) {
+ var aFilter = [];
+ var sQuery = oEvent.getParameter("newValue");
+ if (sQuery) {
+ aFilter.push(new Filter("title", FilterOperator.Contains, sQuery));
+ }
+ var oList = this.byId("booksTable");
+ var oBinding = oList.getBinding("items");
+ oBinding.filter(aFilter);
+
+ let oModel = this.getView().getModel("userSelection")
+ oModel.setProperty("/selectedItemPath", {})
+ oModel.setProperty("/selectedItemData", {})
+ this.getView().byId("orderStatus").setText("")
+ oModel.setProperty("/itemSelected", false)
+ }
+ });
+});
\ No newline at end of file
diff --git a/bookshop/app/performance-webapp/webapp/css/style.css b/bookshop/app/performance-webapp/webapp/css/style.css
new file mode 100644
index 00000000..c2c9cf2f
--- /dev/null
+++ b/bookshop/app/performance-webapp/webapp/css/style.css
@@ -0,0 +1,31 @@
+.orderControls {
+ gap: 20px;
+}
+
+html, body {
+ width: 100%;
+ height: 100%;
+}
+
+body.loading {
+ background-size: 20rem !important;
+ background-position: center;
+ background-repeat: no-repeat;
+ overflow: hidden;
+}
+body.loading > * {
+ visibility: hidden;
+}
+
+@media (prefers-color-scheme: light) {
+ body.loading {
+ background-color: white;
+ background-image: url("data:image/svg+xml;utf8,");
+ }
+}
+@media (prefers-color-scheme: dark) {
+ body.loading {
+ background-color: black;
+ background-image: url("data:image/svg+xml;utf8,");
+ }
+}
\ No newline at end of file
diff --git a/bookshop/app/performance-webapp/webapp/i18n/i18n_de.properties b/bookshop/app/performance-webapp/webapp/i18n/i18n_de.properties
new file mode 100644
index 00000000..32c068ee
--- /dev/null
+++ b/bookshop/app/performance-webapp/webapp/i18n/i18n_de.properties
@@ -0,0 +1,10 @@
+ Bookshop=Buchhandlung
+ Book=Buch
+ Author=Autor
+ Genre=Genre
+ Price=Preis
+ Stock=Verfügbarkeit
+ Order=Bestellen
+ orderSuccessful=Bestellung erfolgreich
+ pieces=Stk.
+ Error=Fehler
\ No newline at end of file
diff --git a/bookshop/app/performance-webapp/webapp/i18n/i18n_en.properties b/bookshop/app/performance-webapp/webapp/i18n/i18n_en.properties
new file mode 100644
index 00000000..0999f6ca
--- /dev/null
+++ b/bookshop/app/performance-webapp/webapp/i18n/i18n_en.properties
@@ -0,0 +1,10 @@
+ Bookshop=Bookshop
+ Book=Book
+ Author=Author
+ Genre=Genre
+ Price=Price
+ Stock=Stock
+ Order=Order
+ orderSuccessful=Order successful
+ pieces=pcs.
+ Error=Error
\ No newline at end of file
diff --git a/bookshop/app/performance-webapp/webapp/index.html b/bookshop/app/performance-webapp/webapp/index.html
new file mode 100644
index 00000000..a326b90b
--- /dev/null
+++ b/bookshop/app/performance-webapp/webapp/index.html
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/chapters/chapter00/readme.md b/chapters/chapter00/readme.md
deleted file mode 100644
index 4c78e0a4..00000000
--- a/chapters/chapter00/readme.md
+++ /dev/null
@@ -1,49 +0,0 @@
-# Chapter 0 - Preparing the Development Environment
-
-By the end of this chapter we will have prepared our development environment so that we can start developing our bookshop app.
-
-## Steps
-
-[1. Navigate into your preferred directory]()
-[2. Clone this repository]()
-[3. Navigate into the newly created directory]()
-[4. Install the project's dependencies]()
-[5. Open the directory]()
-
-### 1. Navigate into your preferred directory
-
-➡️ Open a new terminal session and navigate to where you want to store this repository.
-
-### 2. Clone this repository
-
-➡️ Execute the following command to clone this repository:
-
-```bash
-git clone https://github.com/SAP-samples/ui5-exercises-codejam
-```
-
-The command created new directory for the cloned repository.
-
-### 3. Navigate into the newly created directory
-
-We want to navigate into the `bookshop` directory inside newly created directory, as this is where we will build our application.
-
-➡️ Execute the following command in the same terminal session:
-
-```bash
-cd ui5-exercises-codejam/bookshop
-```
-
-### 4. Install the project's dependencies
-
-➡️ Execute the following command to install the project's dependencies:
-
-```bash
-npm install
-```
-
-### 5. Open the directory
-
-➡️ Open the directory in the code editor of your choice (e.g. [Visual Studio Code](https://code.visualstudio.com/download)).
-
-Continue to [Chapter 1 - Scaffolding the App - Our First View](/chapters/chapter01)
diff --git a/chapters/chapter01/chapter01-01.png b/chapters/chapter01/chapter01-01.png
deleted file mode 100644
index 66deaf41..00000000
Binary files a/chapters/chapter01/chapter01-01.png and /dev/null differ
diff --git a/chapters/chapter01/chapter01-result1.png b/chapters/chapter01/chapter01-result1.png
deleted file mode 100644
index 3cede95e..00000000
Binary files a/chapters/chapter01/chapter01-result1.png and /dev/null differ
diff --git a/chapters/chapter01/chapter01-result2.png b/chapters/chapter01/chapter01-result2.png
deleted file mode 100644
index 764fefbc..00000000
Binary files a/chapters/chapter01/chapter01-result2.png and /dev/null differ
diff --git a/chapters/chapter01/readme.md b/chapters/chapter01/readme.md
deleted file mode 100644
index 13bcd18d..00000000
--- a/chapters/chapter01/readme.md
+++ /dev/null
@@ -1,322 +0,0 @@
-# Chapter 1 - Scaffolding the App - Our First View
-
-By the end of this chapter, we will have created a fully functional UI5 app that displays the title of our bookshop and greets the user.
-
-## Steps
-
-[0. Make sure you are in the project root (`bookshop/`)](#0-make-sure-you-are-in-the-project-root-bookshop)
-[1. Create a new `app/webapp/` directory for our UI5 app](#1-create-a-new-appwebapp-directory-for-our-ui5-app)
-[2. Create an `app/webapp/index.html` file](#2-create-an-appwebappindexhtml-file)
-[3. Add `ComponentSupport` to our `app/webapp/index.html` file](#3-add-componentsupport-to-our-appwebappindexhtml-file)
-[4. Create an `app/webapp/Component.js` file](#4-create-an-appwebappcomponentjs-file)
-[5. Create an `app/webapp/manifest.json` file](#5-create-an-appwebappmanifestjson-file)
-[6. Create an `app/webapp/view/App.view.xml` file (our first view)](#6-create-an-appwebappviewappviewxml-file-our-first-view)
-[7. Run our app](#7-run-our-app)
-
-### 0. Make sure you are in the project root (`bookshop/`)
-
-➡️ Make sure you are in the `bookshop/` directory, which is our project root.
-
-The material in this repository will always reference directories and files in relation to the project root.
-
-### 1. Create a new `app/webapp/` directory for our UI5 app
-
-Let's begin this project by creating a new `app/webapp/` directory inside the `bookshop/` directory. This is where our UI5 application is going to live.
-
-What do the rest of the files and directories of the bookshop do? 💬
-
-
-
-> The rest of the files and directories of the `bookshop` represent the backend application that was built with the Node.js flavour of the SAP Cloud Application Programming Model (CAP):
-> - The [package.json](/bookshop/package.json) declares the project's dependencies, which were loaded into the `node_modules` directory when we ran `npm install` during [Chapter 0 - Preparing the Development Environment](/chapters/chapter00/readme.md).
-> - The [db/](/bookshop/db/) directory represents the database layer of the backend, defining all tables (entities) as well as the association between them.
-> - The [srv/](/bookshop/srv/) directory represents the service layer of the backend, which is the API our frontend UI5 application will be interacting with.
-> - The [app/finished-webapp/](app/finished-webapp/) directory already includes the finished UI5 bookshop app, but we want to rebuild it from scratch step by step.
->
-> Check out the [architectural diagram](/architecture.png) for more info.
-
-
-
-Why do we not use a template to scaffold our project, but build everything from scratch? 💬
-
-
-
-> For the purpose of learning, we will not use a generator or wizard (like [easy-ui5](https://blogs.sap.com/2019/02/05/introducing-the-easy-ui5-generator/)) to scaffold our UI5 application, although this would be a much faster option. We build the app from scratch step-by-step to better understand what every individual piece does.
-
-
-
-### 2. Create an `app/webapp/index.html` file
-
-Like most other web applications our UI5 app needs an `index.html` serving as the entry point.
-
-➡️ Create a new `app/webapp/index.html` file and paste the following code into it:
-
-```html
-
-
-
-
-
-
-
-
-
-
-
-```
-
-We loaded the UI5 framework into our project and configured a few attributes such as the theme and library we want to use, and the name of our project root.
-
-
-What is HTML and how is it structured? 💬
-
-
-
-> HTML (HyperText Markup Language) is the standard markup language for documents that are designed to be displayed in a web browser (web pages) and is one of the fundamental building blocks of the web. It is used to describe a web page's elements such as paragraphs, links and images.
->
->An HTML file usually includes 4 major parts.
-> 1. `` is the document type declaration and tells the browser what the document is - HTML.
-> 1. In HTML, elements always have an opening and a closing tag and everything in between is considered to be a child of that element. The first element of a document is the HTML element itself, represented by the opening `` and closing `` tag. This tells the browser that everything between those markers should be interpreted as HTML.
-> 1. The first child of the HTML element is the head. The head includes meta information about the document.
-> 1. The second child of the HTML element is the body. The body includes the actual content of the document. Elements such as paragraphs, links and images will be children of the body.
->
-> An `index.html` file is usually the entry point to a web page. Our UI5 app is no exception to that.
-
-
-
-
-What do the attributes inside the <script /> element mean? 💬
-
-
-
-> An HTML `` element tells the browser that its content should be interpreted as JavaScript code. In our case we don't have content inside the tags, but rather specify attributes of that `` element. By specifying these attributes we initialize the UI5 framework and turn our blank `index.html` file into a UI5 project. This step is called **bootstrapping**.
->
-> Let's go through each of the attributes step-by-step:
-> - We specify an `id` for the `` element, which is used by the framework to find out where it was initialized from.
-> - The `src` attribute defines where the JavaScript code for the script tag lives. This JavaScript code is the UI5 framework. As you can see, we are loading OpenUI5. You can visit [https://openui5.hana.ondemand.com/resources/sap-ui-core.js](https://openui5.hana.ondemand.com/resources/sap-ui-core.js) and see the code that makes up the framework.
-> - With the `data-sap-ui-theme` attribute we specify which UI5 theme we want to use. This is the parameter we can modify to change the looks of our app. You can read more about Theming in the [SAPUI5 Documentation](https://ui5.sap.com/sdk/#/topic/497c27a8ee26426faacd2b8a1751794a).
-> - With the `data-sap-ui-libs` attribute, which is technically optional, but should always be used, we specify which UI5 library we want to preload before our app is initialized. This drastically improves the performance of our app. We can always load other libraries into our views and controllers on demand (see examples in [step 6](/chapters/chapter01#6-create-an-appwebappviewappviewxml-file-our-first-view) of the current chapter).
-> - With the `data-sap-ui-compatVersion` attribute we specify which version of certain UI5 features we want to use in case of incompatibilities. Since this concept has been abandoned the [SAPUI5 Documentation](https://ui5.sap.com/sdk/#/topic/9feb96da02c2429bb1afcf6534d77c79.html) suggests to set this value to `edge`.
-> - With the `data-sap-ui-resourceroots` attribute we define a namespace for a certain location in our project. In our case we gave the root of our project (`./`) the namespace `sap.codejam`. We will use this namespace to reference our project root in other places of our code (e.g. in [step 5](/chapters/chapter01#5-create-an-appwebappmanifestjson-file) of the current chapter).
-
-
-
-
-### 3. Add `ComponentSupport` to our `app/webapp/index.html` file
-
-At this point we could theoretically already start instantiating UI5 elements (also called ***controls***), but because we want to make sure our project scales well, we will follow one of the best practices and wrap our app into a ***component*** first. A component is an independent and reusable part. This makes our app independent from the environment it's running in. In our case the component will be started from the `index.html`, but because it is encapsulated and reuseable, the same component could also be started from another `html` file that is powering a Fiori Launchpad for example. A UI5 component most of the times contains a whole UI5 app, so it's a little different from components you may know from other frameworks. If you want to reuse smaller UI parts, such as a single button or dialog, UI5 offers the concept of [fragments](https://ui5.sap.com/sdk/#/topic/4da72985139b4b83b5f1c1e0c0d2ed5a), which we will not cover in this project.
-
-We need to add the `ComponentSupport` to the bootstrapping of our app and add a component to our html `
-
-
-
-
-
-```
-
-We used the `data-sap-ui-oninit` attribute in our bootstrapping to specify that we want to initialize a UI5 Component. We also added a new HTML element (`div`) to our `
`.
-
-➡️ Replace the existing content of the `app/webapp/index.html` file with the following code:
-
-```html
-
-
-
-
-
-
-
-
` that holds the component. The component set up is defined in a `Component.js` file, which we will create next.
-
-### 4. Create an `app/webapp/Component.js` file
-
-Our `index.html` is now actively looking for a `Component.js` file on root level of our UI5 app. This is an important naming convention, so it is important not to change the name of this file.
-
-In case you are wondering, we configured the root of our project, which is the `app/webapp/` directory, during the bootstrapping in [step 2](/chapters/chapter01#2-create-an-appwebappindexhtml-file) of this chapter.
-
-➡️ Create a new `app/webapp/Component.js` file and paste the following code into it:
-
-```javascript
-sap.ui.define([
- "sap/ui/core/UIComponent"
-], function (UIComponent) {
- "use strict"
- return UIComponent.extend(
- "sap.codejam.Component", {
- metadata : {
- "interfaces": [
- "sap.ui.core.IAsyncContentCreation"
- ],
- manifest: "json"
- },
- init : function () {
- UIComponent.prototype.init.apply(
- this,
- arguments
- )
- }
- })
- }
-)
-```
-
-We have set up our component by initializing the `UIComponent` from the UI5 library. We extended it with some metadata, referencing the `manifest.json`, which we will create next.
-
-
-What does this code do in detail? 💬
-
-
-
-> Our component set up is essentially a [JavaScript module (in the AMD format)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules). We have defined it with the `sap.ui.define` method. This method takes two parameters (also see its [documentation](https://ui5.sap.com/sdk/#/api/sap.ui%23methods/sap.ui.define)):
-> 1. An array of dependencies from UI5 libraries
-> 1. A function that will be executed
->
-> Our only dependency is the `UIComponent`, which we pass to the function. This function returns the `UIComponent`, but [extends](https://ui5.sap.com/sdk/#/api/sap.ui.core.UIComponent%23methods/sap.ui.core.UIComponent.extend) it with an new subclass that we call `sap.codejam.Component`. This subclass is enriched with a `metadata` parameter, which is an object that points to a `manifest.json` and makes sure that the `UIComponent` is created fully asynchronously (`"sap.ui.core.IAsyncContentCreation"`). The subclass is also enriched by an `init` function, which is automatically invoked by the framework when the component is instantiated. Inside this function, we make sure that the init function of the `UIComponent`'s parent is invoked (which is obligatory).
->
-> Read more about the component configuration in the [SAPUI5 Documentation](https://ui5.sap.com/sdk/#/topic/4cfa60872dca462cb87148ccd0d948ee).
-
-
-
-### 5. Create an `app/webapp/manifest.json` file
-
-The `manifest.json` is our application descriptor file and holds metadata about our app
-
-➡️ Create a new `app/webapp/manifest.json` and paste the following code into it:
-
-```json
-{
- "sap.app": {
- "id": "codejam",
- "type": "application",
- "title": "CodeJam Bookshop",
- "applicationVersion": {
- "version": "1.0.0"
- },
- "dataSources": {}
- },
- "sap.ui5": {
- "rootView": {
- "viewName": "sap.codejam.view.App",
- "type": "XML",
- "id": "app"
- },
- "dependencies": {
- "minUI5Version": "1.107.1",
- "libs": {
- "sap.ui.core": {},
- "sap.m": {}
- }
- },
- "models": {}
- }
-}
-```
-
-
-What is a manifest.json file? 💬
-
-
-
-> A `manifest.json` file is usually used to define metadata about a web app or extension, like its name, icon, or other details. It is not specific to UI5. Apps built with other frameworks also have this file. The `manifest.json` is especially important for UI5, as it is used by the framework during runtime to define important properties of the app, such as data sources, localization settings, and [many more](https://ui5.sap.com/sdk/#/topic/be0cf40f61184b358b5faedaec98b2da.html#loiobe0cf40f61184b358b5faedaec98b2da/section_nonamespace).
-
-
-
-At this point we successfully scaffolded our UI5 project: We have an `app/webapp/index.html` file serving as the entry point and holding our component, which references our `manifest.json`, which describes our application. We can now go ahead and populate our app with actual content that is visible to the user. This is what out project's structure looks like at the moment:
-
-
-
-### 6. Create an `app/webapp/view/App.view.xml` file (our first view)
-
-We already referenced our root XML view in our `app/webapp/manifest.json`. Let's create this file.
-
-➡️ Create a new `app/webapp/view/App.view.xml` file and paste the following code into it:
-
-```xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
-We defined our first XML view with a few UI5 controls. UI5 controls are reusable UI elements provided by the framework. Similar to HTML elements, they have an opening and closing tag and predefined attributes (non-working example: ``). They follow the [Fiori Design Guidelines](https://experience.sap.com/fiori-design-web/) and provide a lot of functionalities out of the box. XML views are the best way to use and structure UI5 controls, as they are very easy to read and represent the hierarchical structure of controls very well. We will just call them 'views' from now on.
-
-You might be wondering how you as a developer can find out which UI5 controls to use and what attributes and APIs they have. The official [SAPUI5 API Reference](https://ui5.sap.com/#/api) as well as the [Code Samples](https://ui5.sap.com/#/controls) are your go-to resources and contain all the information you will ever need.
-
-
-What do the individual controls in our code do? 💬
-
-
-
-> - The [``](https://ui5.sap.com/#/api/sap.ui.core.mvc.View) control is our base class for the view. At the top of the file you can see that it is part of the `sap.ui.core.mvc` library. We assign this library to an xml namespace (abbreviated `xmlns`) that we call `mvc` (we will cover what `mvc` stands for in [chapter 3](/chapters/chapter03#5-add-a-new-flexbox--to-the-appwebappviewappviewxml)). We always use controls by prefixing its namespace (the library it is from) followed by a colon (e.g. ``). Each view can have one default namespace, that can be omitted. In our case we assigned the `sap.m` library to the default namespace.
-> - The [``](https://ui5.sap.com/#/api/sap.m.App) control is the root element of a UI5 app. In the documentation we can see that its [default aggregation](https://ui5.sap.com/#/api/sap.m.App%23aggregations) is ``. This means that these are the expected children of the `` control. Aggregations are always lowercase.
-> - The [``](https://ui5.sap.com/#/api/sap.m.Page) control is a container that holds one whole screen of an app. In its documentation we can see that it can have a `title` text that appears in the page header bar. We can also see that its [default aggregation](https://ui5.sap.com/#/api/sap.m.Page%23aggregations) is ``.
-> - The [``](https://ui5.sap.com/#/api/sap.m.Panel%23overview) control is a container for grouping and displaying information. In its documentation we can see that we can define a `headerText` that will be displayed at the top of it. We can also see that its [default aggregation](https://ui5.sap.com/#/api/sap.m.Panel%23aggregations) is ``.
-
-
-
-### 7. Run our app
-
-At this point we have already created a fully functional UI5 app. Let's start our app.
-
-➡️ Open a terminal session from the `bookshop/` directory and run the following command:
-
-```bash
-npm run dev
-```
-
-We ran the script to start our app locally, which is defined in our [package.json](/bookshop/package.json). We did this on root level of the `bookshop` project, as we want to start both our front and backend application. The SAP Cloud Application Model automatically looks for `html` files inside the `app/` directory and serves them on a web server alongside the service endpoints for the backend application (see [srv](/bookshop/srv/)). You can find the URL to access it in the terminal output. It will be [http://localhost:4004](http://localhost:4004) or something similar depending on your development environment.
-
-The server is set to restart once it detects a change in one of the files. This is very useful as we don't have to do this manually after we one or more changes to our app. You can test this by editing the `title` attribute of the ``, saving the file, and reloading the page in the browser.
-
-
-
-
-
-Further Questions to Discuss 🤔
-
-
-
-- What other options are there to scaffold UI5 based applications?
-- What other tools/packages can you use to run and develop UI5 applications (instead of utilizing the @sap/cds server)?
-
-
-
-
-Continue to [Chapter 2 - Creating and Consuming Our First Model](/chapters/chapter02)
\ No newline at end of file
diff --git a/chapters/chapter02/chapter02-01.png b/chapters/chapter02/chapter02-01.png
deleted file mode 100644
index ae71c06e..00000000
Binary files a/chapters/chapter02/chapter02-01.png and /dev/null differ
diff --git a/chapters/chapter02/chapter02-result.png b/chapters/chapter02/chapter02-result.png
deleted file mode 100644
index 72ba43d4..00000000
Binary files a/chapters/chapter02/chapter02-result.png and /dev/null differ
diff --git a/chapters/chapter02/readme.md b/chapters/chapter02/readme.md
deleted file mode 100644
index bf12cd19..00000000
--- a/chapters/chapter02/readme.md
+++ /dev/null
@@ -1,138 +0,0 @@
-# Chapter 2 - Creating and Consuming Our First Model
-
-By the end of this chapter, we will have added a Table to our UI5 app that displays the books that are available in our bookshop.
-
-## Steps
-
-[1. Add a new `dataSource` and `model` to our `app/webapp/manifest.json`](#1-add-a-new-datasource-and-model-to-our-appwebappmanifestjson)
-[2. Add a new `
` to our `app/webapp/view/App.view.xml` that consumes the model](#2-add-a-new-table--to-our-appwebappviewappviewxml-that-consumes-the-model)
-[3. Inspect our app in the browser](#3-inspect-our-app-in-the-browser)
-
-### 1. Add a new `dataSource` and `model` to our `app/webapp/manifest.json`
-
-Models are another major part of UI5 development. We use models to store data in our app ("data layer"). Models are not bound to or represented by a specific file, but are dynamic objects that can be consumed and modified by different parts of the app. They can be created via the `manifest.json` file or via a controller (which we will do in [chapter 03](/chapters/chapter03#3-add-a-new-userselection-model-and-an-onselect-method-to-our-controller)).
-
-➡️ Paste the following code into the `app/webapp/manifest.json`:
-
-```json
-{
- "sap.app": {
- "id": "codejam",
- "type": "application",
- "title": "CodeJam Bookshop",
- "applicationVersion": {
- "version": "1.0.0"
- },
- "dataSources": {
- "capBooks": {
- "uri": "/browse/Books?$expand=genre",
- "type": "JSON"
- }
- }
- },
- "sap.ui5": {
- "rootView": {
- "viewName": "sap.codejam.view.App",
- "type": "XML",
- "id": "app"
- },
- "dependencies": {
- "minUI5Version": "1.107.1",
- "libs": {
- "sap.ui.core": {},
- "sap.m": {}
- }
- },
- "models": {
- "": {
- "dataSource": "capBooks"
- }
- }
- }
-}
-```
-
-We defined a new model with an empty string as its name, which makes it the default model of the app. The `dataSource` for the model is `capBooks`, which is a new data `dataSource` we created that links to our backend application that is being served on the same domain as our UI5 app (see [chapter 1 - step 7](/chapters/chapter01#7-run-our-app)). We can inspect the data at [http://localhost:4004/browse/Books?$expand=genre](http://localhost:4004/browse/Books?$expand=genre).
-
-### 2. Add a new `
` to our `app/webapp/view/App.view.xml` that consumes the model
-
-We can now go ahead an consume the newly created model in our `app/webapp/view/App.view.xml`.
-
-➡️ Paste the following code into the `` section of the `` in our existing view:
-
-```xml
-
` collapsed in the screen shot):
-
-
-
-We created a Table that displays the books from our bookshop data. Let's go through the code step by step to better understand how we did it:
-
-- We created a new [`
`](https://sapui5.hana.ondemand.com/#/api/sap.m.Table) that holds `` and `` (rows) as its aggregations.
-- The `
` has an `items` attribute where we want to reference the `value` array of our bookshop data as it holds the book items (check the raw data here: [http://localhost:4004/browse/Books?$expand=genre](http://localhost:4004/browse/Books?$expand=genre)). For that we make use of a concept called ***data binding***. Data binding is very important in UI5 and requires a special syntax. We use curly brackets `{}` to tell the framework we will be using a model and then use a slash `/` to enter the json structure of our default model, which is our bookshop data (see step 1). Then we specify that our items live in the `value` array. Putting all the pieces together we end up with `items="{/value}"` as our data binding syntax.
-- The `` aggregation inside the `
` holds several [``](https://sapui5.hana.ondemand.com/#/api/sap.m.Column) controls that each hold a [``](https://sapui5.hana.ondemand.com/#/api/sap.m.Text) control. These are the texts in the header row of our `
`.
-- The `` (rows) of our `
` hold a [``](https://sapui5.hana.ondemand.com/#/api/sap.m.ColumnListItem), which serves as a wrapper for the `` of each row. The controls inside the `` aggregation have to match our `` with respect to the order of the content (book, author, genre, price, stock).
-- Check the [documentation](https://sapui5.hana.ondemand.com/#/api/sap.m.ColumnListItem%23controlProperties) to see what the `vAlign` and `type` attributes for the `` do.
-- Inside the `` we display the actual data and make use of the data binding concept again. As our whole `
` is bound to the `value` array of our data model, we can bind the controls inside the `` aggregation to properties of the items inside that array, such a the title and author.
-- For the controls inside the `` aggregation we selected controls that fit the type of data that they display.
-
-You might want to check the documentation for the [``](https://sapui5.hana.ondemand.com/#/api/sap.m.ColumnListItem%23controlProperties) control to see what the attributes `vAlign` and `type` in our code mean.
-
-### 3. Inspect our app in the browser
-
-➡️ Move over to the browser and refresh the page to see our Table:
-
-
-
-
-Further Questions to Discuss 🤔
-
-
-
-- We used a JSON model for our UI5 application? What other type of model could we have used?
-- Besides storing local data, what else can you use UI5 models for?
-
-
-
-
-Continue to - [Chapter 3 - Creating and Extending our First Controller](/chapters/chapter03)
\ No newline at end of file
diff --git a/chapters/chapter03/chapter03-01.png b/chapters/chapter03/chapter03-01.png
deleted file mode 100644
index 15fe7e92..00000000
Binary files a/chapters/chapter03/chapter03-01.png and /dev/null differ
diff --git a/chapters/chapter03/chapter03-result.png b/chapters/chapter03/chapter03-result.png
deleted file mode 100644
index 224cde17..00000000
Binary files a/chapters/chapter03/chapter03-result.png and /dev/null differ
diff --git a/chapters/chapter03/readme.md b/chapters/chapter03/readme.md
deleted file mode 100644
index ee2e49de..00000000
--- a/chapters/chapter03/readme.md
+++ /dev/null
@@ -1,134 +0,0 @@
-# Chapter 3 - Creating and Extending our First Controller
-
-At the end of this chapter we will have made our UI5 app interactive so that a user can select a book to read more information about it.
-
-## Steps
-
-[1. Create a new `app/webapp/controller/App.controller.js` file](#1-create-a-new-appwebappcontrollerappcontrollerjs-file)
-[2. Reference the controller file in our `app/webapp/view/App.view.xml`](#2-reference-the-controller-file-in-our-appwebappviewappviewxml)
-[3. Add a new `userSelection` model and an `onSelect` method to our controller](#3-add-a-new-userselection-model-and-an-onselect-method-to-our-controller)
-[4. Bind the new `onSelect` method to the ``](#4-bind-the-new-onselect-method-to-the-columnlistitem)
-[5. Add a new `` to the `app/webapp/view/App.view.xml`](#5-add-a-new-flexbox--to-the-appwebappviewappviewxml)
-[6. Inspect our app in the browser](#6-inspect-our-app-in-the-browser)
-
-### 1. Create a new `app/webapp/controller/App.controller.js` file
-
-➡️ Create a new JavaScript file `app/webapp/controller/App.controller.js` and paste the following code into it:
-
-```javascript
-sap.ui.define([
- "sap/ui/core/mvc/Controller"
-], function (Controller) {
- "use strict"
- return Controller.extend("sap.codejam.controller.App", {
- onInit: function () {
- alert("I am about to initialize the view.")
- }
- })
-})
-```
-
-We created our first controller file. We imported the `Controller` from the library, passed it to a function and extended it. To demonstrate how the controller works, we added an alert to the `onInit` method, which automatically gets triggered upon initialization of the view.
-
-### 2. Reference the controller file in our `app/webapp/view/App.view.xml`
-
-To make the browser execute the JavaScript code in our controller file we have to reference it in our `app/webapp/view/App.view.xml`.
-
-➡️ Replace the opening tag of the `` control at the top of our view with the following code snippet:
-
-```xml
-
-```
-
-We can now refresh our app running in the browser and see the alert being displayed just before the view is visible.
-
-### 3. Add a new `userSelection` model and an `onSelect` method to our controller
-
-Of course we can not only extend existing methods in a controller, but we can write our own ones, too.
-
-➡️ Replace the existing content in the `app/webapp/controller/App.controller.js` file with the following code:
-
-```javascript
-sap.ui.define([
- "sap/ui/core/mvc/Controller",
- "sap/ui/model/json/JSONModel"
-], function (Controller, JSONModel) {
- "use strict"
- return Controller.extend("sap.codejam.controller.App", {
- onInit: function () {
- this.getView().setModel(new JSONModel({}), "userSelection")
- },
- onSelect: function (oEvent) {
- let oModel = this.getView().getModel("userSelection")
- let selectedModelPath = oEvent.getSource().getBindingContext().sPath
- let selectedModelData = oEvent.getSource().getModel().getProperty(selectedModelPath)
- oModel.setProperty("/selectedItemPath", selectedModelPath)
- oModel.setProperty("/selectedItemData", selectedModelData)
- }
- })
-})
-```
-
-We imported the `JSONModel` from the library and instantiated a new one with the name `userSelection` in the `onInit` method of the controller. We can use this model to store data that is used to control the state of certain UI elements. We also defined a new `onSelect` method for when a user clicks on a book in the table. Inside this method, we get the `userSelection` model and set the data of the selected item (as well as the path to it) as a new property of the model.
-
-### 4. Bind the new `onSelect` method to the ``
-
-We can now use our new method in our `app/webapp/view/App.view.xml` by binding it to the `press` event of the ``.
-
-➡️ Replace the opening tag of the `` with the following code snippet:
-
-```xml
-
-```
-
-We bound the `onSelect` method to the press event of the `` which means it will be executed when a user clicks on a book in our table. Notice how we prefixed a dot to the method name, which is a naming convention for custom methods that live in a controller. The dot will be omitted when interpreted by the framework.
-
-### 5. Add a new `` to the `app/webapp/view/App.view.xml`
-
-Now that we have a model that holds the data of the selected book, we can create a new area at the bottom of our page to display the description of the selected book.
-
-➡️ Add the following code to the `app/webapp/view/App.view.xml` right after the `
`:
-
-```xml
-
-
-
-
-
-
-```
-
-This is what our view now looks like (`
` collapsed in the screen shot):
-
-
-
-We added two `` controls to display the data as they make it very easy to align content vertically ("Column") or horizontally ("Row"). Inside the inner `` we added controls for the actual data (title and description text). We made use of the data binding concept again and bound our control to our newly created `userSelection` model (see step 3). We also assigned a predefined CSS class that adds a small margin at the top to the `` control.
-
-> In the previous three chapters you learned about Models, Views, and Controllers. This approach is also called the Model-view-controller concept (MVC) and is especially important in UI5 as well as web development in general. As a UI5 developer you should familiarize yourself with the concept as much as possible. You can learn more about it in the [SAPUI5 Documentation](https://sapui5.hana.ondemand.com/#/topic/91f233476f4d1014b6dd926db0e91070).
-
-### 6. Inspect our app in the browser
-
-➡️ Move over to the browser and refresh the page. Select any book to see its description:
-
-
-
-
-Further Questions to Discuss 🤔
-
-
-
-- How does the Model-view-controller (MVC) concept work and what makes it so useful?
-- How can you debug a UI5 based application?
-
-
-
-
-Continue to - [Chapter 4 - Adding an 'Order' Feature to Our Bookshop](/chapters/chapter04)
diff --git a/chapters/chapter04/chapter04-01.png b/chapters/chapter04/chapter04-01.png
deleted file mode 100644
index 1a8dfd7e..00000000
Binary files a/chapters/chapter04/chapter04-01.png and /dev/null differ
diff --git a/chapters/chapter04/chapter04-02.png b/chapters/chapter04/chapter04-02.png
deleted file mode 100644
index 99f0b1e7..00000000
Binary files a/chapters/chapter04/chapter04-02.png and /dev/null differ
diff --git a/chapters/chapter04/chapter04-result.png b/chapters/chapter04/chapter04-result.png
deleted file mode 100644
index e5c7d317..00000000
Binary files a/chapters/chapter04/chapter04-result.png and /dev/null differ
diff --git a/chapters/chapter04/readme.md b/chapters/chapter04/readme.md
deleted file mode 100644
index d33e07c8..00000000
--- a/chapters/chapter04/readme.md
+++ /dev/null
@@ -1,139 +0,0 @@
-# Chapter 4 - Adding an 'Order' Feature to Our Bookshop
-
-At the end of this chapter we will have added a new feature to our bookshop that enables users to order books.
-
-## Steps
-
-[1. Add input field and button to our `app/webapp/view/App.view.xml`](#1-add-input-field-and-button-to-our-appwebappviewappviewxml)
-[2. Modify the `userSelection` model](#2-modify-the-userselection-model)
-[3. Add a new `onSubmitOrder` method to our `app/webapp/controller/App.controller.js`](#3-add-a-new-onsubmitorder-method-to-our-appwebappcontrollerappcontrollerjs)
-[4. Reset `userSelection` model and `orderStatus` text](#4-reset-userselection-model-and-orderstatus-text)
-[5. Test the new feature](#5-test-the-new-feature)
-
-### 1. Add input field and button to our `app/webapp/view/App.view.xml`
-
-We need an input field for the order quantity as well as an order button for our new feature.
-
-➡️ Paste the following code snippet as the first child in the existing outer `` that we created in our `app/webapp/view/App.view.xml` in the previous chapter:
-
-```xml
-
-
-
-
-
-```
-
-This is what our view now looks like (`
` collapsed in the screen shot):
-
-
-
-We added a new `` with the attributes `justifyContent="End" alignItems="Center"` which makes sure all of its children will be centered vertically and displayed at the end of the box horizontally (which is the right side of the browser window in this case). We bound the `value` attribute of the `` to a new `selectedQuantity` property of the `userSelection` model, which we are going to define a default for in the next step. Our `` already triggers an `onSubmitOrder` method which we also have not yet defined.
-
-### 2. Modify the `userSelection` model
-
-We want to define a default value for the `selectedQuantity` property to our `userSelection` model.
-
-➡️ Replace the `onInit` method in the `app/webapp/controller/App.controller.js` with the following code snippet:
-
-```javascript
-onInit: function () {
- this.getView().setModel(new JSONModel({
- selectedQuantity: 1
- }), "userSelection")
-},
-```
-
-We added the `selectedQuantity` property to the `onInit` method where the `userSelection` model gets created in the first place and set the value to `1`. This makes the value the default when the view is initialized. If you are curious, you can already refresh the app in the browser and see the value `1` in the ``.
-
-### 3. Add a new `onSubmitOrder` method to our `app/webapp/controller/App.controller.js`
-
-We successfully set up our `userSelection` model so that we can take the data and send it to the server in the form of an order.
-
-➡️ Add the following code snippet to the `app/webapp/controller/App.controller.js` right after the `onSelect` method:
-
-```javascript
-,
-onSubmitOrder: function () {
- let oView = this.getView()
- let userSelectionData = oView.getModel("userSelection").getData()
-
- let reqSettings = {
- "url": "/browse/submitOrder",
- "method": "POST",
- "timeout": 0,
- "headers": {
- "Content-Type": "application/json"
- },
- "data": JSON.stringify({
- "book": userSelectionData.selectedItemData.ID,
- "quantity": userSelectionData.selectedQuantity
- }),
- }
-
- jQuery.ajax(reqSettings)
- .done(function (response) {
- oView.byId("orderStatus")
- .setText(
- `Order successful
- (${userSelectionData.selectedItemData.title},
- ${userSelectionData.selectedQuantity} pcs.)`
- )
- oView.byId("orderStatus").setState("Success")
-
- let userSelectedPath = oView.getModel("userSelection").getProperty("/selectedItemPath")
- oView.getModel().setProperty(userSelectedPath + "/stock", response.stock)
- oView.getModel("userSelection").setProperty("/selectedItemData/stock", response.stock)
- })
- .fail(function(response) {
- oView.byId("orderStatus").setText("Error")
- oView.byId("orderStatus").setState("Error")
- })
-}
-```
-
-We added a new `onSubmitOrder` method to our controller which we already bound to the press event of the order button in [step 1](/chapters/chapter04#1-add-input-field-and-button-to-our-appwebappviewappviewxml) of this chapter. The method gets the `userSelection` model and defines a new a new Ajax request, which is an easy-to-use technique for accessing web servers (our CAP backend) from a web page (our UI5 frontend) with asynchronous HTTP requests. Ajax is part of the popular jQuery library, which is already included in UI5. The request is sent to the `/browser/submitOrder` endpoint of our backend application. It handles the subtraction of the ordered books in the database for us. After the request is sent it can go one of two ways:
-1. **done ✅** : The request is successful, we get the new `stock` of the book in the response. In that case we display a success message in the `` control that we added in step 1 of this chapter and set its state to `Success`, which makes it turn green. We also update both our default model and our `userSelection` model with the new `stock`. Because of the data binding the stock in the table will then automatically be updated.
-1. **fail ❌** : The request was unsuccessful, we get an error message in response. In that case we display `Error` and set the state of the `` to `Error` as well, which makes it turn red.
-
-### 4. Reset `userSelection` model and `orderStatus` text
-
-The new order feature brought new complexity to our app. You might have noticed that the success or error message of an order does not disappear even if we select a new book. Also, the `selectedQuantity` does not reset, which makes is possible to enter a high quantity, then select a book with lower stock and submit an unsuccessful order. To prevent that from happening, we want to reset the `orderStatus` as well as the `selectedQuantity` when a book is selected.
-
-➡️ Paste the following code snippet at the end of the `onSelect` method in the `controller/App.controller.js`:
-
-```javascript
-oModel.setProperty("/selectedQuantity", 1)
-this.getView().byId("orderStatus").setText("")
-```
-
-This is what our controller looks like after all the changes:
-
-
-
-### 5. Test the new feature
-
-We can now test our new feature and order one or more books.
-
-➡️ Refresh the app. Play around with the `` control and try to set the quantity to be higher than the stock. Also try refreshing the page after you have submitted an order.
-
-You will notice that it's not possible to set the quantity to be higher than the stock, because we set the max value to be the stock in step 1 of this chapter. You will also notice that the data is persisted in the database after submitting an order. It will only be reset when the database is stopped.
-
-
-
-
-Further Questions to Discuss 🤔
-
-
-
-- What other options are there to send requests to the backend from your UI5 application?
-
-
-
-
-Continue to [Chapter 5 - Adding a 'Search' Feature to Our Bookshop](/chapters/chapter05)
diff --git a/chapters/chapter05/chapter05-01.png b/chapters/chapter05/chapter05-01.png
deleted file mode 100644
index 784e81b8..00000000
Binary files a/chapters/chapter05/chapter05-01.png and /dev/null differ
diff --git a/chapters/chapter05/chapter05-02.png b/chapters/chapter05/chapter05-02.png
deleted file mode 100644
index 4fb4a5bb..00000000
Binary files a/chapters/chapter05/chapter05-02.png and /dev/null differ
diff --git a/chapters/chapter05/chapter05-result.png b/chapters/chapter05/chapter05-result.png
deleted file mode 100644
index 1764be79..00000000
Binary files a/chapters/chapter05/chapter05-result.png and /dev/null differ
diff --git a/chapters/chapter05/readme.md b/chapters/chapter05/readme.md
deleted file mode 100644
index a1418cb5..00000000
--- a/chapters/chapter05/readme.md
+++ /dev/null
@@ -1,81 +0,0 @@
-# Chapter 5 - Adding a 'Search' Feature to Our Bookshop
-
-At the end of this chapter we will have added a new feature to our bookshop that enables users to search for books in the table.
-
-## Steps
-
-[1. Add a new `` to our `app/webapp/view/App.view.xml`](#1-add-a-new-searchfield--to-our-appwebappviewappviewxml)
-[2. Add a new `onSearch` method to our `app/webapp/controller/App.controller.js`](#2-add-a-new-onsearch-method-to-our-appwebappcontrollerappcontrollerjs)
-[3. Import `Filter` and `FilterOperator` in our `app/webapp/controller/App.controller.js`](#3-import-filter-and-filteroperator-in-our-appwebappcontrollerappcontrollerjs)
-[4. Test the new feature](#4-test-the-new-feature)
-
-### 1. Add a new `` to our `app/webapp/view/App.view.xml`
-
-➡️ Add the following code to the `app/webapp/view/App.view.xml` just above the `
` and add the new `id` to the `
`:
-
-```xml
-
-
-```
-
-This is what our view now looks like (a few controls collapsed in the screen shot):
-
-
-
-We added a new `` control to our view. It comes with a `liveChange` event that gets triggered on every keystroke the user submits in the field. We bound an `onSearch` method to that event which we will define in the next step. The great thing about the `liveChange` event is that the user doesn't have to actively click the search icon or hit enter to trigger the search.
-
-### 2. Add a new `onSearch` method to our `app/webapp/controller/App.controller.js`
-
-➡️ Add the following method to the `app/webapp/controller/App.controller.js`:
-
-```javascript
-,
-onSearch: function (oEvent) {
- var aFilter = [];
- var sQuery = oEvent.getParameter("newValue");
- if (sQuery) {
- aFilter.push(new Filter("title", FilterOperator.Contains, sQuery));
- }
- var oList = this.byId("booksTable");
- var oBinding = oList.getBinding("items");
- oBinding.filter(aFilter);
-
- let oModel = this.getView().getModel("userSelection")
- oModel.setProperty("/selectedItemPath", {})
- oModel.setProperty("/selectedItemData", {})
- this.getView().byId("orderStatus").setText("")
-}
-```
-
-### 3. Import `Filter` and `FilterOperator` in our `app/webapp/controller/App.controller.js`
-
-The new method uses the `Filter` and `FilterOperator` from the library. Make sure to import them from the library and pass the to the main function of the `app/webapp/controller/App.controller.js`.
-
-➡️ Replace the array defining the library imports as well the main function and it's imports at the top of the file with the following code snippet. Keep the content of the main function (the return statement with our controller methods):
-
-```javascript
-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) {
- //content of the function stays here
-}
-```
-
-This is what our controller now looks like (a few methods collapsed in the screen shot):
-
-
-
-We added a new `onSearch` method to our controller that gets the `newValue` from the `` (where it was triggered from) and filters for matching book titles in our `booksTable`. The `onSearch` method also resets the `userSelection` model as well as the `orderStatus` message to make sure the user cannot see the description or order status of a book that might not even by displayed in the table any longer after a new search is triggered.
-
-### 4. Test the new feature
-
-➡️ Refresh the app. Test the new feature and search for a book in the table.
-
-You'll notice how the search is instantly triggered after a keystroke is submitted in the ``:
-
-
-
-Continue to [Chapter 6 - Adding Custom Formatting](/chapters/chapter06):
diff --git a/chapters/chapter06/chapter06-01.png b/chapters/chapter06/chapter06-01.png
deleted file mode 100644
index b77e9500..00000000
Binary files a/chapters/chapter06/chapter06-01.png and /dev/null differ
diff --git a/chapters/chapter06/chapter06-02.png b/chapters/chapter06/chapter06-02.png
deleted file mode 100644
index 7e42848f..00000000
Binary files a/chapters/chapter06/chapter06-02.png and /dev/null differ
diff --git a/chapters/chapter06/chapter06-result.png b/chapters/chapter06/chapter06-result.png
deleted file mode 100644
index 99f7ba92..00000000
Binary files a/chapters/chapter06/chapter06-result.png and /dev/null differ
diff --git a/chapters/chapter06/readme.md b/chapters/chapter06/readme.md
deleted file mode 100644
index c2025199..00000000
--- a/chapters/chapter06/readme.md
+++ /dev/null
@@ -1,124 +0,0 @@
-# Chapter 6 - Adding Custom Formatting
-
-At the end of this chapter we will have added custom formatting to the `stock` column in our table as well as to the order `` and ``.
-
-## Steps
-
-[1. Replace the `` control for the `stock` with an ``](#1-replace-the-text--control-for-the-stock-with-an-objectstatus)
-[2. Add an `itemSelected` property to the `userSelection` model](#2-add-an-itemselected-property-to-the-userselection-model)
-[3. Add an `enabled` attribute to the order `` and ``](#3-add-an-enabled-attribute-to-the-order-button--and-stepinput)
-[4. Inspect and test the new formatting](#4-inspect-and-test-the-new-formatting)
-
-### 1. Replace the `` control for the `stock` with an ``
-
-➡️ Replace the `` control for the `stock` in the `` in our `app/webapp/view/App.view.xml` with the following control:
-
-```xml
-
-```
-
-This is what our view now looks like (a few controls collapsed in the screen shot):
-
-
-
-We replaced the `` control with an `` which allows us to set a `state` attribute. For the state we use a concept called ***formatting***. 'Formatting' means that we style our content based on conditions. The code for our `state` attribute looks complicated, because it is written as an inline if-statement, but translating it into pseudo code makes it a lot more readable:
-
-```text
-is the stock higher or equal to 20?
- if yes: set state to 'Success'
- if no: is the stock is higher than 0?
- if yes: set state to 'Warning'
- if no: set state to 'Error'
-```
-
-### 2. Add an `itemSelected` property to the `userSelection` model
-
-Let us also use formatting to enable and disable our order `` and `` based on whether a book is currently selected or not. For that, we can add a new property to our `userSelection` model.
-
-➡️ Add the following three code snippets to their respective places in the `app/webapp/controller/App.controller.js` (specified in the comments above):
-
-```javascript
-//in the onInit method where the userSelection model get instantiated, just before the selectedQuantity gets set to 1
-itemSelected: false,
-
-//at the very end of the onSelect method
-oModel.setProperty("/itemSelected", true)
-
-//at the very end of the onSearch method
-oModel.setProperty("/itemSelected", false)
-```
-
-This is what our controller now looks like after all the changes (`onSubmitOrder` method collapsed in the screen shot):
-
-
-
-We added a new `itemSelected` property to the `userSelection` model and made sure it gets updated accordingly when a user selects a book (set to `true`) or searches for another book (set to `false`).
-
-### 3. Add an `enabled` attribute to the order `` and ``
-
-The previous step laid the foundation for being able to enable and disable the order `` and `` control based on whether an item is selected or not.
-
-➡️ Replace the existing `` and `` control in our `app/webapp/view/App.view.xml` with the following controls:
-
-```xml
-
-
-```
-
-We added the `enabled` attribute to our `` and `` control making sure they are only enabled and clickable when the user has selected a book. We used the inline if-statement syntax for it again, which looks complicated at first but can easily be broken down into its individual pieces. This is the pseudo code for the `enabled` attribute of the ``:
-
-```text
-is an item selected?
- if yes: is the stock 0 or the selected quantity 0?
- if yes: set to false
- if not: set to true
- if not: set to false
-```
-
-In case you want to learn more about custom formatting in UI5: There is also a way to write a custom formatter in a separate JavaScript file and avoid the inline syntax. Learn more about that [here](https://sapui5.hana.ondemand.com/#/topic/0f8626ed7b7542ffaa44601828db20de).
-
-### 4. Inspect and test the new formatting
-
-➡️ Refresh the app. Inspect and test our new formatting.
-
-You will see that the `stock` is color coded based on the thresholds we defined. You will also see that the `` and `` controls are disabled by default if no book is selected. Try and set the `selectedQuantity` to match the `stock` of a book and submit an order. After that, the `` and `` control will be disabled based on our formatting, because no more books are available.
-
-
-
-
-Further Questions to Discuss 🤔
-
-
-
-- What are options are there to implement custom formatting in a UI5 application?
-
-
-
-
-Continue to [Chapter 7 - Adding i18n Features](/chapters/chapter07)
diff --git a/chapters/chapter07/chapter07-01.png b/chapters/chapter07/chapter07-01.png
deleted file mode 100644
index 3967a3a9..00000000
Binary files a/chapters/chapter07/chapter07-01.png and /dev/null differ
diff --git a/chapters/chapter07/chapter07-02.png b/chapters/chapter07/chapter07-02.png
deleted file mode 100644
index 407cdc39..00000000
Binary files a/chapters/chapter07/chapter07-02.png and /dev/null differ
diff --git a/chapters/chapter07/chapter07-result.png b/chapters/chapter07/chapter07-result.png
deleted file mode 100644
index 1a9ef992..00000000
Binary files a/chapters/chapter07/chapter07-result.png and /dev/null differ
diff --git a/chapters/chapter07/readme.md b/chapters/chapter07/readme.md
deleted file mode 100644
index 07ab441d..00000000
--- a/chapters/chapter07/readme.md
+++ /dev/null
@@ -1,141 +0,0 @@
-# Chapter 7 - Adding i18n Features
-
-At the end of this chapter we will have added internationalization (i18n) features to our UI5 app, so that it can be displayed in German (and other languages you want to add) as well.
-
-## Steps
-
-[1. Add an `i18n` model to our `app/webapp/manifest.json`](#1-add-an-i18n-model-to-our-appwebappmanifestjson)
-[2. Create a new file `app/webapp/i18n/i18n.properties`](#2-create-a-new-file-appwebappi18ni18nproperties)
-[3. Create a new file `app/webapp/i18n/i18n_de.properties`](#3-create-a-new-file-appwebappi18ni18ndeproperties)
-[4. Consume the `i18n` model in our `app/webapp/view/App.view.xml`](#4-consume-the-i18n-model-in-our-appwebappviewappviewxml)
-[5. Consume the `i18n` model in our `app/webapp/controller/App.controller.js`](#5-consume-the-i18n-model-in-our-appwebappcontrollerappcontrollerjs)
-[6. Test the app in another language](#6-test-the-app-in-another-language)
-
-> BTW: The abbreviation for 'internationalization' is 'i18n' because there are 18 letters between the 'i' and the 'n'. Internationalization is one of the main requirements for enterprise apps and one of the strengths of UI5.
-
-### 1. Add an `i18n` model to our `app/webapp/manifest.json`
-
-➡️ Add the following code snippet to the end of the `models` section of the `manifest.json`:
-
-```json
-,
-"i18n": {
- "type": "sap.ui.model.resource.ResourceModel",
- "settings": {
- "bundleName": "sap.codejam.i18n.i18n"
- }
-}
-```
-
-This is what our application descriptor now looks like:
-
-
-
-We added a new `i18n` model which is of type `ResourceModel` (special type for `i18n` models). It points to an `i18n` directory and file, which we are about to create next.
-
-### 2. Create a new file `app/webapp/i18n/i18n.properties`
-
-➡️ Create a new file `app/webapp/i18n/i18n.properties` and paste the following code into it:
-
-```properties
-Bookshop=Bookshop
-Book=Book
-Author=Author
-Genre=Genre
-Price=Price
-Stock=Stock
-Order=Order
-orderSuccessful=Order successful
-pieces=pcs.
-Error=Error
-```
-
-We created a new file that contains a few key value pairs for texts we want to use in our app. This allows us to simply reference the keys when consuming the model in our view instead of hardcoding the values. Because the keys as well as the values are in English it might look like this doesn't add much value, but you will see what this is about once we add some translations in the next step.
-
-### 3. Create a new file `app/webapp/i18n/i18n_de.properties`
-
-Create a new file `app/webapp/i18n/i18n_de.properties` and paste the following code into it:
-
-```properties
-Bookshop=Buchhandlung
-Book=Buch
-Author=Autor
-Genre=Genre
-Price=Preis
-Stock=Verfügbarkeit
-Order=Bestellen
-orderSuccessful=Bestellung erfolgreich
-pieces=Stk.
-Error=Fehler
-```
-
-This is what our project's structure now looks like:
-
-
-
-We added a new file that contains the German translations for the texts we want to use in our app. This makes it possible for our app to be displayed in German as well. You can add support for other languages as well if you want. Simply add a language code (for example `de`) prefixed with an underscore to the file name (like we did with `i18n_de.properties`).
-
-### 4. Consume the `i18n` model in our `app/webapp/view/App.view.xml`
-
-We can now consume the `i18n` model in our `app/webapp/view/App.view.xml` using the keys. The app will then display their respective values based on the language preferences of the user.
-
-➡️ Replace all hardcoded texts in our view with the data binding syntax for the `i18n` model shown in the example below. Go through the whole file to make sure not to miss any texts:
-
-```xml
-
-```
-
-### 5. Consume the `i18n` model in our `app/webapp/controller/App.controller.js`
-
-We not only want to consume the `i18n` model in our view but also in our `app/webapp/controller/App.controller.js`. We previously hardcoded the text for the `orderStatus` and now want to use the `i18n` model instead.
-
-➡️ Replace the `jQuery.ajax` request including the `done` and `fail` methods with the following code:
-
-```javascript
-let i18nModel = oView.getModel("i18n")
-jQuery.ajax(reqSettings)
- .done(function (response) {
- console.log(response)
- oView.byId("orderStatus")
- .setText(
- `${i18nModel.getProperty("orderSuccessful")}
- (${userSelectionData.selectedItemData.title},
- ${userSelectionData.selectedQuantity}
- ${i18nModel.getProperty("pieces")})`
- )
- oView.byId("orderStatus").setState("Success")
-
- let userSelectedPath = oView.getModel("userSelection").getProperty("/selectedItemPath")
- oView.getModel().setProperty(userSelectedPath + "/stock", response.stock)
- oView.getModel("userSelection").setProperty("/selectedItemData/stock", response.stock)
- })
- .fail(function(response) {
- console.log(response)
- oView.byId("orderStatus").setText(`${i18nModel.getProperty("Error")}`)
- oView.byId("orderStatus").setState("Error")
- })
-```
-
-We added the `i18n` model to our controller and used it when setting the text for the `orderStatus`.
-
-### 6. Test the app in another language
-
-We can now test our app in another language.
-
-➡️ Reload the app and attach a query parameter to the URL of the app to specify which language you want the bookshop to be displayed in. Attach `?sap-ui-language=de` to the URL to view the app in German for example.
-
-Feel free to try other language codes in case you provided translation files for other languages. You can learn more about languages in UI5 and other options to set them in the [SAPUI5 Documentation](https://sapui5.hana.ondemand.com/#/topic/91f21f176f4d1014b6dd926db0e91070).
-
-
-
-
-Further Questions to Discuss 🤔
-
-
-
-- Why are the i18n capabilities of UI5 considered one of its strength? What makes them so powerful?
-
-
-
-
-Continue to [Chapter 8 - Adding Custom CSS](/chapters/chapter08)
diff --git a/chapters/chapter08/chapter08-01.png b/chapters/chapter08/chapter08-01.png
deleted file mode 100644
index 00e28793..00000000
Binary files a/chapters/chapter08/chapter08-01.png and /dev/null differ
diff --git a/chapters/chapter08/chapter08-02.png b/chapters/chapter08/chapter08-02.png
deleted file mode 100644
index 2671ad8c..00000000
Binary files a/chapters/chapter08/chapter08-02.png and /dev/null differ
diff --git a/chapters/chapter08/chapter08-result.png b/chapters/chapter08/chapter08-result.png
deleted file mode 100644
index 567b5d0e..00000000
Binary files a/chapters/chapter08/chapter08-result.png and /dev/null differ
diff --git a/chapters/chapter08/readme.md b/chapters/chapter08/readme.md
deleted file mode 100644
index 1c644ddb..00000000
--- a/chapters/chapter08/readme.md
+++ /dev/null
@@ -1,82 +0,0 @@
-# Chapter 8 - Adding Custom CSS
-
-At the end of this chapter we will have added custom CSS to our UI5 app that makes sure that our `` containing the ``, `Button`, and `` is styled properly.
-
-## Steps
-
-[1. Add new `resource` to our `app/webapp/manifest.json`](#1-add-new-resource-to-our-appwebappmanifestjson)
-[2. Create a new `app/webapp/css/style.css` file](#2-create-a-new-appwebappcssstylecss-file)
-[3. Use a custom CSS class in our `app/webapp/view/App.view.xml`](#3-use-a-custom-css-class-in-our-appwebappviewappviewxml)
-[4. Inspect the new styling](#4-inspect-the-new-styling)
-
-### 1. Add new `resource` to our `app/webapp/manifest.json`
-
-➡️ Add the following code snippet to the `sap.ui5` section of the `app/webapp/manifest.json`:
-
-```json
-,
-"resources": {
- "css": [
- {
- "uri": "css/style.css"
- }
- ]
-}
-```
-
-This is what our application descriptor now looks like:
-
-
-
-We added a new `css` resource to our application descriptor and pointed it to a css file that we are about to create next.
-
-### 2. Create a new `app/webapp/css/style.css` file
-
-➡️ Create a new file `app/webapp/css/style.css` and paste the following code into it:
-
-```css
-.orderControls {
- gap: 20px;
-}
-```
-
-We created a new css file and added a css class to it. The css class uses the `gap` property which makes sure that all items inside a container that is styled with `display = flex` (like the `` control) have the specified gap between each other.
-
-### 3. Use a custom CSS class in our `app/webapp/view/App.view.xml`
-
-We can now apply the css class `orderControls` to our `app/webapp/view/App.view.xml`.
-
-➡️ Add the class to the `` containing the ``, `Button`, and ``, so it looks like this:
-
-```xml
-
-
-
-
-
-```
-
-This is what our view now looks like (a few controls collapsed in the screen shot):
-
-
-
-### 4. Inspect the new styling
-
-➡️ Refresh the app and inspect the new styling.
-
-You will see that all items inside the `` that we applied the new class to now have a gap of 20 pixels between each other.
-
-
-
-
-Further Questions to Discuss 🤔
-
-
-
-- What guidelines are there when it comes to custom CSS in UI5?
-- What other themes exist for Fiori based application? What is the newest one? How can you change the theme of a UI5 application?
-
-
-
-
-Continue to [Chapter 9 - Deploying Our App](/chapters/chapter09)
\ No newline at end of file
diff --git a/chapters/chapter09/chapter09-01.png b/chapters/chapter09/chapter09-01.png
deleted file mode 100644
index 1e0ec1de..00000000
Binary files a/chapters/chapter09/chapter09-01.png and /dev/null differ
diff --git a/chapters/chapter09/chapter09-result1.png b/chapters/chapter09/chapter09-result1.png
deleted file mode 100644
index a77b13cc..00000000
Binary files a/chapters/chapter09/chapter09-result1.png and /dev/null differ
diff --git a/chapters/chapter09/chapter09-result2.png b/chapters/chapter09/chapter09-result2.png
deleted file mode 100644
index a4220410..00000000
Binary files a/chapters/chapter09/chapter09-result2.png and /dev/null differ
diff --git a/chapters/chapter09/readme.md b/chapters/chapter09/readme.md
deleted file mode 100644
index fbd6a3d0..00000000
--- a/chapters/chapter09/readme.md
+++ /dev/null
@@ -1,222 +0,0 @@
-# Chapter 9 - Deploying Our App
-
-Please note: This step is optional and not specific to UI5, but rather covers the basics of how to use the [SAP Approuter](https://www.npmjs.com/package/@sap/approuter), the [Cloud MTA Build Tool](https://sap.github.io/cloud-mta-build-tool/), and the [Cloud Foundry Command Line Interface](https://docs.cloudfoundry.org/cf-cli/).
-
-## Prerequisites
-
-To be able to complete this chapter you need to fulfill ***all*** of the following requirements:
-- You need to have an account on the SAP Business Technology platform (SAP BTP) with the Cloud Foundry Environment enabled. Learn more about how to get an account [here](https://developers.sap.com/group.btp-setup.html). You also need at least 2 units of Cloud Foundry Runtime assigned to your subaccount and available to consume.
-- You need to have the [Cloud Foundry Command Line Interface](https://docs.cloudfoundry.org/cf-cli/) installed in your development environment. If you work in the SAP Business Application Studio or in a devcontainer using the [provided configuration](/.devcontainer) this is already taken care of. You can verify your installation by running `cf -v`.
-
-## Deploying Our App
-
-By the end of this chapter, we will have deployed our UI5 to the Cloud Foundry Environment on the SAP BTP. To keep it as simple as possible, we will not deploy the backend application of this project that we used for local development, but consume a remote service instead.
-
-## Steps
-
-[1. Wrap the UI5 app into a Node.js based application using the `@sap/approuter` package](#1-wrap-the-ui5-app-into-a-nodejs-based-application-using-the-sapapprouter-package)
-[2. Define routes for the approuter in an `app/xs-app.json` file](#2-define-routes-for-the-approuter-in-an-appxs-appjson-file)
-[3. Create a new `app/remote-destination.json` file to define a destination](#3-create-a-new-appremote-destinationjson-file-to-define-a-destination)
-[4. Create an `app/mta.yaml` file to describe the build process](#4-create-an-appmtayaml-file-to-describe-the-build-process)
-[5. Navigate into the `app` directory](#5-navigate-into-the-app-directory)
-[6. Install the dependencies](#6-install-the-dependencies)
-[7. Build the multitarget application](#7-build-the-multitarget-application)
-[8. Log in to the Cloud Foundry Environment](#8-log-in-to-the-cloud-foundry-environment)
-[9. Deploy the multitarget application](#9-deploy-the-multitarget-application)
-[10. Test the bookshop in the cloud](#10-test-the-bookshop-in-the-cloud)
-
-### 1. Wrap the UI5 app into a Node.js based application using the `@sap/approuter` package
-
-We want to wrap our UI5 app into a Node.js based application that uses the `@sap/approuter` package, which allows us to handle incoming and outgoing requests to and from our app. The approuter will be the entry point to our app. You can learn more about the SAP Approuter [here](https://blogs.sap.com/2020/04/03/sap-application-router/).
-
-➡️ Create a new file `app/package.json` and paste the following code into it:
-
-```json
-{
- "name": "bookshop-approuter",
- "dependencies": {
- "@sap/approuter": "^10"
- },
- "devDependencies": {
- "mbt": "^1.2.7"
- },
- "scripts": {
- "start": "node node_modules/@sap/approuter/approuter.js",
- "build": "mbt build"
- }
-}
-```
-
-We initialized a Node.js based application inside our `app` directory. It uses the `@sap/approuter` package which makes it a so called 'standalone approuter'.
-
-### 2. Define routes for the approuter in an `app/xs-app.json` file
-
-An approuter requires an `xs-app.json` file defining all the routes it should handle.
-
-➡️ Create a new file `app/xs-app.json` and paste the following content into it:
-
-```json
-{
- "welcomeFile": "index.html",
- "authenticationMethod": "none",
- "routes": [
- {
- "source": "/browse(.*)",
- "destination": "browse-bookshop",
- "authenticationType": "none"
- },
- {
- "source": "^(.*)$",
- "target": "$1",
- "authenticationType": "none",
- "localDir": "webapp/"
- }
- ]
-}
-```
-
-We defined all routes for our approuter. Each route has a source (based on a regular expression) and additional properties that define it. The most important route is the second one (`^(.*)$`), which states that all incoming requests will be proxied to our local `webapp/` directory. This in combination with `index.html` as our `welcomeFile` makes users get to the UI5 app when accessing the approuter. The `/browse(.*)` route makes sure that requests to the backend (see the `capBooks` data source in our `app/webapp/manifest.json`) will be proxied to a destination called `browse-bookshop`. Let us define this destination in the next step.
-
-### 3. Create a new `app/remote-destination.json` file to define a destination
-
-➡️ Create a new `app/remote-destination.json` file and paste the following code into it:
-
-```json
-{
- "init_data": {
- "instance": {
- "existing_destinations_policy": "update",
- "destinations": [
- {
- "Name": "browse-bookshop",
- "Authentication": "NoAuthentication",
- "ProxyType": "Internet",
- "Type": "HTTP",
- "URL": "https://cf-ic2022-ll-supporters-bookshop-srv.cfapps.eu10-004.hana.ondemand.com"
- }
- ]
- }
- }
-}
-```
-
-We defined a destination named `browse-bookshop` that we already referenced in our `app/xs-app.json` and that points to the remote service we want to consume.
-
-### 4. Create an `app/mta.yaml` file to describe the build process
-
-We can now put all the pieces together.
-
-➡️ Create an `app/mta.yaml` file and paste the following code into it (make sure the indentation is 100% correct as `.yaml` files are very strict in that regard):
-
-```yaml
-_schema-version: '3.1'
-ID: bookshop-approuter
-version: 1.0.0
-parameters:
- enable-parallel-deployments: true
-
-build-parameters:
- before-all:
- - builder: custom
- commands:
- - npm install --production
-
-modules:
- # ---------------------------------
- - name: bookshop-approuter
- # ---------------------------------
- type: approuter.nodejs
- path: .
- requires:
- - name: bookshop-destination
-
-resources:
- # ----------------------------------
- - name: bookshop-destination
- # ----------------------------------
- type: org.cloudfoundry.managed-service
- parameters:
- service-plan: lite
- service: destination
- path: ./remote-destination.json
-```
-
-We described the build process for our multitarget application. Our UI5 app is now part of a 'multitarget application' because we not only want to deploy the approuter module (proxying requests to our UI5 app), but also an instance of the destination service in Cloud Foundry. This instance is listed under `resources` and points to the `app/remote-destination.json` file defining the destination. The `bookshop-approuter` module `requires` this particular resource, which makes sure they will be bound during deployment.
-
-This is what our project's structure now looks like:
-
-
-
-Don't worry about the error you might receive which says that certain dependencies are not yet installed. We will take care of that in the next steps.
-
-### 5. Navigate into the `app` directory
-
-➡️ Open a new terminal sessions and execute the following command to navigate into the `app` directory:
-
-```bash
-cd app
-```
-
-### 6. Install the dependencies
-
-➡️ Run the following command in the same terminal session to install the dependencies that are declared in the `app/package.json`:
-
-```bash
-npm install
-```
-
-### 7. Build the multitarget application
-
-➡️ Execute the following command in the same terminal session to trigger the build:
-
-```bash
-npm run build
-```
-
-We executed the `npm run build` script that is defined as `mbt build` in the `app/package.json`. The `mbt build` command refers to the [Cloud MTA Build Tool](https://sap.github.io/cloud-mta-build-tool/), which is the corresponding build tool for `mta.yaml` files. The build process may take some time, but once it's done, we can see a new `app/mta_archives/` directory has been created with an `.mtar` file inside. This is the file we will later deploy to Cloud Foundry.
-
-### 8. Log in to the Cloud Foundry Environment
-
-We can now log in to the Cloud Foundry environment on the SAP Business Technology Platform.
-
-➡️ Execute the following command in a terminal session and enter your API endpoint (you can find it in the SAP BTP Cockpit in the subaccount overview) and credentials when prompted. Also select the Cloud Foundry organization and space you want to deploy the project:
-
-```bash
-cf login
-```
-
-### 9. Deploy the multitarget application
-
-We can now deploy the project.
-
-➡️ Execute the following command in the same terminal session you previously triggered the build from:
-
-```bash
-cf deploy mta_archives/bookshop_1.0.0.mtar
-```
-
-The deployment may take a while. This is nothing to worry about.
-
-### 10. Test the bookshop in the cloud
-
-If the build was successful and finished with no errors, we can test our bookshop app in the cloud.
-
-➡️ Go into the SAP BTP Cockpit and navigate to the space you deployed the project into. Click on `bookshop-approuter` and open the route displayed at the top. You should see your bookshop running in the cloud.
-
-
-
-
-We successfully deployed our UI5 app to the Cloud Foundry Environment as part of a multitarget application. It consumes a remote service as the data source when running in the cloud (during local development we used a local backend application), so don't be surprised if the stock amounts suddenly decrease. Someone else might be using the same remote service at the same time.
-
-
-Further Questions to Discuss 🤔
-
-
-
-- What other options are there to deploy UI5 applications to the SAP Business Technology Platform?
-- We used a standalone approuter to deploy the application. What is the difference between this standalone approuter and a managed approuter?
-
-
-
-
-Continue to [Chapter 10 - Further Improvements and Learning Material](/chapters/chapter10)
\ No newline at end of file
diff --git a/chapters/chapter10/readme.md b/chapters/chapter10/readme.md
deleted file mode 100644
index 22f5f2a7..00000000
--- a/chapters/chapter10/readme.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Chapter 10 - Further Improvements and Learning Material
-
-## Further Improvements
-
-If you followed the steps described in this repository successfully you have built a fully functional UI5 app that makes use of most core principles of the framework. But of course the material did not cover everything UI5 has to offer. There is a lot more you can do with UI5 and a lot of features you could add to take your bookshop app to the next level. Here are a few ideas, but feel free to improve the app in whatever way you would like to:
-- You could split the view (`app/webapp/view/App.view.xml`) up into multiple views using a concept called [nested views](https://sapui5.hana.ondemand.com/#/topic/df8c9c3d79b54c928855162bafcd88ee), making its structure easier to read and understand.
-
-- You could replace the `` message with a pop-up `` and turn it into a reusable [fragment](https://sapui5.hana.ondemand.com/#/topic/4da72985139b4b83b5f1c1e0c0d2ed5a).
-
-- You could add more pages to your app and split its content up. At this stage, the bookshop's complexity doesn't necessarily require [routing and navigation](https://sapui5.hana.ondemand.com/#/topic/e5200ee755f344c8aef8efcbab3308fb), but it's certainly a good feature to know how to implement.
-
-- You could run the UI5 app on its own webserver during development (instead of using the `@sap/cds` server) leveraging the [UI5 Tooling](https://sap.github.io/ui5-tooling/). This enables you to use a lot of great open source tools and packages. You could for example try consuming a remote service in your app using a [package that mocks Cloud Foundry destination locally](https://www.npmjs.com/package/ui5-middleware-cfdestination).
-
-- The UI5 Tooling also comes with a useful build command that you could use to prepare your app for deployment.
-
-## Further Learning Material
-
-Here are some further learning recommendations in case you are looking for more structured content to follow:
-
-- The [UI5 Walkthrough](https://sapui5.hana.ondemand.com/#/topic/3da5f4be63264db99f2e5b04c5e853db) takes you on a tour through all the development paradigms of UI5. It's a great resource to start with as it covers everything from the most basic steps to advanced functionalities.
-
-- The video series [2 Minutes of SAPUI5](https://www.youtube.com/watch?v=J9NMwsipMkw&list=PL6RpkC85SLQC4kuj22e4hw85Sa1pClD8y) is inspired by UI5 Walkthrough, but introduces the fundamentals of UI% in short and easy to digest videos and also covers additional topics such as authorization and deployment.
-
-- There is a great [openSAP course on SAPUI5](https://open.sap.com/courses/ui52).
\ No newline at end of file
diff --git a/instructions.md b/instructions.md
new file mode 100644
index 00000000..57ff7cdf
--- /dev/null
+++ b/instructions.md
@@ -0,0 +1,196 @@
+## Step 0 - Baseline
+
+The baseline UI5 project runs on the @sap/cds server and does not use any bundling tool. It has a few flaws that need to be addressed in order to improve its performance.
+
+🚨➡️ Lighthouse score ~40
+
+## Step 1 - Introduce UI5 Tooling
+
+We restructure the UI5 project so we can introduce the UI5 Tooling, which we will later use to build the project.
+
+1. Add a new directory `performance-webapp/`.
+1. Add a new directory `performance-webapp/webapp/` to follow standard UI5 structure.
+1. Run `npm init -y` in `performance-webapp/` to initialize a Node.js project.
+1. Run `npm install @ui5/cli --save-dev` to add the UI5 Tooling as a dependency.
+1. Run `npm install @ui5/cli --global` to make the `ui5` command globally available.
+1. Run `ui5 init` to initialize the UI5 project (or make the UI5 Tooling aware of the existing project in this case).
+1. Copy all contents from `finished-webapp/` into `performance-webapp/webapp/`.
+1. Run `ui5 use OpenUI5 && ui5 add sap.ui.core sap.m themelib_sap_horizon` to add libraries to the `ui5.yaml`.
+1. If we where to run the application via `ui5 serve` now, we wouldn't get any data, as the project now runs independently from the CAP-based backend. Let's add some data to `init` method of the `Component.js` file manually, so backend connectivity will not be a factor when measuring performance:
+ ```js
+ sap.ui.define([
+ "sap/ui/core/UIComponent",
+ "sap/ui/model/json/JSONModel"
+ ], function (UIComponent, JSONModel) {
+ "use strict"
+ return UIComponent.extend(
+ "sap.codejam.Component", {
+ metadata : {
+ "interfaces": [
+ "sap.ui.core.IAsyncContentCreation"
+ ],
+ manifest: "json"
+ },
+ init : function () {
+ this.setModel(new JSONModel({"@odata.context":"$metadata#Books(genre())","value":[{"createdAt":"2022-10-18T12:25:17.907Z","modifiedAt":"2022-10-18T12:25:17.907Z","ID":201,"title":"Wuthering Heights","descr":"Wuthering Heights, Emily Brontë's only novel, was published in 1847 under the pseudonym \"Ellis Bell\". It was written between October 1845 and June 1846. Wuthering Heights and Anne Brontë's Agnes Grey were accepted by publisher Thomas Newby before the success of their sister Charlotte's novel Jane Eyre. After Emily's death, Charlotte edited the manuscript of Wuthering Heights and arranged for the edited version to be published as a posthumous second edition in 1850.","author":"Emily Brontë","genre_ID":11,"stock":12,"price":11.11,"currency_code":"GBP","image@odata.mediaContentType":"image/png","genre":{"name":"Drama","descr":null,"ID":11,"parent_ID":10}},{"createdAt":"2022-10-18T12:25:17.907Z","modifiedAt":"2022-10-18T12:25:17.907Z","ID":207,"title":"Jane Eyre","descr":"Jane Eyre /ɛər/ (originally published as Jane Eyre: An Autobiography) is a novel by English writer Charlotte Brontë, published under the pen name \"Currer Bell\", on 16 October 1847, by Smith, Elder & Co. of London. The first American edition was published the following year by Harper & Brothers of New York. Primarily a bildungsroman, Jane Eyre follows the experiences of its eponymous heroine, including her growth to adulthood and her love for Mr. Rochester, the brooding master of Thornfield Hall. The novel revolutionised prose fiction in that the focus on Jane's moral and spiritual development is told through an intimate, first-person narrative, where actions and events are coloured by a psychological intensity. The book contains elements of social criticism, with a strong sense of Christian morality at its core and is considered by many to be ahead of its time because of Jane's individualistic character and how the novel approaches the topics of class, sexuality, religion and feminism.","author":"Charlotte Brontë","genre_ID":11,"stock":11,"price":12.34,"currency_code":"GBP","image@odata.mediaContentType":"image/png","genre":{"name":"Drama","descr":null,"ID":11,"parent_ID":10}},{"createdAt":"2022-10-18T12:25:17.907Z","modifiedAt":"2022-10-18T12:25:17.907Z","ID":251,"title":"The Raven","descr":"\"The Raven\" is a narrative poem by American writer Edgar Allan Poe. First published in January 1845, the poem is often noted for its musicality, stylized language, and supernatural atmosphere. It tells of a talking raven's mysterious visit to a distraught lover, tracing the man's slow fall into madness. The lover, often identified as being a student, is lamenting the loss of his love, Lenore. Sitting on a bust of Pallas, the raven seems to further distress the protagonist with its constant repetition of the word \"Nevermore\". The poem makes use of folk, mythological, religious, and classical references.","author":"Edgar Allen Poe","genre_ID":16,"stock":333,"price":13.13,"currency_code":"USD","image@odata.mediaContentType":"image/png","genre":{"name":"Mystery","descr":null,"ID":16,"parent_ID":10}},{"createdAt":"2022-10-18T12:25:17.907Z","modifiedAt":"2022-10-18T12:25:17.907Z","ID":252,"title":"Eleonora","descr":"\"Eleonora\" is a short story by Edgar Allan Poe, first published in 1842 in Philadelphia in the literary annual The Gift. It is often regarded as somewhat autobiographical and has a relatively \"happy\" ending.","author":"Edgar Allen Poe","genre_ID":16,"stock":555,"price":14,"currency_code":"USD","image@odata.mediaContentType":"image/png","genre":{"name":"Mystery","descr":null,"ID":16,"parent_ID":10}},{"createdAt":"2022-10-18T12:25:17.907Z","modifiedAt":"2022-10-18T12:25:17.907Z","ID":271,"title":"Catweazle","descr":"Catweazle is a British fantasy television series, starring Geoffrey Bayldon in the title role, and created by Richard Carpenter for London Weekend Television. The first series, produced and directed by Quentin Lawrence, was screened in the UK on ITV in 1970. The second series, directed by David Reid and David Lane, was shown in 1971. Each series had thirteen episodes, most but not all written by Carpenter, who also published two books based on the scripts.","author":"Richard Carpenter","genre_ID":13,"stock":22,"price":150,"currency_code":"JPY","image@odata.mediaContentType":"image/png","genre":{"name":"Fantasy","descr":null,"ID":13,"parent_ID":10}}]}))
+ UIComponent.prototype.init.apply(
+ this,
+ arguments
+ )
+ }
+ })
+ })
+ ```
+1. Run `ui5 serve` to start the application.
+
+ 🚨➡️ Lighthouse score ~30
+
+1. We can now experiment a little and bootstrap UI5 from `resources/sap-ui-core.js` ("dev-mode") instead of the content delivery network (CDN) in `index.html`. This demonstrates the negative effects synchronous requests have compared to asynchronous requests.
+
+ 🚨➡️ Lighthouse score ~20
+
+We restructured the project and introduced the UI5 Tooling. We are currently running the application in dev mode, with a lot of synchronous requests. We do this because we want to show a big performance delta throughout this session. If we were to use the CDN instead the requests would be asynchronous and we would get a `library-preload.js` file by default - both factors improve performance.
+
+## Step 2 - Build the Project
+
+To improve the applications performance we can create an optimized build using the UI5 Tooling.
+
+1. Run `ui5 build --all` to build the project and include all project dependencies.
+1. Run `npm install serve --save-dev` to be able to serve the `dist/` directory.
+1. Add new start script to the `package.json`:
+ ```json
+ "start:dist": "serve dist"
+ ```
+1. Run `npm run start:dist` to start the application.
+
+ 🚨➡️ Lighthouse score ~45
+
+Inspecting the network trace of our application shows that we now have a `library-preload.js`, a `Component-preload.js` file as well as asynchronous requests, which drastically improves performance.
+
+## Step 3 - Build the project (self-contained)
+
+To further improve the performance of the application we can create a self-contained build using the UI5 Tooling.
+
+1. Run `ui5 build self-contained --all`.
+1. Run `npm run start:dist` to start the application.
+
+ 🚨➡️ Lighthouse score ~52
+
+## Step 4 - Eliminate 404's and Unnecessary requests
+
+Requests that result in a `404 - Not Found` status are costly when it comes to performance. The network trace and browser console show failed requests to non-existing translation files and backends. Those requests should be avoided whenever possible.
+
+1. Specify all supported locales in the `manifest.json` (the ones we have translation files for):
+ ```json
+ "models": {
+ "i18n": {
+ "type": "sap.ui.model.resource.ResourceModel",
+ "settings": {
+ "bundleName": "sap.codejam.i18n.i18n",
+ "supportedLocales": ["en", "de"],
+ "fallbackLocale": "en"
+ }
+ }
+ }
+ ```
+1. Rename `i18n.properties` to `i18n_en.properties` to match the `manifest.json`.
+1. Delete the data source and default model that we no longer use in the `manifest.json`.
+1. In the network trace we can see that a `Component-preload.js` as well as a `Component.js` are being loaded, which is unnecessary. This is because the `sap.app.id` in the `manifest.json` does not match the `data-sap-ui-resourceroots` in the `index.html`. Change the `sap.app.id` in the `manifest.json` to `sap.codejam` (previously `codejam`).
+1. Fine tuning: We can add dummy dependencies to the `Component.js` so that these (large) files are not requested separately:
+ ```js
+ ,
+ "sap/ui/core/date/Gregorian",
+ "sap/ui/core/ComponentSupport"
+ ```
+1. Fine tuning: We can remove the 👋-emoji in the `App.view.xml` to avoid loading (unnecessary) additional fonts.
+
+ 🚨➡️ Lighthouse score ~64
+
+## Step 5 - Add Splash Screen
+
+To further improve the (perceived) performance of our application we can add a splash screen that is displayed while the application is loading.
+
+1. We have to link to the css file (that will hold the splash screen) at the top of the html `` and add custom `boot.js` script in the `index.html`. Replace the whole content of the `index.html` with the following code:
+ ```html
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ```
+1. Add the `boot.js` file that holds the bootstrap configuration for our application:
+ ```js
+ // configuration object
+ window["sap-ui-config"] = {
+ "compatVersion": "edge",
+ "async": true,
+ "language": "en",
+ "resourceroots": {
+ "sap.codejam": "./"
+ },
+ "onInit": "module:sap/ui/core/ComponentSupport",
+ "theme": (function () {
+ // determine the proper theme for UI5 from current color scheme
+ try {
+ return window.matchMedia("(prefers-color-scheme: dark)").matches ? "sap_horizon_dark" : "sap_horizon";
+ } catch (ex) {
+ console.warn("window.matchMedia not supported - keep default theme");
+ return "sap_horizon";
+ }
+ })()
+ };
+ ```
+1. Remove the css resource in the `manifest.json` as it is now linked in the `index.html` directly.
+1. Add the splash screen to `performance-webapp/css/styles.css`:
+ ```css
+ html, body {
+ width: 100%;
+ height: 100%;
+ }
+
+ body.loading {
+ background-size: 20rem !important;
+ background-position: center;
+ background-repeat: no-repeat;
+ overflow: hidden;
+ }
+ body.loading > * {
+ visibility: hidden;
+ }
+
+ @media (prefers-color-scheme: light) {
+ body.loading {
+ background-color: white;
+ background-image: url("data:image/svg+xml;utf8,");
+ }
+ }
+ @media (prefers-color-scheme: dark) {
+ body.loading {
+ background-color: black;
+ background-image: url("data:image/svg+xml;utf8,");
+ }
+ }
+ ```
+1. Add the `onAfterRendering` method to the `Component.js` that will make sure the splash screen disappears after the UI5 application is ready.
+ ```js
+ ,
+ onAfterRendering: function() {
+ document.body.classList.remove("loading");
+ }
+ ```
+
+ 🚨➡️ Lighthouse score ~72
\ No newline at end of file
diff --git a/thumbnail.png b/thumbnail.png
new file mode 100644
index 00000000..12f2c460
Binary files /dev/null and b/thumbnail.png differ