diff --git a/README.md b/README.md index 8a777c0f..52351cc5 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Please edit this file as it is the primary description file for your project. Yo # OpenUI5 TypeScript Walkthrough -In this tutorial we'll introduce you to all major development paradigms of OpenUI5. We'll demonstrate the use of TypeScript with OpenUI5 and highlight the specific characteristics of this approach. +In this tutorial we'll introduce you to all major development paradigms of OpenUI5.
This section is relevant for TypeScript onlyWe'll also demonstrate the use of TypeScript with OpenUI5 and highlight the specific characteristics of this approach.
## Description @@ -39,54 +39,56 @@ We first introduce you to the basic development paradigms like *Model-View-Contr The tutorial consists of the following steps. To start, just open the first link - you`ll be guided from there. -- **[Step 1: Hello World!](steps/01/README.md "As you know OpenUI5 is all about HTML5. Let’s get started with building a first "Hello World" with only HTML.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/01/index.html) \| [πŸ“₯ Download Solution in TS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-01.zip) \| [πŸ“₯ Download Solution in JS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-01-js.zip)) -- **[Step 2: Bootstrap](steps/02/README.md "Before we can do something with OpenUI5, we need to load and initialize it. This process of loading and initializing OpenUI5 is called bootstrapping. Once this bootstrapping is finished, we simply display an alert.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/02/index-cdn.html) \| [πŸ“₯ Download Solution in TS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-02.zip) \| [πŸ“₯ Download Solution in JS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-02-js.zip)) -- **[Step 3: Controls](steps/03/README.md "Now it is time to build our first little UI by replacing the "Hello World" text in the HTML body by the OpenUI5 control sap/m/Text. In the beginning, we will use the JavaScript control interface to set up the UI, the control instance is then placed into the HTML body. ")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/03/index-cdn.html) \| [πŸ“₯ Download Solution in TS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-03.zip) \| [πŸ“₯ Download Solution in JS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-03-js.zip)) -- **[Step 4: XML Views](steps/04/README.md "Putting all our UI into the index.ts file will very soon result in a messy setup, and there is quite a bit of work ahead of us. So let’s do a first modularization by putting the sap/m/Text control into a dedicated view.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/04/index-cdn.html) \| [πŸ“₯ Download Solution in TS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-04.zip) \| [πŸ“₯ Download Solution in JS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-04-js.zip)) -- **[Step 5: Controllers](steps/05/README.md "In this step, we replace the text with a button and show the "Hello World" message when the button is pressed. The handling of the button's press event is implemented in the controller of the view.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/05/index-cdn.html) \| [πŸ“₯ Download Solution in TS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-05.zip) \| [πŸ“₯ Download Solution in JS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-05-js.zip)) -- **[Step 6: Modules](steps/06/README.md "In OpenUI5, resources are often referred to as modules. In this step, we replace the alert from the last exercise with a proper Message Toast from the sap.m library.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/06/index-cdn.html) \| [πŸ“₯ Download Solution in TS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-06.zip) \| [πŸ“₯ Download Solution in JS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-06-js.zip)) -- **[Step 7: JSON Model](steps/07/README.md "Now that we have set up the view and controller, it’s about time to think about the M in MVC.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/07/index-cdn.html) \| [πŸ“₯ Download Solution in TS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-07.zip) \| [πŸ“₯ Download Solution in JS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-07-js.zip)) -- **[Step 8: Translatable Texts](steps/08/README.md "In this step we move the texts of our UI to a separate resource file.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/08/index-cdn.html) \| [πŸ“₯ Download Solution in TS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-08.zip) \| [πŸ“₯ Download Solution in JS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-08-js.zip)) -- **[Step 9: Component Configuration](steps/09/README.md "After we have introduced all three parts of the Model-View-Controller /(MVC/) concept, we now come to another important structural aspect of OpenUI5. ")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/09/index-cdn.html) \| [πŸ“₯ Download Solution in TS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-09.zip) \| [πŸ“₯ Download Solution in JS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-09-js.zip)) -- **[Step 10: Descriptor for Applications](steps/10/README.md "All application-specific configuration settings will now further be put in a separate descriptor file called manifest.json. This clearly separates the application coding from the configuration settings and makes our app even more flexible. For example, all SAP Fiori applications are realized as components and come with a descriptor file in order to be hosted in the SAP Fiori launchpad.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/10/index-cdn.html) \| [πŸ“₯ Download Solution in TS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-10.zip) \| [πŸ“₯ Download Solution in JS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-10-js.zip)) -- **[Step 11: Pages and Panels](steps/11/README.md "After all the work on the app structure it’s time to improve the look of our app. We will use two controls from the sap.m library to add a bit more "bling" to our UI. You will also learn about control aggregations in this step.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/11/index-cdn.html) \| [πŸ“₯ Download Solution in TS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-11.zip) \| [πŸ“₯ Download Solution in JS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-11-js.zip)) -- **[Step 12: Shell Control as Container](steps/12/README.md "Now we use a shell control as container for our app and use it as our new root element. The shell takes care of visual adaptation of the application to the device’s screen size by introducing a so-called letterbox on desktop screens.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/12/index-cdn.html) \| [πŸ“₯ Download Solution in TS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-12.zip) \| [πŸ“₯ Download Solution in JS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-12-js.zip)) -- **[Step 13: Margins and Paddings](steps/13/README.md "Our app content is still glued to the corners of the letterbox. To fine-tune our layout, we can add margins and paddings to the controls that we added in the previous step. ")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/13/index-cdn.html) \| [πŸ“₯ Download Solution in TS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-13.zip) \| [πŸ“₯ Download Solution in JS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-13-js.zip)) -- **[Step 14: Custom CSS and Theme Colors](steps/14/README.md "Sometimes we need to define some more fine-granular layouts and this is when we can use the flexibility of CSS by adding custom style classes to controls and style them as we like. ")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/14/index-cdn.html) \| [πŸ“₯ Download Solution in TS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-14.zip) \| [πŸ“₯ Download Solution in JS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-14-js.zip)) -- **[Step 15: Nested Views](steps/15/README.md "Our panel content is getting more and more complex and now it is time to move the panel content to a separate view. With that approach, the application structure is much easier to understand, and the individual parts of the app can be reused.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/15/index-cdn.html) \| [πŸ“₯ Download Solution in TS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-15.zip) \| [πŸ“₯ Download Solution in JS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-15-js.zip)) -- **[Step 16: Dialogs and Fragments](steps/16/README.md "In this step, we will take a closer look at another element which can be used to assemble views: the fragment. ")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/16/index-cdn.html) \| [πŸ“₯ Download Solution in TS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-16.zip) \| [πŸ“₯ Download Solution in JS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-16-js.zip)) -- **[Step 17: Fragment Callbacks](steps/17/README.md "Now that we have integrated the dialog, it's time to add some user interaction. The user will definitely want to close the dialog again at some point, so we add a button to close the dialog and assign an event handler.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/17/index-cdn.html) \| [πŸ“₯ Download Solution in TS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-17.zip) \| [πŸ“₯ Download Solution in JS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-17-js.zip)) -- **[Step 18: Icons](steps/18/README.md "Our dialog is still pretty much empty. Since OpenUI5 is shipped with a large icon font that contains more than 500 icons, we will add an icon to greet our users when the dialog is opened.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/18/index-cdn.html) \| [πŸ“₯ Download Solution in TS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-18.zip) \| [πŸ“₯ Download Solution in JS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-18-js.zip)) -- **[Step 19: Aggregation Binding](steps/19/README.md "Now that we have established a good structure for our app, it's time to add some more functionality. We start exploring more features of data binding by adding some invoice data in JSON format that we display in a list below the panel.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/19/index-cdn.html) \| [πŸ“₯ Download Solution in TS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-19.zip) \| [πŸ“₯ Download Solution in JS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-19-js.zip)) -- **[Step 20: Data Types](steps/20/README.md "The list of invoices is already looking nice, but what is an invoice without a price assigned? Typically prices are stored in a technical format and with a /'./' delimiter in the data model. For example, our invoice for pineapples has the calculated price 87.2 without a currency. We are going to use the OpenUI5 data types to format the price properly, with a locale-dependent decimal separator and two digits after the separator.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/20/index-cdn.html) \| [πŸ“₯ Download Solution in TS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-20.zip) \| [πŸ“₯ Download Solution in JS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-20-js.zip)) -- **[Step 21: Expression Binding](steps/21/README.md "Sometimes the predefined types of OpenUI5 are not flexible enough and you want to do a simple calculation or formatting in the view - that is where expressions are really helpful. We use them to format our price according to the current number in the data model.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/21/index-cdn.html) \| [πŸ“₯ Download Solution in TS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-21.zip) \| [πŸ“₯ Download Solution in JS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-21-js.zip)) -- **[Step 22: Custom Formatters](steps/22/README.md "If we want to do a more complex logic for formatting properties of our data model, we can also write a custom formatting function. We will now add a localized status with a custom formatter, because the status in our data model is in a rather technical format.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/22/index-cdn.html) \| [πŸ“₯ Download Solution in TS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-22.zip) \| [πŸ“₯ Download Solution in JS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-22-js.zip)) -- **[Step 23: Filtering](steps/23/README.md "In this step, we add a search field for our product list and define a filter that represents the search term. When searching, the list is automatically updated to show only the items that match the search term.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/23/index-cdn.html) \| [πŸ“₯ Download Solution in TS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-23.zip) \| [πŸ“₯ Download Solution in JS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-23-js.zip)) -- **[Step 24: Sorting and Grouping](steps/24/README.md "To make our list of invoices even more user-friendly, we sort it alphabetically instead of just showing the order from the data model. Additionally, we introduce groups and add the company that ships the products so that the data is easier to consume.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/24/index-cdn.html) \| [πŸ“₯ Download Solution in TS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-24.zip) \| [πŸ“₯ Download Solution in JS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-24-js.zip)) -- **[Step 25: Remote OData Service](steps/25/README.md "So far we have worked with local JSON data, but now we will access a real OData service to visualize remote data.")** (πŸ”— Live Preview *unfeasible* \| [πŸ“₯ Download Solution in TS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-25.zip) \| [πŸ“₯ Download Solution in JS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-25-js.zip)) -- **[Step 26: Mock Server Configuration](steps/26/README.md "We just ran our app against a real service, but for developing and testing our app we do not want to rely on the availability of the β€œreal” service or put additional load on the system where the data service is located.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/26/test/mockServer-cdn.html) \| [πŸ“₯ Download Solution in TS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-26.zip) \| [πŸ“₯ Download Solution in JS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-26-js.zip)) -- **[Step 27: Unit Test with QUnit](steps/27/README.md "Now that we have a test folder in the app, we can start to increase our test coverage. ")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/27/test/Test.cdn.qunit.html?testsuite=test-resources/ui5/walkthrough/testsuite.cdn.qunit&test=unit/unitTests) \| [πŸ“₯ Download Solution in TS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-27.zip) \| [πŸ“₯ Download Solution in JS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-27-js.zip)) -- **[Step 28: Integration Test with OPA](steps/28/README.md "If we want to test interaction patterns or more visual features of our app, we can also write an integration test. ")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/28/test/Test.cdn.qunit.html?testsuite=test-resources/ui5/walkthrough/testsuite.cdn.qunit&test=integration/opaTests) \| [πŸ“₯ Download Solution in TS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-28.zip) \| [πŸ“₯ Download Solution in JS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-28-js.zip)) +- **[Step 1: Hello World!](steps/01/README.md "As you know OpenUI5 is all about HTML5. Let’s get started with building a first "Hello World" with only HTML.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/01/index.html) \|
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-01.zip)
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-01-js.zip)
) +- **[Step 2: Bootstrap](steps/02/README.md "Before we can do something with OpenUI5, we need to load and initialize it. This process of loading and initializing OpenUI5 is called bootstrapping. Once this bootstrapping is finished, we simply display an alert.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/02/index-cdn.html) \|
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-02.zip)
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-02-js.zip)
) +- **[Step 3: Controls](steps/03/README.md "Now it is time to build our first little UI by replacing the "Hello World" text in the HTML body by the OpenUI5 control sap/m/Text. In the beginning, we will use the JavaScript control interface to set up the UI, the control instance is then placed into the HTML body. ")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/03/index-cdn.html) \|
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-03.zip)
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-03-js.zip)
) +- **[Step 4: XML Views](steps/04/README.md "Putting all our UI into the index.ts file will very soon result in a messy setup, and there is quite a bit of work ahead of us. So let’s do a first modularization by putting the sap/m/Text control into a dedicated view.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/04/index-cdn.html) \|
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-04.zip)
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-04-js.zip)
) +- **[Step 5: Controllers](steps/05/README.md "In this step, we replace the text with a button and show the "Hello World" message when the button is pressed. The handling of the button's press event is implemented in the controller of the view.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/05/index-cdn.html) \|
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-05.zip)
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-05-js.zip)
) +- **[Step 6: Modules](steps/06/README.md "In OpenUI5, resources are often referred to as modules. In this step, we replace the alert from the last exercise with a proper Message Toast from the sap.m library.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/06/index-cdn.html) \|
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-06.zip)
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-06-js.zip)
) +- **[Step 7: JSON Model](steps/07/README.md "Now that we have set up the view and controller, it’s about time to think about the M in MVC.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/07/index-cdn.html) \|
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-07.zip)
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-07-js.zip)
) +- **[Step 8: Translatable Texts](steps/08/README.md "In this step we move the texts of our UI to a separate resource file.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/08/index-cdn.html) \|
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-08.zip)
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-08-js.zip)
) +- **[Step 9: Component Configuration](steps/09/README.md "After we have introduced all three parts of the Model-View-Controller /(MVC/) concept, we now come to another important structural aspect of OpenUI5. ")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/09/index-cdn.html) \|
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-09.zip)
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-09-js.zip)
) +- **[Step 10: Descriptor for Applications](steps/10/README.md "All application-specific configuration settings will now further be put in a separate descriptor file called manifest.json. This clearly separates the application coding from the configuration settings and makes our app even more flexible. For example, all SAP Fiori applications are realized as components and come with a descriptor file in order to be hosted in the SAP Fiori launchpad.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/10/index-cdn.html) \|
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-10.zip)
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-10-js.zip)
) +- **[Step 11: Pages and Panels](steps/11/README.md "After all the work on the app structure it’s time to improve the look of our app. We will use two controls from the sap.m library to add a bit more "bling" to our UI. You will also learn about control aggregations in this step.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/11/index-cdn.html) \|
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-11.zip)
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-11-js.zip)
) +- **[Step 12: Shell Control as Container](steps/12/README.md "Now we use a shell control as container for our app and use it as our new root element. The shell takes care of visual adaptation of the application to the device’s screen size by introducing a so-called letterbox on desktop screens.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/12/index-cdn.html) \|
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-12.zip)
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-12-js.zip)
) +- **[Step 13: Margins and Paddings](steps/13/README.md "Our app content is still glued to the corners of the letterbox. To fine-tune our layout, we can add margins and paddings to the controls that we added in the previous step. ")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/13/index-cdn.html) \|
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-13.zip)
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-13-js.zip)
) +- **[Step 14: Custom CSS and Theme Colors](steps/14/README.md "Sometimes we need to define some more fine-granular layouts and this is when we can use the flexibility of CSS by adding custom style classes to controls and style them as we like. ")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/14/index-cdn.html) |
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-14.zip)
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-14-js.zip)
) +- **[Step 15: Nested Views](steps/15/README.md "Our panel content is getting more and more complex and now it is time to move the panel content to a separate view. With that approach, the application structure is much easier to understand, and the individual parts of the app can be reused.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/15/index-cdn.html) \|
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-15.zip)
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-15-js.zip)
) +- **[Step 16: Dialogs and Fragments](steps/16/README.md "In this step, we will take a closer look at another element which can be used to assemble views: the fragment. ")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/16/index-cdn.html) \|
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-16.zip)
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-16-js.zip)
) +- **[Step 17: Fragment Callbacks](steps/17/README.md "Now that we have integrated the dialog, it's time to add some user interaction. The user will definitely want to close the dialog again at some point, so we add a button to close the dialog and assign an event handler.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/17/index-cdn.html) \|
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-17.zip)
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-17-js.zip)
) +- **[Step 18: Icons](steps/18/README.md "Our dialog is still pretty much empty. Since OpenUI5 is shipped with a large icon font that contains more than 500 icons, we will add an icon to greet our users when the dialog is opened.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/18/index-cdn.html) \|
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-18.zip)
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-18-js.zip)
) +- **[Step 19: Aggregation Binding](steps/19/README.md "Now that we have established a good structure for our app, it's time to add some more functionality. We start exploring more features of data binding by adding some invoice data in JSON format that we display in a list below the panel.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/19/index-cdn.html) \|
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-19.zip)
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-19-js.zip)
) +- **[Step 20: Data Types](steps/20/README.md "The list of invoices is already looking nice, but what is an invoice without a price assigned? Typically prices are stored in a technical format and with a /'./' delimiter in the data model. For example, our invoice for pineapples has the calculated price 87.2 without a currency. We are going to use the OpenUI5 data types to format the price properly, with a locale-dependent decimal separator and two digits after the separator.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/20/index-cdn.html) \|
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-20.zip)
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-20-js.zip)
) +- **[Step 21: Expression Binding](steps/21/README.md "Sometimes the predefined types of OpenUI5 are not flexible enough and you want to do a simple calculation or formatting in the view - that is where expressions are really helpful. We use them to format our price according to the current number in the data model.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/21/index-cdn.html) \|
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-21.zip)
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-21-js.zip)
) +- **[Step 22: Custom Formatters](steps/22/README.md "If we want to do a more complex logic for formatting properties of our data model, we can also write a custom formatting function. We will now add a localized status with a custom formatter, because the status in our data model is in a rather technical format.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/22/index-cdn.html) \|
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-22.zip)
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-22-js.zip)
) +- **[Step 23: Filtering](steps/23/README.md "In this step, we add a search field for our product list and define a filter that represents the search term. When searching, the list is automatically updated to show only the items that match the search term.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/23/index-cdn.html) \|
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-23.zip)
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-23-js.zip)
) +- **[Step 24: Sorting and Grouping](steps/24/README.md "To make our list of invoices even more user-friendly, we sort it alphabetically instead of just showing the order from the data model. Additionally, we introduce groups and add the company that ships the products so that the data is easier to consume.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/24/index-cdn.html) \|
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-24.zip)
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-24-js.zip)
) +- **[Step 25: Remote OData Service](steps/25/README.md "So far we have worked with local JSON data, but now we will access a real OData service to visualize remote data.")** (πŸ”— Live Preview *unfeasible* \|
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-25.zip)
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-25-js.zip)
) +- **[Step 26: Mock Server Configuration](steps/26/README.md "We just ran our app against a real service, but for developing and testing our app we do not want to rely on the availability of the β€œreal” service or put additional load on the system where the data service is located.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/26/test/mockServer-cdn.html) \|
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-26.zip)
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-26-js.zip)
) +- **[Step 27: Unit Test with QUnit](steps/27/README.md "Now that we have a test folder in the app, we can start to increase our test coverage. ")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/27/test/Test.cdn.qunit.html?testsuite=test-resources/ui5/walkthrough/testsuite.cdn.qunit&test=unit/unitTests) \|
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-27.zip)
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-27-js.zip)
) +- **[Step 28: Integration Test with OPA](steps/28/README.md "If we want to test interaction patterns or more visual features of our app, we can also write an integration test. ")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/28/test/Test.cdn.qunit.html?testsuite=test-resources/ui5/walkthrough/testsuite.cdn.qunit&test=integration/opaTests) \|
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-28.zip)
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-28-js.zip)
) - **[Step 29: Debugging Tools](steps/29/README.md "Even though we have added a basic test coverage in the previous steps, it seems like we accidentally broke our app, because it does not display prices to our invoices anymore. We need to debug the issue and fix it before someone finds out.")** (*code remains unchanged from the previous step*) -- **[Step 30: Routing and Navigation](steps/30/README.md "So far, we have put all app content on one single page. As we add more and more features, we want to split the content and put it on separate pages.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/30/test/mockServer-cdn.html) \| [πŸ“₯ Download Solution in TS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-30.zip) \| [πŸ“₯ Download Solution in JS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-30-js.zip)) -- **[Step 31: Routing with Parameters](steps/31/README.md "We can now navigate between the overview and the detail page, but the actual item that we selected in the overview is not displayed on the detail page yet. A typical use case for our app is to show additional information for the selected item on the detail page. ")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/31/test/mockServer-cdn.html) \| [πŸ“₯ Download Solution in TS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-31.zip) \| [πŸ“₯ Download Solution in JS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-31-js.zip)) -- **[Step 32: Routing Back and History](steps/32/README.md "Now we can navigate to our detail page and display an invoice, but we cannot go back to the overview page yet. We'll add a back button to the detail page and implement a function that shows our overview page again.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/32/test/mockServer-cdn.html) \| [πŸ“₯ Download Solution in TS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-32.zip) \| [πŸ“₯ Download Solution in JS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-32-js.zip)) -- **[Step 33: Custom Controls](steps/33/README.md "In this step, we are going to extend the functionality of OpenUI5 with a custom control. We want to rate the product shown on the detail page, so we create a composition of multiple standard controls using the OpenUI5 extension mechanism and add some glue code to make them work nicely together. This way, we can reuse the control across the app and keep all related functionality in one module.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/33/test/mockServer-cdn.html) \| [πŸ“₯ Download Solution in TS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-33.zip) \| [πŸ“₯ Download Solution in JS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-33-js.zip)) -- **[Step 34: Responsiveness](steps/34/README.md "In this step, we improve the responsiveness of our app. OpenUI5 applications can be run on phone, tablet, and desktop devices and we can configure the application to make best use of the screen estate for each scenario. Fortunately, OpenUI5 controls like the sap.m.Table already deliver a lot of features that we can use.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/34/test/mockServer-cdn.html) \| [πŸ“₯ Download Solution in TS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-34.zip) \| [πŸ“₯ Download Solution in JS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-34-js.zip)) -- **[Step 35: Device Adaptation](steps/35/README.md "We now configure the visibility and properties of controls based on the device that we run the application on. By making use of the sap.ui.Device API and defining a device model we will make the app look great on many devices.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/35/test/mockServer-cdn.html) \| [πŸ“₯ Download Solution in TS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-35.zip) \| [πŸ“₯ Download Solution in JS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-35-js.zip)) -- **[Step 36: Content Density](steps/36/README.md "In this step of our Walkthrough tutorial, we adjust the content density based on the user’s device. OpenUI5 contains different content densities allowing you to display larger controls for touch-enabled devices and a smaller, more compact design for devices that are operated by mouse. In our app, we will detect the device and adjust the density accordingly.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/36/test/mockServer-cdn.html) \| [πŸ“₯ Download Solution in TS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-36.zip) \| [πŸ“₯ Download Solution in JS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-36-js.zip)) -- **[Step 37: Accessibility](steps/37/README.md "In this step we're going to improve the accessibility of our app.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/37/test/mockServer-cdn.html) \| [πŸ“₯ Download Solution in TS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-37.zip) \| [πŸ“₯ Download Solution in JS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-37-js.zip)) -- **[Step 38: Build Your Application](steps/38/README.md "In this step we're going to build our application and consume the speed of a built OpenUI5 application.")** ([πŸ“₯ Download Solution in TS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-38.zip) \| [πŸ“₯ Download Solution in JS](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-38-js.zip)) +- **[Step 30: Routing and Navigation](steps/30/README.md "So far, we have put all app content on one single page. As we add more and more features, we want to split the content and put it on separate pages.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/30/test/mockServer-cdn.html) \|
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-30.zip)
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-30-js.zip)
) +- **[Step 31: Routing with Parameters](steps/31/README.md "We can now navigate between the overview and the detail page, but the actual item that we selected in the overview is not displayed on the detail page yet. A typical use case for our app is to show additional information for the selected item on the detail page. ")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/31/test/mockServer-cdn.html) |
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-31.zip)
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-31-js.zip)
) +- **[Step 32: Routing Back and History](steps/32/README.md "Now we can navigate to our detail page and display an invoice, but we cannot go back to the overview page yet. We'll add a back button to the detail page and implement a function that shows our overview page again.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/32/test/mockServer-cdn.html) \|
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-32.zip)
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-32-js.zip)
) +- **[Step 33: Custom Controls](steps/33/README.md "In this step, we are going to extend the functionality of OpenUI5 with a custom control. We want to rate the product shown on the detail page, so we create a composition of multiple standard controls using the OpenUI5 extension mechanism and add some glue code to make them work nicely together. This way, we can reuse the control across the app and keep all related functionality in one module.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/33/test/mockServer-cdn.html) \|
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-33.zip)
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-33-js.zip)
) +- **[Step 34: Responsiveness](steps/34/README.md "In this step, we improve the responsiveness of our app. OpenUI5 applications can be run on phone, tablet, and desktop devices and we can configure the application to make best use of the screen estate for each scenario. Fortunately, OpenUI5 controls like the sap.m.Table already deliver a lot of features that we can use.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/34/test/mockServer-cdn.html) \|
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-34.zip)
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-34-js.zip)
) +- **[Step 35: Device Adaptation](steps/35/README.md "We now configure the visibility and properties of controls based on the device that we run the application on. By making use of the sap.ui.Device API and defining a device model we will make the app look great on many devices.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/35/test/mockServer-cdn.html) \|
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-35.zip)
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-35-js.zip)
) +- **[Step 36: Content Density](steps/36/README.md "In this step of our Walkthrough tutorial, we adjust the content density based on the user’s device. OpenUI5 contains different content densities allowing you to display larger controls for touch-enabled devices and a smaller, more compact design for devices that are operated by mouse. In our app, we will detect the device and adjust the density accordingly.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/36/test/mockServer-cdn.html) \|
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-36.zip)
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-36-js.zip)
) +- **[Step 37: Accessibility](steps/37/README.md "In this step we're going to improve the accessibility of our app.")** ([πŸ”— Live Preview](https://sap-samples.github.io/ui5-typescript-walkthrough/build/37/test/mockServer-cdn.html) \|
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-37.zip)
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-37-js.zip)
) +- **[Step 38: Build Your Application](steps/38/README.md "In this step we're going to build our application and consume the speed of a built OpenUI5 application.")** (
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-38.zip)
[πŸ“₯ Download Solution](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-38-js.zip)
) ## Requirements -The project uses npm workspaces and requires a [Node.js](https://nodejs.org/) version >= `20.11.0` to be installed. +Running the content of this repository locally (as opposed to following the tutorial steps) requires a [Node.js](https://nodejs.org/) version >= `20.11.0` to be installed. -### Getting Started +## Download and Installation + +> This section describes how to run the content of the repository locally and is *not* required for following the tutorial. For following the tutorial, simply start with Step 1 in the list of steps above. From there, you can also download and run the result of each step locally. -The project is setup as monorepo. All steps are located inside the `steps` folder and labelled with their step number. The monorepo uses `npm` workspaces to manage all steps together. But you can also run `npm` inside each individual step. +The project is set up as monorepo. All steps are located inside the `steps` folder and labelled with their step number. The monorepo uses `npm` workspaces to manage all steps together. But you can also run `npm` inside each individual step. -To setup the monorepo you first need to install all depenedencies: +To set up the monorepo you first need to install all depenedencies: ```sh npm install @@ -103,15 +105,6 @@ cd steps/01 npm start ``` -## Download and Installation - - - ## Known Issues No known issues. @@ -128,4 +121,4 @@ If you wish to contribute code, offer fixes or improvements, please send a pull ## License -Copyright (c) 2024 SAP SE or an SAP affiliate company. All rights reserved. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](LICENSE) file. +Copyright (c) 2025 SAP SE or an SAP affiliate company. All rights reserved. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](LICENSE) file. diff --git a/README_AUTHORS.md b/README_AUTHORS.md index 8583102d..358e6383 100644 --- a/README_AUTHORS.md +++ b/README_AUTHORS.md @@ -24,21 +24,15 @@ To immediately preview the markdown document you are writing *including* the two npm i ``` -in the root folder of this project once for the setup, then to actually run the server, run +in the root folder of this project once for the setup. -```sh -npm start -``` - -Then, open http://localhost:1337/README.md in your browser. - -Alternatively, and even easier, run +Then, to actually start the server, run ```sh -npm run watch +npm start ``` -And the browser will automatically open (on port 3000) and automatically reload on every saved change. +A browser window will automatically open (on port 3000 or the next free port) and automatically reload on every saved change. ## Writing *one* document which covers both JavaScript and TypeScript without duplication diff --git a/assets/css/custom.css b/assets/css/custom.css index 48d88762..3fe4c640 100644 --- a/assets/css/custom.css +++ b/assets/css/custom.css @@ -114,6 +114,10 @@ body.page-language-ts .language-switch-button.lang-ts { border-color: var(--active-border); } +li > section.ts-only, li > section.js-only { + display: inline; +} + .language-switch-button:hover { opacity: 0.8; background-color: rgba(20, 20, 20, 0.8); @@ -134,3 +138,8 @@ body.page-language-ts .language-switch-button.lang-ts { border-color: var(--active-border); } } + +#__bs_notify__ { + background-color: rgba(27, 32, 50, 0.3) !important; + top: 40px !important; +} \ No newline at end of file diff --git a/assets/js/custom.js b/assets/js/custom.js index 30c9e34c..d4a02830 100644 --- a/assets/js/custom.js +++ b/assets/js/custom.js @@ -164,7 +164,10 @@ function replaceDetailSections() { if (child.tagName.toLocaleUpperCase() === "SUMMARY") { return; } - sectionTag.appendChild(child); + // only add the child if it has content + if (child.innerHTML.trim()) { + sectionTag.appendChild(child); + } }); // remove the detail tag @@ -205,6 +208,27 @@ function replaceFileExtensions(lang) { } } +function updatePrevNextLinks(lang) { + const links = document.querySelectorAll('a[href$="README.md"], a[href$="/"], a[href$="README.md?lang=js"], a[href$="/?lang=js"]'); + links.forEach(link => { + // Only handle relative links (not starting with http://, https://, or //) + if (/^(https?:)?\/\//.test(link.getAttribute("href"))) { + return; + } + + let url = new URL(link.href, window.location.origin); + + // Remove any existing lang param + url.searchParams.delete("lang"); + + if (lang === "js") { + url.searchParams.set("lang", "js"); + } + + link.href = url.pathname + url.search + url.hash; + }); +} + // dynamic overall language switching function addLanguageSwitchButtons() { @@ -233,6 +257,7 @@ function switchLanguage(newLang) { replaceFileExtensions(lang); resetCodeCoupleButtons(); updateAllCodeCouples(lang); + updatePrevNextLinks(lang); } function updateAllCodeCouples(globalLang) { @@ -252,5 +277,6 @@ document.addEventListener("DOMContentLoaded", (event) => { replaceFileExtensions(lang); addLanguageSwitchButtons(); updateAllCodeCouples(lang); + updatePrevNextLinks(lang); }); }); diff --git a/steps/01/README.md b/steps/01/README.md index ffc01fe7..bbb91be9 100644 --- a/steps/01/README.md +++ b/steps/01/README.md @@ -1,6 +1,6 @@ ## Step 1: Hello World! -As you know OpenUI5 is all about HTML5. Let's get started with building a first "Hello World" with only HTML. In addition we'll initialize the UI5 Tooling, so we can benefit from it from the beginning. +As you know, OpenUI5 is all about HTML5. Let's get started with building a first "Hello World" with only HTML. In addition we'll initialize the UI5 Tooling, so we can benefit from it from the beginning.   @@ -15,12 +15,23 @@ As you know OpenUI5 is all about HTML5. Let's get started with building a first You can access the live preview by clicking on this link: [πŸ”— Live Preview of Step 1](https://sap-samples.github.io/ui5-typescript-walkthrough/build/01/index.html). -Download solution for step 1 in [πŸ“₯ TypeScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-01.zip) or [πŸ“₯ JavaScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-01-js.zip). - *** ### Coding +
+ +You can download the solution for this step here: [πŸ“₯ Download step 1](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-01.zip). + +
+ +
+ +You can download the solution for this step here: [πŸ“₯ Download step 1](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-01-js.zip). + +
+*** + ### webapp \(New\) We create a folder on our local machine which will contain all the sources of the app we're going to build. We'll refer to this folder as the β€œapp root directory". @@ -154,7 +165,7 @@ This will open a new browser window hosting your newly created `index.html`. ### Conventions -- The `index.html` file is located in the `webapp` folder if it is used productively. +- The `index.html` file is located in the `webapp` folder.   diff --git a/steps/02/README.md b/steps/02/README.md index 26e281a1..d49a73b3 100644 --- a/steps/02/README.md +++ b/steps/02/README.md @@ -15,11 +15,23 @@ Before we can do something with OpenUI5, we need to load and initialize it. This You can access the live preview by clicking on this link: [πŸ”— Live Preview of Step 2](https://sap-samples.github.io/ui5-typescript-walkthrough/build/02/index-cdn.html). -Download solution for step 2 in [πŸ“₯ TypeScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-02.zip) or [πŸ“₯ JavaScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-02-js.zip). +*** +### Coding + +
+ +You can download the solution for this step here: [πŸ“₯ Download step 2](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-02.zip). + +
+ +
+You can download the solution for this step here: [πŸ“₯ Download step 2](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-02-js.zip). + +
*** -### Tooling +### UI5 Tooling First, let's set up our UI5 Tooling to use the OpenUI5 framework for our project. We also need to add the necessary OpenUI5 libraries as dependencies to the project's UI5 Tooling configuration. @@ -37,19 +49,20 @@ ui5 add sap.ui.core themelib_sap_horizon The `ui5 add` command adds specific libraries as dependency to the projects UI5 Tooling configuration. In this case, we'e adding the `sap.ui.core` library, which provides core functionality of the OpenUI5 framework. This library is essential for bootstrapping OpenUI5. Additionally, we're adding the `themelib_sap_horizon` library which provides the visual styles for the Horizon theme. We'll use this theme with our application. -*** -### package.json +
+ +### TypeScript Setup To work with TypeScript, we must install it in our project. To do this, we execute the following command in the terminal: + ```sh npm install typescript --save-dev ``` By running this command, npm will download the TypeScript package from the npm registry and install it in our project's "node_modules" directory. It will also add an entry for TypeScript in the "devDependencies" section of our package.json file, so that other developers working on the project can easily install the same version of TypeScript. -*** ### tsconfig.json \(New\) @@ -57,6 +70,7 @@ As a next step, we need to create the file `tsconfig.json` in the app root direc We specify the compiler options as follow: + ```json { "compilerOptions": { @@ -101,18 +115,16 @@ Let's go through the compiler options specified in the file: *** -### Coding +
-### webapp/index.ts \(New\) +### webapp/index.?s \(New\) -Now let's move on to the UI work. We create a new `index.ts` script in the webapp folder. In this script, we add a native `alert()` method with the message "UI5 is ready". +Now let's move on to the UI work. We create a new `index.?s` script in the webapp folder. In this script, we add a native `alert()` method with the message "UI5 is ready". -```ts +```js alert("UI5 is ready"); ``` -*** - ### webapp/index.html Next, we'll integrate the script we just created into the `index.html` page to signal when the OpenUI5 framework has finished loading. This process involves first incorporating the OpenUI5 framework into our HTML page by adding a script tag specifically for loading OpenUI5. @@ -157,22 +169,30 @@ We initialize the core modules with the following configuration options: ``` -> πŸ“ **Note:** +> πŸ“ **Note:**
> The namespace is a unique identifier for your application file. It helps prevent naming conflicts with other modules or libraries. *** -### Tooling - -### package.json +### UI5 Tooling Let's enhance our tooling setup once again by installing some custom middleware for the ui5-server. This will help us handle our development project more efficiently. We open a terminal and navigate to the root folder of our app. Then, we execute the following command: +
+ ```sh npm install ui5-middleware-livereload ui5-middleware-serveframework ui5-tooling-transpile --save-dev ``` +
+ +
+ +```sh +npm install ui5-middleware-livereload ui5-middleware-serveframework --save-dev +``` +
When you run the command, npm will download the specified packages from the npm registry and store them in a folder called `node_modules` within your project directory. The `--save-dev` flag instructs npm to save these packages as development dependencies in the `devDependencies` section of the `package.json` file. Development dependencies are packages that are only needed during development and not in production. By separating them from production dependencies, we can keep our project clean and ensure that only the required packages are included when deploying the application. @@ -182,21 +202,28 @@ Let's break down what each package does: - `ui5-middleware-serveframework` is another middleware plugin for the UI5 Tooling that provides a web server to serve your OpenUI5 project during development. It allows you to easily serve the necessary OpenUI5 libraries and resources required by your application from your development environment. +
+ - `ui5-tooling-transpile` is a plugin for the UI5 Tooling that transpiles modern JavaScript (ES6+) and TypeScript into a compatible version for OpenUI5. OpenUI5 is based on older versions of JavaScript, so this plugin allows you to take advantage of the latest language features and syntax while ensuring that your code remains compatible with OpenUI5. -*** +
### ui5.yaml Next, we have to configure the tooling extension we installed from npm to our UI5 Tooling setup, so we can use them in our project. To hook a custom task into a certain build phase of a project, it needs to reference another task that will get executed before or after it. The same applies for a custom middleware: +
- For the `ui5-tooling-transpile-task` we specify that this should happen after the`replaceVersion` task. +
+ - All our custom middleware extensions will be called after the `compression` middleware. > πŸ“Œ **Important:**
> Middleware configurations are applied in the order in which they are defined. +
+ ```yaml framework: name: OpenUI5 @@ -219,7 +246,31 @@ server: ``` Now you can benefit from live reload on changes, built framework resources at development time, and make use of TypeScript in OpenUI5. -> πŸ“ **Note:**
+
+ +
+ +```yaml +framework: + name: OpenUI5 + version: "1.132.1" + libraries: + - name: sap.ui.core + - name: themelib_sap_horizon +builder: +server: + customMiddleware: + - name: ui5-middleware-serveframework + afterMiddleware: compression + - name: ui5-middleware-livereload + afterMiddleware: compression +``` +Now you can benefit from live reload on changes and built framework resources at development time. + +
+ +
+> πŸ“ **Note:**
> During its initial run, the `ui5-middleware-serveframework` middleware will build the framework, which can take a while. In all following steps, the build will not happen again and the framework is served from the built resources.   diff --git a/steps/03/README.md b/steps/03/README.md index 06826cd3..260d1ccd 100644 --- a/steps/03/README.md +++ b/steps/03/README.md @@ -1,6 +1,6 @@ ## Step 3: Controls -Now it is time to build our first little UI by replacing the β€œHello World” text in the HTML body by the OpenUI5 control `sap/m/Text`. In the beginning, we will use the TypeScript control API to set up the UI, the control instance is then placed into the HTML body. +Now it is time to build our first little UI by replacing the β€œHello World” text in the HTML body by the OpenUI5 control `sap/m/Text`. In the beginning, we will create an OpenUI5 control instance and place into the HTML body.   @@ -15,13 +15,24 @@ Now it is time to build our first little UI by replacing the β€œHello World” t You can access the live preview by clicking on this link: [πŸ”— Live Preview of Step 3](https://sap-samples.github.io/ui5-typescript-walkthrough/build/03/index-cdn.html). -Download solution for step 3 in [πŸ“₯ TypeScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-03.zip) or [πŸ“₯ JavaScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-03-js.zip). +*** +### Coding +
+ +You can download the solution for this step here: [πŸ“₯ Download step 3](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-03.zip). + +
+
+ +You can download the solution for this step here: [πŸ“₯ Download step 3](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-03-js.zip). + +
*** -### Tooling +
-### package.json +### UI5 Tooling To get the type definitions for OpenUI5, we need to install them to our project. We open a terminal in the root folder of our app and exectue the following command: @@ -31,13 +42,32 @@ npm install @types/openui5 --save-dev *** -### Coding +
+ +### webapp/index.?s + +
+ +We will replace the native script in our file with the OpenUI5 Text control displaying "Hello Word". +For this, we will create a new instance of the Text control, setting its `text` property to "Hello World" by passing it as an object to the constructor. + +
+ +
+ +We will replace the native script in our file with the OpenUI5 Text control displaying "Hello Word". +For this, we will first use OpenUI5's module definition `sap.ui.define` to create a module. To instantiate and render the Text control, we will define the `sap/m/Text` module as a dependency to this module. We will then create a new instance of the Text control and set its `text` property to "Hello World". + +
-### webapp/index.ts +To place the text control to our HTML document, we chain the constructor call of the control with the `placeAt` method. This method is used to position OpenUI5 controls. In our case, we add the Text control to the DOM element with the ID `content`. -Now we make some changes to our `index.ts` file: We remove the alert method and instantiate an OpenUI5 text control instead. We create an instance of the text control by passing its options as a TypeScript object to the constructor. In our case, we want the text control to display the message "Hello World", so we'll set the `text` property accordingly. +
-To place the text control to our HTML document, we chain the constructor call of the control with the `placeAt` method. This method is used to position OpenUI5 controls. In our case, we add the text control to the DOM element with the ID `content`. +> πŸ“Œ **Important:**
+> It is best practice to use of Anynchronous Module Loading (AMD) style for defining modules and their dependencies. This ensures better performance, proper dependency tracking between modules and helps avoid issues related to loading order. + +
```ts import Text from "sap/m/Text"; @@ -47,7 +77,15 @@ new Text({ }).placeAt("content"); ``` -Controls are used to define appearance and behavior of parts of the screen. +```js +sap.ui.define(["sap/m/Text"], function (Text) { + "use strict"; + + new Text({ + text: "Hello World" + }).placeAt("content"); +}); +``` All controls of OpenUI5 have a fixed set of properties, aggregations, and associations for configuration. You can find their descriptions in the Demo Kit. In addition, each control comes with a set of public functions that you can look up in the API reference. @@ -96,7 +134,7 @@ As we now use the `sap.m` library with our app, we need to add the dependency to We open a terminal in the root folder of our app and exectue the following command: -```sh +```sh ui5 add sap.m ``` @@ -112,8 +150,6 @@ ui5 add sap.m **Related Information** -[TypeScript definitions for OpenUI5](https://www.npmjs.com/package/@types/openui5) - [Working with Controls](https://sdk.openui5.org/topic/91f0a22d6f4d1014b6dd926db0e91070.html "Controls are used to define the appearance and behavior of screen areas.") [API Reference: `sap.m.Text`](https://sdk.openui5.orgapi/sap.m.Text) @@ -125,3 +161,15 @@ ui5 add sap.m [API Reference: `sap.ui.core.Element`](https://sdk.openui5.orgapi/sap.ui.core.Element) [API Reference: `sap.ui.base.ManagedObject`](https://sdk.openui5.orgapi/sap.ui.base.ManagedObject) + +
+ +[TypeScript definitions for OpenUI5](https://www.npmjs.com/package/@types/openui5) + +
+ +
+ +[Best Practices for Loading Modules](https://sdk.openui5.org/topic/00737d6c1b864dc3ab72ef56611491c4 "This section provides best practices for OpenUI5 module loading patterns.") + +
\ No newline at end of file diff --git a/steps/04/README.md b/steps/04/README.md index a1b76b8a..2bf873f6 100644 --- a/steps/04/README.md +++ b/steps/04/README.md @@ -16,12 +16,23 @@ When working with OpenUI5, we recommend the use of XML views, as this produces t You can access the live preview by clicking on this link: [πŸ”— Live Preview of Step 4](https://sap-samples.github.io/ui5-typescript-walkthrough/build/04/index-cdn.html). -Download solution for step 4 in [πŸ“₯ TypeScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-04.zip) or [πŸ“₯ JavaScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-04-js.zip). - *** ### Coding +
+ +You can download the solution for this step here: [πŸ“₯ Download step 4](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-04.zip). + +
+ +
+ +You can download the solution for this step here: [πŸ“₯ Download step 4](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-04-js.zip). + +
+*** + ### webapp/view/App.view.xml \(New\) We create a new folder called `view` inside the `webapp` folder. This folder will hold all our XML view files. Inside the `view` folder, we create a new file called `App.view.xml`. In OpenUI5, the root node of an XML view is the `` tag. To use this tag, we need to declare the XML namespace `mvc`, which corresponds to the `sap.ui.core.mvc` namespace. This namespace provides classes for creating and working with views and other Model-View-Controller \(MVC\) assets. Additionally, we declare the default XML namespace to the `sap.m` namespace. This namespace contains the majority of OpenUI5's UI assets, including the `Text` control that we want to use with our view. @@ -46,9 +57,9 @@ We have created an XML view that displays a text control with the text "Hello Wo *** -### webapp/index.ts +### webapp/index.?s -As a next step, we are going to replace the `sap/m/Text` control in our `index.ts` file with the app view that we've just created. To do this, we utilize the `XMLView.create` function, which is a part of the `sap/ui/core/mvc/View` module. This function needs a `viewName` property, which indicates the resource that needs to be loaded. The `viewName` is a combination of the namespace defined in the bootstrap and the path to the app view, but without the ".view.xml" extension. In addition, we set the `id` property to "app". Providing a stable ID is beneficial as it offers an easy and consistent way to identify and refer to specific views and elements in your code, thus helping to keep your code organized. +As a next step, we are going to replace the `sap/m/Text` control in our `index.?s` file with the app view that we've just created. To do this, we utilize the `XMLView.create` function, which is a part of the `sap/ui/core/mvc/View` module. This function needs a `viewName` property, which indicates the resource that needs to be loaded. The `viewName` is a combination of the namespace defined in the bootstrap and the path to the app view, but without the ".view.xml" extension. In addition, we set the `id` property to "app". Providing a stable ID is beneficial as it offers an easy and consistent way to identify and refer to specific views and elements in your code, thus helping to keep your code organized. ```ts import XMLView from "sap/ui/core/mvc/XMLView"; @@ -61,6 +72,21 @@ XMLView.create({ }); ``` + +```js +sap.ui.define(["sap/ui/core/mvc/XMLView"], function (XMLView) { + "use strict"; + + XMLView.create({ + viewName: "ui5.walkthrough.view.App", + id: "app" + }).then(function (view) { + view.placeAt("content"); + }); +}); + +``` + We have now embed our app view to the body of the HTML document. > πŸ’‘ **Tip:**
diff --git a/steps/05/README.md b/steps/05/README.md index 0fa2e1d0..74dcaa9a 100644 --- a/steps/05/README.md +++ b/steps/05/README.md @@ -15,17 +15,27 @@ In this step, we replace the text with a button and show the β€œHello World” m You can access the live preview by clicking on this link: [πŸ”— Live Preview of Step 5](https://sap-samples.github.io/ui5-typescript-walkthrough/build/05/index-cdn.html). -Download solution for step 5 in [πŸ“₯ TypeScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-05.zip) or [πŸ“₯ JavaScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-05-js.zip). - *** ### Coding -### webapp/controller/App.controller.ts \(New\) +
+ +You can download the solution for this step here: [πŸ“₯ Download step 5](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-05.zip). + +
+
+ +You can download the solution for this step here: [πŸ“₯ download step 5](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-05-js.zip). + +
+*** + +### webapp/controller/App.controller.?s \(New\) First of all, we need a conroller for our app view that defines how the view should react to user inputs, such as a button press event. -We create a new folder called `controller` inside the `webapp` folder. This folder will hold all our controller files. Inside the `controller` folder, we create a new file called `App.view.xml`. We define the app controller in its own file by extending the OpenUI5-provided `sap/ui/core/mvc/Controller`. In the beginning, it holds only a single function called `onShowHello` that shows an alert with the static text "Hello World". +We create a new folder called `controller` inside the `webapp` folder. This folder will hold all our controller files. Inside the `controller` folder, we create a new file called `App.controller.?s`. We define the app controller in its own file by extending the OpenUI5-provided `sap/ui/core/mvc/Controller`. In the beginning, it holds only a single function called `onShowHello` that shows an alert with the static text "Hello World". ```ts @@ -43,9 +53,27 @@ export default class AppController extends Controller { ``` +```js +sap.ui.define(["sap/ui/core/mvc/Controller"], function (Controller) { + "use strict"; + + const AppController = Controller.extend("ui5.walkthrough.controller.App", { + onShowHello: function _onShowHello() { + // show a native JavaScript alert + alert("Hello World"); + } + }); + ; + return AppController; +}); + +``` +
+ > πŸ“ **Note:**
> The comment `@name ui5.walkthrough.controller.App` is a JSDoc comment that names this controller. It can be used by documentation generators and IDEs to provide more information about this class. +
*** ### webapp/view/App.view.xml @@ -80,7 +108,7 @@ A view does not necessarily need an explicitly assigned controller. You do not h - Event handlers are prefixed with `on` -- Controller names always end with `*.controller.js` \(in JavaScript\) or `*.controller.ts` \(in TypeScript\) +- Controller names always end with `*.controller.?s`   diff --git a/steps/06/README.md b/steps/06/README.md index 99e0bc74..c48f1e2a 100644 --- a/steps/06/README.md +++ b/steps/06/README.md @@ -14,15 +14,27 @@ In OpenUI5, resources are often referred to as modules. In this step, we replace You can access the live preview by clicking on this link: [πŸ”— Live Preview of Step 6](https://sap-samples.github.io/ui5-typescript-walkthrough/build/06/index-cdn.html). -Download solution for step 5 in [πŸ“₯ TypeScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-06.zip) or [πŸ“₯ JavaScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-06-js.zip). - *** ### Coding -### webapp/controller/App.controller.ts +
+ +You can download the solution for this step here: [πŸ“₯ Download step 6](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-06.zip). + +
+ +
+ +You can download the solution for this step here: [πŸ“₯ Download step 6](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-06-js.zip). + +
+*** + +### webapp/controller/App.controller.?s + +We now replace the native `alert` function with the `show` method of the `sap.m.MessageToast` control of OpenUI5. -We now replace the native `alert` function with the `show` method of the `sap.m.MessageToast` control of OpenUI5. For this we extend the imports with the `sap/m/MessageToast` module. ```ts import MessageToast from "sap/m/MessageToast"; @@ -39,6 +51,24 @@ export default class AppController extends Controller { ``` +```js +sap.ui.define(["sap/m/MessageToast", "sap/ui/core/mvc/Controller"], function (MessageToast, Controller) { + "use strict"; + + /** + * @name ui5.walkthrough.controller.App + */ + const AppController = Controller.extend("ui5.walkthrough.controller.App", { + onShowHello: function _onShowHello() { + MessageToast.show("Hello World"); + } + }); + ; + return AppController; +}); + +``` + For now, the message toast just displays a static "Hello World" message. We will show how to load a translated text here in [Step 8: Translatable Texts](../08/README.md).   diff --git a/steps/07/README.md b/steps/07/README.md index 8c360e3e..d3311cce 100644 --- a/steps/07/README.md +++ b/steps/07/README.md @@ -16,13 +16,24 @@ We'll create a view model in our controller, add an input field to our app, bind You can access the live preview by clicking on this link: [πŸ”— Live Preview of Step 7](https://sap-samples.github.io/ui5-typescript-walkthrough/build/07/index-cdn.html). -Download solution for step 7 in [πŸ“₯ TypeScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-07.zip) or [πŸ“₯ JavaScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-07-js.zip). - *** ### Coding -### webapp/controller/App.controller.ts +
+ +You can download the solution for this step here: [πŸ“₯ Download step 7](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-07.zip). + +
+ +
+ +You can download the solution for this step here: [πŸ“₯ Download step 7](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-07-js.zip). + +
+*** + +### webapp/controller/App.controller.?s In the controller, we'll create a new data model and link it to the view that is related to the controller. The best time to create a model is during the `onInit` method. This is a special method in the Controller class that is automatically invoked by the framework when the controller is first set up. @@ -58,6 +69,31 @@ export default class AppController extends Controller { ``` +```js +sap.ui.define(["sap/m/MessageToast", "sap/ui/core/mvc/Controller", "sap/ui/model/json/JSONModel"], function (MessageToast, Controller, JSONModel) { + "use strict"; + + const AppController = Controller.extend("ui5.walkthrough.controller.App", { + onInit: function _onInit() { + // set data model on view + const data = { + recipient: { + name: "World" + } + }; + const dataModel = new JSONModel(data); + this.getView()?.setModel(dataModel); + }, + onShowHello: function _onShowHello() { + MessageToast.show("Hello World"); + } + }); + ; + return AppController; +}); + +``` + The data in the model is now accessible to the view and all the child controls within that view. Models in OpenUI5 are used to store and manipulate data that is displayed in the view. By setting a model on the view, the data can be bound to UI controls in the view, allowing for automatic updating of the UI when the data changes. diff --git a/steps/08/README.md b/steps/08/README.md index 03820616..2f697b24 100644 --- a/steps/08/README.md +++ b/steps/08/README.md @@ -16,11 +16,21 @@ This way, they are all in a central place and can be easily translated into othe You can access the live preview by clicking on this link: [πŸ”— Live Preview of Step 8](https://sap-samples.github.io/ui5-typescript-walkthrough/build/08/index-cdn.html). -Download solution for step 8 in [πŸ“₯ TypeScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-08.zip) or [πŸ“₯ JavaScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-08-js.zip). - *** ### Coding +
+ +You can download the solution for this step here: [πŸ“₯ Download step 8](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-08.zip). + +
+ +
+ +You can download the solution for this step here: [πŸ“₯ Download step 8](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-08-js.zip). + +
+*** ### webapp/i18n/i18n.properties \(New\) @@ -37,7 +47,7 @@ In this tutorial we'll only have one properties file. However, in real-world pro *** -### webapp/controller/App.controller.ts +### webapp/controller/App.controller.?s In the controller, we'll create a new resource model that refers to our resource bundle file (`i18n.properties`) and link it to the view associated with the controller. This allows us to bind control properties in the view to translatable texts. We'll also modify the `onShowHello` event handler function to replace the static "Hello World" text with a dynamic greeting text. @@ -65,7 +75,6 @@ export default class AppController extends Controller { } }; const dataModel = new JSONModel(data); - // because of "strict" mode in tsconfig.json a null check is required for this.getView() this.getView()?.setModel(dataModel); // set i18n model on view @@ -84,6 +93,43 @@ export default class AppController extends Controller { MessageToast.show(msg); } }; + +``` + +```js +sap.ui.define(["sap/m/MessageToast", "sap/ui/core/mvc/Controller", "sap/ui/model/json/JSONModel", "sap/ui/model/resource/ResourceModel"], function (MessageToast, Controller, JSONModel, ResourceModel) { + "use strict"; + + const AppController = Controller.extend("ui5.walkthrough.controller.App", { + onInit: function _onInit() { + // set data model on view + const data = { + recipient: { + name: "World" + } + }; + const dataModel = new JSONModel(data); + this.getView()?.setModel(dataModel); + + // set i18n model on view + const i18nModel = new ResourceModel({ + bundleName: "ui5.walkthrough.i18n.i18n" + }); + this.getView()?.setModel(i18nModel, "i18n"); + }, + onShowHello: function _onShowHello() { + // read msg from i18n model + const recipient = this.getView()?.getModel()?.getProperty("/recipient/name"); + const resourceBundle = this.getView()?.getModel("i18n")?.getResourceBundle(); + const msg = resourceBundle.getText("helloMsg", [recipient]); + // show message + MessageToast.show(msg); + } + }); + ; + return AppController; +}); + ``` The bundle name \(`ui5.walkthrough.i18n.i18n`\) consists of the application namespace `ui5.walkthrough` \(the application root as defined in the `index.html`\), the folder name `i18n`, and finally the base file name `i18n` without extension. The OpenUI5 runtime calculates the correct path to the resource, to which `.properties` is then appended. diff --git a/steps/09/README.md b/steps/09/README.md index ba19cdf0..93bac191 100644 --- a/steps/09/README.md +++ b/steps/09/README.md @@ -19,25 +19,37 @@ By encapsulating our application as a component, we can seamlessly integrate it You can access the live preview by clicking on this link: [πŸ”— Live Preview of Step 9](https://sap-samples.github.io/ui5-typescript-walkthrough/build/09/index-cdn.html). -After this step your project structure will look like the figure below. We will create the `Component.ts` file now and modify the related files in the app. +After this step your project structure will look like the figure below. We will create the `Component.?s` file now and modify the related files in the app. ![](assets/loio1e237a36972a44ac8522dd1a540ac062_LowRes.png "Folder Structure for this Step") *Folder Structure for this Step* -Download solution for step 9 in [πŸ“₯ TypeScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-09.zip) or [πŸ“₯ JavaScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-09-js.zip). *** +### Coding +
-### webapp/Component.ts \(New\) +You can download the solution for this step here: [πŸ“₯ Download step 9](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-09.zip). -We navigate to the `webapp` folder and place the `Component.ts` file to it. This file is commonly referred to as the component controller. A component is organized in a unique namespace \(which is synonymous with the application namespace\). All required and optional resources of the component have to be organized in the namespace of the component. +
+ +
+ +You can download the solution for this step here: [πŸ“₯ Download step 9](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-09-js.zip). + +
+*** + +### webapp/Component.?s \(New\) + +We navigate to the `webapp` folder and place the `Component.?s` file to it. This file is commonly referred to as the component controller. A component is organized in a unique namespace \(which is synonymous with the application namespace\). All required and optional resources of the component have to be organized in the namespace of the component. We define the component by extending `sap/ui/core/UIComponent` and supplement the component with additional metadata. Within the `interfaces` settings, we specify that the component should implement the `sap/ui/core/IAsyncContentCreation` interface. This allows the component to be generated asynchronously, which in turn sets the component's rootView and router configuration to async. -When the component is instantiated, OpenUI5 automatically calls the `init` function of the component. It's important to include a call to the `init` function of the base class by using the `super` keyword. In this section, we also instantiate our data model and the `i18n` model, similar to what we did earlier in the `onInit` function of our app controller. +When the component is instantiated, OpenUI5 automatically calls the `init` function of the component. It's obligatory to make the super call to the `init` function of the base class in the overridden `init` method. In this section, we also instantiate our data model and the `i18n` model, similar to what we did earlier in the `onInit` function of our app controller. -Finally we call the `createContent` hook method of the component. This method creates the content \(UI Control Tree\) of this component. Here, we create the view as we did in the `index.ts` file to set our app view as the root view of the component. +Finally we call the `createContent` hook method of the component. This method creates the content \(UI Control Tree\) of this component. Here, we create the view as we did in the `index.?s` file to set our app view as the root view of the component. ```ts import Control from "sap/ui/core/Control"; @@ -78,13 +90,52 @@ export default class Component extends UIComponent { }); }; }; + ``` +```js +sap.ui.define(["sap/ui/core/UIComponent", "sap/ui/core/mvc/XMLView", "sap/ui/model/json/JSONModel", "sap/ui/model/resource/ResourceModel"], function (UIComponent, XMLView, JSONModel, ResourceModel) { + "use strict"; + + const Component = UIComponent.extend("ui5.walkthrough.Component", { + metadata: { + "interfaces": ["sap.ui.core.IAsyncContentCreation"] + }, + init: function _init() { + // call the init function of the parent + UIComponent.prototype.init.call(this); + // set data model + const data = { + recipient: { + name: "World" + } + }; + const dataModel = new JSONModel(data); + this.setModel(dataModel); + + // set i18n model + const i18nModel = new ResourceModel({ + bundleName: "ui5.walkthrough.i18n.i18n" + }); + this.setModel(i18nModel, "i18n"); + }, + createContent: function _createContent() { + return XMLView.create({ + "viewName": "ui5.walkthrough.view.App", + "id": "app" + }); + } + }); + ; + return Component; +}); + +``` Be aware that the models are set directly on the component and not on the root view of the component. However, as nested controls automatically inherit the models from their parent controls, the models are available on the view as well. *** -### webapp/controller/App.controller.ts +### webapp/controller/App.controller.?s We delete the `onInit` function from the app controller; this is now done in the component controller. @@ -111,9 +162,32 @@ export default class AppController extends Controller { ``` +```js +sap.ui.define(["sap/m/MessageToast", "sap/ui/core/mvc/Controller"], function (MessageToast, Controller) { + "use strict"; + + /** + * @name ui5.walkthrough.controller.App + */ + const AppController = Controller.extend("ui5.walkthrough.controller.App", { + onShowHello: function _onShowHello() { + // read msg from i18n model + // functions with generic return values require casting + const resourceBundle = this.getView()?.getModel("i18n")?.getResourceBundle(); + const recipient = this.getView()?.getModel()?.getProperty("/recipient/name"); + const msg = resourceBundle.getText("helloMsg", [recipient]); + // show message + MessageToast.show(msg); + } + }); + ; + return AppController; +}); + +``` *** -#### webapp/index.ts +#### webapp/index.?s We'll replace the view with a UI component. To do this, we use a control called `ComponentContainer`. This control allows us to wrap a UI Component and place it in our HTML document. We configure this instance by providing the following options: @@ -141,13 +215,30 @@ new ComponentContainer({ autoPrefixId: true, async: true }).placeAt("content"); + ``` +```js +sap.ui.define(["sap/ui/core/ComponentContainer"], function (ComponentContainer) { + "use strict"; + + new ComponentContainer({ + id: "container", + name: "ui5.walkthrough", + settings: { + id: "walkthrough" + }, + autoPrefixId: true, + async: true + }).placeAt("content"); +}); + +``` *** ### Conventions -- The component is named `Component.js` or rather `Component.ts`. +- The component is named `Component.?s`. - Together with all UI assets of the app, the component is located in the `webapp` folder. diff --git a/steps/10/README.md b/steps/10/README.md index 292455f2..31357ee1 100644 --- a/steps/10/README.md +++ b/steps/10/README.md @@ -15,13 +15,23 @@ Instead of relying on a local HTML file for the bootstrap, the manifest is parse You can access the live preview by clicking on this link: [πŸ”— Live Preview of Step 10](https://sap-samples.github.io/ui5-typescript-walkthrough/build/10/index-cdn.html). -Download solution for step 10 in [πŸ“₯ TypeScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-10.zip) or [πŸ“₯ JavaScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-10-js.zip). - *** - ### Coding +
+ +You can download the solution for this step here: [πŸ“₯ Download step 10](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-10.zip). + +
+ +
+ +You can download the solution for this step here: [πŸ“₯ Download step 10](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-10-js.zip). + +
+*** + ### webapp/i18n/i18n.properties In our resource bundle, we include two new name-value pairs: `appTitle` for the title of our app and `appDescription` for a short description. We'll use these texts in our manifest file at a later stage. @@ -151,7 +161,7 @@ In our current scenario, we only have one model called `i18n`, which is a resour *** -### webapp/Component.ts +### webapp/Component.?s To apply the settings specified in the manifest to the component, we need to include the manifest in the component's metadata. To do this, we add a `manifest` property to the `metadata` section of the component and set it to "json". This property acts as a reference to the `manifest.json` file, which will be loaded and used. @@ -183,6 +193,36 @@ export default class Component extends UIComponent { this.setModel(dataModel); }; }; + +``` + +```js +sap.ui.define(["sap/ui/core/UIComponent", "sap/ui/model/json/JSONModel"], function (UIComponent, JSONModel) { + "use strict"; + + const Component = UIComponent.extend("ui5.walkthrough.Component", { + metadata: { + "interfaces": ["sap.ui.core.IAsyncContentCreation"], + "manifest": "json" + }, + init: function _init() { + // call the init function of the parent + UIComponent.prototype.init.call(this); + + // set data model + const data = { + recipient: { + name: "World" + } + }; + const dataModel = new JSONModel(data); + this.setModel(dataModel); + } + }); + ; + return Component; +}); + ``` *** @@ -219,7 +259,7 @@ It's worth noting that the `ComponentSupport` module enforces asynchronous loadi ``` -We can now delete our `index.ts` file, because our component is now initiated directly in the HTML markup. +We can now delete our `index.?s` file, because our component is now initiated directly in the HTML markup. *** diff --git a/steps/11/README.md b/steps/11/README.md index c691dc9f..bbcb57c5 100644 --- a/steps/11/README.md +++ b/steps/11/README.md @@ -14,11 +14,24 @@ After all the work on the app structure it’s time to improve the look of our a You can access the live preview by clicking on this link: [πŸ”— Live Preview of Step 11](https://sap-samples.github.io/ui5-typescript-walkthrough/build/11/index-cdn.html). -Download solution for step 11 in [πŸ“₯ TypeScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-11.zip) or [πŸ“₯ JavaScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-11-js.zip). + *** ### Coding +
+ +You can download the solution for this step here: [πŸ“₯ Download step 11](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-11.zip). + +
+ +
+ +You can download the solution for this step here: [πŸ“₯ Download step 11](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-11-js.zip). + +
+*** + ### webapp/i18n/i18n.properties We add new key/value pairs to the '# Hello Panel' section of our text bundle for the start page title and the panel title. diff --git a/steps/12/README.md b/steps/12/README.md index 59760b79..a99ab2af 100644 --- a/steps/12/README.md +++ b/steps/12/README.md @@ -14,12 +14,23 @@ Now we use a shell control as container for our app and use it as our new root e You can access the live preview by clicking on this link: [πŸ”— Live Preview of Step 12](https://sap-samples.github.io/ui5-typescript-walkthrough/build/12/index-cdn.html). -Download solution for step 12 in [πŸ“₯ TypeScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-12.zip) or [πŸ“₯ JavaScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-12-js.zip). - *** ### Coding +
+ +You can download the solution for this step here: [πŸ“₯ Download step 12](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-12.zip). + +
+ +
+ +You can download the solution for this step here: [πŸ“₯ Download step 12](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-12-js.zip). + +
+*** + ### webapp/view/App.view.xml In your App view, we put the `App` control inside a `sap/m/Shell` control. diff --git a/steps/13/README.md b/steps/13/README.md index 2b53a6a1..2b6f126f 100644 --- a/steps/13/README.md +++ b/steps/13/README.md @@ -16,12 +16,23 @@ Instead of manually adding CSS to the controls, we will use the standard classes You can access the live preview by clicking on this link: [πŸ”— Live Preview of Step 13](https://sap-samples.github.io/ui5-typescript-walkthrough/build/13/index-cdn.html). -Download solution for step 13 in [πŸ“₯ TypeScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-13.zip) or [πŸ“₯ JavaScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-13-js.zip). - *** ### Coding +
+ +You can download the solution for this step here: [πŸ“₯ Download step 13](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-13.zip). + +
+ +
+ +You can download the solution for this step here: [πŸ“₯ Download step 13](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-13-js.zip). + +
+*** + ### webapp/view/App.view.xml To layout the panel, we add the CSS class `sapUiResponsiveMargin` that will add some space around the panel. We have to set the width of the panel to `auto` since the margin would otherwise be added to the default width of 100% and exceed the page size. diff --git a/steps/14/README.md b/steps/14/README.md index f33e2884..d0a0a90a 100644 --- a/steps/14/README.md +++ b/steps/14/README.md @@ -2,6 +2,9 @@ Sometimes we need to define some more fine-granular layouts and this is when we can use the flexibility of CSS by adding custom style classes to controls and style them as we like. +> ### 🚨 Caution: +> As stated in the [Compatibility Rules](https://sdk.openui5.org/topic/91f087396f4d1014b6dd926db0e91070.html), the HTML and CSS generated by OpenUI5 is not part of the public API and may change in patch and minor releases. If you decide to override styles, you need to test and update your modifications each time OpenUI5 is updated. A prerequisite for this is that you have control over the version of OpenUI5 being used, for example in a standalone scenario. This is not possible when running your app in the SAP Fiori launchpad where OpenUI5 is centrally loaded for all apps. As such, SAP Fiori launchpad apps should not override styles. +   *** @@ -14,16 +17,23 @@ Sometimes we need to define some more fine-granular layouts and this is when we You can access the live preview by clicking on this link: [πŸ”— Live Preview of Step 14](https://sap-samples.github.io/ui5-typescript-walkthrough/build/14/index-cdn.html). -Download solution for step 14 in [πŸ“₯ TypeScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-14.zip) or [πŸ“₯ JavaScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-14-js.zip). - - -> ### 🚨 Caution: -> As stated in the [Compatibility Rules](https://sdk.openui5.org/topic/91f087396f4d1014b6dd926db0e91070.html), the HTML and CSS generated by OpenUI5 is not part of the public API and may change in patch and minor releases. If you decide to override styles, you need to test and update your modifications each time OpenUI5 is updated. A prerequisite for this is that you have control over the version of OpenUI5 being used, for example in a standalone scenario. This is not possible when running your app in the SAP Fiori launchpad where OpenUI5 is centrally loaded for all apps. As such, SAP Fiori launchpad apps should not override styles. *** ### Coding +
+ +You can download the solution for this step here: [πŸ“₯ Download step 14](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-14.zip). + +
+ +
+ +You can download the solution for this step here: [πŸ“₯ Download step 14](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-14-js.zip). + +
+*** ### webapp/css/style.css \(New\) @@ -49,7 +59,6 @@ html[dir="ltr"] .myAppDemoWT .myCustomButton.sapMBtn { font-weight: bold; } ``` -*** ### webapp/manifest.json @@ -74,8 +83,6 @@ We configure the CSS file to our app descriptor: In the `resources` section of t } ``` -*** - ### webapp/view/App.view.xml The app control is configured with our custom namespace class `myAppDemoWT`. This class has no styling rules set and is used in the definition of the CSS rules to define CSS selectors that are only valid for this app. @@ -120,7 +127,7 @@ To highlight the output text, we replace the text control by a `FormattedText` c
``` - +  The actual color now depends on the selected theme which ensures that the color always fits to the theme and is semantically clear. For a complete list of the available CSS class names, see [CSS Classes for Theme Parameters](https://sdk.openui5.org/topic/ea08f53503da42c19afd342f4b0c9ec7.html). *** @@ -140,7 +147,9 @@ The actual color now depends on the selected theme which ensures that the color **Previous:** [Step 13: Margins and Paddings](../13/README.md "Our app content is still glued to the corners of the letterbox. To fine-tune our layout, we can add margins and paddings to the controls that we added in the previous step.") +*** +**Related Information** [Descriptor for Applications, Components, and Libraries \(manifest.json\)](https://sdk.openui5.org/topic/be0cf40f61184b358b5faedaec98b2da.html "The descriptor for applications, components, and libraries (in short: app descriptor) is inspired by the WebApplication Manifest concept introduced by the W3C. The descriptor provides a central, machine-readable, and easy-to-access location for storing metadata associated with an application, an application component, or a library.") diff --git a/steps/15/README.md b/steps/15/README.md index ec7294f0..90133e31 100644 --- a/steps/15/README.md +++ b/steps/15/README.md @@ -14,16 +14,27 @@ Our panel content is getting more and more complex and now it is time to move th You can access the live preview by clicking on this link: [πŸ”— Live Preview of Step 15](https://sap-samples.github.io/ui5-typescript-walkthrough/build/15/index-cdn.html). -Download solution for step 15 in [πŸ“₯ TypeScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-15.zip) or [πŸ“₯ JavaScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-15-js.zip). - *** ### Coding -### webapp/controller/HelloPanel.controller.ts \(New\) +
+ +You can download the solution for this step here: [πŸ“₯ Download step 15](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-15.zip). + +
-In folder `webapp/controller` we create a new `HelloPanel.controller.ts` file and move the method `onShowHello` of the app controller to it, so we get a reusable asset. +
+ +You can download the solution for this step here: [πŸ“₯ Download step 15](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-15-js.zip). + +
+*** + +### webapp/controller/HelloPanel.controller.?s \(New\) + +In folder `webapp/controller` we create a new `HelloPanel.controller.?s` file and move the method `onShowHello` of the app controller to it, so we get a reusable asset. ```ts import Controller from "sap/ui/core/mvc/Controller"; @@ -47,6 +58,27 @@ export default class HelloPanel extends Controller { MessageToast.show(msg); } }; + +``` + +```js +sap.ui.define(["sap/ui/core/mvc/Controller", "sap/m/MessageToast"], function (Controller, MessageToast) { + "use strict"; + + const HelloPanel = Controller.extend("ui5.walkthrough.controller.HelloPanel", { + onShowHello: function _onShowHello() { + // read msg from i18n model + const recipient = this.getView()?.getModel()?.getProperty("/recipient/name"); + const resourceBundle = this.getView()?.getModel("i18n")?.getResourceBundle(); + const msg = resourceBundle.getText("helloMsg", [recipient]); + // show message + MessageToast.show(msg); + } + }); + ; + return HelloPanel; +}); + ``` ### webapp/view/HelloPanel.view.xml \(New\) @@ -78,7 +110,6 @@ We create a new `HelloPanel.view.xml` file in folder `webapp/view` and move the ``` -*** ### webapp/view/App.view.xml @@ -104,9 +135,7 @@ In the App view, we remove the panel control and its content and put the `XMLVie ``` -*** - -### webapp/controller/App.controller.ts +### webapp/controller/App.controller.?s We remove the `onShowHello` method from the App controller, as this is not needed anymore. @@ -118,8 +147,21 @@ import Controller from "sap/ui/core/mvc/Controller"; export default class App extends Controller { }; + ``` +```js +sap.ui.define(["sap/ui/core/mvc/Controller"], function (Controller) { + "use strict"; + + const App = Controller.extend("ui5.walkthrough.controller.App", {}); + ; + return App; +}); + +``` +  + We have now moved everything out of the app view and controller. The app controller remains an empty stub for now, we will use it later to add more functionality.   diff --git a/steps/16/README.md b/steps/16/README.md index 3c5cd55c..2d405599 100644 --- a/steps/16/README.md +++ b/steps/16/README.md @@ -4,9 +4,9 @@ In this step, we will take a closer look at another element which can be used to Fragments are light-weight UI parts \(UI subtrees\) which can be reused but do not have any controller. This means, whenever you want to define a certain part of your UI to be reusable across multiple views, or when you want to exchange some parts of a view against one another under certain circumstances \(different user roles, edit mode vs read-only mode\), a fragment is a good candidate, especially where no additional controller logic is required. -A fragment can consist of 1 to n controls. At runtime, fragments placed in a view behave similar to "normal" view content, which means controls inside the fragment will just be included into the view’s DOM when rendered. There are of course controls that are not designed to become part of a view, for example, dialogs. But even for these controls, fragments can be particularly useful, as you will see in a minute. +A fragment can consist of any number of controls. At runtime, fragments placed in a view behave similar to "normal" view content, which means controls inside the fragment will just be included into the view’s DOM when rendered. There are controls that are not designed to become part of a view, for example, dialogs. But even for these controls, fragments can be particularly useful, as you will see in a minute. -We will now add a dialog to our app. Dialogs are special, because they open on top of the regular app content and thus do not belong to a specific view. That means the dialog must be instantiated somewhere in the controller code, but since we want to stick with the declarative approach and create reusable artifacts to be as flexible as possible, we will create an XML fragment containing the dialog. A dialog, after all, can be used in more than one view of your app. +We will now add a dialog to our app. Dialogs are special, because they open on top of the regular app content and thus are not part of a specific view. That means the dialog must be instantiated somewhere in the controller code, but since we want to stick with the declarative approach and create reusable artifacts to be as flexible as possible, we will create an XML fragment containing the dialog. A dialog, after all, can be used in more than one view of your app.   @@ -20,16 +20,27 @@ We will now add a dialog to our app. Dialogs are special, because they open on t You can access the live preview by clicking on this link: [πŸ”— Live Preview of Step 16](https://sap-samples.github.io/ui5-typescript-walkthrough/build/16/index-cdn.html). -Download solution for step 16 in [πŸ“₯ TypeScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-16.zip) or [πŸ“₯ JavaScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-16-js.zip). - *** ### Coding +
+ +You can download the solution for this step here: [πŸ“₯ Download step 16](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-16.zip). + +
+ +
+ +You can download the solution for this step here: [πŸ“₯ Download step 16](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-16-js.zip). + +
+*** + ### webapp/view/HelloDialog.fragment.xml \(New\) -We add a new XML file to declaratively define our dialog in a fragment. The fragment assets are located in the `core` namespace, so we add an `xml` namespace for it inside the `FragmentDefinition` tag. +We add a new XML file to declaratively define our dialog in a fragment. The FragmentDefinition element is located in the `sap.ui.core` library, so we add an `xml` namespace for it inside the `FragmentDefinition` tag. ```xml ``` - +  The syntax is similar to a view, but since fragments do not have a controller this attribute is missing. Also, the fragment does not have any footprint in the DOM tree of the app, and there is no control instance of the fragment itself (only the contained controls). It is simply a container for a set of reuse controls. -*** - -### webapp/controller/HelloPanel.controller.ts +### webapp/controller/HelloPanel.controller.?s -In the HelloPanel controller, we define a new event handler function `onOpenDialog` which calls the dialog in the HelloDialog fragment when triggered. To do so we need to import the `sap.m.Dialog` module. +In the HelloPanel controller, we define a new event handler function `onOpenDialog` which calls the dialog in the HelloDialog fragment when triggered. To do so we need the `sap.m.Dialog` module. Using async/await, we handle the opening of the dialog asynchronously whenever the event is triggered. @@ -77,8 +86,29 @@ export default class HelloPanel extends Controller { this.dialog.open(); } }; + ``` +```js +sap.ui.define(["sap/ui/core/mvc/Controller", "sap/m/MessageToast"], function (Controller, MessageToast) { + "use strict"; + + const HelloPanel = Controller.extend("ui5.walkthrough.controller.HelloPanel", { + onShowHello: function _onShowHello() { + ... + }, + onOpenDialog: async function _onOpenDialog() { + this.dialog ??= await this.loadFragment({ + name: "ui5.walkthrough.view.HelloDialog" + }); + this.dialog.open(); + } + }); + ; + return HelloPanel; +}); +``` +  > πŸ’‘ **Tip:**
> To reuse the dialog opening and closing functionality in other controllers, you might create a new file `ui5.walkthrough.controller.controller.BaseController`, which extends `sap.ui.core.mvc.Controller`, and put all your dialog-related coding into this controller. Now, all the other controllers can extend from `ui5.walkthrough.controller.BaseController` instead of `sap.ui.core.mvc.Controller`. @@ -131,10 +161,10 @@ We add a new button to the view to open the dialog and assign an unique `id`to i ``` - +  You will need the id of the button control `id="helloDialogButton"` in [Step 28: Integration Test with OPA](../28/README.md). -It is a good practice to set a unique ID like `helloWorldButton` to key controls of your app so that they can be identified easily. If the attribute `id` is not specified, the OpenUI5 runtime generates unique but changing ID like `\_\_button23` for the control. Inspect the DOM elements of your app in the browser to see the difference. +It is a good practice to set a unique ID like `helloWorldButton` to key controls of your app so that they can be identified easily. If the attribute `id` is not specified, the OpenUI5 runtime generates unique but changing ID like `__button23` for the control. Inspect the DOM elements of your app in the browser to see the difference.   diff --git a/steps/17/README.md b/steps/17/README.md index f79b6b33..c1fc953e 100644 --- a/steps/17/README.md +++ b/steps/17/README.md @@ -13,14 +13,26 @@ Now that we have integrated the dialog, it's time to add some user interaction. *The dialog now has an "OK" button to close the dialog* You can access the live preview by clicking on this link: [πŸ”— Live Preview of Step 17](https://sap-samples.github.io/ui5-typescript-walkthrough/build/17/index-cdn.html). - -Download solution for step 17 in [πŸ“₯ TypeScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-17.zip) or [πŸ“₯ JavaScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-17-js.zip). +*** ### Coding -### webapp/controller/HelloPanel.controller.ts +
+ +You can download the solution for this step here: [πŸ“₯ Download step 17](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-17.zip). + +
+ +
-We add an event handler function into the HelloPanel controller file that closes the dialog when triggered. To get the dialog instance we use the `byId` function and then call the `close` function of the dialog. +You can download the solution for this step here: [πŸ“₯ Download step 17](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-17-js.zip). + +
+*** + +### webapp/controller/HelloPanel.controller.?s + +We add an `onCloseDialog` event handler function into the HelloPanel controller file that closes the dialog when triggered. To get the dialog instance we use the `byId` function and then call the `close` function of the dialog. ```ts import Controller from "sap/ui/core/mvc/Controller"; @@ -34,23 +46,56 @@ import Dialog from "sap/m/Dialog"; * @namespace ui5.walkthrough.controller */ export default class HelloPanel extends Controller { - - + private dialog: Dialog; onShowHello(): void { - ... + // read msg from i18n model + const recipient = (this.getView()?.getModel() as JSONModel)?.getProperty("/recipient/name"); + const resourceBundle = (this.getView()?.getModel("i18n") as ResourceModel)?.getResourceBundle() as ResourceBundle; + const msg = resourceBundle.getText("helloMsg", [recipient]); + // show message + MessageToast.show(msg); } async onOpenDialog(): Promise { - ... + this.dialog ??= await this.loadFragment({ + name: "ui5.walkthrough.view.HelloDialog" + }) as Dialog; + this.dialog.open(); } onCloseDialog(): void { - // note: We don't need to chain to the pDialog promise, since this event-handler - // is only called from within the loaded dialog itself. (this.byId("helloDialog") as Dialog)?.close(); - } + } }; + ``` -*** +```js +sap.ui.define(["sap/ui/core/mvc/Controller", "sap/m/MessageToast"], function (Controller, MessageToast) { + "use strict"; + + const HelloPanel = Controller.extend("ui5.walkthrough.controller.HelloPanel", { + onShowHello: function _onShowHello() { + // read msg from i18n model + const recipient = this.getView()?.getModel()?.getProperty("/recipient/name"); + const resourceBundle = this.getView()?.getModel("i18n")?.getResourceBundle(); + const msg = resourceBundle.getText("helloMsg", [recipient]); + // show message + MessageToast.show(msg); + }, + onOpenDialog: async function _onOpenDialog() { + this.dialog ??= await this.loadFragment({ + name: "ui5.walkthrough.view.HelloDialog" + }); + this.dialog.open(); + }, + onCloseDialog: function _onCloseDialog() { + this.byId("helloDialog")?.close(); + } + }); + ; + return HelloPanel; +}); + +``` ### webapp/i18n/i18n.properties @@ -68,8 +113,6 @@ openDialogButtonText=Say Hello With Dialog dialogCloseButtonText=Ok ``` -*** - ### webapp/view/HelloDialog.fragment.xml In the fragment definition, we add a button to the `beginButton` aggregation of the dialog and refer the press handler to the event handler we just defined in the controller of the panel’s content view. @@ -89,8 +132,8 @@ In the fragment definition, we add a button to the `beginButton` aggregation of ``` - -By using the `loadFragment` function to create the fragment content in the controller of the panel’s content view, the method will be invoked there when the button is pressed. The dialog has an aggregation named `beginButton` as well as `endButton`. Placing buttons in both of these aggregations makes sure that the `beginButton` is placed before the `endButton` on the UI. What `before` means, however, depends on the text direction of the current language. We therefore use the terms `begin` and `end` as a synonym to β€œleft” and β€œright". In languages with left-to-right direction, the `beginButton` will be rendered left, the `endButton` on the right side of the dialog footer; in right-to-left mode for specific languages the order is switched. +  +By using the `loadFragment` function to create the fragment content in the controller of the panel’s content view, the method will be invoked there when the button is pressed. The dialog has an aggregation named `beginButton` as well as `endButton`. Placing buttons in both of these aggregations makes sure that the `beginButton` is placed before the `endButton` on the UI. What `before` means, however, depends on the text direction of the current language. We therefore use the terms `begin` and `end` as a synonym to "left" and "right". In languages with left-to-right direction, the `beginButton` will be rendered left, the `endButton` on the right side; in right-to-left mode for specific languages the order is switched.   diff --git a/steps/17/webapp/controller/HelloPanel.controller.ts b/steps/17/webapp/controller/HelloPanel.controller.ts index 9b845783..68c6fe50 100644 --- a/steps/17/webapp/controller/HelloPanel.controller.ts +++ b/steps/17/webapp/controller/HelloPanel.controller.ts @@ -25,8 +25,6 @@ export default class HelloPanel extends Controller { this.dialog.open(); } onCloseDialog(): void { - // note: We don't need to chain to the pDialog promise, since this event-handler - // is only called from within the loaded dialog itself. (this.byId("helloDialog") as Dialog)?.close(); } }; diff --git a/steps/18/README.md b/steps/18/README.md index 16ff76c2..8b280d8f 100644 --- a/steps/18/README.md +++ b/steps/18/README.md @@ -13,13 +13,23 @@ Our dialog is still pretty much empty. Since OpenUI5 is shipped with a large ico *An icon is now displayed in the dialog box* You can access the live preview by clicking on this link: [πŸ”— Live Preview of Step 18](https://sap-samples.github.io/ui5-typescript-walkthrough/build/18/index-cdn.html). - -Download solution for step 15 in [πŸ“₯ TypeScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-18.zip) or [πŸ“₯ JavaScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-18-js.zip). - *** ### Coding +
+ +You can download the solution for this step here: [πŸ“₯ Download step 18](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-18.zip). + +
+ +
+ +You can download the solution for this step here: [πŸ“₯ Download step 18](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-18-js.zip). + +
+*** + ### webapp/view/HelloPanel.view.xml We add an icon to the button that opens the dialog. The `sap-icon://` protocol is indicating that an icon from the *SAP icon font* should be loaded. The identifier `world` is the readable name of the icon in the icon font. @@ -55,12 +65,11 @@ We add an icon to the button that opens the dialog. The `sap-icon://` protocol i ``` - +  >πŸ’‘ **Tip:**
> You can look up other icons using the [Icon Explorer tool](https://sdk.openui5.org/test-resources/sap/m/demokit/iconExplorer/webapp/index.html). > To call any icon, use its name as listed in the *Icon Explorer* in sap-icon://<iconname>. -*** ### webapp/view/HelloDialog.fragment.xml diff --git a/steps/19/README.md b/steps/19/README.md index 2b8623da..125da14e 100644 --- a/steps/19/README.md +++ b/steps/19/README.md @@ -1,7 +1,6 @@ ## Step 19: Aggregation Binding Now that we have established a good structure for our app, it's time to add some more functionality. We start exploring more features of data binding by adding some invoice data in JSON format that we display in a list below the panel. -   *** @@ -14,12 +13,23 @@ Now that we have established a good structure for our app, it's time to add some You can access the live preview by clicking on this link: [πŸ”— Live Preview of Step 19](https://sap-samples.github.io/ui5-typescript-walkthrough/build/19/index-cdn.html). -Download solution for step 19 in [πŸ“₯ TypeScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-19.zip) or [πŸ“₯ JavaScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-19-js.zip). - *** ### Coding +
+ +You can download the solution for this step here: [πŸ“₯ Download step 19](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-19.zip). + +
+ +
+ +You can download the solution for this step here: [πŸ“₯ Download step 19](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-19-js.zip). + +
+*** + ### webapp/model/localInvoices.json \(New\) We create a new folder `model` in our app project and place the new `localInvoices.json` file in it. We describe the following JSON data in the file: @@ -70,16 +80,13 @@ We create a new folder `model` in our app project and place the new `localInvoic ] } ``` - +  The `localinvoices` file simply contains five invoices in a JSON format that we can use to bind controls against them in the app. JSON is a very lightweight format for storing data and can be directly used as a data source for OpenUI5 applications. -*** - ### webapp/manifest.json We add a new named model `invoice` to the `sap.ui5` section of the descriptor. This time we want a JSONModel, so we set the type to `sap.ui.model.json.JSONModel`. The `uri` key is the path to our data relative to the component. - ```json { ... @@ -111,11 +118,9 @@ We add a new named model `invoice` to the `sap.ui5` section of the descriptor. T } } ``` - +  With this little configuration our component will automatically instantiate a new `JSONModel` which loads the invoice data from the `localInvoices.json` file. Finally, the instantiated `JSONModel` is put onto the component as a named model invoice. The named model is then visible throughout our app. -*** - ### webapp/i18n/i18n.properties In the text bundle we create a new text for a Invoice List title we will need in the view we are about to create. @@ -156,11 +161,9 @@ In the `items` aggregation, we define the template for the list that will be aut ``` - +  The binding in the list item works, because we have bound the `items` aggregation via `items={invoice>/Invoices}` to the invoices. -*** - ### webapp/view/App.view.xml In the app view we add a second view and assign it to our newly created InvoiceList view to display our invoices below the panel. @@ -216,6 +219,4 @@ In the app view we add a second view and assign it to our newly created InvoiceL [API Reference: sap.m.List](https://sdk.openui5.org/#/api/sap.m.List) -[Samples: sap.m.List](https://sdk.openui5.org/#/entity/sap.m.List) - - +[Samples: sap.m.List](https://sdk.openui5.org/#/entity/sap.m.List) \ No newline at end of file diff --git a/steps/20/README.md b/steps/20/README.md index 6a0cebe6..08a3cb0f 100644 --- a/steps/20/README.md +++ b/steps/20/README.md @@ -13,14 +13,28 @@ The list of invoices is already looking nice, but what is an invoice without a p *The list of invoices with prices and number units* You can access the live preview by clicking on this link: [πŸ”— Live Preview of Step 20](https://sap-samples.github.io/ui5-typescript-walkthrough/build/20/index-cdn.html). - -Download solution for step 20 in [πŸ“₯ TypeScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-20.zip) or [πŸ“₯ JavaScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-20-js.zip). +*** ### Coding -### webapp/controller/InvoiceList.controller.js \(New\) +
+ +You can download the solution for this step here: [πŸ“₯ Download step 20](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-20.zip). + +
+ +
+ +You can download the solution for this step here: [πŸ“₯ Download step 20](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-20-js.zip). + +
+*** + +### webapp/controller/InvoiceList.controller.?s \(New\) -We want to display in our list view the price in Euro, and typically the currency is part of our data model on the back end. In our case this is not the case, so we need to define it directly in the app. We therefore create a controller for the invoice list and define a view model for the currency code for Euro. It is a simple JSON model with just one key `currency` and the value `EUR`. +We want to display in our list view the price in Euro. Since currency information isn't available in our backend data model, we'll handle the currency formatting within the application. + +We'll create a controller for the InvoiceList view and use a JSON model (`sap/ui/model/json/JSONModel`) to store the currency code. This model will contain a single property, `currency: "EUR"`, which will be used for formatting the prices in the view. ```ts import Controller from "sap/ui/core/mvc/Controller"; @@ -38,9 +52,26 @@ export default class App extends Controller { this.getView()?.setModel(viewModel, "view"); } }; + ``` -*** +```js +sap.ui.define(["sap/ui/core/mvc/Controller", "sap/ui/model/json/JSONModel"], function (Controller, JSONModel) { + "use strict"; + + const App = Controller.extend("ui5.walkthrough.controller.App", { + onInit: function _onInit() { + const viewModel = new JSONModel({ + currency: "EUR" + }); + this.getView()?.setModel(viewModel, "view"); + } + }); + ; + return App; +}); + +``` ### webapp/view/InvoiceList.view.xml diff --git a/steps/21/README.md b/steps/21/README.md index c5fba92f..fe906516 100644 --- a/steps/21/README.md +++ b/steps/21/README.md @@ -14,12 +14,23 @@ Sometimes the predefined types of OpenUI5 are not flexible enough and you want t You can access the live preview by clicking on this link: [πŸ”— Live Preview of Step 21](https://sap-samples.github.io/ui5-typescript-walkthrough/build/21/index-cdn.html). -Download solution for step 21 in [πŸ“₯ TypeScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-21.zip) or [πŸ“₯ JavaScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-21-js.zip). - *** ### Coding +
+ +You can download the solution for this step here: [πŸ“₯ Download step 21](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-21.zip). + +
+ +
+ +You can download the solution for this step here: [πŸ“₯ Download step 21](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-21-js.zip). + +
+*** + ### webapp/view/InvoiceList.view.xml We add the `numberState` attribute to the `ObjectListItem` control in our invoices list view. We use the '=' symbol to initiate an expression binding and specify that the number in `numberState` appears in red, in case the price is greater than 50, otherwise in green. diff --git a/steps/22/README.md b/steps/22/README.md index 266661e4..39683e0a 100644 --- a/steps/22/README.md +++ b/steps/22/README.md @@ -14,15 +14,26 @@ If we want to do a more complex logic for formatting properties of our data mode You can access the live preview by clicking on this link: [πŸ”— Live Preview of Step 22](https://sap-samples.github.io/ui5-typescript-walkthrough/build/22/index-cdn.html). -Download solution for step 22 in [πŸ“₯ TypeScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-22.zip) or [πŸ“₯ JavaScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-22-js.zip). - *** ### Coding +
+ +You can download the solution for this step here: [πŸ“₯ Download step 22](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-22.zip). + +
+ +
+ +You can download the solution for this step here: [πŸ“₯ Download step 22](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-22-js.zip). + +
+*** + ### webapp/i18n/i18n.properties -We add three new entries to the resource bundle that reflect translated status texts 'New', 'In Progess', and 'Done'. We will use these texts to format the status values 'A', 'B', and 'C' of our invoices when displayed in the invoice list view. +We will add three new entries to the resource bundle that reflect translated status texts 'New', 'In Progess', and 'Done'. We will use these texts to format the status values 'A', 'B', and 'C' of our invoices when displayed in the invoice list view. ### webapp/i18n/i18n.properties @@ -36,11 +47,11 @@ invoiceStatusB=In Progress invoiceStatusC=Done ``` -*** +### webapp/model/formatter.?s \(New\) -### webapp/model/formatter.ts \(New\) +We will create a formatter function to transform status codes into user-friendly text labels. -We place a new `formatter.ts` in the model folder of the app. This time we do not need to extend from any base object, but just return an object with our `formatter` functions in it. We add a `statusText` function, that gets a status as input parameter and returns a human-readable text that is read from the `resourceBundle` file. +We create a file named `formatter.?s` within the `model` folder. This module contains the `statusText` function which takes a status code as input, retrieves the corresponding descriptive text from the resource bundle, and returns it. If no matching text is found in the resource bundle, or if the resource bundle can't be found, the function returns the original status code itself. ```ts import ResourceBundle from "sap/base/i18n/ResourceBundle"; @@ -65,8 +76,35 @@ export default { ``` -The new `formatter` file is placed in the model folder of the app, because formatters are working on data properties and format them for display on the UI. This time we do not extend from any base object but just return an object with our `formatter` functions inside. +```js +sap.ui.define([], function () { + "use strict"; + + var __exports = { + statusText: function (status) { + const resourceBundle = this?.getOwnerComponent()?.getModel("i18n")?.getResourceBundle(); + switch (status) { + case "A": + return resourceBundle.getText("invoiceStatusA"); + case "B": + return resourceBundle.getText("invoiceStatusB"); + case "C": + return resourceBundle.getText("invoiceStatusC"); + default: + return status; + } + } + }; + return __exports; +}); + +``` +  +This time we do not extend from any base object but just return an object with our `formatter` functions inside. + +The new `formatter` file is placed in the model folder of the app, because formatters are working on data properties and format them for display on the UI. +  >πŸ“Œ **Important:**
> In the above example, `this` refers to the controller instance as soon as the formatter gets called. We access the resource bundle via the component using `this.getOwnerComponent().getModel()` instead of using `this.getView().getModel()`. The latter call might return `undefined`, because the view might not have been attached to the component yet, and thus the view can't inherit a model from the component. @@ -76,13 +114,9 @@ The new `formatter` file is placed in the model folder of the app, because forma - [API Reference: `sap.ui.core.mvc.Controller#getOwnerComponent`](https://sdk.openui5.org/#/api/sap.ui.core.mvc.Controller/methods/getOwnerComponent). - [API Reference: `sap.ui.core.mvc.Controller#onInit`](https://sdk.openui5.org/#/api/sap.ui.core.mvc.Controller/methods/onInit). -*** - ### webapp/view/InvoiceList.view.xml -To load our formatter functions, we use the `require` attribute with the namespace URI `sap.ui.core`, for which the prefix `core` is already defined in our XML view. This allows us to write the attribute as `core:require`. We then add our custom formatter module to the list of required modules and assign it the alias `Formatter`, making it available for use within the view. - -We add a status using the `firstStatus` aggregation to our `ObjectListItem` that will display the status of our invoice. There we defined our alias `Formatter` that holds our formatter functions, so we can access it by `Formatter.statusText`. When called, we want the `this` context to be set to the controller instance of the current view. To achieve this, we use `.bind($controller)`. +We add the `ObjectStatus` control to our `ObjectListItem` using the `firstStatus` aggregation. We bind the control not only to the technical status but also to the `statusText` function in our formatter to displaly the human-readable texts per invoice we specified in our resource bundle. ```xml ``` +  +We used the `require` attribute with the namespace URI `sap.ui.core`, for which the prefix `core` is already defined in our XML view. This allows us to write the attribute as `core:require`. We then added our custom formatter module to the list of required modules and assigned it the alias `Formatter`, making it available for use within the view. -Instead of a technical status we get now the human-readable texts per invoice we specified in our resource bundle below the `number` attribute of the `ObjectListItem`. +in the `ObjectStatus` control we defined our alias `Formatter` that holds our formatter functions, so we can access our function by `Formatter.statusText`. When called, we want the `this` context to be set to the controller instance of the current view. To achieve this, we used `.bind($controller)`.   @@ -147,3 +183,5 @@ Instead of a technical status we get now the human-readable texts per invoice we [Formatting, Parsing, and Validating Data](https://sdk.openui5.org/topic/07e4b920f5734fd78fdaa236f26236d8.html "Data that is presented on the UI often has to be converted so that is human readable and fits to the locale of the user. On the other hand, data entered by the user has to be parsed and validated to be understood by the data source. For this purpose, you use formatters and data types.") [Require Modules in XML View and Fragment](https://sdk.openui5.org/topic/b11d853a8e784db6b2d210ef57b0f7d7.html "Modules can be required in XML views and fragments and assigned to aliases which can be used as variables in properties, event handlers, and bindings.") + +[API Reference: `sap.ui.base.ManagedObject.PropertyBindingInfo`](https://sdk.openui5.org/api/sap.ui.base.ManagedObject.PropertyBindingInfo#overview) diff --git a/steps/23/README.md b/steps/23/README.md index cb0e1636..14beaa5f 100644 --- a/steps/23/README.md +++ b/steps/23/README.md @@ -14,22 +14,32 @@ In this step, we add a search field for our product list and define a filter tha You can access the live preview by clicking on this link: [πŸ”— Live Preview of Step 23](https://sap-samples.github.io/ui5-typescript-walkthrough/build/23/index-cdn.html). -Download solution for step 23 in [πŸ“₯ TypeScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-23.zip) or [πŸ“₯ JavaScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-23-js.zip). - *** ### Coding -### webapp/controller/InvoiceList.controller.ts +
+ +You can download the solution for this step here: [πŸ“₯ Download step 23](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-23.zip). + +
+ +
+ +You can download the solution for this step here: [πŸ“₯ Download step 23](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-23-js.zip). -In the controller of the invoice list view we add a new `onFilterInvoices` event handler function with the `event` of type `SearchField$SearchEvent` as import parameter. -The search field defines a parameter `query` that we access by calling `getParameter("query")` on the `event` parameter. +
+*** + +### webapp/controller/InvoiceList.controller.?s + +We will implement a new `onFilterInvoices` event handler function to our controller. This function will enable users to filter the invoice list based on a search term entered in a `sap/m/SearchField` control. -If the query is not empty, we add a new filter object that searches in the `ProductName` for a given query string with filter operator `Contains`. The filter operator `FilterOperator.Contains` is **not** case-sensitive. +In the in the event handler, we create a filter object for the search term with filter operator `Contains` that targets the `ProductName` property of the invoice data. -We get the invoice list by asking the view for the control with the ID "invoiceList". To achieve this we make use of the helper function `byId`. On the list control we access the binding of the aggregation `items` to filter it with our newly constructed filter object. This will automatically filter the list by our search string so that only the matching items are shown when the search is triggered. +If the query is empty, we filter the binding with an empty array. This will make sure that we see all list elements again. -If the query is empty, we filter the binding with an empty array. This makes sure that we see all list elements again. We could also add more filters to the array, if we wanted to search more than one data field. +Finally we apply the filter to the items binding of the invoice list in our view, updating the displayed items. ```ts import Controller from "sap/ui/core/mvc/Controller"; @@ -67,8 +77,54 @@ export default class App extends Controller { binding?.filter(filter); } }; + +``` + +```js +sap.ui.define(["sap/ui/core/mvc/Controller", "sap/ui/model/json/JSONModel", "sap/ui/model/Filter", "sap/ui/model/FilterOperator"], function (Controller, JSONModel, Filter, FilterOperator) { + "use strict"; + + const App = Controller.extend("ui5.walkthrough.controller.App", { + onInit: function _onInit() { + const viewModel = new JSONModel({ + currency: "EUR" + }); + this.getView()?.setModel(viewModel, "view"); + }, + onFilterInvoices: function _onFilterInvoices(event) { + // build filter array + const filter = []; + const query = event.getParameter("query"); + if (query) { + filter.push(new Filter("ProductName", FilterOperator.Contains, query)); + } + + // filter binding + const list = this.byId("invoiceList"); + const binding = list?.getBinding("items"); + binding?.filter(filter); + } + }); + ; + return App; +}); + ``` +  +The `onFilterInvoices` function is triggered by a `SearchField$SearchEvent`(an event generated by the search field when the user enters a search term and presses Enter or clicks the search icon). Event handlers always receive an event argument. This argument provides access to the event's specific parameters. + +We use `event.getParameter("query")` to extract the text entered by the user in the search field. The `"query"` parameter is a standard property of the `SearchField$SearchEvent`. +If the query is not empty, we create a new `sap/ui/model/Filter` object. This filter object is configured as follows: +- `"ProductName"` (path): Specifies the data property to filter on. In this case, we're filtering the ProductName property of each invoice item. +- `FilterOperator.Contains` (operator): Specifies the filtering logic. FilterOperator.Contains checks if the ProductName contains the query string. Other operators are available (e.g., FilterOperator.Equals, FilterOperator.StartsWith). The FilterOperator is part of the sap/ui/model/FilterOperator module. The `FilterOperator.Contains` operator performs a case-insensitive search. +- `query` (value): The value to filter with – the search term entered by the user. + +The `byId` method is a helper function provided by the `sap/ui/core/mvc/Controller` class. It allows you to retrieve a control instance by its ID. Because the control ID is automatically prefixed with the view ID at runtime, we need to use `byId` to get a reference to the list control that's defined in the view. + +`list?.getBinding("items")` gets the binding object for the items aggregation of the `sap/m/List`. The items aggregation is the one bound to the list items. + +`binding?.filter(filter)` applies the filter (which is an array, even if it only contains one filter) to the list binding. This triggers the list to update its display, showing only the items that match the filter criteria. If the filter array is empty (because the query was empty), the `binding?.filter(filter)` call will effectively clear any existing filters, displaying all the items in the list again. ### webapp/view/InvoiceList.view.xml @@ -99,7 +155,7 @@ In addition, we remove the `headerText` property in the list control and use `he ``` - +  The search field is part of the list header and therefore, each change on the list binding will trigger a rerendering of the whole list, including the search field.   diff --git a/steps/24/README.md b/steps/24/README.md index fd460499..1865a922 100644 --- a/steps/24/README.md +++ b/steps/24/README.md @@ -15,12 +15,22 @@ To make our list of invoices even more user-friendly, we sort it alphabetically You can access the live preview by clicking on this link: [πŸ”— Live Preview of Step 24](https://sap-samples.github.io/ui5-typescript-walkthrough/build/24/index-cdn.html). -Download solution for step 24 in [πŸ“₯ TypeScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-24.zip) or [πŸ“₯ JavaScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-24-js.zip). - *** ### Coding +
+ +You can download the solution for this step here: [πŸ“₯ Download step 24](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-24.zip). + +
+ +
+ +You can download the solution for this step here: [πŸ“₯ Download step 24](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-24-js.zip). + +
+*** ### webapp/view/InvoiceList.view.xml @@ -49,8 +59,6 @@ By default, the sorting is ascending, but you could also add a property `descend If we run the app now we can see a list of invoices sorted by the name of the products. -*** - ### webapp/view/InvoiceList.view.xml We modify the view and change the sorter so the path addresses to the `ShipperName` data field instead of `ProductName`. This groups the invoice items by the shipping company. In addition set the sorter attribute `group` to true. diff --git a/steps/25/README.md b/steps/25/README.md index cfd46dff..a0ce97f7 100644 --- a/steps/25/README.md +++ b/steps/25/README.md @@ -14,12 +14,25 @@ In the real world, data often resides on remote servers and is accessed via an O ![](assets/loio5b76bb4b15eb44e1862d0b6c1c802571_LowRes.png "Products from the OData invoices test service are now shown within our app") ->*Products from the OData invoices test service are now shown within our app* +*Products from the OData invoices test service are now shown within our app* *A real-time preview utilizing data from the OData remote service is currently unavailable in this setup. However, we assure you that it will work on your local machine as long as you avoid making any mistakes. So, give it a try and see the results for yourself!* -Download solution for step 25 in [πŸ“₯ TypeScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-25.zip) or [πŸ“₯ JavaScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-25-js.zip). +*** + +### Coding + +
+ +You can download the solution for this step here: [πŸ“₯ Download step 25](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-25.zip). + +
+
+ +You can download the solution for this step here: [πŸ“₯ Download step 25](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-25-js.zip). + +
*** ### UI5 Tooling @@ -36,8 +49,6 @@ In this tutorial we'll use [ui5-middleware-simpleproxy](https://bestofui5.org/#/ Open a new terminal window in your app root folder and execute `npm i -D ui5-middleware-simpleproxy` to install this package as a new development dependency in your `package.json`. -*** - ### ui5.yaml We now configure the `ui5-middleware-simpleproxy` in the `ui5.yaml` file, so the proxy is used with the UI5 Tooling when serving the app. @@ -74,7 +85,6 @@ server: configuration: baseUri: "https://services.odata.org" ``` -*** ### manifest.json diff --git a/steps/26/README.md b/steps/26/README.md index b812746f..c1513031 100644 --- a/steps/26/README.md +++ b/steps/26/README.md @@ -11,26 +11,35 @@ This system is the so-called back-end system that we will now simulate with anOp ### Preview -![](assets/loiofe1403346ce9499f8bb102beaa4986d5_LowRes.png "Folder Structure for this Step") +![](assets/loiofe1403346ce9499f8bb102beaa4986d5_LowRes.png "The list of invoices is now served by the Mock Server") *The list of invoices is now served by the Mock Server* -You can access the live preview by clicking on this link: [πŸ”— Live Preview of Step 26](https://sap-samples.github.io/ui5-typescript-walkthrough/build/26/test/mockServer-cdn.html). +The folder structure of our app project is clearly separating test and productive files after this step. The new `test` folder now contains a new HTML page `mockServer.html` which will launch our application in test mode without calling the real service. + +The new `localService` folder contains a `metadata.xml` service description file for OData, the `mockserver.js` file that simulates a real service with local data, and the `mockdata` subfolder that contains the local test data \(`Invoices.json`\). -Download solution for step 26 in [πŸ“₯ TypeScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-26.zip) or [πŸ“₯ JavaScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-26-js.zip). +![](assets/loio7a5e2b02d72d40d388f5e601d7de74df_LowRes.png "Folder Structure for this Step") + +*Folder Structure for this Step* + +You can access the live preview by clicking on this link: [πŸ”— Live Preview of Step 26](https://sap-samples.github.io/ui5-typescript-walkthrough/build/26/test/mockServer-cdn.html). *** ### Coding +
-The folder structure of our app project is clearly separating test and productive files after this step. The new `test` folder now contains a new HTML page `mockServer.html` which will launch our application in test mode without calling the real service. +You can download the solution for this step here: [πŸ“₯ Download step 26](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-26.zip). -The new `localService` folder contains a `metadata.xml` service description file for OData, the `mockserver.js` file that simulates a real service with local data, and the `mockdata` subfolder that contains the local test data \(`Invoices.json`\). +
-![](assets/loiofe1403346ce9499f8bb102beaa4986d5_LowRes.png "Folder Structure for this Step") +
-*Folder Structure for this Step* +You can download the solution for this step here: [πŸ“₯ Download step 26](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-26-js.zip). +
+*** ### webapp/localService/metadata.xml \(New\) @@ -128,9 +137,9 @@ In folder `localService` we create the new folder `mockdata`. The mock server de This file will automatically be found and read by the mock server. *** -### webapp/localService/mockserver.ts \(New\) +### webapp/localService/mockserver.?s \(New\) -Now we can write the code to initialize the mock server which will then simulate any OData request to the real Northwind server. For this we add a new file `mockserver.ts` to the `localService` folder. +Now we can write the code to initialize the mock server which will then simulate any OData request to the real Northwind server. For this we add a new file `mockserver.?s` to the `localService` folder. We import the standard OpenUI5 `MockServer` module and create a helper object that defines an `init` method to start the server. The `init` method creates a `MockServer` instance with the same URL as the real service calls. The URL in the `rootUri` configuration parameter has to point to the same URL as defined in the `uri` property of the data source in the `manifest.json` descriptor file. In the `manifest.json`, OpenUI5 automatically interprets a relative URL as being relative to the application namespace. In the TypeScript code, you can ensure this by using the `sap.ui.require.toUrl` method. The `sap/ui/core/util/MockServer` then catches every request to the real service and returns a response. @@ -166,13 +175,43 @@ export default { mockServer.start(); } }; + ``` -*** +```js +sap.ui.define(["sap/ui/core/util/MockServer"], function (MockServer) { + "use strict"; + + var __exports = { + init: function () { + // create + const mockServer = new MockServer({ + rootUri: sap.ui.require.toUrl("ui5/walkthrough/V2/Northwind/Northwind.svc/") + }); + const urlParams = new URLSearchParams(window.location.search); + + // configure mock server with a delay + MockServer.config({ + autoRespond: true, + autoRespondAfter: parseInt(urlParams.get("serverDelay") || "500") + }); + + // simulate + const path = sap.ui.require.toUrl("ui5/walkthrough/localService"); + mockServer.simulate(path + "/metadata.xml", path + "/mockdata"); + + // start + mockServer.start(); + } + }; + return __exports; +}); + +``` -### webapp/test/initMockServer.ts \(New\) +### webapp/test/initMockServer.?s \(New\) -As a next step, we create a module that initializes our local `mockserver`. For this, we add the new `test` folder to our App folder where we place the new `initmockServer.ts` file. +As a next step, we create a module that initializes our local `mockserver`. For this, we add the new `test` folder to our App folder where we place the new `initmockServer.?s` file. First, we call the `init` method of our local `mockserver`, then we initialize the app component. @@ -184,8 +223,41 @@ mockserver.init(); // initialize the embedded component on the HTML page import("sap/ui/core/ComponentSupport"); + +``` + +```js +sap.ui.define(["../localService/mockserver"], function (__mockserver) { + "use strict"; + + function _interopRequireDefault(obj) { + return obj && obj.__esModule && typeof obj.default !== "undefined" ? obj.default : obj; + } + function __ui5_require_async(path) { + return new Promise(function (resolve, reject) { + sap.ui.require([path], function (module) { + if (!(module && module.__esModule)) { + module = module === null || !(typeof module === "object" && path.endsWith("/library")) ? { + default: module + } : module; + Object.defineProperty(module, "__esModule", { + value: true + }); + } + resolve(module); + }, function (err) { + reject(err); + }); + }); + } + const mockserver = _interopRequireDefault(__mockserver); // initialize the mock server + mockserver.init(); + + // initialize the embedded component on the HTML page + __ui5_require_async("sap/ui/core/ComponentSupport"); +}); + ``` -*** #### webapp/test/mockServer.html \(New\) @@ -219,7 +291,7 @@ Instead the app component, we define that the `initMockServer` is initialized fr ``` - +  When launching the app with the `mockServer.html` file the `initMockServer` is called, that first initializes our local mock server and only then our app component. This way we catch all requests that would go to the "real" service and process them locally by our mock server instead. The component itself does not "know" that it now runs in test mode. If you switch from the `index.html` file to the `mockServer.html` file in the browser, you can now see that the test data is displayed from the local sources, but with a short delay as defined in our local mock server configuration. @@ -228,8 +300,6 @@ This approach is perfect for local testing, even without any network connection. From this point on, we have two different entry pages: One for the real β€œconnected” app \(`index.html`\) and one for local testing \(`test/mockServer.html`\). You can freely decide if you want to do the next steps on the real service data or on the local data within the app. Try calling the app with the `index.html` file and the `mockServer.html` file to see the difference. If the real service connection cannot be made, for example when there is no network connection, you can always fall back to the local test page. -*** - ### UI5 Tooling ### package.json (optional) @@ -246,13 +316,7 @@ In case you prefer to continue with the local data, you should adjust the `start "start": "ui5 serve -o test/mockServer.html" }, "devDependencies": { - "@types/openui5": "^1.132.0", - "@ui5/cli": "^3.7.1", - "typescript": "^5.2.2", - "ui5-middleware-livereload": "^3.0.2", - "ui5-middleware-serveframework": "^3.0.0", - "ui5-middleware-simpleproxy": "^3.2.8", - "ui5-tooling-transpile": "^3.2.7" +... } } ``` diff --git a/steps/26/assets/loio7a5e2b02d72d40d388f5e601d7de74df_LowRes.png b/steps/26/assets/loio7a5e2b02d72d40d388f5e601d7de74df_LowRes.png new file mode 100644 index 00000000..09902422 Binary files /dev/null and b/steps/26/assets/loio7a5e2b02d72d40d388f5e601d7de74df_LowRes.png differ diff --git a/steps/27/README.md b/steps/27/README.md index 6fffe03e..bfa66bf5 100644 --- a/steps/27/README.md +++ b/steps/27/README.md @@ -17,24 +17,33 @@ Actually, every feature that we added to the app so far, would require a separat *A unit test for our formatters is now available* -You can access the live preview by clicking on this link: [πŸ”— Live Preview of Step 27](https://sap-samples.github.io/ui5-typescript-walkthrough/build/27/test/Test.cdn.qunit.html?testsuite=test-resources/ui5/walkthrough/testsuite.cdn.qunit&test=unit/unitTests). +We add a new folder `unit` under the `test` folder and a `model` subfolder where we will place our formatter unit test. The folder structure matches the app structure to easily find the corresponding unit tests. + +![](assets/loio1b5613ac3ab94757af2c7823039222a9_LowRes.png "Folder Structure for this Step") +*Folder Structure for this Step* -Download solution for step 27 in [πŸ“₯ TypeScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-27.zip) or [πŸ“₯ JavaScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-27-js.zip). +You can access the live preview by clicking on this link: [πŸ”— Live Preview of Step 27](https://sap-samples.github.io/ui5-typescript-walkthrough/build/27/test/Test.cdn.qunit.html?testsuite=test-resources/ui5/walkthrough/testsuite.cdn.qunit&test=unit/unitTests). *** ### Coding - -We add a new folder `unit` under the `test` folder and a `model` subfolder where we will place our formatter unit test. The folder structure matches the app structure to easily find the corresponding unit tests. - -![](assets/loio1b5613ac3ab94757af2c7823039222a9_LowRes.png "Folder Structure for this Step") -*Folder Structure for this Step* +
+ +You can download the solution for this step here: [πŸ“₯ Download step 27](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-27.zip). + +
+ +
+ +You can download the solution for this step here: [πŸ“₯ Download step 27](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-27-js.zip). + +
*** -### webapp/test/unit/model/formatter.ts \(New\) +### webapp/test/unit/model/formatter.?s \(New\) -We create a new `formatter.ts` file under `webapp/test/unit/model` where the unit test for the custom formatter is implemented. The formatter function that we want to test is from the `formatter.ts` file located in the `webapp/model` folder. +We create a new `formatter.?s` file under `webapp/test/unit/model` where the unit test for the custom formatter is implemented. The formatter function that we want to test is from the `formatter.ts` file located in the `webapp/model` folder. The new formatter file just contains one QUnit module for our formatter function and one unit test for the formatter function. In the implementation of the `statusText` function that we created in Step 23, we use the translated texts when calling the formatter. As we do not want to test the OpenUI5 binding functionality, we just use text in the test instead of a `ResourceBundle`. @@ -78,22 +87,65 @@ QUnit.test("Should return the translated texts", (assert) => { assert.strictEqual(fnIsolatedFormatter("C"), "Done", "The long text for status C is correct"); assert.strictEqual(fnIsolatedFormatter("Foo"), "Foo", "The long text for status Foo is correct"); }); + ``` -*** +```js +sap.ui.define(["sap/ui/model/resource/ResourceModel", "ui5/walkthrough/model/formatter"], function (ResourceModel, __formatter) { + "use strict"; + + function _interopRequireDefault(obj) { + return obj && obj.__esModule && typeof obj.default !== "undefined" ? obj.default : obj; + } + const formatter = _interopRequireDefault(__formatter); + QUnit.module("Formatting function", {}); + QUnit.test("Should return the translated texts", assert => { + const resourceModel = new ResourceModel({ + bundleUrl: sap.ui.require.toUrl("ui5/walkthrough/i18n/i18n.properties"), + supportedLocales: [""], + fallbackLocale: "" + }); + const controllerMock = { + getOwnerComponent() { + return { + getModel() { + return resourceModel; + } + }; + } + }; + + // System under test + const fnIsolatedFormatter = formatter.statusText.bind(controllerMock); + + // Assert + assert.strictEqual(fnIsolatedFormatter("A"), "New", "The long text for status A is correct"); + assert.strictEqual(fnIsolatedFormatter("B"), "In Progress", "The long text for status B is correct"); + assert.strictEqual(fnIsolatedFormatter("C"), "Done", "The long text for status C is correct"); + assert.strictEqual(fnIsolatedFormatter("Foo"), "Foo", "The long text for status Foo is correct"); + }); +}); + +``` -### webapp/test/unit/unitTests.qunit.ts \(New\) +### webapp/test/unit/unitTests.qunit.?s \(New\) -We create a new `unitTests.qunit.ts` file under `webapp/test/unit/`. +We create a new `unitTests.qunit.?s` file under `webapp/test/unit/`. This module will serve as the entry point for all our unit tests. It will be referenced in the test suite that we will set up later on. -Inside the `unitTests.qunit.ts` file, we import the unit test for the custom formatter. This ensures that any tests related to the custom formatter functionality will be included when running our unit tests. +Inside the `unitTests.qunit.?s` file, we import the unit test for the custom formatter. This ensures that any tests related to the custom formatter functionality will be included when running our unit tests. ```ts import "./model/formatter"; + ``` -*** +```js +sap.ui.define(["./model/formatter"], function (___model_formatter) { + "use strict"; +}); + +``` ### webapp/test/Test.qunit.html \(New\) @@ -122,13 +174,13 @@ The page will be referenced in the test suite that we will create next.
+ ``` -*** -### webapp/test/testsuite.qunit.ts \(New\) +### webapp/test/testsuite.qunit.js \(New\) -The `testsuite.qunit.ts` file contains the configuration for our test suite. +The `testsuite.qunit.js` file contains the configuration for our test suite. Although it comes with a set of defaults, we recommend specifying the used QUnit version to prevent potential future updates from breaking our tests. Additionally, the `sap_horizon` theme is configured in the `ui5` section, where you can provide the UI5 runtime configuration. @@ -164,9 +216,39 @@ export default { } } }; + ``` -*** +```js +sap.ui.define([], function () { + "use strict"; + + var __exports = { + name: "QUnit test suite for UI5 TypeScript Walkthrough", + defaults: { + page: "ui5://test-resources/ui5/walkthrough/Test.qunit.html?testsuite={suite}&test={name}", + qunit: { + version: 2 + }, + ui5: { + theme: "sap_horizon" + }, + loader: { + paths: { + "ui5/walkthrough": "../" + } + } + }, + tests: { + "unit/unitTests": { + title: "UI5 TypeScript Walkthrough - Unit Tests" + } + } + }; + return __exports; +}); + +``` ### webapp/test/testsuite.qunit.html \(New\) @@ -192,6 +274,7 @@ It registers a resource root mapping for the test resources of our project and r + ``` If we now open the `webapp/test/testsuite.qunit.html` file in the browser and select `unit/unitTests`, we should see our test running and verifying the formatter logic. diff --git a/steps/28/README.md b/steps/28/README.md index 7b71a34d..afe14de2 100644 --- a/steps/28/README.md +++ b/steps/28/README.md @@ -3,7 +3,7 @@ If we want to test interaction patterns or more visual features of our app, we can also write an integration test. We haven’t thought about testing our interaction with the app yet, so in this step we will check if the dialog actually opens when we click the β€œSay Hello with Dialog” button. We can easily do this with OPA5, a feature of OpenUI5 that is easy to set up and is based on JavaScript and QUnit. Using integration and unit tests and running them consistently in a continuous integration \(CI\) environment, we can make sure that we don’t accidentally break our app or introduce logical errors in existing code. - +  > πŸ“ **Note:** > In this tutorial, we focus on a simple use case for the test implementation. If you want to learn more about OPA tests, have a look at our [Testing Tutorial](https://sdk.openui5.org/topic/291c9121e6044ab381e0b51716f97f52.html) tutorial, especially [Step 6: A First OPA Test](https://sdk.openui5.org/topic/1b47457cbe4941ee926317d827517acb.html). @@ -17,24 +17,32 @@ We haven’t thought about testing our interaction with the app yet, so in this *An OPA test opens the "Hello" dialog from step 16* -You can access the live preview by clicking on this link: [πŸ”— Live Preview of Step 28](https://sap-samples.github.io/ui5-typescript-walkthrough/build/28/test/Test.cdn.qunit.html?testsuite=test-resources/ui5/walkthrough/testsuite.cdn.qunit&test=integration/opaTests). +We add a new folder `integration` below the `test` folder, where we put our new test cases. Page objects that help structuring such integration tests are put in the `pages` subfolder that we also create now. -Download solution for step 28 in [πŸ“₯ TypeScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-28.zip) or [πŸ“₯ JavaScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-28-js.zip). +![](assets/loio27e84d5bd72a485498564b92894869b5_LowRes.png "Folder Structure for this Step") +*Folder Structure for this Step* + +You can access the live preview by clicking on this link: [πŸ”— Live Preview of Step 28](https://sap-samples.github.io/ui5-typescript-walkthrough/build/28/test/Test.cdn.qunit.html?testsuite=test-resources/ui5/walkthrough/testsuite.cdn.qunit&test=integration/opaTests). *** ### Coding -We add a new folder `integration` below the `test` folder, where we put our new test cases. Page objects that help structuring such integration tests are put in the `pages` subfolder that we also create now. +
-![](assets/loio27e84d5bd72a485498564b92894869b5_LowRes.png "Folder Structure for this Step") -*Folder Structure for this Step* +You can download the solution for this step here: [πŸ“₯ Download step 28](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-28.zip). + +
+
+You can download the solution for this step here: [πŸ“₯ Download step 28](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-28-js.zip). + +
*** -### webapp/test/integration/pages/HelloPanelPage.ts \(New\) +### webapp/test/integration/pages/HelloPanelPage.?s \(New\) We create a new `HelloPanelPage.ts` file under `webapp/test/integration/pages`. @@ -73,11 +81,44 @@ export default class HelloPanelPage extends Opa5 { }); } }; + ``` -*** +```js +sap.ui.define(["sap/ui/test/Opa5", "sap/ui/test/actions/Press"], function (Opa5, Press) { + "use strict"; + + const viewName = "ui5.walkthrough.view.HelloPanel"; + class HelloPanelPage extends Opa5 { + // Actions + iPressTheSayHelloWithDialogButton() { + return this.waitFor({ + id: "helloDialogButton", + viewName, + actions: new Press(), + errorMessage: "Did not find the 'Say Hello With Dialog' button on the HelloPanel view" + }); + } + + // Assertions + iShouldSeeTheHelloDialog() { + return this.waitFor({ + controlType: "sap.m.Dialog", + success: function () { + // we set the view busy, so we need to query the parent of the app + Opa5.assert.ok(true, "The dialog is open"); + }, + errorMessage: "Did not find the dialog control" + }); + } + } + ; + return HelloPanelPage; +}); + +``` -### webapp/test/integration/NavigationJourney.ts \(New\) +### webapp/test/integration/NavigationJourney.?s \(New\) We create a new `NavigationJourney` file under `webapp/test/integration/`. @@ -122,26 +163,62 @@ opaTest("Should open the Hello dialog", function () { // Cleanup onTheHelloPanelPage.iTeardownMyApp(); }); + ``` -As you can see, the test case reads like a user story, we actually do not need the implementation of the methods yet to understand the meaning of the test case. This approach is called "Behavior Driven Development" or simply BDD and is popular in "Agile Software Development". +```js +sap.ui.define(["sap/ui/test/opaQunit", "./pages/HelloPanelPage"], function (opaTest, __HelloPanelPage) { + "use strict"; + + function _interopRequireDefault(obj) { + return obj && obj.__esModule && typeof obj.default !== "undefined" ? obj.default : obj; + } + const HelloPanelPage = _interopRequireDefault(__HelloPanelPage); + const onTheHelloPanelPage = new HelloPanelPage(); + QUnit.module("Navigation"); + opaTest("Should open the Hello dialog", function () { + // Arrangements + onTheHelloPanelPage.iStartMyUIComponent({ + componentConfig: { + name: "ui5.walkthrough" + } + }); + + // Actions + onTheHelloPanelPage.iPressTheSayHelloWithDialogButton(); + + // Assertions + onTheHelloPanelPage.iShouldSeeTheHelloDialog(); + + // Cleanup + onTheHelloPanelPage.iTeardownMyApp(); + }); +}); -*** +``` +  +As you can see, the test case reads like a user story, we actually do not need the implementation of the methods yet to understand the meaning of the test case. This approach is called "Behavior Driven Development" or simply BDD and is popular in "Agile Software Development". -### webapp/test/integration/opaTests.qunit.ts \(New\) +### webapp/test/integration/opaTests.qunit.?s \(New\) -We create a new `opaTests.qunit.ts` file under `webapp/test/integration/`. +We create a new `opaTests.qunit.?s` file under `webapp/test/integration/`. This module imports our `NavigationJourney` and is the entrypoint for all integration tests in the project. ```ts import "./NavigationJourney"; + ``` -*** +```js +sap.ui.define(["./NavigationJourney"], function (___NavigationJourney) { + "use strict"; +}); + +``` -### webapp/test/testsuite.qunit.ts +### webapp/test/testsuite.qunit.?s -Finally we reference the new `integration/opaTests.qunit.ts` in the `testsuite.qunit.ts` file. The `.qunit.ts` extension is omitted and will be added automatically during runtime. +Finally we reference the new `integration/opaTests.qunit.?s` in the `testsuite.qunit.?s` file. The `.qunit.?s` extension is omitted and will be added automatically during runtime. ```ts export default { @@ -155,8 +232,27 @@ export default { } } }; + ``` +```js +sap.ui.define([], function () { + "use strict"; + //... + tests: { + "unit/unitTests": { + title: "UI5 TypeScript Walkthrough - Unit Tests" + }, + "integration/opaTests": { + title: "UI5 TypeScript Walkthrough - Integration Tests" + } + } + }; + return __exports; +}); + +``` +  If we now open the `webapp/test/testsuite.qunit.html` file in the browser and select `integration/opaTests`, the QUnit layout should appear and a test β€œShould see the Hello dialog” will run immediately. This action will load the app component on the right side of the page. There you can see the operations the test is performing on the app. If everything works correctly, a button click will be triggered, then a dialog will be displayed and the test case will be green. *** diff --git a/steps/29/README.md b/steps/29/README.md index e5a2b02a..bd0500be 100644 --- a/steps/29/README.md +++ b/steps/29/README.md @@ -21,6 +21,19 @@ Luckily, OpenUI5 provides a couple of debugging tools that we can use within the ### Coding +
+ +You can download the solution for this step here: [πŸ“₯ Download step 29](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-29.zip). + +
+ +
+ +You can download the solution for this step here: [πŸ“₯ Download step 29](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-29-js.zip). + +
+ +*** ### webapp/view/InvoiceList.view.xml @@ -80,7 +93,7 @@ We introduced a typo in the binding of the number attribute to simulate a freque ``` - +  Now we call the app and notice that the price is actually missing. By entering the [shortcut](https://sdk.openui5.org/topic/154844c3ac2a4675a37aeb6259a5e034.html) [Ctrl\] + [Shift\] + [Alt\] /[Option\] + [S\] we open the OpenUI5 support diagnostics tool and check the app. > πŸ“ **Note:** diff --git a/steps/30/README.md b/steps/30/README.md index 2ee60067..2b7445be 100644 --- a/steps/30/README.md +++ b/steps/30/README.md @@ -17,13 +17,24 @@ In this step, we will use the OpenUI5 navigation features to load and show a sep You can access the live preview by clicking on this link: [πŸ”— Live Preview of Step 30](https://sap-samples.github.io/ui5-typescript-walkthrough/build/30/test/mockServer-cdn.html). -Download solution for step 30 in [πŸ“₯ TypeScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-30.zip) or [πŸ“₯ JavaScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-30-js.zip). - *** ### Coding +
+ +You can download the solution for this step here: [πŸ“₯ Download step 30](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-30.zip). + +
+ +
+ +You can download the solution for this step here: [πŸ“₯ Download step 30](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-30-js.zip). + +
+*** + ### webapp/i18n/i18n.properties First, we add a new text value pair to our resource bundle to define a title for the new detail page we plan to create. @@ -40,8 +51,6 @@ invoiceStatusC=Done detailPageTitle=UI5 TypeScript Walkthrough - Details ``` -*** - ### webapp/view/Detail.view.xml \(New\) Now we add the new `Detail.view.xml` file to our view folder. Beside of the the root node of the XML structure and the required namespaces, it only contains a `Page` control that displays the title we just defined in our resource boundle and an `ObjectHeader` control with a static text *Invoice* assigned to the `title` attribute (this we will change in the next step). @@ -58,8 +67,6 @@ Now we add the new `Detail.view.xml` file to our view folder. Beside of the the ``` -*** - ### webapp/view/Overview.view.xml \(New\) Next, we create another view in the view folder, called `Overview.view.xml`. We add the root node of the XML structure including the required namespaces to it. Then we copy and paste from the app view everything between and including the `Page` control to our new view. @@ -80,11 +87,9 @@ For simplicity, we reuse the controller `ui5.walkthrough.controller.App` for our ``` - +  As we reuse the controller `ui5.walkthrough.controller.App` for two different views \(for the new overview and for the app view\), two instances of that controller are instantiated at runtime. In general, one instance of a controller is instantiated for each view that references the controller. -*** - ### webapp/view/App.view.xml In the app view, we now remove everything and between the control aggregation `pages` in the app view as this found its new home in the overview view we just created. We provide an `id` to the app control, as we want to use this control for our router configuration in the app descriptor in the next step. @@ -103,8 +108,6 @@ In the app view, we now remove everything and between the control aggregation `p ``` -*** - ### webapp/manifest.json We thus have everything we need to define a routing from the starting view to the details view we just defined. We want to start with the app view loading our new overview view by default and being replaced by the detail view when a specific route has been hit. @@ -170,7 +173,7 @@ We add a new β€œrouting" section to the `sap.ui5` part of the descriptor. There } } ``` - +  The router will automatically add the view that corresponds to the current URL into the app control. The router identifies the app control with the ID that corresponds to the property `controlId: β€œapp”` in the `AppDescriptor`. The overview view is always shown when the hash is empty. The detail view is shown when the hash matches the pattern `detail`. @@ -178,9 +181,7 @@ The overview view is always shown when the hash is empty. The detail view is sho > πŸ“Œ **Important:**
> The sequence of the routes in the routes definition is important. As soon as a pattern is matched, the following patterns are ignored. To prevent this for a specific route, you use the `greedy` parameter. If set to `true`, the route is always taken into account. -*** - -### webapp/Component.ts +### webapp/Component.?s In the component initialization method, we now add a call to initialize the router. @@ -213,15 +214,48 @@ export default class Component extends UIComponent { this.getRouter().initialize(); }; }; + ``` +```js +sap.ui.define(["sap/ui/core/UIComponent", "sap/ui/model/json/JSONModel"], function (UIComponent, JSONModel) { + "use strict"; + + const Component = UIComponent.extend("ui5.walkthrough.Component", { + metadata: { + "interfaces": ["sap.ui.core.IAsyncContentCreation"], + "manifest": "json" + }, + init: function _init() { + // call the init function of the parent + UIComponent.prototype.init.call(this); + + // set data model + const data = { + recipient: { + name: "World" + } + }; + const model = new JSONModel(data); + this.setModel(model); + + // create the views based on the url/hash + this.getRouter().initialize(); + } + }); + ; + return Component; +}); + +``` +  We do not need to instantiate the router manually, it is automatically instantiated based on our configuration in the app descriptor and assigned to the component. Initializing the router will evaluate the current URL and load the corresponding view automatically. This is done with the help of the routes and targets that have been configured in the `manifest.json`. If a route has been hit, the view of its corresponding target is loaded and displayed. *** -### webapp/controller/InvoiceList.controller.ts +### webapp/controller/InvoiceList.controller.?s What is still missing is the event handler that performs a navigation to the detail page by clicking an item in the invoice list: To access the router instance for our app use the static method `getRouterFor()` on the `UIComponent` module. On the router we call the `navTo` method passing the pattern name we defined in our app descriptor for routing to the details page. @@ -238,7 +272,8 @@ import UIComponent from "sap/ui/core/UIComponent"; * @namespace ui5.walkthrough.controller */ export default class App extends Controller { - … + + //... onPress(): void { const router = UIComponent.getRouterFor(this); @@ -247,8 +282,24 @@ export default class App extends Controller { }; ``` -*** +```js +sap.ui.define(["sap/ui/core/mvc/Controller", "sap/ui/model/json/JSONModel", "sap/ui/model/Filter", "sap/ui/model/FilterOperator", "sap/ui/core/UIComponent"], function (Controller, JSONModel, Filter, FilterOperator, UIComponent) { + "use strict"; + const App = Controller.extend("ui5.walkthrough.controller.App", { + + //... + + onPress: function _onPress() { + const router = UIComponent.getRouterFor(this); + router.navTo("detail"); + } + }); + ; + return App; +}); + +``` ### webapp/view/InvoiceList.view.xml @@ -293,7 +344,7 @@ In the invoice list view we finally add the press event to the list item we just ``` - +  If you now open the app, you should now see the detail page when clicking an item in the list of invoices. *** diff --git a/steps/31/README.md b/steps/31/README.md index 33ae6fc2..fe3bfa78 100644 --- a/steps/31/README.md +++ b/steps/31/README.md @@ -17,12 +17,24 @@ To make this work, we have to pass over the information which item has been sele You can access the live preview by clicking on this link: [πŸ”— Live Preview of Step 31](https://sap-samples.github.io/ui5-typescript-walkthrough/build/31/test/mockServer-cdn.html). -Download solution for step 31 in [πŸ“₯ TypeScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-31.zip) or [πŸ“₯ JavaScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-31-js.zip). - *** ### Coding +
+ +You can download the solution for this step here: [πŸ“₯ Download step 31](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-31.zip). + +
+ +
+ +You can download the solution for this step here: [πŸ“₯ Download step 31](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-31-js.zip). + +
+ +*** + ### webapp/manifest.json We want to hand over the information for the selected item when navigating to the detail view. To achieve that, we add the navigation parameter `invoicePath` to the detail route in the app descriptor. There, we add a navigation parameter `invoicePath` to the detail route so that we can hand over the information for the selected item to the detail page. @@ -71,9 +83,7 @@ We want to hand over the information for the selected item when navigating to th } ``` -*** - -### webapp/controller/InvoiceList.controller.ts +### webapp/controller/InvoiceList.controller.?s In the controller for the invoice list view, we extend the `onPress` event handler in such a way, it not only triggers the navigation to the detail view but also passes the selected item to the routing. @@ -99,7 +109,8 @@ import UIComponent from "sap/ui/core/UIComponent"; * @namespace ui5.walkthrough.controller */ export default class App extends Controller { - … + + //... onPress(event: Event): void { const item = event.getSource() as ObjectListItem; @@ -109,11 +120,32 @@ export default class App extends Controller { }); } }; + ``` -*** +```js +sap.ui.define(["sap/ui/core/mvc/Controller", "sap/ui/model/json/JSONModel", "sap/ui/model/Filter", "sap/ui/model/FilterOperator", "sap/ui/core/UIComponent"], function (Controller, JSONModel, Filter, FilterOperator, UIComponent) { + "use strict"; -### webapp/controller/Detail.controller.ts \(New\) + const App = Controller.extend("ui5.walkthrough.controller.App", { + + //... + + onPress: function _onPress(event) { + const item = event.getSource(); + const router = UIComponent.getRouterFor(this); + router.navTo("detail", { + invoicePath: window.encodeURIComponent(item.getBindingContext("invoice").getPath().substring(1)) + }); + } + }); + ; + return App; +}); + +``` + +### webapp/controller/Detail.controller.?s \(New\) Now we need to create a new detail controller to set the content we passed in with the URL parameter `invoicePath` on the detail view. This will allow us to access the data of the selected item and display them on the view. @@ -148,8 +180,30 @@ export default class Detail extends Controller { }); } }; + +``` + +```js +sap.ui.define(["sap/ui/core/mvc/Controller", "sap/ui/core/UIComponent"], function (Controller, UIComponent) { + "use strict"; + + const Detail = Controller.extend("ui5.walkthrough.controller.Detail", { + onInit: function _onInit() { + const router = UIComponent.getRouterFor(this); + router.getRoute("detail").attachPatternMatched(this.onObjectMatched, this); + }, + onObjectMatched: function _onObjectMatched(event) { + this.getView().bindElement({ + path: "/" + window.decodeURIComponent(event.getParameter("arguments").invoicePath), + model: "invoice" + }); + } + }); + ; + return Detail; +}); + ``` -*** ### webapp/view/Detail.view.xml @@ -172,7 +226,7 @@ Our last piece to fit the puzzle together is the detail view. We replace the app You should now see the invoice details on a separate page when you click on an item in the list of invoices. *** - +  ### Conventions - Define the routing configuration in the `manifest.json` / app descriptor diff --git a/steps/32/README.md b/steps/32/README.md index 9de40818..b89ac6ee 100644 --- a/steps/32/README.md +++ b/steps/32/README.md @@ -15,20 +15,30 @@ Now we can navigate to our detail page and display an invoice, but we cannot go You can access the live preview by clicking on this link: [πŸ”— Live Preview of Step 32](https://sap-samples.github.io/ui5-typescript-walkthrough/build/32/test/mockServer-cdn.html). -Download solution for step 32 in [πŸ“₯ TypeScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-32.zip) or [πŸ“₯ JavaScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-32-js.zip). - *** ### Coding +
+ +You can download the solution for this step here: [πŸ“₯ Download step 32](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-32.zip). + +
+ +
+ +You can download the solution for this step here: [πŸ“₯ Download step 32](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-32-js.zip). + +
+ *** -### webapp/controller/Detail.controller.ts +### webapp/controller/Detail.controller.?s To be able to navigate from the detail view back to the view we came from we implement a new event handler function in the controller of the detail view. For a start we load a new class called `History` from the `sap.ui.core.routing` namespace. This class provides methods for navigating through the history of the application. -In the event handler we access the navigation history and try to determine the previous hash. In contrast to the browser history, we will get a valid result only if a navigation step inside our app has already happened. The `History.getInstance()` method returns a singleton instance of the "History" class. Then, we use the `getPreviousHash` method to get the hash of the previous page. If there's a previous page (i.e., `previousHash` isn't `undefined`), we will simply use the browser history to go back to the previous page. +In the event handler we access the navigation history and try to determine the previous hash. In contrast to the browser history, we will get a valid result only if a navigation step inside our app has already happened. The `History.getInstance()` method returns a singleton instance of the `History` class. Then, we use the `getPreviousHash` method to get the hash of the previous page. If there's a previous page (i.e., `previousHash` isn't `undefined`), we will simply use the browser history to go back to the previous page. If no navigation has happened before, we get a reference to the router and use the `navTo` method to navigate to the "overview" route. As a second parameter we specify an empty array \(`{}`\) as we do not pass any additional parameters to the route. The third parameter is set to `true`. This tells the router to replace the current history state with the new one since we actually do a back navigation by ourselves and we do not want to have an entry in the browser history. @@ -43,17 +53,7 @@ import UIComponent from "sap/ui/core/UIComponent"; */ export default class Detail extends Controller { - onInit(): void { - const router = UIComponent.getRouterFor(this); - router.getRoute("detail").attachPatternMatched(this.onObjectMatched, this); - } - - onObjectMatched(event: Route$PatternMatchedEvent): void { - this.getView().bindElement({ - path: "/" + window.decodeURIComponent( (event.getParameter("arguments") as any).invoicePath), - model: "invoice" - }); - } + //... onNavBack(): void { const history = History.getInstance(); @@ -67,11 +67,33 @@ export default class Detail extends Controller { } } }; + ``` -This implementation is a bit better than the browser’s back button for our use case. The browser would simply go back one step in the history even though we were on another page outside of the app. In the app, we always want to go back to the overview page even if we came from another link or opened the detail page directly with a bookmark. You can try it by loading the detail page in a new tab directly and clicking on the back button in the app, it will still go back to the overview page. +```js +sap.ui.define(["sap/ui/core/mvc/Controller", "sap/ui/core/routing/History", "sap/ui/core/UIComponent"], function (Controller, History, UIComponent) { + "use strict"; + + const Detail = Controller.extend("ui5.walkthrough.controller.Detail", { + //... + onNavBack: function _onNavBack() { + const history = History.getInstance(); + const previousHash = history.getPreviousHash(); + if (previousHash !== undefined) { + window.history.go(-1); + } else { + const router = UIComponent.getRouterFor(this); + router.navTo("overview", {}, true); + } + } + }); + ; + return Detail; +}); -*** +``` + +This implementation is a bit better than the browser’s back button for our use case. The browser would simply go back one step in the history even though we were on another page outside of the app. In the app, we always want to go back to the overview page even if we came from another link or opened the detail page directly with a bookmark. You can try it by loading the detail page in a new tab directly and clicking on the back button in the app, it will still go back to the overview page. ### webapp/view/Detail.view.xml @@ -92,7 +114,7 @@ Now only the back button is missing on the detail page. We do this by telling th ``` - +  You should now see a back button when navigating to the detail page and being able to get back to the overview when clicking on it. *** diff --git a/steps/33/README.md b/steps/33/README.md index e959e93e..046c7636 100644 --- a/steps/33/README.md +++ b/steps/33/README.md @@ -13,13 +13,22 @@ In this step, we are going to extend the functionality of OpenUI5 with a custom *A custom product rating control is added to the detail page* You can access the live preview by clicking on this link: [πŸ”— Live Preview of Step 33](https://sap-samples.github.io/ui5-typescript-walkthrough/build/33/test/mockServer-cdn.html). - -Download solution for step 33 in [πŸ“₯ TypeScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-33.zip) or [πŸ“₯ JavaScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-33-js.zip). *** ### Coding +
+ +You can download the solution for this step here: [πŸ“₯ Download step 33](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-33.zip). + +
+
+ +You can download the solution for this step here: [πŸ“₯ Download step 33](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-33-js.zip). + +
+*** ### webapp/i18n/i18n.properties @@ -38,8 +47,6 @@ productRatingLabelFinal=Thank you for your rating! productRatingButton=Rate ``` -*** - ### webapp/css/style.css To layout our new custom control, we specify some additional css. We create a root class `myAppDemoWTProductRating` that sets the padding to `0.75rem`. We will use this class to specify some space around our inner controls. In a second rule we reset the vertical alignment of controls with the class `sapMRI` assigned to inside controls with the class `myAppDemoWTProductRating` to the initial value. We'll need this rule to align all the controls we use with our composition. @@ -66,9 +73,7 @@ html[dir="rtl"] .myAppDemoWT .myCustomButton.sapMBtn { We could also do this with more HTML in the renderer but this is the simplest way and it will only be applied inside our custom control. However, please be aware that the custom control is in your app and might have to be adjusted when the inner controls change in future versions of OpenUI5. -*** - -### webapp/control/ProductRating.ts \(New\) +### webapp/control/ProductRating.?s \(New\) Custom controls are small reuse components that can be created within an application very easily. Due to their nature, they are sometimes also referred to as "notepad” or β€œon the fly” controls. A custom control is an object that has two special sections \(`metadata` and `renderer`\) and various methods that determine the control's functionality. @@ -114,6 +119,24 @@ export default class ProductRating extends Control { }; ``` +```js +sap.ui.define(["sap/ui/core/Control"], function (Control) { + "use strict"; + + const ProductRating = Control.extend("ui5.walkthrough.control.ProductRating", { + renderer: { + apiVersion: 4, + render: (rm, control) => { } + }, + metadata: {}, + init: function _init() {}, + }); + ; + return ProductRating; +}); + +``` +  > πŸ“Œ **Remember:**
> Controls always extend `sap.ui.core.Control` and render themselves. You could also extend `sap.ui.core.Element` or `sap.ui.base.ManagedObject` directly if you want to reuse life cycle features of OpenUI5 including data binding for objects that are not rendered. Please refer to the API reference to learn more about the inheritance hierarchy of controls. @@ -281,8 +304,113 @@ export default class ProductRating extends Control { }; ``` -*** +```js +sap.ui.define(["sap/ui/core/Control", "sap/m/Label", "sap/m/Button", "sap/m/RatingIndicator"], function (Control, Label, Button, RatingIndicator) { + "use strict"; + + const ProductRating = Control.extend("ui5.walkthrough.control.ProductRating", { + renderer: { + apiVersion: 4, + render: (rm, control) => { + const tooltip = control.getTooltip_AsString(); + rm.openStart("div", control); + rm.class("myAppDemoWTProductRating"); + if (tooltip) { + rm.attr("title", tooltip); + } + rm.openEnd(); + rm.renderControl(control.getAggregation("_rating")); + rm.renderControl(control.getAggregation("_label")); + rm.renderControl(control.getAggregation("_button")); + rm.close("div"); + } + }, + metadata: { + properties: { + value: { + type: "float", + defaultValue: 0 + } + }, + aggregations: { + _rating: { + type: "sap.m.RatingIndicator", + multiple: false, + visibility: "hidden" + }, + _label: { + type: "sap.m.Label", + multiple: false, + visibility: "hidden" + }, + _button: { + type: "sap.m.Button", + multiple: false, + visibility: "hidden" + } + }, + events: { + change: { + parameters: { + "value": "float" + } + } + } + }, + constructor: function _constructor(id, settings) { + Control.prototype.constructor.call(this, id, settings); + }, + init: function _init() { + this.setAggregation("_rating", new RatingIndicator({ + value: this.getValue(), + iconSize: "2rem", + liveChange: this._onRate.bind(this) + })); + this.setAggregation("_label", new Label({ + text: "{i18n>productRatingLabelInitial}" + }).addStyleClass("sapUiSmallMargin")); + this.setAggregation("_button", new Button({ + text: "{i18n>productRatingButton}", + press: this._onSubmit.bind(this) + }).addStyleClass("sapUiTinyMarginTopBottom")); + }, + setValue: function _setValue(value) { + this.setProperty("value", value, true); + this.getAggregation("_rating").setValue(value); + return this; + }, + reset: function _reset() { + const resourceBundle = this?.getModel("i18n")?.getResourceBundle(); + this.setValue(0); + this.getAggregation("_label").setDesign("Standard"); + this.getAggregation("_rating").setEnabled(true); + this.getAggregation("_label").setText(resourceBundle.getText("productRatingLabelInitial")); + this.getAggregation("_button").setEnabled(true); + }, + _onRate: function _onRate(event) { + const resourceBundle = this?.getModel("i18n")?.getResourceBundle(); + const value = event.getParameter("value"); + this.setProperty("value", value, true); + this.getAggregation("_label").setText(resourceBundle.getText("productRatingLabelIndicator", [value, event.getSource().getMaxValue()])); + this.getAggregation("_label").setDesign("Bold"); + }, + _onSubmit: function _onSubmit(event) { + const resourceBundle = this?.getModel("i18n")?.getResourceBundle(); + this.getAggregation("_rating").setEnabled(false); + this.getAggregation("_label").setText(resourceBundle.getText("productRatingLabelFinal")); + this.getAggregation("_button").setEnabled(false); + this.fireEvent("change", { + value: this.getValue() + }); + } + }); + ; + return ProductRating; +}); + +``` +
### Generate Control Interfaces to Resolve the TypeScript Errors While the application would run successfully, the editor still displays an error in the `ProductRating.ts` renderer. @@ -299,9 +427,9 @@ As a result, the TypeScript error message related to the new `ProductRating` con You can now stop the interface generator again, as no further control API changes will be done in this tutorial. For continuous control development with frequent API changes, you would likely add a "watch" script to `package.json` for starting this generator. -*** +
-### webapp/controller/Detail.controller.ts +### webapp/controller/Detail.controller.?s In the `Detail` controller we implement a new `onRatingChange` event that reads the value of our coustom change event that is fired when a rating has been submitted. This requires to import our new control, as well as the `ProductRating$ChangeEvent` type we just defined to the detail controller. To keep the sample simple we only display a message message instead of sending the rating to the backend. We therefore load the `MessageToast` module from the `sap.m` namespace to our script. In addition we need the `ResourceBundle` module from the `sap/base/i18n` namespace as well as the `ResourceModel` module from the `sap/ui/model/resource` namespace as we want to display the confirmation message we specified in our resource bundle in the message toast. @@ -357,9 +485,46 @@ export default class Detail extends Controller { MessageToast.show(resourceBundle.getText("ratingConfirmation", [value])); } }; + ``` -*** +```js +sap.ui.define(["sap/ui/core/mvc/Controller", "sap/ui/core/routing/History", "sap/m/MessageToast", "sap/ui/core/UIComponent"], function (Controller, History, MessageToast, UIComponent) { + "use strict"; + + const Detail = Controller.extend("ui5.walkthrough.controller.Detail", { + onInit: function _onInit() { + const router = UIComponent.getRouterFor(this); + router.getRoute("detail").attachPatternMatched(this.onObjectMatched, this); + }, + onObjectMatched: function _onObjectMatched(event) { + this.byId("rating").reset(); + this.getView().bindElement({ + path: "/" + window.decodeURIComponent(event.getParameter("arguments").invoicePath), + model: "invoice" + }); + }, + onNavBack: function _onNavBack() { + const history = History.getInstance(); + const previousHash = history.getPreviousHash(); + if (previousHash !== undefined) { + window.history.go(-1); + } else { + const router = UIComponent.getRouterFor(this); + router.navTo("overview", {}, true); + } + }, + onRatingChange: function _onRatingChange(event) { + const value = event.getParameter("value"); + const resourceBundle = this?.getView().getModel("i18n")?.getResourceBundle(); + MessageToast.show(resourceBundle.getText("ratingConfirmation", [value])); + } + }); + ; + return Detail; +}); + +``` ### webapp/view/Detail.view.xml @@ -386,10 +551,10 @@ All we need now is to add our new control to the detail view. To do so we must a ``` - +  We can now rate a product on the detail page with our brand new control. -*** +
### webapp/control/ProductRating.ts @@ -431,8 +596,9 @@ export default class ProductRating extends Control { ... } ``` - +  Adding the block between the BEGIN and END line into the `ProductRating` class body in the file `webapp/control/ProductRating.ts` provides the constructors and the structure of the constructor settings object. As result, the constructor signatures with and without control ID are available. Furthermore, TypeScript checks the settings you give in the constructor and suggests the available ones, like the direction property. +
*** diff --git a/steps/34/README.md b/steps/34/README.md index 092fb838..98fdb399 100644 --- a/steps/34/README.md +++ b/steps/34/README.md @@ -15,12 +15,22 @@ In this step, we improve the responsiveness of our app. OpenUI5 applications can You can access the live preview by clicking on this link: [πŸ”— Live Preview of Step 34](https://sap-samples.github.io/ui5-typescript-walkthrough/build/34/test/mockServer-cdn.html). -Download solution for step 34 in [πŸ“₯ TypeScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-34.zip) or [πŸ“₯ JavaScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-34-js.zip). *** ### Coding +
+You can download the solution for this step here: [πŸ“₯ Download step 34](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-34.zip). + +
+ +
+ +You can download the solution for this step here: [πŸ“₯ Download step 34](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-34-js.zip). + +
+*** ### webapp/i18n/i18n.properties @@ -43,8 +53,6 @@ columnPrice=Price ... ``` -*** - ### webapp/view/InvoiceList.view.xml On the invoice list view, we exchange the list with a table simply by replacing the tag `` with ``. The table has a built-in responsiveness feature that allows us to make the app more flexible. The table and the list share the same set of properties so we can simply reuse these and also the sorter. @@ -180,7 +188,7 @@ Instead of the `ObjectListItem` that we had before, we will now split the inform
``` - +  Now we have defined our table responsively and can see the results when we decrease the browsers screen size. The *Supplier* column is not shown on phone sizes and the two columns *Quantity* and *Status* will be shown below the name. We can see the results when we decrease the browser's screen size or open the app on a small device. diff --git a/steps/35/README.md b/steps/35/README.md index cb1f1c88..bb439629 100644 --- a/steps/35/README.md +++ b/steps/35/README.md @@ -14,13 +14,23 @@ We now configure the visibility and properties of controls based on the device t You can access the live preview by clicking on this link: [πŸ”— Live Preview of Step 35](https://sap-samples.github.io/ui5-typescript-walkthrough/build/35/test/mockServer-cdn.html). -Download solution for step 35 in [πŸ“₯ TypeScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-35.zip) or [πŸ“₯ JavaScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-35-js.zip). - *** ### Coding +
+ +You can download the solution for this step here: [πŸ“₯ Download step 35](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-35.zip). + +
+ +
+ +You can download the solution for this step here: [πŸ“₯ Download step 35](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-35-js.zip). + +
+*** -### webapp/Component.ts +### webapp/Component.?s In the `app` component we import the `Device` module from the `sap.ui` namespace and initialize the device model in the `init` method. We can simply pass the loaded dependency `Device` to the constructor function of the JSONModel. This will make most properties of the OpenUI5 device API available as a JSON model. The model is then set on the component as a named model so that we can reference it in data binding. @@ -62,9 +72,45 @@ export default class Component extends UIComponent { this.getRouter().initialize(); }; }; + ``` -*** +```js +sap.ui.define(["sap/ui/core/UIComponent", "sap/ui/model/json/JSONModel", "sap/ui/Device"], function (UIComponent, JSONModel, Device) { + "use strict"; + + const Component = UIComponent.extend("ui5.walkthrough.Component", { + metadata: { + "interfaces": ["sap.ui.core.IAsyncContentCreation"], + "manifest": "json" + }, + init: function _init() { + // call the init function of the parent + UIComponent.prototype.init.call(this); + + // set data model + const data = { + recipient: { + name: "World" + } + }; + const model = new JSONModel(data); + this.setModel(model); + + // set device model + const deviceModel = new JSONModel(Device); + deviceModel.setDefaultBindingMode("OneWay"); + this.setModel(deviceModel, "device"); + + // create the views based on the url/hash + this.getRouter().initialize(); + } + }); + ; + return Component; +}); + +``` ### webapp/view/HelloPanel.view.xml @@ -105,15 +151,13 @@ We can also hide single controls by device type when we set a CSS class like `sa ``` - +  The device API of OpenUI5 offers more functionality to detect various device-specific settings, please have a look at the [documentation](https://sdk.openui5.org/api/sap.ui.Device) for more details. > πŸ“Œ **Important:**
> The `sap.ui.Device` API detects the device type \(Phone, Tablet, Desktop\) based on the user agent and many other properties of the device. Therefore simply reducing the screen size will not change the device type. To test this feature, you will have to enable device emulation in your browser or open it on a real device. -*** - -### webapp/controller/Detail.controller.ts +### webapp/controller/Detail.controller.?s In the `Detail` controller we simply add the view model with our currency definition to display the number properly. It is the same code as in the `InvoiceList` controller file. @@ -142,8 +186,30 @@ export default class Detail extends Controller { const router = UIComponent.getRouterFor(this); router.getRoute("detail").attachPatternMatched(this.onObjectMatched, this); } - … + //... }; + +``` + +```js +sap.ui.define(["sap/ui/core/mvc/Controller", "sap/ui/core/routing/History", "sap/m/MessageToast", "sap/ui/model/json/JSONModel", "sap/ui/core/UIComponent"], function (Controller, History, MessageToast, JSONModel, UIComponent) { + "use strict"; + + const Detail = Controller.extend("ui5.walkthrough.controller.Detail", { + onInit: function _onInit() { + const viewModel = new JSONModel({ + currency: "EUR" + }); + this.getView().setModel(viewModel, "view"); + const router = UIComponent.getRouterFor(this); + router.getRoute("detail").attachPatternMatched(this.onObjectMatched, this); + }, + //... + }); + ; + return Detail; +}); + ``` ### webapp/i18n/i18n.properties @@ -158,8 +224,6 @@ dateTitle=Shipped date quantityTitle=Quantity ``` -*** - ### webapp/view/Detail.view.xml Some controls already have built-in responsive features that can be configured. The `ObjectHeader` control can be put in a more flexible mode by setting the attribute `responsive` to `true` and `fullScreenOptimized` to true as well. This will show the data that we add to the view now at different positions on the screen based on the device size. @@ -223,7 +287,7 @@ We add the `number` and `numberUnit` field from the list of the previous steps a ``` -*** +  We can see the results when we decrease the browser's screen size or open the app on a small device. diff --git a/steps/36/README.md b/steps/36/README.md index 9ac96b15..d05fe0f9 100644 --- a/steps/36/README.md +++ b/steps/36/README.md @@ -14,13 +14,23 @@ In this step of our Walkthrough tutorial, we adjust the content density based on You can access the live preview by clicking on this link: [πŸ”— Live Preview of Step 36](https://sap-samples.github.io/ui5-typescript-walkthrough/build/36/test/mockServer-cdn.html). -Download solution for step 36 in [πŸ“₯ TypeScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-36.zip) or [πŸ“₯ JavaScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-36-js.zip). - *** ### Coding +
+ +You can download the solution for this step here: [πŸ“₯ Download step 36](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-36.zip). + +
-### webapp/Component.ts +
+ +You can download the solution for this step here: [πŸ“₯ Download step 36](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-36-js.zip). + +
+*** + +### webapp/Component.?s To prepare the content density feature we add a helper method `getContentDensityClass` to the app component. OpenUI5 controls can be displayed in multiple sizes, for example in a `compact` size that is optimized for desktop and non-touch devices, and in a `cozy` mode that is optimized for touch interaction. The controls look for a specific CSS class in the HTML structure of the application to adjust their size. @@ -48,11 +58,33 @@ export default class Component extends UIComponent { return Device.support.touch ? "sapUiSizeCozy" : "sapUiSizeCompact"; } }; + ``` -*** +```js +sap.ui.define(["sap/ui/core/UIComponent", "sap/ui/model/json/JSONModel", "sap/ui/Device"], function (UIComponent, JSONModel, Device) { + "use strict"; -### webapp/controller/App.controller.ts + const Component = UIComponent.extend("ui5.walkthrough.Component", { + metadata: { + "interfaces": ["sap.ui.core.IAsyncContentCreation"], + "manifest": "json" + }, + init: function _init() { + // call the init function of the parent + ... + }, + getContentDensityClass: function _getContentDensityClass() { + return Device.support.touch ? "sapUiSizeCozy" : "sapUiSizeCompact"; + } + }); + ; + return Component; +}); + +``` + +### webapp/controller/App.controller.?s We add the `onInit` method to the app controller that is called when the app view is instantiated. There, we query the helper function that we defined on the app component in order to set the corresponding style class on the app view. All controls inside the app view will now automatically adjust to either the compact or the cozy size, as defined by the style. @@ -68,9 +100,23 @@ export default class App extends Controller { this.getView().addStyleClass((this.getOwnerComponent() as Component).getContentDensityClass()) } }; + ``` -*** +```js +sap.ui.define(["sap/ui/core/mvc/Controller"], function (Controller) { + "use strict"; + + const App = Controller.extend("ui5.walkthrough.controller.App", { + onInit: function _onInit() { + this.getView().addStyleClass(this.getOwnerComponent().getContentDensityClass()); + } + }); + ; + return App; +}); + +``` ### webapp/manifest.json diff --git a/steps/37/README.md b/steps/37/README.md index b4b7b7dd..5ef994ef 100644 --- a/steps/37/README.md +++ b/steps/37/README.md @@ -4,6 +4,8 @@ In this step we're going to improve the accessibility of our app. To achieve this, we will add ARIA attributes. ARIA attributes are used by screen readers to recognize the application structure and to interpret UI elements properly. That way, we can make our app more accessible for users who are limited in their use of computers, for example visually impaired persons. The main goal here is to make our app usable for as many people as we can. +One part of the ARIA attribute set are the so-called landmarks. You can compare landmarks to maps in that they help the user navigate through an app. For this step, we will use Google Chrome with a free [landmark navigation extension](https://chrome.google.com/webstore/detail/landmark-navigation-via-k/ddpokpbjopmeeiiolheejjpkonlkklgp) We will now add meaningful landmarks to our code. + > πŸ’‘ **Tip:**
> ARIA is short for **Accessible Rich Internet Applications**. It is a set of attributes that enable us to make apps more accessible by assigning semantic characteristics to certain elements. For more information, see [Accessible Rich Internet Applications \(ARIA\) – Part 1: Introduction](https://blogs.sap.com/2015/06/01/accessible-rich-internet-applications-aria-part-1-introduction/). @@ -19,13 +21,22 @@ To achieve this, we will add ARIA attributes. ARIA attributes are used by screen You can access the live preview by clicking on this link: [πŸ”— Live Preview of Step 37](https://sap-samples.github.io/ui5-typescript-walkthrough/build/37/test/mockServer-cdn.html). -Download solution for step 37 in [πŸ“₯ TypeScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-37.zip) or [πŸ“₯ JavaScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-37-js.zip). - *** ### Coding -One part of the ARIA attribute set are the so-called landmarks. You can compare landmarks to maps in that they help the user navigate through an app. For this step, we will use Google Chrome with a free [landmark navigation extension](https://chrome.google.com/webstore/detail/landmark-navigation-via-k/ddpokpbjopmeeiiolheejjpkonlkklgp) We will now add meaningful landmarks to our code. +
+ +You can download the solution for this step here: [πŸ“₯ Download step 37](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-37.zip). + +
+ +
+ +You can download the solution for this step here: [πŸ“₯ Download step 37](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-37-js.zip). + +
+*** ### webapp/i18n/i18n.properties @@ -45,8 +56,6 @@ Overview_contentLabel=Page Content ... ``` -*** - ### webapp/view/Overview.view.xml We add the `landmarkInfo` aggregation to the page and use `sap.m.PageAccessibleLandmarkInfo` to define ARIA roles and labels for the overview page areas. In the `PageAccessibilityLandmarkInfo` control we specify a role and a title for the root, the content, and the header of the page. @@ -77,8 +86,6 @@ For more information, see the [API Reference: `sap.m.PageAccessibleLandmarkInfo` ``` -*** - ### webapp/view/InvoiceList.view.xml We add an `sap.m.Panel` around the invoice list and move the toolbar from the table into the panel, so that the region can take the title of the toolbar as its own. This has the effect that it will now be a region in our landmarks. @@ -115,8 +122,6 @@ We add an `sap.m.Panel` around the invoice list and move the toolbar from the ta ``` -*** - ### webapp/view/HelloPanel.view.xml In the `HelloPanel` view, we already have a panel, so we just add the `accessibleRole` attribute. @@ -146,29 +151,29 @@ In the `HelloPanel` view, we already have a panel, so we just add the `accessibl -![](assets/loio54e9bca5a5844c14b45b5405496166b1_HiRes.png) + -![](assets/loiof38dee2624c2437d8977de70575b3eae_HiRes.png) + -**Landmarks on the overview page - before** +Landmarks on the overview page - before -**Landmarks on the overview page - after** +Landmarks on the overview page - after - +  As you can see, we now have four landmarks on our page. The top three landmarks structure our page: - *Overview Page* marks the complete page. diff --git a/steps/38/README.md b/steps/38/README.md index 59ca2d01..80dcad07 100644 --- a/steps/38/README.md +++ b/steps/38/README.md @@ -13,11 +13,21 @@ In this step we're going to build our application and consume the speed of a bui *The OpenUI5 application is built and served* -Download solution for step 38 in [πŸ“₯ TypeScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-38.zip) or [πŸ“₯ JavaScript](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-38-js.zip). - *** ### Coding +
+ +You can download the solution for this step here: [πŸ“₯ Download step 38](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-38.zip). + +
+ +
+ +You can download the solution for this step here: [πŸ“₯ Download step 38](https://sap-samples.github.io/ui5-typescript-walkthrough/ui5-typescript-walkthrough-step-38-js.zip). + +
+*** ### package.json @@ -33,19 +43,10 @@ Now we create a production-ready version of our OpenUI5 application that can be "start": "ui5 serve -o test/mockServer.html", "build": "ui5 build --all --clean-dest" }, - "devDependencies": { - "@types/openui5": "^1.132.0", - "@ui5/cli": "^3.7.1", - "@ui5/ts-interface-generator": "^0.8.1", - "typescript": "^5.2.2", - "ui5-middleware-livereload": "^3.0.2", - "ui5-middleware-serveframework": "^3.0.0", - "ui5-middleware-simpleproxy": "^3.2.8", - "ui5-tooling-transpile": "^3.2.7" - } + ... } ``` - +  Now that we added this script to our project configuration, we can also execute it: Open a terminal and execute `npm run build` in the project root folder. @@ -75,20 +76,10 @@ To actually use the newly added web server, we have to add a new script to our ` "build": "ui5 build --all --clean-dest", "serve-dist": "ws --compress -d dist --open" }, - "devDependencies": { - "@types/openui5": "^1.132.0", - "@ui5/cli": "^3.7.1", - "@ui5/ts-interface-generator": "^0.8.1", - "typescript": "^5.2.2", - "local-web-server": "^5.3.0", - "ui5-middleware-livereload": "^3.0.2", - "ui5-middleware-serveframework": "^3.0.0", - "ui5-middleware-simpleproxy": "^3.2.8", - "ui5-tooling-transpile": "^3.2.7" - } + ... } ``` - +  Now it's time to start the server by executing `npm run serve-dist` in a terminal in the project root folder. Your default browser opens automatically and the built application is hosted. The `local-web-server` does not offer proxy capabilities so far, nor does it allow to open a specific HTML file in the browser. To display any actual data, it is therefore necessary to open the `test/mockServer.html` file in the browser instead of the `index.html`. *** diff --git a/tools/dev-server/server.js b/tools/dev-server/server.js index 101af61f..c548242b 100644 --- a/tools/dev-server/server.js +++ b/tools/dev-server/server.js @@ -44,9 +44,10 @@ app.use("/node_modules", express.static(join(cwd, "node_modules"))); app.use(async (req, res, next) => { let file, url; - if (req.url.endsWith("/")) { + const reqUrlWithoutParams = req.url.split("?")[0]; + if (reqUrlWithoutParams.endsWith("/")) { for (const index of ["index.md", "README.md"]) { - url = `${req.url}${index}`; + url = `${reqUrlWithoutParams}${index}`; file = join(cwd, url); if (existsSync(file) && statSync(file).isFile()) { break; @@ -55,7 +56,7 @@ app.use(async (req, res, next) => { } } } else { - file = join(cwd, req.url); + file = join(cwd, reqUrlWithoutParams); if (!(existsSync(file) && statSync(file).isFile())) { file = undefined; } @@ -65,7 +66,7 @@ app.use(async (req, res, next) => { const bodyContent = await convertMarkdown(md); const templateFn = await getTemplate(); // get title as first line in the md file which starts with hashes, which indicates it is a title of some kind - const title = md.match(/^##* (.+)$/m)?.[1] || req.url; + const title = md.match(/^##* (.+)$/m)?.[1] || reqUrlWithoutParams; const html = templateFn({ title, bodyContent }); res.send(html); } else if (file) {