diff --git a/samples/with-shell-navigation/README.md b/samples/with-shell-navigation/README.md new file mode 100644 index 0000000..8721196 --- /dev/null +++ b/samples/with-shell-navigation/README.md @@ -0,0 +1,35 @@ +# With Shell navigation extension sample + +This extension is a minimalist sample of a front-end extension, which runs inside Shell as a full custom app making use of the Shell side navigation. While running inside Shell, the extension hides its top bar and its side navigation meanwhile running standalone, the extension shows its top bar and its side navigation. The extension should be assigned to the Shell Home Screen and should not be assigned to an outlet. + + +## How to run + +Front-end extension only requires a static storage with a web server to be running. + +### Run locally + +You can run a local web server using the [http-server](https://www.npmjs.com/package/http-server) node package +``` +npm install --global http-server +http-server [path] [options] +``` + +An alternative solution might already be available on your machine using python3 +``` +python3 -m http.server 8080 +``` + +#### Publicly accessible + +For testing purpose, an external solution like [ngrok](https://ngrok.com/) or [localtunnel](https://github.com/localtunnel/localtunnel) can provide a publicly accessible URL that will proxy all requests to your locally running webserver. + +### GitHub Pages + +GitHub offers static hosting as part of the [GitHub pages](https://pages.github.com/) functionality. Each GitHub repository can host static files and then be used with some limits to host your front-end application. + +## How to obtain support +In case you find a bug or need support, please open an issue [here](https://github.com/SAP-samples/fsm-extension-sample/issues/new). + +## License +Copyright (c) 2021 SAP SE or an SAP affiliate company. All rights reserved. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](./LICENSES/Apache-2.0.txt) file. diff --git a/samples/with-shell-navigation/appconfig.json b/samples/with-shell-navigation/appconfig.json new file mode 100644 index 0000000..9e1958c --- /dev/null +++ b/samples/with-shell-navigation/appconfig.json @@ -0,0 +1,68 @@ +{ + "name": "Extension with Shell navigation", + "provider": "SAP FSM", + "description": "Sample extension inside Shell with Shell navigation", + "version": "1.0.0", + "icon": "https://raw.githubusercontent.com/SAP-samples/fsm-extension-sample/refactor/samples/with-shell-navigation/icon.png", + "lang": "en", + "outletPositions": [ + "SHELL_HOME_SCREEN" + ], + "shellNavigation": { + "icon": "activities", + "sideNavigation": [ + { + "pathSegment": "home", + "labelTranslations": [ + { + "lang": "en", + "value": "Home" + }, + { + "lang": "de", + "value": "Startseite" + } + ], + "viewUrl": "/", + "icon": "home", + "isDefault": true, + "children": [] + }, + { + "pathSegment": "actions", + "labelTranslations": [ + { + "lang": "en", + "value": "Actions" + }, + { + "lang": "de", + "value": "Aktionen" + } + ], + "viewUrl": null, + "icon": "activity-2", + "isDefault": false, + "children": [ + { + "pathSegment": "edit", + "labelTranslations": [ + { + "lang": "en", + "value": "Edit" + }, + { + "lang": "de", + "value": "Editieren" + } + ], + "viewUrl": "/activities/actions/edit", + "icon": null, + "isDefault": false, + "children": [] + } + ] + } + ] + } +} \ No newline at end of file diff --git a/samples/with-shell-navigation/helpers.js b/samples/with-shell-navigation/helpers.js new file mode 100644 index 0000000..4884804 --- /dev/null +++ b/samples/with-shell-navigation/helpers.js @@ -0,0 +1,181 @@ +// Available routes +const ROUTES = [ + { path: '/', id: 'section-home' }, + { path: '/actions/edit', id: 'section-actions-edit' } +]; + +const parseLocation = () => location.hash.slice(1).toLowerCase() || '/'; + +// Determine ID of the section, which should be visible +function determineSectionID(path, routes) { + const CURRENT_ROUTE = routes.find(r => r.path.match(new RegExp(`^\\${path}$`, 'gm'))) || undefined; + + if (CURRENT_ROUTE !== undefined) { + return CURRENT_ROUTE.id; + } + + // In case route is unknown, ID for the "Not Found" section is returned + return 'section-not-found'; +} + +const router = () => { + const PATH = parseLocation(); + const SECTION_ID = determineSectionID(PATH, ROUTES); // Determine ID of the section, which belongs to the new route + const MAIN_SECTION = document.getElementById('extension-main'); + const SHOWN_SECTIONS = MAIN_SECTION.getElementsByClassName('section-shown'); // Determine current (old) visible sections (0 or 1) + + // Hide current visible sections + for (i = 0; i < SHOWN_SECTIONS.length; i++) { + SHOWN_SECTIONS[i].classList.add('section-hidden'); + SHOWN_SECTIONS[i].classList.remove('section-shown'); + } + + // Show section, which belongs to the new route + document.getElementById(SECTION_ID).classList.add('section-shown'); + document.getElementById(SECTION_ID).classList.remove('section-hidden'); + + // Deselect side navigation entry, which belongs to the old visible section + deselectAllSideNavEntries(); + + // Select side navigation entry, which belongs to the new visible section + if (SECTION_ID == 'section-home') { + document.getElementById('side-home').classList.add('is-selected'); + } else if (SECTION_ID == 'section-actions-edit') { + document.getElementById('side-edit').classList.add('is-selected'); + + // In case of the second level side navigation entry 'edit', in addition open the respective sub menu + document.getElementById('SUB1').setAttribute('aria-hidden', 'false'); + const BUTTON = document.getElementById('side-actions-button'); + BUTTON.setAttribute('aria-expanded', 'true'); + BUTTON.querySelector('i[role]').setAttribute('class', 'sap-icon--navigation-down-arrow'); + } +}; + +// Register router function for route change events +window.addEventListener('hashchange', router); +window.addEventListener('load', router); + +// Expande/Collapse sub menu of structural navigation entries like 'Actions' +function toggleNestedListSubmenu(event){ + const BUTTON = event.currentTarget; + const SUBLIST_ID = BUTTON.getAttribute('aria-controls'); + const IS_EXPANDED = BUTTON.getAttribute('aria-expanded'); + + if (IS_EXPANDED === 'true') { + document.getElementById(SUBLIST_ID).setAttribute('aria-hidden', 'true'); + BUTTON.setAttribute('aria-expanded', 'false'); + BUTTON.querySelector('i[role]').setAttribute('class', 'sap-icon--navigation-right-arrow'); + } else { + document.getElementById(SUBLIST_ID).setAttribute('aria-hidden', 'false'); + BUTTON.setAttribute('aria-expanded', 'true'); + BUTTON.querySelector('i[role]').setAttribute('class', 'sap-icon--navigation-down-arrow'); + } +} + +// Deselect current selected side navigation entry (0 or 1) +function deselectAllSideNavEntries() { + const SIDE_NAV = document.getElementById('side-nav'); + const SELECTED_ENTRIES = SIDE_NAV.getElementsByClassName('is-selected'); + + for (i = 0; i < SELECTED_ENTRIES.length; i++) { + SELECTED_ENTRIES[i].classList.remove('is-selected'); + } +} + +// Return translations based on the provided language key +function getTranslation(lang) { + const TRANSLATIONS = { + en: { + 'EDIT_TITLE': 'Actions - Edit', + 'EDIT_TEXT': 'This is the page for editing actions.', + 'EXTENSION_TITLE': 'Extension with Shell navigation', + 'HOME_TITLE': 'Home', + 'HOME_TEXT': 'This is the starting page.', + 'NOT_FOUND_TITLE': 'Not Found', + 'NOT_FOUND_TEXT': 'Page has not been found.', + 'SIDE_HOME_TITLE': 'Home', + 'SIDE_ACTIONS_TITLE': 'Actions', + 'SIDE_EDIT_TITLE': 'Edit' + }, + de: { + 'EDIT_TITLE': 'Aktionen - Editieren', + 'EDIT_TEXT': 'Das ist die Seite zum Editieren von Aktionen.', + 'EXTENSION_TITLE': 'Erweiterung mit Shell Navigation', + 'HOME_TITLE': 'Startseite', + 'HOME_TEXT': 'Das ist die Startseite.', + 'NOT_FOUND_TITLE': 'Nicht gefunden', + 'NOT_FOUND_TEXT': 'Die Seite konnte nicht gefunden werden.', + 'SIDE_HOME_TITLE': 'Startseite', + 'SIDE_ACTIONS_TITLE': 'Aktionen', + 'SIDE_EDIT_TITLE': 'Editieren' + } + } + if (Object.keys(TRANSLATIONS).indexOf(lang) !== -1) { + return TRANSLATIONS[lang]; + } else { + return TRANSLATIONS[en]; + } +} + +// Translate based on translations for the provided language key +function translate(lang) { + const I18N = getTranslation(lang); + + document.getElementById('actions-edit-title').innerHTML = I18N.EDIT_TITLE; + document.getElementById('actions-edit-text').innerHTML = I18N.EDIT_TEXT; + document.getElementById('extension-title').innerHTML = I18N.EXTENSION_TITLE; + document.getElementById('home-title').innerHTML = I18N.HOME_TITLE; + document.getElementById('home-text').innerHTML = I18N.HOME_TEXT; + document.getElementById('not-found-title').innerHTML = I18N.NOT_FOUND_TITLE; + document.getElementById('not-found-text').innerHTML = I18N.NOT_FOUND_TEXT; + document.getElementById('side-home-title').innerHTML = I18N.SIDE_HOME_TITLE; + document.getElementById('side-actions-title').innerHTML = I18N.SIDE_ACTIONS_TITLE; + document.getElementById('side-edit-title').innerHTML = I18N.SIDE_EDIT_TITLE; +} + + +function newLanguageSelected() { + const NEW_SELECTED_LANG = document.getElementById('language-select').value; + translate(NEW_SELECTED_LANG); +} + +// Hide side navigation and top bar from the extension (required in case extension runs inside the Shell) +function hideSideNavAndTopBar() { + document.getElementById('top-bar').style.display = 'none'; + document.getElementById('side-nav').style.display = 'none'; +} + + +// Supported languages besides English +const SUPPORTED_LOCALES = [ + { + code: 'de' + }, + { + code: 'de-ch', + language: 'de' + } +]; + +// Default language +const DEFAULT_LOCALE = { + code: 'en' +}; + +// Determine language based on Shell locale +function getLanguageByLocale(currentLocale) { + const LANGUAGE = SUPPORTED_LOCALES.find(supportedLocale => { + try { + return supportedLocale.code === currentLocale.toLowerCase(); + } catch { + return false; + } + }); + + if (LANGUAGE !== undefined) { + return LANGUAGE; + } + + // In case the current Shell language is not supported by the extension, return default language. + return DEFAULT_LOCALE; +} \ No newline at end of file diff --git a/samples/with-shell-navigation/icon.png b/samples/with-shell-navigation/icon.png new file mode 100644 index 0000000..1dfed6f Binary files /dev/null and b/samples/with-shell-navigation/icon.png differ diff --git a/samples/with-shell-navigation/index.html b/samples/with-shell-navigation/index.html new file mode 100644 index 0000000..8c1d38d --- /dev/null +++ b/samples/with-shell-navigation/index.html @@ -0,0 +1,110 @@ + + +
+ +