-
Notifications
You must be signed in to change notification settings - Fork 35
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Create sample extension with Shell side navigation
- Loading branch information
Showing
6 changed files
with
475 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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": [] | ||
} | ||
] | ||
} | ||
] | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.