├── .devcontainer ├── Dockerfile └── devcontainer.json ├── .gitignore ├── LICENSE ├── README.md ├── _config.yml ├── de.tammenit.uhdapp ├── . eslintignore ├── .__eslintrc__ ├── .editorconfig ├── .eslintrc.js ├── .gitignore ├── .npmrc ├── .prettierrc.js ├── .vscode │ ├── launch.json │ └── tasks.json ├── .yo-rc.json ├── cf_deployment_resources │ ├── package.json │ └── xs-app.json ├── karma-ci.conf.js ├── karma.conf.js ├── mta.yaml ├── package.json ├── tsconfig.json ├── typings │ └── UHDApp.d.ts ├── ui5.yaml └── webapp │ ├── Component.ts │ ├── controller │ ├── App.controller.ts │ ├── BaseController.ts │ ├── Main.controller.ts │ ├── NotFound.controller.ts │ └── blocks │ │ └── main │ │ ├── BlockAssignedRoles.controller.ts │ │ ├── BlockLDAP.controller.ts │ │ ├── BlockOpenRequests.controller.ts │ │ └── BlockPersonalData.controller.ts │ ├── controls │ └── StripToaster.js │ ├── css │ └── style.css │ ├── formatter │ └── formatter.ts │ ├── i18n │ ├── i18n.properties │ ├── i18n_de.properties │ └── i18n_en.properties │ ├── index.html │ ├── localService │ └── mockserver.ts │ ├── manifest.json │ ├── model │ ├── getUserList.json │ └── models.ts │ ├── resources │ └── img │ │ └── favicon.ico │ ├── test │ ├── initMockServer.ts │ ├── integration │ │ ├── AllJourneys.js │ │ ├── BasicJourney.js │ │ ├── arrangements │ │ │ └── Startup.js │ │ ├── opaTests.qunit.html │ │ ├── opaTests.qunit.js │ │ └── pages │ │ │ └── App.js │ ├── mockserver.html │ ├── testsuite.qunit.html │ └── testsuite.qunit.js │ └── view │ ├── App.view.xml │ ├── Main.view.xml │ ├── NotFound.view.xml │ └── blocks │ └── main │ ├── BlockAssignedRoles.ts │ ├── BlockAssignedRoles.view.xml │ ├── BlockLDAP.ts │ ├── BlockLDAP.view.xml │ ├── BlockOpenRequests.ts │ ├── BlockOpenRequests.view.xml │ ├── BlockPersonalData.ts │ └── BlockPersonalData.view.xml ├── presi ├── PresenterNotes.md ├── README.html ├── README.md ├── images │ ├── function.png │ ├── interface.png │ └── venn.png └── runPresi.sh ├── tssandbox ├── .vscode │ └── tasks.json ├── function.js ├── function.ts ├── greetServer.js ├── greetServer.ts ├── greetings.js ├── greetings.ts ├── interface.js ├── interface.ts └── tsconfig.json └── ui5con_brussels.code-workspace /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------------------------------------- 2 | # Copyright (c) Microsoft Corporation. All rights reserved. 3 | # Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. 4 | #------------------------------------------------------------------------------------------------------------- 5 | 6 | FROM node:10 7 | 8 | # Avoid warnings by switching to noninteractive 9 | ENV DEBIAN_FRONTEND=noninteractive 10 | 11 | # The node image includes a non-root user with sudo access. Use the "remoteUser" 12 | # property in devcontainer.json to use it. On Linux, the container user's GID/UIDs 13 | # will be updated to match your local UID/GID (when using the dockerFile property). 14 | # See https://aka.ms/vscode-remote/containers/non-root-user for details. 15 | ARG USERNAME=node 16 | ARG USER_UID=1000 17 | ARG USER_GID=$USER_UID 18 | 19 | # Configure apt and install packages 20 | RUN apt-get update \ 21 | && apt-get -y install --no-install-recommends apt-utils dialog 2>&1 \ 22 | # 23 | # Verify git and needed tools are installed 24 | && apt-get -y install git iproute2 procps \ 25 | # 26 | # Remove outdated yarn from /opt and install via package 27 | # so it can be easily updated via apt-get upgrade yarn 28 | && rm -rf /opt/yarn-* \ 29 | && rm -f /usr/local/bin/yarn \ 30 | && rm -f /usr/local/bin/yarnpkg \ 31 | && apt-get install -y curl apt-transport-https lsb-release \ 32 | && curl -sS https://dl.yarnpkg.com/$(lsb_release -is | tr '[:upper:]' '[:lower:]')/pubkey.gpg | apt-key add - 2>/dev/null \ 33 | && echo "deb https://dl.yarnpkg.com/$(lsb_release -is | tr '[:upper:]' '[:lower:]')/ stable main" | tee /etc/apt/sources.list.d/yarn.list \ 34 | && apt-get update \ 35 | && apt-get -y install --no-install-recommends yarn \ 36 | # 37 | # Install tslint and typescript globally 38 | && npm install -g tslint eslint typescript \ 39 | # 40 | # [Optional] Update a non-root user to UID/GID if needed. 41 | && if [ "$USER_GID" != "1000" ] || [ "$USER_UID" != "1000" ]; then \ 42 | groupmod --gid $USER_GID $USERNAME \ 43 | && usermod --uid $USER_UID --gid $USER_GID $USERNAME \ 44 | && chown -R $USER_UID:$USER_GID /home/$USERNAME; \ 45 | fi \ 46 | # [Optional] Add add sudo support for non-root user 47 | && apt-get install -y sudo \ 48 | && echo node ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \ 49 | && chmod 0440 /etc/sudoers.d/$USERNAME \ 50 | # 51 | # Clean up 52 | && apt-get autoremove -y \ 53 | && apt-get clean -y \ 54 | && rm -rf /var/lib/apt/lists/* 55 | 56 | # Switch back to dialog for any ad-hoc use of apt-get 57 | ENV DEBIAN_FRONTEND=dialog 58 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/vscode-remote/devcontainer.json or the definition README at 2 | // https://github.com/microsoft/vscode-dev-containers/tree/master/containers/typescript-node-10 3 | { 4 | "name": "Node.js 10 & TypeScript", 5 | "dockerFile": "Dockerfile", 6 | 7 | // Use 'settings' to set *default* container specific settings.json values on container create. 8 | // You can edit these settings after create using File > Preferences > Settings > Remote. 9 | "settings": { 10 | "terminal.integrated.shell.linux": "/bin/bash" 11 | }, 12 | 13 | // Use 'appPort' to create a container with published ports. If the port isn't working, be sure 14 | // your server accepts connections from all interfaces (0.0.0.0 or '*'), not just localhost. 15 | "appPort": [8080], 16 | 17 | // Uncomment the next line to run commands after the container is created. 18 | // "postCreateCommand": "yarn install", 19 | 20 | // Uncomment the next line to have VS Code connect as an existing non-root user in the container. 21 | // On Linux, by default, the container user's UID/GID will be updated to match your local user. See 22 | // https://aka.ms/vscode-remote/containers/non-root for details on adding a non-root user if none exist. 23 | // "remoteUser": "node", 24 | 25 | "mounts": [ 26 | ], 27 | 28 | "extensions": [ 29 | "dbaeumer.vscode-eslint", 30 | 31 | // TSLint is included for backwards compatibility, but is deprecated. 32 | // See https://github.com/palantir/tslint/issues/4534 33 | "ms-vscode.vscode-typescript-tslint-plugin" 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | interfaces.md 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Helmut Tammen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UI5con_typescript 2 | This is the material of my **UI5 with Typescript** talk I gave at UI5Con 2020 in Brussels. 3 | 4 | ## Workspace structure 5 | ### Folder de.tammenit.uhdapp 6 | In this folder you find the sourcecode of my example project I used to demonstrate the usage of typescript in UI5 development. 7 | This app is a very little application that I`ve written for the user help desk of a company. The UHD agents can enter a username, 8 | id, email address in a search field. When they press enter or select a user with the mouse they get information about this user 9 | in an Object page layout view. 10 | All shown data is dynamically created by a mockserver. 11 | 12 | Start the application by entering `npm start` at the command prompt. 13 | 14 | ### Folder presi 15 | In this folder you find the presentation source. To read the content of the presentation just open the `README.md` file either 16 | with any text editor, with a markdown rendering tool or simply go to the [github repo](https://github.com/htammen/ui5con_typescript/tree/master/presi). 17 | 18 | Btw. I named this folder presi cause I used the markdown presentation software [markpress](https://github.com/gamell/markpress) which in turn uses [impress](https://github.com/impress/impress.js/). 19 | Impress implemented some of the features the presentation tool [Prezi](https://prezi.com/) offers which I used for former presentations. 20 | So I thought naming the folder **pre~~z~~si** was a good idea. 21 | 22 | ### Folder tssandbox 23 | The folder tssandbox contains some simple core Typescript examples 24 | 25 | ### Folder .devcontainer 26 | If you have installed the [VS Code Remote Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) extension you are automatically asked if you want to open this workspace in an isolated docker container as soon as you open the workspace file [ui5con_brussels.code-workspace](https://github.com/htammen/ui5con_typescript/blob/master/ui5con_brussels.code-workspace) in VS Code. 27 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-hacker -------------------------------------------------------------------------------- /de.tammenit.uhdapp/. eslintignore: -------------------------------------------------------------------------------- 1 | # don't ever lint node_modules 2 | node_modules 3 | # don't lint build output (make sure it's set to your correct build folder name) 4 | dist 5 | # don't lint nyc coverage output 6 | coverage -------------------------------------------------------------------------------- /de.tammenit.uhdapp/.__eslintrc__: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/.editorconfig: -------------------------------------------------------------------------------- 1 | # see http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = tab 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | # npm is using 2 spaces when modifying package.json 13 | [package.json] 14 | indent_style = space 15 | indent_size = 2 16 | 17 | [*.md] 18 | trim_trailing_whitespace = false 19 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | plugins: [ 5 | '@typescript-eslint', 6 | ], 7 | extends: [ 8 | 'eslint:recommended', 9 | 'plugin:@typescript-eslint/eslint-recommended', 10 | 'plugin:@typescript-eslint/recommended', 11 | 'prettier/@typescript-eslint', // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier 12 | 'plugin:prettier/recommended', // Enables eslint-plugin-prettier and eslint-config-prettier. This will display prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. 13 | ], 14 | "env": { 15 | "browser": true 16 | }, 17 | "globals": { 18 | "sap": true, 19 | "jQuery": true 20 | }, 21 | rules: { 22 | '@typescript-eslint/ban-ts-ignore': 'warn', 23 | } 24 | }; -------------------------------------------------------------------------------- /de.tammenit.uhdapp/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .svn/ 3 | .idea/ 4 | .mta/ 5 | *.mtar 6 | dist/ 7 | package-lock.json 8 | webapp/Component.js 9 | webapp/**/*.js.map 10 | interfaces.pdf 11 | webapp/controller/App.controller.js 12 | webapp/controller/BaseController.js 13 | webapp/controller/Main.controller.js 14 | webapp/localService/mockserver.js 15 | webapp/model/models.js 16 | webapp/test/initMockServer.js 17 | webapp/controller/NotFound.controller.js 18 | webapp/view/blocks/main/BlockAssignedRoles.js 19 | webapp/controller/blocks/main/BlockAssignedRoles.controller.js 20 | webapp/view/blocks/main/BlockOpenRequests.js 21 | webapp/controller/blocks/main/BlockOpenRequests.controller.js 22 | webapp/controller/blocks/main/BlockPersonalData.controller.js 23 | webapp/view/blocks/main/BlockPersonalData.js 24 | webapp/controller/blocks/main/BlockLDAP.controller.js 25 | webapp/formatter/formatter.js 26 | webapp/view/blocks/main/BlockLDAP.js 27 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/.npmrc: -------------------------------------------------------------------------------- 1 | @sap:registry=https://npm.sap.com/ 2 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/.prettierrc.js: -------------------------------------------------------------------------------- 1 | // see https://www.robertcooper.me/using-eslint-and-prettier-in-a-typescript-project for config details 2 | module.exports = { 3 | semi: false, 4 | trailingComma: 'es5', 5 | singleQuote: true, 6 | printWidth: 120, 7 | tabWidth: 2, 8 | } 9 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Verwendet IntelliSense zum Ermitteln möglicher Attribute. 3 | // Zeigen Sie auf vorhandene Attribute, um die zugehörigen Beschreibungen anzuzeigen. 4 | // Weitere Informationen finden Sie unter https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "chrome", 9 | "request": "launch", 10 | "name": "Launch Chrome", 11 | "url": "http://localhost:8080/test/mockserver.html?sap-ui-logLevel=ERROR", 12 | "webRoot": "${workspaceFolder}/webapp" 13 | }, 14 | { 15 | "type": "node", 16 | "request": "launch", 17 | "name": "Launch Program", 18 | "program": "${workspaceFolder}\\index.html", 19 | "outFiles": [ 20 | "${workspaceFolder}/**/*.js" 21 | ] 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /de.tammenit.uhdapp/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // Unter https://go.microsoft.com/fwlink/?LinkId=733558 3 | // finden Sie Informationen zum Format von "tasks.json" 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "typescript", 8 | "tsconfig": "tsconfig.json", 9 | "option": "watch", 10 | "problemMatcher": [ 11 | "$tsc-watch" 12 | ], 13 | "group": { 14 | "kind": "build", 15 | "isDefault": true 16 | }, 17 | "runOptions": { 18 | "runOn": "folderOpen" 19 | } 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-easy-ui5": { 3 | "projectname": "UHDApp", 4 | "namespace": "de.tammenit", 5 | "viewtype": "XML", 6 | "viewname": "Main" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/cf_deployment_resources/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "UHDApp", 3 | "version": "0.0.1", 4 | "engines": { 5 | "node": ">=10.0.0" 6 | }, 7 | "scripts": { 8 | "start": "node node_modules/@sap/approuter/approuter.js" 9 | }, 10 | "dependencies": { 11 | "@sap/approuter": "^5.13.0" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/cf_deployment_resources/xs-app.json: -------------------------------------------------------------------------------- 1 | { 2 | "welcomeFile": "/index.html", 3 | "authenticationMethod": "none", 4 | "logout": { 5 | "logoutEndpoint": "/do/logout" 6 | }, 7 | "routes": [ 8 | { 9 | "source": "^/(.*)$", 10 | "target": "$1", 11 | "localDir": "." 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/karma-ci.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Wed Jun 13 2018 14:38:44 GMT+0200 (CEST) 3 | 4 | module.exports = function(config) { 5 | require("./karma.conf")(config); 6 | config.set({ 7 | 8 | client: { 9 | qunit: { 10 | showUI: false 11 | } 12 | }, 13 | 14 | // test results reporter to use 15 | // possible values: 'dots', 'progress', 'coverage' 16 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 17 | reporters: ['progress'], 18 | 19 | // start these browsers 20 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 21 | browsers: ['ChromeHeadless'], 22 | 23 | // Continuous Integration mode 24 | // if true, Karma captures browsers, runs the tests and exits 25 | singleRun: true 26 | 27 | }); 28 | }; 29 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Wed Jun 13 2018 14:38:44 GMT+0200 (CEST) 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | 7 | // base path that will be used to resolve all patterns (eg. files, exclude) 8 | basePath: 'webapp', 9 | 10 | // frameworks to use 11 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 12 | frameworks: ['qunit', 'openui5'], 13 | 14 | openui5: { 15 | path: 'http://localhost:8080/resources/sap-ui-core.js' 16 | }, 17 | 18 | client: { 19 | openui5: { 20 | config: { 21 | theme: 'sap_belize', 22 | language: 'EN', 23 | bindingSyntax: 'complex', 24 | compatVersion: 'edge', 25 | async: true, 26 | resourceroots: { 'de.tammenit.UHDApp': './base' } 27 | }, 28 | tests: [ 29 | 'de/tammenit/UHDApp/test/integration/AllJourneys' 30 | ] 31 | }, 32 | clearContext: false, 33 | qunit: { 34 | showUI: true 35 | } 36 | }, 37 | 38 | // list of files / patterns to load in the browser 39 | files: [ 40 | { pattern: '**', included: false, served: true, watched: true } 41 | ], 42 | 43 | // test results reporter to use 44 | // possible values: 'dots', 'progress' 45 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 46 | reporters: ['progress'], 47 | 48 | // level of logging 49 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 50 | logLevel: config.LOG_INFO, 51 | 52 | // level of browser logging 53 | browserConsoleLogOptions: { 54 | level: 'error' 55 | }, 56 | 57 | // enable / disable watching file and executing tests whenever any file changes 58 | autoWatch: true, 59 | 60 | // start these browsers 61 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 62 | browsers: ['Chrome'], 63 | 64 | // Continuous Integration mode 65 | // if true, Karma captures browsers, runs the tests and exits 66 | singleRun: false 67 | 68 | }); 69 | }; 70 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/mta.yaml: -------------------------------------------------------------------------------- 1 | ID: UHDApp 2 | _schema-version: 3.2.0 3 | description: Enter description here 4 | version: 0.0.1 5 | 6 | modules: 7 | - name: UHDApp 8 | type: nodejs 9 | path: dist 10 | parameters: 11 | disk-quota: 512M 12 | memory: 512M 13 | requires: 14 | - name: dest_UHDApp 15 | resources: 16 | - name: dest_UHDApp 17 | parameters: 18 | service-plan: lite 19 | service: destination 20 | type: org.cloudfoundry.managed-service 21 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "uhd-app", 3 | "author": { 4 | "name": "Helmut Tammen", 5 | "email": "h.tammen@tammen-it-solutions.de", 6 | "url": "https://htammen.github.io/" 7 | }, 8 | "version": "1.0.0", 9 | "license": "MIT", 10 | "keywords": [ 11 | "uhd", 12 | "user help desk", 13 | "support", 14 | "ui5con", 15 | "typescript" 16 | ], 17 | "description": "A program to demonstrate the use of TypeScript with openUI5", 18 | "engines": { 19 | "node": ">=10.0.0", 20 | "npm": ">=6.13.0", 21 | "yarn": ">=1.21.0" 22 | }, 23 | "scripts": { 24 | "start": "ui5 serve --accept-remote-connections true -o index.html", 25 | "serve": "ui5 serve", 26 | "test": "npm run lint && npm run karma", 27 | "testlocal": "npm run karmalocal", 28 | "karma-ci": "karma start karma-ci.conf.js", 29 | "karma": "rimraf coverage && start-server-and-test serve http://localhost:8080 karma-ci", 30 | "karmalocal": "rimraf coverage && start-server-and-test serve http://localhost:8080 karma-local", 31 | "lint": "eslint webapp", 32 | "build-ui": "rimraf dist && tsc --build tsconfig.json && ui5 build --a", 33 | "build-ui-dev": "rimraf dist && tsc --build tsconfig.json && ui5 build dev --a --dev-exclude-project=sap.ui.core sap.m sap.f sap.ui.layout sap.uxap themelib_sap_belize themelib_sap_fiori_3", 34 | "build-sc": "rimraf dist && tsc --build tsconfig.json && ui5 build self-contained --all --dest=dist", 35 | "package-cf": "npm run build-ui && cp cf_deployment_resources/package.json dist/package.json && cp cf_deployment_resources/xs-app.json dist/xs-app.json && mbt init && make -f Makefile.mta p=cf", 36 | "deploy-cf": "npm run package-cf && cross-var cf deploy mta_archives/UHDApp_$npm_package_version.mtar", 37 | "cf-log": "cf logs UHDApp" 38 | }, 39 | "dependencies": { 40 | "@openui5/sap.m": "^1.67.1", 41 | "@openui5/sap.ui.core": "^1.67.1", 42 | "@openui5/sap.ui.layout": "^1.67.1", 43 | "@openui5/sap.uxap": "^1.67.1", 44 | "@openui5/themelib_sap_belize": "^1.67.1", 45 | "@openui5/themelib_sap_fiori_3": "^1.67.1" 46 | }, 47 | "devDependencies": { 48 | "@openui5/ts-types": "^1.65.1", 49 | "@typescript-eslint/eslint-plugin": "^2.13.0", 50 | "@typescript-eslint/parser": "^2.13.0", 51 | "@ui5/cli": "^1.5.5", 52 | "cross-var": "^1.1.0", 53 | "eslint": "^6.8.0", 54 | "karma": "^3.1.3", 55 | "karma-chrome-launcher": "^2.2.0", 56 | "karma-coverage": "^1.1.2", 57 | "karma-openui5": "^0.2.3", 58 | "karma-qunit": "^1.2.1", 59 | "ncp": "^2.0.0", 60 | "qunitjs": "^2.4.1", 61 | "rimraf": "^2.6.2", 62 | "start-server-and-test": "^1.9.1", 63 | "prettier": "^1.19.1", 64 | "eslint-config-prettier": "^6.7.0", 65 | "eslint-plugin-prettier": "^3.1.1", 66 | "typescript": "^3.7.4" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "none", 4 | "target": "es5", 5 | "sourceMap": true, 6 | "noEmit": false, 7 | "downlevelIteration": true, 8 | "checkJs": false, 9 | "allowJs": false, 10 | "lib": ["dom", "es5", "es2016"], 11 | "types": ["@openui5/ts-types"] 12 | }, 13 | "include": [ 14 | "./webapp/**/*.ts", 15 | "./typings/**/*" 16 | ], 17 | "exclude": ["./node_modules/**/*"] 18 | } 19 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/typings/UHDApp.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-use-before-define */ 2 | declare namespace de { 3 | namespace tammenit { 4 | namespace UHDApp { 5 | export class Component extends sap.ui.core.UIComponent { 6 | /** 7 | * returns the main Model of the application 8 | * 9 | * @returns {sap.ui.model.json.JSONModel} the main model of the application 10 | * @memberof Component 11 | */ 12 | public getMainModel(): sap.ui.model.json.JSONModel 13 | 14 | /** 15 | * Displays a Message in a StripToaster 16 | * 17 | * @param {string} msg 18 | * @param {sap.ui.core.MessageType} type 19 | * @memberof Component 20 | */ 21 | public displayMessage(msg: string, type?: sap.ui.core.MessageType): void 22 | 23 | /** 24 | * Reads a list of userdata from the backend. This can be displayed e.g. as value help to help the UHD employee 25 | * finding a concrete user 26 | * The input can either be a userId, a part of a userId, an email or a part of an email. The backend is supposed 27 | * to search with "contains" 28 | */ 29 | public getUserList(input: data.UserId | data.Email): Promise> 30 | /** 31 | * Reads data for a single user with all the information a UHD employee should be able to see. 32 | * This is some personal information, open application requests, assigned roles and registration/ldap information 33 | * @param userId - userid which is used on the backend to find the user 34 | * @returns a promise that resolves to a UHDUserData object 35 | */ 36 | public getUserData(userId: string): Promise 37 | } 38 | 39 | namespace controller { 40 | export class BaseController extends sap.ui.core.mvc.Controller { 41 | /** 42 | * Convenience method for accessing the router in every controller of the application. 43 | * @public 44 | * @returns {sap.ui.core.routing.Router} the router for this component 45 | */ 46 | getRouter(): sap.ui.core.routing.Router 47 | /** 48 | * Convenience method for getting the view model by name in every controller of the application. 49 | * @public 50 | * @param {string} sName the model name 51 | * @returns {sap.ui.model.json.JSONModel} the model instance 52 | */ 53 | getModel(sName?: string): sap.ui.model.json.JSONModel 54 | /** 55 | * Convenience method for setting the view model in every controller of the application. 56 | * @public 57 | * @param {sap.ui.model.json.JSONModel} oModel the model instance 58 | * @param {string} sName the model name 59 | * @returns {sap.ui.mvc.View} the view instance 60 | */ 61 | setModel(oModel: sap.ui.model.json.JSONModel, sName: string): void 62 | /** 63 | * Convenience method for getting the resource bundle. 64 | * @public 65 | * @returns {sap.base.i18n.ResourceBundle} the resourceBundle of the component 66 | */ 67 | getResourceBundle(): { 68 | getText(key: string, aArgs?: Array, bIgnoreKeyFallback?: boolean): string 69 | setText(value: string): void 70 | } 71 | /** 72 | * Returns the Name of the current component as defined when creating it 73 | * @returns {String} name of the component 74 | */ 75 | _getComponentName(): string 76 | /** 77 | * Event handler for navigating back. 78 | * It checks if there is a history entry. If yes, history.go(-1) will happen. 79 | * If not, it will replace the current entry of the browser history with the master route. 80 | * @public 81 | */ 82 | onNavBack(): void 83 | /** 84 | * gets the application main component. Technically its the return value of UIComponent.getOwnerComponent 85 | * casted to the concrete class of ...UHDApp.Component 86 | * 87 | * @returns {de.tammenit.UHDApp.Component} 88 | * @memberof BaseController 89 | */ 90 | getOwnerAppComponent(): Component 91 | /** 92 | * Displays a Message in a StripToaster. Convinience method that just calls the same named metho 93 | * in the OwnerComponent 94 | * 95 | * @param {string} msg 96 | * @param {sap.ui.core.MessageType} type 97 | * @memberof Component 98 | */ 99 | public displayMessage(msg: string, type: sap.ui.core.MessageType): void 100 | } 101 | } 102 | 103 | namespace formatter { 104 | export class Formatter { 105 | public formatCreateDate(date: Date): string 106 | } 107 | } 108 | 109 | namespace data { 110 | /** Type alias for userId */ 111 | type UserId = string 112 | /** Type alias for email address */ 113 | type Email = string 114 | 115 | /** 116 | * enum for the status of a request 117 | * 118 | * @enum {number} 119 | */ 120 | enum RequestStatus { 121 | NOT_ASSIGNED = 0, 122 | REQUESTED = 1, 123 | ASSIGNED = 2, 124 | REJECTED = 3, 125 | REMOVED = 4, 126 | } 127 | 128 | /** 129 | * Simple JSON result from backend. In some cases we just receive the string 130 | * "success", "error" or a message that describes the error in more Detail e.g. 131 | * "Data could not be loaded". 132 | * The latter case schould only be an exception cause the backend shouldn't deliver 133 | * texts but just status codes 134 | */ 135 | export interface SimpleJSONResult { 136 | /** result as string e.g. success or error */ 137 | result: string 138 | /** a more detailed message that describes the error or the result */ 139 | msg?: string 140 | } 141 | 142 | /** 143 | * Struktur, die eine Rollenbeantragung oder Rollenabmeldung beinhaltet 144 | */ 145 | export interface Request { 146 | /** eindeutige ID der Anwendung */ 147 | appId: string 148 | /** Anwendungsname */ 149 | appName: string 150 | /** Anwendungsbeschreibung */ 151 | appDescription: string 152 | /** eindeutige Rollen-Id */ 153 | roleId: string 154 | /** Rollenname */ 155 | roleName: string 156 | /** Rollenbeschreibung */ 157 | roleDescription: string 158 | } 159 | 160 | /** 161 | * Personal data of a user 162 | */ 163 | export interface UserData { 164 | userId: UserId 165 | title: string 166 | firstName: string 167 | lastName: string 168 | email: Email 169 | company: string 170 | location: string 171 | } 172 | 173 | /** 174 | * Date structure for applications that are available for user, admin, ... 175 | */ 176 | export interface ApplicationData { 177 | /** application id */ 178 | appId: string 179 | /** application name */ 180 | appName: string 181 | /** application description */ 182 | appDescription: string 183 | } 184 | 185 | /** 186 | * Eine Rolle in einer Anwendung 187 | */ 188 | export interface Role { 189 | /** eindeutige ID der Anwendung */ 190 | appId: string 191 | /** Anwendungsname */ 192 | appName: string 193 | /** Anwendungsbeschreibung */ 194 | appDescription: string 195 | /** eindeutige Rollen-Id */ 196 | roleId: string 197 | /** Rollenname */ 198 | roleName: string 199 | /** Rollenbeschreibung */ 200 | roleDescription: string 201 | } 202 | 203 | /** 204 | * Informationen zur Registrierung 205 | */ 206 | export interface Registration { 207 | /** Registrierungsdatum. Dieses wird vom Backend nur gefüllt, wenn der User sich registriert hat, die Registrierung aber noch abgeschlossen wurde. 208 | * Der User ist also noch nicht im LDAP angelegt. Ist der User schon im LDAP angelegt, wird hier 1970-01-01T00:00:00.000Z zurückgegeben 209 | * Datum incl. Zeit 210 | */ 211 | created: Date 212 | } 213 | 214 | /** 215 | * Informationen über den LDAP Status des Users 216 | */ 217 | export interface LDAP { 218 | /** Anlegedatum incl. Zeit der Anlage des Users im LDAP. Ist der User noch nicht im LDAP angelegt, wird 1970-01-01T00:00:00.000Z zurückgegeben. */ 219 | created: Date 220 | /** ist der User z.Zt. aktiv im LDAP? */ 221 | active: boolean 222 | } 223 | 224 | /** 225 | * The data for a single user read from the backend 226 | * Contains personal information, open requests, assigned roles and registration information 227 | */ 228 | export interface UHDData { 229 | /** Personal information about the user */ 230 | userData?: UserData 231 | /** List of all application requests the user has opened */ 232 | openRequests?: Array 233 | /** List of all roles the user is assigned to */ 234 | roles?: Array 235 | /** Registration data */ 236 | registration?: Registration 237 | /** LDAP Status Informationen */ 238 | ldap?: LDAP 239 | } 240 | 241 | /** 242 | * Daten, die im Main Model der Anwendung gehalten werden 243 | */ 244 | export interface MainModelData { 245 | uhdUserData?: UHDData 246 | } 247 | } 248 | } 249 | } 250 | } 251 | 252 | declare namespace sap.ui.base.input { 253 | namespace event { 254 | interface Lifechange extends sap.ui.base.Event { 255 | getSource(): sap.ui.base.EventProvider 256 | getParameter(param: 'value' | 'escPressed' | 'previousValue'): string | boolean 257 | } 258 | } 259 | } 260 | declare namespace sap.m.ListItemBase { 261 | namespace event { 262 | interface Press extends sap.ui.base.Event { 263 | getSource(): sap.ui.base.EventProvider 264 | // getParameter(param) 265 | } 266 | } 267 | } 268 | 269 | declare namespace sap.ui.core.routing.target.Event { 270 | interface Display extends sap.ui.base.Event { 271 | getSource(): sap.ui.base.EventProvider 272 | getParameter(param: 'view' | 'control' | 'config' | 'data'): object 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/ui5.yaml: -------------------------------------------------------------------------------- 1 | specVersion: '1.0' 2 | metadata: 3 | name: UHDApp 4 | type: application 5 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/webapp/Component.ts: -------------------------------------------------------------------------------- 1 | sap.ui.define( 2 | [ 3 | 'sap/ui/core/UIComponent', 4 | 'sap/ui/Device', 5 | 'sap/ui/model/json/JSONModel', 6 | 'de/tammenit/UHDApp/model/models', 7 | 'sap/base/Log', 8 | 'sap/ui/core/Popup', 9 | 'de/tammenit/UHDApp/controls/StripToaster', 10 | ], 11 | function( 12 | UIComponent: typeof sap.ui.core.UIComponent, 13 | Device: typeof sap.ui.Device, 14 | JSONModel: typeof sap.ui.model.json.JSONModel, 15 | models, 16 | Log, 17 | Popup: typeof sap.ui.core.Popup, 18 | StripToaster 19 | ) { 20 | 'use strict' 21 | 22 | /** 23 | * Component Controller 24 | */ 25 | class Component extends UIComponent { 26 | private _logger 27 | private _myTestTSCVar 28 | private mainModel: sap.ui.model.json.JSONModel 29 | 30 | constructor(mSettings: object) { 31 | // it's important to call the extend method here. It creates metadata that is used in UI5 apps via 32 | // the method getMetadata. Hence we also assign this method to the prototype of our class. 33 | const fnClass = UIComponent.extend('de.tammenit.UHDApp.Component', { 34 | metadata: { 35 | manifest: 'json', 36 | }, 37 | }) 38 | Component.prototype.getMetadata = fnClass.prototype.getMetadata 39 | 40 | super('de.tammenit.UHDApp.Component', mSettings) 41 | } 42 | 43 | /** 44 | * The component is initialized by UI5 automatically during the startup of the app and calls the init method once. 45 | * @public 46 | * @override 47 | */ 48 | init(): void { 49 | // call the base component's init function 50 | // eslint-disable-next-line prefer-rest-params 51 | UIComponent.prototype.init.apply(this, arguments) 52 | 53 | this._logger = Log.getLogger('de.tammenit.UHDApp.Component') 54 | // enable routing 55 | this.getRouter().initialize() 56 | 57 | // set the device model 58 | this.setModel(models.createDeviceModel(), 'device') 59 | 60 | // initialize the main model 61 | this.mainModel = new JSONModel({}, false) 62 | 63 | // are there startup parameters? 64 | // eslint-disable-next-line @typescript-eslint/no-empty-function 65 | this._analyseStartupParams().then(function(): void {}.bind(this)) 66 | } 67 | 68 | /** 69 | * checks if startup parameters were transmitted and sets them in model appParams 70 | */ 71 | private _analyseStartupParams(): Promise { 72 | return new Promise((resolve /*, reject */) => { 73 | const appParamsModel = new JSONModel({}, false) 74 | this.setModel(appParamsModel, 'appParams') 75 | 76 | const oComponentData = this.getComponentData() as { 77 | startupParameters: { webResourceURI: string; backendContextURI: string } 78 | } 79 | // if parameter direction was set on url we save this in a temporary model, otherwise we set PUSH as default 80 | if (oComponentData && oComponentData.startupParameters) { 81 | if (oComponentData.startupParameters.webResourceURI) { 82 | appParamsModel.setProperty('/webResourceURI', oComponentData.startupParameters.webResourceURI) 83 | } 84 | if (oComponentData.startupParameters.backendContextURI) { 85 | appParamsModel.setProperty('/backendContextURI', oComponentData.startupParameters.backendContextURI) 86 | } 87 | this._logger.info( 88 | 'app was started with parameters ' + JSON.stringify(oComponentData.startupParameters || {}) 89 | ) 90 | } 91 | resolve() 92 | }) 93 | } 94 | 95 | /** 96 | * Displays a Message in a StripToaster 97 | * 98 | * @param {string} msg 99 | * @param {sap.ui.core.MessageType} type 100 | * @memberof Component 101 | */ 102 | public displayMessage(msg: string, type?: sap.ui.core.MessageType, timeout?: number): void { 103 | // get string from resourceBundle. If not available text = msg 104 | const resBundle = (this.getModel('i18n') as sap.ui.model.resource.ResourceModel).getResourceBundle() 105 | let text = resBundle.getText(msg) 106 | text = text || msg 107 | type = type || sap.ui.core.MessageType.Error 108 | // set timeout to given value. If no value is given set to 10 seconds or eternal in case of error message 109 | timeout = timeout || (type === sap.ui.core.MessageType.Error ? -1 : 10000) 110 | StripToaster.notify({ 111 | //@ts-ignore 112 | text: text, 113 | timeOut: timeout, 114 | //@ts-ignore 115 | position: Popup.Dock.EndTop, 116 | type: type, 117 | offset: '-20 50', 118 | }) 119 | } 120 | 121 | /** 122 | * returns the main Model of the application 123 | * 124 | * @returns {sap.ui.model.json.JSONModel} the main model of the application 125 | * @memberof Component 126 | */ 127 | public getMainModel(): sap.ui.model.json.JSONModel { 128 | return this.mainModel 129 | } 130 | 131 | /** 132 | * gibt das Model mit den Startparametern der Anwendung zurück. 133 | * Die Daten des Models entsprechen der Struktur de.tammenit.SuperAdminApp.data.AppParamsModelData 134 | * 135 | * @returns {sap.ui.model.json.JSONModel} das appParams Model der Anwendung 136 | * @memberof Component 137 | */ 138 | public getAppParamsModel(): sap.ui.model.json.JSONModel { 139 | return this.getModel('appParams') as sap.ui.model.json.JSONModel 140 | } 141 | 142 | /** 143 | * Convinience Methode, die den Backend-Context aus dem appParams Model zurückgibt 144 | * @returns {string} 145 | * @memberof Component 146 | */ 147 | public getBackendContext(): string { 148 | return this.getAppParamsModel().getProperty('/backendContextURI') 149 | } 150 | 151 | /** 152 | * Liest eine Liste von UserDaten aus dem Backend, die als Wertehilfe zum Finden eines konkreten 153 | * Users verwendet werden kann. 154 | * Der input kann entweder ein Teil einer userId oder einer email-Adresse sein. Das Backend sollte in 155 | * beiden Felder mit contains suchen 156 | * @param {string} input 157 | * @returns {Promise} ein Promise, das ein Array von Userdaten enthält. 158 | */ 159 | public getUserList(input: string): Promise> { 160 | return new Promise((resolve, reject) => { 161 | const xhr = new XMLHttpRequest() 162 | xhr.open('GET', `${this.getBackendContext()}GetUserList?filter=${input}`) 163 | xhr.onload = function(): void { 164 | if (xhr.status === 200) { 165 | const rawData: any = JSON.parse(xhr.responseText) 166 | if (rawData && rawData.result && rawData.result === 'error') { 167 | if (rawData.msg) { 168 | this._logger.error(rawData.msg) 169 | reject(rawData.msg) 170 | } 171 | } else { 172 | let data: Array = JSON.parse(xhr.responseText) 173 | if (!Array.isArray(data)) { 174 | data = [] 175 | } 176 | 177 | resolve(data) 178 | } 179 | } else { 180 | this._logger.error(JSON.stringify(xhr.responseText)) 181 | reject(xhr.responseText) 182 | } 183 | }.bind(this) 184 | xhr.onerror = function(oEvent): void { 185 | reject('Error') 186 | } 187 | xhr.send() 188 | }) 189 | } 190 | 191 | /** 192 | * Liest einen User für den UHD komplett mit allen persönlichen Daten, offenen Requests, Rollen sowie Registrierungsinformationen 193 | * aus dem Backend aus. 194 | * @param userId - Eine Benutzer-Id, mit der das Backend den Benutzer finden kann 195 | * @returns Ein Promise, das zu einem UHDUserData ojbect resolved 196 | */ 197 | public getUserData(userId: string): Promise { 198 | return new Promise((resolve, reject) => { 199 | const xhr = new XMLHttpRequest() 200 | xhr.open('GET', `${this.getBackendContext()}GetUHDUserData?userId=${userId}`) 201 | xhr.onload = function() { 202 | if (xhr.status === 200) { 203 | const rawData: any = JSON.parse(xhr.responseText) 204 | if (rawData && rawData.result && rawData.result === 'error') { 205 | if (rawData.msg) { 206 | this._logger.error(rawData.msg) 207 | reject(rawData.msg) 208 | } 209 | } else { 210 | const data = rawData as de.tammenit.UHDApp.data.UHDData 211 | if (data.ldap.created && typeof data.ldap.created === 'string') { 212 | data.ldap.created = new Date(data.ldap.created) 213 | } 214 | if (data.registration.created && typeof data.registration.created === 'string') { 215 | data.registration.created = new Date(data.registration.created) 216 | } 217 | 218 | resolve(data) 219 | } 220 | } else { 221 | this._logger.error(JSON.stringify(xhr)) 222 | reject(xhr.statusText) 223 | } 224 | }.bind(this) 225 | xhr.onerror = function(): void { 226 | reject('Error') 227 | } 228 | xhr.send() 229 | }) 230 | } 231 | } 232 | 233 | return Component 234 | } 235 | ) 236 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/webapp/controller/App.controller.ts: -------------------------------------------------------------------------------- 1 | sap.ui.define(['de/tammenit/UHDApp/controller/BaseController'], function( 2 | Controller: typeof de.tammenit.UHDApp.controller.BaseController 3 | ) { 4 | 'use strict' 5 | 6 | class App extends Controller { 7 | constructor() { 8 | // it's important to call the extend method here. It creates metadata that is used in UI5 apps via 9 | // the method getMetadata. Hence we also assign this method to the prototype of our class. 10 | const fnClass = Controller.extend('de.tammenit.UHDApp.controller.App', {}) 11 | App.prototype.getMetadata = fnClass.prototype.getMetadata 12 | 13 | super('de.tammenit.UHDApp.controller.App') 14 | } 15 | 16 | // eslint-disable-next-line @typescript-eslint/no-empty-function 17 | public onInit(): void {} 18 | 19 | onAfterRendering(): void { 20 | const resourceBundle = (this.getOwnerComponent().getModel( 21 | 'i18n' 22 | ) as sap.ui.model.resource.ResourceModel).getResourceBundle() 23 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 24 | const text: string = resourceBundle.getText('title') 25 | } 26 | } 27 | 28 | return App 29 | }) 30 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/webapp/controller/BaseController.ts: -------------------------------------------------------------------------------- 1 | sap.ui.define( 2 | [ 3 | 'sap/ui/core/mvc/Controller', 4 | 'sap/ui/core/routing/History', 5 | 'de/tammenit/UHDApp/Component', 6 | 'sap/ui/model/json/JSONModel', 7 | ], 8 | function( 9 | Controller: typeof sap.ui.core.mvc.Controller, 10 | History: typeof sap.ui.core.routing.History, 11 | Component: typeof de.tammenit.UHDApp.Component, 12 | JSONModel: typeof sap.ui.model.json.JSONModel 13 | ) { 14 | 'use strict' 15 | 16 | class BaseController extends Controller { 17 | private model: sap.ui.model.json.JSONModel 18 | 19 | constructor() { 20 | // it's important to call the extend method here. It creates metadata that is used in UI5 apps via 21 | // the method getMetadata. Hence we also assign this method to the prototype of our class. 22 | const fnClass = Controller.extend('de.tammenit.UHDApp.controller.BaseController', {}) 23 | BaseController.prototype.getMetadata = fnClass.prototype.getMetadata 24 | 25 | super('de.tammenit.UHDApp.controller.BaseController') 26 | } 27 | 28 | // eslint-disable-next-line @typescript-eslint/no-empty-function 29 | public onInit(): void {} 30 | 31 | public getOwnerAppComponent(): de.tammenit.UHDApp.Component { 32 | // this double cast is weired but is documented here: https://basarat.gitbooks.io/typescript/docs/types/type-assertion.html 33 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 34 | const comp = (this.getOwnerComponent() as any) as de.tammenit.UHDApp.Component 35 | return comp 36 | } 37 | 38 | /** 39 | * get the Router for this view 40 | * 41 | * @returns {*} 42 | * @memberof BaseController 43 | */ 44 | getRouter(): sap.ui.core.routing.Router { 45 | return Component.getRouterFor(this) 46 | } 47 | 48 | getModel(sName?: string): sap.ui.model.json.JSONModel { 49 | if (sName) { 50 | return this.getView().getModel(sName) as sap.ui.model.json.JSONModel 51 | } else { 52 | return this.getView().getModel() as sap.ui.model.json.JSONModel 53 | } 54 | } 55 | 56 | getResourceBundle(): { getText(value: string): string; setText(value: string): void } { 57 | return (this.getOwnerAppComponent().getModel('i18n') as sap.ui.model.resource.ResourceModel).getResourceBundle() 58 | } 59 | 60 | /** 61 | * Displays a Message in a StripToaster. Convinience method that just calls the same named metho 62 | * in the OwnerComponent 63 | * 64 | * @param {string} msg 65 | * @param {sap.ui.core.MessageType} type 66 | * @memberof Component 67 | */ 68 | public displayMessage(msg: string, type: sap.ui.core.MessageType): void { 69 | this.getOwnerAppComponent().displayMessage(msg, type) 70 | } 71 | 72 | onNavBack(): void { 73 | const oHistory = History.getInstance() 74 | const sPreviousHash = oHistory.getPreviousHash() 75 | 76 | if (sPreviousHash !== undefined) { 77 | window.history.go(-1) 78 | } else { 79 | this.getRouter().navTo('RouteMain', {}, true /*no history*/) 80 | } 81 | } 82 | 83 | /** 84 | * Formatter für den Titel einer Tabelle. Der Formatter ermittelt die Anzahl Einträge in der Tabelle 85 | * @param list Liste der Einträge in der Tabelle 86 | */ 87 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 88 | private formatTableCount(list: Array): number { 89 | if (list) { 90 | return list.length 91 | } else { 92 | return 0 93 | } 94 | } 95 | } 96 | 97 | return BaseController 98 | } 99 | ) 100 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/webapp/controller/Main.controller.ts: -------------------------------------------------------------------------------- 1 | sap.ui.define( 2 | [ 3 | 'de/tammenit/UHDApp/controller/BaseController', 4 | 'sap/ui/model/json/JSONModel', 5 | 'sap/ui/core/format/DateFormat', 6 | 'sap/ui/core/Fragment', 7 | 'de/tammenit/UHDApp/formatter/formatter', 8 | ], 9 | function( 10 | BaseController: typeof de.tammenit.UHDApp.controller.BaseController, 11 | JSONModel: typeof sap.ui.model.json.JSONModel, 12 | DateFormat: typeof sap.ui.core.format.DateFormat, 13 | Fragment: typeof sap.ui.core.Fragment, 14 | Formatter: typeof de.tammenit.UHDApp.formatter.Formatter 15 | ) { 16 | 'use strict' 17 | 18 | enum LinkTargets { 19 | HOMEPAGE = 'homepage', 20 | } 21 | 22 | class Main extends BaseController { 23 | formatter: de.tammenit.UHDApp.formatter.Formatter 24 | 25 | constructor() { 26 | // it's important to call the extend method here. It creates metadata that is used in UI5 apps via 27 | // the method getMetadata. Hence we also assign this method to the prototype of our class. 28 | const fnClass = BaseController.extend('de.tammenit.UHDApp.controller.Main', {}) 29 | Main.prototype.getMetadata = fnClass.prototype.getMetadata 30 | 31 | super('de.tammenit.UHDApp.controller.Main') 32 | } 33 | 34 | public onInit(): void { 35 | this.formatter = new Formatter() 36 | 37 | this.getView().setModel( 38 | new JSONModel( 39 | { 40 | breadcrumbs: { 41 | currentText: this.getResourceBundle().getText('breadcrumb.page.main.title'), 42 | links: [ 43 | { 44 | text: this.getResourceBundle().getText('page.homepage'), 45 | linkTarget: LinkTargets.HOMEPAGE, 46 | }, 47 | ], 48 | }, 49 | showObjectPage: false, 50 | userList: [], 51 | }, 52 | false 53 | ), 54 | 'viewModel' 55 | ) 56 | // this.getView().setModel(this.getOwnerAppComponent().getMainModel()) 57 | this.getView().setModel(this.getOwnerAppComponent().getMainModel()) 58 | 59 | const oRouter = this.getRouter() 60 | const oTarget = oRouter.getTarget('TargetMain') as sap.ui.core.routing.Target 61 | oTarget.attachDisplay(this._handleDisplay.bind(this)) 62 | } 63 | 64 | /** 65 | * This eventhandler retrieves data that was sent from the calling view. This data is 66 | * e.g. the target from which this view was caled, the email address and the current appId. 67 | * @param oEvent 68 | */ 69 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 70 | private _handleDisplay(oEvent: sap.ui.core.routing.target.Event.Display): void { 71 | this.getView().bindObject({ path: '/' }) 72 | this.byId('idSearchField').focus({ preventScroll: false }) 73 | } 74 | 75 | onAfterRendering(): void { 76 | this.byId('idSearchField').focus({ preventScroll: false }) 77 | } 78 | 79 | /** 80 | * Eventhandler für das Press Event der Breadcrumb links 81 | * Jeder breadcrumb link hat eine custom property linkTarget, die hier ausgewertet wird, daraufhin 82 | * navigieren zu können. 83 | * @param oEvent 84 | */ 85 | public onBreadcrumbLinkPress(oEvent: sap.ui.base.Event): void { 86 | // eslint-disable-next-line @typescript-eslint/ban-ts-ignore 87 | // @ts-ignore 88 | const linkTarget: string = (oEvent.getSource() as sap.m.Link).data('linkTarget') 89 | switch (linkTarget) { 90 | case LinkTargets.HOMEPAGE: 91 | window.location.href = 'https://service.myapp.de' 92 | break 93 | } 94 | } 95 | 96 | public async onSuggest(oEvent: sap.ui.base.Event): Promise { 97 | const value: string = oEvent.getParameter('suggestValue') 98 | try { 99 | // yyy 100 | const userList = await this.getOwnerAppComponent().getUserList(value) 101 | this.getModel('viewModel').setProperty('/userList', userList) 102 | ;(this.byId('idSearchField') as sap.m.SearchField).suggest(true) 103 | } catch (err) { 104 | this.getOwnerAppComponent().displayMessage(err) 105 | this.getModel('viewModel').setProperty('/showObjectPage', false) 106 | } 107 | } 108 | 109 | public async onSearch(oEvent: sap.ui.base.Event): Promise { 110 | const key: string = oEvent.getParameter('query') 111 | if (key && key !== '') { 112 | try { 113 | // zzz 114 | //const uhdUserData = await this.getOwnerAppComponent().getUserData(key) 115 | const uhdUserData = await this.getOwnerAppComponent().getUserData(key) 116 | this.getOwnerAppComponent() 117 | .getMainModel() 118 | .setProperty('/userData', uhdUserData.userData) 119 | this.getOwnerAppComponent() 120 | .getMainModel() 121 | .setProperty('/openRequests', uhdUserData.openRequests) 122 | this.getOwnerAppComponent() 123 | .getMainModel() 124 | .setProperty('/roles', uhdUserData.roles) 125 | this.getOwnerAppComponent() 126 | .getMainModel() 127 | .setProperty('/ldap', uhdUserData.ldap) 128 | this.getOwnerAppComponent() 129 | .getMainModel() 130 | .setProperty('/registration', uhdUserData.registration) 131 | this.getModel('viewModel').setProperty('/showObjectPage', true) 132 | } catch (err) { 133 | this.getOwnerAppComponent().displayMessage(err) 134 | } 135 | } 136 | } 137 | 138 | public formatCreateDate(date: Date): string { 139 | return this.formatter.formatCreateDate(date) 140 | } 141 | } 142 | 143 | return Main 144 | } 145 | ) 146 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/webapp/controller/NotFound.controller.ts: -------------------------------------------------------------------------------- 1 | sap.ui.define(['de/tammenit/UHDApp/controller/BaseController'], function( 2 | Controller: typeof de.tammenit.UHDApp.controller.BaseController 3 | ) { 4 | 'use strict' 5 | 6 | class NotFound extends Controller { 7 | constructor() { 8 | // it's important to call the extend method here. It creates metadata that is used in UI5 apps via 9 | // the method getMetadata. Hence we also assign this method to the prototype of our class. 10 | const fnClass = Controller.extend('de.tammenit.UHDApp.controller.NotFound', {}) 11 | NotFound.prototype.getMetadata = fnClass.prototype.getMetadata 12 | 13 | super('de.tammenit.UHDApp.controller.NotFound') 14 | } 15 | 16 | public onInit(): void {} 17 | } 18 | 19 | return NotFound 20 | }) 21 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/webapp/controller/blocks/main/BlockAssignedRoles.controller.ts: -------------------------------------------------------------------------------- 1 | sap.ui.define( 2 | [ 3 | 'de/tammenit/UHDApp/controller/BaseController', 4 | 'sap/ui/model/json/JSONModel', 5 | 'sap/ui/core/format/DateFormat', 6 | 'sap/ui/core/Fragment', 7 | ], 8 | function( 9 | BaseController: typeof de.tammenit.UHDApp.controller.BaseController, 10 | JSONModel: typeof sap.ui.model.json.JSONModel, 11 | DateFormat: typeof sap.ui.core.format.DateFormat, 12 | Fragment: typeof sap.ui.core.Fragment 13 | ) { 14 | 'use strict' 15 | 16 | class BlockAssignedRoles extends BaseController { 17 | private model: sap.ui.model.json.JSONModel 18 | 19 | constructor() { 20 | // it's important to call the extend method here. It creates metadata that is used in UI5 apps via 21 | // the method getMetadata. Hence we also assign this method to the prototype of our class. 22 | const fnClass = BaseController.extend('de.tammenit.UHDApp.controller.BlockAssignedRoles', {}) 23 | BlockAssignedRoles.prototype.getMetadata = fnClass.prototype.getMetadata 24 | 25 | super('de.tammenit.UHDApp.controller.BlockAssignedRoles') 26 | } 27 | 28 | public onInit(): void { 29 | // @ts-ignore 30 | this.getView().setModel(this.getOwnerComponent().getMainModel()) 31 | this.getView().bindObject({ path: '/roles' }) 32 | } 33 | 34 | /** 35 | * Grouping Funktion. Die Rollentabelle wollen wir nach der appId gruppieren 36 | * @param oCtx 37 | */ 38 | getGroup(oCtx): string { 39 | return oCtx.getProperty('appId') 40 | } 41 | } 42 | 43 | return BlockAssignedRoles 44 | } 45 | ) 46 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/webapp/controller/blocks/main/BlockLDAP.controller.ts: -------------------------------------------------------------------------------- 1 | sap.ui.define( 2 | [ 3 | 'de/tammenit/UHDApp/controller/BaseController', 4 | 'sap/ui/model/json/JSONModel', 5 | 'sap/ui/core/format/DateFormat', 6 | 'sap/ui/core/Fragment', 7 | 'de/tammenit/UHDApp/formatter/formatter', 8 | ], 9 | function( 10 | BaseController: typeof de.tammenit.UHDApp.controller.BaseController, 11 | JSONModel: typeof sap.ui.model.json.JSONModel, 12 | DateFormat: typeof sap.ui.core.format.DateFormat, 13 | Fragment: typeof sap.ui.core.Fragment, 14 | Formatter: typeof de.tammenit.UHDApp.formatter.Formatter 15 | ) { 16 | 'use strict' 17 | 18 | class BlockLDAP extends BaseController { 19 | private model: sap.ui.model.json.JSONModel 20 | private formatter: de.tammenit.UHDApp.formatter.Formatter 21 | 22 | constructor() { 23 | // it's important to call the extend method here. It creates metadata that is used in UI5 apps via 24 | // the method getMetadata. Hence we also assign this method to the prototype of our class. 25 | const fnClass = BaseController.extend('de.tammenit.UHDApp.controller.BlockLDAP', {}) 26 | BlockLDAP.prototype.getMetadata = fnClass.prototype.getMetadata 27 | 28 | super('de.tammenit.UHDApp.controller.BlockLDAP') 29 | } 30 | 31 | public onInit(): void { 32 | this.formatter = new Formatter() 33 | // @ts-ignore 34 | this.getView().setModel(this.getOwnerComponent().getMainModel()) 35 | this.getView().bindObject({ path: '/ldap' }) 36 | } 37 | 38 | public formatCreateDate(date: Date): string { 39 | return this.formatter.formatCreateDate(date) 40 | } 41 | } 42 | 43 | return BlockLDAP 44 | } 45 | ) 46 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/webapp/controller/blocks/main/BlockOpenRequests.controller.ts: -------------------------------------------------------------------------------- 1 | sap.ui.define( 2 | [ 3 | 'de/tammenit/UHDApp/controller/BaseController', 4 | 'sap/ui/model/json/JSONModel', 5 | 'sap/ui/core/format/DateFormat', 6 | 'sap/ui/core/Fragment', 7 | ], 8 | function( 9 | BaseController: typeof de.tammenit.UHDApp.controller.BaseController, 10 | JSONModel: typeof sap.ui.model.json.JSONModel, 11 | DateFormat: typeof sap.ui.core.format.DateFormat, 12 | Fragment: typeof sap.ui.core.Fragment 13 | ) { 14 | 'use strict' 15 | 16 | class BlockOpenRequests extends BaseController { 17 | private model: sap.ui.model.json.JSONModel 18 | 19 | constructor() { 20 | // it's important to call the extend method here. It creates metadata that is used in UI5 apps via 21 | // the method getMetadata. Hence we also assign this method to the prototype of our class. 22 | const fnClass = BaseController.extend('de.tammenit.UHDApp.controller.BlockOpenRequests', {}) 23 | BlockOpenRequests.prototype.getMetadata = fnClass.prototype.getMetadata 24 | 25 | super('de.tammenit.UHDApp.controller.BlockOpenRequests') 26 | } 27 | 28 | public onInit(): void { 29 | // @ts-ignore 30 | this.getView().setModel(this.getOwnerComponent().getMainModel()) 31 | this.getView().bindObject({ path: '/openRequests' }) 32 | } 33 | 34 | /** 35 | * Grouping Funktion. Die Rollentabelle wollen wir nach der appId gruppieren 36 | * @param oCtx 37 | */ 38 | getGroup(oCtx): string { 39 | return oCtx.getProperty('appId') 40 | } 41 | } 42 | 43 | return BlockOpenRequests 44 | } 45 | ) 46 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/webapp/controller/blocks/main/BlockPersonalData.controller.ts: -------------------------------------------------------------------------------- 1 | sap.ui.define( 2 | [ 3 | 'de/tammenit/UHDApp/controller/BaseController', 4 | 'sap/ui/model/json/JSONModel', 5 | 'sap/ui/core/format/DateFormat', 6 | 'sap/ui/core/Fragment', 7 | ], 8 | function( 9 | BaseController: typeof de.tammenit.UHDApp.controller.BaseController, 10 | JSONModel: typeof sap.ui.model.json.JSONModel, 11 | DateFormat: typeof sap.ui.core.format.DateFormat, 12 | Fragment: typeof sap.ui.core.Fragment 13 | ) { 14 | 'use strict' 15 | 16 | class BlockPersonalData extends BaseController { 17 | private model: sap.ui.model.json.JSONModel 18 | 19 | constructor() { 20 | // it's important to call the extend method here. It creates metadata that is used in UI5 apps via 21 | // the method getMetadata. Hence we also assign this method to the prototype of our class. 22 | const fnClass = BaseController.extend('de.tammenit.UHDApp.controller.BlockPersonalData', {}) 23 | BlockPersonalData.prototype.getMetadata = fnClass.prototype.getMetadata 24 | 25 | super('de.tammenit.UHDApp.controller.BlockPersonalData') 26 | } 27 | 28 | public onInit(): void { 29 | // @ts-ignore 30 | this.getView().setModel(this.getOwnerComponent().getMainModel()) 31 | this.getView().bindObject({ path: '/userData' }) 32 | } 33 | 34 | /** 35 | * Grouping Funktion. Die Rollentabelle wollen wir nach der appId gruppieren 36 | * @param oCtx 37 | */ 38 | getGroup(oCtx): string { 39 | return oCtx.getProperty('appId') 40 | } 41 | } 42 | 43 | return BlockPersonalData 44 | } 45 | ) 46 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/webapp/controls/StripToaster.js: -------------------------------------------------------------------------------- 1 | /** 2 | * striptoastr - Message Strips that Growl 3 | * @version v1.0.0 4 | * @link https://github.com/jasper07/StripToastr#readme 5 | * @license MIT 6 | */ 7 | sap.ui.define(["sap/m/MessageStrip", "sap/m/MessageStripRenderer", "sap/ui/core/Popup", "sap/ui/layout/VerticalLayout"], function( 8 | MessageStrip, 9 | MessageStripRenderer, 10 | Popup, 11 | VerticalLayout 12 | ) { 13 | "use strict"; 14 | var MessageStripExt = MessageStrip.extend("MessageStripExt", { 15 | //on rerender CSS transition bindings are destroyed 16 | renderer: function(oRm, oControl) { 17 | if (oControl._bClosed) { 18 | oControl.fireClose(); 19 | return; 20 | } 21 | MessageStripRenderer.render.apply(this, arguments); 22 | }, 23 | onAfterRendering: function() { 24 | this.$().attr("role", "alert"); 25 | }, 26 | close: function() { 27 | this._bClosed = true; 28 | MessageStrip.prototype.close.apply(this, arguments); 29 | } 30 | }); 31 | 32 | var StripToastr = { 33 | sContainerId: "stripToastr-container", 34 | 35 | _oSettings: { 36 | text: null, 37 | showCloseButton: true, 38 | showIcon: true, 39 | customIcon: null, 40 | type: sap.ui.core.MessageType.Information, 41 | link: null, 42 | close: null, 43 | timeOut: 5000, 44 | newestFirst: false, 45 | width: "310px", 46 | position: Popup.Dock.RightTop, 47 | anchor: document, 48 | style: "stripToastr", 49 | offset: "0 0" 50 | }, 51 | 52 | /** 53 | * Gets the container that holds toasts 54 | * @param {object} oSettings optional used to create new container 55 | * @return {sap.ui.core.Popup} Popup 56 | */ 57 | getContainer: function(oSettings) { 58 | var oContainer = sap.ui.getCore().byId(this.sContainerId); 59 | 60 | if (!oContainer && oSettings) { 61 | oContainer = new VerticalLayout(this.sContainerId, { 62 | width: oSettings.width 63 | }); 64 | 65 | var oPopup = new Popup(oContainer); 66 | oPopup.setShadow(false); 67 | oPopup.__bAutoClose = true; 68 | oPopup.open(0, oSettings.position, oSettings.position, oSettings.anchor, oSettings.offset); 69 | } 70 | 71 | return oContainer; 72 | }, 73 | 74 | /** 75 | * Creates the StripToastr instance 76 | * @param {object} oOptions has the constructor properties 77 | * @return {StripToastr} instance of the StripToastr 78 | */ 79 | notify: function(oOptions) { 80 | var oSettings = jQuery.extend({}, this._oSettings, oOptions); 81 | var fnAttachClose = function(oEvent) { 82 | oEvent.getSource().setVisible(false); 83 | oEvent.getSource().destroy(); 84 | var fnSomeVisible = function(oControl) { 85 | return oControl.getVisible(); 86 | }; 87 | 88 | if ( 89 | !this.getContainer() 90 | .getContent() 91 | .some(fnSomeVisible) 92 | ) { 93 | this.destroyContainer(); 94 | } 95 | 96 | if (oSettings.close) { 97 | oSettings.close.apply(this); 98 | } 99 | }.bind(this); 100 | 101 | var oControl = new MessageStripExt({ 102 | text: oSettings.text, 103 | showCloseButton: oSettings.showCloseButton, 104 | showIcon: oSettings.showIcon, 105 | customIcon: oSettings.customIcon, 106 | type: oSettings.type, 107 | link: oSettings.link, 108 | close: fnAttachClose 109 | }); 110 | 111 | oControl.addStyleClass(oSettings.style); 112 | 113 | var oContainer = this.getContainer(oSettings); 114 | if (oSettings.newestFirst) { 115 | oContainer.insertContent(oControl, 0); 116 | } else { 117 | oContainer.addContent(oControl); 118 | } 119 | 120 | if (oSettings.timeOut > 0) { 121 | jQuery.sap.delayedCall(oSettings.timeOut, oControl, "close"); 122 | } 123 | 124 | return oControl; 125 | }, 126 | 127 | /** 128 | * Clear the container or close the instance 129 | * @param {StripToastr} oControl instance of StripToastr 130 | */ 131 | clear: function(oControl) { 132 | if (!oControl) { 133 | this.clearContainer(); 134 | } else { 135 | oControl.close(); 136 | } 137 | }, 138 | 139 | /** 140 | * Detroy the container 141 | */ 142 | destroyContainer: function() { 143 | var oContainer = this.getContainer(); 144 | if (oContainer) { 145 | oContainer.setVisible(false); 146 | oContainer.destroy(); 147 | } 148 | }, 149 | 150 | /** 151 | * Clear the container 152 | */ 153 | clearContainer: function() { 154 | var oContainer = this.getContainer(); 155 | 156 | if (oContainer) { 157 | var aContent = oContainer.getContent(); 158 | if (aContent.length === 0) { 159 | this.destroyContainer(); 160 | } else { 161 | var fnClear = function(oControl) { 162 | this.clear(oControl); 163 | }.bind(this); 164 | 165 | aContent.forEach(fnClear); 166 | } 167 | } 168 | } 169 | }; 170 | 171 | return StripToastr; 172 | }); 173 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/webapp/css/style.css: -------------------------------------------------------------------------------- 1 | /* Enter your custom styles here */ 2 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/webapp/formatter/formatter.ts: -------------------------------------------------------------------------------- 1 | sap.ui.define(['sap/ui/core/format/DateFormat', 'sap/base/Log'], function( 2 | DateFormat: typeof sap.ui.core.format.DateFormat, 3 | Log: any 4 | ) { 5 | 'use strict' 6 | 7 | class Formatter { 8 | public constructor() {} 9 | 10 | public formatCreateDate(date: Date): string { 11 | if (date) { 12 | if (date.getTime() === new Date(0).getTime()) { 13 | return '----' 14 | } else { 15 | return DateFormat.getDateTimeInstance().format(date, false) 16 | } 17 | } else { 18 | return '' 19 | } 20 | } 21 | } 22 | 23 | return Formatter 24 | }) 25 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/webapp/i18n/i18n.properties: -------------------------------------------------------------------------------- 1 | title=UHDApp 2 | appTitle=UHDApp 3 | appDescription=App Description 4 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/webapp/i18n/i18n_de.properties: -------------------------------------------------------------------------------- 1 | title=User help desk Anwendung 2 | appTitle=UHDApp 3 | appDescription=Anwendung f�r den User Help Desk 4 | 5 | yes=Ja 6 | no=Nein 7 | 8 | page.homepage=service.myapp.de 9 | breadcrumb.page.main.title=Startseite 10 | 11 | page.main.section.messagetext.text=Bitte geben Sie einen Benutzernamen, eine E-Mail Adresse oder einen Nachnamen ein. 12 | page.main.section.registration.header=Registrierung 13 | page.main.section.ldap.header=LDAP 14 | page.main.section.roles.header=Rollen 15 | page.main.section.requests.header=Antr�ge 16 | page.main.section.data.header=Gepeicherte Daten 17 | 18 | page.main.section.registration.date.label=Registrierung durchgef�hrt am 19 | page.main.section.registration.enddate.label=Registrierung abgeschlossen am 20 | page.main.section.ldap.usercreated.label=Benutzer im LDAP angelegt 21 | page.main.section.ldap.useractive.label=Benutzer im LDAP aktiv 22 | 23 | page.main.section.personaldata.userid.label=Benutzer-Id 24 | page.main.section.personaldata.firstname.label=Vorname 25 | page.main.section.personaldata.lastname.label=Nachname 26 | page.main.section.personaldata.title.label=Anrede 27 | page.main.section.personaldata.email.label=E-Mail 28 | page.main.section.personaldata.company.label=Firma 29 | page.main.section.personaldata.location.label=Ort 30 | 31 | page.main.section.roles.table.assignedroles.header=Zugeordnete Rollen 32 | page.main.section.roles.table.openrequests.header=Offene Antr�ge 33 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/webapp/i18n/i18n_en.properties: -------------------------------------------------------------------------------- 1 | title=User help desk application 2 | appTitle=UHDApp 3 | appDescription=Application for the User Help Desk 4 | 5 | yes=Yes 6 | no=No 7 | 8 | page.homepage=service.myapp.de 9 | breadcrumb.page.main.title=Start Page 10 | 11 | page.main.section.messagetext.text=Please enter a Username, an email address or a surname 12 | page.main.section.registration.header=Registration 13 | page.main.section.ldap.header=LDAP 14 | page.main.section.roles.header=Roles 15 | page.main.section.requests.header=Requests 16 | page.main.section.data.header=Saved data 17 | 18 | page.main.section.registration.date.label=Registration completed at 19 | page.main.section.registration.enddate.label=Registration finished at 20 | page.main.section.ldap.usercreated.label=User created in LDAP 21 | page.main.section.ldap.useractive.label=User active in LDAP 22 | 23 | page.main.section.personaldata.userid.label=User Id 24 | page.main.section.personaldata.firstname.label=Firstname 25 | page.main.section.personaldata.lastname.label=Surname 26 | page.main.section.personaldata.title.label=Salutation 27 | page.main.section.personaldata.email.label=EMail 28 | page.main.section.personaldata.company.label=Company 29 | page.main.section.personaldata.location.label=Location 30 | 31 | page.main.section.roles.table.assignedroles.header=Assigned roles 32 | page.main.section.roles.table.openrequests.header=Open requests 33 | 34 | page.main.section.roles.table.assignedroles.column.application=Application 35 | page.main.section.roles.table.assignedroles.column.description=App. Description 36 | page.main.section.roles.table.assignedroles.column.role=Role 37 | page.main.section.roles.table.assignedroles.column.roledescription=Role description 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/webapp/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | UHDApp 9 | 10 | 22 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/webapp/localService/mockserver.ts: -------------------------------------------------------------------------------- 1 | sap.ui.define(['sap/ui/core/util/MockServer', 'sap/base/Log'], function( 2 | MockServer: typeof sap.ui.core.util.MockServer, 3 | Log: any 4 | ) { 5 | 'use strict' 6 | 7 | interface Request { 8 | method: string 9 | path: string | RegExp 10 | response: any 11 | } 12 | 13 | class UHDAppMockServer { 14 | _oLogger: any 15 | _prefix = 'de/tammenit/UHDApp' 16 | _userListModel: sap.ui.model.json.JSONModel 17 | /** 18 | * Initializes the mock server. 19 | * You can configure the delay with the URL parameter "serverDelay". 20 | * The local mock data in this folder is returned instead of the real data for testing. 21 | * @public 22 | */ 23 | init(): void { 24 | this._oLogger = Log.getLogger('de.tammenit.UHDApp.mockserver') 25 | const aRequests = [] 26 | aRequests.push(this.createGetUserList()) 27 | aRequests.push(this.createGetUserData()) 28 | 29 | const oMockServer = new MockServer() 30 | oMockServer.setRequests(aRequests) 31 | 32 | // start 33 | oMockServer.start() 34 | 35 | Log.info('Running the app with mock data') 36 | } 37 | 38 | /** 39 | * Lädt die userliste aus einer JSON-Datei 40 | */ 41 | private loadUserList(): void { 42 | //@ts-ignore 43 | const pfad = sap.ui.loader._.getResourcePath(`${this._prefix}/model/getUserList.json`) 44 | const jsonModel = new sap.ui.model.json.JSONModel({}, false) 45 | jsonModel.loadData(pfad, null, false) 46 | this._userListModel = jsonModel 47 | } 48 | 49 | private createGetUserList(): Request { 50 | const that = this 51 | return { 52 | method: 'GET', 53 | path: new RegExp('./GetUserList\\?.*'), 54 | response: (xhr): void => { 55 | const query: string = xhr.url.split('?')[1] 56 | const params: Array = query.split('&') 57 | 58 | const input = params[0].split('=')[1] 59 | if (!that._userListModel) { 60 | // load the application data from local json file 61 | that.loadUserList() 62 | } 63 | //@ts-ignore 64 | let filteredUserList: Array = that._userListModel.getProperty('/users') 65 | if (filteredUserList) { 66 | filteredUserList = filteredUserList.filter(obj => { 67 | return obj.userId.indexOf(input) > -1 || obj.lastName.indexOf(input) > -1 || obj.email.indexOf(input) > -1 68 | }) 69 | if (filteredUserList.length > 0) { 70 | xhr.respondJSON(200, null, filteredUserList) 71 | } else { 72 | xhr.respondJSON(200, null, { result: 'error', msg: 'A user could not be found' }) 73 | } 74 | } else { 75 | xhr.respondJSON(200, null, { 76 | result: 'error', 77 | msg: 'Does the mockdata file exist? I couldn`t read any user data', 78 | }) 79 | } 80 | }, 81 | } 82 | } 83 | 84 | /* 85 | */ 86 | private createGetUserData(): Request { 87 | const that = this 88 | return { 89 | method: 'GET', 90 | path: new RegExp('./GetUHDUserData\\?.*'), 91 | response: xhr => { 92 | const query: string = xhr.url.split('?')[1] 93 | if (query) { 94 | const params: Array = query.split('&') 95 | 96 | const userId = params[0].split('=')[1] 97 | if (!that._userListModel) { 98 | // load the application data from local json file 99 | that.loadUserList() 100 | } 101 | let filteredUserList: Array = that._userListModel.getProperty('/users') 102 | filteredUserList = filteredUserList.filter(obj => { 103 | return obj.userId === userId 104 | }) 105 | if (filteredUserList.length === 1) { 106 | xhr.respondJSON(200, null, that.generateUHDUserData(filteredUserList[0])) 107 | } else { 108 | if (filteredUserList.length > 1) { 109 | xhr.respondJSON(200, null, { 110 | result: 'error', 111 | msg: 'There are more than one users with this ID. Is this possible?', 112 | }) 113 | } else { 114 | xhr.respondJSON(200, null, { result: 'error', msg: 'Couldn`t find a user with this Id' }) 115 | } 116 | } 117 | } else { 118 | xhr.respondJSON(200, null, { result: 'error', msg: 'No user ID provided' }) 119 | } 120 | }, 121 | } 122 | } 123 | 124 | /** 125 | * Genereriert Beispiel Output für den Mock-Service getUserData. 126 | * Rollen und Request werden mit einem Zufallszahlen-Generator erzeugt. 127 | * @param userData 128 | */ 129 | private generateUHDUserData(userData: de.tammenit.UHDApp.data.UserData): de.tammenit.UHDApp.data.UHDData { 130 | const arrAppIds = ['fr2', 'bb4', 'fk5', 'hg3', 'ur9', 'zz0', 'zz2', 'zz3', 'op4', 'pu3'] 131 | const arrRoleIds = ['role1', 'role2', 'role3', 'role4'] 132 | const retValue: de.tammenit.UHDApp.data.UHDData = {} 133 | retValue.userData = userData 134 | 135 | retValue.openRequests = [] 136 | const reqCount = Math.floor(Math.random() * 8) 137 | for (let i = 0; i < reqCount; i++) { 138 | const appId = arrAppIds[Math.floor(Math.random() * 10)] 139 | const roleId = arrRoleIds[Math.floor(4 * Math.random())] 140 | retValue.openRequests.push({ 141 | appId: appId, 142 | appName: `Application ${appId}`, 143 | appDescription: `Description of application ${appId}`, 144 | roleId: roleId, 145 | roleName: `Role ${roleId} Application ${appId}`, 146 | roleDescription: `Description of role ${roleId} in application ${appId}`, 147 | }) 148 | } 149 | retValue.roles = [] 150 | const roleCount = Math.floor(Math.random() * 6) 151 | for (let i = 0; i < roleCount; i++) { 152 | const appId = arrAppIds[Math.floor(Math.random() * 10)] 153 | const roleId = arrRoleIds[Math.floor(4 * Math.random())] 154 | retValue.roles.push({ 155 | appId: appId, 156 | appName: `Application ${appId}`, 157 | appDescription: `Description of application ${appId}`, 158 | roleId: roleId, 159 | roleName: `Role ${roleId} Application ${appId}`, 160 | roleDescription: `Description of role ${roleId} in application ${appId}`, 161 | }) 162 | } 163 | 164 | const arrLDAPActive = [true, false] 165 | const ldapCreated = Math.random() > 0.4999 166 | let ldapCreatedDate = new Date(0) 167 | let registrationDate = new Date(0) 168 | let ldapActive = false 169 | if (ldapCreated) { 170 | ldapCreatedDate = this.randomDate() 171 | ldapActive = arrLDAPActive[Math.floor(2 * Math.random())] 172 | } else { 173 | registrationDate = this.randomDate() 174 | } 175 | retValue.registration = { 176 | created: registrationDate, 177 | } 178 | retValue.ldap = { 179 | created: ldapCreatedDate, 180 | active: ldapActive, 181 | } 182 | return retValue 183 | } 184 | 185 | private randomDate() { 186 | const startDate = new Date() 187 | const endDate = new Date() 188 | startDate.setMonth(startDate.getMonth() - 4) 189 | return new Date(startDate.getTime() + Math.random() * (endDate.getTime() - startDate.getTime())) 190 | } 191 | } 192 | return UHDAppMockServer 193 | }) 194 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/webapp/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "_version": "1.12.0", 3 | "sap.app": { 4 | "id": "de.tammenit.UHDApp", 5 | "type": "application", 6 | "i18n": "i18n/i18n.properties", 7 | "applicationVersion": { 8 | "version": "1.0.0" 9 | }, 10 | "title": "{{appTitle}}", 11 | "description": "{{appDescription}}" 12 | }, 13 | 14 | "sap.ui": { 15 | "technology": "UI5", 16 | "icons": { 17 | "icon": "", 18 | "favIcon": "", 19 | "phone": "", 20 | "phone@2": "", 21 | "tablet": "", 22 | "tablet@2": "" 23 | }, 24 | "deviceTypes": { 25 | "desktop": true, 26 | "tablet": true, 27 | "phone": true 28 | } 29 | }, 30 | 31 | "sap.ui5": { 32 | "rootView": { 33 | "viewName": "de.tammenit.UHDApp.view.App", 34 | "type": "XML", 35 | "async": true, 36 | "id": "app" 37 | }, 38 | "dependencies": { 39 | "minUI5Version": "1.60.0", 40 | "libs": { 41 | "sap.ui.core": {}, 42 | "sap.m": {}, 43 | "sap.ui.layout": {}, 44 | "sap.f": {}, 45 | "sap.ui.unified": {}, 46 | "sap.uxap": {} 47 | } 48 | }, 49 | "contentDensities": { 50 | "compact": true, 51 | "cozy": true 52 | }, 53 | "models": { 54 | "i18n": { 55 | "type": "sap.ui.model.resource.ResourceModel", 56 | "settings": { 57 | "bundleName": "de.tammenit.UHDApp.i18n.i18n" 58 | } 59 | } 60 | }, 61 | "resources": { 62 | "css": [{ 63 | "uri": "css/style.css" 64 | }] 65 | }, 66 | "routing": { 67 | "config": { 68 | "routerClass": "sap.m.routing.Router", 69 | "viewType": "XML", 70 | "bypassed": { 71 | "target": "notFound" 72 | }, 73 | "viewPath": "de.tammenit.UHDApp.view", 74 | "controlId": "idAppControl", 75 | "controlAggregation": "pages", 76 | "async": true 77 | }, 78 | "routes": [{ 79 | "name": "RouteMain", 80 | "pattern": "/", 81 | "target": ["TargetMain"] 82 | }], 83 | "targets": { 84 | "TargetMain": { 85 | "viewType": "XML", 86 | "viewLevel": 1, 87 | "viewName": "Main" 88 | }, 89 | "notFound": { 90 | "viewId": "notFound", 91 | "viewName": "NotFound", 92 | "transition": "show" 93 | } 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/webapp/model/getUserList.json: -------------------------------------------------------------------------------- 1 | { 2 | "users": [ 3 | { 4 | "userId": "gconrad", 5 | "firstName": "Gisela", 6 | "lastName": "Conrad", 7 | "email": "neque.Nullam@dictumeueleifend.com", 8 | "company": "Blandit Enim Consulting", 9 | "location": "San Antonio", 10 | "title": "Frau" 11 | }, 12 | { 13 | "userId": "jmurphy", 14 | "firstName": "Jaquelyn", 15 | "lastName": "Murphy", 16 | "email": "sem@non.com", 17 | "company": "Iaculis Lacus LLP", 18 | "location": "Itapipoca", 19 | "title": "Herr" 20 | }, 21 | { 22 | "userId": "arush", 23 | "firstName": "Adele", 24 | "lastName": "Rush", 25 | "email": "imperdiet@morbitristique.edu", 26 | "company": "Posuere Vulputate Lacus Incorporated", 27 | "location": "Bad Nauheim", 28 | "title": "Dr." 29 | }, 30 | { 31 | "userId": "sdotson", 32 | "firstName": "Sharon", 33 | "lastName": "Dotson", 34 | "email": "sit@tellusjustosit.co.uk", 35 | "company": "Enim Commodo Hendrerit Consulting", 36 | "location": "Portici", 37 | "title": "Frau" 38 | }, 39 | { 40 | "userId": "rcooper", 41 | "firstName": "Ria", 42 | "lastName": "Cooper", 43 | "email": "justo.eu.arcu@at.ca", 44 | "company": "Sapien Cursus In PC", 45 | "location": "Salon-de-Provence", 46 | "title": "Frau" 47 | }, 48 | { 49 | "userId": "mhoover", 50 | "firstName": "Marsden", 51 | "lastName": "Hoover", 52 | "email": "pede.Suspendisse@aliquam.edu", 53 | "company": "Quam A PC", 54 | "location": "Borgomasino", 55 | "title": "Dr." 56 | }, 57 | { 58 | "userId": "sweaver", 59 | "firstName": "Shay", 60 | "lastName": "Weaver", 61 | "email": "sed.libero.Proin@pharetra.org", 62 | "company": "Laoreet Posuere Enim Inc.", 63 | "location": "Milestone", 64 | "title": "Herr" 65 | }, 66 | { 67 | "userId": "aboyd", 68 | "firstName": "Avye", 69 | "lastName": "Boyd", 70 | "email": "mauris.blandit.mattis@et.ca", 71 | "company": "Aptent Taciti Associates", 72 | "location": "Acerra", 73 | "title": "Herr" 74 | }, 75 | { 76 | "userId": "gfloyd", 77 | "firstName": "Geraldine", 78 | "lastName": "Floyd", 79 | "email": "enim@libero.net", 80 | "company": "Dolor Sit Corporation", 81 | "location": "Zaffelare", 82 | "title": "Dr." 83 | }, 84 | { 85 | "userId": "cbaird", 86 | "firstName": "Caryn", 87 | "lastName": "Baird", 88 | "email": "Lorem.ipsum.dolor@liberoat.edu", 89 | "company": "Penatibus Et Magnis Foundation", 90 | "location": "Cropalati", 91 | "title": "Frau" 92 | }, 93 | { 94 | "userId": "cmeadows", 95 | "firstName": "Colin", 96 | "lastName": "Meadows", 97 | "email": "Nunc.laoreet.lectus@urnajustofaucibus.net", 98 | "company": "Vitae Posuere Corp.", 99 | "location": "Stroud", 100 | "title": "Frau" 101 | }, 102 | { 103 | "userId": "ahahn", 104 | "firstName": "Aspen", 105 | "lastName": "Hahn", 106 | "email": "Sed@massaSuspendisseeleifend.ca", 107 | "company": "Vitae Aliquam Eros Inc.", 108 | "location": "Kakisa", 109 | "title": "Herr" 110 | }, 111 | { 112 | "userId": "earnold", 113 | "firstName": "Erasmus", 114 | "lastName": "Arnold", 115 | "email": "Curabitur.vel@egettinciduntdui.org", 116 | "company": "Vel Convallis Company", 117 | "location": "Paillaco", 118 | "title": "Frau" 119 | }, 120 | { 121 | "userId": "emartinez", 122 | "firstName": "Ella", 123 | "lastName": "Martinez", 124 | "email": "molestie@pellentesque.edu", 125 | "company": "Luctus Ipsum Leo Company", 126 | "location": "Thurso", 127 | "title": "Frau" 128 | }, 129 | { 130 | "userId": "lbird", 131 | "firstName": "Leonard", 132 | "lastName": "Bird", 133 | "email": "luctus.lobortis.Class@orci.net", 134 | "company": "Nibh Consulting", 135 | "location": "Gorakhpur", 136 | "title": "Frau" 137 | }, 138 | { 139 | "userId": "ghubbard", 140 | "firstName": "Garth", 141 | "lastName": "Hubbard", 142 | "email": "at.pretium@faucibuslectusa.org", 143 | "company": "Quam Curabitur Foundation", 144 | "location": "Natales", 145 | "title": "Herr" 146 | }, 147 | { 148 | "userId": "aferguson", 149 | "firstName": "Ashely", 150 | "lastName": "Ferguson", 151 | "email": "vestibulum.nec.euismod@ligulaconsectetuer.ca", 152 | "company": "Nulla Integer Vulputate Industries", 153 | "location": "Neerrepen", 154 | "title": "Frau" 155 | }, 156 | { 157 | "userId": "koconnor", 158 | "firstName": "Kimberly", 159 | "lastName": "Oconnor", 160 | "email": "risus@metusVivamuseuismod.edu", 161 | "company": "Tellus Lorem Incorporated", 162 | "location": "Saltcoats", 163 | "title": "Frau" 164 | }, 165 | { 166 | "userId": "jtorres", 167 | "firstName": "Jermaine", 168 | "lastName": "Torres", 169 | "email": "rutrum.Fusce@pedeblanditcongue.org", 170 | "company": "Nibh Lacinia PC", 171 | "location": "Kamarhati", 172 | "title": "Herr" 173 | }, 174 | { 175 | "userId": "iherrera", 176 | "firstName": "Irene", 177 | "lastName": "Herrera", 178 | "email": "ut.eros@diamatpretium.com", 179 | "company": "Mollis Dui In Inc.", 180 | "location": "Gdańsk", 181 | "title": "Frau" 182 | }, 183 | { 184 | "userId": "eboyle", 185 | "firstName": "Ezekiel", 186 | "lastName": "Boyle", 187 | "email": "leo.Cras.vehicula@tellus.net", 188 | "company": "Suspendisse Sed Dolor Consulting", 189 | "location": "Sainte-Flavie", 190 | "title": "Herr" 191 | }, 192 | { 193 | "userId": "mreid", 194 | "firstName": "Maya", 195 | "lastName": "Reid", 196 | "email": "nisi.Cum.sociis@acfermentum.ca", 197 | "company": "Nec Tempus Mauris LLP", 198 | "location": "Caprauna", 199 | "title": "Herr" 200 | }, 201 | { 202 | "userId": "ftownsend", 203 | "firstName": "Felix", 204 | "lastName": "Townsend", 205 | "email": "cursus@consectetuer.net", 206 | "company": "Mauris Industries", 207 | "location": "Duns", 208 | "title": "Frau" 209 | }, 210 | { 211 | "userId": "kmoore", 212 | "firstName": "Keane", 213 | "lastName": "Moore", 214 | "email": "Nullam.scelerisque.neque@Aliquam.net", 215 | "company": "Nunc Limited", 216 | "location": "Serskamp", 217 | "title": "Frau" 218 | }, 219 | { 220 | "userId": "tmosley", 221 | "firstName": "Travis", 222 | "lastName": "Mosley", 223 | "email": "faucibus.leo@lobortisquam.edu", 224 | "company": "Odio Ltd", 225 | "location": "Louisville", 226 | "title": "Frau" 227 | }, 228 | { 229 | "userId": "bvinson", 230 | "firstName": "Brian", 231 | "lastName": "Vinson", 232 | "email": "nisl@nonarcuVivamus.com", 233 | "company": "Nisl Nulla Eu Corp.", 234 | "location": "Nossegem", 235 | "title": "Frau" 236 | }, 237 | { 238 | "userId": "bwoodard", 239 | "firstName": "Blossom", 240 | "lastName": "Woodard", 241 | "email": "eleifend.nunc@purus.net", 242 | "company": "Eu Tempor LLC", 243 | "location": "Monte San Savino", 244 | "title": "Dr." 245 | }, 246 | { 247 | "userId": "ncarpenter", 248 | "firstName": "Noble", 249 | "lastName": "Carpenter", 250 | "email": "dis.parturient.montes@sitametrisus.co.uk", 251 | "company": "Elit Pellentesque Industries", 252 | "location": "Port Augusta", 253 | "title": "Frau" 254 | }, 255 | { 256 | "userId": "pgeorge", 257 | "firstName": "Penelope", 258 | "lastName": "George", 259 | "email": "Curabitur.sed@Quisqueornaretortor.net", 260 | "company": "Pretium Aliquet Institute", 261 | "location": "Carahue", 262 | "title": "Dr." 263 | }, 264 | { 265 | "userId": "mortega", 266 | "firstName": "Moana", 267 | "lastName": "Ortega", 268 | "email": "Proin.eget@at.edu", 269 | "company": "Vitae Limited", 270 | "location": "Lakeland County", 271 | "title": "Frau" 272 | }, 273 | { 274 | "userId": "qwolf", 275 | "firstName": "Quon", 276 | "lastName": "Wolf", 277 | "email": "Duis.volutpat.nunc@eunibh.edu", 278 | "company": "Cursus Incorporated", 279 | "location": "Tallahassee", 280 | "title": "Frau" 281 | }, 282 | { 283 | "userId": "mbarber", 284 | "firstName": "May", 285 | "lastName": "Barber", 286 | "email": "taciti.sociosqu.ad@hendreritidante.com", 287 | "company": "Fermentum Corporation", 288 | "location": "Zelem", 289 | "title": "Dr." 290 | }, 291 | { 292 | "userId": "pmichael", 293 | "firstName": "Phyllis", 294 | "lastName": "Michael", 295 | "email": "Nullam.velit.dui@ultrices.edu", 296 | "company": "Viverra Corp.", 297 | "location": "Middlesbrough", 298 | "title": "Frau" 299 | }, 300 | { 301 | "userId": "hwhitaker", 302 | "firstName": "Halee", 303 | "lastName": "Whitaker", 304 | "email": "augue@Nullatinciduntneque.org", 305 | "company": "Ac Arcu Nunc Industries", 306 | "location": "Viddalba", 307 | "title": "Frau" 308 | }, 309 | { 310 | "userId": "vblair", 311 | "firstName": "Victor", 312 | "lastName": "Blair", 313 | "email": "eget.laoreet@nisisemsemper.net", 314 | "company": "Ante Maecenas Mi LLC", 315 | "location": "Anklam", 316 | "title": "Frau" 317 | }, 318 | { 319 | "userId": "aadams", 320 | "firstName": "Armando", 321 | "lastName": "Adams", 322 | "email": "mattis.semper@ac.org", 323 | "company": "Eget Magna Suspendisse Associates", 324 | "location": "Edremit", 325 | "title": "Frau" 326 | }, 327 | { 328 | "userId": "ccunningham", 329 | "firstName": "Colby", 330 | "lastName": "Cunningham", 331 | "email": "nibh.sit@fames.com", 332 | "company": "Et LLC", 333 | "location": "Chesapeake", 334 | "title": "Dr." 335 | }, 336 | { 337 | "userId": "npaul", 338 | "firstName": "Noble", 339 | "lastName": "Paul", 340 | "email": "sollicitudin@Curabitursed.edu", 341 | "company": "Molestie Pharetra Nibh Consulting", 342 | "location": "Appelterre-Eichem", 343 | "title": "Dr." 344 | }, 345 | { 346 | "userId": "elevine", 347 | "firstName": "Elaine", 348 | "lastName": "Levine", 349 | "email": "Proin.nisl@diam.com", 350 | "company": "Ultrices Institute", 351 | "location": "Stokrooie", 352 | "title": "Dr." 353 | }, 354 | { 355 | "userId": "ymooney", 356 | "firstName": "Yuri", 357 | "lastName": "Mooney", 358 | "email": "lacinia@quis.net", 359 | "company": "Eget Massa Suspendisse Corporation", 360 | "location": "Plauen", 361 | "title": "Dr." 362 | }, 363 | { 364 | "userId": "ffisher", 365 | "firstName": "Ferris", 366 | "lastName": "Fisher", 367 | "email": "ornare.egestas@risus.co.uk", 368 | "company": "Justo Eu Arcu Limited", 369 | "location": "Hall in Tirol", 370 | "title": "Herr" 371 | }, 372 | { 373 | "userId": "sperry", 374 | "firstName": "Sydnee", 375 | "lastName": "Perry", 376 | "email": "Proin.mi.Aliquam@dictum.edu", 377 | "company": "A Enim Suspendisse LLC", 378 | "location": "Nieuwegein", 379 | "title": "Dr." 380 | }, 381 | { 382 | "userId": "gbrooks", 383 | "firstName": "Genevieve", 384 | "lastName": "Brooks", 385 | "email": "nec.leo@lacinia.ca", 386 | "company": "Nec Orci Institute", 387 | "location": "Virelles", 388 | "title": "Frau" 389 | }, 390 | { 391 | "userId": "ichase", 392 | "firstName": "Ian", 393 | "lastName": "Chase", 394 | "email": "nunc@Proineget.com", 395 | "company": "Sagittis Augue Eu Associates", 396 | "location": "Dehri", 397 | "title": "Herr" 398 | }, 399 | { 400 | "userId": "iferguson", 401 | "firstName": "Indigo", 402 | "lastName": "Ferguson", 403 | "email": "nibh.Quisque@semconsequat.org", 404 | "company": "Pede Ac Urna Corp.", 405 | "location": "Abolens", 406 | "title": "Dr." 407 | }, 408 | { 409 | "userId": "bclark", 410 | "firstName": "Brenda", 411 | "lastName": "Clark", 412 | "email": "dui.quis@enim.com", 413 | "company": "Sodales Mauris Corporation", 414 | "location": "Bassano in Teverina", 415 | "title": "Frau" 416 | }, 417 | { 418 | "userId": "slarson", 419 | "firstName": "Shelley", 420 | "lastName": "Larson", 421 | "email": "ipsum@nequeet.ca", 422 | "company": "Odio PC", 423 | "location": "Middelburg", 424 | "title": "Dr." 425 | }, 426 | { 427 | "userId": "kboyer", 428 | "firstName": "Kellie", 429 | "lastName": "Boyer", 430 | "email": "Phasellus.libero@metusInnec.edu", 431 | "company": "Quisque LLP", 432 | "location": "Honolulu", 433 | "title": "Herr" 434 | }, 435 | { 436 | "userId": "bfrancis", 437 | "firstName": "Brett", 438 | "lastName": "Francis", 439 | "email": "vel@semper.net", 440 | "company": "Tellus Id Nunc Associates", 441 | "location": "Mollem", 442 | "title": "Dr." 443 | }, 444 | { 445 | "userId": "lhull", 446 | "firstName": "Lamar", 447 | "lastName": "Hull", 448 | "email": "molestie.tortor@nequenonquam.ca", 449 | "company": "Etiam Gravida Limited", 450 | "location": "Marzabotto", 451 | "title": "Frau" 452 | }, 453 | { 454 | "userId": "gsellers", 455 | "firstName": "George", 456 | "lastName": "Sellers", 457 | "email": "Vivamus.rhoncus@tristiquealiquet.co.uk", 458 | "company": "Nunc Pulvinar LLP", 459 | "location": "Gresham", 460 | "title": "Herr" 461 | }, 462 | { 463 | "userId": "psims", 464 | "firstName": "Paloma", 465 | "lastName": "Sims", 466 | "email": "sed.sem.egestas@malesuada.com", 467 | "company": "Commodo Inc.", 468 | "location": "Nashik", 469 | "title": "Frau" 470 | }, 471 | { 472 | "userId": "ymaddox", 473 | "firstName": "Yvette", 474 | "lastName": "Maddox", 475 | "email": "faucibus.ut@pede.com", 476 | "company": "In Tincidunt Congue Associates", 477 | "location": "Khanpur", 478 | "title": "Frau" 479 | }, 480 | { 481 | "userId": "ggoff", 482 | "firstName": "Gavin", 483 | "lastName": "Goff", 484 | "email": "arcu@rutrummagna.edu", 485 | "company": "Mauris Aliquam Eu Inc.", 486 | "location": "Frankenthal", 487 | "title": "Frau" 488 | }, 489 | { 490 | "userId": "fvinson", 491 | "firstName": "Fuller", 492 | "lastName": "Vinson", 493 | "email": "amet.risus@magnaLoremipsum.edu", 494 | "company": "Nulla Magna Corp.", 495 | "location": "Kanchrapara", 496 | "title": "Frau" 497 | }, 498 | { 499 | "userId": "jhorton", 500 | "firstName": "Jack", 501 | "lastName": "Horton", 502 | "email": "vestibulum@justoPraesent.ca", 503 | "company": "Sagittis Duis Corporation", 504 | "location": "Götzis", 505 | "title": "Herr" 506 | }, 507 | { 508 | "userId": "mphelps", 509 | "firstName": "Malachi", 510 | "lastName": "Phelps", 511 | "email": "Nulla@liberodui.ca", 512 | "company": "Ac Corporation", 513 | "location": "Kent", 514 | "title": "Herr" 515 | }, 516 | { 517 | "userId": "ychen", 518 | "firstName": "Yvonne", 519 | "lastName": "Chen", 520 | "email": "nec.euismod.in@enimconsequatpurus.co.uk", 521 | "company": "Mauris Inc.", 522 | "location": "Pordenone", 523 | "title": "Dr." 524 | }, 525 | { 526 | "userId": "lhouse", 527 | "firstName": "Libby", 528 | "lastName": "House", 529 | "email": "sem.semper.erat@faucibusorciluctus.net", 530 | "company": "Molestie Tortor Nibh LLC", 531 | "location": "Nemi", 532 | "title": "Frau" 533 | }, 534 | { 535 | "userId": "ejames", 536 | "firstName": "Eric", 537 | "lastName": "James", 538 | "email": "lectus.quis@eratvolutpatNulla.org", 539 | "company": "Elit Incorporated", 540 | "location": "Williams Lake", 541 | "title": "Dr." 542 | }, 543 | { 544 | "userId": "lbarr", 545 | "firstName": "Lydia", 546 | "lastName": "Barr", 547 | "email": "Nunc.laoreet.lectus@Maurismolestie.ca", 548 | "company": "Mauris Sit Amet Limited", 549 | "location": "Kufstein", 550 | "title": "Frau" 551 | }, 552 | { 553 | "userId": "birwin", 554 | "firstName": "Brock", 555 | "lastName": "Irwin", 556 | "email": "quis@turpisnonenim.net", 557 | "company": "Justo Faucibus Lectus Limited", 558 | "location": "Oviedo", 559 | "title": "Frau" 560 | }, 561 | { 562 | "userId": "gcallahan", 563 | "firstName": "Gareth", 564 | "lastName": "Callahan", 565 | "email": "cursus.in.hendrerit@Crassedleo.co.uk", 566 | "company": "Ullamcorper Corporation", 567 | "location": "Fairbanks", 568 | "title": "Frau" 569 | }, 570 | { 571 | "userId": "twallace", 572 | "firstName": "Troy", 573 | "lastName": "Wallace", 574 | "email": "massa@gravidaPraesenteu.edu", 575 | "company": "Elementum PC", 576 | "location": "Siegendorf", 577 | "title": "Dr." 578 | }, 579 | { 580 | "userId": "choward", 581 | "firstName": "Camille", 582 | "lastName": "Howard", 583 | "email": "Nulla@Fuscedolorquam.net", 584 | "company": "Et Magna Ltd", 585 | "location": "Los Sauces", 586 | "title": "Frau" 587 | }, 588 | { 589 | "userId": "icurry", 590 | "firstName": "Ignacia", 591 | "lastName": "Curry", 592 | "email": "magna.nec@ultricesVivamusrhoncus.com", 593 | "company": "Erat Sed Institute", 594 | "location": "HŽvillers", 595 | "title": "Herr" 596 | }, 597 | { 598 | "userId": "rsantos", 599 | "firstName": "Rebecca", 600 | "lastName": "Santos", 601 | "email": "nunc.sit.amet@ipsumSuspendissenon.edu", 602 | "company": "Malesuada LLP", 603 | "location": "Roccamena", 604 | "title": "Dr." 605 | }, 606 | { 607 | "userId": "mhickman", 608 | "firstName": "Mollie", 609 | "lastName": "Hickman", 610 | "email": "ligula.Aenean@sempereratin.edu", 611 | "company": "Magna Phasellus Inc.", 612 | "location": "Celle", 613 | "title": "Herr" 614 | }, 615 | { 616 | "userId": "dwoods", 617 | "firstName": "Desirae", 618 | "lastName": "Woods", 619 | "email": "Mauris@ac.org", 620 | "company": "Sapien Cursus PC", 621 | "location": "York", 622 | "title": "Frau" 623 | }, 624 | { 625 | "userId": "orobinson", 626 | "firstName": "Orla", 627 | "lastName": "Robinson", 628 | "email": "non.massa@mieleifendegestas.org", 629 | "company": "Dictum Eu Corporation", 630 | "location": "Saint-Dié-des-Vosges", 631 | "title": "Frau" 632 | }, 633 | { 634 | "userId": "cmaxwell", 635 | "firstName": "Connor", 636 | "lastName": "Maxwell", 637 | "email": "Donec.vitae@Aliquam.co.uk", 638 | "company": "Est Tempor Corporation", 639 | "location": "Porretta Terme", 640 | "title": "Herr" 641 | }, 642 | { 643 | "userId": "blarson", 644 | "firstName": "Brandon", 645 | "lastName": "Larson", 646 | "email": "Mauris@at.org", 647 | "company": "Nec Cursus Corp.", 648 | "location": "Canmore", 649 | "title": "Frau" 650 | }, 651 | { 652 | "userId": "omorgan", 653 | "firstName": "Olga", 654 | "lastName": "Morgan", 655 | "email": "nulla.vulputate.dui@Suspendissecommodotincidunt.org", 656 | "company": "Non Luctus Sit Consulting", 657 | "location": "Springdale", 658 | "title": "Herr" 659 | }, 660 | { 661 | "userId": "ihinton", 662 | "firstName": "Ivana", 663 | "lastName": "Hinton", 664 | "email": "Curabitur.vel@ligulaeu.ca", 665 | "company": "Egestas Associates", 666 | "location": "Melbourne", 667 | "title": "Frau" 668 | }, 669 | { 670 | "userId": "cpatton", 671 | "firstName": "Cade", 672 | "lastName": "Patton", 673 | "email": "neque.vitae@ullamcorpereu.com", 674 | "company": "In Limited", 675 | "location": "Kester", 676 | "title": "Frau" 677 | }, 678 | { 679 | "userId": "crichard", 680 | "firstName": "Cedric", 681 | "lastName": "Richard", 682 | "email": "dictum@erosturpisnon.org", 683 | "company": "Maecenas Ornare LLC", 684 | "location": "Gorbea", 685 | "title": "Herr" 686 | }, 687 | { 688 | "userId": "kbartlett", 689 | "firstName": "Kelly", 690 | "lastName": "Bartlett", 691 | "email": "dapibus.ligula.Aliquam@montesnasceturridiculus.net", 692 | "company": "Elit Aliquam Auctor Incorporated", 693 | "location": "Melton", 694 | "title": "Dr." 695 | }, 696 | { 697 | "userId": "sbenjamin", 698 | "firstName": "Sybill", 699 | "lastName": "Benjamin", 700 | "email": "nec.urna.suscipit@posuereat.ca", 701 | "company": "Et Libero Foundation", 702 | "location": "Provo", 703 | "title": "Frau" 704 | }, 705 | { 706 | "userId": "astanley", 707 | "firstName": "Amena", 708 | "lastName": "Stanley", 709 | "email": "et.rutrum@utmolestie.net", 710 | "company": "Metus LLP", 711 | "location": "Wrigley", 712 | "title": "Dr." 713 | }, 714 | { 715 | "userId": "fjohnston", 716 | "firstName": "Flavia", 717 | "lastName": "Johnston", 718 | "email": "pretium.neque.Morbi@musDonec.net", 719 | "company": "A Ltd", 720 | "location": "Anchorage", 721 | "title": "Frau" 722 | }, 723 | { 724 | "userId": "kcote", 725 | "firstName": "Kato", 726 | "lastName": "Cote", 727 | "email": "ultrices@veliteget.co.uk", 728 | "company": "Dolor Dolor LLC", 729 | "location": "Leighton Buzzard", 730 | "title": "Herr" 731 | }, 732 | { 733 | "userId": "ldaniels", 734 | "firstName": "Laura", 735 | "lastName": "Daniels", 736 | "email": "massa.Suspendisse@lobortisquispede.net", 737 | "company": "Tempor Bibendum Donec PC", 738 | "location": "Valparai", 739 | "title": "Herr" 740 | }, 741 | { 742 | "userId": "charmon", 743 | "firstName": "Cheyenne", 744 | "lastName": "Harmon", 745 | "email": "mi@Sedcongue.com", 746 | "company": "At Risus Corporation", 747 | "location": "Arauco", 748 | "title": "Frau" 749 | }, 750 | { 751 | "userId": "schase", 752 | "firstName": "Stuart", 753 | "lastName": "Chase", 754 | "email": "dis.parturient.montes@diamatpretium.edu", 755 | "company": "Pede Incorporated", 756 | "location": "Rizes", 757 | "title": "Frau" 758 | }, 759 | { 760 | "userId": "mwallace", 761 | "firstName": "Mara", 762 | "lastName": "Wallace", 763 | "email": "commodo.auctor@Mauris.net", 764 | "company": "Nullam Enim Company", 765 | "location": "Laramie", 766 | "title": "Herr" 767 | }, 768 | { 769 | "userId": "bhooper", 770 | "firstName": "Blair", 771 | "lastName": "Hooper", 772 | "email": "ornare@laciniaorci.ca", 773 | "company": "Ut Pellentesque Eget Incorporated", 774 | "location": "Frignano", 775 | "title": "Frau" 776 | }, 777 | { 778 | "userId": "djimenez", 779 | "firstName": "Debra", 780 | "lastName": "Jimenez", 781 | "email": "penatibus@elitpharetra.org", 782 | "company": "Duis Mi Enim Ltd", 783 | "location": "Lowell", 784 | "title": "Dr." 785 | }, 786 | { 787 | "userId": "cfitzpatrick", 788 | "firstName": "Ciaran", 789 | "lastName": "Fitzpatrick", 790 | "email": "Donec.luctus@semeget.org", 791 | "company": "At Pede PC", 792 | "location": "Bath", 793 | "title": "Frau" 794 | }, 795 | { 796 | "userId": "jcline", 797 | "firstName": "Jin", 798 | "lastName": "Cline", 799 | "email": "Integer@commodoatlibero.edu", 800 | "company": "Urna Ut Tincidunt LLP", 801 | "location": "Detroit", 802 | "title": "Herr" 803 | }, 804 | { 805 | "userId": "rshields", 806 | "firstName": "Rinah", 807 | "lastName": "Shields", 808 | "email": "cursus.et.magna@tempusscelerisquelorem.ca", 809 | "company": "Euismod LLP", 810 | "location": "Thorold", 811 | "title": "Dr." 812 | }, 813 | { 814 | "userId": "fhewitt", 815 | "firstName": "Finn", 816 | "lastName": "Hewitt", 817 | "email": "eu.augue@augue.net", 818 | "company": "Molestie Arcu Industries", 819 | "location": "Campomorone", 820 | "title": "Herr" 821 | }, 822 | { 823 | "userId": "shamilton", 824 | "firstName": "Shelley", 825 | "lastName": "Hamilton", 826 | "email": "ipsum.dolor@Nullamfeugiat.ca", 827 | "company": "Non Inc.", 828 | "location": "Swan Hills", 829 | "title": "Frau" 830 | }, 831 | { 832 | "userId": "mpoole", 833 | "firstName": "Molly", 834 | "lastName": "Poole", 835 | "email": "erat.nonummy@consectetuer.co.uk", 836 | "company": "In Condimentum Associates", 837 | "location": "Maria", 838 | "title": "Frau" 839 | }, 840 | { 841 | "userId": "efrazier", 842 | "firstName": "Erin", 843 | "lastName": "Frazier", 844 | "email": "Integer@Vivamusnisi.edu", 845 | "company": "Nulla Semper Tellus Incorporated", 846 | "location": "Lawton", 847 | "title": "Frau" 848 | }, 849 | { 850 | "userId": "kbray", 851 | "firstName": "Kameko", 852 | "lastName": "Bray", 853 | "email": "sit.amet@odioauctor.ca", 854 | "company": "Pede Sagittis Associates", 855 | "location": "Hanau", 856 | "title": "Frau" 857 | }, 858 | { 859 | "userId": "bcaldwell", 860 | "firstName": "Bo", 861 | "lastName": "Caldwell", 862 | "email": "lacus@metus.co.uk", 863 | "company": "Aliquam Iaculis Lacus PC", 864 | "location": "Ludhiana", 865 | "title": "Frau" 866 | }, 867 | { 868 | "userId": "ldaniels", 869 | "firstName": "Laura", 870 | "lastName": "Daniels", 871 | "email": "tellus@Quisqueliberolacus.org", 872 | "company": "Enim Consequat PC", 873 | "location": "Sooke", 874 | "title": "Frau" 875 | }, 876 | { 877 | "userId": "bbrock", 878 | "firstName": "Barrett", 879 | "lastName": "Brock", 880 | "email": "condimentum@tinciduntaliquam.com", 881 | "company": "Non Feugiat PC", 882 | "location": "Osimo", 883 | "title": "Frau" 884 | }, 885 | { 886 | "userId": "wbird", 887 | "firstName": "Willa", 888 | "lastName": "Bird", 889 | "email": "eu.accumsan.sed@Nullasemper.net", 890 | "company": "Diam Eu Dolor PC", 891 | "location": "Vizianagaram", 892 | "title": "Herr" 893 | }, 894 | { 895 | "userId": "fvincent", 896 | "firstName": "Fiona", 897 | "lastName": "Vincent", 898 | "email": "egestas.Fusce@disparturient.net", 899 | "company": "Pede Praesent Foundation", 900 | "location": "Paranaguá", 901 | "title": "Herr" 902 | }, 903 | { 904 | "userId": "nmoore", 905 | "firstName": "Neil", 906 | "lastName": "Moore", 907 | "email": "rutrum@Nullamscelerisqueneque.edu", 908 | "company": "Lacus Inc.", 909 | "location": "Langley", 910 | "title": "Dr." 911 | }, 912 | { 913 | "userId": "efaulkner", 914 | "firstName": "Eric", 915 | "lastName": "Faulkner", 916 | "email": "porttitor@sociisnatoque.ca", 917 | "company": "Aliquet Sem Ut Company", 918 | "location": "Gaggio Montano", 919 | "title": "Frau" 920 | }, 921 | { 922 | "userId": "bshaw", 923 | "firstName": "Barbara", 924 | "lastName": "Shaw", 925 | "email": "feugiat@malesuadavel.org", 926 | "company": "Montes Nascetur Foundation", 927 | "location": "Allahabad", 928 | "title": "Frau" 929 | }, 930 | { 931 | "userId": "areed", 932 | "firstName": "Abigail", 933 | "lastName": "Reed", 934 | "email": "rutrum.non.hendrerit@malesuadaaugue.co.uk", 935 | "company": "Donec Est Nunc Company", 936 | "location": "Padre Hurtado", 937 | "title": "Frau" 938 | }, 939 | { 940 | "userId": "state", 941 | "firstName": "Sylvester", 942 | "lastName": "Tate", 943 | "email": "imperdiet.dictum.magna@DonecnibhQuisque.co.uk", 944 | "company": "Euismod Et Commodo Associates", 945 | "location": "Lansing", 946 | "title": "Frau" 947 | }, 948 | { 949 | "userId": "carmstrong", 950 | "firstName": "Candace", 951 | "lastName": "Armstrong", 952 | "email": "lobortis.mauris@tempuseu.edu", 953 | "company": "Natoque Penatibus Et Associates", 954 | "location": "Saint-Martin", 955 | "title": "Dr." 956 | }, 957 | { 958 | "userId": "adudley", 959 | "firstName": "Alice", 960 | "lastName": "Dudley", 961 | "email": "diam.Proin.dolor@DuisgravidaPraesent.edu", 962 | "company": "Dictum Company", 963 | "location": "Rishra", 964 | "title": "Herr" 965 | }, 966 | { 967 | "userId": "aandrews", 968 | "firstName": "Alea", 969 | "lastName": "Andrews", 970 | "email": "neque.pellentesque.massa@etmagnisdis.ca", 971 | "company": "Et LLP", 972 | "location": "Rachecourt", 973 | "title": "Dr." 974 | }, 975 | { 976 | "userId": "hgeorge", 977 | "firstName": "Hiram", 978 | "lastName": "George", 979 | "email": "dictum.magna.Ut@metus.com", 980 | "company": "Est Vitae Incorporated", 981 | "location": "Río Negro", 982 | "title": "Frau" 983 | }, 984 | { 985 | "userId": "dsalinas", 986 | "firstName": "Damian", 987 | "lastName": "Salinas", 988 | "email": "netus.et@doloregestasrhoncus.net", 989 | "company": "Maecenas Libero Est Associates", 990 | "location": "Recogne", 991 | "title": "Frau" 992 | }, 993 | { 994 | "userId": "eblackburn", 995 | "firstName": "Eliana", 996 | "lastName": "Blackburn", 997 | "email": "augue.ut@lobortisquam.org", 998 | "company": "Metus Sit Amet Institute", 999 | "location": "Feltre", 1000 | "title": "Frau" 1001 | }, 1002 | { 1003 | "userId": "zrowland", 1004 | "firstName": "Zia", 1005 | "lastName": "Rowland", 1006 | "email": "Pellentesque.habitant@natoquepenatibus.ca", 1007 | "company": "Etiam Vestibulum Massa LLP", 1008 | "location": "Masullas", 1009 | "title": "Frau" 1010 | }, 1011 | { 1012 | "userId": "raustin", 1013 | "firstName": "Remedios", 1014 | "lastName": "Austin", 1015 | "email": "tempus.lorem@nonmassanon.com", 1016 | "company": "In Consequat Enim PC", 1017 | "location": "Koninksem", 1018 | "title": "Herr" 1019 | }, 1020 | { 1021 | "userId": "dbridges", 1022 | "firstName": "Dexter", 1023 | "lastName": "Bridges", 1024 | "email": "pede.ultrices@Suspendisse.ca", 1025 | "company": "A Aliquet Vel Industries", 1026 | "location": "Virton", 1027 | "title": "Dr." 1028 | }, 1029 | { 1030 | "userId": "hfisher", 1031 | "firstName": "Harding", 1032 | "lastName": "Fisher", 1033 | "email": "Lorem.ipsum.dolor@Nullamfeugiat.com", 1034 | "company": "Egestas Urna Justo Ltd", 1035 | "location": "Nedlands", 1036 | "title": "Frau" 1037 | }, 1038 | { 1039 | "userId": "apacheco", 1040 | "firstName": "Aretha", 1041 | "lastName": "Pacheco", 1042 | "email": "consectetuer@luctuset.ca", 1043 | "company": "Ipsum Company", 1044 | "location": "Åkersberga", 1045 | "title": "Frau" 1046 | }, 1047 | { 1048 | "userId": "bcombs", 1049 | "firstName": "Bevis", 1050 | "lastName": "Combs", 1051 | "email": "vel.convallis@erosnon.com", 1052 | "company": "Vestibulum Industries", 1053 | "location": "Daussoulx", 1054 | "title": "Herr" 1055 | }, 1056 | { 1057 | "userId": "pcasey", 1058 | "firstName": "Phyllis", 1059 | "lastName": "Casey", 1060 | "email": "ac.urna@atfringilla.ca", 1061 | "company": "Feugiat LLC", 1062 | "location": "Chandler", 1063 | "title": "Herr" 1064 | }, 1065 | { 1066 | "userId": "uanderson", 1067 | "firstName": "Unity", 1068 | "lastName": "Anderson", 1069 | "email": "tellus.lorem@Curabitur.ca", 1070 | "company": "Ornare Lectus Justo LLP", 1071 | "location": "Poppel", 1072 | "title": "Herr" 1073 | }, 1074 | { 1075 | "userId": "jmichael", 1076 | "firstName": "Jessica", 1077 | "lastName": "Michael", 1078 | "email": "lorem.lorem.luctus@faucibusid.co.uk", 1079 | "company": "Non Lacinia At Inc.", 1080 | "location": "Westmalle", 1081 | "title": "Dr." 1082 | }, 1083 | { 1084 | "userId": "ameyers", 1085 | "firstName": "Amos", 1086 | "lastName": "Meyers", 1087 | "email": "ornare@ornare.edu", 1088 | "company": "Donec Foundation", 1089 | "location": "Bloomington", 1090 | "title": "Frau" 1091 | }, 1092 | { 1093 | "userId": "halvarez", 1094 | "firstName": "Hedy", 1095 | "lastName": "Alvarez", 1096 | "email": "sit.amet.faucibus@Duis.co.uk", 1097 | "company": "Sapien Nunc Consulting", 1098 | "location": "Böblingen", 1099 | "title": "Frau" 1100 | }, 1101 | { 1102 | "userId": "acarroll", 1103 | "firstName": "Aladdin", 1104 | "lastName": "Carroll", 1105 | "email": "id@interdumligula.ca", 1106 | "company": "Gravida Non Sollicitudin Corporation", 1107 | "location": "Maule", 1108 | "title": "Frau" 1109 | }, 1110 | { 1111 | "userId": "znavarro", 1112 | "firstName": "Zelenia", 1113 | "lastName": "Navarro", 1114 | "email": "ultrices@Nulla.edu", 1115 | "company": "Parturient Incorporated", 1116 | "location": "Quinchao", 1117 | "title": "Herr" 1118 | }, 1119 | { 1120 | "userId": "dbaldwin", 1121 | "firstName": "Darrel", 1122 | "lastName": "Baldwin", 1123 | "email": "Ut@Aliquam.net", 1124 | "company": "Sem Eget Massa Incorporated", 1125 | "location": "Concepción", 1126 | "title": "Dr." 1127 | }, 1128 | { 1129 | "userId": "crusso", 1130 | "firstName": "Chaim", 1131 | "lastName": "Russo", 1132 | "email": "sed.facilisis@pharetranibhAliquam.com", 1133 | "company": "Lobortis Nisi Nibh PC", 1134 | "location": "Ethe", 1135 | "title": "Frau" 1136 | }, 1137 | { 1138 | "userId": "lbernard", 1139 | "firstName": "Lane", 1140 | "lastName": "Bernard", 1141 | "email": "non.quam.Pellentesque@egetvarius.co.uk", 1142 | "company": "Cras Dictum Ultricies Company", 1143 | "location": "Cametá", 1144 | "title": "Herr" 1145 | }, 1146 | { 1147 | "userId": "alevine", 1148 | "firstName": "Amelia", 1149 | "lastName": "Levine", 1150 | "email": "Maecenas.libero@sagittisNullamvitae.com", 1151 | "company": "Massa LLC", 1152 | "location": "Laino Castello", 1153 | "title": "Herr" 1154 | }, 1155 | { 1156 | "userId": "tewing", 1157 | "firstName": "Timothy", 1158 | "lastName": "Ewing", 1159 | "email": "erat.vitae.risus@rutrum.net", 1160 | "company": "Duis LLC", 1161 | "location": "Calco", 1162 | "title": "Herr" 1163 | }, 1164 | { 1165 | "userId": "cwong", 1166 | "firstName": "Cooper", 1167 | "lastName": "Wong", 1168 | "email": "quam.vel.sapien@risusquis.org", 1169 | "company": "Sit Amet Diam Associates", 1170 | "location": "Bellingen", 1171 | "title": "Herr" 1172 | }, 1173 | { 1174 | "userId": "dherrera", 1175 | "firstName": "Donna", 1176 | "lastName": "Herrera", 1177 | "email": "porttitor.eros.nec@sapienNuncpulvinar.org", 1178 | "company": "Erat Inc.", 1179 | "location": "Caccamo", 1180 | "title": "Frau" 1181 | }, 1182 | { 1183 | "userId": "fgreer", 1184 | "firstName": "Fredericka", 1185 | "lastName": "Greer", 1186 | "email": "ornare.sagittis.felis@acturpis.ca", 1187 | "company": "Ac Feugiat Non Ltd", 1188 | "location": "Vrasene", 1189 | "title": "Frau" 1190 | }, 1191 | { 1192 | "userId": "ccarney", 1193 | "firstName": "Cally", 1194 | "lastName": "Carney", 1195 | "email": "consequat.purus@et.ca", 1196 | "company": "Imperdiet Erat Nonummy Ltd", 1197 | "location": "Bursa", 1198 | "title": "Frau" 1199 | }, 1200 | { 1201 | "userId": "dherring", 1202 | "firstName": "Diana", 1203 | "lastName": "Herring", 1204 | "email": "ligula@Fusce.net", 1205 | "company": "Congue In Scelerisque Industries", 1206 | "location": "Lissewege", 1207 | "title": "Frau" 1208 | }, 1209 | { 1210 | "userId": "wrhodes", 1211 | "firstName": "Wynne", 1212 | "lastName": "Rhodes", 1213 | "email": "gravida.Aliquam@malesuada.edu", 1214 | "company": "Magna A LLP", 1215 | "location": "Corroy-le-Grand", 1216 | "title": "Frau" 1217 | }, 1218 | { 1219 | "userId": "jmiddleton", 1220 | "firstName": "Jared", 1221 | "lastName": "Middleton", 1222 | "email": "diam@odio.edu", 1223 | "company": "Euismod Company", 1224 | "location": "Rangiora", 1225 | "title": "Frau" 1226 | }, 1227 | { 1228 | "userId": "mwarner", 1229 | "firstName": "Mallory", 1230 | "lastName": "Warner", 1231 | "email": "interdum@mattissemperdui.com", 1232 | "company": "Lacus Corporation", 1233 | "location": "Otricoli", 1234 | "title": "Herr" 1235 | }, 1236 | { 1237 | "userId": "tchristian", 1238 | "firstName": "Tyler", 1239 | "lastName": "Christian", 1240 | "email": "interdum@arcuVestibulumante.com", 1241 | "company": "Justo Nec Ante Associates", 1242 | "location": "Petacciato", 1243 | "title": "Frau" 1244 | }, 1245 | { 1246 | "userId": "bburton", 1247 | "firstName": "Brody", 1248 | "lastName": "Burton", 1249 | "email": "porttitor.scelerisque@loremvehiculaet.co.uk", 1250 | "company": "Purus Sapien Gravida Incorporated", 1251 | "location": "Coreglia Antelminelli", 1252 | "title": "Frau" 1253 | }, 1254 | { 1255 | "userId": "brivers", 1256 | "firstName": "Brenden", 1257 | "lastName": "Rivers", 1258 | "email": "malesuada@adipiscingenimmi.co.uk", 1259 | "company": "Laoreet Libero Et Industries", 1260 | "location": "Pickering", 1261 | "title": "Herr" 1262 | }, 1263 | { 1264 | "userId": "kparks", 1265 | "firstName": "Kai", 1266 | "lastName": "Parks", 1267 | "email": "eu.neque@eu.ca", 1268 | "company": "Tellus Associates", 1269 | "location": "Pichilemu", 1270 | "title": "Dr." 1271 | }, 1272 | { 1273 | "userId": "mchristian", 1274 | "firstName": "Myles", 1275 | "lastName": "Christian", 1276 | "email": "nulla@molestiedapibus.net", 1277 | "company": "Aliquet Nec Imperdiet Foundation", 1278 | "location": "Kamalia", 1279 | "title": "Dr." 1280 | }, 1281 | { 1282 | "userId": "mhampton", 1283 | "firstName": "Morgan", 1284 | "lastName": "Hampton", 1285 | "email": "amet.dapibus.id@a.net", 1286 | "company": "Vel LLP", 1287 | "location": "Gandhinagar", 1288 | "title": "Frau" 1289 | }, 1290 | { 1291 | "userId": "xrobinson", 1292 | "firstName": "Xavier", 1293 | "lastName": "Robinson", 1294 | "email": "ac@necante.org", 1295 | "company": "Consequat Dolor Vitae Inc.", 1296 | "location": "Russell", 1297 | "title": "Herr" 1298 | }, 1299 | { 1300 | "userId": "whale", 1301 | "firstName": "Wade", 1302 | "lastName": "Hale", 1303 | "email": "Maecenas.iaculis.aliquet@odiovelest.org", 1304 | "company": "Pellentesque Limited", 1305 | "location": "Duluth", 1306 | "title": "Dr." 1307 | }, 1308 | { 1309 | "userId": "apeterson", 1310 | "firstName": "Audra", 1311 | "lastName": "Peterson", 1312 | "email": "eu.lacus.Quisque@pedeCrasvulputate.net", 1313 | "company": "Ornare Libero At Incorporated", 1314 | "location": "Mobile", 1315 | "title": "Dr." 1316 | }, 1317 | { 1318 | "userId": "tochoa", 1319 | "firstName": "Teagan", 1320 | "lastName": "Ochoa", 1321 | "email": "volutpat.Nulla.dignissim@miDuisrisus.ca", 1322 | "company": "Quam Industries", 1323 | "location": "Ongole", 1324 | "title": "Frau" 1325 | }, 1326 | { 1327 | "userId": "agreer", 1328 | "firstName": "Anika", 1329 | "lastName": "Greer", 1330 | "email": "at.pede.Cras@conubia.co.uk", 1331 | "company": "Id Sapien Cras LLC", 1332 | "location": "Maiduguri", 1333 | "title": "Frau" 1334 | }, 1335 | { 1336 | "userId": "xwalsh", 1337 | "firstName": "Xerxes", 1338 | "lastName": "Walsh", 1339 | "email": "mollis.vitae@idante.org", 1340 | "company": "Dis Parturient Montes Limited", 1341 | "location": "Landenne", 1342 | "title": "Dr." 1343 | }, 1344 | { 1345 | "userId": "acastro", 1346 | "firstName": "Allistair", 1347 | "lastName": "Castro", 1348 | "email": "ridiculus.mus.Proin@a.edu", 1349 | "company": "Ante Bibendum LLP", 1350 | "location": "Llangefni", 1351 | "title": "Dr." 1352 | }, 1353 | { 1354 | "userId": "atyson", 1355 | "firstName": "Audra", 1356 | "lastName": "Tyson", 1357 | "email": "magna.Lorem@interdum.org", 1358 | "company": "Mauris Industries", 1359 | "location": "Dalbeattie", 1360 | "title": "Herr" 1361 | }, 1362 | { 1363 | "userId": "akaufman", 1364 | "firstName": "Ainsley", 1365 | "lastName": "Kaufman", 1366 | "email": "eget.magna.Suspendisse@dolorvitaedolor.com", 1367 | "company": "Facilisi Ltd", 1368 | "location": "Zittau", 1369 | "title": "Herr" 1370 | }, 1371 | { 1372 | "userId": "ewalsh", 1373 | "firstName": "Erica", 1374 | "lastName": "Walsh", 1375 | "email": "vel@magna.ca", 1376 | "company": "Vulputate Ullamcorper Magna Industries", 1377 | "location": "Pietrarubbia", 1378 | "title": "Herr" 1379 | }, 1380 | { 1381 | "userId": "ihudson", 1382 | "firstName": "Isadora", 1383 | "lastName": "Hudson", 1384 | "email": "Integer.urna.Vivamus@AliquamnislNulla.co.uk", 1385 | "company": "Orci Consectetuer Ltd", 1386 | "location": "Opdorp", 1387 | "title": "Herr" 1388 | }, 1389 | { 1390 | "userId": "jmercer", 1391 | "firstName": "Jolene", 1392 | "lastName": "Mercer", 1393 | "email": "volutpat@Vestibulum.com", 1394 | "company": "Sem Molestie Inc.", 1395 | "location": "Leduc", 1396 | "title": "Dr." 1397 | }, 1398 | { 1399 | "userId": "ptyson", 1400 | "firstName": "Palmer", 1401 | "lastName": "Tyson", 1402 | "email": "ligula.Nullam@sagittisNullamvitae.com", 1403 | "company": "Eu Odio Phasellus Associates", 1404 | "location": "Archennes", 1405 | "title": "Herr" 1406 | }, 1407 | { 1408 | "userId": "njennings", 1409 | "firstName": "Nayda", 1410 | "lastName": "Jennings", 1411 | "email": "vitae@orcitincidunt.com", 1412 | "company": "Vulputate Inc.", 1413 | "location": "Legal", 1414 | "title": "Frau" 1415 | }, 1416 | { 1417 | "userId": "ohorne", 1418 | "firstName": "Octavia", 1419 | "lastName": "Horne", 1420 | "email": "semper@mollisIntegertincidunt.net", 1421 | "company": "Nibh Enim Gravida Incorporated", 1422 | "location": "Santa Bárbara", 1423 | "title": "Frau" 1424 | }, 1425 | { 1426 | "userId": "ifuentes", 1427 | "firstName": "Ivor", 1428 | "lastName": "Fuentes", 1429 | "email": "nascetur@semper.org", 1430 | "company": "Iaculis Lacus Corp.", 1431 | "location": "Jundiaí", 1432 | "title": "Frau" 1433 | }, 1434 | { 1435 | "userId": "fhammond", 1436 | "firstName": "Felix", 1437 | "lastName": "Hammond", 1438 | "email": "turpis@diamvel.net", 1439 | "company": "Felis Corporation", 1440 | "location": "Maubeuge", 1441 | "title": "Herr" 1442 | }, 1443 | { 1444 | "userId": "bwillis", 1445 | "firstName": "Britanni", 1446 | "lastName": "Willis", 1447 | "email": "Ut.tincidunt@Integer.org", 1448 | "company": "Velit Quisque Foundation", 1449 | "location": "Pierrefonds", 1450 | "title": "Herr" 1451 | }, 1452 | { 1453 | "userId": "srowland", 1454 | "firstName": "Simone", 1455 | "lastName": "Rowland", 1456 | "email": "at@diamProin.edu", 1457 | "company": "Sem Egestas Inc.", 1458 | "location": "Austin", 1459 | "title": "Herr" 1460 | }, 1461 | { 1462 | "userId": "zcrosby", 1463 | "firstName": "Zia", 1464 | "lastName": "Crosby", 1465 | "email": "aliquet@Duisvolutpat.edu", 1466 | "company": "Quisque Varius Nam LLP", 1467 | "location": "Ripalta Guerina", 1468 | "title": "Herr" 1469 | }, 1470 | { 1471 | "userId": "rmercer", 1472 | "firstName": "Robert", 1473 | "lastName": "Mercer", 1474 | "email": "quis@Curabituregestas.org", 1475 | "company": "Et Consulting", 1476 | "location": "Marano Lagunare", 1477 | "title": "Frau" 1478 | }, 1479 | { 1480 | "userId": "qmaldonado", 1481 | "firstName": "Quintessa", 1482 | "lastName": "Maldonado", 1483 | "email": "taciti.sociosqu@atfringillapurus.edu", 1484 | "company": "Mollis Duis Sit Inc.", 1485 | "location": "Owerri", 1486 | "title": "Frau" 1487 | }, 1488 | { 1489 | "userId": "trodriguez", 1490 | "firstName": "Tashya", 1491 | "lastName": "Rodriguez", 1492 | "email": "Phasellus@dui.net", 1493 | "company": "Arcu Sed Eu Corp.", 1494 | "location": "Buckingham", 1495 | "title": "Frau" 1496 | }, 1497 | { 1498 | "userId": "ssexton", 1499 | "firstName": "Savannah", 1500 | "lastName": "Sexton", 1501 | "email": "Phasellus.elit.pede@metussitamet.org", 1502 | "company": "Magna Nec Corporation", 1503 | "location": "Hull", 1504 | "title": "Herr" 1505 | }, 1506 | { 1507 | "userId": "chill", 1508 | "firstName": "Christine", 1509 | "lastName": "Hill", 1510 | "email": "auctor.quis@nequeMorbiquis.org", 1511 | "company": "Et Inc.", 1512 | "location": "Craco", 1513 | "title": "Frau" 1514 | }, 1515 | { 1516 | "userId": "hbutler", 1517 | "firstName": "Hedda", 1518 | "lastName": "Butler", 1519 | "email": "Nunc@vitaeorci.org", 1520 | "company": "Sapien Cras Consulting", 1521 | "location": "Fort St. John", 1522 | "title": "Dr." 1523 | }, 1524 | { 1525 | "userId": "lcardenas", 1526 | "firstName": "Larissa", 1527 | "lastName": "Cardenas", 1528 | "email": "lacus.Nulla@Fuscemi.co.uk", 1529 | "company": "Orci Tincidunt Associates", 1530 | "location": "San Clemente", 1531 | "title": "Frau" 1532 | }, 1533 | { 1534 | "userId": "sdorsey", 1535 | "firstName": "Shelby", 1536 | "lastName": "Dorsey", 1537 | "email": "in@sitametmetus.ca", 1538 | "company": "Aliquam Gravida LLP", 1539 | "location": "San Isidro", 1540 | "title": "Herr" 1541 | }, 1542 | { 1543 | "userId": "hlove", 1544 | "firstName": "Haley", 1545 | "lastName": "Love", 1546 | "email": "dictum@adipiscinglobortisrisus.com", 1547 | "company": "Ipsum LLC", 1548 | "location": "Hatfield", 1549 | "title": "Dr." 1550 | }, 1551 | { 1552 | "userId": "imorrison", 1553 | "firstName": "Isabelle", 1554 | "lastName": "Morrison", 1555 | "email": "non.sollicitudin.a@sapien.ca", 1556 | "company": "Tellus Aenean Egestas Associates", 1557 | "location": "Negrete", 1558 | "title": "Herr" 1559 | }, 1560 | { 1561 | "userId": "bfrank", 1562 | "firstName": "Belle", 1563 | "lastName": "Frank", 1564 | "email": "mus.Aenean.eget@lacus.org", 1565 | "company": "Eu Incorporated", 1566 | "location": "Cáceres", 1567 | "title": "Dr." 1568 | }, 1569 | { 1570 | "userId": "dolson", 1571 | "firstName": "Donna", 1572 | "lastName": "Olson", 1573 | "email": "at.libero@dolornonummyac.net", 1574 | "company": "Justo Ltd", 1575 | "location": "Agra", 1576 | "title": "Frau" 1577 | }, 1578 | { 1579 | "userId": "jcrosby", 1580 | "firstName": "Justina", 1581 | "lastName": "Crosby", 1582 | "email": "malesuada.fames@lectuspedeultrices.org", 1583 | "company": "A Industries", 1584 | "location": "Whitby", 1585 | "title": "Dr." 1586 | }, 1587 | { 1588 | "userId": "psmith", 1589 | "firstName": "Petra", 1590 | "lastName": "Smith", 1591 | "email": "auctor.odio@Suspendissealiquetsem.co.uk", 1592 | "company": "Mauris Molestie Pharetra Associates", 1593 | "location": "San Fratello", 1594 | "title": "Herr" 1595 | }, 1596 | { 1597 | "userId": "ksilva", 1598 | "firstName": "Karyn", 1599 | "lastName": "Silva", 1600 | "email": "montes.nascetur@erat.com", 1601 | "company": "Vitae Risus Duis Foundation", 1602 | "location": "Zierikzee", 1603 | "title": "Herr" 1604 | }, 1605 | { 1606 | "userId": "mleon", 1607 | "firstName": "Maya", 1608 | "lastName": "Leon", 1609 | "email": "lacus.varius.et@orci.org", 1610 | "company": "Lacus Mauris Incorporated", 1611 | "location": "MŽlin", 1612 | "title": "Frau" 1613 | }, 1614 | { 1615 | "userId": "bsheppard", 1616 | "firstName": "Beau", 1617 | "lastName": "Sheppard", 1618 | "email": "et.netus@porttitorvulputateposuere.org", 1619 | "company": "Vivamus Nibh Dolor Consulting", 1620 | "location": "Owen Sound", 1621 | "title": "Dr." 1622 | }, 1623 | { 1624 | "userId": "daguilar", 1625 | "firstName": "Dustin", 1626 | "lastName": "Aguilar", 1627 | "email": "adipiscing.elit.Curabitur@Fuscedolorquam.net", 1628 | "company": "Erat Inc.", 1629 | "location": "Poggiodomo", 1630 | "title": "Frau" 1631 | }, 1632 | { 1633 | "userId": "lpearson", 1634 | "firstName": "Lucian", 1635 | "lastName": "Pearson", 1636 | "email": "pede@liberoProinsed.co.uk", 1637 | "company": "Arcu Imperdiet Ltd", 1638 | "location": "Kapfenberg", 1639 | "title": "Herr" 1640 | }, 1641 | { 1642 | "userId": "bsloan", 1643 | "firstName": "Bethany", 1644 | "lastName": "Sloan", 1645 | "email": "magna.Suspendisse@Nullatinciduntneque.co.uk", 1646 | "company": "Curabitur Limited", 1647 | "location": "Senneville", 1648 | "title": "Frau" 1649 | }, 1650 | { 1651 | "userId": "pwalker", 1652 | "firstName": "Paloma", 1653 | "lastName": "Walker", 1654 | "email": "purus@nequeMorbi.org", 1655 | "company": "Tempus Eu Ligula Limited", 1656 | "location": "Hohen Neuendorf", 1657 | "title": "Frau" 1658 | }, 1659 | { 1660 | "userId": "tflynn", 1661 | "firstName": "Tallulah", 1662 | "lastName": "Flynn", 1663 | "email": "egestas.a@Nullasempertellus.net", 1664 | "company": "Nunc Inc.", 1665 | "location": "Braunschweig", 1666 | "title": "Herr" 1667 | }, 1668 | { 1669 | "userId": "kle", 1670 | "firstName": "Kalia", 1671 | "lastName": "Le", 1672 | "email": "consectetuer.cursus@Nullam.ca", 1673 | "company": "Ligula Consectetuer Corporation", 1674 | "location": "Clare", 1675 | "title": "Dr." 1676 | }, 1677 | { 1678 | "userId": "cshepard", 1679 | "firstName": "Chester", 1680 | "lastName": "Shepard", 1681 | "email": "Donec@eunibh.co.uk", 1682 | "company": "Nonummy Ipsum Non Associates", 1683 | "location": "Rotheux-RimiŽre", 1684 | "title": "Herr" 1685 | }, 1686 | { 1687 | "userId": "amckenzie", 1688 | "firstName": "Allistair", 1689 | "lastName": "Mckenzie", 1690 | "email": "libero.et.tristique@facilisisvitae.com", 1691 | "company": "Eleifend Cras Sed Incorporated", 1692 | "location": "Pontevedra", 1693 | "title": "Frau" 1694 | }, 1695 | { 1696 | "userId": "flindsay", 1697 | "firstName": "Flynn", 1698 | "lastName": "Lindsay", 1699 | "email": "parturient.montes.nascetur@Sednec.org", 1700 | "company": "Sit Amet Nulla Industries", 1701 | "location": "Tucson", 1702 | "title": "Dr." 1703 | }, 1704 | { 1705 | "userId": "rgoff", 1706 | "firstName": "Rowan", 1707 | "lastName": "Goff", 1708 | "email": "Duis.mi.enim@metusfacilisislorem.com", 1709 | "company": "Risus Nulla Eget Ltd", 1710 | "location": "Châlons-en-Champagne", 1711 | "title": "Frau" 1712 | }, 1713 | { 1714 | "userId": "dchase", 1715 | "firstName": "Damian", 1716 | "lastName": "Chase", 1717 | "email": "ligula.Aliquam.erat@nisidictum.ca", 1718 | "company": "Cursus LLP", 1719 | "location": "Stirling", 1720 | "title": "Frau" 1721 | }, 1722 | { 1723 | "userId": "bpotts", 1724 | "firstName": "Blythe", 1725 | "lastName": "Potts", 1726 | "email": "Sed.eu@ultricesposuerecubilia.org", 1727 | "company": "Ligula Aenean Institute", 1728 | "location": "Sant'Angelo Limosano", 1729 | "title": "Frau" 1730 | }, 1731 | { 1732 | "userId": "astark", 1733 | "firstName": "Akeem", 1734 | "lastName": "Stark", 1735 | "email": "mattis.Cras.eget@sed.com", 1736 | "company": "Pretium Aliquet Institute", 1737 | "location": "Sankt Johann im Pongau", 1738 | "title": "Frau" 1739 | }, 1740 | { 1741 | "userId": "jmacdonald", 1742 | "firstName": "Jenette", 1743 | "lastName": "Macdonald", 1744 | "email": "commodo.auctor@ante.ca", 1745 | "company": "Sociis Natoque Inc.", 1746 | "location": "Rinconada", 1747 | "title": "Dr." 1748 | }, 1749 | { 1750 | "userId": "pstewart", 1751 | "firstName": "Price", 1752 | "lastName": "Stewart", 1753 | "email": "arcu.Vestibulum.ante@nullaCraseu.com", 1754 | "company": "Leo Associates", 1755 | "location": "Donnas", 1756 | "title": "Herr" 1757 | }, 1758 | { 1759 | "userId": "ogamble", 1760 | "firstName": "Oliver", 1761 | "lastName": "Gamble", 1762 | "email": "mollis.Phasellus@urna.edu", 1763 | "company": "Erat Ltd", 1764 | "location": "Capestrano", 1765 | "title": "Frau" 1766 | }, 1767 | { 1768 | "userId": "kbarnes", 1769 | "firstName": "Keith", 1770 | "lastName": "Barnes", 1771 | "email": "Vestibulum.ut@ipsum.edu", 1772 | "company": "Libero At Auctor Foundation", 1773 | "location": "Lacombe County", 1774 | "title": "Dr." 1775 | }, 1776 | { 1777 | "userId": "kmorse", 1778 | "firstName": "Kaitlin", 1779 | "lastName": "Morse", 1780 | "email": "magna.Praesent.interdum@nonummyultriciesornare.net", 1781 | "company": "Turpis Egestas LLP", 1782 | "location": "Itegem", 1783 | "title": "Dr." 1784 | }, 1785 | { 1786 | "userId": "cwiggins", 1787 | "firstName": "Craig", 1788 | "lastName": "Wiggins", 1789 | "email": "amet@dolortempus.edu", 1790 | "company": "Dignissim Company", 1791 | "location": "Lustin", 1792 | "title": "Dr." 1793 | }, 1794 | { 1795 | "userId": "eanthony", 1796 | "firstName": "Erica", 1797 | "lastName": "Anthony", 1798 | "email": "erat.in.consectetuer@acarcuNunc.org", 1799 | "company": "Posuere Vulputate Lacus Institute", 1800 | "location": "Jundiaí", 1801 | "title": "Frau" 1802 | } 1803 | ] 1804 | } -------------------------------------------------------------------------------- /de.tammenit.uhdapp/webapp/model/models.ts: -------------------------------------------------------------------------------- 1 | sap.ui.define(['sap/ui/model/json/JSONModel', 'sap/ui/Device'], function( 2 | JSONModel: typeof sap.ui.model.json.JSONModel, 3 | Device: typeof sap.ui.Device 4 | ) { 5 | 'use strict' 6 | 7 | return { 8 | createDeviceModel: function() { 9 | const oModel = new JSONModel(Device, false) 10 | oModel.setDefaultBindingMode(sap.ui.model.BindingMode.OneWay) 11 | return oModel 12 | }, 13 | } 14 | }) 15 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/webapp/resources/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/htammen/ui5con_typescript/131c746ad0b339def65b6872577fb5d7576784f9/de.tammenit.uhdapp/webapp/resources/img/favicon.ico -------------------------------------------------------------------------------- /de.tammenit.uhdapp/webapp/test/initMockServer.ts: -------------------------------------------------------------------------------- 1 | /** global Promise */ 2 | sap.ui.define(['de/tammenit/UHDApp/localService/mockserver'], function(Mockserver) { 3 | 'use strict' 4 | 5 | // initialize the mock server 6 | const mockserver = new Mockserver() 7 | mockserver.init() 8 | 9 | // initialize the embedded component on the HTML page 10 | sap.ui.require(['sap/ui/core/ComponentSupport']) 11 | }) 12 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/webapp/test/integration/AllJourneys.js: -------------------------------------------------------------------------------- 1 | sap.ui.define([ 2 | "sap/ui/test/Opa5", 3 | "de/tammenit/UHDApp/test/integration/arrangements/Startup", 4 | "de/tammenit/UHDApp/test/integration/BasicJourney" 5 | ], function(Opa5, Startup) { 6 | "use strict"; 7 | 8 | Opa5.extendConfig({ 9 | arrangements: new Startup(), 10 | pollingInterval: 1 11 | }); 12 | 13 | }); 14 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/webapp/test/integration/BasicJourney.js: -------------------------------------------------------------------------------- 1 | sap.ui.define([ 2 | "sap/ui/test/opaQunit", 3 | "de/tammenit/UHDApp/test/integration/pages/App" 4 | ], function (opaTest) { 5 | "use strict"; 6 | 7 | opaTest("should show correct number of nested pages", function (Given, When, Then) { 8 | 9 | // Arrangements 10 | Given.iStartMyApp(); 11 | 12 | // Assertions 13 | Then.onTheAppPage.iShouldSeePageCount(1); 14 | 15 | // Cleanup 16 | Then.iTeardownMyApp(); 17 | }); 18 | 19 | }); 20 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/webapp/test/integration/arrangements/Startup.js: -------------------------------------------------------------------------------- 1 | sap.ui.define([ 2 | "sap/ui/test/Opa5" 3 | ], function(Opa5) { 4 | "use strict"; 5 | 6 | return Opa5.extend("de.tammenit.UHDApp.test.integration.arrangements.Startup", { 7 | 8 | iStartMyApp: function () { 9 | this.iStartMyUIComponent({ 10 | componentConfig: { 11 | name: "de.tammenit.UHDApp", 12 | async: true, 13 | manifest: true 14 | } 15 | }); 16 | } 17 | 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/webapp/test/integration/opaTests.qunit.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Integration tests for Todo App 6 | 7 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 |
29 | 30 | 31 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/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 | "de/tammenit/UHDApp/test/integration/BasicJourney" 10 | ], function() { 11 | QUnit.start(); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/webapp/test/integration/pages/App.js: -------------------------------------------------------------------------------- 1 | sap.ui.require([ 2 | "sap/ui/test/Opa5", 3 | "sap/ui/test/matchers/AggregationLengthEquals" 4 | ], function (Opa5, AggregationLengthEquals) { 5 | "use strict"; 6 | 7 | var sViewName = "de.tammenit.UHDApp.view.Main"; 8 | var sAppId = "idAppControl"; 9 | 10 | Opa5.createPageObjects({ 11 | onTheAppPage: { 12 | 13 | assertions: { 14 | 15 | iShouldSeePageCount: function(iItemCount) { 16 | return this.waitFor({ 17 | id: sAppId, 18 | viewName: sViewName, 19 | matchers: [new AggregationLengthEquals({ 20 | name: "pages", 21 | length: iItemCount 22 | })], 23 | success: function() { 24 | Opa5.assert.ok(true, "The app contains one page"); 25 | }, 26 | errorMessage: "App does not have expected number of pages '" + iItemCount + "'." 27 | }); 28 | } 29 | } 30 | 31 | } 32 | }); 33 | 34 | }); 35 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/webapp/test/mockserver.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | UHDApp 9 | 10 | 22 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/webapp/test/testsuite.qunit.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | QUnit test suite for Worklist 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/webapp/test/testsuite.qunit.js: -------------------------------------------------------------------------------- 1 | /* global window, parent, location */ 2 | 3 | // eslint-disable-next-line sap-no-global-define 4 | window.suite = function() { 5 | "use strict"; 6 | 7 | // eslint-disable-next-line 8 | var oSuite = new parent.jsUnitTestSuite(), 9 | sContextPath = location.pathname.substring(0, location.pathname.lastIndexOf("/") + 1); 10 | 11 | oSuite.addTestPage(sContextPath + "integration/opaTests.qunit.html"); 12 | 13 | return oSuite; 14 | }; 15 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/webapp/view/App.view.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/webapp/view/Main.view.xml: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 22 | 23 | 24 | 25 | 26 | 35 | 36 | 40 | 41 | 42 | 43 | 44 | 45 | 48 | 49 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 81 | 82 | 83 | 84 | 87 | 88 | 89 | 90 | 91 | 95 | 96 | 97 | 98 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/webapp/view/NotFound.view.xml: -------------------------------------------------------------------------------- 1 | 5 | 12 | 13 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/webapp/view/blocks/main/BlockAssignedRoles.ts: -------------------------------------------------------------------------------- 1 | sap.ui.define( 2 | ['sap/uxap/BlockBase'], 3 | function(BlockBase) { 4 | 'use strict' 5 | 6 | const BlockAssignedRoles = BlockBase.extend('de.tammenit.UHDApp.view.blocks.main.BlockAssignedRoles', { 7 | metadata: {}, 8 | 9 | onToggleCollaborations: function(oEvent) {}, 10 | }) 11 | return BlockAssignedRoles 12 | }, 13 | true 14 | ) 15 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/webapp/view/blocks/main/BlockAssignedRoles.view.xml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 21 | 22 | 23 | 24 | 25 | 28 | 29 | 30 | 31 | 32 | 33 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 |
50 |
51 |
52 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/webapp/view/blocks/main/BlockLDAP.ts: -------------------------------------------------------------------------------- 1 | sap.ui.define( 2 | ['sap/uxap/BlockBase'], 3 | function(BlockBase) { 4 | 'use strict' 5 | 6 | const BlockAssignedRoles = BlockBase.extend('de.tammenit.UHDApp.view.blocks.main.BlockLDAP', { 7 | metadata: {}, 8 | }) 9 | return BlockAssignedRoles 10 | }, 11 | true 12 | ) 13 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/webapp/view/blocks/main/BlockLDAP.view.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 19 | 20 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/webapp/view/blocks/main/BlockOpenRequests.ts: -------------------------------------------------------------------------------- 1 | sap.ui.define( 2 | ['sap/uxap/BlockBase'], 3 | function(BlockBase) { 4 | 'use strict' 5 | 6 | const BlockOpenRequests = BlockBase.extend('de.tammenit.UHDApp.view.blocks.main.BlockOpenRequests', { 7 | metadata: {}, 8 | 9 | onToggleCollaborations: function(oEvent) {}, 10 | }) 11 | return BlockOpenRequests 12 | }, 13 | true 14 | ) 15 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/webapp/view/blocks/main/BlockOpenRequests.view.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 17 | 18 | 19 | 20 | 21 | 24 | 25 | 26 | 27 | 28 | 29 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 |
47 |
48 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/webapp/view/blocks/main/BlockPersonalData.ts: -------------------------------------------------------------------------------- 1 | sap.ui.define( 2 | ['sap/uxap/BlockBase'], 3 | function(BlockBase) { 4 | 'use strict' 5 | 6 | const BlockPersonalData = BlockBase.extend('de.tammenit.UHDApp.view.blocks.main.BlockPersonalData', { 7 | metadata: {}, 8 | 9 | onToggleCollaborations: function(oEvent) { 10 | // 11 | }, 12 | }) 13 | return BlockPersonalData 14 | }, 15 | true 16 | ) 17 | -------------------------------------------------------------------------------- /de.tammenit.uhdapp/webapp/view/blocks/main/BlockPersonalData.view.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 19 | 20 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /presi/PresenterNotes.md: -------------------------------------------------------------------------------- 1 | # Demo tssandbox 2 | ## 1st Example 3 | - create `greeting.ts` 4 | - add code 5 | ```typescript 6 | function greet(msg: string): string { 7 | return `Hello ${msg}` 8 | } 9 | 10 | console.log(greet("UI5Con")) 11 | console.log(greet(true)) 12 | ``` 13 | - notice error in `greet(true)` 14 | - tsc greeting.ts 15 | - compile time error !! 16 | - run program `node greeting.js` 17 | - show @ts-ignore in source 18 | - show tsc --watch (cmd line and VS Code) 19 | - show ts settings 20 | ```json 21 | "files.exclude": { 22 | "**/*.js": { 23 | "when": "$(basename).ts" 24 | }, 25 | "**/*.js.map": true 26 | }, 27 | ``` 28 | - show `Problems` view in VS Code 29 | - Mention `.vscode/tasks.json` 30 | ``` 31 | "problemMatcher": [ 32 | "$tsc-watch" 33 | ], 34 | ``` 35 | and 36 | ``` 37 | "runOptions": { 38 | "runOn": "folderOpen" 39 | } 40 | ``` 41 | 42 | ## UI5 example 43 | - Show application: `npm start`, `http://localhost:8080/index.html` 44 | - open `tsconfig.json` 45 | - `"module": "none"` 46 | - `"target": "..."` 47 | - `"sourceMap": true` 48 | - `"types": ["@openui5/ts-types"]` 49 | - `"include": ["./webapp/**/*.ts", "./typings/**/*"],` 50 | - open `package.json` 51 | - `"devDependencies": { "@openui5/ts-types": "^1.65.1",` 52 | - https://github.com/SAP/ui5-typescript/tree/master/packages/ts-types 53 | 54 | 55 | -------------------------------------------------------------------------------- /presi/README.md: -------------------------------------------------------------------------------- 1 | 12 | 13 | # UI5Con 2020 Brussels 14 | 15 |

UI5 with Typescript

16 |

Date: 2020-02-14

17 |
18 | 19 | - 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | # About me 29 | 30 |

I'm a freelancer. You can book me

31 | 32 | - Name: Helmut Tammen 33 | - Location: Hannover (Germany) 34 | - Age: 55; can count down the years till retiring :pensive: 35 | - website: [www.tammen-it-solutions.de](http://www.tammen-it-solutions.de) 36 | - twitter: [@helmuttammen](https://twitter.com/helmuttammen) 37 | - SAP Community: [@helmut.tammen2](https://people.sap.com/helmut.tammen2) 38 | - IT-Professional since 1990, SAP-Professional since 2000 39 | 40 |

Professional topics / interests

41 | 42 | (Cloud)-Development, Cloud (in general), SAP Technology, Javascript, **Node.js**, **Typescript**, **CAP**, Functional programming, Terminal, Java, ABAP (try to avoid if possible), **SAPUI5**, **markdown**, 43 | and a lot more 44 | 45 | # UI5 with Typescript? That's not possible! 46 | 47 | - Yes, it is 48 |

49 | 50 | # TS is more verbose than JS! 51 | 52 | - Yes, but editors help you 53 | - Yes, but you get a lot for some extra typing 54 |

55 | 56 | # But what's with all the JS libraries I use? 57 | 58 | - No problem, for almost all libs I used so far there are type definitions 59 |

60 | 61 | # Can I use it in SAP Web IDE? 62 | 63 | - No, but who is still using SAP Web IDE? 64 | - It will run in SAP Business Application Studio 65 |

66 | 67 | # Why Typescript? 68 | 69 | - I'm fine with Javascript 70 | - I don't want to learn another language 71 | - I don't have time to learn another language 72 |

73 | 74 | # You have more options 75 | 76 |
images/venn.png
77 | 78 | # Better quality 79 | 80 | - find errors at compile time 81 | - even better, find errors while typing 82 |

83 | 84 | # Program faster 85 | 86 | - IDE support 87 | - newest (JS) language features 88 |

89 | 90 | # Enhance maintainability 91 | 92 | - it's easier for colleagues to understand your code 93 | - it's easier for you to understand what you did months / years ago 94 |

95 | 96 | # Have more fun 97 | 98 | - use newest language features 99 |

100 | 101 | # So, what exactly is Typescript? 102 | 103 | - TS is a typed superset of JS that compiles to plain JS 104 | - TS is a typed language like Java, C++, C#, ... (but even cooler) 105 | - TS is developed and maintained by Microsoft 106 | 107 | # Features of TS 108 | 109 | - Every line of JS code is valid TS code 110 | - TS implements newest JS features (similar to Babel, but better) 111 | - TS compiles to plain JS (like Babel) 112 | - TS can compile even to ES3 113 | - TS can be installed via npm (`npm i -g typescript`) 114 | - TS is Open source 115 | 116 | ## More on their website 117 | [http://www.typescriptlang.org](http://www.typescriptlang.org/) 118 | 119 | # Why switch from JS to a typed language / TS? 120 | 121 | - TS is self-documenting (like all typed languages) 122 | - TS helps you find errors early 123 | - TS helps you to call the right functions with the right parameters :arrow_right: enhanced DX 124 | - Let TS refactor your code 125 | - Dev tools for TS are great 126 | 127 | # Basics of TS 128 | 129 | ## Basic types 130 | ```javascript 131 | let isDone: boolean = false // boolean 132 | let level: number = 6 // number 133 | let color: string = "blue" // string 134 | let list: number[] = [1, 2, 3] // array of numbers 135 | 136 | let x: [string, number]; // tuple (special form of array) 137 | x = ["hello", 10]; // OK 138 | x = [10, "hello"]; // Error 139 | 140 | enum Color {Red, Green, Blue} 141 | let c: Color = Color.Green; 142 | 143 | ... (any, void, null, Generics, Type Aliases) 144 | 145 | ``` 146 | # Basics of TS 147 | 148 | ## functions 149 | ```javascript 150 | function reverse(word: string): string { 151 | return word.split('').reverse().join('') 152 | } 153 | ``` 154 |
155 |
You can have optional or read-only parameters
156 | 157 | # Basics of TS 158 | 159 | ## Interfaces and Classes 160 | ```javascript 161 | interface Person { 162 | name: string 163 | age?: number // optional property 164 | } 165 | 166 | function birthYear(person: Person) { 167 | return 2020 - person.age 168 | } 169 | 170 | class Greeter { 171 | greeting: string; 172 | constructor(message: string) { 173 | this.greeting = message; 174 | } 175 | greet() { 176 | return "Hello, " + this.greeting; 177 | } 178 | } 179 | 180 | let greeter = new Greeter("world"); 181 | ``` 182 |
TS infers types
183 |
184 | 185 | 186 | # Typescript 187 | 188 |
Demo
189 | 190 | # UI5 Typescript essentials (config) 191 | 192 | - UI5-Types: [https://github.com/SAP/ui5-typescript/tree/master/packages/ts-types](https://github.com/SAP/ui5-typescript/tree/master/packages/ts-types) 193 | - `package.json` 194 | - `"devDependencies": { "@openui5/ts-types": "^1.65.1",` **// install ui5 ts-types as dev dependency** 195 | - `tsconfig.json` **(in the root folder of your project)** 196 | - `"module": "none"` **// cause UI5 uses it's own module system** 197 | - `"target": "es5"` 198 | - `"sourceMap": true` **// for debugging ts files** 199 | - `"types": ["@openui5/ts-types"]` **// Typescript needs to know where the UI5 types are located** 200 | - `"include": ["./webapp/**/*.ts", "./typings/**/*"],` **// webapp and your own typings** 201 | 202 | # UI5 Typescript essentials (coding 1/2) 203 | 204 | - Reference types in Component and Controllers with typeof 205 | ```javascript 206 | sap.ui.define(['sap/ui/core/UIComponent','sap/ui/model/json/JSONModel'], 207 | function( 208 | UIComponent: typeof sap.ui.core.UIComponent, 209 | JSONModel: typeof sap.ui.model.json.JSONModel, 210 | ) { 211 | ``` 212 | - Create `Class` with `Constructor` 213 | ```javascript 214 | class Component extends UIComponent { 215 | private _logger 216 | private _myTestTSCVar 217 | private mainModel: sap.ui.model.json.JSONModel 218 | 219 | constructor(mSettings: object) { 220 | // it's important to call the extend method here. It creates metadata that is used in UI5 apps via 221 | // the method getMetadata. Hence we also assign this method to the prototype of our class. 222 | const fnClass = UIComponent.extend('de.tammenit.UHDApp.Component', { 223 | metadata: { 224 | manifest: 'json', 225 | }, 226 | }) 227 | Component.prototype.getMetadata = fnClass.prototype.getMetadata 228 | 229 | super('de.tammenit.UHDApp.Component', mSettings) 230 | } 231 | ``` 232 | 233 | 234 | # UI5 Typescript essentials (coding 2/2) 235 | 236 | - In your View Controllers 237 | ```javascript 238 | sap.ui.define( 239 | [ 240 | 'de/tammenit/UHDApp/controller/BaseController', 241 | 'sap/ui/model/json/JSONModel', 242 | 'sap/ui/core/format/DateFormat', 243 | 'sap/ui/core/Fragment', 244 | 'de/tammenit/UHDApp/formatter/formatter', 245 | ], 246 | function( 247 | BaseController: typeof de.tammenit.UHDApp.controller.BaseController, 248 | JSONModel: typeof sap.ui.model.json.JSONModel, 249 | DateFormat: typeof sap.ui.core.format.DateFormat, 250 | Fragment: typeof sap.ui.core.Fragment, 251 | Formatter: typeof de.tammenit.UHDApp.formatter.Formatter 252 | ) { 253 | 'use strict' 254 | 255 | enum LinkTargets { 256 | HOMEPAGE = 'homepage', 257 | } 258 | 259 | class Main extends BaseController { 260 | formatter: de.tammenit.UHDApp.formatter.Formatter 261 | 262 | constructor() { 263 | // it's important to call the extend method here. It creates metadata that is used in UI5 apps via 264 | // the method getMetadata. Hence we also assign this method to the prototype of our class. 265 | const fnClass = BaseController.extend('de.tammenit.UHDApp.controller.Main', {}) 266 | Main.prototype.getMetadata = fnClass.prototype.getMetadata 267 | 268 | super('de.tammenit.UHDApp.controller.Main') 269 | } 270 | 271 | public onInit(): void { 272 | ``` 273 | 274 | 275 | # UI5 Typescript essentials (type definitions) 276 | 277 | - Your type definitions: **typings/\.d.ts**, e.g. UHDApp.d.ts 278 | ```javascript 279 | declare namespace de { 280 | namespace tammenit { 281 | namespace UHDApp { 282 | export class Component extends sap.ui.core.UIComponent { 283 | /** 284 | * Reads a list of userdata from the backend. This can be displayed e.g. as value help to help the UHD employee 285 | * finding a concrete user 286 | * The input can either be a userId, a part of a userId, an email or a part of an email. The backend is supposed 287 | * to search with "contains" 288 | */ 289 | public getUserList(input: data.UserId | data.Email): Promise> 290 | /** 291 | * Reads data for a single user with all the information a UHD employee should be able to see. 292 | * This is some personal information, open application requests, assigned roles and registration/ldap information 293 | * @param userId - userid which is used on the backend to find the user 294 | * @returns a promise that resolves to a UHDUserData object 295 | */ 296 | public getUserData(userId: string): Promise 297 | ... 298 | } namespace controller { 299 | export class BaseController extends sap.ui.core.mvc.Controller { 300 | /** 301 | * gets the application main component. Technically its the return value of UIComponent.getOwnerComponent 302 | * casted to the concrete class of ...UHDApp.Component 303 | * 304 | * @returns {de.tammenit.UHDApp.Component} 305 | * @memberof BaseController 306 | */ 307 | getOwnerAppComponent(): Component 308 | ... 309 | } namespace data { 310 | /** 311 | * Personal data of a user 312 | */ 313 | export interface UserData { 314 | userId: UserId 315 | /** the title of the user */ 316 | title: string 317 | firstName: string 318 | lastName: string 319 | email: Email 320 | /** at which company / department does the user work */ 321 | company: string 322 | /** where is the user located */ 323 | location: string 324 | } 325 | }}} 326 | } 327 | ``` 328 | 329 | # UI5 330 | 331 |
Demo
332 | 333 | - Start: npm start 334 | - URL: http://localhost:8080/index.html 335 | 336 | # Resources 337 | 338 | - Typescript presentation from a [Shopify](https://www.youtube.com/watch?v=-BGxJn3c7NA) architect 339 | - Session repository: [https://github.com/htammen/ui5con_typescript/tree/master/de.tammenit.uhdapp](https://github.com/htammen/ui5con_typescript/tree/master/de.tammenit.uhdapp) 340 | 341 | 355 | 356 | -------------------------------------------------------------------------------- /presi/images/function.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/htammen/ui5con_typescript/131c746ad0b339def65b6872577fb5d7576784f9/presi/images/function.png -------------------------------------------------------------------------------- /presi/images/interface.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/htammen/ui5con_typescript/131c746ad0b339def65b6872577fb5d7576784f9/presi/images/interface.png -------------------------------------------------------------------------------- /presi/images/venn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/htammen/ui5con_typescript/131c746ad0b339def65b6872577fb5d7576784f9/presi/images/venn.png -------------------------------------------------------------------------------- /presi/runPresi.sh: -------------------------------------------------------------------------------- 1 | !/bin/bash 2 | markpress -a README.md --edit 3 | -------------------------------------------------------------------------------- /tssandbox/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // Unter https://go.microsoft.com/fwlink/?LinkId=733558 3 | // finden Sie Informationen zum Format von "tasks.json" 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "typescript", 8 | "tsconfig": "tsconfig.json", 9 | "option": "watch", 10 | "problemMatcher": [ 11 | "$tsc-watch" 12 | ], 13 | "group": { 14 | "kind": "build", 15 | "isDefault": true 16 | }, 17 | "runOptions": { 18 | "runOn": "folderOpen" 19 | } 20 | } 21 | ] 22 | } 23 | 24 | 25 | -------------------------------------------------------------------------------- /tssandbox/function.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | function reverse(word) { 4 | return word.split('').reverse().join(''); 5 | } 6 | console.log(reverse('UI5Con')); 7 | //reverse(true) 8 | -------------------------------------------------------------------------------- /tssandbox/function.ts: -------------------------------------------------------------------------------- 1 | import {greet} from './greetServer' 2 | 3 | function reverse(word: string): string { 4 | return word.split('').reverse().join('') 5 | } 6 | 7 | console.log(reverse('UI5Con')) 8 | //reverse(true) 9 | -------------------------------------------------------------------------------- /tssandbox/greetServer.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | function greet(dayTime, msg) { 4 | return `${dayTime} ${msg}`; 5 | } 6 | exports.greet = greet; 7 | -------------------------------------------------------------------------------- /tssandbox/greetServer.ts: -------------------------------------------------------------------------------- 1 | export function greet(dayTime: string, msg: string): string { 2 | return `${dayTime} ${msg}` 3 | } -------------------------------------------------------------------------------- /tssandbox/greetings.js: -------------------------------------------------------------------------------- 1 | function greet(msg) { 2 | return `Hello ${msg}`; 3 | } 4 | console.log(greet('Helmut')); 5 | greet(33); 6 | -------------------------------------------------------------------------------- /tssandbox/greetings.ts: -------------------------------------------------------------------------------- 1 | function greet(msg: string): string { 2 | return `Hello ${msg}` 3 | } 4 | 5 | console.log(greet('Helmut')) 6 | //greet(33) -------------------------------------------------------------------------------- /tssandbox/interface.js: -------------------------------------------------------------------------------- 1 | function birthYear(person) { 2 | var _a; 3 | return 2020 - (((_a = person) === null || _a === void 0 ? void 0 : _a.age) || 0); 4 | } 5 | console.log(birthYear({ name: 'Helmut', age: 55 })); 6 | -------------------------------------------------------------------------------- /tssandbox/interface.ts: -------------------------------------------------------------------------------- 1 | interface Person { 2 | name: string 3 | age?: number 4 | } 5 | 6 | function birthYear(person: Person) { 7 | return 2020 - (person?.age || 0) 8 | } 9 | 10 | console.log(birthYear({name: 'Helmut', age: 55})) 11 | -------------------------------------------------------------------------------- /tssandbox/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "CommonJS", 4 | "target": "ES2017", 5 | }, 6 | "include": [ 7 | "./*.ts" 8 | ], 9 | "exclude": ["./node_modules/**/*"] 10 | } 11 | -------------------------------------------------------------------------------- /ui5con_brussels.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "de.tammenit.uhdapp" 5 | }, 6 | { 7 | "path": "tssandbox" 8 | }, 9 | { 10 | "path": "presi" 11 | } 12 | ], 13 | "settings": {} 14 | } --------------------------------------------------------------------------------