Skip to content

Commit

Permalink
Create sample extension with Shell side navigation
Browse files Browse the repository at this point in the history
  • Loading branch information
kistll committed Apr 30, 2021
1 parent 6e43646 commit 1baf171
Show file tree
Hide file tree
Showing 6 changed files with 475 additions and 0 deletions.
35 changes: 35 additions & 0 deletions samples/with-shell-navigation/README.md
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.
68 changes: 68 additions & 0 deletions samples/with-shell-navigation/appconfig.json
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": []
}
]
}
]
}
}
181 changes: 181 additions & 0 deletions samples/with-shell-navigation/helpers.js
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;
}
Binary file added samples/with-shell-navigation/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 1baf171

Please sign in to comment.