├── demo-project
├── .gitignore
├── packages
│ ├── server
│ │ ├── .env.example
│ │ ├── ui5.yaml
│ │ ├── index.js
│ │ ├── package.json
│ │ ├── .eslintrc.json
│ │ └── lib
│ │ │ └── middleware.js
│ ├── app
│ │ ├── webapp
│ │ │ ├── test
│ │ │ │ ├── unit
│ │ │ │ │ ├── AllTests.js
│ │ │ │ │ ├── unitTests.qunit.js
│ │ │ │ │ ├── helper
│ │ │ │ │ │ └── FakeI18nModel.js
│ │ │ │ │ ├── unitTests.qunit.html
│ │ │ │ │ └── model
│ │ │ │ │ │ ├── formatter.js
│ │ │ │ │ │ └── models.js
│ │ │ │ ├── integration
│ │ │ │ │ ├── opaTests.qunit.js
│ │ │ │ │ ├── AllJourneys.js
│ │ │ │ │ ├── opaTests.qunit.html
│ │ │ │ │ ├── ObjectJourney.js
│ │ │ │ │ ├── pages
│ │ │ │ │ │ ├── shareOptions.js
│ │ │ │ │ │ ├── Browser.js
│ │ │ │ │ │ ├── Common.js
│ │ │ │ │ │ ├── App.js
│ │ │ │ │ │ ├── NotFound.js
│ │ │ │ │ │ ├── Object.js
│ │ │ │ │ │ └── Worklist.js
│ │ │ │ │ ├── WorklistJourney.js
│ │ │ │ │ ├── arrangements
│ │ │ │ │ │ └── Startup.js
│ │ │ │ │ ├── NotFoundJourney.js
│ │ │ │ │ └── NavigationJourney.js
│ │ │ │ ├── testsuite.qunit.html
│ │ │ │ └── testsuite.qunit.js
│ │ │ ├── view
│ │ │ │ ├── App.view.xml
│ │ │ │ ├── NotFound.view.xml
│ │ │ │ ├── ObjectNotFound.view.xml
│ │ │ │ ├── Object.view.xml
│ │ │ │ └── Worklist.view.xml
│ │ │ ├── model
│ │ │ │ ├── models.js
│ │ │ │ └── formatter.js
│ │ │ ├── controller
│ │ │ │ ├── NotFound.controller.js
│ │ │ │ ├── App.controller.js
│ │ │ │ ├── BaseController.js
│ │ │ │ ├── ErrorHandler.js
│ │ │ │ ├── Object.controller.js
│ │ │ │ └── Worklist.controller.js
│ │ │ ├── index.html
│ │ │ ├── test.html
│ │ │ ├── manifest.json
│ │ │ ├── i18n
│ │ │ │ └── i18n.properties
│ │ │ └── Component.js
│ │ ├── ui5.yaml
│ │ ├── package.json
│ │ └── .eslintrc.json
│ ├── theme-library
│ │ ├── src
│ │ │ ├── sap
│ │ │ │ ├── ui
│ │ │ │ │ ├── core
│ │ │ │ │ │ └── themes
│ │ │ │ │ │ │ └── ui5con20
│ │ │ │ │ │ │ ├── library.source.less
│ │ │ │ │ │ │ └── base.less
│ │ │ │ │ └── layout
│ │ │ │ │ │ └── themes
│ │ │ │ │ │ └── ui5con20
│ │ │ │ │ │ └── library.source.less
│ │ │ │ ├── f
│ │ │ │ │ └── themes
│ │ │ │ │ │ └── ui5con20
│ │ │ │ │ │ └── library.source.less
│ │ │ │ └── m
│ │ │ │ │ └── themes
│ │ │ │ │ └── ui5con20
│ │ │ │ │ └── library.source.less
│ │ │ └── ui5con20
│ │ │ │ └── library
│ │ │ │ └── themes
│ │ │ │ └── ui5con20
│ │ │ │ └── library.source.less
│ │ ├── package.json
│ │ └── ui5.yaml
│ └── library
│ │ ├── src
│ │ └── ui5con20
│ │ │ └── library
│ │ │ ├── themes
│ │ │ ├── base
│ │ │ │ ├── library.source.less
│ │ │ │ └── Chart.less
│ │ │ ├── sap_fiori_3
│ │ │ │ └── library.source.less
│ │ │ └── sap_fiori_3_dark
│ │ │ │ └── library.source.less
│ │ │ ├── .library
│ │ │ ├── library.js
│ │ │ ├── ChartRenderer.js
│ │ │ └── Chart.js
│ │ ├── package.json
│ │ ├── ui5.yaml
│ │ ├── test
│ │ └── ui5con20
│ │ │ └── library
│ │ │ └── qunit
│ │ │ ├── Chart.qunit.html
│ │ │ └── Chart.qunit.js
│ │ └── .eslintrc.json
├── .editorconfig
├── package.json
└── README.md
├── demo-nodejs-api
├── webapp
│ ├── view
│ │ ├── App.view.xml
│ │ ├── NotFound.view.xml
│ │ ├── ObjectNotFound.view.xml
│ │ ├── Object.view.xml
│ │ └── Worklist.view.xml
│ ├── controller
│ │ ├── NotFound.controller.js
│ │ ├── App.controller.js
│ │ ├── ErrorHandler.js
│ │ ├── BaseController.js
│ │ ├── Object.controller.js
│ │ └── Worklist.controller.js
│ ├── manifest.json
│ ├── i18n
│ │ └── i18n.properties
│ └── Component.js
├── README.md
├── package.json
└── createPreload.js
├── README.md
├── .gitignore
└── LICENSE.txt
/demo-project/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | dist/
3 |
--------------------------------------------------------------------------------
/demo-project/packages/server/.env.example:
--------------------------------------------------------------------------------
1 | UI5CON_DEMO_GITHUB_USER=
2 | UI5CON_DEMO_GITHUB_TOKEN=
--------------------------------------------------------------------------------
/demo-project/packages/app/webapp/test/unit/AllTests.js:
--------------------------------------------------------------------------------
1 | sap.ui.define([
2 | "./model/formatter",
3 | "./model/models"
4 | ], function() {
5 | "use strict";
6 | });
--------------------------------------------------------------------------------
/demo-project/packages/theme-library/src/sap/ui/core/themes/ui5con20/library.source.less:
--------------------------------------------------------------------------------
1 | @import "../sap_fiori_3_dark/library.source.less";
2 | @import "base.less";
3 |
--------------------------------------------------------------------------------
/demo-project/packages/server/ui5.yaml:
--------------------------------------------------------------------------------
1 | specVersion: "2.1"
2 | kind: extension
3 | type: server-middleware
4 | metadata:
5 | name: backend
6 | middleware:
7 | path: lib/middleware.js
8 |
--------------------------------------------------------------------------------
/demo-project/packages/theme-library/src/sap/f/themes/ui5con20/library.source.less:
--------------------------------------------------------------------------------
1 | @import "../sap_fiori_3_dark/library.source.less";
2 | @import "../../../../sap/ui/core/themes/ui5con20/base.less";
3 |
--------------------------------------------------------------------------------
/demo-project/packages/theme-library/src/sap/m/themes/ui5con20/library.source.less:
--------------------------------------------------------------------------------
1 | @import "../sap_fiori_3_dark/library.source.less";
2 | @import "../../../../sap/ui/core/themes/ui5con20/base.less";
3 |
--------------------------------------------------------------------------------
/demo-project/packages/theme-library/src/sap/ui/layout/themes/ui5con20/library.source.less:
--------------------------------------------------------------------------------
1 | @import "../sap_fiori_3_dark/library.source.less";
2 | @import "../../../../../sap/ui/core/themes/ui5con20/base.less";
3 |
--------------------------------------------------------------------------------
/demo-project/packages/theme-library/src/ui5con20/library/themes/ui5con20/library.source.less:
--------------------------------------------------------------------------------
1 | @import "../sap_fiori_3_dark/library.source.less";
2 | @import "../../../../sap/ui/core/themes/ui5con20/base.less";
3 |
--------------------------------------------------------------------------------
/demo-project/packages/library/src/ui5con20/library/themes/base/library.source.less:
--------------------------------------------------------------------------------
1 | /*!
2 | * ${copyright}
3 | */
4 |
5 | @import "../../../../sap/ui/core/themes/base/base.less";
6 | @import "../../../../sap/ui/core/themes/base/global.less";
7 |
8 | @import "Chart.less";
9 |
--------------------------------------------------------------------------------
/demo-project/packages/theme-library/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@ui5con20/theme-library",
3 | "private": true,
4 | "version": "1.0.0",
5 | "description": "UI5con Demo Theme Library",
6 | "scripts": {},
7 | "dependencies": {
8 | "@ui5con20/library": "^1.0.0"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/demo-project/packages/library/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@ui5con20/library",
3 | "private": true,
4 | "version": "1.0.0",
5 | "description": "UI5con Demo Library",
6 | "scripts": {
7 | "test": "eslint ./src ./test"
8 | },
9 | "dependencies": {
10 | "chartist": "^0.11.4"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/demo-project/packages/library/src/ui5con20/library/themes/sap_fiori_3/library.source.less:
--------------------------------------------------------------------------------
1 | /*!
2 | * ${copyright}
3 | */
4 |
5 | @import "../base/library.source.less";
6 | @import "../../../../sap/ui/core/themes/sap_fiori_3/base.less";
7 | @import "../../../../sap/ui/core/themes/sap_fiori_3/global.less";
8 |
--------------------------------------------------------------------------------
/demo-project/packages/app/webapp/test/unit/unitTests.qunit.js:
--------------------------------------------------------------------------------
1 | /* global QUnit */
2 | QUnit.config.autostart = false;
3 |
4 | sap.ui.getCore().attachInit(function () {
5 | "use strict";
6 |
7 | sap.ui.require([
8 | "ui5con20/app/test/unit/AllTests"
9 | ], function () {
10 | QUnit.start();
11 | });
12 | });
--------------------------------------------------------------------------------
/demo-project/packages/library/src/ui5con20/library/themes/sap_fiori_3_dark/library.source.less:
--------------------------------------------------------------------------------
1 | /*!
2 | * ${copyright}
3 | */
4 |
5 | @import "../base/library.source.less";
6 | @import "../../../../sap/ui/core/themes/sap_fiori_3_dark/base.less";
7 | @import "../../../../sap/ui/core/themes/sap_fiori_3_dark/global.less";
8 |
--------------------------------------------------------------------------------
/demo-project/packages/app/webapp/test/integration/opaTests.qunit.js:
--------------------------------------------------------------------------------
1 | /* global QUnit */
2 |
3 | QUnit.config.autostart = false;
4 |
5 | sap.ui.getCore().attachInit(function() {
6 | "use strict";
7 |
8 | sap.ui.require([
9 | "ui5con20/app/test/integration/AllJourneys"
10 | ], function() {
11 | QUnit.start();
12 | });
13 | });
--------------------------------------------------------------------------------
/demo-nodejs-api/webapp/view/App.view.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/demo-project/packages/app/webapp/test/testsuite.qunit.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | QUnit test suite for UI5con 2020 Demo App
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/demo-project/packages/app/webapp/view/App.view.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/demo-project/packages/theme-library/ui5.yaml:
--------------------------------------------------------------------------------
1 | specVersion: '2.1'
2 | metadata:
3 | name: theme-library
4 | type: theme-library
5 | framework:
6 | name: OpenUI5
7 | version: "1.79.0"
8 | libraries:
9 | - name: sap.f
10 | - name: sap.m
11 | - name: sap.ui.core
12 | - name: sap.ui.layout
13 | - name: themelib_sap_fiori_3
14 |
--------------------------------------------------------------------------------
/demo-nodejs-api/README.md:
--------------------------------------------------------------------------------
1 | # demo-nodejs-api
2 |
3 | Example of how to use the UI5 Tooling Node.js API to create a Component preload.
4 |
5 | ## Getting started
6 |
7 | Install dependencies with npm
8 | ```
9 | npm install
10 | ```
11 |
12 | ## Run
13 | Run `npm run build` to run the `createPreload.js` script that produces a Component preload (`dist/Component-preload.js`)
14 |
--------------------------------------------------------------------------------
/demo-project/packages/app/webapp/model/models.js:
--------------------------------------------------------------------------------
1 | sap.ui.define([
2 | "sap/ui/model/json/JSONModel",
3 | "sap/ui/Device"
4 | ], function (JSONModel, Device) {
5 | "use strict";
6 |
7 | return {
8 |
9 | createDeviceModel : function () {
10 | var oModel = new JSONModel(Device);
11 | oModel.setDefaultBindingMode("OneWay");
12 | return oModel;
13 | }
14 |
15 | };
16 | });
--------------------------------------------------------------------------------
/demo-project/packages/library/src/ui5con20/library/themes/base/Chart.less:
--------------------------------------------------------------------------------
1 | /* Basic styling from chartist */
2 | @import (inline) "../../thirdparty/chartist/chartist.css";
3 |
4 | /* Overrides with UI5 theming parameters */
5 | .ct-label {
6 | fill: @sapUiNeutralText;
7 | color: @sapUiNeutralText;
8 | }
9 |
10 | .ct-grid {
11 | stroke: @sapUiContentDisabledTextColor;
12 | }
13 |
--------------------------------------------------------------------------------
/demo-project/.editorconfig:
--------------------------------------------------------------------------------
1 | # see http://editorconfig.org
2 |
3 | root = true
4 |
5 | [*]
6 | charset = utf-8
7 | indent_style = tab
8 |
9 | [*.{css,html,js,less,txt,json,yml,md}]
10 | trim_trailing_whitespace = true
11 | end_of_line = lf
12 | indent_size = 4
13 | insert_final_newline = true
14 |
15 | [*.{yml,yaml}]
16 | indent_style = space
17 | indent_size = 2
18 |
19 | [*.md]
20 | trim_trailing_whitespace = false
21 |
--------------------------------------------------------------------------------
/demo-project/packages/app/webapp/test/integration/AllJourneys.js:
--------------------------------------------------------------------------------
1 | sap.ui.define([
2 | "sap/ui/test/Opa5",
3 | "./arrangements/Startup",
4 | "./WorklistJourney",
5 | "./NavigationJourney",
6 | "./NotFoundJourney",
7 | "./ObjectJourney"
8 | ], function (Opa5, Startup) {
9 | "use strict";
10 |
11 | Opa5.extendConfig({
12 | arrangements: new Startup(),
13 | viewNamespace: "ui5con20.app.view.",
14 | autoWait: true
15 | });
16 |
17 | });
--------------------------------------------------------------------------------
/demo-nodejs-api/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "demo-nodejs-api",
3 | "private": true,
4 | "version": "1.0.0",
5 | "description": "UI5con Node.js API Demo",
6 | "main": "createPreload.js",
7 | "scripts": {
8 | "build": "node createPreload.js"
9 | },
10 | "devDependencies": {
11 | "@openui5/sap.ui.core": "^1.85.2",
12 | "@ui5/builder": "^2.7.2",
13 | "@ui5/fs": "^2.0.6",
14 | "@ui5/project": "^2.2.6"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/demo-nodejs-api/webapp/controller/NotFound.controller.js:
--------------------------------------------------------------------------------
1 | sap.ui.define([
2 | "./BaseController"
3 | ], function (BaseController) {
4 | "use strict";
5 |
6 | return BaseController.extend("ui5con20.demo.nodejs.controller.NotFound", {
7 |
8 | /**
9 | * Navigates to the worklist when the link is pressed
10 | * @public
11 | */
12 | onLinkPressed : function () {
13 | this.getRouter().navTo("worklist");
14 | }
15 |
16 | });
17 |
18 | });
--------------------------------------------------------------------------------
/demo-project/packages/app/webapp/controller/NotFound.controller.js:
--------------------------------------------------------------------------------
1 | sap.ui.define([
2 | "./BaseController"
3 | ], function (BaseController) {
4 | "use strict";
5 |
6 | return BaseController.extend("ui5con20.app.controller.NotFound", {
7 |
8 | /**
9 | * Navigates to the worklist when the link is pressed
10 | * @public
11 | */
12 | onLinkPressed : function () {
13 | this.getRouter().navTo("worklist");
14 | }
15 |
16 | });
17 |
18 | });
--------------------------------------------------------------------------------
/demo-project/packages/app/webapp/test/testsuite.qunit.js:
--------------------------------------------------------------------------------
1 | window.suite = function () {
2 | "use strict";
3 | /* eslint-disable new-cap */
4 | var oSuite = new parent.jsUnitTestSuite(),
5 | sContextPath = location.pathname.substring(0, location.pathname.lastIndexOf("/") + 1);
6 |
7 | oSuite.addTestPage(sContextPath + "unit/unitTests.qunit.html");
8 | oSuite.addTestPage(sContextPath + "integration/opaTests.qunit.html");
9 |
10 | return oSuite;
11 | };
12 |
--------------------------------------------------------------------------------
/demo-project/packages/server/index.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const middleware = require("./lib/middleware");
3 | const path = require("path");
4 | const app = express();
5 | const port = 3001;
6 |
7 | const appDir = path.dirname(require.resolve("@ui5con20/app/dist/index.html"));
8 |
9 | app.use(express.static(appDir));
10 | app.use("/data", middleware());
11 |
12 | app.listen(port, () => console.log(`App listening at http://localhost:${port}`));
13 |
--------------------------------------------------------------------------------
/demo-project/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "demo-project",
3 | "private": true,
4 | "version": "1.0.0",
5 | "description": "UI5con Demo Project",
6 | "scripts": {
7 | "build": "cd packages/app && ui5 build --all",
8 | "start": "cd packages/app && ui5 serve -o index.html",
9 | "serve-prod": "cd packages/server && yarn start"
10 | },
11 | "workspaces": [
12 | "packages/*"
13 | ],
14 | "devDependencies": {
15 | "@ui5/cli": "^2.9.3",
16 | "eslint": "^7.19.0"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/demo-nodejs-api/webapp/view/NotFound.view.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/demo-project/packages/app/webapp/view/NotFound.view.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/demo-project/packages/app/webapp/model/formatter.js:
--------------------------------------------------------------------------------
1 | sap.ui.define([], function () {
2 | "use strict";
3 |
4 | return {
5 |
6 | /**
7 | * Rounds the number unit value to 2 digits
8 | * @public
9 | * @param {string} sValue the number string to be rounded
10 | * @returns {string} sValue with 2 digits rounded
11 | */
12 | numberUnit : function (sValue) {
13 | if (!sValue) {
14 | return "";
15 | }
16 | return parseFloat(sValue).toFixed(2);
17 | }
18 |
19 | };
20 |
21 | });
22 |
--------------------------------------------------------------------------------
/demo-nodejs-api/webapp/view/ObjectNotFound.view.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/demo-project/packages/app/webapp/view/ObjectNotFound.view.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/demo-project/packages/library/src/ui5con20/library/.library:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ui5con20.library
5 | SAP SE
6 | ${copyright}
7 | ${version}
8 |
9 | UI5con 2020 demo library
10 |
11 |
12 |
13 | sap.ui.core
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/demo-project/packages/app/webapp/test/unit/helper/FakeI18nModel.js:
--------------------------------------------------------------------------------
1 | sap.ui.define([
2 | "sap/ui/model/Model"
3 | ], function (Model) {
4 | "use strict";
5 |
6 | return Model.extend("ui5con20.app.test.unit.helper.FakeI18nModel", {
7 |
8 | constructor : function (mTexts) {
9 | Model.call(this);
10 | this.mTexts = mTexts || {};
11 | },
12 |
13 | getResourceBundle : function () {
14 | return {
15 | getText : function (sTextName) {
16 | return this.mTexts[sTextName];
17 | }.bind(this)
18 | };
19 | }
20 |
21 | });
22 |
23 | });
--------------------------------------------------------------------------------
/demo-project/packages/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@ui5con20/server",
3 | "private": true,
4 | "version": "1.0.0",
5 | "description": "UI5con Demo Server",
6 | "main": "index.js",
7 | "scripts": {
8 | "test": "eslint ./index.js ./lib",
9 | "start": "node index.js"
10 | },
11 | "dependencies": {
12 | "dotenv": "^8.2.0",
13 | "express": "^4.17.1",
14 | "parseurl": "^1.3.3",
15 | "request": "^2.88.2"
16 | },
17 | "devDependencies": {
18 | "@ui5con20/app": "^1.0.0"
19 | },
20 | "ui5": {
21 | "dependencies": []
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/demo-project/packages/app/ui5.yaml:
--------------------------------------------------------------------------------
1 | specVersion: "2.1"
2 | metadata:
3 | name: app
4 | type: application
5 | framework:
6 | name: OpenUI5
7 | version: "1.79.0"
8 | libraries:
9 | - name: sap.f
10 | - name: sap.m
11 | - name: sap.ui.core
12 | server:
13 | customMiddleware:
14 | - name: ui5-middleware-livereload
15 | afterMiddleware: compression
16 | configuration:
17 | extraExts: "xml,json,properties"
18 | port: 35729
19 | watchPath: "webapp"
20 | - name: backend
21 | mountPath: /data
22 | afterMiddleware: compression
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ui5con20-ui5-tooling
2 |
3 | The repository contains the content for the UI5con ON AIR 2020 session *UI5 Tooling 2020*.
4 |
5 | [Slides](https://github.com/RandomByte/talks/blob/master/UI5con_ON_AIR_2020/UI5con2020_UI5_Tooling_2020.pdf)
6 | [Recording](https://www.youtube.com/watch?v=8IHoVJLKN34)
7 |
8 | ## [demo-nodejs-api](./demo-nodejs-api)
9 |
10 | Example of how to use the UI5 Tooling Node.js API to create a Component preload.
11 |
12 | ## [demo-project](./demo-project)
13 |
14 | Monorepo setup containing an application, library, theme-library and server.
15 |
--------------------------------------------------------------------------------
/demo-project/packages/library/src/ui5con20/library/library.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * ${copyright}
3 | */
4 |
5 | /**
6 | * Initialization Code of ui5con20.library
7 | */
8 | sap.ui.define([
9 | "sap/ui/core/library" // library dependency
10 | ], function() {
11 | "use strict";
12 |
13 | // delegate further initialization of this library to the Core
14 | sap.ui.getCore().initLibrary({
15 | name : "ui5con20.library",
16 | version: "${version}",
17 | dependencies : ["sap.ui.core"],
18 | controls: [
19 | "ui5con20.library.Chart"
20 | ]
21 | });
22 |
23 | return ui5con20.library;
24 | });
25 |
--------------------------------------------------------------------------------
/demo-project/packages/library/ui5.yaml:
--------------------------------------------------------------------------------
1 | specVersion: '2.1'
2 | metadata:
3 | name: library
4 | type: library
5 |
6 | framework:
7 | name: OpenUI5
8 | version: "1.79.0"
9 | libraries:
10 | - name: sap.ui.core
11 |
12 | ---
13 | specVersion: "2.1"
14 | kind: extension
15 | type: project-shim
16 | metadata:
17 | name: chartist-shim
18 | shims:
19 | configurations:
20 | chartist:
21 | specVersion: "2.1"
22 | type: module
23 | metadata:
24 | name: chartist
25 | resources:
26 | configuration:
27 | paths:
28 | /resources/ui5con20/library/thirdparty/chartist/: "dist"
29 |
--------------------------------------------------------------------------------
/demo-project/packages/library/test/ui5con20/library/qunit/Chart.qunit.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | QUnit Test: ui5con20.library.Chart
6 |
7 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/demo-project/packages/app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@ui5con20/app",
3 | "private": true,
4 | "version": "1.0.0",
5 | "description": "UI5con Demo App",
6 | "scripts": {
7 | "build": "ui5 build",
8 | "test": "eslint ./webapp",
9 | "start": "ui5 serve -o index.html"
10 | },
11 | "dependencies": {
12 | "@ui5con20/library": "^1.0.0",
13 | "@ui5con20/theme-library": "^1.0.0"
14 | },
15 | "devDependencies": {
16 | "@ui5con20/server": "^1.0.0",
17 | "ui5-middleware-livereload": "^0.5.1"
18 | },
19 | "ui5": {
20 | "dependencies": [
21 | "@ui5con20/library",
22 | "@ui5con20/server",
23 | "@ui5con20/theme-library",
24 | "ui5-middleware-livereload"
25 | ]
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/demo-project/packages/theme-library/src/sap/ui/core/themes/ui5con20/base.less:
--------------------------------------------------------------------------------
1 | @sapBaseColor: #191717;
2 |
3 | @sapList_Background: @sapBaseColor;
4 | @sapField_Background: @sapBaseColor;
5 | @sapBackgroundColor: @sapBaseColor;
6 |
7 | @sapBrandColor: #ffa42c;
8 | @sapUiAccent6: @sapBrandColor;
9 |
10 | @sapUiShellBackground: linear-gradient(343deg,#fb5b38 54%,#fdbb2d);
11 |
12 | @sapTextColor: #fff;
13 | @sapGroup_TitleTextColor: #ffa42c;
14 | @sapField_TextColor: #fff;
15 | @sapNeutralColor: #fff;
16 |
17 | @sapSelectedColor: rgba(255,162,66,.2);
18 | @sapList_SelectionBackgroundColor: @sapSelectedColor;
19 |
20 | @sapList_HeaderBackground: @sapGroup_ContentBackground;
21 | @sapPageFooter_Background: @sapGroup_ContentBackground;
22 |
--------------------------------------------------------------------------------
/demo-project/packages/app/webapp/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | UI5con 2020 Demo App
7 |
8 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/demo-project/README.md:
--------------------------------------------------------------------------------
1 | # demo-project
2 |
3 | Monorepo setup containing an application, library, theme-library and server.
4 |
5 | ## Getting started
6 |
7 | Install and link dependencies with Yarn
8 | ```
9 | yarn
10 | ```
11 |
12 | Copy the example env file
13 | ```
14 | cp packages/server/.env.example packages/server/.env
15 | ```
16 |
17 | Create a [GitHub personal access token](https://github.com/settings/tokens/new) (read-only / no scopes required).
18 | Add your GitHub user and token to `packages/server/.env`
19 |
20 | ## Development
21 | Run `yarn start` to serve and open the application in the browser
22 |
23 | ## Building
24 | Run `yarn build` to build the app with all dependencies
25 |
26 | ## Deployment
27 | Run `yarn serve-prod` to host a local webserver with the built application
28 |
--------------------------------------------------------------------------------
/demo-project/packages/library/src/ui5con20/library/ChartRenderer.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * ${copyright}
3 | */
4 |
5 | // Provides default renderer for controlsap.m.Avatar
6 | sap.ui.define(["./library"], function (/* library */) {
7 | "use strict";
8 |
9 | var ChartRenderer = {
10 | apiVersion: 2
11 | };
12 |
13 | /**
14 | * Renders the HTML for the given control, using the provided {@link sap.ui.core.RenderManager}.
15 | *
16 | * @param {sap.ui.core.RenderManager} oRm the RenderManager that can be used for writing to the Render-Output-Buffer
17 | * @param {sap.ui.core.Control} oChart an object representation of the control that should be rendered
18 | */
19 | ChartRenderer.render = function (oRm, oChart) {
20 | oRm.openStart("div", oChart);
21 | oRm.openEnd();
22 | oRm.renderControl(oChart.getAggregation("_html"));
23 | oRm.close("div");
24 | };
25 |
26 | return ChartRenderer;
27 | });
28 |
--------------------------------------------------------------------------------
/demo-project/packages/app/webapp/test/integration/opaTests.qunit.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Integration tests for UI5con 2020 Demo App
6 |
7 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/demo-nodejs-api/webapp/controller/App.controller.js:
--------------------------------------------------------------------------------
1 | sap.ui.define([
2 | "./BaseController",
3 | "sap/ui/model/json/JSONModel"
4 | ], function (BaseController, JSONModel) {
5 | "use strict";
6 |
7 | return BaseController.extend("ui5con20.demo.nodejs.controller.App", {
8 |
9 | onInit : function () {
10 | var oViewModel,
11 | fnSetAppNotBusy,
12 | iOriginalBusyDelay = this.getView().getBusyIndicatorDelay();
13 |
14 | oViewModel = new JSONModel({
15 | busy : true,
16 | delay : 0
17 | });
18 | this.setModel(oViewModel, "appView");
19 |
20 | fnSetAppNotBusy = function() {
21 | oViewModel.setProperty("/busy", false);
22 | oViewModel.setProperty("/delay", iOriginalBusyDelay);
23 | };
24 |
25 | this.getOwnerComponent().getDataLoaded().then(fnSetAppNotBusy, fnSetAppNotBusy);
26 |
27 | // apply content density mode to root view
28 | this.getView().addStyleClass(this.getOwnerComponent().getContentDensityClass());
29 | }
30 | });
31 |
32 | });
33 |
--------------------------------------------------------------------------------
/demo-project/packages/app/webapp/controller/App.controller.js:
--------------------------------------------------------------------------------
1 | sap.ui.define([
2 | "./BaseController",
3 | "sap/ui/model/json/JSONModel"
4 | ], function (BaseController, JSONModel) {
5 | "use strict";
6 |
7 | return BaseController.extend("ui5con20.app.controller.App", {
8 |
9 | onInit : function () {
10 | var oViewModel,
11 | fnSetAppNotBusy,
12 | iOriginalBusyDelay = this.getView().getBusyIndicatorDelay();
13 |
14 | oViewModel = new JSONModel({
15 | busy : true,
16 | delay : 0
17 | });
18 | this.setModel(oViewModel, "appView");
19 |
20 | fnSetAppNotBusy = function() {
21 | oViewModel.setProperty("/busy", false);
22 | oViewModel.setProperty("/delay", iOriginalBusyDelay);
23 | };
24 |
25 | this.getOwnerComponent().getDataLoaded().then(fnSetAppNotBusy, fnSetAppNotBusy);
26 |
27 | // apply content density mode to root view
28 | this.getView().addStyleClass(this.getOwnerComponent().getContentDensityClass());
29 | }
30 | });
31 |
32 | });
33 |
--------------------------------------------------------------------------------
/demo-project/packages/library/test/ui5con20/library/qunit/Chart.qunit.js:
--------------------------------------------------------------------------------
1 | /* global QUnit */
2 | QUnit.config.autostart = false;
3 |
4 | sap.ui.getCore().attachInit(function () {
5 | "use strict";
6 |
7 | sap.ui.require(["ui5con20/library/Chart"], function(Chart) {
8 |
9 | QUnit.test("Chart", function(assert) {
10 | var oChart = new Chart({
11 | data: {
12 | series: [
13 | [ { value: 1 }, { value: 2 } ],
14 | [ { value: 3 }, { value: 4 } ]
15 | ]
16 | },
17 | options: {
18 | fullWidth: true,
19 | chartPadding: {
20 | top: 20,
21 | right: 50,
22 | bottom: 30,
23 | left: 10
24 | },
25 | showArea: true,
26 | showPoint: true,
27 | axisX: {
28 | labelOffset: {
29 | x: -10,
30 | y: 10
31 | }
32 | },
33 | axisY: {
34 | onlyInteger: true
35 | }
36 | }
37 | });
38 |
39 | oChart.placeAt("qunit-fixture");
40 |
41 | assert.ok(true);
42 | })
43 |
44 | QUnit.start();
45 | });
46 | });
47 |
--------------------------------------------------------------------------------
/demo-project/packages/app/webapp/test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Testing Overview
5 |
6 |
7 |
8 |
9 | Testing Overview
10 | This is an overview page of various ways to test the generated app during development. Choose one of the access points below to launch the app as a standalone application.
11 |
12 |
18 |
19 |
--------------------------------------------------------------------------------
/demo-project/packages/app/webapp/test/unit/unitTests.qunit.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Unit tests for UI5con 2020 Demo App
6 |
7 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/demo-project/packages/app/webapp/view/Object.view.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
14 |
15 |
16 |
19 |
20 |
21 |
22 |
28 |
34 |
35 |
36 |
37 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/demo-project/packages/app/webapp/test/unit/model/formatter.js:
--------------------------------------------------------------------------------
1 | /*global QUnit*/
2 |
3 | sap.ui.define([
4 | "ui5con20/app/model/formatter"
5 | ], function (formatter) {
6 | "use strict";
7 |
8 | QUnit.module("Number unit");
9 |
10 | function numberUnitValueTestCase(assert, sValue, fExpectedNumber) {
11 | // Act
12 | var fNumber = formatter.numberUnit(sValue);
13 |
14 | // Assert
15 | assert.strictEqual(fNumber, fExpectedNumber, "The rounding was correct");
16 | }
17 |
18 | QUnit.test("Should round down a 3 digit number", function (assert) {
19 | numberUnitValueTestCase.call(this, assert, "3.123", "3.12");
20 | });
21 |
22 | QUnit.test("Should round up a 3 digit number", function (assert) {
23 | numberUnitValueTestCase.call(this, assert, "3.128", "3.13");
24 | });
25 |
26 | QUnit.test("Should round a negative number", function (assert) {
27 | numberUnitValueTestCase.call(this, assert, "-3", "-3.00");
28 | });
29 |
30 | QUnit.test("Should round an empty string", function (assert) {
31 | numberUnitValueTestCase.call(this, assert, "", "");
32 | });
33 |
34 | QUnit.test("Should round a zero", function (assert) {
35 | numberUnitValueTestCase.call(this, assert, "0", "0.00");
36 | });
37 |
38 | });
39 |
--------------------------------------------------------------------------------
/demo-nodejs-api/webapp/view/Object.view.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
14 |
15 |
16 |
19 |
20 |
21 |
22 |
28 |
34 |
35 |
36 |
37 |
38 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/demo-project/packages/app/webapp/test/integration/ObjectJourney.js:
--------------------------------------------------------------------------------
1 | /*global QUnit*/
2 |
3 | sap.ui.define([
4 | "sap/ui/test/opaQunit",
5 | "./pages/Worklist",
6 | "./pages/Browser",
7 | "./pages/Object",
8 | "./pages/App"
9 | ], function (opaTest) {
10 | "use strict";
11 |
12 | QUnit.module("Object");
13 |
14 | opaTest("Should remember the first item", function (Given, When, Then) {
15 | // Arrangements
16 | Given.iStartMyApp();
17 |
18 | //Actions
19 | When.onTheWorklistPage.iRememberTheItemAtPosition(1);
20 |
21 | // Assertions
22 | Then.onTheWorklistPage.theTitleShouldDisplayTheTotalAmountOfItems();
23 |
24 | // Cleanup
25 | Then.iTeardownMyApp();
26 | });
27 |
28 | opaTest("Should start the app with remembered item", function (Given, When, Then) {
29 | // Arrangements
30 | Given.iRestartTheAppWithTheRememberedItem({
31 | delay: 1000,
32 | autoWait: false
33 | });
34 |
35 | //Actions
36 | When.onTheAppPage.iWaitUntilTheAppBusyIndicatorIsGone();
37 |
38 | // Assertions
39 | Then.onTheObjectPage.iShouldSeeTheObjectViewsBusyIndicator().
40 | and.theObjectViewsBusyIndicatorDelayIsRestored().
41 | and.iShouldSeeTheRememberedObject().
42 | and.theObjectViewShouldContainOnlyFormattedUnitNumbers();
43 |
44 | // Cleanup
45 | Then.iTeardownMyApp();
46 | });
47 |
48 |
49 | });
--------------------------------------------------------------------------------
/demo-project/packages/app/webapp/test/integration/pages/shareOptions.js:
--------------------------------------------------------------------------------
1 | sap.ui.define([
2 | "sap/ui/test/Opa5",
3 | "sap/ui/test/actions/Press",
4 | "sap/ui/test/matchers/PropertyStrictEquals"
5 | ], function(Opa5, Press, PropertyStrictEquals) {
6 | "use strict";
7 |
8 | return {
9 |
10 | createActions : function (sViewName) {
11 | return {
12 | iPressOnTheShareButton : function () {
13 | return this.waitFor({
14 | controlType : "sap.m.Button",
15 | viewName : sViewName,
16 | matchers : new PropertyStrictEquals({
17 | name : "icon",
18 | value : "sap-icon://action"
19 | }),
20 | actions : new Press(),
21 | errorMessage : "Did not find the share button"
22 | });
23 | }
24 | };
25 | },
26 |
27 | createAssertions : function (sViewName) {
28 | return {
29 |
30 | iShouldSeeTheShareEmailButton : function () {
31 | return this.waitFor({
32 | viewName : sViewName,
33 | id : "shareEmail-button",
34 | matchers : new PropertyStrictEquals({
35 | name : "icon",
36 | value : "sap-icon://email"
37 | }),
38 | success : function () {
39 | Opa5.assert.ok(true, "The E-Mail button is visible");
40 | },
41 | errorMessage : "The E-Mail button was not found"
42 | });
43 | }
44 |
45 | };
46 |
47 | }
48 |
49 | };
50 |
51 | });
--------------------------------------------------------------------------------
/demo-project/packages/server/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "node": true
4 | },
5 | "parserOptions": {
6 | "ecmaVersion": 2017
7 | },
8 | "rules": {
9 | "block-scoped-var": 1,
10 | "brace-style": [2, "1tbs", { "allowSingleLine": true }],
11 | "consistent-this": 2,
12 | "no-div-regex": 2,
13 | "no-floating-decimal": 2,
14 | "no-self-compare": 2,
15 | "no-mixed-spaces-and-tabs": [2, true],
16 | "no-nested-ternary": 2,
17 | "no-unused-vars": [2, {"vars":"all", "args":"none"}],
18 | "radix": 2,
19 | "keyword-spacing": 2,
20 | "space-unary-ops": 2,
21 | "wrap-iife": [2, "any"],
22 |
23 | "camelcase": 1,
24 | "consistent-return": 1,
25 | "max-nested-callbacks": [1, 3],
26 | "new-cap": 1,
27 | "no-extra-boolean-cast": 1,
28 | "no-lonely-if": 1,
29 | "no-new": 1,
30 | "no-new-wrappers": 1,
31 | "no-redeclare": 1,
32 | "no-unused-expressions": 1,
33 | "no-use-before-define": [1, "nofunc"],
34 | "no-warning-comments": 1,
35 | "valid-jsdoc": [1, {
36 | "requireReturn": false
37 | }],
38 | "default-case": 1,
39 |
40 | "dot-notation": 0,
41 | "eol-last": 0,
42 | "eqeqeq": 0,
43 | "no-trailing-spaces": 0,
44 | "no-underscore-dangle": 0,
45 | "quotes": 0,
46 | "key-spacing": 0,
47 | "comma-spacing": 0,
48 | "no-multi-spaces": 0,
49 | "no-shadow": 0,
50 | "no-irregular-whitespace": 0
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/demo-project/packages/app/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true
4 | },
5 | "globals": {
6 | "sap": true,
7 | "jQuery": true
8 | },
9 | "rules": {
10 | "block-scoped-var": 1,
11 | "brace-style": [2, "1tbs", { "allowSingleLine": true }],
12 | "consistent-this": 2,
13 | "no-div-regex": 2,
14 | "no-floating-decimal": 2,
15 | "no-self-compare": 2,
16 | "no-mixed-spaces-and-tabs": [2, true],
17 | "no-nested-ternary": 2,
18 | "no-unused-vars": [2, {"vars":"all", "args":"none"}],
19 | "radix": 2,
20 | "keyword-spacing": 2,
21 | "space-unary-ops": 2,
22 | "wrap-iife": [2, "any"],
23 |
24 | "camelcase": 1,
25 | "consistent-return": 1,
26 | "max-nested-callbacks": [1, 3],
27 | "new-cap": 1,
28 | "no-extra-boolean-cast": 1,
29 | "no-lonely-if": 1,
30 | "no-new": 1,
31 | "no-new-wrappers": 1,
32 | "no-redeclare": 1,
33 | "no-unused-expressions": 1,
34 | "no-use-before-define": [1, "nofunc"],
35 | "no-warning-comments": 1,
36 | "strict": 1,
37 | "valid-jsdoc": [1, {
38 | "requireReturn": false
39 | }],
40 | "default-case": 1,
41 |
42 | "dot-notation": 0,
43 | "eol-last": 0,
44 | "eqeqeq": 0,
45 | "no-trailing-spaces": 0,
46 | "no-underscore-dangle": 0,
47 | "quotes": 0,
48 | "key-spacing": 0,
49 | "comma-spacing": 0,
50 | "no-multi-spaces": 0,
51 | "no-shadow": 0,
52 | "no-irregular-whitespace": 0
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/demo-project/packages/app/webapp/test/integration/pages/Browser.js:
--------------------------------------------------------------------------------
1 | sap.ui.define([
2 | "sap/ui/test/Opa5",
3 | "./Common"
4 | ], function(Opa5, Common) {
5 | "use strict";
6 |
7 | Opa5.createPageObjects({
8 | onTheBrowser : {
9 | baseClass : Common,
10 |
11 | actions : {
12 |
13 | iPressOnTheBackwardsButton : function () {
14 | return this.waitFor({
15 | success : function () {
16 | // manipulate history directly for testing purposes
17 | Opa5.getWindow().history.back();
18 | }
19 | });
20 | },
21 |
22 | iPressOnTheForwardsButton : function () {
23 | return this.waitFor({
24 | success : function () {
25 | // manipulate history directly for testing purposes
26 | Opa5.getWindow().history.forward();
27 | }
28 | });
29 | },
30 |
31 | iChangeTheHashToSomethingInvalid : function () {
32 | return this.waitFor({
33 | success : function () {
34 | Opa5.getHashChanger().setHash("somethingInvalid");
35 | }
36 | });
37 | },
38 |
39 | iChangeTheHashToTheRememberedItem : function () {
40 | return this.waitFor({
41 | success : function () {
42 | var sObjectId = this.getContext().currentItem.id;
43 | Opa5.getHashChanger().setHash("Pony/" + sObjectId);
44 | }
45 | });
46 | }
47 | },
48 |
49 | assertions: {}
50 | }
51 |
52 | });
53 | });
--------------------------------------------------------------------------------
/demo-project/packages/library/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true
4 | },
5 | "globals": {
6 | "sap": true,
7 | "jQuery": true
8 | },
9 | "rules": {
10 | "block-scoped-var": 1,
11 | "brace-style": [2, "1tbs", { "allowSingleLine": true }],
12 | "consistent-this": 2,
13 | "no-div-regex": 2,
14 | "no-floating-decimal": 2,
15 | "no-self-compare": 2,
16 | "no-mixed-spaces-and-tabs": [2, true],
17 | "no-nested-ternary": 2,
18 | "no-unused-vars": [2, {"vars":"all", "args":"none"}],
19 | "radix": 2,
20 | "keyword-spacing": 2,
21 | "space-unary-ops": 2,
22 | "wrap-iife": [2, "any"],
23 |
24 | "camelcase": 1,
25 | "consistent-return": 1,
26 | "max-nested-callbacks": [1, 3],
27 | "new-cap": 1,
28 | "no-extra-boolean-cast": 1,
29 | "no-lonely-if": 1,
30 | "no-new": 1,
31 | "no-new-wrappers": 1,
32 | "no-redeclare": 1,
33 | "no-unused-expressions": 1,
34 | "no-use-before-define": [1, "nofunc"],
35 | "no-warning-comments": 1,
36 | "strict": 1,
37 | "valid-jsdoc": [1, {
38 | "requireReturn": false
39 | }],
40 | "default-case": 1,
41 |
42 | "dot-notation": 0,
43 | "eol-last": 0,
44 | "eqeqeq": 0,
45 | "no-trailing-spaces": 0,
46 | "no-underscore-dangle": 0,
47 | "quotes": 0,
48 | "key-spacing": 0,
49 | "comma-spacing": 0,
50 | "no-multi-spaces": 0,
51 | "no-shadow": 0,
52 | "no-irregular-whitespace": 0
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/demo-nodejs-api/createPreload.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const {resourceFactory} = require("@ui5/fs");
3 | const {FileSystem, Memory} = require("@ui5/fs").adapters;
4 | const {generateComponentPreload} = require("@ui5/builder").tasks;
5 | const {normalizer} = require("@ui5/project");
6 |
7 | async function getCoreDependencyReader() {
8 | const coreTree = await normalizer.generateProjectTree({
9 | cwd: path.dirname(require.resolve("@openui5/sap.ui.core/package.json"))
10 | });
11 | return (await resourceFactory.createCollectionsForTree(coreTree)).source;
12 | }
13 |
14 | async function main() {
15 | const projectName = "UI5con Node.js API Demo";
16 |
17 | const sources = new FileSystem({
18 | virBasePath: "/resources/ui5con20/demo/nodejs/",
19 | fsBasePath: "./webapp",
20 | });
21 |
22 | const dependencies = await getCoreDependencyReader();
23 |
24 | const target = new FileSystem({
25 | virBasePath: "/resources/ui5con20/demo/nodejs/",
26 | fsBasePath: "./dist"
27 | });
28 |
29 | const workspace = resourceFactory.createWorkspace({
30 | virBasePath: "/",
31 | writer: target,
32 | reader: sources,
33 | name: projectName
34 | });
35 |
36 | await generateComponentPreload({
37 | workspace,
38 | dependencies,
39 | options: {
40 | projectName,
41 | namespaces: [
42 | "ui5con20/demo/nodejs"
43 | ]
44 | }
45 | });
46 | }
47 |
48 | main().catch((err) => {
49 | console.log(err);
50 | process.exit(1);
51 | });
52 |
--------------------------------------------------------------------------------
/demo-project/packages/app/webapp/controller/BaseController.js:
--------------------------------------------------------------------------------
1 | sap.ui.define([
2 | "sap/ui/core/mvc/Controller",
3 | "sap/ui/core/UIComponent"
4 | ], function (Controller, UIComponent) {
5 | "use strict";
6 |
7 | return Controller.extend("ui5con20.app.controller.BaseController", {
8 | /**
9 | * Convenience method for accessing the router.
10 | * @public
11 | * @returns {sap.ui.core.routing.Router} the router for this component
12 | */
13 | getRouter : function () {
14 | return UIComponent.getRouterFor(this);
15 | },
16 |
17 | /**
18 | * Convenience method for getting the view model by name.
19 | * @public
20 | * @param {string} [sName] the model name
21 | * @returns {sap.ui.model.Model} the model instance
22 | */
23 | getModel : function (sName) {
24 | return this.getView().getModel(sName);
25 | },
26 |
27 | /**
28 | * Convenience method for setting the view model.
29 | * @public
30 | * @param {sap.ui.model.Model} oModel the model instance
31 | * @param {string} sName the model name
32 | * @returns {sap.ui.mvc.View} the view instance
33 | */
34 | setModel : function (oModel, sName) {
35 | return this.getView().setModel(oModel, sName);
36 | },
37 |
38 | /**
39 | * Getter for the resource bundle.
40 | * @public
41 | * @returns {sap.ui.model.resource.ResourceModel} the resourceModel of the component
42 | */
43 | getResourceBundle : function () {
44 | return this.getOwnerComponent().getModel("i18n").getResourceBundle();
45 | }
46 |
47 | });
48 |
49 | });
50 |
--------------------------------------------------------------------------------
/demo-project/packages/app/webapp/test/integration/pages/Common.js:
--------------------------------------------------------------------------------
1 | sap.ui.define([
2 | "sap/ui/test/Opa5"
3 | ], function(Opa5) {
4 | "use strict";
5 |
6 | return Opa5.extend("ui5con20.app.test.integration.pages.Common", {
7 |
8 | createAWaitForAnEntitySet : function (oOptions) {
9 | return {
10 | success: function () {
11 | var aEntitySet;
12 |
13 | var oMockServerInitialized = this.getMockServer().then(function (oMockServer) {
14 | aEntitySet = oMockServer.getEntitySetData(oOptions.entitySet);
15 | });
16 |
17 | this.iWaitForPromise(oMockServerInitialized);
18 | return this.waitFor({
19 | success : function () {
20 | oOptions.success.call(this, aEntitySet);
21 | }
22 | });
23 | }
24 | };
25 | },
26 |
27 | theUnitNumbersShouldHaveTwoDecimals : function (sControlType, sViewName, sSuccessMsg, sErrMsg) {
28 | var rTwoDecimalPlaces = /^-?\d+\.\d{2}$/;
29 |
30 | return this.waitFor({
31 | controlType : sControlType,
32 | viewName : sViewName,
33 | success : function (aNumberControls) {
34 | Opa5.assert.ok(aNumberControls.every(function(oNumberControl){
35 | return rTwoDecimalPlaces.test(oNumberControl.getNumber());
36 | }),
37 | sSuccessMsg);
38 | },
39 | errorMessage : sErrMsg
40 | });
41 | },
42 |
43 | getMockServer : function () {
44 | return new Promise(function (success) {
45 | Opa5.getWindow().sap.ui.require(["ui5con20/app/localService/mockserver"], function (mockserver) {
46 | success(mockserver.getMockServer());
47 | });
48 | });
49 | }
50 |
51 | });
52 |
53 | });
54 |
--------------------------------------------------------------------------------
/demo-project/packages/app/webapp/controller/ErrorHandler.js:
--------------------------------------------------------------------------------
1 | sap.ui.define([
2 | "sap/ui/base/Object",
3 | "sap/m/MessageBox"
4 | ], function (UI5Object, MessageBox) {
5 | "use strict";
6 |
7 | return UI5Object.extend("ui5con20.app.controller.ErrorHandler", {
8 |
9 | /**
10 | * Handles application errors by automatically attaching to the model events and displaying errors when needed.
11 | * @class
12 | * @param {sap.ui.core.UIComponent} oComponent reference to the app's component
13 | * @public
14 | * @alias ui5con20.app.controller.ErrorHandler
15 | */
16 | constructor : function (oComponent) {
17 | this._oResourceBundle = oComponent.getModel("i18n").getResourceBundle();
18 | this._oComponent = oComponent;
19 | this._oModel = oComponent.getModel();
20 | this._bMessageOpen = false;
21 | this._sErrorText = this._oResourceBundle.getText("errorText");
22 | },
23 |
24 | /**
25 | * Shows a {@link sap.m.MessageBox} when a service call has failed.
26 | * Only the first error message will be display.
27 | * @param {string} sDetails a technical error to be displayed on request
28 | * @private
29 | */
30 | _showServiceError : function (sDetails) {
31 | if (this._bMessageOpen) {
32 | return;
33 | }
34 | this._bMessageOpen = true;
35 | MessageBox.error(
36 | this._sErrorText,
37 | {
38 | id : "serviceErrorMessageBox",
39 | details: sDetails,
40 | styleClass: this._oComponent.getContentDensityClass(),
41 | actions: [MessageBox.Action.CLOSE],
42 | onClose: function () {
43 | this._bMessageOpen = false;
44 | }.bind(this)
45 | }
46 | );
47 | }
48 | });
49 | });
--------------------------------------------------------------------------------
/demo-nodejs-api/webapp/controller/ErrorHandler.js:
--------------------------------------------------------------------------------
1 | sap.ui.define([
2 | "sap/ui/base/Object",
3 | "sap/m/MessageBox"
4 | ], function (UI5Object, MessageBox) {
5 | "use strict";
6 |
7 | return UI5Object.extend("ui5con20.demo.nodejs.controller.ErrorHandler", {
8 |
9 | /**
10 | * Handles application errors by automatically attaching to the model events and displaying errors when needed.
11 | * @class
12 | * @param {sap.ui.core.UIComponent} oComponent reference to the app's component
13 | * @public
14 | * @alias ui5con20.demo.nodejs.controller.ErrorHandler
15 | */
16 | constructor : function (oComponent) {
17 | this._oResourceBundle = oComponent.getModel("i18n").getResourceBundle();
18 | this._oComponent = oComponent;
19 | this._oModel = oComponent.getModel();
20 | this._bMessageOpen = false;
21 | this._sErrorText = this._oResourceBundle.getText("errorText");
22 | },
23 |
24 | /**
25 | * Shows a {@link sap.m.MessageBox} when a service call has failed.
26 | * Only the first error message will be display.
27 | * @param {string} sDetails a technical error to be displayed on request
28 | * @private
29 | */
30 | _showServiceError : function (sDetails) {
31 | if (this._bMessageOpen) {
32 | return;
33 | }
34 | this._bMessageOpen = true;
35 | MessageBox.error(
36 | this._sErrorText,
37 | {
38 | id : "serviceErrorMessageBox",
39 | details: sDetails,
40 | styleClass: this._oComponent.getContentDensityClass(),
41 | actions: [MessageBox.Action.CLOSE],
42 | onClose: function () {
43 | this._bMessageOpen = false;
44 | }.bind(this)
45 | }
46 | );
47 | }
48 | });
49 | });
--------------------------------------------------------------------------------
/demo-project/packages/app/webapp/test/unit/model/models.js:
--------------------------------------------------------------------------------
1 | /*global QUnit*/
2 |
3 | sap.ui.define([
4 | "ui5con20/app/model/models",
5 | "sap/ui/Device"
6 | ], function (models, Device) {
7 | "use strict";
8 |
9 | QUnit.module("createDeviceModel", {
10 | afterEach : function () {
11 | this.oDeviceModel.destroy();
12 | }
13 | });
14 |
15 | function isPhoneTestCase(assert, bIsPhone) {
16 | // Arrange
17 | this.stub(Device, "system", { phone : bIsPhone });
18 |
19 | // System under test
20 | this.oDeviceModel = models.createDeviceModel();
21 |
22 | // Assert
23 | assert.strictEqual(this.oDeviceModel.getData().system.phone, bIsPhone, "IsPhone property is correct");
24 | }
25 |
26 | QUnit.test("Should initialize a device model for desktop", function (assert) {
27 | isPhoneTestCase.call(this, assert, false);
28 | });
29 |
30 | QUnit.test("Should initialize a device model for phone", function (assert) {
31 | isPhoneTestCase.call(this, assert, true);
32 | });
33 |
34 | function isTouchTestCase(assert, bIsTouch) {
35 | // Arrange
36 | this.stub(Device, "support", { touch : bIsTouch });
37 |
38 | // System under test
39 | this.oDeviceModel = models.createDeviceModel();
40 |
41 | // Assert
42 | assert.strictEqual(this.oDeviceModel.getData().support.touch, bIsTouch, "IsTouch property is correct");
43 | }
44 |
45 | QUnit.test("Should initialize a device model for non touch devices", function (assert) {
46 | isTouchTestCase.call(this, assert, false);
47 | });
48 |
49 | QUnit.test("Should initialize a device model for touch devices", function (assert) {
50 | isTouchTestCase.call(this, assert, true);
51 | });
52 |
53 | QUnit.test("The binding mode of the device model should be one way", function (assert) {
54 |
55 | // System under test
56 | this.oDeviceModel = models.createDeviceModel();
57 |
58 | // Assert
59 | assert.strictEqual(this.oDeviceModel.getDefaultBindingMode(), "OneWay", "Binding mode is correct");
60 | });
61 |
62 | });
63 |
--------------------------------------------------------------------------------
/demo-nodejs-api/webapp/controller/BaseController.js:
--------------------------------------------------------------------------------
1 | sap.ui.define([
2 | "sap/ui/core/mvc/Controller",
3 | "sap/ui/core/UIComponent",
4 | "sap/m/library"
5 | ], function (Controller, UIComponent, mobileLibrary) {
6 | "use strict";
7 |
8 | // shortcut for sap.m.URLHelper
9 | var URLHelper = mobileLibrary.URLHelper;
10 |
11 | return Controller.extend("ui5con20.demo.nodejs.controller.BaseController", {
12 | /**
13 | * Convenience method for accessing the router.
14 | * @public
15 | * @returns {sap.ui.core.routing.Router} the router for this component
16 | */
17 | getRouter : function () {
18 | return UIComponent.getRouterFor(this);
19 | },
20 |
21 | /**
22 | * Convenience method for getting the view model by name.
23 | * @public
24 | * @param {string} [sName] the model name
25 | * @returns {sap.ui.model.Model} the model instance
26 | */
27 | getModel : function (sName) {
28 | return this.getView().getModel(sName);
29 | },
30 |
31 | /**
32 | * Convenience method for setting the view model.
33 | * @public
34 | * @param {sap.ui.model.Model} oModel the model instance
35 | * @param {string} sName the model name
36 | * @returns {sap.ui.mvc.View} the view instance
37 | */
38 | setModel : function (oModel, sName) {
39 | return this.getView().setModel(oModel, sName);
40 | },
41 |
42 | /**
43 | * Getter for the resource bundle.
44 | * @public
45 | * @returns {sap.ui.model.resource.ResourceModel} the resourceModel of the component
46 | */
47 | getResourceBundle : function () {
48 | return this.getOwnerComponent().getModel("i18n").getResourceBundle();
49 | },
50 |
51 | /**
52 | * Event handler when the share by E-Mail button has been clicked
53 | * @public
54 | */
55 | onShareEmailPress : function () {
56 | var oViewModel = (this.getModel("objectView") || this.getModel("worklistView"));
57 | URLHelper.triggerEmail(
58 | null,
59 | oViewModel.getProperty("/shareSendEmailSubject"),
60 | oViewModel.getProperty("/shareSendEmailMessage")
61 | );
62 | } });
63 |
64 | });
--------------------------------------------------------------------------------
/demo-project/packages/app/webapp/test/integration/pages/App.js:
--------------------------------------------------------------------------------
1 | sap.ui.define([
2 | "sap/ui/test/Opa5",
3 | "sap/ui/test/matchers/PropertyStrictEquals",
4 | "./Common"
5 | ], function(Opa5, PropertyStrictEquals, Common) {
6 | "use strict";
7 |
8 | var sViewName = "App",
9 | sAppId = "app";
10 |
11 | Opa5.createPageObjects({
12 | onTheAppPage : {
13 | baseClass : Common,
14 |
15 | actions : {
16 |
17 | iWaitUntilTheAppBusyIndicatorIsGone : function () {
18 | return this.waitFor({
19 | id : sAppId,
20 | viewName : sViewName,
21 | matchers: new PropertyStrictEquals({
22 | name: "busy",
23 | value: false
24 | }),
25 | autoWait: false,
26 | success : function () {
27 | Opa5.assert.ok(true, "The app is not busy");
28 | },
29 | errorMessage : "The app is busy"
30 | });
31 | },
32 | iCloseTheMessageBox : function () {
33 | return this.waitFor({
34 | id : "serviceErrorMessageBox",
35 | autoWait: false,
36 | success : function (oMessageBox) {
37 | oMessageBox.close();
38 | Opa5.assert.ok(true, "The MessageBox was closed");
39 | }
40 | });
41 | }
42 | },
43 |
44 | assertions : {
45 |
46 | iShouldSeeTheBusyIndicatorForTheWholeApp : function () {
47 | return this.waitFor({
48 | id : sAppId,
49 | viewName : sViewName,
50 | matchers : new PropertyStrictEquals({
51 | name : "busy",
52 | value : true
53 | }),
54 | autoWait: false,
55 | success : function () {
56 | // we set the view busy, so we need to query the parent of the app
57 | Opa5.assert.ok(true, "The app is busy");
58 | },
59 | errorMessage : "The App is not busy"
60 | });
61 | },
62 | iShouldSeeTheMessageBox : function () {
63 | return this.waitFor({
64 | id : "serviceErrorMessageBox",
65 | autoWait: false,
66 | success : function () {
67 | Opa5.assert.ok(true, "The correct MessageBox was shown");
68 | }
69 | });
70 | }
71 | }
72 | }
73 | });
74 | });
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 | *.lcov
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (https://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Dependency directories
41 | node_modules/
42 | jspm_packages/
43 |
44 | # TypeScript v1 declaration files
45 | typings/
46 |
47 | # TypeScript cache
48 | *.tsbuildinfo
49 |
50 | # Optional npm cache directory
51 | .npm
52 |
53 | # Optional eslint cache
54 | .eslintcache
55 |
56 | # Microbundle cache
57 | .rpt2_cache/
58 | .rts2_cache_cjs/
59 | .rts2_cache_es/
60 | .rts2_cache_umd/
61 |
62 | # Optional REPL history
63 | .node_repl_history
64 |
65 | # Output of 'npm pack'
66 | *.tgz
67 |
68 | # Yarn Integrity file
69 | .yarn-integrity
70 |
71 | # dotenv environment variables file
72 | .env
73 | .env.test
74 |
75 | # parcel-bundler cache (https://parceljs.org/)
76 | .cache
77 |
78 | # Next.js build output
79 | .next
80 |
81 | # Nuxt.js build / generate output
82 | .nuxt
83 | dist
84 |
85 | # Gatsby files
86 | .cache/
87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js
88 | # https://nextjs.org/blog/next-9-1#public-directory-support
89 | # public
90 |
91 | # vuepress build output
92 | .vuepress/dist
93 |
94 | # Serverless directories
95 | .serverless/
96 |
97 | # FuseBox cache
98 | .fusebox/
99 |
100 | # DynamoDB Local files
101 | .dynamodb/
102 |
103 | # TernJS port file
104 | .tern-port
105 |
106 | .DS_Store
107 |
--------------------------------------------------------------------------------
/demo-project/packages/app/webapp/test/integration/WorklistJourney.js:
--------------------------------------------------------------------------------
1 | /*global QUnit*/
2 |
3 | sap.ui.define([
4 | "sap/ui/test/opaQunit",
5 | "sap/ui/Device",
6 | "./pages/Worklist",
7 | "./pages/App"
8 | ], function (opaTest, Device) {
9 | "use strict";
10 |
11 | var iDelay = (Device.browser.msie || Device.browser.edge) ? 1500 : 1000;
12 |
13 | QUnit.module("Worklist");
14 |
15 | opaTest("Should see the table with all entries", function (Given, When, Then) {
16 | // Arrangements
17 | Given.iStartMyApp();
18 |
19 | // Assertions
20 | Then.onTheWorklistPage.theTableShouldHaveAllEntries().
21 | and.theTableShouldContainOnlyFormattedUnitNumbers().
22 | and.theTitleShouldDisplayTheTotalAmountOfItems();
23 | });
24 |
25 | opaTest("Search for the First object should deliver results that contain the firstObject in the name", function (Given, When, Then) {
26 | //Actions
27 | When.onTheWorklistPage.iSearchForTheFirstObject();
28 |
29 | // Assertions
30 | Then.onTheWorklistPage.theTableShowsOnlyObjectsWithTheSearchStringInTheirTitle();
31 | });
32 |
33 | opaTest("Entering something that cannot be found into search field and pressing search field's refresh should leave the list as it was", function (Given, When, Then) {
34 | //Actions
35 | When.onTheWorklistPage.iTypeSomethingInTheSearchThatCannotBeFoundAndTriggerRefresh();
36 |
37 | // Assertions
38 | Then.onTheWorklistPage.theTableHasEntries();
39 |
40 | // Cleanup
41 | Then.iTeardownMyApp();
42 | });
43 |
44 |
45 | opaTest("Should see the busy indicator on app view while worklist view metadata is loaded", function (Given, When, Then) {
46 | // Arrangements
47 | Given.iStartMyApp({
48 | delay: iDelay,
49 | autoWait: false
50 | });
51 |
52 | // Assertions
53 | Then.onTheAppPage.iShouldSeeTheBusyIndicatorForTheWholeApp();
54 | });
55 |
56 | opaTest("Should see the busy indicator on worklist table after metadata is loaded", function (Given, When, Then) {
57 | //Actions
58 | When.onTheAppPage.iWaitUntilTheAppBusyIndicatorIsGone();
59 |
60 | // Assertions
61 | Then.onTheWorklistPage.iShouldSeeTheWorklistTableBusyIndicator();
62 |
63 | // Cleanup
64 | Then.iTeardownMyApp();
65 | });
66 |
67 | });
--------------------------------------------------------------------------------
/demo-project/packages/app/webapp/test/integration/arrangements/Startup.js:
--------------------------------------------------------------------------------
1 | sap.ui.define([
2 | "sap/ui/test/Opa5",
3 | "ui5con20/app/localService/mockserver",
4 | "sap/ui/model/odata/v2/ODataModel"
5 | ], function(Opa5, mockserver, ODataModel) {
6 | "use strict";
7 |
8 | return Opa5.extend("ui5con20.app.test.integration.arrangements.Startup", {
9 |
10 | /**
11 | * Initializes mock server, then starts the app component
12 | * @param {object} oOptionsParameter An object that contains the configuration for starting up the app
13 | * @param {integer} oOptionsParameter.delay A custom delay to start the app with
14 | * @param {string} [oOptionsParameter.hash] The in-app hash can also be passed separately for better readability in tests
15 | * @param {boolean} [oOptionsParameter.autoWait=true] Automatically wait for pending requests while the application is starting up
16 | */
17 | iStartMyApp : function (oOptionsParameter) {
18 | var oOptions = oOptionsParameter || {};
19 |
20 | this._clearSharedData();
21 |
22 | // start the app with a minimal delay to make tests fast but still async to discover basic timing issues
23 | oOptions.delay = oOptions.delay || 1;
24 |
25 | // configure mock server with the current options
26 | var oMockServerInitialized = mockserver.init(oOptions);
27 |
28 | this.iWaitForPromise(oMockServerInitialized);
29 | // start the app UI component
30 | this.iStartMyUIComponent({
31 | componentConfig: {
32 | name: "ui5con20.app",
33 | async: true
34 | },
35 | hash: oOptions.hash,
36 | autoWait: oOptions.autoWait
37 | });
38 | },
39 | iRestartTheAppWithTheRememberedItem : function (oOptions) {
40 | var sObjectId;
41 | this.waitFor({
42 | success : function () {
43 | sObjectId = this.getContext().currentItem.id;
44 | }
45 | });
46 |
47 | this.waitFor({
48 | success : function() {
49 | oOptions.hash = "Pony/" + sObjectId;
50 | this.iStartMyApp(oOptions);
51 | }
52 | });
53 | },
54 | _clearSharedData: function () {
55 | // clear shared metadata in ODataModel to allow tests for loading the metadata
56 | ODataModel.mSharedData = { server: {}, service: {}, meta: {} };
57 | }
58 | });
59 | });
--------------------------------------------------------------------------------
/demo-project/packages/app/webapp/test/integration/NotFoundJourney.js:
--------------------------------------------------------------------------------
1 | /*global QUnit*/
2 |
3 | sap.ui.define([
4 | "sap/ui/test/opaQunit",
5 | "./pages/Worklist",
6 | "./pages/Browser",
7 | "./pages/NotFound",
8 | "./pages/App"
9 | ], function (opaTest) {
10 | "use strict";
11 |
12 | QUnit.module("NotFound");
13 |
14 | opaTest("Should see the resource not found page when changing to an invalid hash", function (Given, When, Then) {
15 | //Arrangement
16 | Given.iStartMyApp();
17 |
18 | //Actions
19 | When.onTheBrowser.iChangeTheHashToSomethingInvalid();
20 |
21 | // Assertions
22 | Then.onTheNotFoundPage.iShouldSeeResourceNotFound();
23 | });
24 |
25 | opaTest("Clicking the 'Show my worklist' link on the 'Resource not found' page should bring me back to the worklist", function (Given, When, Then) {
26 | //Actions
27 | When.onTheAppPage.iWaitUntilTheAppBusyIndicatorIsGone();
28 | When.onTheNotFoundPage.iPressTheNotFoundShowWorklistLink();
29 |
30 | // Assertions
31 | Then.onTheWorklistPage.iShouldSeeTheTable();
32 | });
33 |
34 | opaTest("Should see the not found text for no search results", function (Given, When, Then) {
35 | //Actions
36 | When.onTheWorklistPage.iSearchForSomethingWithNoResults();
37 |
38 | // Assertions
39 | Then.onTheWorklistPage.iShouldSeeTheNoDataTextForNoSearchResults();
40 | });
41 |
42 | opaTest("Clicking the back button should take me back to the not found page", function (Given, When, Then) {
43 | //Actions
44 | When.onTheBrowser.iPressOnTheBackwardsButton();
45 |
46 | // Assertions
47 | Then.onTheNotFoundPage.iShouldSeeResourceNotFound();
48 |
49 | // Cleanup
50 | Then.iTeardownMyApp();
51 | });
52 |
53 | opaTest("Should see the 'Object not found' page if an invalid object id has been called", function (Given, When, Then) {
54 | //Arrangement
55 | Given.iStartMyApp({hash : "Pony/SomeInvalidObjectId"});
56 |
57 | // Assertions
58 | Then.onTheNotFoundPage.iShouldSeeObjectNotFound();
59 | });
60 |
61 | opaTest("Clicking the 'Show my worklist' link on the 'Object not found' page should bring me back to the worklist", function (Given, When, Then) {
62 | //Actions
63 | When.onTheAppPage.iWaitUntilTheAppBusyIndicatorIsGone();
64 | When.onTheNotFoundPage.iPressTheObjectNotFoundShowWorklistLink();
65 |
66 | // Assertions
67 | Then.onTheWorklistPage.iShouldSeeTheTable();
68 |
69 | // Cleanup
70 | Then.iTeardownMyApp();
71 | });
72 |
73 | });
--------------------------------------------------------------------------------
/demo-project/packages/library/src/ui5con20/library/Chart.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * ${copyright}
3 | */
4 |
5 | // Provides a shim for the chartist library
6 | sap.ui.loader.config({
7 | shim: {
8 | "ui5con20/library/thirdparty/chartist/chartist": {
9 | amd: true,
10 | exports: "Chartist"
11 | }
12 | }
13 | });
14 |
15 | // Provides control ui5con20.library.Chart
16 | sap.ui.define([
17 | "sap/ui/core/Control",
18 | "sap/ui/core/HTML",
19 | "./ChartRenderer",
20 | "./thirdparty/chartist/chartist",
21 | "./library"
22 | ], function(Control, HTML, ChartRenderer, Chartist /*, library*/) {
23 | "use strict";
24 |
25 | var Chart = Control.extend("ui5con20.library.Chart", {
26 | metadata: {
27 | properties: {
28 | "data": {
29 | type: "object"
30 | },
31 | "options": {
32 | type: "object"
33 | },
34 | "type": {
35 | type: "string",
36 | defaultValue: "Line"
37 | }
38 | },
39 | aggregations: {
40 | "_html": {
41 | type: "sap.ui.core.HTML",
42 | multiple: false,
43 | visibility: "hidden"
44 | }
45 | },
46 | events: {
47 | chartInitialized: {}
48 | }
49 | },
50 | init: function() {
51 | var sHtmlId = this.getId() + "-html";
52 | this.setAggregation("_html", new HTML(sHtmlId, {
53 | content: "
"
54 | }));
55 | },
56 | onAfterRendering: function() {
57 | if (!this._chartistChart) {
58 | this._chartistChart = new Chartist[this.getType()](this.getAggregation("_html").getDomRef(), this.getData(), this.getOptions());
59 | this.fireChartInitialized({
60 | chart: this._chartistChart
61 | });
62 | }
63 | },
64 | setData: function(data) {
65 | this.setProperty("data", data, /* bSuppressInvalidate */ true);
66 | if (this._chartistChart) {
67 | this._chartistChart.update(data || {});
68 | }
69 | return this;
70 | },
71 | setOptions: function(options, bSuppressInvalidate) {
72 | this.setProperty("options", options, bSuppressInvalidate);
73 | if (this._chartistChart) {
74 | this._chartistChart.detach();
75 | }
76 | this._chartistChart = new Chartist[this.getType()](this.getAggregation("_html").getDomRef(), this.getData(), this.getOptions());
77 | },
78 | setType: function(type, bSuppressInvalidate) {
79 | this.setProperty("type", type, bSuppressInvalidate);
80 | if (this._chartistChart) {
81 | this._chartistChart.detach();
82 | }
83 | this._chartistChart = new Chartist[this.getType()](this.getAggregation("_html").getDomRef(), this.getData(), this.getOptions());
84 | },
85 | renderer: ChartRenderer
86 | });
87 |
88 | return Chart;
89 | });
90 |
--------------------------------------------------------------------------------
/demo-project/packages/app/webapp/view/Worklist.view.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
11 |
12 |
13 |
16 |
17 |
18 |
19 |
34 |
35 |
36 |
37 |
41 |
42 |
46 |
47 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
71 |
72 |
74 |
80 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/demo-nodejs-api/webapp/view/Worklist.view.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
11 |
12 |
13 |
16 |
17 |
18 |
19 |
34 |
35 |
36 |
37 |
41 |
42 |
46 |
47 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
71 |
72 |
74 |
80 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/demo-project/packages/app/webapp/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "_version": "1.12.0",
3 |
4 | "sap.app": {
5 | "id": "ui5con20.app",
6 | "type": "application",
7 | "i18n": "i18n/i18n.properties",
8 | "title": "{{appTitle}}",
9 | "description": "{{appDescription}}",
10 | "applicationVersion": {
11 | "version": "1.0.0"
12 | },
13 | "ach": "set-ach",
14 | "resources": "resources.json"
15 | },
16 |
17 | "sap.fiori": {
18 | "registrationIds": [
19 | ],
20 | "archeType": "transactional"
21 | },
22 |
23 | "sap.ui": {
24 | "technology": "UI5",
25 | "icons": {
26 | "icon": "sap-icon://task",
27 | "favIcon": "",
28 | "phone": "",
29 | "phone@2": "",
30 | "tablet": "",
31 | "tablet@2": ""
32 | },
33 | "deviceTypes": {
34 | "desktop": true,
35 | "tablet": true,
36 | "phone": true
37 | }
38 | },
39 |
40 | "sap.ui5": {
41 | "rootView": {
42 | "viewName": "ui5con20.app.view.App",
43 | "type": "XML",
44 | "async": true,
45 | "id": "app"
46 | },
47 |
48 | "dependencies": {
49 | "minUI5Version": "1.66.0",
50 | "libs": {
51 | "sap.ui.core": {},
52 | "sap.m": {},
53 | "sap.f": {},
54 | "ui5con20.library": {}
55 | }
56 | },
57 |
58 | "contentDensities": {
59 | "compact": true,
60 | "cozy": true
61 | },
62 |
63 | "models": {
64 | "i18n": {
65 | "type": "sap.ui.model.resource.ResourceModel",
66 | "settings": {
67 | "bundleName": "ui5con20.app.i18n.i18n"
68 | }
69 | },
70 | "": {
71 | "type": "sap.ui.model.json.JSONModel"
72 | }
73 | },
74 |
75 | "routing": {
76 | "config": {
77 | "routerClass": "sap.m.routing.Router",
78 | "viewType": "XML",
79 | "viewPath": "ui5con20.app.view",
80 | "controlId": "app",
81 | "controlAggregation": "pages",
82 | "bypassed": {
83 | "target": ["notFound"]
84 | },
85 | "async": true
86 | },
87 |
88 | "routes": [
89 | {
90 | "pattern": "",
91 | "name": "worklist",
92 | "target": ["worklist"]
93 | },
94 | {
95 | "pattern": "Repository/{objectId}",
96 | "name": "object",
97 | "target": ["object"]
98 | }
99 | ],
100 |
101 | "targets": {
102 | "worklist": {
103 | "viewName": "Worklist",
104 | "viewId": "worklist",
105 | "viewLevel": 1,
106 | "title": "{i18n>worklistViewTitle}"
107 | },
108 | "object": {
109 | "viewName": "Object",
110 | "viewId": "object",
111 | "viewLevel": 2,
112 | "title": "{i18n>objectViewTitle}"
113 | },
114 | "objectNotFound": {
115 | "viewName": "ObjectNotFound",
116 | "viewId": "objectNotFound"
117 | },
118 | "notFound": {
119 | "viewName": "NotFound",
120 | "viewId": "notFound"
121 | }
122 | }
123 | }
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/demo-nodejs-api/webapp/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "_version": "1.12.0",
3 |
4 | "sap.app": {
5 | "id": "ui5con20.demo.nodejs",
6 | "type": "application",
7 | "i18n": "i18n/i18n.properties",
8 | "title": "{{appTitle}}",
9 | "description": "{{appDescription}}",
10 | "applicationVersion": {
11 | "version": "1.0.0"
12 | },
13 | "ach": "set-ach",
14 | "resources": "resources.json"
15 | },
16 |
17 | "sap.fiori": {
18 | "registrationIds": [
19 | ],
20 | "archeType": "transactional"
21 | },
22 |
23 | "sap.ui": {
24 | "technology": "UI5",
25 | "icons": {
26 | "icon": "sap-icon://task",
27 | "favIcon": "",
28 | "phone": "",
29 | "phone@2": "",
30 | "tablet": "",
31 | "tablet@2": ""
32 | },
33 | "deviceTypes": {
34 | "desktop": true,
35 | "tablet": true,
36 | "phone": true
37 | }
38 | },
39 |
40 | "sap.ui5": {
41 | "rootView": {
42 | "viewName": "ui5con20.demo.nodejs.view.App",
43 | "type": "XML",
44 | "async": true,
45 | "id": "app"
46 | },
47 |
48 | "dependencies": {
49 | "minUI5Version": "1.66.0",
50 | "libs": {
51 | "sap.ui.core": {},
52 | "sap.m": {},
53 | "sap.f": {},
54 | "ui5con20.library": {}
55 | }
56 | },
57 |
58 | "contentDensities": {
59 | "compact": true,
60 | "cozy": true
61 | },
62 |
63 | "models": {
64 | "i18n": {
65 | "type": "sap.ui.model.resource.ResourceModel",
66 | "settings": {
67 | "bundleName": "ui5con20.demo.nodejs.i18n.i18n"
68 | }
69 | },
70 | "": {
71 | "type": "sap.ui.model.json.JSONModel"
72 | }
73 | },
74 |
75 | "routing": {
76 | "config": {
77 | "routerClass": "sap.m.routing.Router",
78 | "viewType": "XML",
79 | "viewPath": "ui5con20.demo.nodejs.view",
80 | "controlId": "app",
81 | "controlAggregation": "pages",
82 | "bypassed": {
83 | "target": ["notFound"]
84 | },
85 | "async": true
86 | },
87 |
88 | "routes": [
89 | {
90 | "pattern": "",
91 | "name": "worklist",
92 | "target": ["worklist"]
93 | },
94 | {
95 | "pattern": "Repository/{objectId}",
96 | "name": "object",
97 | "target": ["object"]
98 | }
99 | ],
100 |
101 | "targets": {
102 | "worklist": {
103 | "viewName": "Worklist",
104 | "viewId": "worklist",
105 | "viewLevel": 1,
106 | "title": "{i18n>worklistViewTitle}"
107 | },
108 | "object": {
109 | "viewName": "Object",
110 | "viewId": "object",
111 | "viewLevel": 2,
112 | "title": "{i18n>objectViewTitle}"
113 | },
114 | "objectNotFound": {
115 | "viewName": "ObjectNotFound",
116 | "viewId": "objectNotFound"
117 | },
118 | "notFound": {
119 | "viewName": "NotFound",
120 | "viewId": "notFound"
121 | }
122 | }
123 | }
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/demo-project/packages/app/webapp/test/integration/NavigationJourney.js:
--------------------------------------------------------------------------------
1 | /*global QUnit*/
2 |
3 | sap.ui.define([
4 | "sap/ui/test/opaQunit",
5 | "sap/ui/Device",
6 | "./pages/Worklist",
7 | "./pages/Browser",
8 | "./pages/Object",
9 | "./pages/App"
10 | ], function (opaTest, Device) {
11 | "use strict";
12 |
13 | var iDelay = (Device.browser.msie || Device.browser.edge) ? 1500 : 1000;
14 |
15 | QUnit.module("Navigation");
16 |
17 | opaTest("Should see the objects list", function (Given, When, Then) {
18 | // Arrangements
19 | Given.iStartMyApp();
20 |
21 | // Assertions
22 | Then.onTheWorklistPage.iShouldSeeTheTable();
23 | });
24 |
25 | opaTest("Should react on hash change", function (Given, When, Then) {
26 | // Actions
27 | When.onTheWorklistPage.iRememberTheItemAtPosition(2);
28 | When.onTheBrowser.iChangeTheHashToTheRememberedItem();
29 |
30 | // Assertions
31 | Then.onTheObjectPage.iShouldSeeTheRememberedObject().
32 | and.theViewIsNotBusyAnymore();
33 | });
34 |
35 | opaTest("Should go back to the TablePage", function (Given, When, Then) {
36 | // Actions
37 | When.onTheBrowser.iPressOnTheBackwardsButton();
38 |
39 | // Assertions
40 | Then.onTheWorklistPage.iShouldSeeTheTable();
41 | });
42 |
43 | opaTest("Object Page shows the correct object Details", function (Given, When, Then) {
44 | // Actions
45 | When.onTheWorklistPage.iRememberTheItemAtPosition(1).
46 | and.iPressATableItemAtPosition(1);
47 |
48 | // Assertions
49 | Then.onTheObjectPage.iShouldSeeTheRememberedObject();
50 | });
51 |
52 | opaTest("Should be on the table page again when browser back is pressed", function (Given, When, Then) {
53 | // Actions
54 | When.onTheBrowser.iPressOnTheBackwardsButton();
55 |
56 | // Assertions
57 | Then.onTheWorklistPage.iShouldSeeTheTable();
58 | });
59 |
60 | opaTest("Should be on the object page again when browser forwards is pressed", function (Given, When, Then) {
61 | // Actions
62 | When.onTheBrowser.iPressOnTheForwardsButton();
63 |
64 | // Assertions
65 | Then.onTheObjectPage.iShouldSeeTheRememberedObject();
66 |
67 | // Cleanup
68 | Then.iTeardownMyApp();
69 | });
70 |
71 | opaTest("Start the App and simulate metadata error: MessageBox should be shown", function (Given, When, Then) {
72 | //Arrangement
73 | Given.iStartMyApp({
74 | delay: iDelay,
75 | metadataError: true
76 | });
77 |
78 | //Assertions
79 | Then.onTheAppPage.iShouldSeeTheMessageBox();
80 |
81 | // Actions
82 | When.onTheAppPage.iCloseTheMessageBox();
83 |
84 | // Cleanup
85 | Then.iTeardownMyApp();
86 | });
87 |
88 | opaTest("Start the App and simulate bad request error: MessageBox should be shown", function (Given, When, Then) {
89 | //Arrangement
90 | Given.iStartMyApp({
91 | delay: iDelay,
92 | errorType: "serverError"
93 | });
94 |
95 | //Assertions
96 | Then.onTheAppPage.iShouldSeeTheMessageBox();
97 |
98 | // Actions
99 | When.onTheAppPage.iCloseTheMessageBox();
100 |
101 | // Cleanup
102 | Then.iTeardownMyApp();
103 | });
104 |
105 | });
--------------------------------------------------------------------------------
/demo-nodejs-api/webapp/i18n/i18n.properties:
--------------------------------------------------------------------------------
1 | # This is the resource bundle for UI5con 2020 Demo App
2 | # __ldi.translation.uuid=
3 |
4 | #XTIT: Application name
5 | appTitle=UI5con 2020 Demo App
6 |
7 | #YDES: Application description
8 | appDescription=UI5con 2020 Demo App
9 |
10 | #~~~ Worklist View ~~~~~~~~~~~~~~~~~~~~~~~~~~
11 | #XTIT: Worklist view title
12 | worklistViewTitle=Manage Repositories
13 |
14 | #XTIT: Worklist page title
15 | worklistTitle=UI5con 2020 Demo App
16 |
17 | #XTIT: Table view title
18 | worklistTableTitle=Repositories
19 |
20 | #XTOL: Tooltip for the search field
21 | worklistSearchTooltip=Enter an Repository name or a part of it.
22 |
23 | #XBLI: text for a table with no data with filter or search
24 | worklistNoDataWithSearchText=No matching Repositories found
25 |
26 | #XTIT: Table view title with placeholder for the number of items
27 | worklistTableTitleCount=Repositories ({0})
28 |
29 | #XTIT: The title of the column containing the Repository of Repository
30 | tableNameColumnTitle=Repository
31 |
32 | #XTIT: The title of the column containing the Repository and the unit of measure
33 | starNumberColumnTitle=GitHub Stars
34 |
35 | #XTIT: The title of the column containing the Repository and the unit of measure
36 | downloadNumberColumnTitle=npm Downloads
37 |
38 | #XBLI: text for a table with no data
39 | tableNoDataText=No Repositories are currently available
40 |
41 | #XLNK: text for link in 'not found' pages
42 | backToWorklist=Show UI5con 2020 Demo App
43 |
44 | #~~~ Object View ~~~~~~~~~~~~~~~~~~~~~~~~~~
45 | #XTIT: Object view title
46 | objectViewTitle=Repository Details
47 |
48 | #XTIT: Object page title
49 | objectTitle=Repository
50 |
51 |
52 | #XTIT: Label for the Repository
53 | RepositoryLabel=Repository
54 |
55 | #XTIT: Label for the Repository
56 | RepositoryLabel=Repository
57 |
58 |
59 | #~~~ Share Menu Options ~~~~~~~~~~~~~~~~~~~~~~~
60 |
61 | #XTIT: Send E-Mail subject
62 | shareSendEmailWorklistSubject=
63 |
64 | #YMSG: Send E-Mail message
65 | shareSendEmailWorklistMessage=\r\n{0}
66 |
67 | #XTIT: Send E-Mail subject
68 | shareSendEmailObjectSubject= {0}
69 |
70 | #YMSG: Send E-Mail message
71 | shareSendEmailObjectMessage= {0} (id: {1})\r\n{2}
72 |
73 |
74 | #~~~ Not Found View ~~~~~~~~~~~~~~~~~~~~~~~
75 |
76 | #XTIT: Not found view title
77 | notFoundTitle=Not Found
78 |
79 | #YMSG: The Repository not found text is displayed when there is no Repository with this id
80 | noObjectFoundText=This Repository is not available
81 |
82 | #YMSG: The Repository not available text is displayed when there is no data when starting the app
83 | noObjectsAvailableText=No Repositories are currently available
84 |
85 | #YMSG: The not found text is displayed when there was an error loading the resource (404 error)
86 | notFoundText=The requested resource was not found
87 |
88 | #~~~ Error Handling ~~~~~~~~~~~~~~~~~~~~~~~
89 |
90 | #YMSG: Error dialog description
91 | errorText=Sorry, a technical error occurred! Please try again later.
--------------------------------------------------------------------------------
/demo-project/packages/app/webapp/i18n/i18n.properties:
--------------------------------------------------------------------------------
1 | # This is the resource bundle for UI5con 2020 Demo App
2 | # __ldi.translation.uuid=
3 |
4 | #XTIT: Application name
5 | appTitle=UI5con 2020 Demo App
6 |
7 | #YDES: Application description
8 | appDescription=UI5con 2020 Demo App
9 |
10 | #~~~ Worklist View ~~~~~~~~~~~~~~~~~~~~~~~~~~
11 | #XTIT: Worklist view title
12 | worklistViewTitle=Manage Repositories
13 |
14 | #XTIT: Worklist page title
15 | worklistTitle=UI5con 2020 Demo App
16 |
17 | #XTIT: Table view title
18 | worklistTableTitle=Repositories
19 |
20 | #XTOL: Tooltip for the search field
21 | worklistSearchTooltip=Enter an Repository name or a part of it.
22 |
23 | #XBLI: text for a table with no data with filter or search
24 | worklistNoDataWithSearchText=No matching Repositories found
25 |
26 | #XTIT: Table view title with placeholder for the number of items
27 | worklistTableTitleCount=Repositories ({0})
28 |
29 | #XTIT: The title of the column containing the Repository of Repository
30 | tableNameColumnTitle=Repository
31 |
32 | #XTIT: The title of the column containing the Repository and the unit of measure
33 | starNumberColumnTitle=GitHub Stars
34 |
35 | #XTIT: The title of the column containing the Repository and the unit of measure
36 | downloadNumberColumnTitle=npm Downloads
37 |
38 | #XBLI: text for a table with no data
39 | tableNoDataText=No Repositories are currently available
40 |
41 | #XLNK: text for link in 'not found' pages
42 | backToWorklist=Show UI5con 2020 Demo App
43 |
44 | #~~~ Object View ~~~~~~~~~~~~~~~~~~~~~~~~~~
45 | #XTIT: Object view title
46 | objectViewTitle=Repository Details
47 |
48 | #XTIT: Object page title
49 | objectTitle=Repository
50 |
51 |
52 | #XTIT: Label for the Repository
53 | RepositoryLabel=Repository
54 |
55 | #XTIT: Label for the Repository
56 | RepositoryLabel=Repository
57 |
58 |
59 | #~~~ Share Menu Options ~~~~~~~~~~~~~~~~~~~~~~~
60 |
61 | #XTIT: Send E-Mail subject
62 | shareSendEmailWorklistSubject=
63 |
64 | #YMSG: Send E-Mail message
65 | shareSendEmailWorklistMessage=\r\n{0}
66 |
67 | #XTIT: Send E-Mail subject
68 | shareSendEmailObjectSubject= {0}
69 |
70 | #YMSG: Send E-Mail message
71 | shareSendEmailObjectMessage= {0} (id: {1})\r\n{2}
72 |
73 |
74 | #~~~ Not Found View ~~~~~~~~~~~~~~~~~~~~~~~
75 |
76 | #XTIT: Not found view title
77 | notFoundTitle=Not Found
78 |
79 | #YMSG: The Repository not found text is displayed when there is no Repository with this id
80 | noObjectFoundText=This Repository is not available
81 |
82 | #YMSG: The Repository not available text is displayed when there is no data when starting the app
83 | noObjectsAvailableText=No Repositories are currently available
84 |
85 | #YMSG: The not found text is displayed when there was an error loading the resource (404 error)
86 | notFoundText=The requested resource was not found
87 |
88 | #~~~ Error Handling ~~~~~~~~~~~~~~~~~~~~~~~
89 |
90 | #YMSG: Error dialog description
91 | errorText=Sorry, a technical error occurred! Please try again later.
--------------------------------------------------------------------------------
/demo-project/packages/app/webapp/test/integration/pages/NotFound.js:
--------------------------------------------------------------------------------
1 | sap.ui.define([
2 | "sap/ui/test/Opa5",
3 | "sap/ui/test/actions/Press",
4 | "./Common"
5 | ], function(Opa5, Press, Common) {
6 | "use strict";
7 |
8 | Opa5.createPageObjects({
9 | onTheNotFoundPage : {
10 | baseClass : Common,
11 |
12 | actions : {
13 |
14 | iWaitUntilISeeObjectNotFoundPage : function () {
15 | return this.waitFor({
16 | id : "page",
17 | viewName : "ObjectNotFound",
18 | success : function (oPage) {
19 | Opa5.assert.strictEqual(oPage.getTitle(), oPage.getModel("i18n").getProperty("objectTitle"), "the object text is shown as title");
20 | Opa5.assert.strictEqual(oPage.getText(), oPage.getModel("i18n").getProperty("noObjectFoundText"), "the object not found text is shown");
21 | },
22 | errorMessage : "Did not display the object not found text"
23 | });
24 | },
25 |
26 | iWaitUntilISeeResourceNotFoundPage : function () {
27 | return this.waitFor({
28 | id : "page",
29 | viewName : "NotFound",
30 | success : function (oPage) {
31 | Opa5.assert.strictEqual(oPage.getTitle(), oPage.getModel("i18n").getProperty("notFoundTitle"), "the not found title is shown as title");
32 | Opa5.assert.strictEqual(oPage.getText(), oPage.getModel("i18n").getProperty("notFoundText"), "the not found text is shown");
33 | },
34 | errorMessage : "Did not display the object not found text"
35 | });
36 | },
37 |
38 | iPressTheObjectNotFoundShowWorklistLink : function () {
39 | return this.waitFor({
40 | id : "link",
41 | viewName : "ObjectNotFound",
42 | actions : new Press(),
43 | errorMessage : "Did not find the link on the not found page"
44 | });
45 | },
46 |
47 | iPressTheNotFoundShowWorklistLink : function () {
48 | return this.waitFor({
49 | id : "link",
50 | viewName : "NotFound",
51 | actions : new Press(),
52 | errorMessage : "Did not find the link on the not found page"
53 | });
54 | }
55 | },
56 |
57 | assertions : {
58 |
59 | iShouldSeeObjectNotFound : function () {
60 | return this.waitFor({
61 | id : "page",
62 | viewName : "ObjectNotFound",
63 | success: function (oPage) {
64 | Opa5.assert.strictEqual(oPage.getTitle(), oPage.getModel("i18n").getProperty("objectTitle"), "the object text is shown as title");
65 | Opa5.assert.strictEqual(oPage.getText(), oPage.getModel("i18n").getProperty("noObjectFoundText"), "the object not found text is shown");
66 | },
67 | errorMessage: "Did not display the object not found text"
68 | });
69 | },
70 |
71 | iShouldSeeResourceNotFound : function () {
72 | return this.waitFor({
73 | id : "page",
74 | viewName : "NotFound",
75 | success: function (oPage) {
76 | Opa5.assert.strictEqual(oPage.getTitle(), oPage.getModel("i18n").getProperty("notFoundTitle"), "the not found title is shown as title");
77 | Opa5.assert.strictEqual(oPage.getText(), oPage.getModel("i18n").getProperty("notFoundText"), "the not found text is shown");
78 | },
79 | errorMessage: "Did not display the object not found text"
80 | });
81 | }
82 |
83 | }
84 |
85 | }
86 |
87 | });
88 |
89 | });
--------------------------------------------------------------------------------
/demo-project/packages/app/webapp/Component.js:
--------------------------------------------------------------------------------
1 | sap.ui.define([
2 | "sap/ui/core/UIComponent",
3 | "sap/ui/Device",
4 | "./model/models",
5 | "./controller/ErrorHandler"
6 | ], function (UIComponent, Device, models, ErrorHandler) {
7 | "use strict";
8 |
9 | return UIComponent.extend("ui5con20.app.Component", {
10 |
11 | metadata : {
12 | manifest: "json"
13 | },
14 |
15 | /**
16 | * The component is initialized by UI5 automatically during the startup of the app and calls the init method once.
17 | * In this function, the device models are set and the router is initialized.
18 | * @public
19 | * @override
20 | */
21 | init : function () {
22 | // call the base component's init function
23 | UIComponent.prototype.init.apply(this, arguments);
24 |
25 | // initialize the error handler with the component
26 | this._oErrorHandler = new ErrorHandler(this);
27 |
28 | // set the device model
29 | this.setModel(models.createDeviceModel(), "device");
30 |
31 | // create the views based on the url/hash
32 | this.getRouter().initialize();
33 |
34 | this._dataLoaded = this.loadData([
35 | "SAP/ui5-tooling",
36 | "SAP/ui5-cli",
37 | "SAP/ui5-project",
38 | "SAP/ui5-server",
39 | "SAP/ui5-builder",
40 | "SAP/ui5-fs",
41 | "SAP/ui5-logger"
42 | ]);
43 | },
44 |
45 | loadData: function(repositories) {
46 | var oModel = this.getModel();
47 | return Promise.all(repositories.map(function(repositoryName) {
48 |
49 | // return {
50 | // repositoryName,
51 | // stars: 200,
52 | // downloads: 100000
53 | // };
54 | return fetch("data/" + repositoryName)
55 | .then(function(response) {
56 | return response.json();
57 | });
58 | })).then(function(data) {
59 | console.log(data);
60 |
61 | oModel.setProperty("/Repositories", data);
62 | });
63 | },
64 |
65 | getDataLoaded: function() {
66 | return this._dataLoaded;
67 | },
68 |
69 | /**
70 | * The component is destroyed by UI5 automatically.
71 | * In this method, the ErrorHandler is destroyed.
72 | * @public
73 | * @override
74 | */
75 | destroy : function () {
76 | this._oErrorHandler.destroy();
77 | // call the base component's destroy function
78 | UIComponent.prototype.destroy.apply(this, arguments);
79 | },
80 |
81 | /**
82 | * This method can be called to determine whether the sapUiSizeCompact or sapUiSizeCozy
83 | * design mode class should be set, which influences the size appearance of some controls.
84 | * @public
85 | * @return {string} css class, either 'sapUiSizeCompact' or 'sapUiSizeCozy' - or an empty string if no css class should be set
86 | */
87 | getContentDensityClass : function() {
88 | if (this._sContentDensityClass === undefined) {
89 | // check whether FLP has already set the content density class; do nothing in this case
90 | if (document.body.classList.contains("sapUiSizeCozy") || document.body.classList.contains("sapUiSizeCompact")) {
91 | this._sContentDensityClass = "";
92 | } else if (!Device.support.touch) { // apply "compact" mode if touch is not supported
93 | this._sContentDensityClass = "sapUiSizeCompact";
94 | } else {
95 | // "cozy" in case of touch support; default for most sap.m controls, but needed for desktop-first controls like sap.ui.table.Table
96 | this._sContentDensityClass = "sapUiSizeCozy";
97 | }
98 | }
99 | return this._sContentDensityClass;
100 | }
101 |
102 | });
103 |
104 | });
105 |
--------------------------------------------------------------------------------
/demo-nodejs-api/webapp/Component.js:
--------------------------------------------------------------------------------
1 | sap.ui.define([
2 | "sap/ui/core/UIComponent",
3 | "sap/ui/Device",
4 | "./model/models",
5 | "./controller/ErrorHandler"
6 | ], function (UIComponent, Device, models, ErrorHandler) {
7 | "use strict";
8 |
9 | return UIComponent.extend("ui5con20.demo.nodejs.Component", {
10 |
11 | metadata : {
12 | manifest: "json"
13 | },
14 |
15 | /**
16 | * The component is initialized by UI5 automatically during the startup of the app and calls the init method once.
17 | * In this function, the device models are set and the router is initialized.
18 | * @public
19 | * @override
20 | */
21 | init : function () {
22 | // call the base component's init function
23 | UIComponent.prototype.init.apply(this, arguments);
24 |
25 | // initialize the error handler with the component
26 | this._oErrorHandler = new ErrorHandler(this);
27 |
28 | // set the device model
29 | this.setModel(models.createDeviceModel(), "device");
30 |
31 | // create the views based on the url/hash
32 | this.getRouter().initialize();
33 |
34 | this._dataLoaded = this.loadData([
35 | "SAP/ui5-tooling",
36 | "SAP/ui5-cli",
37 | "SAP/ui5-project",
38 | "SAP/ui5-server",
39 | "SAP/ui5-builder",
40 | "SAP/ui5-fs",
41 | "SAP/ui5-logger"
42 | ]);
43 | },
44 |
45 | loadData: function(repositories) {
46 | var oModel = this.getModel();
47 | return Promise.all(repositories.map(function(repositoryName) {
48 |
49 | // return {
50 | // repositoryName,
51 | // stars: 200,
52 | // downloads: 100000
53 | // };
54 | return fetch(`data/${repositoryName}`)
55 | .then(function(response) {
56 | return response.json();
57 | });
58 | })).then(function(data) {
59 | console.log(data);
60 |
61 | oModel.setProperty("/Repositories", data);
62 | });
63 | },
64 |
65 | getDataLoaded: function() {
66 | return this._dataLoaded;
67 | },
68 |
69 | /**
70 | * The component is destroyed by UI5 automatically.
71 | * In this method, the ErrorHandler is destroyed.
72 | * @public
73 | * @override
74 | */
75 | destroy : function () {
76 | this._oErrorHandler.destroy();
77 | // call the base component's destroy function
78 | UIComponent.prototype.destroy.apply(this, arguments);
79 | },
80 |
81 | /**
82 | * This method can be called to determine whether the sapUiSizeCompact or sapUiSizeCozy
83 | * design mode class should be set, which influences the size appearance of some controls.
84 | * @public
85 | * @return {string} css class, either 'sapUiSizeCompact' or 'sapUiSizeCozy' - or an empty string if no css class should be set
86 | */
87 | getContentDensityClass : function() {
88 | if (this._sContentDensityClass === undefined) {
89 | // check whether FLP has already set the content density class; do nothing in this case
90 | // eslint-disable-next-line sap-no-proprietary-browser-api
91 | if (document.body.classList.contains("sapUiSizeCozy") || document.body.classList.contains("sapUiSizeCompact")) {
92 | this._sContentDensityClass = "";
93 | } else if (!Device.support.touch) { // apply "compact" mode if touch is not supported
94 | this._sContentDensityClass = "sapUiSizeCompact";
95 | } else {
96 | // "cozy" in case of touch support; default for most sap.m controls, but needed for desktop-first controls like sap.ui.table.Table
97 | this._sContentDensityClass = "sapUiSizeCozy";
98 | }
99 | }
100 | return this._sContentDensityClass;
101 | }
102 |
103 | });
104 |
105 | });
--------------------------------------------------------------------------------
/demo-project/packages/app/webapp/test/integration/pages/Object.js:
--------------------------------------------------------------------------------
1 | sap.ui.define([
2 | "sap/ui/test/Opa5",
3 | "sap/ui/test/actions/Press",
4 | "sap/ui/test/matchers/PropertyStrictEquals",
5 | "./Common",
6 | "./shareOptions"
7 | ], function (Opa5, Press, PropertyStrictEquals, Common, shareOptions) {
8 | "use strict";
9 |
10 | var sViewName = "Object";
11 |
12 | Opa5.createPageObjects({
13 | onTheObjectPage: {
14 | baseClass : Common,
15 |
16 | actions : Object.assign({
17 | iPressTheBackButton : function () {
18 | return this.waitFor({
19 | id : "page",
20 | viewName : sViewName,
21 | actions: new Press(),
22 | errorMessage : "Did not find the nav button on object page"
23 | });
24 | }
25 |
26 | }, shareOptions.createActions(sViewName)),
27 |
28 | assertions: Object.assign({
29 |
30 | iShouldSeeTheRememberedObject : function () {
31 | return this.waitFor({
32 | success : function () {
33 | var sBindingPath = this.getContext().currentItem.bindingPath;
34 | this.waitFor({
35 | id : "page",
36 | viewName : sViewName,
37 | matchers : function (oPage) {
38 | return oPage.getBindingContext() && oPage.getBindingContext().getPath() === sBindingPath;
39 | },
40 | success : function (oPage) {
41 | Opa5.assert.strictEqual(oPage.getBindingContext().getPath(), sBindingPath, "was on the remembered detail page");
42 | },
43 | errorMessage : "Remembered object " + sBindingPath + " is not shown"
44 | });
45 | }
46 | });
47 | },
48 |
49 | iShouldSeeTheObjectViewsBusyIndicator : function () {
50 | return this.waitFor({
51 | id : "page",
52 | viewName : sViewName,
53 | matchers: new PropertyStrictEquals({
54 | name : "busy",
55 | value: true
56 | }),
57 | autoWait: false,
58 | success : function (oPage) {
59 | Opa5.assert.ok(oPage.getBusy(), "The object view is busy");
60 | },
61 | errorMessage : "The object view is not busy"
62 | });
63 | },
64 |
65 | theViewIsNotBusyAnymore : function () {
66 | return this.waitFor({
67 | id : "page",
68 | viewName : sViewName,
69 | matchers: new PropertyStrictEquals({
70 | name : "busy",
71 | value: false
72 | }),
73 | autoWait: false,
74 | success : function (oPage) {
75 | Opa5.assert.ok(!oPage.getBusy(), "The object view is not busy");
76 | },
77 | errorMessage : "The object view is busy"
78 | });
79 | },
80 |
81 | theObjectViewsBusyIndicatorDelayIsZero : function () {
82 | return this.waitFor({
83 | id : "page",
84 | viewName : sViewName,
85 | success : function (oPage) {
86 | Opa5.assert.strictEqual(oPage.getBusyIndicatorDelay(), 0, "The object view's busy indicator delay is zero.");
87 | },
88 | errorMessage : "The object view's busy indicator delay is not zero."
89 | });
90 | },
91 |
92 | theObjectViewsBusyIndicatorDelayIsRestored : function () {
93 | return this.waitFor({
94 | id : "page",
95 | viewName : sViewName,
96 | matchers: new PropertyStrictEquals({
97 | name : "busyIndicatorDelay",
98 | value: 1000
99 | }),
100 | success : function () {
101 | Opa5.assert.ok(true, "The object view's busy indicator delay default is restored.");
102 | },
103 | errorMessage : "The object view's busy indicator delay is still zero."
104 | });
105 | },
106 |
107 | theObjectViewShouldContainOnlyFormattedUnitNumbers : function () {
108 | return this.theUnitNumbersShouldHaveTwoDecimals("sap.m.ObjectNumber",
109 | sViewName,
110 | "Object numbers are properly formatted",
111 | "Object view has no entries which can be checked for their formatting");
112 | },
113 |
114 | theShareTileButtonShouldContainTheRememberedObjectName : function () {
115 | return this.waitFor({
116 | id : "shareTile",
117 | viewName : sViewName,
118 | matchers : function (oButton) {
119 | var sObjectName = this.getContext().currentItem.name;
120 | var sTitle = oButton.getTitle();
121 | return sTitle && sTitle.indexOf(sObjectName) > -1;
122 | }.bind(this),
123 | success : function () {
124 | Opa5.assert.ok(true, "The Save as Tile button contains the object name");
125 | },
126 | errorMessage : "The Save as Tile did not contain the object name"
127 | });
128 | }
129 |
130 | }, shareOptions.createAssertions(sViewName))
131 |
132 | }
133 |
134 | });
135 |
136 | });
--------------------------------------------------------------------------------
/demo-project/packages/server/lib/middleware.js:
--------------------------------------------------------------------------------
1 | module.exports = function() {
2 | const request = require("request");
3 | const parseurl = require("parseurl");
4 | const path = require("path");
5 |
6 | const gitHubBaseUrl = "https://api.github.com";
7 | const npmBaseUrl = "https://api.npmjs.org";
8 |
9 | require('dotenv').config({
10 | path: path.join(__dirname, "..", '.env')
11 | });
12 |
13 | if (!process.env.UI5CON_DEMO_GITHUB_USER || !process.env.UI5CON_DEMO_GITHUB_TOKEN) {
14 | throw new Error("Missing GitHub credentials");
15 | }
16 |
17 | function formatMonth(monthId) {
18 | const date = new Date(monthId + "-01");
19 | return (date.getUTCMonth() + 1) + "." + date.getUTCFullYear();
20 | }
21 |
22 | function formatChartDataMonthly(dailyDownloads) {
23 | const months = {};
24 |
25 | const lastIdx = dailyDownloads.length - 1;
26 | dailyDownloads.forEach((report, idx) => {
27 | const monthId = report.day.substring(0, 7);
28 |
29 | if (idx === lastIdx) {
30 | // Remove latest month as data is not complete yet
31 | delete months[monthId];
32 | return;
33 | }
34 | if (!months[monthId]) {
35 | months[monthId] = 0;
36 | }
37 |
38 | months[monthId] += report.downloads;
39 | });
40 |
41 | const dataPoints = Object.values(months);
42 |
43 | const labels = Object.keys(months).map(formatMonth);
44 |
45 | return {
46 | labels,
47 | series: [dataPoints]
48 | };
49 | }
50 |
51 | const gitHubClient = request.defaults({
52 | baseUrl: gitHubBaseUrl,
53 | json: true,
54 | headers: {
55 | "Accept": "application/vnd.github.v3+json; charset=UTF-8",
56 | "Content-Type": "application/json",
57 | "User-Agent": "ui5con-demo-project"
58 | },
59 | auth: {
60 | user: process.env.UI5CON_DEMO_GITHUB_USER,
61 | password: process.env.UI5CON_DEMO_GITHUB_TOKEN
62 | },
63 | gzip: true
64 | });
65 |
66 | const gitHubCache = {};
67 | function fetchGitHubData(repositoryName) {
68 | if (gitHubCache[repositoryName]) {
69 | return gitHubCache[repositoryName];
70 | }
71 | return gitHubCache[repositoryName] = new Promise((resolve, reject) => {
72 | gitHubClient.get({
73 | uri: "/repos" + repositoryName
74 | }, function(err, httpRes, data) {
75 | if (err) {
76 | console.log("GitHub API repo fetch failed:");
77 | console.error(err);
78 | reject(err);
79 | return;
80 | }
81 |
82 | if (httpRes.statusCode !== 200) {
83 | console.log("GitHub API repo fetch failed:");
84 | console.error(httpRes.statusCode + ": " + data);
85 | reject(httpRes.statusCode + ": " + data);
86 | return;
87 | }
88 |
89 | resolve({
90 | "stargazers_count": data.stargazers_count,
91 | "html_url": data.html_url,
92 | "full_name": data.full_name
93 | });
94 | });
95 | });
96 | }
97 |
98 | const npmClient = request.defaults({
99 | baseUrl: npmBaseUrl,
100 | json: true,
101 | headers: {
102 | "cache": "force-cache", // Force using cache as data should not change anymore
103 | "referrerPolicy": "no-referrer"
104 | },
105 | gzip: true
106 | });
107 |
108 | const npmCache = {};
109 | async function fetchNpmData(repositoryName) {
110 | const npmName = repositoryName.replace("/SAP/ui5-", "@ui5/");
111 | if (npmName === "@ui5/tooling") {
112 | return {};
113 | }
114 |
115 | if (npmCache[npmName]) {
116 | return npmCache[npmName];
117 | }
118 | const nowDateString = new Date().toISOString().split("T")[0];
119 | return npmCache[npmName] = Promise.all([
120 | new Promise((resolve, reject) => {
121 | npmClient.get({
122 | uri: `/downloads/point/2016-01-01:${nowDateString}/${npmName}`
123 | }, function(err, httpRes, data) {
124 | if (err) {
125 | console.log("npm Registry API fetch failed:");
126 | console.error(err);
127 | reject(err);
128 | return;
129 | }
130 |
131 | if (httpRes.statusCode !== 200) {
132 | console.log("npm Registry API fetch failed:");
133 | console.error(httpRes.statusCode + ": " + data);
134 | reject(httpRes.statusCode + ": " + data);
135 | return;
136 | }
137 | resolve(data);
138 | });
139 | }),
140 | new Promise((resolve, reject) => {
141 | npmClient.get({
142 | uri: `/downloads/range/2016-01-01:${nowDateString}/${npmName}`
143 | }, function(err, httpRes, data) {
144 | if (err) {
145 | console.log("npm Registry API fetch failed:");
146 | console.error(err);
147 | reject(err);
148 | return;
149 | }
150 |
151 | if (httpRes.statusCode !== 200) {
152 | console.log("npm Registry API fetch failed:");
153 | console.error(httpRes.statusCode + ": " + data);
154 | reject(httpRes.statusCode + ": " + data);
155 | return;
156 | }
157 | resolve(formatChartDataMonthly(data.downloads));
158 | });
159 | }),
160 | ]).then(([total, range]) => {
161 | return {
162 | downloadsTotal: total.downloads,
163 | downloadsRange: range
164 | }
165 | });
166 | }
167 |
168 | return function (req, res, next) {
169 | let {pathname} = parseurl(req);
170 | pathname = decodeURIComponent(pathname);
171 |
172 | // Try to read a corresponding markdown file
173 | Promise.all([fetchGitHubData(pathname), fetchNpmData(pathname)])
174 | .then(([gitHubData, npmData]) => {
175 | res.json({
176 | "stars": gitHubData.stargazers_count,
177 | "html_url": gitHubData.html_url,
178 | "repositoryName": gitHubData.full_name,
179 | "downloadsTotal": npmData.downloadsTotal,
180 | "downloadsRange": npmData.downloadsRange
181 | });
182 |
183 | }, function(err) {
184 | next(err);
185 | });
186 | }
187 | };
188 |
--------------------------------------------------------------------------------
/demo-project/packages/app/webapp/controller/Object.controller.js:
--------------------------------------------------------------------------------
1 | sap.ui.define([
2 | "./BaseController",
3 | "sap/ui/model/json/JSONModel",
4 | "sap/ui/core/routing/History",
5 | "../model/formatter"
6 | ], function (BaseController, JSONModel, History, formatter) {
7 | "use strict";
8 |
9 | return BaseController.extend("ui5con20.app.controller.Object", {
10 |
11 | formatter: formatter,
12 |
13 | /* =========================================================== */
14 | /* lifecycle methods */
15 | /* =========================================================== */
16 |
17 | /**
18 | * Called when the worklist controller is instantiated.
19 | * @public
20 | */
21 | onInit : function () {
22 | // Model used to manipulate control states. The chosen values make sure,
23 | // detail page is busy indication immediately so there is no break in
24 | // between the busy indication for loading the view's meta data
25 | var iOriginalBusyDelay,
26 | oViewModel = new JSONModel({
27 | busy : true,
28 | delay : 0
29 | });
30 |
31 | this.getRouter().getRoute("object").attachPatternMatched(this._onObjectMatched, this);
32 |
33 | // Store original busy indicator delay, so it can be restored later on
34 | iOriginalBusyDelay = this.getView().getBusyIndicatorDelay();
35 | this.setModel(oViewModel, "objectView");
36 | oViewModel.setProperty("/delay", iOriginalBusyDelay);
37 |
38 | this.initChart();
39 | },
40 |
41 | initChart: function() {
42 | var oChart = this.byId("chart");
43 | oChart.attachChartInitialized(function(oEvent) {
44 | var chart = oEvent.getParameter("chart");
45 | var dur = 1000;
46 | var easing = Chartist.Svg.Easing.easeOutQuint;
47 | var animationDone;
48 | function draw(data) {
49 | var begin;
50 | if (data.type === "line" || data.type === "area") {
51 | begin = 100 * data.index;
52 | data.element.animate({
53 | d: {
54 | begin: begin,
55 | dur: dur,
56 | from: data.path.clone().scale(1, 0).translate(0, data.chartRect.height()).stringify(),
57 | to: data.path.clone().stringify(),
58 | easing: easing
59 | }
60 | });
61 | } else if (data.type === "point") {
62 | begin = 100 * data.seriesIndex;
63 | var animation = {
64 | begin: begin,
65 | dur: dur,
66 | from: data.axisY.chartRect.height(),
67 | to: data.y,
68 | easing: easing
69 | };
70 | data.element.animate({
71 | // Need to use shallow clone as the object is being modified by the animate function
72 | y1: Object.assign({}, animation),
73 | y2: Object.assign({}, animation)
74 | });
75 | }
76 | if (animationDone) {
77 | clearTimeout(animationDone);
78 | }
79 | animationDone = setTimeout(removeDrawHandler, begin + dur);
80 | }
81 | function removeDrawHandler() {
82 | chart.off("draw", draw);
83 | }
84 |
85 | chart.on("draw", draw);
86 | });
87 | },
88 |
89 | /* =========================================================== */
90 | /* event handlers */
91 | /* =========================================================== */
92 |
93 |
94 | /**
95 | * Event handler for navigating back.
96 | * It there is a history entry we go one step back in the browser history
97 | * If not, it will replace the current entry of the browser history with the worklist route.
98 | * @public
99 | */
100 | onNavBack : function() {
101 | var sPreviousHash = History.getInstance().getPreviousHash();
102 |
103 | if (sPreviousHash !== undefined) {
104 | history.go(-1);
105 | } else {
106 | this.getRouter().navTo("worklist", {}, true);
107 | }
108 | },
109 |
110 | /* =========================================================== */
111 | /* internal methods */
112 | /* =========================================================== */
113 |
114 | /**
115 | * Binds the view to the object path.
116 | * @function
117 | * @param {sap.ui.base.Event} oEvent pattern match event in route 'object'
118 | * @private
119 | */
120 | _onObjectMatched : function (oEvent) {
121 | var sObjectId = oEvent.getParameter("arguments").objectId;
122 | this._bindView("/Repositories/" + sObjectId);
123 | },
124 |
125 | /**
126 | * Binds the view to the object path.
127 | * @function
128 | * @param {string} sObjectPath path to the object to be bound
129 | * @private
130 | */
131 | _bindView : function (sObjectPath) {
132 | this.getView().bindElement({
133 | path: sObjectPath,
134 | events: {
135 | change: this._onBindingChange.bind(this)
136 | }
137 | });
138 | },
139 |
140 | _onBindingChange : function () {
141 | var oView = this.getView(),
142 | oViewModel = this.getModel("objectView"),
143 | oElementBinding = oView.getElementBinding();
144 |
145 | // No data for the binding
146 | if (!oElementBinding.getBoundContext()) {
147 | this.getRouter().getTargets().display("objectNotFound");
148 | return;
149 | }
150 |
151 | var oResourceBundle = this.getResourceBundle(),
152 | oObject = oView.getBindingContext().getObject(),
153 | sObjectId = oObject.repositoryName;
154 |
155 | oViewModel.setProperty("/busy", false);
156 |
157 | oViewModel.setProperty("/shareSendEmailSubject",
158 | oResourceBundle.getText("shareSendEmailObjectSubject", [sObjectId]));
159 | oViewModel.setProperty("/shareSendEmailMessage",
160 | oResourceBundle.getText("shareSendEmailObjectMessage", [sObjectId, location.href]));
161 | }
162 |
163 | });
164 |
165 | });
166 |
--------------------------------------------------------------------------------
/demo-nodejs-api/webapp/controller/Object.controller.js:
--------------------------------------------------------------------------------
1 | sap.ui.define([
2 | "./BaseController",
3 | "sap/ui/model/json/JSONModel",
4 | "sap/ui/core/routing/History",
5 | "../model/formatter"
6 | ], function (BaseController, JSONModel, History, formatter) {
7 | "use strict";
8 |
9 | return BaseController.extend("ui5con20.demo.nodejs.controller.Object", {
10 |
11 | formatter: formatter,
12 |
13 | /* =========================================================== */
14 | /* lifecycle methods */
15 | /* =========================================================== */
16 |
17 | /**
18 | * Called when the worklist controller is instantiated.
19 | * @public
20 | */
21 | onInit : function () {
22 | // Model used to manipulate control states. The chosen values make sure,
23 | // detail page is busy indication immediately so there is no break in
24 | // between the busy indication for loading the view's meta data
25 | var iOriginalBusyDelay,
26 | oViewModel = new JSONModel({
27 | busy : true,
28 | delay : 0
29 | });
30 |
31 | this.getRouter().getRoute("object").attachPatternMatched(this._onObjectMatched, this);
32 |
33 | // Store original busy indicator delay, so it can be restored later on
34 | iOriginalBusyDelay = this.getView().getBusyIndicatorDelay();
35 | this.setModel(oViewModel, "objectView");
36 | oViewModel.setProperty("/delay", iOriginalBusyDelay);
37 |
38 | this.initChart();
39 | },
40 |
41 | initChart: function() {
42 | var oChart = this.byId("chart");
43 | oChart.attachChartInitialized(function(oEvent) {
44 | var chart = oEvent.getParameter("chart");
45 | var dur = 1000;
46 | var easing = Chartist.Svg.Easing.easeOutQuint;
47 | var animationDone;
48 | function draw(data) {
49 | var begin;
50 | if(data.type === "line" || data.type === "area") {
51 | begin = 100 * data.index;
52 | data.element.animate({
53 | d: {
54 | begin,
55 | dur,
56 | from: data.path.clone().scale(1, 0).translate(0, data.chartRect.height()).stringify(),
57 | to: data.path.clone().stringify(),
58 | easing
59 | }
60 | });
61 | } else if (data.type === "point") {
62 | begin = 100 * data.seriesIndex;
63 | var animation = {
64 | begin,
65 | dur,
66 | from: data.axisY.chartRect.height(),
67 | to: data.y,
68 | easing
69 | };
70 | data.element.animate({
71 | // Need to use shallow clone as the object is being modified by the animate function
72 | y1: Object.assign({}, animation),
73 | y2: Object.assign({}, animation)
74 | });
75 | }
76 | if (animationDone) {
77 | clearTimeout(animationDone);
78 | }
79 | animationDone = setTimeout(removeDrawHandler, begin + dur);
80 | }
81 | function removeDrawHandler() {
82 | chart.off("draw", draw);
83 | }
84 |
85 | chart.on("draw", draw);
86 | });
87 | },
88 |
89 | /* =========================================================== */
90 | /* event handlers */
91 | /* =========================================================== */
92 |
93 |
94 | /**
95 | * Event handler for navigating back.
96 | * It there is a history entry we go one step back in the browser history
97 | * If not, it will replace the current entry of the browser history with the worklist route.
98 | * @public
99 | */
100 | onNavBack : function() {
101 | var sPreviousHash = History.getInstance().getPreviousHash();
102 |
103 | if (sPreviousHash !== undefined) {
104 | history.go(-1);
105 | } else {
106 | this.getRouter().navTo("worklist", {}, true);
107 | }
108 | },
109 |
110 | /* =========================================================== */
111 | /* internal methods */
112 | /* =========================================================== */
113 |
114 | /**
115 | * Binds the view to the object path.
116 | * @function
117 | * @param {sap.ui.base.Event} oEvent pattern match event in route 'object'
118 | * @private
119 | */
120 | _onObjectMatched : function (oEvent) {
121 | var sObjectId = oEvent.getParameter("arguments").objectId;
122 | this._bindView("/Repositories/" + sObjectId);
123 | },
124 |
125 | /**
126 | * Binds the view to the object path.
127 | * @function
128 | * @param {string} sObjectPath path to the object to be bound
129 | * @private
130 | */
131 | _bindView : function (sObjectPath) {
132 | var oViewModel = this.getModel("objectView"),
133 | oDataModel = this.getModel();
134 |
135 | this.getView().bindElement({
136 | path: sObjectPath,
137 | events: {
138 | change: this._onBindingChange.bind(this)
139 | }
140 | });
141 | },
142 |
143 | _onBindingChange : function () {
144 | var oView = this.getView(),
145 | oViewModel = this.getModel("objectView"),
146 | oElementBinding = oView.getElementBinding();
147 |
148 | // No data for the binding
149 | if (!oElementBinding.getBoundContext()) {
150 | this.getRouter().getTargets().display("objectNotFound");
151 | return;
152 | }
153 |
154 | var oResourceBundle = this.getResourceBundle(),
155 | oObject = oView.getBindingContext().getObject(),
156 | sObjectId = oObject.repositoryName;
157 |
158 | oViewModel.setProperty("/busy", false);
159 |
160 | oViewModel.setProperty("/shareSendEmailSubject",
161 | oResourceBundle.getText("shareSendEmailObjectSubject", [sObjectId]));
162 | oViewModel.setProperty("/shareSendEmailMessage",
163 | oResourceBundle.getText("shareSendEmailObjectMessage", [sObjectId, location.href]));
164 | }
165 |
166 | });
167 |
168 | });
169 |
--------------------------------------------------------------------------------
/demo-project/packages/app/webapp/controller/Worklist.controller.js:
--------------------------------------------------------------------------------
1 | sap.ui.define([
2 | "./BaseController",
3 | "sap/ui/model/json/JSONModel",
4 | "../model/formatter",
5 | "sap/ui/model/Filter",
6 | "sap/ui/model/FilterOperator"
7 | ], function (BaseController, JSONModel, formatter, Filter, FilterOperator) {
8 | "use strict";
9 |
10 | return BaseController.extend("ui5con20.app.controller.Worklist", {
11 |
12 | formatter: formatter,
13 |
14 | /* =========================================================== */
15 | /* lifecycle methods */
16 | /* =========================================================== */
17 |
18 | /**
19 | * Called when the worklist controller is instantiated.
20 | * @public
21 | */
22 | onInit : function () {
23 | var oViewModel,
24 | iOriginalBusyDelay,
25 | oTable = this.byId("table");
26 |
27 | // Put down worklist table's original value for busy indicator delay,
28 | // so it can be restored later on. Busy handling on the table is
29 | // taken care of by the table itself.
30 | iOriginalBusyDelay = oTable.getBusyIndicatorDelay();
31 | // keeps the search state
32 | this._aTableSearchState = [];
33 |
34 | // Model used to manipulate control states
35 | oViewModel = new JSONModel({
36 | worklistTableTitle : this.getResourceBundle().getText("worklistTableTitle"),
37 | shareOnJamTitle: this.getResourceBundle().getText("worklistTitle"),
38 | shareSendEmailSubject: this.getResourceBundle().getText("shareSendEmailWorklistSubject"),
39 | shareSendEmailMessage: this.getResourceBundle().getText("shareSendEmailWorklistMessage", [location.href]),
40 | tableNoDataText : this.getResourceBundle().getText("tableNoDataText"),
41 | tableBusyDelay : 0
42 | });
43 | this.setModel(oViewModel, "worklistView");
44 |
45 | // Make sure, busy indication is showing immediately so there is no
46 | // break after the busy indication for loading the view's meta data is
47 | // ended (see promise 'oWhenMetadataIsLoaded' in AppController)
48 | oTable.attachEventOnce("updateFinished", function(){
49 | // Restore original busy indicator delay for worklist's table
50 | oViewModel.setProperty("/tableBusyDelay", iOriginalBusyDelay);
51 | });
52 | },
53 |
54 | /* =========================================================== */
55 | /* event handlers */
56 | /* =========================================================== */
57 |
58 | /**
59 | * Triggered by the table's 'updateFinished' event: after new table
60 | * data is available, this handler method updates the table counter.
61 | * This should only happen if the update was successful, which is
62 | * why this handler is attached to 'updateFinished' and not to the
63 | * table's list binding's 'dataReceived' method.
64 | * @param {sap.ui.base.Event} oEvent the update finished event
65 | * @public
66 | */
67 | onUpdateFinished : function (oEvent) {
68 | // update the worklist's object counter after the table update
69 | var sTitle,
70 | oTable = oEvent.getSource(),
71 | iTotalItems = oEvent.getParameter("total");
72 | // only update the counter if the length is final and
73 | // the table is not empty
74 | if (iTotalItems && oTable.getBinding("items").isLengthFinal()) {
75 | sTitle = this.getResourceBundle().getText("worklistTableTitleCount", [iTotalItems]);
76 | } else {
77 | sTitle = this.getResourceBundle().getText("worklistTableTitle");
78 | }
79 | this.getModel("worklistView").setProperty("/worklistTableTitle", sTitle);
80 | },
81 |
82 | /**
83 | * Event handler when a table item gets pressed
84 | * @param {sap.ui.base.Event} oEvent the table selectionChange event
85 | * @public
86 | */
87 | onPress : function (oEvent) {
88 | // The source is the list item that got pressed
89 | this._showObject(oEvent.getSource());
90 | },
91 |
92 | /**
93 | * Event handler for navigating back.
94 | * We navigate back in the browser history
95 | * @public
96 | */
97 | onNavBack : function() {
98 | history.go(-1);
99 | },
100 |
101 | onSearch : function (oEvent) {
102 | if (oEvent.getParameters().refreshButtonPressed) {
103 | // Search field's 'refresh' button has been pressed.
104 | // This is visible if you select any master list item.
105 | // In this case no new search is triggered, we only
106 | // refresh the list binding.
107 | this.onRefresh();
108 | } else {
109 | var aTableSearchState = [];
110 | var sQuery = oEvent.getParameter("query");
111 |
112 | if (sQuery && sQuery.length > 0) {
113 | aTableSearchState = [new Filter("repositoryName", FilterOperator.Contains, sQuery)];
114 | }
115 | this._applySearch(aTableSearchState);
116 | }
117 |
118 | },
119 |
120 | /**
121 | * Event handler for refresh event. Keeps filter, sort
122 | * and group settings and refreshes the list binding.
123 | * @public
124 | */
125 | onRefresh : function () {
126 | var oTable = this.byId("table");
127 | oTable.getBinding("items").refresh();
128 | },
129 |
130 | /* =========================================================== */
131 | /* internal methods */
132 | /* =========================================================== */
133 |
134 | /**
135 | * Shows the selected item on the object page
136 | * On phones a additional history entry is created
137 | * @param {sap.m.ObjectListItem} oItem selected Item
138 | * @private
139 | */
140 | _showObject : function (oItem) {
141 | this.getRouter().navTo("object", {
142 | objectId: oItem.getBindingContext().getPath().replace("/Repositories/", "")
143 | });
144 | },
145 |
146 | /**
147 | * Internal helper method to apply both filter and search state together on the list binding
148 | * @param {sap.ui.model.Filter[]} aTableSearchState An array of filters for the search
149 | * @private
150 | */
151 | _applySearch: function(aTableSearchState) {
152 | var oTable = this.byId("table"),
153 | oViewModel = this.getModel("worklistView");
154 | oTable.getBinding("items").filter(aTableSearchState, "Application");
155 | // changes the noDataText of the list in case there are no filter results
156 | if (aTableSearchState.length !== 0) {
157 | oViewModel.setProperty("/tableNoDataText", this.getResourceBundle().getText("worklistNoDataWithSearchText"));
158 | }
159 | }
160 |
161 | });
162 | });
163 |
--------------------------------------------------------------------------------
/demo-nodejs-api/webapp/controller/Worklist.controller.js:
--------------------------------------------------------------------------------
1 | sap.ui.define([
2 | "./BaseController",
3 | "sap/ui/model/json/JSONModel",
4 | "../model/formatter",
5 | "sap/ui/model/Filter",
6 | "sap/ui/model/FilterOperator"
7 | ], function (BaseController, JSONModel, formatter, Filter, FilterOperator) {
8 | "use strict";
9 |
10 | return BaseController.extend("ui5con20.demo.nodejs.controller.Worklist", {
11 |
12 | formatter: formatter,
13 |
14 | /* =========================================================== */
15 | /* lifecycle methods */
16 | /* =========================================================== */
17 |
18 | /**
19 | * Called when the worklist controller is instantiated.
20 | * @public
21 | */
22 | onInit : function () {
23 | var oViewModel,
24 | iOriginalBusyDelay,
25 | oTable = this.byId("table");
26 |
27 | // Put down worklist table's original value for busy indicator delay,
28 | // so it can be restored later on. Busy handling on the table is
29 | // taken care of by the table itself.
30 | iOriginalBusyDelay = oTable.getBusyIndicatorDelay();
31 | // keeps the search state
32 | this._aTableSearchState = [];
33 |
34 | // Model used to manipulate control states
35 | oViewModel = new JSONModel({
36 | worklistTableTitle : this.getResourceBundle().getText("worklistTableTitle"),
37 | shareOnJamTitle: this.getResourceBundle().getText("worklistTitle"),
38 | shareSendEmailSubject: this.getResourceBundle().getText("shareSendEmailWorklistSubject"),
39 | shareSendEmailMessage: this.getResourceBundle().getText("shareSendEmailWorklistMessage", [location.href]),
40 | tableNoDataText : this.getResourceBundle().getText("tableNoDataText"),
41 | tableBusyDelay : 0
42 | });
43 | this.setModel(oViewModel, "worklistView");
44 |
45 | // Make sure, busy indication is showing immediately so there is no
46 | // break after the busy indication for loading the view's meta data is
47 | // ended (see promise 'oWhenMetadataIsLoaded' in AppController)
48 | oTable.attachEventOnce("updateFinished", function(){
49 | // Restore original busy indicator delay for worklist's table
50 | oViewModel.setProperty("/tableBusyDelay", iOriginalBusyDelay);
51 | });
52 | },
53 |
54 | /* =========================================================== */
55 | /* event handlers */
56 | /* =========================================================== */
57 |
58 | /**
59 | * Triggered by the table's 'updateFinished' event: after new table
60 | * data is available, this handler method updates the table counter.
61 | * This should only happen if the update was successful, which is
62 | * why this handler is attached to 'updateFinished' and not to the
63 | * table's list binding's 'dataReceived' method.
64 | * @param {sap.ui.base.Event} oEvent the update finished event
65 | * @public
66 | */
67 | onUpdateFinished : function (oEvent) {
68 | // update the worklist's object counter after the table update
69 | var sTitle,
70 | oTable = oEvent.getSource(),
71 | iTotalItems = oEvent.getParameter("total");
72 | // only update the counter if the length is final and
73 | // the table is not empty
74 | if (iTotalItems && oTable.getBinding("items").isLengthFinal()) {
75 | sTitle = this.getResourceBundle().getText("worklistTableTitleCount", [iTotalItems]);
76 | } else {
77 | sTitle = this.getResourceBundle().getText("worklistTableTitle");
78 | }
79 | this.getModel("worklistView").setProperty("/worklistTableTitle", sTitle);
80 | },
81 |
82 | /**
83 | * Event handler when a table item gets pressed
84 | * @param {sap.ui.base.Event} oEvent the table selectionChange event
85 | * @public
86 | */
87 | onPress : function (oEvent) {
88 | // The source is the list item that got pressed
89 | this._showObject(oEvent.getSource());
90 | },
91 |
92 | /**
93 | * Event handler for navigating back.
94 | * We navigate back in the browser history
95 | * @public
96 | */
97 | onNavBack : function() {
98 | // eslint-disable-next-line sap-no-history-manipulation
99 | history.go(-1);
100 | },
101 |
102 |
103 | onSearch : function (oEvent) {
104 | if (oEvent.getParameters().refreshButtonPressed) {
105 | // Search field's 'refresh' button has been pressed.
106 | // This is visible if you select any master list item.
107 | // In this case no new search is triggered, we only
108 | // refresh the list binding.
109 | this.onRefresh();
110 | } else {
111 | var aTableSearchState = [];
112 | var sQuery = oEvent.getParameter("query");
113 |
114 | if (sQuery && sQuery.length > 0) {
115 | aTableSearchState = [new Filter("repositoryName", FilterOperator.Contains, sQuery)];
116 | }
117 | this._applySearch(aTableSearchState);
118 | }
119 |
120 | },
121 |
122 | /**
123 | * Event handler for refresh event. Keeps filter, sort
124 | * and group settings and refreshes the list binding.
125 | * @public
126 | */
127 | onRefresh : function () {
128 | var oTable = this.byId("table");
129 | oTable.getBinding("items").refresh();
130 | },
131 |
132 | /* =========================================================== */
133 | /* internal methods */
134 | /* =========================================================== */
135 |
136 | /**
137 | * Shows the selected item on the object page
138 | * On phones a additional history entry is created
139 | * @param {sap.m.ObjectListItem} oItem selected Item
140 | * @private
141 | */
142 | _showObject : function (oItem) {
143 | this.getRouter().navTo("object", {
144 | objectId: oItem.getBindingContext().getPath().replace("/Repositories/", "")
145 | });
146 | },
147 |
148 | /**
149 | * Internal helper method to apply both filter and search state together on the list binding
150 | * @param {sap.ui.model.Filter[]} aTableSearchState An array of filters for the search
151 | * @private
152 | */
153 | _applySearch: function(aTableSearchState) {
154 | var oTable = this.byId("table"),
155 | oViewModel = this.getModel("worklistView");
156 | oTable.getBinding("items").filter(aTableSearchState, "Application");
157 | // changes the noDataText of the list in case there are no filter results
158 | if (aTableSearchState.length !== 0) {
159 | oViewModel.setProperty("/tableNoDataText", this.getResourceBundle().getText("worklistNoDataWithSearchText"));
160 | }
161 | }
162 |
163 | });
164 | });
165 |
--------------------------------------------------------------------------------
/demo-project/packages/app/webapp/test/integration/pages/Worklist.js:
--------------------------------------------------------------------------------
1 | sap.ui.define([
2 | "sap/ui/test/Opa5",
3 | "sap/ui/test/actions/Press",
4 | "sap/ui/test/actions/EnterText",
5 | "sap/ui/test/matchers/AggregationLengthEquals",
6 | "sap/ui/test/matchers/AggregationFilled",
7 | "sap/ui/test/matchers/PropertyStrictEquals",
8 | "./Common",
9 | "./shareOptions"
10 | ], function(Opa5, Press, EnterText, AggregationLengthEquals, AggregationFilled, PropertyStrictEquals, Common, shareOptions) {
11 | "use strict";
12 |
13 | var sViewName = "Worklist",
14 | sTableId = "table",
15 | sSearchFieldId = "searchField",
16 | sSomethingThatCannotBeFound = "*#-Q@@||";
17 |
18 | function allItemsInTheListContainTheSearchTerm (aControls) {
19 | var oTable = aControls[0],
20 | oSearchField = aControls[1],
21 | aItems = oTable.getItems();
22 |
23 | // table needs items
24 | if (aItems.length === 0) {
25 | return false;
26 | }
27 |
28 | return aItems.every(function (oItem) {
29 | return oItem.getCells()[0].getTitle().indexOf(oSearchField.getValue()) !== -1;
30 | });
31 | }
32 |
33 | function createWaitForItemAtPosition (oOptions) {
34 | var iPosition = oOptions.position;
35 | return {
36 | id : sTableId,
37 | viewName : sViewName,
38 | matchers : function (oTable) {
39 | return oTable.getItems()[iPosition];
40 | },
41 | actions : oOptions.actions,
42 | success : oOptions.success,
43 | errorMessage : "Table in view '" + sViewName + "' does not contain an Item at position '" + iPosition + "'"
44 | };
45 | }
46 |
47 | Opa5.createPageObjects({
48 |
49 | onTheWorklistPage : {
50 | baseClass : Common,
51 | actions : Object.assign({
52 | iPressATableItemAtPosition : function (iPosition) {
53 | return this.waitFor(createWaitForItemAtPosition({
54 | position : iPosition,
55 | actions : new Press()
56 | }));
57 | },
58 |
59 | iRememberTheItemAtPosition : function (iPosition){
60 | return this.waitFor(createWaitForItemAtPosition({
61 | position : iPosition,
62 | success : function (oTableItem) {
63 | var oBindingContext = oTableItem.getBindingContext();
64 |
65 | // Don't remember objects just strings since IE will not allow accessing objects of destroyed frames
66 | this.getContext().currentItem = {
67 | bindingPath: oBindingContext.getPath(),
68 | id: oBindingContext.getProperty("Pony"),
69 | name: oBindingContext.getProperty("Pony")
70 | };
71 | }
72 | }));
73 | },
74 |
75 | iPressOnMoreData : function (){
76 | return this.waitFor({
77 | id : sTableId,
78 | viewName : sViewName,
79 | matchers : function (oTable) {
80 | return !!oTable.$("trigger").length;
81 | },
82 | actions : new Press(),
83 | errorMessage : "The Table does not have a trigger"
84 | });
85 | },
86 |
87 | iSearchForTheFirstObject: function() {
88 | var sFirstObjectTitle;
89 |
90 | return this.waitFor({
91 | id: sTableId,
92 | viewName: sViewName,
93 | matchers: new AggregationFilled({
94 | name: "items"
95 | }),
96 | success: function(oTable) {
97 | sFirstObjectTitle = oTable.getItems()[0].getCells()[0].getTitle();
98 |
99 | this.iSearchForValue(sFirstObjectTitle);
100 |
101 | this.waitFor({
102 | id: [sTableId, sSearchFieldId],
103 | viewName: sViewName,
104 | check : allItemsInTheListContainTheSearchTerm,
105 | errorMessage: "Did not find any table entries or too many while trying to search for the first object."
106 | });
107 | },
108 | errorMessage: "Did not find table entries while trying to search for the first object."
109 | });
110 | },
111 |
112 | iSearchForValueWithActions : function (aActions) {
113 | return this.waitFor({
114 | id : sSearchFieldId,
115 | viewName : sViewName,
116 | actions: aActions,
117 | errorMessage : "Failed to find search field in Worklist view.'"
118 | });
119 | },
120 |
121 | iSearchForValue : function (sSearchString) {
122 | return this.iSearchForValueWithActions([new EnterText({text : sSearchString}), new Press()]);
123 | },
124 |
125 | iTypeSomethingInTheSearchThatCannotBeFoundAndTriggerRefresh : function () {
126 | var fnEnterTextAndFireRefreshButtonPressedOnSearchField = function (oSearchField) {
127 | // set the search field value directly as EnterText action triggers a search event
128 | oSearchField.setValue(sSomethingThatCannotBeFound);
129 |
130 | // fire the search to simulate a refresh button press
131 | oSearchField.fireSearch({refreshButtonPressed: true});
132 | };
133 | return this.iSearchForValueWithActions(fnEnterTextAndFireRefreshButtonPressedOnSearchField);
134 | },
135 |
136 | iClearTheSearch : function () {
137 | return this.iSearchForValueWithActions([new EnterText({text: ""}), new Press()]);
138 | },
139 |
140 | iSearchForSomethingWithNoResults : function () {
141 | return this.iSearchForValueWithActions([new EnterText({text: sSomethingThatCannotBeFound}), new Press()]);
142 | }
143 |
144 | }, shareOptions.createActions(sViewName)),
145 |
146 | assertions: Object.assign({
147 |
148 | iShouldSeeTheTable : function () {
149 | return this.waitFor({
150 | id : sTableId,
151 | viewName : sViewName,
152 | success : function (oTable) {
153 | Opa5.assert.ok(oTable, "Found the object Table");
154 | },
155 | errorMessage : "Can't see the master Table."
156 | });
157 | },
158 |
159 | theTableShowsOnlyObjectsWithTheSearchStringInTheirTitle : function () {
160 | this.waitFor({
161 | id : [sTableId, sSearchFieldId],
162 | viewName : sViewName,
163 | check: allItemsInTheListContainTheSearchTerm,
164 | success : function () {
165 | Opa5.assert.ok(true, "Every item did contain the title");
166 | },
167 | errorMessage : "The table did not have items"
168 | });
169 | },
170 |
171 | theTableHasEntries : function () {
172 | return this.waitFor({
173 | viewName : sViewName,
174 | id : sTableId,
175 | matchers : new AggregationFilled({
176 | name : "items"
177 | }),
178 | success : function () {
179 | Opa5.assert.ok(true, "The table has entries");
180 | },
181 | errorMessage : "The table had no entries"
182 | });
183 | },
184 |
185 | theTableShouldHaveAllEntries : function () {
186 | var aAllEntities,
187 | iExpectedNumberOfItems;
188 |
189 | // retrieve all Pony to be able to check for the total amount
190 | this.waitFor(this.createAWaitForAnEntitySet({
191 | entitySet: "Pony",
192 | success: function (aEntityData) {
193 | aAllEntities = aEntityData;
194 | }
195 | }));
196 |
197 | return this.waitFor({
198 | id : sTableId,
199 | viewName : sViewName,
200 | matchers : function (oTable) {
201 | // If there are less items in the list than the growingThreshold, only check for this number.
202 | iExpectedNumberOfItems = Math.min(oTable.getGrowingThreshold(), aAllEntities.length);
203 | return new AggregationLengthEquals({name : "items", length : iExpectedNumberOfItems}).isMatching(oTable);
204 | },
205 | success : function (oTable) {
206 | Opa5.assert.strictEqual(oTable.getItems().length, iExpectedNumberOfItems, "The growing Table has " + iExpectedNumberOfItems + " items");
207 | },
208 | errorMessage : "Table does not have all entries."
209 | });
210 | },
211 |
212 | theTitleShouldDisplayTheTotalAmountOfItems : function () {
213 | return this.waitFor({
214 | id : sTableId,
215 | viewName : sViewName,
216 | matchers : new AggregationFilled({name : "items"}),
217 | success : function (oTable) {
218 | var iObjectCount = oTable.getBinding("items").getLength();
219 | this.waitFor({
220 | id : "tableHeader",
221 | viewName : sViewName,
222 | matchers : function (oPage) {
223 | var sExpectedText = oPage.getModel("i18n").getResourceBundle().getText("worklistTableTitleCount", [iObjectCount]);
224 | return new PropertyStrictEquals({name : "text", value: sExpectedText}).isMatching(oPage);
225 | },
226 | success : function () {
227 | Opa5.assert.ok(true, "The Page has a title containing the number " + iObjectCount);
228 | },
229 | errorMessage : "The Page's header does not container the number of items " + iObjectCount
230 | });
231 | },
232 | errorMessage : "The table has no items."
233 | });
234 | },
235 |
236 | theTableShouldHaveTheDoubleAmountOfInitialEntries : function () {
237 | var iExpectedNumberOfItems;
238 |
239 | return this.waitFor({
240 | id : sTableId,
241 | viewName : sViewName,
242 | matchers : function (oTable) {
243 | iExpectedNumberOfItems = oTable.getGrowingThreshold() * 2;
244 | return new AggregationLengthEquals({name : "items", length : iExpectedNumberOfItems}).isMatching(oTable);
245 | },
246 | success : function () {
247 | Opa5.assert.ok(true, "The growing Table had the double amount: " + iExpectedNumberOfItems + " of entries");
248 | },
249 | errorMessage : "Table does not have the double amount of entries."
250 | });
251 | },
252 |
253 | theTableShouldContainOnlyFormattedUnitNumbers : function () {
254 | return this.theUnitNumbersShouldHaveTwoDecimals("sap.m.ObjectNumber",
255 | sViewName,
256 | "Object numbers are properly formatted",
257 | "Table has no entries which can be checked for their formatting");
258 | },
259 |
260 | iShouldSeeTheWorklistViewsBusyIndicator : function () {
261 | return this.waitFor({
262 | id : "page",
263 | viewName : sViewName,
264 | success : function (oPage) {
265 | Opa5.assert.ok(oPage.getParent().getBusy(), "The worklist view is busy");
266 | },
267 | errorMessage : "The worklist view is not busy"
268 | });
269 | },
270 |
271 | iShouldSeeTheWorklistTableBusyIndicator : function () {
272 | return this.waitFor({
273 | id : "table",
274 | viewName : sViewName,
275 | matchers : new PropertyStrictEquals({
276 | name : "busy",
277 | value: true
278 | }),
279 | autoWait : false,
280 | success : function () {
281 | Opa5.assert.ok(true, "The worklist table is busy");
282 | },
283 | errorMessage : "The worklist table is not busy"
284 | });
285 | },
286 |
287 | iShouldSeeTheNoDataTextForNoSearchResults : function () {
288 | return this.waitFor({
289 | id : sTableId,
290 | viewName : sViewName,
291 | success : function (oTable) {
292 | Opa5.assert.strictEqual(
293 | oTable.getNoDataText(),
294 | oTable.getModel("i18n").getProperty("worklistNoDataWithSearchText"),
295 | "the table should show the no data text for search");
296 | },
297 | errorMessage : "table does not show the no data text for search"
298 | });
299 | }
300 | }, shareOptions.createAssertions(sViewName))
301 |
302 | }
303 |
304 | });
305 |
306 | });
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------