├── .devcontainer
├── Dockerfile
├── arm64
│ ├── Dockerfile
│ └── README.md
└── devcontainer.json
├── .github
└── ISSUE_TEMPLATE
│ └── session-feedback-template.md
├── .gitignore
├── CODEOWNERS
├── LICENSE
├── LICENSES
└── Apache-2.0.txt
├── README.md
├── REUSE.toml
├── bookshop
└── finished-webapp
│ ├── package-lock.json
│ ├── package.json
│ ├── ui5.yaml
│ └── webapp
│ ├── Component.js
│ ├── controller
│ └── App.controller.js
│ ├── css
│ └── style.css
│ ├── i18n
│ ├── i18n.properties
│ └── i18n_de.properties
│ ├── index.html
│ ├── localService
│ ├── metadata.xml
│ ├── mockdata
│ │ ├── Books.json
│ │ ├── Currencies.json
│ │ └── Genres.json
│ └── mockserver.js
│ ├── manifest.json
│ ├── model
│ └── formatter.js
│ ├── test
│ ├── initMockServer.js
│ └── mockServer.html
│ └── view
│ └── App.view.xml
├── chapters
├── 00-prep-dev-environment
│ └── readme.md
├── 01-scaffolding
│ ├── readme.md
│ └── result.png
├── 02-first-view
│ ├── readme.md
│ └── result.png
├── 03-first-model
│ ├── App.view.png
│ ├── readme.md
│ └── result.png
├── 04-first-controller
│ ├── App.view.png
│ ├── alert.png
│ ├── readme.md
│ └── result.png
├── 05-order-feature
│ ├── App.controller.png
│ ├── App.view.png
│ ├── readme.md
│ └── result.png
├── 06-search-feature
│ ├── App.controller.png
│ ├── App.view.png
│ ├── readme.md
│ └── result.png
├── 07-formatting
│ ├── App.controller.png
│ ├── App.view.png
│ ├── readme.md
│ └── result.png
├── 08-i18n
│ ├── manifest.png
│ ├── readme.md
│ └── result.png
├── 09-custom-css
│ ├── App.view.png
│ ├── manifest.png
│ ├── readme.md
│ └── result.png
├── 10-deployment
│ ├── readme.md
│ ├── result1.png
│ └── result2.png
├── 11-further-improvements
│ └── readme.md
├── appendix-01-fe-fpm
│ ├── fiori-tools.png
│ ├── readme.md
│ └── result.png
├── appendix-02-object-page
│ ├── readme.md
│ └── result.png
└── appendix-03-fiori-tools
│ ├── page-map.png
│ ├── readme.md
│ └── result.png
└── finished-app.png
/.devcontainer/Dockerfile:
--------------------------------------------------------------------------------
1 | # See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.187.0/containers/javascript-node/.devcontainer/base.Dockerfile
2 |
3 | # [Choice] Node.js version: 16, 14, 12
4 | ARG VARIANT="18-buster"
5 | FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-${VARIANT}
6 |
7 | # Prepare for apt-based install of Cloud Foundry CLI by adding Cloud Foundry Foundation public key & package repository (see https://docs.cloudfoundry.org/cf-cli/install-go-cli.html)
8 | RUN wget -q -O - https://packages.cloudfoundry.org/debian/cli.cloudfoundry.org.key | sudo apt-key add -
9 | RUN echo "deb https://packages.cloudfoundry.org/debian stable main" | sudo tee /etc/apt/sources.list.d/cloudfoundry-cli.list
10 |
11 | # Update local package index and run installs
12 | RUN apt-get update
13 | RUN apt-get install cf7-cli sqlite3
14 |
15 | # Install global node modules for SAP CAP and frontend development
16 | RUN su node -c "npm install -g @ui5/cli @sap/cds-dk yo mbt"
17 |
--------------------------------------------------------------------------------
/.devcontainer/arm64/Dockerfile:
--------------------------------------------------------------------------------
1 | # Base build stage
2 | FROM arm64v8/debian:latest as foundry
3 |
4 | # Install necessary dependencies
5 | RUN apt-get update && \
6 | apt-get install -y apt-transport-https ca-certificates gnupg curl && \
7 | rm -rf /var/lib/apt/lists/*
8 |
9 | # Install the Cloud Foundry CLI
10 | RUN curl -L "https://packages.cloudfoundry.org/stable?release=linux64-binary&version=v7" | tar -zx && \
11 | mv cf7 /usr/local/bin/cf && \
12 | chmod +x /usr/local/bin/cf
13 |
14 | # Final build stage
15 | ARG VARIANT="18-buster-slim"
16 | FROM arm64v8/node:${VARIANT}
17 |
18 | COPY --from=foundry /usr/local/bin/cf /usr/local/bin/cf
19 |
20 | # Update local package index and run installs
21 | RUN apt-get update
22 |
23 | RUN apt-get install -y sqlite3
24 | RUN apt-get install -y git
25 |
26 | # Install global node modules for SAP CAP and frontend development
27 | RUN npm install -g @ui5/cli @sap/cds-dk yo mbt
28 |
29 | EXPOSE 8080
30 | EXPOSE 8081
31 |
--------------------------------------------------------------------------------
/.devcontainer/arm64/README.md:
--------------------------------------------------------------------------------
1 | # Info Dockerfile for arm64
2 |
3 | ## Trivia
4 |
5 | Based on the original Dockerfile from this repo, this one will create a base image with which one can natively run a containerized exercise app on an M1/M2 MacBook.
6 |
7 | ## Issues
8 |
9 | Opening the running app via https://localhost:8080 may fail.
10 |
11 | To solve this, one needs to add `--accept-remote-connections` to `ui5 serve`.
12 |
--------------------------------------------------------------------------------
/.devcontainer/devcontainer.json:
--------------------------------------------------------------------------------
1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
2 | // https://github.com/microsoft/vscode-dev-containers/tree/v0.187.0/containers/javascript-node
3 | {
4 | "name": "SAP UI5 CodeJam Exercises Devcontainer",
5 | "build": {
6 | "dockerfile": "Dockerfile",
7 | // Update 'VARIANT' to pick a Node version: 12, 14, 16
8 | "args": { "VARIANT": "18" }
9 | },
10 |
11 | // Set *default* container specific settings.json values on container create.
12 | "settings": {},
13 |
14 | // Add the IDs of extensions you want installed when the container is created.
15 | "extensions": [
16 | "sapse.vscode-cds",
17 | "sapse.sap-ux-fiori-tools-extension-pack",
18 | "hookyqr.beautify",
19 | "yzhang.markdown-all-in-one",
20 | "mechatroner.rainbow-csv",
21 | "saposs.sap-hana-driver-for-sqltools",
22 | "saposs.xml-toolkit"
23 | ],
24 |
25 | // Use 'forwardPorts' to make a list of ports inside the container available locally.
26 | "forwardPorts": [ 4004 ],
27 |
28 | // Use 'postCreateCommand' to run commands after the container is created.
29 | // "postCreateCommand": "npm install",
30 |
31 | // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
32 | "remoteUser": "node"
33 | }
34 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/session-feedback-template.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Session Feedback Template
3 | about: To give feedback on the session
4 | title: Session Feedback
5 | labels: feedback
6 | assignees: ''
7 |
8 | ---
9 |
10 | Thanks for taking a couple of minutes to give feedback, which will help me improve for next time. **Before doing anything else, hit the green button "Submit new issue" right now to save this issue content, rather than try to complete the feedback in this raw form.** Then go through the questions and mark a single checkbox for each, to represent your answer. Finally, in the empty comment box below this one, please describe what you liked and what you didn't like.
11 |
12 | **How experienced were you with web development in general before this session?**
13 |
14 | - [ ] Didn't know what HTML is
15 | - [ ] Heard about HTML/CSS/JavaScript, but never developed a web app
16 | - [ ] Used HTML/CSS/JavaScript before, but never developed a complete web app on my own
17 | - [ ] Developed one or more (smaller) web app(s) on my own
18 | - [ ] I am a very experienced web developer
19 |
20 | **How experienced were you with UI5 before this session?**
21 |
22 | - [ ] Didn't know what UI5 was
23 | - [ ] Heard about UI5, but never developed a UI5 app
24 | - [ ] Used UI5 before, but never developed a complete UI5 app on my own
25 | - [ ] Developed one or more (smaller) UI5 app(s) on my own
26 | - [ ] I am [Peter Muessig](https://twitter.com/pmuessig), or at least very experienced
27 |
28 | **How do you feel about UI5 now (after this session)?**
29 |
30 | - [ ] Don't know what UI5 is
31 | - [ ] Know what UI5 is, but don't know how develop a UI5 app
32 | - [ ] Know how to use parts of it, but don't know how to built a complete application on my own
33 | - [ ] Know how to built a complete application on my own
34 | - [ ] I am [Peter Muessig](https://twitter.com/pmuessig), or at least very experienced
35 |
36 | **Did this session meet your expectations?**
37 |
38 | - [ ] Not really
39 | - [ ] Somewhat
40 | - [ ] Mostly
41 | - [ ] Fully
42 |
43 | **Was the time allotted to each exercise enough for you to work through them?**
44 |
45 | - [ ] Not really
46 | - [ ] Somewhat
47 | - [ ] Mostly
48 | - [ ] Fully
49 |
50 | **What did you think of the extra information (collapsable sections 💬) and explanations provided?**
51 |
52 | - [ ] Too much information
53 | - [ ] Didn't really read it, didn't really bother me though
54 | - [ ] Found it helpful as context and background
55 |
56 | **How did you find the way we all moved at the same pace through the exercises?**
57 |
58 | - [ ] Would have preferred to go through them on my own at my own speed
59 | - [ ] Didn't mind, no strong feelings either way
60 | - [ ] Found it useful to keep pace and discuss the exercise questions, and reflect
61 |
62 | **Which development environment(s) did you use?**
63 |
64 | - [ ] SAP Business Application Studio
65 | - [ ] Code editor on my local machine
66 | - [ ] Devcontainer
67 |
68 | If you have time, please add a comment below to write free-form what you liked and what you disliked about the session. Thank you!
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | dist/
3 | node_modules/
4 |
5 | bookshop/manifest.yaml
6 | bookshop/package-lock.json
7 | bookshop/package.json
8 | bookshop/ui5.yaml
9 | bookshop/xs-app.json
10 |
11 | .DS_Store
12 | .vscode/
--------------------------------------------------------------------------------
/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * nicolai.schoenteich@sap.com
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/LICENSES/Apache-2.0.txt:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
10 |
11 | "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
12 |
13 | "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
14 |
15 | "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
16 |
17 | "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
18 |
19 | "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
20 |
21 | "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
22 |
23 | "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
24 |
25 | "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
26 |
27 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
28 |
29 | 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
30 |
31 | 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
32 |
33 | 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
34 |
35 | (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and
36 |
37 | (b) You must cause any modified files to carry prominent notices stating that You changed the files; and
38 |
39 | (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
40 |
41 | (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
42 |
43 | You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
44 |
45 | 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
46 |
47 | 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
48 |
49 | 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
50 |
51 | 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
52 |
53 | 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
54 |
55 | END OF TERMS AND CONDITIONS
56 |
57 | APPENDIX: How to apply the Apache License to your work.
58 |
59 | To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives.
60 |
61 | Copyright [yyyy] [name of copyright owner]
62 |
63 | Licensed under the Apache License, Version 2.0 (the "License");
64 | you may not use this file except in compliance with the License.
65 | You may obtain a copy of the License at
66 |
67 | http://www.apache.org/licenses/LICENSE-2.0
68 |
69 | Unless required by applicable law or agreed to in writing, software
70 | distributed under the License is distributed on an "AS IS" BASIS,
71 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
72 | See the License for the specific language governing permissions and
73 | limitations under the License.
74 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://api.reuse.software/info/github.com/SAP-samples/ui5-exercises-codejam)
2 |
3 | # SAP CodeJam - UI5
4 |
5 | This repository contains the material for SAP CodeJam events on UI5.
6 |
7 | Please check the [prerequisites](/chapters/00-prep-dev-environment/readme.md#1-prerequisites) before the event an make sure you meet them.
8 |
9 | ## Overview
10 |
11 | The material in this repository introduces you to the core principles of UI5, an enterprise-ready web development framework used to build apps that follow the Fiori design guidelines. This repository is a step-by-step guide explaining how build a frontend web application using UI5. The finished app is a bookshop app, where users can browse and order books. The app sits on top of the well-known [bookshop](https://github.com/SAP-samples/cloud-cap-samples/tree/main/bookshop) backend application built with the Node.js flavour of the SAP Cloud Application Programming Model (CAP).
12 |
13 | 
14 |
15 | The finished UI5 bookshop app already exists in the [bookshop/finished-webapp](/bookshop/finished-webapp/) directory, but we want to rebuild it from scratch step by step. You can compare the finished app with your version in case you have issues along the way.
16 |
17 | After reading all chapters and following the instructions, you will be able to build your own UI5 applications leveraging the official [SAPUI5 API Reference](https://sapui5.hana.ondemand.com/#/api).
18 |
19 | ## Previous Knowledge
20 |
21 | The material in this repository aims to be beginner friendly. If you have never built a (UI5) web app before, you will still be able to follow along. No prior knowledge is required, although it certainly helps to have experience in (web) development.
22 |
23 | The material includes additional explanations in collapsable sections (see example below), whenever a concept is used that web developers are probably already familiar with, but beginners might not be. You can decide for yourself whether you want to read or skip them.
24 |
25 | See this example:
26 |
27 |
28 | What is SAPUI5? 💬
29 |
30 |
31 |
32 | > SAPUI5 is an HTML5 framework for creating cross-platform, enterprise-grade web applications in an efficient way.
33 | >
34 | > See this [blog post](https://blogs.sap.com/2021/08/23/what-is-sapui5/) for more information.
35 |
36 |
37 |
38 | ## Material Organization
39 |
40 | The material consists of a series of chapters, each in their own directory. The chapters build on top of each other and are meant to be completed in the given order. Each of the [chapters](#chapters) has its own 'readme' file with explanations, instructions, code samples and screen shots. From a session flow perspective, we are taking a "coordinated" approach:
41 |
42 | The instructor will set you off on the first chapter. Do not proceed to the next chapter until the instructor tells you to do so. If you finish a chapter before others, there are some questions at the end of each chapter for you to ponder.
43 |
44 | > The exercises are written in a conversational way - this is so that they have enough context and information to be completed outside the hands-on session itself. To help you navigate and find what you have to actually do next, there are pointers like this ➡️ throughout that indicate the things you have to actually do (as opposed to just read for background information).
45 |
46 | ## Chapters
47 |
48 | - [00 - Preparing the Development Environment](/chapters/00-prep-dev-environment/)
49 | - [01 - Scaffolding the App](/chapters/01-scaffolding/)
50 | - [02 - Creating the First View](/chapters/02-first-view/)
51 | - [03 - Creating and Consuming the First Model](/chapters/03-first-model/)
52 | - [04 - Creating and Extending the First Controller](/chapters/04-first-controller/)
53 | - [05 - Adding an 'Order' Feature](/chapters/05-order-feature/)
54 | - [06 - Adding a 'Search' Feature](/chapters/06-search-feature/)
55 | - [07 - Adding Expression Binding and Custom Formatting](/chapters/07-formatting/)
56 | - [08 - Adding i18n Features](/chapters/08-i18n/)
57 | - [09 - Adding Custom CSS](/chapters/09-custom-css/)
58 | - [10 - Deploying the App](/chapters/10-deployment/) (Optional)
59 | - [11 - Further Improvements and Learning Material](/chapters/11-further-improvements/)
60 |
61 | ## SAPUI5 vs. OpenUI5
62 |
63 | You will often read about either SAPUI5 or OpenUI5 when working with the framework. The main difference between the two is the license. Whereas SAPUI5 requires a license and is integrated into a lot of SAP products, OpenUI5 is open source and generally available under an Apache 2.0 license. SAPUI5 includes more libraries than OpenUI5, but the latter still contains all central functionality and most commonly used control libraries are identical in both deliveries.
64 |
65 | The material in this repository would work with both deliveries, but uses OpenUI5. For the sake of simplicity and to indicate that the material would work with SAPUI5, too, the material simply refers to the framework as 'UI5'.
66 |
67 | You can find more information about this in the [SAPUI5 Documentation](https://sapui5.hana.ondemand.com/#/topic/5982a9734748474aa8d4af9c3d8f31c0).
68 |
69 | ## Feedback
70 |
71 | If you can spare a couple of minutes at the end of the session, please help the [author](https://github.com/nicoschoenteich) improve for next time by providing some feedback.
72 |
73 | Simply use this [template](https://github.com/SAP-samples/ui5-exercises-codejam/issues/new?assignees=&labels=feedback&template=session-feedback-template.md&title=Session%20Feedback) link to create a special "feedback" issue, and follow the instructions in there.
74 |
75 | Thank you!
76 |
77 | ## Support
78 |
79 | Support for the content in this repository is available during SAP CodeJam events, for which this content has been designed. Otherwise, this content is provided 'as-is' with no other support.
80 |
81 | ## Contributing
82 | If you wish to contribute code, offer fixes or improvements, please send a pull request. Due to legal reasons, contributors will be asked to accept a DCO when they create the first pull request to this project. This happens in an automated fashion during the submission process. SAP uses [the standard DCO text of the Linux Foundation](https://developercertificate.org/).
83 |
84 | ## License
85 | Copyright (c) 2022 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.
86 |
--------------------------------------------------------------------------------
/REUSE.toml:
--------------------------------------------------------------------------------
1 | version = 1
2 | SPDX-PackageName = "ui5-exercises-codejam"
3 | SPDX-PackageSupplier = "Nico Schoenteich "
4 | SPDX-PackageDownloadLocation = ""
5 | SPDX-PackageComment = "The code in this project may include calls to APIs (\"API Calls\") of\n SAP or third-party products or services developed outside of this project\n (\"External Products\").\n \"APIs\" means application programming interfaces, as well as their respective\n specifications and implementing code that allows software to communicate with\n other software.\n API Calls to External Products are not licensed under the open source license\n that governs this project. The use of such API Calls and related External\n Products are subject to applicable additional agreements with the relevant\n provider of the External Products. In no event shall the open source license\n that governs this project grant any rights in or to any External Products,or\n alter, expand or supersede any terms of the applicable additional agreements.\n If you have a valid license agreement with SAP for the use of a particular SAP\n External Product, then you may make use of any API Calls included in this\n project's code for that SAP External Product, subject to the terms of such\n license agreement. If you do not have a valid license agreement for the use of\n a particular SAP External Product, then you may only make use of any API Calls\n in this project for that SAP External Product for your internal, non-productive\n and non-commercial test and evaluation of such API Calls. Nothing herein grants\n you any rights to use or access any SAP External Product, or provide any third\n parties the right to use of access any SAP External Product, through API Calls."
6 |
7 | [[annotations]]
8 | path = "**"
9 | precedence = "aggregate"
10 | SPDX-FileCopyrightText = "2022 SAP SE or an SAP affiliate company and ui5-exercises-codejam contributors"
11 | SPDX-License-Identifier = "Apache-2.0"
12 |
--------------------------------------------------------------------------------
/bookshop/finished-webapp/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bookshop",
3 | "version": "0.0.1",
4 | "scripts": {
5 | "dev": "ui5 serve --open \"index.html\"",
6 | "dev:mock": "ui5 serve --open \"test/mockServer.html\""
7 |
8 | },
9 | "devDependencies": {
10 | "@ui5/cli": "^4",
11 | "@sap/ux-ui5-tooling": "^1"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/bookshop/finished-webapp/ui5.yaml:
--------------------------------------------------------------------------------
1 | specVersion: '2.6'
2 | metadata:
3 | name: bookshop
4 | type: application
5 | server:
6 | customMiddleware:
7 | - name: fiori-tools-proxy
8 | afterMiddleware: compression
9 | configuration:
10 | backend:
11 | - path: /v2/browse
12 | url: https://developer-advocates-free-tier-central-hana-cloud-instan3b540fd6.cfapps.us10.hana.ondemand.com
13 |
--------------------------------------------------------------------------------
/bookshop/finished-webapp/webapp/Component.js:
--------------------------------------------------------------------------------
1 | sap.ui.define([
2 | "sap/ui/core/UIComponent"
3 | ], function (UIComponent) {
4 | "use strict"
5 | return UIComponent.extend(
6 | "sap.codejam.Component", {
7 | metadata : {
8 | "interfaces": [
9 | "sap.ui.core.IAsyncContentCreation"
10 | ],
11 | manifest: "json"
12 | },
13 | init : function () {
14 | UIComponent.prototype.init.apply(
15 | this,
16 | arguments
17 | )
18 | }
19 | })
20 | }
21 | )
--------------------------------------------------------------------------------
/bookshop/finished-webapp/webapp/controller/App.controller.js:
--------------------------------------------------------------------------------
1 | sap.ui.define([
2 | "sap/ui/core/mvc/Controller",
3 | "sap/m/MessageToast",
4 | "sap/m/Dialog",
5 | "sap/m/Button",
6 | "sap/m/Text",
7 | "sap/ui/model/Filter",
8 | "sap/ui/model/FilterOperator",
9 | "../model/formatter"
10 | ], function (Controller, MessageToast, Dialog, Button, Text, Filter, FilterOperator, formatter) {
11 | "use strict"
12 | return Controller.extend("sap.codejam.controller.App", {
13 | formatter: formatter,
14 | onSelect: function (oEvent) {
15 | const oSource = oEvent.getSource()
16 | const contextPath = oSource.getBindingContextPath()
17 | const form = this.getView().byId("bookDetails")
18 | form.bindElement(contextPath)
19 | },
20 | onSubmitOrder: function (oEvent) {
21 | const oBindingContext = this.getView().byId("bookDetails").getBindingContext()
22 | const selectedBookID = oBindingContext.getProperty("ID")
23 | const selectedBookTitle = oBindingContext.getProperty("title")
24 | const inputValue = this.getView().byId("stepInput").getValue()
25 |
26 | const i18nModel = this.getView().getModel("i18n")
27 | const oModel = this.getView().getModel()
28 | oModel.callFunction("/submitOrder", {
29 | method: "POST",
30 | urlParameters: {
31 | "book": selectedBookID,
32 | "quantity": inputValue
33 | },
34 | success: function(oData, oResponse) {
35 | oModel.refresh()
36 | const oText = `${i18nModel.getProperty("orderSuccessful")} (${selectedBookTitle}, ${inputValue} ${i18nModel.getProperty("pieces")})`
37 | MessageToast.show(oText)
38 | },
39 | error: function(oError) {
40 | if (oError.responseText) {
41 | oError = JSON.parse(oError.responseText).error
42 | }
43 | this.oErrorMessageDialog = new Dialog({
44 | type: "Standard",
45 | title: i18nModel.getProperty("Error"),
46 | state: "Error",
47 | content: new Text({ text: oError.message })
48 | .addStyleClass("sapUiTinyMargin"),
49 | beginButton: new Button({
50 | text: i18nModel.getProperty("Close"),
51 | press: function () {
52 | this.oErrorMessageDialog.close()
53 | }.bind(this)
54 | })
55 | })
56 | this.oErrorMessageDialog.open()
57 | }.bind(this)
58 | })
59 | },
60 | onSearch: function (oEvent) {
61 | const sQuery = oEvent.getParameter("newValue")
62 | const aFilter = []
63 | if (sQuery) {
64 | aFilter.push(new Filter("title", FilterOperator.Contains, sQuery))
65 | }
66 | const oList = this.byId("booksTable")
67 | const oBinding = oList.getBinding("items")
68 | oBinding.filter(aFilter)
69 | },
70 | onAfterRendering: function () {
71 | this.getView().byId("orderBtn").setEnabled(false)
72 | }
73 | })
74 | })
--------------------------------------------------------------------------------
/bookshop/finished-webapp/webapp/css/style.css:
--------------------------------------------------------------------------------
1 | .orderControls {
2 | gap: 20px;
3 | }
--------------------------------------------------------------------------------
/bookshop/finished-webapp/webapp/i18n/i18n.properties:
--------------------------------------------------------------------------------
1 | Bookshop=Bookshop
2 | Book=Book
3 | Author=Author
4 | Genre=Genre
5 | Price=Price
6 | Stock=Stock
7 | Order=Order
8 | orderSuccessful=Order successful
9 | pieces=pcs.
10 | Error=Error
11 | Close=Close
--------------------------------------------------------------------------------
/bookshop/finished-webapp/webapp/i18n/i18n_de.properties:
--------------------------------------------------------------------------------
1 | Bookshop=Buchhandlung
2 | Book=Buch
3 | Author=Autor
4 | Genre=Genre
5 | Price=Preis
6 | Stock=Verfügbarkeit
7 | Order=Bestellen
8 | orderSuccessful=Bestellung erfolgreich
9 | pieces=Stk.
10 | Error=Fehler
11 | Close=Schließen
--------------------------------------------------------------------------------
/bookshop/finished-webapp/webapp/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
16 |
17 |
18 |
19 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/bookshop/finished-webapp/webapp/localService/metadata.xml:
--------------------------------------------------------------------------------
1 |
4 |
7 |
8 |
9 |
11 |
12 |
13 |
16 |
17 |
18 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
53 |
54 |
55 |
56 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
--------------------------------------------------------------------------------
/bookshop/finished-webapp/webapp/localService/mockdata/Books.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "createdAt": "2023-02-22T17:14:38.366Z",
4 | "modifiedAt": "2023-02-22T17:14:38.366Z",
5 | "ID": 201,
6 | "title": "Wuthering Heights",
7 | "descr": "Wuthering Heights, Emily Brontë's only novel, was published in 1847 under the pseudonym \"Ellis Bell\". It was written between October 1845 and June 1846. Wuthering Heights and Anne Brontë's Agnes Grey were accepted by publisher Thomas Newby before the success of their sister Charlotte's novel Jane Eyre. After Emily's death, Charlotte edited the manuscript of Wuthering Heights and arranged for the edited version to be published as a posthumous second edition in 1850.",
8 | "author": "Emily Brontë",
9 | "genre_ID": 11,
10 | "stock": 0,
11 | "price": 11.11,
12 | "currency_code": "GBP"
13 | },
14 | {
15 | "createdAt": "2023-02-22T17:14:38.366Z",
16 | "modifiedAt": "2023-02-22T17:14:38.366Z",
17 | "ID": 207,
18 | "title": "Jane Eyre",
19 | "descr": "Jane Eyre /ɛər/ (originally published as Jane Eyre: An Autobiography) is a novel by English writer Charlotte Brontë, published under the pen name \"Currer Bel\", on 16 October 1847, by Smith, Elder & Co. of London. The first American edition was published the following year by Harper & Brothers of New York. Primarily a bildungsroman, Jane Eyre follows the experiences of its eponymous heroine, including her growth to adulthood and her love for Mr. Rochester, the brooding master of Thornfield Hall. The novel revolutionised prose fiction in that the focus on Jane's moral and spiritual development is told through an intimate, first-person narrative, where actions and events are coloured by a psychological intensity. The book contains elements of social criticism, with a strong sense of Christian morality at its core and is considered by many to be ahead of its time because of Jane's individualistic character and how the novel approaches the topics of class, sexuality, religion and feminism.",
20 | "author": "Charlotte Brontë",
21 | "genre_ID": 11,
22 | "stock": 11,
23 | "price": 12.34,
24 | "currency_code": "GBP"
25 | },
26 | {
27 | "createdAt": "2023-02-22T17:14:38.366Z",
28 | "modifiedAt": "2023-02-22T17:14:38.366Z",
29 | "ID": 251,
30 | "title": "The Raven",
31 | "descr": "\"The Raven\" is a narrative poem by American writer Edgar Allan Poe. First published in January 1845, the poem is often noted for its musicality, stylized language, and supernatural atmosphere. It tells of a talking raven's mysterious visit to a distraught lover, tracing the man's slow fall into madness. The lover, often identified as being a student, is lamenting the loss of his love, Lenore. Sitting on a bust of Pallas, the raven seems to further distress the protagonist with its constant repetition of the word \"Nevermore\". The poem makes use of folk, mythological, religious, and classical references.",
32 | "author": "Edgar Allen Poe",
33 | "genre_ID": 16,
34 | "stock": 333,
35 | "price": 13.13,
36 | "currency_code": "USD"
37 | },
38 | {
39 | "createdAt": "2023-02-22T17:14:38.366Z",
40 | "modifiedAt": "2023-02-22T17:14:38.366Z",
41 | "ID": 252,
42 | "title": "Eleonora",
43 | "descr": "\"Eleonora\" is a short story by Edgar Allan Poe, first published in 1842 in Philadelphia in the literary annual The Gift. It is often regarded as somewhat autobiographical and has a relatively \"happy\" ending.",
44 | "author": "Edgar Allen Poe",
45 | "genre_ID": 16,
46 | "stock": 555,
47 | "price": 14,
48 | "currency_code": "USD"
49 | },
50 | {
51 | "createdAt": "2023-02-22T17:14:38.366Z",
52 | "modifiedAt": "2023-02-22T17:14:38.366Z",
53 | "ID": 271,
54 | "title": "Catweazle",
55 | "descr": "Catweazle is a British fantasy television series, starring Geoffrey Bayldon in the title role, and created by Richard Carpenter for London Weekend Television. The first series, produced and directed by Quentin Lawrence, was screened in the UK on ITV in 1970. The second series, directed by David Reid and David Lane, was shown in 1971. Each series had thirteen episodes, most but not all written by Carpenter, who also published two books based on the scripts.",
56 | "author": "Richard Carpenter",
57 | "genre_ID": 13,
58 | "stock": 22,
59 | "price": 150,
60 | "currency_code": "JPY"
61 | }
62 | ]
--------------------------------------------------------------------------------
/bookshop/finished-webapp/webapp/localService/mockdata/Currencies.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "Euro",
4 | "descr": null,
5 | "code": "EUR",
6 | "symbol": "€"
7 | },
8 | {
9 | "name": "British Pound",
10 | "descr": null,
11 | "code": "GBP",
12 | "symbol": "£"
13 | },
14 | {
15 | "name": "Shekel",
16 | "descr": null,
17 | "code": "ILS",
18 | "symbol": "₪"
19 | },
20 | {
21 | "name": "Yen",
22 | "descr": null,
23 | "code": "JPY",
24 | "symbol": "¥"
25 | },
26 | {
27 | "name": "US Dollar",
28 | "descr": null,
29 | "code": "USD",
30 | "symbol": "$"
31 | }
32 | ]
--------------------------------------------------------------------------------
/bookshop/finished-webapp/webapp/localService/mockdata/Genres.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "ID": 10,
4 | "name": "Fiction"
5 | },
6 | {
7 | "ID": 11,
8 | "name": "Drama"
9 | },
10 | {
11 | "ID": 12,
12 | "name": "Poetry"
13 | },
14 | {
15 | "ID": 13,
16 | "name": "Fantasy"
17 | },
18 | {
19 | "ID": 14,
20 | "name": "Science Fiction"
21 | },
22 | {
23 | "ID": 15,
24 | "name": "Romance"
25 | },
26 | {
27 | "ID": 16,
28 | "name": "Mystery"
29 | },
30 | {
31 | "ID": 17,
32 | "name": "Thriller"
33 | },
34 | {
35 | "ID": 18,
36 | "name": "Dystopia"
37 | },
38 | {
39 | "ID": 19,
40 | "name": "Fairy Tale"
41 | },
42 | {
43 | "ID": 20,
44 | "name": "Non-Fiction"
45 | },
46 | {
47 | "ID": 21,
48 | "name": "Biography"
49 | },
50 | {
51 | "ID": 22,
52 | "name": "Autobiography"
53 | },
54 | {
55 | "ID": 23,
56 | "name": "Essay"
57 | },
58 | {
59 | "ID": 24,
60 | "name": "Speech"
61 | }
62 | ]
--------------------------------------------------------------------------------
/bookshop/finished-webapp/webapp/localService/mockserver.js:
--------------------------------------------------------------------------------
1 | sap.ui.define([
2 | "sap/ui/core/util/MockServer",
3 | "sap/base/util/UriParameters"
4 | ], function (MockServer, UriParameters) {
5 | "use strict"
6 |
7 | return {
8 | init: function () {
9 | // create
10 | const oMockServer = new MockServer({
11 | rootUri: "/v2/browse/"
12 | })
13 |
14 | const oUriParameters = new UriParameters(window.location.href)
15 |
16 | // configure mock server with a delay
17 | MockServer.config({
18 | autoRespond: true,
19 | autoRespondAfter: oUriParameters.get("serverDelay") || 500
20 | })
21 |
22 | // simulate
23 | const sPath = sap.ui.require.toUrl("sap/codejam/localService")
24 | oMockServer.simulate(sPath + "/metadata.xml", sPath + "/mockdata")
25 |
26 | // mock custom function
27 | const defaultRequests = oMockServer.getRequests()
28 | oMockServer.setRequests(defaultRequests.concat({
29 | method: "POST",
30 | path: new RegExp("submitOrder(.*)"),
31 | response: function (oXhr, sUrlParams) {
32 | const responseBody = { d: {} } // sending empty data, just mocking backend functionality
33 | const responseHeader = { "Content-Type": "application/json" }
34 | oXhr.respond(200, responseHeader, JSON.stringify(responseBody))
35 | }
36 | }))
37 |
38 | // start
39 | oMockServer.start()
40 | }
41 | }
42 |
43 | })
--------------------------------------------------------------------------------
/bookshop/finished-webapp/webapp/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "sap.app": {
3 | "id": "codejam",
4 | "type": "application",
5 | "title": "CodeJam Bookshop",
6 | "applicationVersion": {
7 | "version": "1.0.0"
8 | },
9 | "dataSources": {
10 | "remoteBookshop": {
11 | "uri": "/v2/browse/",
12 | "type" : "OData",
13 | "settings" : {
14 | "odataVersion" : "2.0"
15 | }
16 | }
17 | }
18 | },
19 | "sap.ui5": {
20 | "rootView": {
21 | "viewName": "sap.codejam.view.App",
22 | "type": "XML",
23 | "id": "app"
24 | },
25 | "models": {
26 | "": {
27 | "dataSource": "remoteBookshop"
28 | },
29 | "i18n": {
30 | "type": "sap.ui.model.resource.ResourceModel",
31 | "settings": {
32 | "bundleName": "sap.codejam.i18n.i18n"
33 | }
34 | }
35 | },
36 | "resources": {
37 | "css": [
38 | {
39 | "uri": "css/style.css"
40 | }
41 | ]
42 | }
43 | }
44 | }
--------------------------------------------------------------------------------
/bookshop/finished-webapp/webapp/model/formatter.js:
--------------------------------------------------------------------------------
1 | sap.ui.define([], function () {
2 | "use strict"
3 | return {
4 | inputLowerThanStock: function (availableStock) {
5 | const inputValue = this.getView().byId("stepInput").getValue()
6 | return inputValue <= availableStock
7 | }
8 | }
9 | })
--------------------------------------------------------------------------------
/bookshop/finished-webapp/webapp/test/initMockServer.js:
--------------------------------------------------------------------------------
1 | sap.ui.define([
2 | "../localService/mockserver"
3 | ], function (mockserver) {
4 | "use strict"
5 |
6 | // initialize the mock server
7 | mockserver.init()
8 |
9 | // initialize the embedded component on the HTML page
10 | sap.ui.require(["sap/ui/core/ComponentSupport"])
11 | })
--------------------------------------------------------------------------------
/bookshop/finished-webapp/webapp/test/mockServer.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
17 |
18 |
19 |
20 |
60 |
61 |
65 |
73 |
79 |
80 |
81 |
82 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/chapters/00-prep-dev-environment/readme.md:
--------------------------------------------------------------------------------
1 | # Chapter 00 - Preparing the Development Environment
2 |
3 | By the end of this chapter we will have prepared our development environment so that we can start developing our bookshop app.
4 |
5 | ## Steps
6 |
7 | [1. Prerequisites](#1-prerequisites)
8 | [2. Navigate into your preferred directory](#2-navigate-into-your-preferred-directory)
9 | [3. Clone this repository](#3-clone-this-repository)
10 | [4. Navigate into the newly created directory](#4-navigate-into-the-newly-created-directory)
11 | [5. Open the directory](#5-open-the-directory)
12 |
13 | ### 1. Prerequisites
14 |
15 | To be able to follow the instructions in this repository you need to fulfill ***one*** of the following requirements:
16 |
17 | - **Option 1:** You have access to an instance of the SAP Business Application Studio with the role collection `Business_Application_Studio_Developer` assigned to you in the SAP Business Technology Platform Cockpit. Create a new dev space of type `Full Stack Cloud Application` to get started.
18 |
19 | > If you don't have access to the SAP Business Application Studio yet, check this tutorial on [how to get a free account on SAP BTP trial](https://developers.sap.com/tutorials/hcp-create-trial-account.html), from where you can [subscribe to the SAP Business Application Studio](https://developers.sap.com/tutorials/appstudio-onboarding.html).
20 |
21 | - **Option 2:** You can use your local machine and have the following tools installed:
22 | - [Node.js](https://nodejs.org/en/) (version 18 or higher) including `npm`
23 | - [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
24 | - Your favorite code editor (e.g. [Visual Studio Code](https://code.visualstudio.com/download))
25 | - You need privileges to install npm packages from the [npm registry](https://www.npmjs.com/)
26 |
27 | - **Option 3:** You can run this project in a devcontainer using the [provided configuration](/.devcontainer). You need to meet *one* of the following requirements:
28 | - **Option 3a**: You have GitHub codespaces enabled for your GitHub organization and account.
29 | - **Option 3b**: You have [Docker Desktop](https://www.docker.com/products/docker-desktop/) installed on your machine and can use a [Visual Studio Code extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) to run the devcontainer locally.
30 |
31 | ### 2. Navigate into your preferred directory
32 |
33 | ➡️ In your development environment (see [step 1](#1-prerequisites)), open a new terminal session and navigate to where you want to store this repository.
34 |
35 | ### 3. Clone this repository
36 |
37 | ➡️ Execute the following command to clone this repository:
38 |
39 | ```bash
40 | git clone https://github.com/SAP-samples/ui5-exercises-codejam
41 | ```
42 |
43 | The command created new directory for the cloned repository.
44 |
45 | ### 4. Navigate into the newly created directory
46 |
47 | We want to navigate into the `bookshop` directory inside newly created directory, as this is where we will build our application.
48 |
49 | ➡️ Execute the following command in the same terminal session:
50 |
51 | ```bash
52 | cd ui5-exercises-codejam/bookshop
53 | ```
54 |
55 | ### 5. Open the directory
56 |
57 | ➡️ Open the directory in the code editor of your choice, depending on the option you chose in [step 1](#1-prerequisites).
58 |
59 | Continue to [Chapter 01 - Scaffolding the App](/chapters/01-scaffolding/)
60 |
--------------------------------------------------------------------------------
/chapters/01-scaffolding/readme.md:
--------------------------------------------------------------------------------
1 | # Chapter 01 - Scaffolding the App
2 |
3 | By the end of this chapter, we will have scaffolded an empty UI5 application.
4 |
5 | ## Steps
6 |
7 | [0. Make sure you are in the project root (`bookshop/`)](#0-make-sure-you-are-in-the-project-root-bookshop)
8 | [1. Create a new `webapp/` directory for the UI5 app](#1-create-a-new-webapp-directory-for-the-ui5-app)
9 | [2. Create a `webapp/index.html` file](#2-create-a-webappindexhtml-file)
10 | [3. Add `ComponentSupport` to the `webapp/index.html` file](#3-add-componentsupport-to-the-webappindexhtml-file)
11 | [4. Create a `webapp/Component.js` file](#4-create-a-webappcomponentjs-file)
12 | [5. Create a `webapp/manifest.json` file](#5-create-a-webappmanifestjson-file)
13 | [6. Create a `ui5.yaml` file](#6-create-a-ui5yaml-file)
14 | [7. Create a `package.json` file](#7-create-a-packagejson-file)
15 | [8. Install dependencies and start application](#8-install-dependencies-and-start-application)
16 |
17 | ### 0. Make sure you are in the project root (`bookshop/`)
18 |
19 | ➡️ Make sure you are in the `bookshop/` directory, which is our project root.
20 |
21 | The material in this repository will always reference directories and files in relation to the project root.
22 |
23 | ### 1. Create a new `webapp/` directory for the UI5 app
24 |
25 | Let's begin this project by creating an empty directory, which our UI5 application is going to sit in.
26 |
27 | ➡️ Create a new `webapp/` directory inside the `bookshop/` directory.
28 |
29 | ### 2. Create a `webapp/index.html` file
30 |
31 | Like most other web applications our UI5 app needs an `index.html` serving as the entry point.
32 |
33 | ➡️ Create a new `webapp/index.html` file and paste the following code into it:
34 |
35 | ```html
36 |
37 |
38 |
39 |
40 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | ```
57 |
58 | We loaded the UI5 framework into our project and configured a few attributes such as the theme and library we want to use, and the name of our project root. This process is called **bootstrapping**.
59 |
60 |
61 | What is HTML and how is it structured? 💬
62 |
63 |
64 |
65 | > HTML (HyperText Markup Language) is the standard markup language for documents that are designed to be displayed in a web browser (web pages) and is one of the fundamental building blocks of the web. It is used to describe a web page's elements such as paragraphs, links and images.
66 | >
67 | >An HTML file usually includes 4 major parts.
68 | > 1. `` is the document type declaration and tells the browser what the document is - HTML.
69 | > 1. In HTML, elements always have an opening and a closing tag and everything in between is considered to be a child of that element. The first element of a document is the HTML element itself, represented by the opening `` and closing `` tag. This tells the browser that everything between those markers should be interpreted as HTML.
70 | > 1. The first child of the HTML element is the head. The head includes meta information about the document.
71 | > 1. The second child of the HTML element is the body. The body includes the actual content of the document. Elements such as paragraphs, links and images will be children of the body.
72 | >
73 | > An `index.html` file is usually the entry point to a web page. Our UI5 app is no exception to that.
74 |
75 |
76 |
77 |
78 | What do the attributes inside the <script /> element mean? 💬
79 |
80 |
81 |
82 | > An HTML `` element tells the browser that its content should be interpreted as JavaScript code. In our case we don't have content inside the tags, but rather specify attributes of that `` element. By specifying these attributes we initialize the UI5 framework and turn our blank `index.html` file into a UI5 project. This step is called **bootstrapping**.
83 | >
84 | > Let's go through each of the attributes step-by-step:
85 | > - We specify an `id` for the `` element, which is used by the framework to find out where it was initialized from.
86 | > - The `src` attribute defines where the JavaScript code for the script tag lives. This JavaScript code is the UI5 framework. As you can see, we are loading OpenUI5. You can visit [https://openui5.hana.ondemand.com/resources/sap-ui-core.js](https://openui5.hana.ondemand.com/resources/sap-ui-core.js) and see the code that makes up the framework.
87 | > - With the `data-sap-ui-theme` attribute we specify which UI5 theme we want to use. This is the parameter we can modify to change the looks of our app. You can read more about Theming in the [SAPUI5 Documentation](https://sapui5.hana.ondemand.com/sdk/#/topic/497c27a8ee26426faacd2b8a1751794a).
88 | > - With the `data-sap-ui-libs` attribute, which is technically optional, but should always be used, we specify which UI5 library we want to preload before our app is initialized. This drastically improves the performance of our app. We can always load other libraries into our views and controllers on demand (see examples in [chapter 02](/chapters/02-first-view/readme.md#1-create-a-webappviewappviewxml-file) of the current chapter).
89 | > - With the `data-sap-ui-compatVersion` attribute we specify which version of certain UI5 features we want to use in case of incompatibilities. Since this concept has been abandoned the [SAPUI5 Documentation](https://sapui5.hana.ondemand.com/sdk/#/topic/9feb96da02c2429bb1afcf6534d77c79.html) suggests to set this value to `edge`.
90 | > - With the `data-sap-ui-resourceroots` attribute we define a namespace for a certain location in our project. In our case we gave the root of our project (`./`) the namespace `sap.codejam`. We will use this namespace to reference our project root in other places of our code (e.g. in [step 5](#5-create-a-webappmanifestjson-file) of this chapter).
91 |
92 |
93 |
94 |
95 | ### 3. Add `ComponentSupport` to the `webapp/index.html` file
96 |
97 | At this point we could theoretically already start instantiating UI5 elements (also called ***controls***), but because we want to make sure our project scales well, we will follow one of the best practices and wrap our app into a ***component*** first. A component is an independent and reusable part. This makes our app independent from the environment it's running in. In our case the component will be started from the `index.html`, but because it is encapsulated and reuseable, the same component could also be started from another `html` file that is powering a Fiori Launchpad for example. A UI5 component most of the times contains a whole UI5 app, so it's a little different from components you may now from other frameworks. If you want to reuse smaller UI parts, such as a single button or dialog, UI5 offers the concept of [fragments](https://sapui5.hana.ondemand.com/sdk/#/topic/4da72985139b4b83b5f1c1e0c0d2ed5a), which we will not cover in this project.
98 |
99 | We need to add the `ComponentSupport` to the bootstrapping of our app and add a component to our html ``.
100 |
101 | ➡️ Replace the existing content of the `webapp/index.html` file with the following code:
102 |
103 | ```html
104 |
105 |
106 |
107 |
108 |
119 |
120 |
121 |
122 |
127 |
128 |
129 |
130 |
131 | ```
132 |
133 | We used the `data-sap-ui-oninit` attribute in our bootstrapping to specify that we want to initialize a UI5 Component. We also added a new HTML element (`div`) to our `` that holds the component. The component set up is defined in a `Component.js` file, which we will create in the next step.
134 |
135 | ### 4. Create a `webapp/Component.js` file
136 |
137 | Our `index.html` is now actively looking for a `Component.js` file on root level of our UI5 app. This is an important naming convention, so don't change the name of this file.
138 |
139 | In case you are wondering, we configured the root of our project, which is the `webapp/` directory, during the bootstrapping in [step 2](#2-create-a-webappindexhtml-file) of this chapter.
140 |
141 | ➡️ Create a new `webapp/Component.js` file and paste the following code into it:
142 |
143 | ```javascript
144 | sap.ui.define([
145 | "sap/ui/core/UIComponent"
146 | ], function (UIComponent) {
147 | "use strict"
148 | return UIComponent.extend(
149 | "sap.codejam.Component", {
150 | metadata : {
151 | "interfaces": [
152 | "sap.ui.core.IAsyncContentCreation"
153 | ],
154 | manifest: "json"
155 | },
156 | init : function () {
157 | UIComponent.prototype.init.apply(
158 | this,
159 | arguments
160 | )
161 | }
162 | })
163 | }
164 | )
165 | ```
166 |
167 | We have set up our component by initializing the `UIComponent` from the UI5 library. We extended it with some metadata, referencing the `manifest.json`, which we will create next.
168 |
169 |
170 | What does this code do in detail? 💬
171 |
172 |
173 |
174 | > Our component set up is essentially a JavaScript module. We have defined it with the `sap.ui.define` method. This method takes two parameters (also see its [documentation](https://sapui5.hana.ondemand.com/sdk/#/api/sap.ui%23methods/sap.ui.define)):
175 | > 1. An array of dependencies from UI5 libraries
176 | > 1. A function that will be called
177 | >
178 | > Our only dependency is the `UIComponent` class, which we pass to the function. This function returns the `UIComponent`, but [extends](https://sapui5.hana.ondemand.com/sdk/#/api/sap.ui.core.UIComponent%23methods/sap.ui.core.UIComponent.extend) it with a new subclass that we call `sap.codejam.Component`. This subclass is enriched with a `metadata` parameter, which is an object that points to a `manifest.json` and makes sure that the `UIComponent` is created fully asynchronously (`"sap.ui.core.IAsyncContentCreation"`). The subclass is also enriched by an `init` function, which is automatically invoked by the framework when the component is instantiated. Inside this function, we make sure that the init function of the `UIComponent`'s parent is invoked (which is obligatory).
179 | >
180 | > Read more about the component configuration in the [SAPUI5 Documentation](https://sapui5.hana.ondemand.com/sdk/#/topic/4cfa60872dca462cb87148ccd0d948ee).
181 |
182 |
183 |
184 | ### 5. Create a `webapp/manifest.json` file
185 |
186 | The `manifest.json` is our application descriptor file and holds metadata about our app
187 |
188 | ➡️ Create a new `webapp/manifest.json` and paste the following code into it:
189 |
190 | ```json
191 | {
192 | "sap.app": {
193 | "id": "codejam",
194 | "type": "application",
195 | "title": "CodeJam Bookshop",
196 | "applicationVersion": {
197 | "version": "1.0.0"
198 | },
199 | "dataSources": {}
200 | },
201 | "sap.ui5": {
202 | "rootView": {
203 | "viewName": "sap.codejam.view.App",
204 | "type": "XML",
205 | "id": "app"
206 | },
207 | "models": {}
208 | }
209 | }
210 | ```
211 |
212 |
213 | What is a manifest.json file? 💬
214 |
215 |
216 |
217 | > A `manifest.json` file is usually used to define metadata about a web app or extension, like its name, icon, or other details. It is not specific to UI5. Apps built with other frameworks also have this file. The `manifest.json` is especially important for UI5, as it is used by the framework during runtime to define important properties of the app, such as data sources, localization settings, and [many more](https://sapui5.hana.ondemand.com/sdk/#/topic/be0cf40f61184b358b5faedaec98b2da.html#loiobe0cf40f61184b358b5faedaec98b2da/section_nonamespace).
218 |
219 |
220 |
221 | This is what our project's structure now looks like:
222 |
223 | ```text
224 | - bookshop/
225 | - webapp/
226 | - Component.js
227 | - index.html
228 | - manifest.json
229 | ```
230 |
231 | At this point the UI5 application itself is complete, although it's more an empty shell. We have a `webapp/index.html` file (entry point) containing our component, which references our `manifest.json`, which describes our application. The only thing missing is a web server to serve these files. Let's implement this web server next.
232 |
233 | ### 6. Create a `ui5.yaml` file
234 |
235 | It is recommended to use the [UI5 Tooling](https://sap.github.io/ui5-tooling/v2/) to serve a UI5 application. This tooling requires a `ui5.yaml` file describing the project.
236 |
237 | ➡️ Create a new `ui5.yaml` file in the *project root* (NOT in the `webapp/` directory) with the following content:
238 |
239 | ```yaml
240 | specVersion: '2.6'
241 | metadata:
242 | name: bookshop
243 | type: application
244 | ```
245 |
246 | We created a minimal `ui5.yaml` file describing our UI5 project. The UI5 Tooling uses this file to configure the web server that the application will be hosted on.
247 |
248 | ### 7. Create a `package.json` file
249 |
250 | The UI5 Tooling is a Node.js based package, so we have to turn our project into a Node.js based project to be able to use it. We can do so by adding a `package.json` file to the project root.
251 |
252 | ➡️ Create a new `package.json` file in the *project root* (NOT in the `webapp/` directory) with the following content:
253 |
254 | ```json
255 | {
256 | "name": "bookshop",
257 | "version": "0.0.1",
258 | "scripts": {
259 | "dev": "ui5 serve --open \"index.html\""
260 | },
261 | "devDependencies": {
262 | "@ui5/cli": "^4"
263 | }
264 | }
265 | ```
266 |
267 | We added a new `package.json` file, which essentially turns our project into a Node.js based project. We currently only have one script and one (development) dependency, which are both pointing to the UI5 Tooling (`@ui5/cli`). We can now finally start installing the tooling and start our application.
268 |
269 | ### 8. Install dependencies and start application
270 |
271 | ➡️ Open a new terminal session on root level of our project and run the following command:
272 |
273 | ```bash
274 | npm install && npm run dev
275 | ```
276 |
277 | We installed the project's dependencies and executed the `start` script of our project. This script is like a "shortcut" for the `ui5 serve --open "index.html"` command of the UI5 Tooling. You should see a new browser window or tab automatically opening and displaying our (empty) UI5 application.
278 |
279 | This is what our project structure now looks like:
280 |
281 | ```text
282 | - bookshop/
283 | + node_modules/
284 | - webapp/
285 | - Component.js
286 | - index.html
287 | - manifest.json
288 | - package-lock.json
289 | - package.json
290 | - ui5.yaml
291 | ```
292 |
293 | This is what our application currently looks like:
294 |
295 | 
296 |
297 | Continue to [Chapter 02 - Creating the First View](/chapters/02-first-view/)
298 |
--------------------------------------------------------------------------------
/chapters/01-scaffolding/result.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SAP-samples/ui5-exercises-codejam/c553ebf6cb35c5458b003edd14cb6a2c5d2aa637/chapters/01-scaffolding/result.png
--------------------------------------------------------------------------------
/chapters/02-first-view/readme.md:
--------------------------------------------------------------------------------
1 | # Chapter 02 - Creating the First View
2 |
3 | By the end of this chapter, we will have added the first view to our UI5 application.
4 |
5 | ## Steps
6 |
7 | [1. Create a `webapp/view/App.view.xml` file](#1-create-a-webappviewappviewxml-file)
8 | [2. Inspect the app in the browser](#2-inspect-the-app-in-the-browser)
9 |
10 | ### 1. Create a `webapp/view/App.view.xml` file
11 |
12 | As of now, our project is more an empty shell than a real application, so let's now enrich it with some "real" content that is visible to the user - a so called "view". We already referenced our root XML view in our `webapp/manifest.json` in the [previous chapter](/chapters/01-scaffolding/readme.md#5-create-a-webappmanifestjson-file). Let's create this file now.
13 |
14 | ➡️ Create a new `webapp/view/App.view.xml` file and paste the following code into it:
15 |
16 | ```xml
17 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | ```
36 |
37 | We defined our first XML view with a few UI5 controls. UI5 controls are reusable UI elements provided by the framework. Similar to HTML elements, they have an opening and closing tag and predefined attributes (non-working example: ``). They follow the [Fiori Design Guidelines](https://experience.sap.com/fiori-design-web/) and provide a lot of functionalities out of the box. XML views are the best way to use and structure UI5 controls, as they are very easy to read and represent the hierarchical structure of controls very well. We will just call them 'views' from now on.
38 |
39 | You might be wondering how you as a developer can find out which UI5 controls to use and what attributes and APIs they have. The official [SAPUI5 API Reference](https://sapui5.hana.ondemand.com/#/api) as well as the [Code Samples](https://sapui5.hana.ondemand.com/#/controls) are your go-to resources and contain all the information you will ever need.
40 |
41 |
42 | What do the individual controls in our view do? 💬
43 |
44 |
45 |
46 | > - The [``](https://sapui5.hana.ondemand.com/#/api/sap.ui.core.mvc.View) control is our base class for the view. At the top of the file you can see that it is part of the `sap.ui.core.mvc` library. We assign this library to an xml namespace (abbreviated `xmlns`) that we call `mvc` (we will cover what `mvc` stands for in [chapter 04](/chapters/04-first-controller/readme.md#5-add-a-new-flexbox--to-the-appwebappviewappviewxml)). We always use controls by prefixing its namespace (the library it is from) followed by a colon (e.g. ``). Each view can have one default namespace, that can be omitted. In our case we assigned the `sap.m` library to the default namespace.
47 | > - The [``](https://sapui5.hana.ondemand.com/#/api/sap.m.App) control is the root element of a UI5 app. In the documentation we can see that its [default aggregation](https://sapui5.hana.ondemand.com/#/api/sap.m.App%23aggregations) is ``. This means that these are the expected children of the `` control. Aggregations are always lowercase.
48 | > - The [``](https://sapui5.hana.ondemand.com/#/api/sap.m.Page) control is a container that holds one whole screen of an app. In its documentation we can see that it can have a `title` text that appears in the page header bar. We can also see that its [default aggregation](https://sapui5.hana.ondemand.com/#/api/sap.m.Page%23aggregations) is ``.
49 | > - The [``](https://sapui5.hana.ondemand.com/#/api/sap.m.Panel%23overview) control is a container for grouping and displaying information. In its documentation we can see that we can define a `headerText` that will be displayed at the top of it. We can also see that its [default aggregation](https://sapui5.hana.ondemand.com/#/api/sap.m.Panel%23aggregations) is ``.
50 |
51 |
52 |
53 | This is what our project structure now looks like:
54 |
55 | ```text
56 | - bookshop/
57 | + node_modules/
58 | - webapp/
59 | - view/
60 | - App.view.xml
61 | - Component.js
62 | - index.html
63 | - manifest.json
64 | - package-lock.json
65 | - package.json
66 | - ui5.yaml
67 | ```
68 |
69 |
70 | ### 2. Inspect the app in the browser
71 |
72 | ➡️ Refresh the UI5 application in the browser.
73 |
74 | This is what the application now looks like:
75 |
76 | 
77 |
78 | Continue to [Chapter 03 - Creating and Consuming the First Model](/chapters/03-first-model/)
79 |
--------------------------------------------------------------------------------
/chapters/02-first-view/result.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SAP-samples/ui5-exercises-codejam/c553ebf6cb35c5458b003edd14cb6a2c5d2aa637/chapters/02-first-view/result.png
--------------------------------------------------------------------------------
/chapters/03-first-model/App.view.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SAP-samples/ui5-exercises-codejam/c553ebf6cb35c5458b003edd14cb6a2c5d2aa637/chapters/03-first-model/App.view.png
--------------------------------------------------------------------------------
/chapters/03-first-model/readme.md:
--------------------------------------------------------------------------------
1 | # Chapter 03 - Creating and Consuming the First Model
2 |
3 | By the end of this chapter, we will have added a remote bookshop data source to our app and stored the data in a model. We will also have added a table displaying this model data.
4 |
5 | ## Steps
6 |
7 | [1. Add `@sap/ux-ui5-tooling` as a dependency in the `package.json`](#1-add-sapux-ui5-tooling-as-a-dependency-in-the-packagejson)
8 | [2. Install dependencies](#2-install-dependencies)
9 | [3. Configure the `fiori-tools-proxy` in the `ui5.yaml`](#3-configure-the-fiori-tools-proxy-in-the-ui5yaml)
10 | [4. Add a new `dataSource` and `model` to the `webapp/manifest.json`](#4-add-a-new-datasource-and-model-to-the-webappmanifestjson)
11 | [5. Add a new `
` to the `webapp/view/App.view.xml`](#5-add-a-new-table--to-the-webappviewappviewxml)
12 | [6. Inspect the app in the browser](#6-inspect-the-app-in-the-browser)
13 |
14 | ### 1. Add `@sap/ux-ui5-tooling` as a dependency in the `package.json`
15 |
16 | We want to consume a remote data source []() for our bookshop. Unfortunately, we cannot call the remote source directly from our UI5 app, as this would lead to [CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) issues. We need a middleware that handles the API call for us on the server side. For that, we need to add another Node.js based package as a dependency and install it later on.
17 |
18 | ➡️ Replace the current content of the `package.json` file with the following code:
19 |
20 | ```json
21 | {
22 | "name": "bookshop",
23 | "version": "0.0.1",
24 | "scripts": {
25 | "dev": "ui5 serve --open \"index.html\""
26 | },
27 | "devDependencies": {
28 | "@ui5/cli": "^4",
29 | "@sap/ux-ui5-tooling": "^1"
30 | }
31 | }
32 | ```
33 |
34 | We added the [@sap/ux-ui5-tooling](https://www.npmjs.com/package/@sap/ux-ui5-tooling?activeTab=readme) package as a development dependency and also added it to the `ui5.dependencies` section, which will make sure the UI5 Tooling finds it. This package is part of the SAP Fiori Tools and seamlessly integrates with the UI5 Tooling. It contains a selection of custom middlewares that can be used for UI5 development.
35 |
36 | ### 2. Install dependencies
37 |
38 | In order to use the newly added dependency, we have to install it.
39 |
40 | ➡️ Run the following command in the `bookshop/` directory:
41 |
42 | ```bash
43 | npm install
44 | ```
45 |
46 | We successfully installed all our dependencies.
47 |
48 | ### 3. Configure the `fiori-tools-proxy` in the `ui5.yaml`
49 |
50 | We can now configure the custom middleware in our `ui5.yaml`.
51 |
52 | ➡️ Replace the current content of the `ui5.yaml` with the following code:
53 |
54 | ```yaml
55 | specVersion: '2.6'
56 | metadata:
57 | name: bookshop
58 | type: application
59 | server:
60 | customMiddleware:
61 | - name: fiori-tools-proxy
62 | afterMiddleware: compression
63 | configuration:
64 | backend:
65 | - path: /v2/browse
66 | url: https://developer-advocates-free-tier-central-hana-cloud-instan3b540fd6.cfapps.us10.hana.ondemand.com
67 | ```
68 |
69 | We configured a custom middleware using the `fiori-tools-proxy`. This middleware will handle all calls that our UI5 app initiates to the `v2/browse/` path and will proxy (think "forward") them to `https://developer-advocates-free-tier-central-hana-cloud-instan3b540fd6.cfapps.us10.hana.ondemand.com`. We can now add the `v2/browse/` path as the data source to our UI5 application.
70 |
71 | ### 4. Add a new `dataSource` and `model` to the `webapp/manifest.json`
72 |
73 | Models are a major part of UI5 development. We use models to store data in our app ("data layer"). Models are not bound to or represented by a specific file, but are dynamic objects that can be consumed and modified by different parts of the app. They can be created via the `manifest.json` file or via a controller.
74 |
75 | ➡️ Replace the current content of the `webapp/manifest.json` with the following code:
76 |
77 | ```json
78 | {
79 | "sap.app": {
80 | "id": "codejam",
81 | "type": "application",
82 | "title": "CodeJam Bookshop",
83 | "applicationVersion": {
84 | "version": "1.0.0"
85 | },
86 | "dataSources": {
87 | "remoteBookshop": {
88 | "uri": "/v2/browse/",
89 | "type": "OData",
90 | "settings": {
91 | "odataVersion": "2.0"
92 | }
93 | }
94 | }
95 | },
96 | "sap.ui5": {
97 | "rootView": {
98 | "viewName": "sap.codejam.view.App",
99 | "type": "XML",
100 | "id": "app"
101 | },
102 | "models": {
103 | "": {
104 | "dataSource": "remoteBookshop"
105 | }
106 | }
107 | }
108 | }
109 | ```
110 |
111 | We defined a new model with an empty string as its name, which makes it the default model of the app. The `dataSource` for the model is the `remoteBookshop`, which is a new `dataSource` we created that calls the `/v2/browse/` path and therefore gets proxied by the middleware (see [step 2](#2-configure-the-fiori-tools-proxy-in-the-ui5yaml) of this chapter). The model we created is of type [OData](https://www.odata.org/getting-started/) V2, which is a [RESTful](https://www.youtube.com/watch?v=bhn-Dl87SDE) protocol that heavily used within the SAP ecosphere. We will learn more about OData and its strengths in the upcoming chapters.
112 |
113 | ### 5. Add a new `
` to the `webapp/view/App.view.xml`
114 |
115 | We can now go ahead an consume the newly created model in our `webapp/view/App.view.xml`.
116 |
117 | ➡️ Paste the following code into the `` section of the `` in our existing view:
118 |
119 | ```xml
120 |
159 | ```
160 |
161 | We created a Table that displays the books from our bookshop data. Let's go through the code step by step to better understand how we did it:
162 |
163 | - We created a new [`
`](https://sapui5.hana.ondemand.com/#/api/sap.m.Table) that holds `` and `` (think "rows") as its aggregations.
164 | - The `
` has an `items` attribute that we want to bind to the `Books` in our data model (you can access the data at [https://developer-advocates-free-tier-central-hana-cloud-instan3b540fd6.cfapps.us10.hana.ondemand.com](https://developer-advocates-free-tier-central-hana-cloud-instan3b540fd6.cfapps.us10.hana.ondemand.com)). For that we make use of a concept called ***data binding*** (more specifically [list binding](https://ui5.sap.com/#/topic/91f0d8ab6f4d1014b6dd926db0e91070)). Data binding is very important in UI5 and requires a special syntax. We use curly brackets (`{}`) and specify the `path` to the data in the model we want to bind. This list binding automatically creates child controls (``) according to model data (like cloning a template). Additionally, we specify the `expand` parameter that will be sent with the OData request when getting the data. This is not always necessary, but rather specific to our data model as the `genre` information is nested and not expanded by default.
165 | - The `` aggregation inside the `
` holds several [``](https://sapui5.hana.ondemand.com/#/api/sap.m.Column) controls that each hold a [``](https://sapui5.hana.ondemand.com/#/api/sap.m.Text) control. These are the texts in the header row of our `
`.
166 | - The `` (rows) of our `
` hold a [``](https://sapui5.hana.ondemand.com/#/api/sap.m.ColumnListItem), which serves as a wrapper for the `` of each row. The controls inside the `` aggregation have to match our `` with respect to the order of the content (book, author, genre, price, stock).
167 | - Check the [documentation](https://sapui5.hana.ondemand.com/#/api/sap.m.ColumnListItem%23controlProperties) to see what the `vAlign` and `type` attributes for the `` do.
168 | - Inside the `` we display the actual data and make use of the data binding concept again. This time it's a relative [property binding](https://ui5.sap.com/#/topic/91f0d8ab6f4d1014b6dd926db0e91070) (relative to the parent's list binding), which is indicated by omitting the slash ('slash'). The whole `
` is bound to `Books`, so we can bind the controls inside the `` aggregation to individual book properties, such a the title and author.
169 | - For the controls inside the `` aggregation we chose controls that suit the type of data they display.
170 |
171 | Check the [documentation](https://ui5.sap.com/#/topic/91f0d8ab6f4d1014b6dd926db0e91070) to learn more about data binding and its different types.
172 |
173 | This is what our view now looks like (`
` collapsed in the screen shot):
174 |
175 | 
176 |
177 | ### 6. Inspect the app in the browser
178 |
179 | ➡️ Move over to the browser and refresh the page to see the table:
180 |
181 | 
182 |
183 | Continue to - [Chapter 04 - Creating and Extending the First Controller](/chapters/04-first-controller/)
184 |
--------------------------------------------------------------------------------
/chapters/03-first-model/result.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SAP-samples/ui5-exercises-codejam/c553ebf6cb35c5458b003edd14cb6a2c5d2aa637/chapters/03-first-model/result.png
--------------------------------------------------------------------------------
/chapters/04-first-controller/App.view.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SAP-samples/ui5-exercises-codejam/c553ebf6cb35c5458b003edd14cb6a2c5d2aa637/chapters/04-first-controller/App.view.png
--------------------------------------------------------------------------------
/chapters/04-first-controller/alert.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SAP-samples/ui5-exercises-codejam/c553ebf6cb35c5458b003edd14cb6a2c5d2aa637/chapters/04-first-controller/alert.png
--------------------------------------------------------------------------------
/chapters/04-first-controller/readme.md:
--------------------------------------------------------------------------------
1 | # Chapter 04 - Creating and Extending the First Controller
2 |
3 | At the end of this chapter we will have made our UI5 app interactive so that a user can select a book to read more information about it.
4 |
5 | ## Steps
6 |
7 | [1. Create a new `webapp/controller/App.controller.js` file](#1-create-a-new-webappcontrollerappcontrollerjs-file)
8 | [2. Reference the controller in the `webapp/view/App.view.xml`](#2-reference-the-controller-in-the-webappviewappviewxml)
9 | [3. Add a new `onSelect` method to our controller](#3-add-a-new-onselect-method-to-the-controller)
10 | [4. Bind the new `.onSelect` method to the ``](#4-bind-the-new-onselect-method-to-the-columnlistitem)
11 | [5. Add a new `` to the `webapp/view/App.view.xml`](#5-add-a-new-flexbox--to-the-webappviewappviewxml)
12 | [6. Inspect the app in the browser](#6-inspect-the-app-in-the-browser)
13 |
14 | ### 1. Create a new `webapp/controller/App.controller.js` file
15 |
16 | ➡️ Create a new JavaScript file `webapp/controller/App.controller.js` and paste the following code into it:
17 |
18 | ```javascript
19 | sap.ui.define([
20 | "sap/ui/core/mvc/Controller"
21 | ], function (Controller) {
22 | "use strict"
23 | return Controller.extend("sap.codejam.controller.App", {
24 | onInit: function () {
25 | alert("I am about to initialize the view.")
26 | }
27 | })
28 | })
29 | ```
30 |
31 | We created our first controller file. We imported the core `Controller` from the library, passed it to a function and extended it. To demonstrate how the controller works, we added an alert to the `onInit` [lifecycle hook](https://sapui5.hana.ondemand.com/sdk/#/topic/121b8e6337d147af9819129e428f1f75.html), which automatically gets called upon initialization of the view.
32 |
33 | This is what our project's structure now looks like:
34 |
35 | ```text
36 | - bookshop/
37 | + node_modules/
38 | - webapp/
39 | - controller/
40 | - App.controller.js
41 | - view/
42 | - App.view.xml
43 | - Component.js
44 | - index.html
45 | - manifest.json
46 | - package-lock.json
47 | - package.json
48 | - ui5.yaml
49 | ```
50 |
51 | ### 2. Reference the controller in the `webapp/view/App.view.xml`
52 |
53 | To make the browser execute the JavaScript code in our controller file we have to reference it in our `webapp/view/App.view.xml`.
54 |
55 | ➡️ Replace the opening tag of the `` control at the top of our view with the following code snippet:
56 |
57 | ```xml
58 |
62 | ```
63 |
64 | We added the controller to the view. We can now refresh our app running in the browser and see the alert being displayed just before the view is visible:
65 |
66 | 
67 |
68 | ### 3. Add a new `onSelect` method to the controller
69 |
70 | Of course we can not only extend existing methods in a controller, but we can write our own ones, too.
71 |
72 | ➡️ Replace the existing content in the `webapp/controller/App.controller.js` file with the following code:
73 |
74 | ```javascript
75 | sap.ui.define([
76 | "sap/ui/core/mvc/Controller"
77 | ], function (Controller) {
78 | "use strict"
79 | return Controller.extend("sap.codejam.controller.App", {
80 | onSelect: function (oEvent) {
81 | const oSource = oEvent.getSource()
82 | const contextPath = oSource.getBindingContextPath()
83 | const form = this.getView().byId("bookDetails")
84 | form.bindElement(contextPath)
85 | }
86 | })
87 | })
88 | ```
89 |
90 | We defined a new `onSelect` method that is being passed an event, which will be the press event when a user clicks on a book. First, the method gets the source of the event. It then gets the binding context path of that source (the selected book, for example `/Books(201)` when clicking on the Book with the ID 201) and binds this element to the "bookDetails" section of the view, which we have not defined yet. This so-called "context binding" (see [binding types](https://ui5.sap.com/#/topic/91f0d8ab6f4d1014b6dd926db0e91070)) will allow us to use relative binding within the "bookDetails" section and all of its children (see [step 5](#5-add-a-new-flexbox--to-the-webappviewappviewxml)).
91 |
92 | ### 4. Bind the new `.onSelect` method to the ``
93 |
94 | We can now use our new method in our `webapp/view/App.view.xml` by binding it to the `press` event of the ``.
95 |
96 | ➡️ Replace the opening tag of the `` with the following code snippet:
97 |
98 | ```xml
99 |
103 | ```
104 |
105 | We bound the `.onSelect` method to the press event of the `` which means it will be called when a user clicks on a book in our table. Notice how we prefixed a dot (`.`) to the method name, which is a naming convention for custom methods that live in a controller. The prefixed dot will be omitted when interpreted by the framework.
106 |
107 | ### 5. Add a new `` to the `webapp/view/App.view.xml`
108 |
109 | Now that we have a model that holds the data of the selected book, we can create a new area at the bottom of our page to display the description of the selected book.
110 |
111 | ➡️ Add the following code to the `webapp/view/App.view.xml` right after the `
`:
112 |
113 | ```xml
114 |
115 |
116 |
117 |
120 |
121 |
122 | ```
123 |
124 | This is what our view now looks like (`
` collapsed in the screen shot):
125 |
126 | 
127 |
128 | We added two `` controls to display the "bookDetails". A `` is convenient for aligning content vertically ("Column", same as ``) or horizontally ("Row", same as ``). Inside the inner `` we added controls to display the data (title and description text). We made use of the data binding concept again - in this case we used property binding ([binding types](https://ui5.sap.com/#/topic/91f0d8ab6f4d1014b6dd926db0e91070)). We also assigned a predefined CSS class (`sapUiSmallMarginTop`, see [other available classes](https://sapui5.hana.ondemand.com/sdk/#/topic/777168ffe8324873973151dae2356d1c.html))that adds a small margin to the top of the `` control.
129 |
130 | > In the previous three chapters you learned about Models, Views, and Controllers. This approach is also called the Model-view-controller concept (MVC) and is especially important in UI5 as well as web development in general. As a UI5 developer you should familiarize yourself with the concept as much as possible. You can learn more about it in the [SAPUI5 Documentation](https://sapui5.hana.ondemand.com/#/topic/91f233476f4d1014b6dd926db0e91070).
131 |
132 | ### 6. Inspect the app in the browser
133 |
134 | ➡️ Move over to the browser and refresh the page. Select any book to see its description:
135 |
136 | 
137 |
138 | Continue to - [Chapter 05 - Adding an 'Order' Feature](/chapters/05-order-feature/)
139 |
--------------------------------------------------------------------------------
/chapters/04-first-controller/result.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SAP-samples/ui5-exercises-codejam/c553ebf6cb35c5458b003edd14cb6a2c5d2aa637/chapters/04-first-controller/result.png
--------------------------------------------------------------------------------
/chapters/05-order-feature/App.controller.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SAP-samples/ui5-exercises-codejam/c553ebf6cb35c5458b003edd14cb6a2c5d2aa637/chapters/05-order-feature/App.controller.png
--------------------------------------------------------------------------------
/chapters/05-order-feature/App.view.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SAP-samples/ui5-exercises-codejam/c553ebf6cb35c5458b003edd14cb6a2c5d2aa637/chapters/05-order-feature/App.view.png
--------------------------------------------------------------------------------
/chapters/05-order-feature/readme.md:
--------------------------------------------------------------------------------
1 | # Chapter 05 - Adding an 'Order' Feature
2 |
3 | At the end of this chapter we will have added a new feature to our bookshop that enables users to order books.
4 |
5 | ## Steps
6 |
7 | [1. Add input field and button to the `webapp/view/App.view.xml`](#1-add-input-field-and-button-to-the-webappviewappviewxml)
8 | [2. Add a new `onSubmitOrder` method to the `webapp/controller/App.controller.js`](#2-add-a-new-onsubmitorder-method-to-the-webappcontrollerappcontrollerjs)
9 | [3. Add new imports to `webapp/controller/App.controller.js`](#3-add-new-imports-to-webappcontrollerappcontrollerjs)
10 | [4. Test the new feature](#4-test-the-new-feature)
11 |
12 | ### 1. Add input field and button to the `webapp/view/App.view.xml`
13 |
14 | We need an input field for the order quantity as well as an order button for our new feature.
15 |
16 | ➡️ Paste the following code snippet as the first child in the existing outer `` that we created in our `webapp/view/App.view.xml` in the previous chapter:
17 |
18 | ```xml
19 |
23 |
27 |
32 |
33 | ```
34 |
35 | This is what our view now looks like (`
` collapsed in the screen shot):
36 |
37 | 
38 |
39 | We added a new `` with the attributes `alignItems="Center"` and `justifyContent="End"` which makes sure all of its children will be centered vertically and displayed at the end of the box horizontally (which is the right side of the browser window in this case). Inside that `` we define a `` that calls an `.onSubmitOrder` method when pressed, which we will define in the next step. We also added a `` so the user can set the order quantity.
40 |
41 | ### 2. Add a new `onSubmitOrder` method to the `webapp/controller/App.controller.js`
42 |
43 | We can now implement the `onSubmitOrder` method that will send an order request to the remote backend system.
44 |
45 | ➡️ Add the following code snippet to the `webapp/controller/App.controller.js` right after the `onSelect` method:
46 |
47 | ```javascript
48 | ,
49 | onSubmitOrder: function (oEvent) {
50 | const oBindingContext = this.getView().byId("bookDetails").getBindingContext()
51 | const selectedBookID = oBindingContext.getProperty("ID")
52 | const selectedBookTitle = oBindingContext.getProperty("title")
53 | const inputValue = this.getView().byId("stepInput").getValue()
54 |
55 | const oModel = this.getView().getModel()
56 | oModel.callFunction("/submitOrder", {
57 | method: "POST",
58 | urlParameters: {
59 | "book": selectedBookID,
60 | "quantity": inputValue
61 | },
62 | success: function(oData, oResponse) {
63 | oModel.refresh()
64 | const oText = `Order successful (${selectedBookTitle}, ${inputValue} pcs.)`
65 | MessageToast.show(oText)
66 | },
67 | error: function(oError) {
68 | if (oError.responseText) {
69 | oError = JSON.parse(oError.responseText).error
70 | }
71 | this.oErrorMessageDialog = new Dialog({
72 | type: "Standard",
73 | title: "Error",
74 | state: "Error",
75 | content: new Text({ text: oError.message })
76 | .addStyleClass("sapUiTinyMargin"),
77 | beginButton: new Button({
78 | text: "Close",
79 | press: function () {
80 | this.oErrorMessageDialog.close()
81 | }.bind(this)
82 | })
83 | })
84 | this.oErrorMessageDialog.open()
85 | }.bind(this)
86 | })
87 | }
88 | ```
89 |
90 | We added a new `onSubmitOrder` method to our controller, which we already bound to the order button's press event in [step 1](/chapters/05-order-feature/readme.md#2-add-a-new-onsubmitorder-method-to-our-webappcontrollerappcontrollerjs) of this chapter. First, the method gets the binding context from the `bookDetails` wrapper control. It then gets the book's ID and title from that context as well as the order quantity from the step input. It then get the view's default model (> no parameter when calling `.getModel()`) and calls the `/submitOrder` function on this model. This function was implemented a part of the OData backend service. So, at this point the application sends a `POST` request to the backend service, which handles the processing of the order and calculates the new stock. The promise of the call gets either resolved or rejected, so only one of the following two callback functions gets called:
91 | 1. **success** : The request is successful and we can refresh the data in our data model. This is to make sure the stock gets updated in our table accordingly. We can then display a message toast informing the user about the successful order and its details.
92 | 1. **error** : The request was unsuccessful and we get passed and error object. We instantiate a new dialog control (a pop-up window basically) and display the error message inside that dialog. We also add a button to the dialog so it can be closed by the user.
93 |
94 | ### 3. Add new imports to `webapp/controller/App.controller.js`
95 |
96 | The new `onSubmitOrder` method uses several UI5 controls we have not imported yet. Make sure to import them from the library and pass the to the main function of the `webapp/controller/App.controller.js`.
97 |
98 | ➡️ Replace the array defining the library imports as well the main function and its arguments at the top of the file with the following code snippet. Keep the content of the main function (the return statement with all controller methods):
99 |
100 | ```javascript
101 | sap.ui.define([
102 | "sap/ui/core/mvc/Controller",
103 | "sap/m/MessageToast",
104 | "sap/m/Dialog",
105 | "sap/m/Button",
106 | "sap/m/Text"
107 | ], function (Controller, MessageToast, Dialog, Button, Text) {
108 | // content of the function stays here
109 | })
110 | ```
111 |
112 | This is what our controller now looks like (a few methods collapsed in the screen shot):
113 |
114 | 
115 |
116 | ### 4. Test the new feature
117 |
118 | We can now test our new feature and order one or more books.
119 |
120 | ➡️ Refresh the app. Play around with the `` control and order one ore more books. Refresh the page to check if the data is persistent. Try to order more books than there are available to provoke an error. You might notice changes in the data that you didn't make yourself, because everyone in this SAP CodeJam is consuming the same OData service.
121 |
122 | 
123 |
124 | Continue to [Chapter 06 - Adding a 'Search' Feature](/chapters/06-search-feature/)
125 |
--------------------------------------------------------------------------------
/chapters/05-order-feature/result.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SAP-samples/ui5-exercises-codejam/c553ebf6cb35c5458b003edd14cb6a2c5d2aa637/chapters/05-order-feature/result.png
--------------------------------------------------------------------------------
/chapters/06-search-feature/App.controller.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SAP-samples/ui5-exercises-codejam/c553ebf6cb35c5458b003edd14cb6a2c5d2aa637/chapters/06-search-feature/App.controller.png
--------------------------------------------------------------------------------
/chapters/06-search-feature/App.view.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SAP-samples/ui5-exercises-codejam/c553ebf6cb35c5458b003edd14cb6a2c5d2aa637/chapters/06-search-feature/App.view.png
--------------------------------------------------------------------------------
/chapters/06-search-feature/readme.md:
--------------------------------------------------------------------------------
1 | # Chapter 06 - Adding a 'Search' Feature
2 |
3 | At the end of this chapter we will have added a new feature to our bookshop that enables users to search for books.
4 |
5 | ## Steps
6 |
7 | [1. Add a new `` to the `webapp/view/App.view.xml`](#1-add-a-new-searchfield--to-the-webappviewappviewxml)
8 | [2. Add a new `onSearch` method to the `webapp/controller/App.controller.js`](#2-add-a-new-onsearch-method-to-the-webappcontrollerappcontrollerjs)
9 | [3. Import `Filter` and `FilterOperator` in the `webapp/controller/App.controller.js`](#3-import-filter-and-filteroperator-in-the-webappcontrollerappcontrollerjs)
10 | [4. Test the new feature](#4-test-the-new-feature)
11 |
12 | ### 1. Add a new `` to the `webapp/view/App.view.xml`
13 |
14 | ➡️ Add the following code to the `webapp/view/App.view.xml` just above the `
` and add the new `id` to the `
`:
15 |
16 | ```xml
17 |
18 |
19 | ```
20 |
21 | This is what our view now looks like (a few controls collapsed in the screen shot):
22 |
23 | 
24 |
25 | We added a new `` control to our view. It comes with a `liveChange` event that gets fired on every keystroke the user submits in the field. We bound an `.onSearch` method to that event which we will define in the next step. The great thing about the `liveChange` event is that the user doesn't have to actively click the search icon or hit enter to start the search.
26 |
27 | ### 2. Add a new `onSearch` method to the `webapp/controller/App.controller.js`
28 |
29 | ➡️ Add the following method to the `webapp/controller/App.controller.js`:
30 |
31 | ```javascript
32 | ,
33 | onSearch: function (oEvent) {
34 | const sQuery = oEvent.getParameter("newValue")
35 | const aFilter = []
36 | if (sQuery) {
37 | aFilter.push(new Filter("title", FilterOperator.Contains, sQuery))
38 | }
39 | const oList = this.byId("booksTable")
40 | const oBinding = oList.getBinding("items")
41 | oBinding.filter(aFilter)
42 | }
43 | ```
44 |
45 | We added a new `onSearch` method, which is being passed an event. The method gets the query string from that event, sets an empty filter array, and if a query string exists, adds a new filter to the array that filters for the book title. It then gets the `booksTable` and its binding, to then execute the filter method. This will update the UI accordingly - only showing the books that match the filter.
46 |
47 |
48 | Are you curious how we could filter for multiple columns such as `title` and/or `author` or how case sensitivity can be handled? 💬
49 |
50 |
51 |
52 | ```javascript
53 | onSearch: function (oEvent) {
54 | const sQuery = oEvent.getParameter("newValue")
55 | const aFilter = []
56 | if (sQuery) {
57 | aFilter.push(new Filter({
58 | filters: [
59 | new Filter({path: 'title',
60 | caseSensitive: false,
61 | operator: FilterOperator.Contains,
62 | value1: sQuery}),
63 | new Filter({path: 'author',
64 | caseSensitive: false,
65 | operator: FilterOperator.Contains,
66 | value1: sQuery}),
67 | ],
68 | and: false
69 | }))
70 | }
71 | const oList = this.byId("booksTable")
72 | const oBinding = oList.getBinding("items")
73 | oBinding.filter(aFilter)
74 | }
75 | ```
76 |
77 |
78 |
79 | ### 3. Import `Filter` and `FilterOperator` in the `webapp/controller/App.controller.js`
80 |
81 | The new `.onSearch` method uses the `Filter` and `FilterOperator` from the library. Make sure to import them from the library and pass the to the main function of the `webapp/controller/App.controller.js`.
82 |
83 | ➡️ Replace the array defining the library imports as well the main function and its arguments at the top of the file with the following code snippet. Keep the content of the main function (the return statement with all controller methods):
84 |
85 | ```javascript
86 | sap.ui.define([
87 | "sap/ui/core/mvc/Controller",
88 | "sap/m/MessageToast",
89 | "sap/m/Dialog",
90 | "sap/m/Button",
91 | "sap/m/Text",
92 | "sap/ui/model/Filter",
93 | "sap/ui/model/FilterOperator",
94 | ], function (Controller, MessageToast, Dialog, Button, Text, Filter, FilterOperator) {
95 | // content of the function stays here
96 | })
97 | ```
98 |
99 | This is what our controller now looks like (a few methods collapsed in the screen shot):
100 |
101 | 
102 |
103 | ### 4. Test the new feature
104 |
105 | ➡️ Refresh the app. Test the new feature and search for a book in the table.
106 |
107 | You'll notice how the search is instantly triggered after a keystroke is submitted in the ``:
108 |
109 | 
110 |
111 | Continue to [Chapter 07 - Adding Expression Binding and Custom Formatting](/chapters/07-formatting/)
112 |
--------------------------------------------------------------------------------
/chapters/06-search-feature/result.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SAP-samples/ui5-exercises-codejam/c553ebf6cb35c5458b003edd14cb6a2c5d2aa637/chapters/06-search-feature/result.png
--------------------------------------------------------------------------------
/chapters/07-formatting/App.controller.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SAP-samples/ui5-exercises-codejam/c553ebf6cb35c5458b003edd14cb6a2c5d2aa637/chapters/07-formatting/App.controller.png
--------------------------------------------------------------------------------
/chapters/07-formatting/App.view.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SAP-samples/ui5-exercises-codejam/c553ebf6cb35c5458b003edd14cb6a2c5d2aa637/chapters/07-formatting/App.view.png
--------------------------------------------------------------------------------
/chapters/07-formatting/readme.md:
--------------------------------------------------------------------------------
1 | # Chapter 07 - Adding Expression Binding and Custom Formatting
2 |
3 | At the end of this chapter we will have added expression binding to the stock column in our table as well as custom formatting to the order ``.
4 |
5 | ## Steps
6 |
7 | [1. Replace the `` control for the `stock` with an ``](#1-replace-the-text--control-for-the-stock-with-an-objectstatus)
8 | [2. Use a custom formatting function](#2-use-a-custom-formatting-function)
9 | [3. Create a `webapp/model/formatter.js` file](#3-create-a-webappmodelformatterjs-file)
10 | [4. Import and use the `../model/formatter` in the `webapp/controller/App.controller.js`](#4-import-and-use-the-modelformatter-in-the-webappcontrollerappcontrollerjs)
11 | [5. Disable the order `` by default](#5-disable-the-order-button--by-default)
12 | [6. Test the new formatting](#6-test-the-new-formatting)
13 |
14 | ### 1. Replace the `` control for the `stock` with an ``
15 |
16 | ➡️ Replace the `` control for the `stock` in the `` in our `webapp/view/App.view.xml` with the following control:
17 |
18 | ```xml
19 |
27 | ```
28 |
29 | This is what the view now looks like (a few controls collapsed and/or cut off in the screen shot):
30 |
31 | 
32 |
33 | We replaced the `` control with an `` which allows us to set a `state` attribute. For the state we make use of a concept called [expression binding](https://sapui5.hana.ondemand.com/#/topic/c98d57347ba444c6945f596584d2db45). Expressions bindings can be used to set a control's attributes based on simple conditions or calculations. Our expression might look complicated, because it is written as an inline if-statement, but translating it into pseudo code makes it a lot more readable:
34 |
35 | ```text
36 | is the stock higher or equal to 20?
37 | if yes: set state to 'Success'
38 | if no: is the stock is higher than 0?
39 | if yes: set state to 'Warning'
40 | if no: set state to 'Error'
41 | ```
42 |
43 | ### 2. Use a custom formatting function
44 |
45 | We want to make sure that users cannot order more books than there are available. Interestingly, this feature is a little too complex to be represented as an expression binding, as we will have to compare two values from different sources. We will have to compare the `{stock}` (bound via element binding) to the current value of the `` (gettable via `getValue()`). To achieve that, we will implement custom formatting function.
46 |
47 | ➡️ Replace the existing `` and `` in our `webapp/view/App.view.xml` with the following controls:
48 |
49 | ```xml
50 |
58 |
64 | ```
65 |
66 | We added data binding syntax to the `enabled` attribute of the `` and specified a `formatter`. A formatter is a JavaScript function that is being passed the value of the `path` binding and gets called every time this data changes. The function should return the value that should be set for the `enabled` attribute - a boolean (true/false) in this case.
67 |
68 |
69 |
70 | We also set a max value for the `` using a relative [property binding](https://ui5.sap.com/#/topic/91f0d8ab6f4d1014b6dd926db0e91070). However, this only prevents users from using the plus and minus icons of the `` to set invalid value and doesn't restrict the free input.
71 |
72 | ### 3. Create a `webapp/model/formatter.js` file
73 |
74 | Let's now implement the custom formatting function.
75 |
76 | ➡️ Create a new `webapp/model/formatter.js` file with the following content:
77 |
78 | ```javascript
79 | sap.ui.define([], function () {
80 | "use strict"
81 | return {
82 | inputLowerThanStock: function (availableStock) {
83 | const inputValue = this.getView().byId("stepInput").getValue()
84 | return inputValue <= availableStock
85 | }
86 | }
87 | })
88 | ```
89 |
90 | We implemented a custom formatting function to custom format the order ``. The function does not extend any base object but just returns a JavaScript object with the `formatter` method(s) inside the `sap.ui.define` call. The `inputLowerThanStock` method is being passed the `availableStock` from the control's `{stock}` property binding and gets the current value of the `` via the standard internal UI5 APIs. It does a simple comparison and returns a boolean.
91 |
92 | This is what our project's structure now looks like:
93 |
94 | ```text
95 | - bookshop/
96 | + node_modules/
97 | - webapp/
98 | - controller/
99 | - App.controller.js
100 | - model/
101 | - formatter.js
102 | - view/
103 | - App.view.xml
104 | - Component.js
105 | - index.html
106 | - manifest.json
107 | - package-lock.json
108 | - package.json
109 | - ui5.yaml
110 | ```
111 |
112 | ### 4. Import and use the `../model/formatter` in the `webapp/controller/App.controller.js`
113 |
114 | Although we have implemented and used our custom formatting function already, our view doesn't actually know where the function lives yet. Let's make our view aware of the function by importing it in the corresponding controller.
115 |
116 | ➡️ Replace the array defining the library imports as well the main function and its arguments at the top of the file with the following code snippet. Also add the formatter method. Keep the content of the main function (the return statement with all controller methods):
117 |
118 | ```javascript
119 | sap.ui.define([
120 | "sap/ui/core/mvc/Controller",
121 | "sap/m/MessageToast",
122 | "sap/m/Dialog",
123 | "sap/m/Button",
124 | "sap/m/Text",
125 | "sap/ui/model/Filter",
126 | "sap/ui/model/FilterOperator",
127 | "../model/formatter"
128 | ], function (Controller, MessageToast, Dialog, Button, Text, Filter, FilterOperator, formatter) {
129 | "use strict"
130 | return Controller.extend("sap.codejam.controller.App", {
131 | formatter: formatter,
132 | // content of the function stays here
133 | })
134 | })
135 | ```
136 |
137 | We added the formatter to our controller to be able to call it in the corresponding view.
138 |
139 | ### 5. Disable the order `` by default
140 |
141 | The current default behavior of our app is that no book is selected when users first visit the bookshop. However, the order button is already enabled at that time. This problem can be solved using a [lifecycle hook](https://sapui5.hana.ondemand.com/sdk/#/topic/121b8e6337d147af9819129e428f1f75.html).
142 |
143 | ➡️ Add the following method to the `webapp/controller/App.controller.js` file:
144 |
145 | ```javascript
146 | ,
147 | onAfterRendering: function () {
148 | this.getView().byId("orderBtn").setEnabled(false)
149 | }
150 | ```
151 |
152 | This is what our controller now looks like:
153 |
154 | 
155 |
156 | We added the `onAfterRendering` method to our controller. This method is automatically called once the rendering of the view is completed and its HTML is part of the document. Inside the method, we disable the order button. It will later be enabled once the user selects a book and the `{stock}` (property binding) changes and the custom formatting function gets called.
157 |
158 | There are more lifecycle hooks available in UI5 that are suited for different tasks. Check the [documentation](https://sapui5.hana.ondemand.com/sdk/#/topic/121b8e6337d147af9819129e428f1f75.html) for more information.
159 |
160 | ### 6. Test the new formatting
161 |
162 | ➡️ Refresh the app. Inspect and test the new formatting.
163 |
164 | You will notice that the `stock` is color coded based on the thresholds we defined in [step 1](#1-replace-the-text--control-for-the-stock-with-an-objectstatus). You will also notice that the order button is disabled by default and gets disabled when selecting a book with a lower stock than the current `` value.
165 |
166 | 
167 |
168 | Continue to [Chapter 08 - Adding i18n Features](/chapters/08-i18n/)
169 |
--------------------------------------------------------------------------------
/chapters/07-formatting/result.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SAP-samples/ui5-exercises-codejam/c553ebf6cb35c5458b003edd14cb6a2c5d2aa637/chapters/07-formatting/result.png
--------------------------------------------------------------------------------
/chapters/08-i18n/manifest.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SAP-samples/ui5-exercises-codejam/c553ebf6cb35c5458b003edd14cb6a2c5d2aa637/chapters/08-i18n/manifest.png
--------------------------------------------------------------------------------
/chapters/08-i18n/readme.md:
--------------------------------------------------------------------------------
1 | # Chapter 08 - Adding i18n Features
2 |
3 | At the end of this chapter we will have added internationalization (i18n) features to our UI5 app, so that it can be displayed in German (and other languages you want to add) as well.
4 |
5 | ## Steps
6 |
7 | [1. Add an `i18n` model to the `webapp/manifest.json`](#1-add-an-i18n-model-to-the-webappmanifestjson)
8 | [2. Create a `webapp/i18n/i18n.properties` file](#2-create-a-webappi18ni18nproperties-file)
9 | [3. Create a `webapp/i18n/i18n_de.properties` file](#3-create-a-webappi18ni18n_deproperties-file)
10 | [4. Consume the `i18n` model in the `webapp/view/App.view.xml`](#4-consume-the-i18n-model-in-the-webappviewappviewxml)
11 | [5. Consume the `i18n` model in the `webapp/controller/App.controller.js`](#5-consume-the-i18n-model-in-the-webappcontrollerappcontrollerjs)
12 | [6. Test the app in another language](#6-test-the-app-in-another-language)
13 |
14 | > BTW: The abbreviation for 'internationalization' is 'i18n' because there are 18 letters between the 'i' and the 'n'. Internationalization is one of the main requirements for enterprise apps and one of the strengths of UI5.
15 |
16 | ### 1. Add an `i18n` model to the `webapp/manifest.json`
17 |
18 | ➡️ Add the following code snippet to the end of the `models` section of the `manifest.json`:
19 |
20 | ```json
21 | ,
22 | "i18n": {
23 | "type": "sap.ui.model.resource.ResourceModel",
24 | "settings": {
25 | "bundleName": "sap.codejam.i18n.i18n"
26 | }
27 | }
28 | ```
29 |
30 | This is what our application descriptor now looks like:
31 |
32 | 
33 |
34 | We added a new `i18n` model which is of type `ResourceModel` (special type for `i18n` models). It points to an `i18n` directory and file, which we are about to create next.
35 |
36 | ### 2. Create a `webapp/i18n/i18n.properties` file
37 |
38 | ➡️ Create a new file `webapp/i18n/i18n.properties` and paste the following code into it:
39 |
40 | ```properties
41 | Bookshop=Bookshop
42 | Book=Book
43 | Author=Author
44 | Genre=Genre
45 | Price=Price
46 | Stock=Stock
47 | Order=Order
48 | OrderNoun=Order
49 | orderSuccessful=Order successful
50 | pieces=pcs.
51 | Error=Error
52 | Close=Close
53 | ```
54 |
55 | We created a new file that contains a few key value pairs for texts we want to use in our app. This allows us to simply reference the keys when consuming the model in our view instead of hardcoding the values. Because the keys as well as the values are in English it might look like this doesn't add much value, but you will see what this is about once we add some translations in the next step.
56 |
57 | ### 3. Create a `webapp/i18n/i18n_de.properties` file
58 |
59 | Create a new file `webapp/i18n/i18n_de.properties` and paste the following code into it:
60 |
61 | ```properties
62 | Bookshop=Buchhandlung
63 | Book=Buch
64 | Author=Autor
65 | Genre=Genre
66 | Price=Preis
67 | Stock=Verfügbarkeit
68 | Order=Bestellen
69 | OrderNoun=Bestellung
70 | orderSuccessful=Bestellung erfolgreich
71 | pieces=Stk.
72 | Error=Fehler
73 | Close=Schließen
74 | ```
75 |
76 | This is what our project's structure now looks like:
77 |
78 | ```text
79 | - bookshop/
80 | + node_modules/
81 | - webapp/
82 | - controller/
83 | - App.controller.js
84 | - i18n/
85 | - i18n_de.properties
86 | - i18n.properties
87 | - model/
88 | - formatter.js
89 | - view/
90 | - App.view.xml
91 | - Component.js
92 | - index.html
93 | - manifest.json
94 | - package-lock.json
95 | - package.json
96 | - ui5.yaml
97 | ```
98 |
99 | We added a new file that contains the German translations for the texts we want to use in our app. This makes it possible for our app to be displayed in German as well. You can add support for other languages as well if you want. Simply add a language code (for example `de`) prefixed with an underscore to the file name (like we did with `i18n_de.properties`).
100 |
101 | ### 4. Consume the `i18n` model in the `webapp/view/App.view.xml`
102 |
103 | We can now consume the `i18n` model in our `webapp/view/App.view.xml` using the keys. The app will display their respective values based on the language preference of the user.
104 |
105 | ➡️ Replace all hardcoded texts in our view with the data binding syntax for the `i18n` model shown in the example below. Go through the whole file and make sure to not miss any texts:
106 |
107 | ```xml
108 |
109 | ```
110 |
111 | ### 5. Consume the `i18n` model in the `webapp/controller/App.controller.js`
112 |
113 | We not only want to consume the `i18n` model in our view but also in our `webapp/controller/App.controller.js`. We previously hardcoded the success and error messages and now want to use the `i18n` model instead.
114 |
115 | ➡️ Replace the `onSubmitOrder` method with the following code:
116 |
117 | ```javascript
118 | ,
119 | onSubmitOrder: function (oEvent) {
120 | const oBindingContext = this.getView().byId("bookDetails").getBindingContext()
121 | const selectedBookID = oBindingContext.getProperty("ID")
122 | const selectedBookTitle = oBindingContext.getProperty("title")
123 | const inputValue = this.getView().byId("stepInput").getValue()
124 |
125 | const i18nModel = this.getView().getModel("i18n")
126 | const oModel = this.getView().getModel()
127 | oModel.callFunction("/submitOrder", {
128 | method: "POST",
129 | urlParameters: {
130 | "book": selectedBookID,
131 | "quantity": inputValue
132 | },
133 | success: function(oData, oResponse) {
134 | oModel.refresh()
135 | const oText = `${i18nModel.getProperty("orderSuccessful")} (${selectedBookTitle}, ${inputValue} ${i18nModel.getProperty("pieces")})`
136 | MessageToast.show(oText)
137 | },
138 | error: function(oError) {
139 | if (oError.responseText) {
140 | oError = JSON.parse(oError.responseText).error
141 | }
142 | this.oErrorMessageDialog = new Dialog({
143 | type: "Standard",
144 | title: i18nModel.getProperty("Error"),
145 | state: "Error",
146 | content: new Text({ text: oError.message })
147 | .addStyleClass("sapUiTinyMargin"),
148 | beginButton: new Button({
149 | text: i18nModel.getProperty("Close"),
150 | press: function () {
151 | this.oErrorMessageDialog.close()
152 | }.bind(this)
153 | })
154 | })
155 | this.oErrorMessageDialog.open()
156 | }.bind(this)
157 | })
158 | }
159 | ```
160 |
161 | We added the `i18n` model to our controller and used it for the success and error messages.
162 |
163 | ### 6. Test the app in another language
164 |
165 | We can now test the app in another language.
166 |
167 | ➡️ Reload the app and attach a query parameter to the URL of the app to specify which language you want the bookshop to be displayed in. Attach `?sap-ui-language=de` to the URL to view the app in German for example.
168 |
169 | Feel free to try other language codes in case you provided translation files for other languages. You can learn more about languages in UI5 and other options to set them in the [SAPUI5 Documentation](https://sapui5.hana.ondemand.com/#/topic/91f21f176f4d1014b6dd926db0e91070).
170 |
171 | 
172 |
173 | Continue to [Chapter 09 - Adding Custom CSS](/chapters/09-custom-css/)
174 |
--------------------------------------------------------------------------------
/chapters/08-i18n/result.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SAP-samples/ui5-exercises-codejam/c553ebf6cb35c5458b003edd14cb6a2c5d2aa637/chapters/08-i18n/result.png
--------------------------------------------------------------------------------
/chapters/09-custom-css/App.view.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SAP-samples/ui5-exercises-codejam/c553ebf6cb35c5458b003edd14cb6a2c5d2aa637/chapters/09-custom-css/App.view.png
--------------------------------------------------------------------------------
/chapters/09-custom-css/manifest.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SAP-samples/ui5-exercises-codejam/c553ebf6cb35c5458b003edd14cb6a2c5d2aa637/chapters/09-custom-css/manifest.png
--------------------------------------------------------------------------------
/chapters/09-custom-css/readme.md:
--------------------------------------------------------------------------------
1 | # Chapter 09 - Adding Custom CSS
2 |
3 | At the end of this chapter we will have added custom CSS to our UI5 app that applies styling to the `` containing the order `` and ``.
4 |
5 | ## Steps
6 |
7 | [1. Add new `resource` to our `webapp/manifest.json`](#1-add-new-resource-to-our-webappmanifestjson)
8 | [2. Create a `webapp/css/style.css` file](#2-create-a-webappcssstylecss-file)
9 | [3. Use a custom CSS class in the `webapp/view/App.view.xml`](#3-use-a-custom-css-class-in-the-webappviewappviewxml)
10 | [4. Inspect the new styling](#4-inspect-the-new-styling)
11 |
12 | ### 1. Add new `resource` to our `webapp/manifest.json`
13 |
14 | ➡️ Add the following code snippet to the `sap.ui5` section of the `webapp/manifest.json`:
15 |
16 | ```json
17 | ,
18 | "resources": {
19 | "css": [
20 | {
21 | "uri": "css/style.css"
22 | }
23 | ]
24 | }
25 | ```
26 |
27 | This is what our application descriptor now looks like:
28 |
29 | 
30 |
31 | We added a new `css` resource to our application descriptor and pointed it to a css file that we are about to create next.
32 |
33 | ### 2. Create a `webapp/css/style.css` file
34 |
35 | ➡️ Create a new file `webapp/css/style.css` and paste the following code into it:
36 |
37 | ```css
38 | .orderControls {
39 | gap: 20px;
40 | }
41 | ```
42 |
43 | We created a new css file and added a css class to it. The css class uses the `gap` property which makes sure that all items inside a container with `display = flex` (like the `` control) have the specified gap between each other.
44 |
45 | This is what our project's structure now looks like:
46 |
47 | ```text
48 | - bookshop/
49 | + node_modules/
50 | - webapp/
51 | - controller/
52 | - App.controller.js
53 | - css/
54 | - style.css
55 | - i18n/
56 | - i18n_de.properties
57 | - i18n.properties
58 | - model/
59 | - formatter.js
60 | - view/
61 | - App.view.xml
62 | - Component.js
63 | - index.html
64 | - manifest.json
65 | - package-lock.json
66 | - package.json
67 | - ui5.yaml
68 | ```
69 |
70 | ### 3. Use a custom CSS class in the `webapp/view/App.view.xml`
71 |
72 | We can now use the css class `orderControls` in our `webapp/view/App.view.xml`.
73 |
74 | ➡️ Add the class to the `` containing the order ``, and ``, so it looks like this:
75 |
76 | ```xml
77 |
81 |
82 |
83 |
84 | ```
85 |
86 | This is what our view now looks like (a few controls collapsed in the screen shot):
87 |
88 | 
89 |
90 | ### 4. Inspect the new styling
91 |
92 | ➡️ Refresh the app and inspect the new styling.
93 |
94 | You will see that the items inside the `` now have a gap of 20 pixels between each other.
95 |
96 | 
97 |
98 | Continue to [Chapter 10 - Deploying the App](/chapters/10-deployment/)
99 |
--------------------------------------------------------------------------------
/chapters/09-custom-css/result.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SAP-samples/ui5-exercises-codejam/c553ebf6cb35c5458b003edd14cb6a2c5d2aa637/chapters/09-custom-css/result.png
--------------------------------------------------------------------------------
/chapters/10-deployment/readme.md:
--------------------------------------------------------------------------------
1 | # Chapter 10 - Deploying the App
2 |
3 | Please note: This step is optional and not specific to UI5, but rather covers the basics of how to use the [SAP Approuter](https://www.npmjs.com/package/@sap/approuter), the [Cloud MTA Build Tool](https://sap.github.io/cloud-mta-build-tool/), and the [Cloud Foundry Command Line Interface](https://docs.cloudfoundry.org/cf-cli/).
4 |
5 | ## Prerequisites
6 |
7 | To be able to complete this chapter you need to fulfill ***all*** of the following requirements:
8 | - You need to have an account on the SAP Business Technology platform (SAP BTP) with the Cloud Foundry Environment enabled. Learn more about how to get an account [here](https://developers.sap.com/group.btp-setup.html). You also need at least 2 units of Cloud Foundry Runtime assigned to your subaccount and available to consume.
9 | - You need to have the [Cloud Foundry Command Line Interface](https://docs.cloudfoundry.org/cf-cli/) installed in your development environment. If you work in the SAP Business Application Studio or in a devcontainer using the [provided configuration](/.devcontainer) this is already taken care of. You can verify your installation by running `cf -v`.
10 |
11 | ## Deploying Our App
12 |
13 | At the end of this chapter we will have deployed our UI5 to the Cloud Foundry Environment on the SAP BTP. To keep it as simple as possible, we will not deploy the backend application of this project that we used for local development, but consume a remote service instead.
14 |
15 | ## Steps
16 |
17 | [1. Add the `@sap/approuter` package](#1-add-the-sapapprouter-package)
18 | [2. Define routes for the approuter in a `webapp/xs-app.json` file](#2-define-routes-for-the-approuter-in-a-webappxs-appjson-file)
19 | [3. Create a `manifest.yaml` file](#3-create-a-manifestyaml-file)
20 | [4. Install the dependencies and build the UI5 app](#4-install-the-dependencies-and-build-the-ui5-app)
21 | [5. Log in to the Cloud Foundry Environment](#5-log-in-to-the-cloud-foundry-environment)
22 | [6. Deploy the application](#6-deploy-the-application)
23 | [7. Test the bookshop in the cloud](#7-test-the-bookshop-in-the-cloud)
24 |
25 | ### 1. Add the `@sap/approuter` package
26 |
27 | We want to run our app using the `@sap/approuter` package, which allows us to handle incoming and outgoing requests to and from our app. The approuter will be the entry point to our app. You can learn more about the SAP Approuter [here](https://blogs.sap.com/2020/04/03/sap-application-router/).
28 |
29 | ➡️ Replace the current content of the `package.json` file with the following code:
30 |
31 | ```json
32 | {
33 | "name": "bookshop",
34 | "version": "0.0.1",
35 | "scripts": {
36 | "start": "node node_modules/@sap/approuter/approuter.js",
37 | "dev": "ui5 serve --open \"index.html\"",
38 | "build": "ui5 build"
39 | },
40 | "dependencies": {
41 | "@sap/approuter": "^14"
42 | },
43 | "devDependencies": {
44 | "@ui5/cli": "^3",
45 | "@sap/ux-ui5-tooling": "^1"
46 | },
47 | "ui5": {
48 | "dependencies": [
49 | "@sap/ux-ui5-tooling"
50 | ]
51 | }
52 | }
53 | ```
54 |
55 | Our Node.js based application now uses the `@sap/approuter` package which makes it a so-called 'standalone approuter'.
56 |
57 | ### 2. Define routes for the approuter in a `webapp/xs-app.json` file
58 |
59 | An approuter requires an `xs-app.json` file defining all the routes it should handle.
60 |
61 | ➡️ Create a new file `xs-app.json` and paste the following content into it:
62 |
63 | ```json
64 | {
65 | "welcomeFile": "index.html",
66 | "authenticationMethod": "none",
67 | "routes": [
68 | {
69 | "source": "/v2/browse(.*)",
70 | "destination": "remote-bookshop",
71 | "authenticationType": "none"
72 | },
73 | {
74 | "source": "^(.*)$",
75 | "target": "$1",
76 | "authenticationType": "none",
77 | "localDir": "dist/"
78 | }
79 | ]
80 | }
81 | ```
82 |
83 | We defined all routes for our approuter. Each route has a source (based on a regular expression) and additional properties that define it. The most important route is the second one (`^(.*)$`), which states that all incoming requests will be proxied to a local `dist/` directory, which we will be created shortly. This in combination with `index.html` as our `welcomeFile` makes users get to the UI5 app when accessing the approuter. The `/v2/browse(.*)` route makes sure that requests to the backend (see the `capBooks` data source in our `webapp/manifest.json`) will be proxied to a destination called `browse-bookshop`. Let us define the details of this destination in the next step.
84 |
85 | ### 3. Create a `manifest.yaml` file
86 |
87 | We can now put all the pieces together and describe our application in a manifest.
88 |
89 | ➡️ Create a `manifest.yaml` file and paste the following code into it (make sure the indentation is 100% correct as `.yaml` files are very strict in that regard):
90 |
91 | ```yaml
92 | applications:
93 | - name: bookshop-ui5
94 | path: .
95 | buildpacks:
96 | - https://github.com/cloudfoundry/nodejs-buildpack
97 | memory: 256M
98 | command: npm run start
99 | random-route: true
100 | env:
101 | destinations: "[{\"name\":\"remote-bookshop\", \"url\":\"https://developer-advocates-free-tier-central-hana-cloud-instan3b540fd6.cfapps.us10.hana.ondemand.com\"}]"
102 | ```
103 |
104 | We described our application in a `manifest.yaml` file. This file will be picked up and interpreted by Cloud Foundry to deploy the application. We define some basic information regarding our app and how it should run in Cloud Foundry. Most interestingly, we define the `destinations` environment variable that includes the actual URL of the of the service we want to use the backend. The approuter will look for this destination information every time a request hits the `/v2/browse(.*)` route (see [step 2](#2-define-routes-for-the-approuter-in-an-webappxs-appjson-file) of this chapter).
105 |
106 | This is what our project's structure now looks like:
107 |
108 | This is what our project's structure now looks like:
109 |
110 | ```text
111 | - bookshop/
112 | + node_modules/
113 | + webapp/
114 | - manifest.yaml
115 | - package-lock.json
116 | - package.json
117 | - ui5.yaml
118 | - xs-app.json
119 | ```
120 |
121 | > BTW: There are other options to deploy applications to deploy Cloud Foundry. The most prominent approach is using `multi-target applications` (MTAs). See the [SAP BTP documentation](https://help.sap.com/docs/btp/sap-business-technology-platform/multitarget-applications-in-cloud-foundry-environment) for more information.
122 |
123 | ### 4. Install the dependencies and build the UI5 app
124 |
125 | ➡️ Run the following command to install all dependencies declared in the `package.json` and start the build process:
126 |
127 | ```bash
128 | npm install && npm run build
129 | ```
130 |
131 | We executed the `npm run build` script that is defined as `ui5 build` in the `package.json`. The `ui5 build` command refers to the UI5 Tooling that we also used to run our application locally. The build command creates the `dist/` directory and moves a performance optimized version of our app into it. This is the directory that will be served by the approuter, as per definition in the `xs-app.json` file.
132 |
133 | ### 5. Log in to the Cloud Foundry Environment
134 |
135 | We can now log in to the Cloud Foundry environment on the SAP Business Technology Platform.
136 |
137 | ➡️ Execute the following command in a terminal session and enter your API endpoint (you can find it in the SAP BTP Cockpit in the subaccount overview) and credentials when prompted. Also select the Cloud Foundry organization and space you want to deploy the project:
138 |
139 | ```bash
140 | cf login
141 | ```
142 |
143 | ### 6. Deploy the application
144 |
145 | We can now deploy the application.
146 |
147 | ➡️ Execute the following command in the same terminal session you previously triggered the build from:
148 |
149 | ```bash
150 | cf push
151 | ```
152 |
153 | The deployment may take a while. This is nothing to worry about.
154 |
155 | ### 7. Test the bookshop in the cloud
156 |
157 | If the build was successful and finished with no errors, we can test our bookshop app in the cloud.
158 |
159 | ➡️ Go into the SAP BTP Cockpit and navigate to the space you deployed the project into. Click on `bookshop-ui5` and open the route displayed at the top. You should see your bookshop running in the cloud. Alternatively, you can see the URL of the deployed application in the terminal output of the deployment.
160 |
161 | 
162 | 
163 |
164 | We successfully deployed our UI5 app to the Cloud Foundry Environment. It consumes a remote service as the data source when running in the cloud via the destination environment variable.
165 |
166 | Continue to [Chapter 11 - Further Improvements and Learning Material](/chapters/11-further-improvements/)
167 |
--------------------------------------------------------------------------------
/chapters/10-deployment/result1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SAP-samples/ui5-exercises-codejam/c553ebf6cb35c5458b003edd14cb6a2c5d2aa637/chapters/10-deployment/result1.png
--------------------------------------------------------------------------------
/chapters/10-deployment/result2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SAP-samples/ui5-exercises-codejam/c553ebf6cb35c5458b003edd14cb6a2c5d2aa637/chapters/10-deployment/result2.png
--------------------------------------------------------------------------------
/chapters/11-further-improvements/readme.md:
--------------------------------------------------------------------------------
1 | # Chapter 11 - Further Improvements and Learning Material
2 |
3 | ## Further Improvements
4 |
5 | If you followed the steps described in this repository successfully you have built a fully functional UI5 app that makes use of most core principles of the framework. But of course the material did not cover everything UI5 has to offer. There is a lot more you can do with UI5 and a lot of features you could add to take your bookshop app to the next level. Here are a few ideas, but feel free to improve the app in whatever way you would like to:
6 | - You could split the view (`webapp/view/App.view.xml`) up into multiple views using a concept called [nested views](https://sapui5.hana.ondemand.com/#/topic/df8c9c3d79b54c928855162bafcd88ee), making its structure easier to read and understand.
7 |
8 | - You could also move parts of the UI into reusable [fragments](https://sapui5.hana.ondemand.com/#/topic/4da72985139b4b83b5f1c1e0c0d2ed5a). While this is not necessary for a simple app like ours, this does help to remove redundancies once the project grows.
9 |
10 | - You could add more pages to your app and split its content up. At this stage, the bookshop's complexity doesn't necessarily require [routing and navigation](https://sapui5.hana.ondemand.com/#/topic/e5200ee755f344c8aef8efcbab3308fb), but it's certainly a good feature to know how to implement.
11 |
12 | ## Further Learning Material
13 |
14 | Here are some further learning recommendations in case you are looking for more structured content to follow:
15 |
16 | - The [UI5 Walkthrough](https://sapui5.hana.ondemand.com/#/topic/3da5f4be63264db99f2e5b04c5e853db) takes you on a tour through all the development paradigms of UI5. It's a great resource to start with as it covers everything from the most basic steps to advanced functionalities.
17 |
18 | - The video series [2 Minutes of SAPUI5](https://www.youtube.com/watch?v=J9NMwsipMkw&list=PL6RpkC85SLQC4kuj22e4hw85Sa1pClD8y) is inspired by UI5 Walkthrough, but introduces the fundamentals of UI% in short and easy to digest videos and also covers additional topics such as authorization and deployment.
19 |
20 | - There is a great [openSAP course on SAPUI5](https://open.sap.com/courses/ui52).
21 |
--------------------------------------------------------------------------------
/chapters/appendix-01-fe-fpm/fiori-tools.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SAP-samples/ui5-exercises-codejam/c553ebf6cb35c5458b003edd14cb6a2c5d2aa637/chapters/appendix-01-fe-fpm/fiori-tools.png
--------------------------------------------------------------------------------
/chapters/appendix-01-fe-fpm/readme.md:
--------------------------------------------------------------------------------
1 | # Appendix 01 - Enabling the SAP Fiori Elements Flexible Programming Model
2 |
3 | **If you want to learn more about the SAP Fiori elements flexible programming model, checkout this other [SAP CodeJam repository](https://github.com/SAP-samples/fiori-elements-fpm-exercises-codejam).**
4 |
5 | The following series of chapters (in the appendix) introduces the **SAP Fiori elements flexible programming model**, which bridges the gap between freestyle UI5 development and [SAP Fiori elements](https://ui5.sap.com/#/topic/03265b0408e2432c9571d6b3feb6b1fd).
6 |
7 | The application we built so far in part 1 used a freestyle approach, meaning we built our own custom view with specific controls and controller logic. In contrast to that, SAP Fiori elements provide predefined [floorplans](https://ui5.sap.com/#/topic/797c3239b2a9491fa137e4998fd76aa7.html) (think "application layouts") for common business application use cases. Using this approach, the framework (SAPUI5) generates an application by interpreting metadata that is part of the consumed OData backend services. The specific parts of OData metadata that define the way a backend service is represented in frontend applications are called "annotations" and are mandatory when using SAP Fiori elements.
8 |
9 | Usually you would decide before starting a new development which of the two approaches you want to use to build your application. The [SAP Fiori Tools](https://help.sap.com/docs/SAP_FIORI_tools/17d50220bcd848aa854c9c182d65b699/2d8b1cb11f6541e5ab16f05461c64201.html?locale=en-US) provide guided application generators for both approaches.
10 |
11 | 
12 |
13 | However, the situation is not exactly black and white, as the [SAP Fiori elements flexible programming model](https://sapui5.hana.ondemand.com/test-resources/sap/fe/core/fpmExplorer/index.html#/overview/introduction) provides building blocks (macros), which are metadata-driven UI controls that can be used in any (freestyle) SAPUI5 application. This flexible programming model is perfect for our use case, as we already have a working freestyle UI5 application and solely want to enhance it - while learning about SAP Fiori elements and OData annotations along the way. The instructions given in this chapter align with the [the official documentation](https://sapui5.hana.ondemand.com/test-resources/sap/fe/core/fpmExplorer/index.html#/buildingBlocks/guidance/guidanceCustomApps), but are more detailed and more specific to our use case.
14 |
15 | At the end of this chapter we will have enabled the Fiori elements flexible programming model for our custom UI5 application. Essentially, we will have turned our application into an SAP Fiori elements application.
16 |
17 | ## Steps
18 |
19 | [1. Duplicate the existing application](#1-duplicate-the-existing-application)
20 | [2. Extend the `sap/fe/core/AppComponent` instead of the `sap/ui/core/UIComponent`](#2-extend-the-sapfecoreappcomponent-instead-of-the-sapuicoreuicomponent)
21 | [3. Use OData V4](#3-use-odata-v4)
22 | [4. Add OData V4 model settings](#4-add-odata-v4-model-settings)
23 | [5. Add routing to the `webapp/manifest.json`](#5-add-routing-to-the-webappmanifestjson)
24 | [6. Remove the `rootView` from the `webapp/manifest.json`](#6-remove-the-rootview-from-the-webappmanifestjson)
25 | [7. Add dependencies to the `webapp/manifest.json`](#7-add-dependencies-to-the-webappmanifestjson)
26 | [8. Use SAPUI5 instead of OpenUI5](#8-use-sapui5-instead-of-openui5)
27 | [9. Rebuild the `webapp/view/App.view.xml`](#9-rebuild-the-webappviewappviewxml)
28 | [10. Use the `sap/fe/core/PageController` instead of the `sap/ui/core/mvc/Controller`](#10-use-the-sapfecorepagecontroller-instead-of-the-sapuicoremvccontroller)
29 | [11. Change `ui5.yaml` configuration](#11-change-ui5yaml-configuration)
30 | [12. Test the new app](#12-test-the-new-app)
31 |
32 | ### 1. Duplicate the existing application
33 |
34 | ➡️ Duplicate your existing UI5 application living in `webapp/`. Then rename the copy to `freestyle-webapp/`.
35 |
36 | This is what our project's structure now looks like:
37 |
38 | ```text
39 | - bookshop/
40 | + node_modules/
41 | + freestyle-webapp/
42 | + webapp/
43 | - manifest.yaml
44 | - package-lock.json
45 | - package.json
46 | - ui5.yaml
47 | - xs-app.json
48 | ```
49 |
50 | We duplicated our existing application to preserve the progress we made in the previous chapters (`freestyle-webapp/`). The structural changes we are about to make to our application require us to delete parts of the application (and thus the progress we made).
51 |
52 | ### 2. Extend the `sap/fe/core/AppComponent` instead of the `sap/ui/core/UIComponent`
53 |
54 | ➡️ Replace the content of the existing `webapp/Component.js` with the following code:
55 |
56 | ```javascript
57 | sap.ui.define([
58 | "sap/fe/core/AppComponent",
59 | ], function (AppComponent) {
60 | "use strict"
61 | return AppComponent.extend(
62 | "sap.codejam.Component", {
63 | metadata : {
64 | "interfaces": [
65 | "sap.ui.core.IAsyncContentCreation"
66 | ],
67 | manifest: "json"
68 | },
69 | init : function () {
70 | AppComponent.prototype.init.apply(
71 | this,
72 | arguments
73 | )
74 | }
75 | })
76 | })
77 | ```
78 |
79 | We now use and extend the `sap/fe/core/AppComponent` instead of the `sap/ui/core/UIComponent` to make sure our [component](/chapters/chapter001/readme.md#4-create-an-webappcomponentjs-file) runs within the SAP Fiori elements framework.
80 |
81 | ### 3. Use OData V4
82 |
83 | The SAP Fiori elements Flexible Programming Model is only available for OData V4, meaning it's not compatible with the OData service we are currently consuming, which is version 2 of the protocol. Luckily, our remote backend service provide endpoints for both V2 and V4, so all we have to do is change the data source in our `webapp/manifest.json`.
84 |
85 | ➡️ Replace the `sap.app.dataSources` section of the `webapp/manifest.json` with the following code:
86 |
87 | ```json
88 | ,
89 | "dataSources": {
90 | "remoteBookshop": {
91 | "uri": "/browse/",
92 | "type" : "OData",
93 | "settings" : {
94 | "odataVersion" : "4.0"
95 | }
96 | }
97 | }
98 | ```
99 |
100 | We changed the OData uri (different endpoint of the remote bookshop service) and changed the version to V4.
101 |
102 | ### 4. Add OData V4 model settings
103 |
104 | ➡️ Replace the default model in the `sap.ui5.models` section of the `webapp/manifest.json` with the following code:
105 |
106 | ```json
107 | "": {
108 | "dataSource": "remoteBookshop",
109 | "settings": {
110 | "synchronizationMode": "None",
111 | "operationMode": "Server",
112 | "autoExpandSelect": true,
113 | "earlyRequests": true
114 | }
115 | }
116 | ```
117 |
118 | We added a few settings to our default model that are specific to OData V4. Let's go through them step-by-step:
119 | - We set the `operationMode` to `Server`, which is mandatory for filtering and sorting queries with OData V4.
120 | - We set the `synchronizationMode` to `None`, which is also mandatory for OData V4.
121 | - `"autoExpandSelect": true` makes sure the `$expand` and `$select` OData query parameters are automatically being used, which we need for nested entities like `Genre` (https://developer-advocates-free-tier-central-hana-cloud-instan3b540fd6.cfapps.us10.hana.ondemand.com/browse/Books?$expand=genre).
122 | - `"earlyRequests": true` makes sure the OData service metadata is called as early as possible.
123 |
124 | ### 5. Add routing to the `webapp/manifest.json`
125 |
126 | ➡️ Add the following code to the `sap.ui5` section of the `webapp/manifest.json`:
127 |
128 | ```json
129 | ,
130 | "routing": {
131 | "routes": [
132 | {
133 | "pattern": ":?query:",
134 | "name": "mainPage",
135 | "target": "mainPage"
136 | }
137 | ],
138 | "targets": {
139 | "mainPage": {
140 | "type": "Component",
141 | "id": "mainPage",
142 | "name": "sap.fe.core.fpm",
143 | "options": {
144 | "settings": {
145 | "viewName": "sap.codejam.view.App",
146 | "entitySet": "Books",
147 | "navigation": {}
148 | }
149 | }
150 | }
151 | }
152 | }
153 | ```
154 |
155 | We added a new `sap.ui5.routing` section to our application descriptor file in which we define all `routes` (think "pages") our application contains. Currently we have just one route and target, but that will change shortly. Let's go through the additions step by step:
156 |
157 | - We describe a `pattern` for our route, which allows us to access it by attaching it to the URL of our application.
158 | - Our route also has a `name` and a `target`, which points to the `mainPage` object we define in the `targets` section.
159 | - The `targets` section is where we provide content information for the pages of our app. If we where to follow a freestyle UI5 approach, we could directly point to an xml file at this point, but within the SAP Fiori elements framework we specify the type as `Component` and use `sap.fe` templates for the pages.
160 | - For our `mainPage` we use the `sap.fe.core.fpm` template component, which allows us to point to our own `sap.codejam.view.App` xml view, but have it run inside the SAP Fiori elements flexible programming model. We define the main `entitySet` (coming from the backend OData service) we want to use on the page. We leave the `navigation` section empty for now, as we currently only have one route.
161 |
162 | ### 6. Remove the `rootView` from the `webapp/manifest.json`
163 |
164 | ➡️ Remove the entire `rootView` section (inside `sap.ui5`) from the `webapp/manifest.json` file.
165 |
166 | It was mandatory to remove the `rootView` from our application descriptor as we now have a dedicated `sap.ui5.routing` section and want the SAP Fiori elements framework to handle the routing, including embedding our views.
167 |
168 | ### 7. Add dependencies to the `webapp/manifest.json`
169 |
170 | ➡️ Add the following code at the beginning of the `sap.ui5` section of the `webapp/manifest.json`:
171 |
172 | ```json
173 | ,
174 | "dependencies": {
175 | "minUI5Version": "1.60.0",
176 | "libs": {
177 | "sap.ui.core": {},
178 | "sap.m": {},
179 | "sap.fe.macros": {},
180 | "sap.fe.templates": {}
181 | }
182 | }
183 | ```
184 |
185 | We added SAP Fiori elements related libraries to our dependencies to make sure they are preloaded by the SAPUI5 (performance optimizations) and are available at runtime.
186 |
187 | This is what our `webapp/manifest.json` now looks like:
188 |
189 | ```json
190 | {
191 | "sap.app": {
192 | "id": "codejam",
193 | "type": "application",
194 | "title": "CodeJam Bookshop",
195 | "applicationVersion": {
196 | "version": "1.0.0"
197 | },
198 | "dataSources": {
199 | "remoteBookshop": {
200 | "uri": "/browse/",
201 | "type" : "OData",
202 | "settings" : {
203 | "odataVersion" : "4.0"
204 | }
205 | }
206 | }
207 | },
208 | "sap.ui5": {
209 | "models": {
210 | "": {
211 | "dataSource": "remoteBookshop",
212 | "settings": {
213 | "synchronizationMode": "None",
214 | "operationMode": "Server",
215 | "autoExpandSelect": true,
216 | "earlyRequests": true
217 | }
218 | },
219 | "i18n": {
220 | "type": "sap.ui.model.resource.ResourceModel",
221 | "settings": {
222 | "bundleName": "sap.codejam.i18n.i18n"
223 | }
224 | }
225 | },
226 | "resources": {
227 | "css": [
228 | {
229 | "uri": "css/style.css"
230 | }
231 | ]
232 | },
233 | "routing": {
234 | "routes": [
235 | {
236 | "pattern": ":?query:",
237 | "name": "mainPage",
238 | "target": "mainPage"
239 | }
240 | ],
241 | "targets": {
242 | "mainPage": {
243 | "type": "Component",
244 | "id": "mainPage",
245 | "name": "sap.fe.core.fpm",
246 | "options": {
247 | "settings": {
248 | "viewName": "sap.codejam.view.App",
249 | "entitySet": "Books",
250 | "navigation": {}
251 | }
252 | }
253 | }
254 | }
255 | },
256 | "dependencies": {
257 | "minUI5Version": "1.60.0",
258 | "libs": {
259 | "sap.ui.core": {},
260 | "sap.m": {},
261 | "sap.fe.macros": {},
262 | "sap.fe.templates": {}
263 | }
264 | }
265 | }
266 | }
267 | ```
268 |
269 | ### 8. Use SAPUI5 instead of OpenUI5
270 |
271 | ➡️ Replace `openui5` with `sapui5` in the `webapp/index.html` and specify a version, so the bootstrapping (the script tag) looks like this:
272 |
273 | ```html
274 |
286 | ```
287 |
288 | - We moved from OpenUI5 to SAPUI5, because SAP Fiori elements are not available and not part of OpenUI5. Check the [base readme](/README.md#sapui5-vs-openui5) to learn more about the differences between SAPUI5 and OpenUI5.
289 | - We also specified a [long-term maintenance version](https://sapui5.hana.ondemand.com/versionoverview.html) of SAPUI5. Interestingly, we didn't specify a patch number, which means we will always load the latest patch ([patch-level independent bootstrap](https://blogs.sap.com/2022/04/14/sapui5-patch-level-independent-bootstrap)).
290 |
291 | ### 9. Rebuild the `webapp/view/App.view.xml`
292 |
293 | ➡️ Replace the content of the `webapp/view/App.view.xml` with the following code:
294 |
295 | ```xml
296 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 | ```
314 |
315 | We removed almost all the content of our app view and replaced it with a ``, which is a so called **building block** that we can use in SAP Fiori elements flexible programming model enabled applications. We pass it a `metaPath` pointing to specific **OData annotations** using the [SAP UI vocabulary](https://github.com/SAP/odata-vocabularies/blob/main/vocabularies/UI.md). As described [earlier](#chapter-201---enabling-the-sap-fiori-elements-flexible-programming-model), annotations are mandatory when using SAP Fiori elements. Luckily, they have already been implemented as part of the OData backend application (built with the SAP Cloud Application Programming model) and are ready to be consumed.
316 |
317 |
318 | How have the OData annotations been implemented? 💬
319 |
320 |
321 |
322 | > This is what the annotation file in the backend looks like:
323 | >
324 | >```cds
325 | >using {CatalogService} from '../srv/cat-service';
326 | >
327 | >// List Report
328 | >annotate CatalogService.Books with @(
329 | > UI: {
330 | > LineItem: [
331 | > {
332 | > $Type: 'UI.DataField',
333 | > Label: 'Book',
334 | > Value: title
335 | > },
336 | > {
337 | > $Type: 'UI.DataField',
338 | > Label: 'Author',
339 | > Value: author
340 | > },
341 | > {
342 | > $Type: 'UI.DataField',
343 | > Label: 'Genre',
344 | > Value: genre.name
345 | > },
346 | > {
347 | > $Type: 'UI.DataField',
348 | > Label: 'Price',
349 | > Value: price
350 | > },
351 | > {
352 | > $Type: 'UI.DataField',
353 | > Label: 'Stock',
354 | > Value: stock
355 | > }
356 | > ]
357 | > }
358 | >);
359 | >```
360 | >
361 | >Although annotations are considered backend development and therefore not exactly the scope of this repository, it is important to understand how annotations work and how SAP Fiori elements is able to interpret them:
362 | >
363 | >CDS based OData annotations are one of the superpowers of the SAP Cloud Application Programming Model. It automatically picks up and reads annotation files and serves the provided information in the service [metadata document](https://developer-advocates-free-tier-central-hana-cloud-instan3b540fd6.cfapps.us10.hana.ondemand.com/browse/$metadata). This is the document our SAP Fiori elements application interprets and uses to build the UI. Let's go through the annotations file step by step:
364 | >
365 | >- It uses the [UI vocabulary](https://github.com/SAP/odata-vocabularies/blob/main/vocabularies/UI.md) developed by SAP to describe how the entity should to be displayed in user interfaces. Generally a vocabulary is a collection of terms that can be used to describe and give meaning to OData services.
366 | >- It describes a `LineItem`, which is a collection (array) of data fields (think "columns") suitable to be visualized in a table or list.
367 | >- The objects of the `LineItem` array are of type `UI.DataField` and therefore simply represent a piece of data. Each data field (think "column") has a `Label` and a `Value`, the latter comes directly from our Books entity.
368 | >
369 | >You can learn more about annotations in this [document](https://github.com/SAP-samples/odata-basics-handsonsapdev/blob/annotations/bookshop/README.md).
370 |
371 |
372 |
373 |
374 | ### 10. Use the `sap/fe/core/PageController` instead of the `sap/ui/core/mvc/Controller`
375 |
376 | ➡️ Replace the content of the `webapp/controller/App.controller.js` with the following code:
377 |
378 | ```javascript
379 | sap.ui.define([
380 | "sap/fe/core/PageController"
381 | ], function (PageController) {
382 | "use strict"
383 | return PageController.extend("sap.codejam.controller.App", {})
384 | })
385 | ```
386 |
387 | Similar to what we did in [step 2](#2-extend-the-sapfecoreappcomponent-instead-of-the-sapuicoreuicomponent) we now use the `PageController` provided by SAP Fiori elements instead of the core UI5 controller. This is to make sure our controller code runs within the SAP Fiori elements framework and has access to its [extension APIs](https://ui5.sap.com/#/api/sap.fe.core.PageController%23methods/getExtensionAPI). The `sap/fe/core/PageController` itself extends the `sap/ui/core/mvc/Controller`.
388 |
389 | ### 11. Change `ui5.yaml` configuration
390 |
391 | Now that we are consuming a different OData service endpoint (V4), we need to tweak the UI5 server configuration in the `ui5.yaml` file:
392 |
393 | ➡️ Replace the current content of the `ui5.yaml` file with the following code:
394 |
395 | ```yaml
396 | specVersion: '2.6'
397 | metadata:
398 | name: bookshop
399 | type: application
400 | server:
401 | customMiddleware:
402 | - name: fiori-tools-proxy
403 | afterMiddleware: compression
404 | configuration:
405 | backend:
406 | - path: /browse
407 | url: https://developer-advocates-free-tier-central-hana-cloud-instan3b540fd6.cfapps.us10.hana.ondemand.com
408 | ```
409 |
410 | We changed the path to the backend service (via the `fiori-tools-proxy`) to `/browse`, as we are now consuming a different OData service endpoint (V4). This configuration has to match our `webapp/manifest.json` file.
411 |
412 | ### 12. Test the new app
413 |
414 | ➡️ Stop the UI5 server (`control + c`) and restart it `npm run dev` so the the server configuration changes take effect.
415 |
416 | 
417 |
418 | We enabled the SAP Fiori elements flexible programming model for our custom SAPUI5 application and used the Table building block, powered by OData annotations. You might have to resize your browser window to see all the columns of the Table. We will continue to fine tune and work on our application in the following chapters.
419 |
420 | Continue to [Appendix 02 - Adding an Object Page](/chapters/appendix-02-object-page/)
421 |
--------------------------------------------------------------------------------
/chapters/appendix-01-fe-fpm/result.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SAP-samples/ui5-exercises-codejam/c553ebf6cb35c5458b003edd14cb6a2c5d2aa637/chapters/appendix-01-fe-fpm/result.png
--------------------------------------------------------------------------------
/chapters/appendix-02-object-page/readme.md:
--------------------------------------------------------------------------------
1 | # Appendix 02 - Adding an Object Page
2 |
3 | For this version of our bookshop application (using the SAP Fiori elements flexible programming model) we will implement a separate object (detail) page to display more information about a selected book instead of using a custom section under the `booksTable`. This design is known as [List Report Object Page](https://sapui5.hana.ondemand.com/sdk/#/topic/c0eec49db81a441e878f528c8f3d28de.html).
4 |
5 | ## Steps
6 |
7 | [1. Add routing in the `webapp/manifest.json` file](#1-add-routing-in-the-webappmanifestjson-file)
8 | [2. Inspect annotations](#2-inspect-annotations)
9 | [3. Test the app](#3-test-the-app)
10 |
11 | ### 1. Add routing in the `webapp/manifest.json` file
12 |
13 | ➡️ Replace the current `sap.ui5.routing` section of the `webapp/manifest.json` with the following code:
14 |
15 | ```json
16 | ,
17 | "routing": {
18 | "routes": [
19 | {
20 | "pattern": ":?query:",
21 | "name": "mainPage",
22 | "target": "mainPage"
23 | },
24 | {
25 | "pattern": "/Books({key}):?query:",
26 | "name": "detailPage",
27 | "target": "detailPage"
28 | }
29 | ],
30 | "targets": {
31 | "mainPage": {
32 | "type": "Component",
33 | "id": "mainPage",
34 | "name": "sap.fe.core.fpm",
35 | "options": {
36 | "settings": {
37 | "viewName": "sap.codejam.view.App",
38 | "entitySet": "Books",
39 | "navigation": {
40 | "Books": {
41 | "detail": {
42 | "route": "detailPage"
43 | }
44 | }
45 | }
46 | }
47 | }
48 | },
49 | "detailPage": {
50 | "type": "Component",
51 | "id": "detailPage",
52 | "name": "sap.fe.templates.ObjectPage",
53 | "options": {
54 | "settings": {
55 | "entitySet": "Books",
56 | "navigation": {}
57 | }
58 | }
59 | }
60 | }
61 | }
62 | ```
63 |
64 | We added a new page to our application in the form of a new route and target. Let's dive into the details:
65 | - The `detailPage` will be reachable via the `/Books({key}):?query:` route, so the primary key of the book we want to see will be part of the URL (plus optional query parameters, as always).
66 | - The route points to the `detailPage` target, which is a component using the Fiori elements ObjectPage template (`sap.fe.templates.ObjectPage`)
67 | - We also addded the `detailPage` to the `navigation` section of the `mainPage` target, which will allow us to get to the `detailPage` by selecting a book on the `mainPage`.
68 |
69 | ### 2. Inspect annotations
70 |
71 | You might be wondering how the application knows what to display on the new object page. This information is provided via annotations in the metadata of the backend service (built with the SAP Cloud Application Programming model). The service doesn't only contain the annotations we already used in the [previous step](/chapters/appendix-01-fe-fpm#9-rebuild-the-webappviewappviewxml), but also some more annotations that the object page interprets.
72 |
73 | ➡️ Inspect the annotation file that was used to create the metadata annotation in the backend:
74 |
75 | ```cds
76 | using {CatalogService} from '../srv/cat-service';
77 |
78 | // List Report
79 | annotate CatalogService.Books with @(
80 | UI: {
81 | LineItem: [
82 | {
83 | $Type: 'UI.DataField',
84 | Label: 'Book',
85 | Value: title
86 | },
87 | {
88 | $Type: 'UI.DataField',
89 | Label: 'Author',
90 | Value: author
91 | },
92 | {
93 | $Type: 'UI.DataField',
94 | Label: 'Genre',
95 | Value: genre.name
96 | },
97 | {
98 | $Type: 'UI.DataField',
99 | Label: 'Price',
100 | Value: price
101 | },
102 | {
103 | $Type: 'UI.DataField',
104 | Label: 'Stock',
105 | Value: stock
106 | }
107 | ]
108 | }
109 | );
110 |
111 | //Object Page
112 | annotate CatalogService.Books with @(
113 | UI: {
114 | Identification: [ { Value: ID } ],
115 | HeaderInfo: {
116 | TypeName : 'Book',
117 | TypeNamePlural: 'Books',
118 | Title: {
119 | Value: title
120 | },
121 | Description: {
122 | Value: author
123 | }
124 | },
125 | Facets: [
126 | {
127 | $Type : 'UI.ReferenceFacet',
128 | ID : 'GeneratedFacet',
129 | Label : 'General Information',
130 | Target: '@UI.FieldGroup#GeneratedGroup'
131 | }
132 | ],
133 | FieldGroup#GeneratedGroup: {
134 | $Type: 'UI.FieldGroupType',
135 | Data : [
136 | {
137 | $Type: 'UI.DataField',
138 | Label: 'Title',
139 | Value: title
140 | },
141 | {
142 | $Type: 'UI.DataField',
143 | Label: 'Author',
144 | Value: author
145 | },
146 | {
147 | $Type: 'UI.DataField',
148 | Label: 'Genre',
149 | Value: genre.name
150 | },
151 | {
152 | $Type: 'UI.DataField',
153 | Label: 'Price',
154 | Value: price
155 | },
156 | {
157 | $Type: 'UI.DataField',
158 | Label: 'Stock',
159 | Value: stock
160 | },
161 | {
162 | $Type: 'UI.DataField',
163 | Label: 'Description',
164 | Value: descr
165 | }
166 | ]
167 | }
168 | }
169 | );
170 | ```
171 |
172 | The bottom block of annotations is used and interpreted by the object page. Let's dive into the details:
173 | - The `HeaderInfo` with `Title`, `Description`, and more is used to construct the header of the object page.
174 | - In the `Facets` array, one facet is created that points to a custom `@UI.FieldGroup` with the name `GeneratedGroup`.
175 | - The `FieldGroup#GeneratedGroup` references the data points to be displayed in a group.
176 |
177 | You can learn more about annotations in this [document](https://github.com/SAP-samples/odata-basics-handsonsapdev/blob/annotations/bookshop/README.md).
178 |
179 | ### 3. Test the app
180 |
181 | ➡️ Refresh the app and select a book. This should get you to the object page.
182 |
183 | 
184 |
185 | We added an object page to our application using an SAP Fiori elements template, powered by OData annotations.
186 |
187 | Continue to [Chapter 03 - Using the SAP Fiori Tools](/chapters/appendix-03-fiori-tools/)
188 |
--------------------------------------------------------------------------------
/chapters/appendix-02-object-page/result.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SAP-samples/ui5-exercises-codejam/c553ebf6cb35c5458b003edd14cb6a2c5d2aa637/chapters/appendix-02-object-page/result.png
--------------------------------------------------------------------------------
/chapters/appendix-03-fiori-tools/page-map.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SAP-samples/ui5-exercises-codejam/c553ebf6cb35c5458b003edd14cb6a2c5d2aa637/chapters/appendix-03-fiori-tools/page-map.png
--------------------------------------------------------------------------------
/chapters/appendix-03-fiori-tools/readme.md:
--------------------------------------------------------------------------------
1 | # Appendix 03 - Using the SAP Fiori Tools
2 |
3 | At the end of this chapter you will have made our application visible to the SAP Fiori Tools and have used them. The [SAP Fiori Tools](https://marketplace.visualstudio.com/items?itemName=SAPSE.sap-ux-fiori-tools-extension-pack) are available both vor VS Code and the SAP Business Application Studio, and are the tooling of choice whenever we work with SAP Fiori elements based applications.
4 |
5 | ## Steps
6 |
7 | [1. Add `@sap/ux-specification` as dependency](#2-add-sapux-specification-as-dependency)
8 | [2. Add `sapux` to `package.json`](#3-add-sapux-to-packagejson)
9 | [3. Install the SAP Fiori Tools](#5-install-the-sap-fiori-tools)
10 | [4. Work with the SAP Fiori Tools Page Map](#6-work-with-the-sap-fiori-tools-page-map)
11 | [5. Test the new layout](#7-test-the-new-layout)
12 |
13 | ### 1. Add `@sap/ux-specification` as dependency
14 |
15 | The `@sap/ux-specification` package is required by the SAP Fiori Tools. It provides the necessary SAP Fiori elements template structure information for the UI5 version we are using. It is important to install the correct version, matching the version [we define in the bootstrapping](/chapters/appendix-01-fe-fpm/readme.md#8-use-sapui5-instead-of-openui5).
16 |
17 | ➡️ Run the following command in the `bookshop/` directory:
18 |
19 | ```bash
20 | npm install @sap/ux-specification@UI5-1.108 --save-dev
21 | ```
22 |
23 | ### 2. Add `sapux` to `package.json`
24 |
25 | We can now tell the SAP Fiori Tools where to look for our application by specifying a parameter in the `package.json`.
26 |
27 | ➡️ Add the following block of code to the `package.json`:
28 |
29 | ```json
30 | ,
31 | "sapux": [ "./" ]
32 | ```
33 |
34 | ### 3. Install the SAP Fiori Tools
35 |
36 | If you are working in the SAP Business Application Studio, you already have the SAP Fiori Tools, but if you are working in VS Code, you have to install them manually.
37 |
38 | ➡️ Go to the extension marketplace, search for `sap fiori tools extension pack` and install the first extension that comes up.
39 |
40 | ### 4. Work with the SAP Fiori Tools Page Map
41 |
42 | We can now test and use the SAP Fiori Tools, more specifically the Page Map.
43 |
44 | ➡️ Open the SAP Fiori Tools Page Map via the built-in Command Palette by searching for the term `Page Map`. Then select the Flexible Column Layout:
45 |
46 | 
47 |
48 | The SAP Fiori Tools Page Map shows the overall structure of our application and allows us to edit it. We selected the Flexible Column Layout, and the corresponding code was automatically added to our the `webapp/manifest.json` by the SAP Fiori Tools.
49 |
50 | ### 5. Test the new layout
51 |
52 | ➡️ Refresh the application in the browser and select a book. The app should app have the Flexible Column Layout:
53 |
54 | 
55 |
56 | Wow, you have made it all the way through this CodeJam content, congratulations! 🎉
57 |
58 | We would really appreciate if you took the time to [leave feedback](https://github.com/SAP-samples/ui5-exercises-codejam/issues/new?assignees=&labels=feedback&template=session-feedback-template.md&title=Session+Feedback) for this session/content, thank you!
59 |
60 | You should feel confident enough to start developing your own UI5 applications now, whether it be freestyle or with the SAP Fiori elements approach. If not, feel free to ask questions or raise concerns via a GitHub issue in this repository.
61 |
--------------------------------------------------------------------------------
/chapters/appendix-03-fiori-tools/result.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SAP-samples/ui5-exercises-codejam/c553ebf6cb35c5458b003edd14cb6a2c5d2aa637/chapters/appendix-03-fiori-tools/result.png
--------------------------------------------------------------------------------
/finished-app.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SAP-samples/ui5-exercises-codejam/c553ebf6cb35c5458b003edd14cb6a2c5d2aa637/finished-app.png
--------------------------------------------------------------------------------