Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New: Added Navigation Button API #339

Merged
merged 29 commits into from
May 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
d9dfbab
New: Added Navigation Button API
oliverfoster Mar 30, 2023
1d1866b
Fixed new button creation
oliverfoster Mar 30, 2023
9775269
Added missing filters to classes
oliverfoster Mar 30, 2023
c2d187f
Icon and label alignment
swashbuck Apr 4, 2023
2b89e20
Additional positioning options, changed label class
swashbuck Apr 5, 2023
c9d2fc5
Only show labels for medium and above
swashbuck Apr 11, 2023
33c8d2b
Merge remote-tracking branch 'origin/master' into issue/338
swashbuck Apr 14, 2023
16e4e93
Add positioning and breakpoint properties / classes
swashbuck Apr 14, 2023
410d063
Remove label font size - fixed by Vanilla issue #414
swashbuck Apr 14, 2023
8baa0c9
Merge remote-tracking branch 'origin/master' into issue/338
swashbuck Apr 17, 2023
1cc4aa6
Schema updates for the new nav label options
swashbuck Apr 17, 2023
785fb14
Fix _showLabelAtWidth: large
swashbuck Apr 18, 2023
8582a47
Less optimizations
swashbuck Apr 19, 2023
2cddf38
Less minor indentation fix
swashbuck Apr 19, 2023
4e88d48
Use @icon-padding/2 for label margins, rewrite margins for readability
swashbuck Apr 21, 2023
c3f872c
Add auto option and make it default, add styles for RTL (auto) and LT…
swashbuck Apr 21, 2023
e84e277
Switch to --adapt-navigation-height CSS variable for padding, remove …
swashbuck Apr 21, 2023
8e488aa
Set --adapt-navigation-height with JS, adjust Drawer toolbar styles
swashbuck Apr 21, 2023
7215859
Merge remote-tracking branch 'origin/master' into issue/338
swashbuck Apr 21, 2023
199e022
Merge remote-tracking branch 'origin/master' into issue/338
swashbuck Apr 24, 2023
6439bdd
Restore @navigation-height for backwards compatibility
swashbuck Apr 24, 2023
d5cb6c1
Move initial setNavigationHeight() call to navigationView:postRender …
swashbuck Apr 24, 2023
d6ba8cc
Fix: Sort api added buttons
oliverfoster Apr 25, 2023
5a90c7d
Add option to show label at any size
swashbuck Apr 25, 2023
833d975
Fix for hiding labels globally
swashbuck Apr 25, 2023
c43a400
Update _labelPosition default to 'auto'
swashbuck Apr 26, 2023
635c8c7
Nest rtl/ltr styles under has-label-auto
swashbuck Apr 27, 2023
596291f
Merge branch 'master' into issue/338
oliverfoster Apr 28, 2023
c2475b5
Suggestions and loading fix
oliverfoster May 2, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion js/device.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ class Device extends Backbone.Controller {
this.osVersion = this.bowser.os.version || '';
this.renderingEngine = this.getRenderingEngine();
this.listenTo(Adapt, {
'configModel:dataLoaded': this.onConfigDataLoaded
'configModel:dataLoaded': this.onConfigDataLoaded,
'navigationView:postRender': this.setNavigationHeight
});
const browser = this.browser.toLowerCase();
// Convert 'msie' and 'internet explorer' to 'ie'.
Expand Down Expand Up @@ -105,6 +106,10 @@ class Device extends Backbone.Controller {
document.documentElement.style.setProperty('--adapt-viewport-height', `${window.innerHeight}px`);
}

setNavigationHeight() {
document.documentElement.style.setProperty('--adapt-navigation-height', `${$('.nav').height()}px`);
}

getOperatingSystem() {
let os = this.bowser.os.name.toLowerCase() || '';

Expand Down Expand Up @@ -141,6 +146,7 @@ class Device extends Backbone.Controller {
this.screenWidth = this.getScreenWidth();
this.screenHeight = this.getScreenHeight();
this.setViewportHeight();
this.setNavigationHeight();

if (previousWidth === this.screenWidth && previousHeight === this.screenHeight) {
// Do not trigger a change if the viewport hasn't actually changed. Scrolling on iOS will trigger a resize.
Expand Down
19 changes: 19 additions & 0 deletions js/models/NavigationButtonModel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import LockingModel from 'core/js/models/lockingModel';

export default class NavigationButtonModel extends LockingModel {

defaults() {
return {
_id: '',
_classes: '',
_iconClasses: '',
_order: 0,
_event: '',
_showLabel: null,
_role: 'button',
ariaLabel: '',
text: '{{ariaLabel}}'
};
}

}
15 changes: 15 additions & 0 deletions js/models/NavigationModel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import LockingModel from 'core/js/models/lockingModel';

export default class NavigationModel extends LockingModel {

defaults() {
return {
_navigationAlignment: 'top',
_isBottomOnTouchDevices: false,
_showLabel: false,
_showLabelAtWidth: 'medium',
_labelPosition: 'auto'
};
}

}
24 changes: 5 additions & 19 deletions js/navigation.js
Original file line number Diff line number Diff line change
@@ -1,37 +1,23 @@
import Adapt from 'core/js/adapt';
import NavigationView from 'core/js/views/navigationView';
import device from './device';
import NavigationModel from './models/NavigationModel';

class NavigationController extends Backbone.Controller {

initialize() {
this.listenTo(Adapt, {
'adapt:preInitialize': this.addNavigationBar,
'adapt:preInitialize device:resize': this.onDeviceResize
});
this.navigation = new NavigationView();
this.listenTo(Adapt, 'adapt:preInitialize', this.addNavigationBar);
}

addNavigationBar() {
const adaptConfig = Adapt.course.get('_navigation');

if (adaptConfig?._isDefaultNavigationDisabled) {
Adapt.trigger('navigation:initialize');
return;
}

Adapt.navigation = new NavigationView();// This should be triggered after 'app:dataReady' as plugins might want to manipulate the navigation
}

onDeviceResize() {
const adaptConfig = Adapt.course.get('_navigation');
const $html = $('html');
$html.addClass('is-nav-top');
let navigationAlignment = adaptConfig?._navigationAlignment ?? 'top';
const isBottomOnTouchDevices = (device.touch && adaptConfig?._isBottomOnTouchDevices);
if (isBottomOnTouchDevices) navigationAlignment = 'bottom';
$html.removeClass('is-nav-top').addClass('is-nav-' + navigationAlignment);
this.navigation.start(new NavigationModel(adaptConfig));
}

}

export default new NavigationController();
export default (Adapt.navigation = (new NavigationController()).navigation);
174 changes: 174 additions & 0 deletions js/views/NavigationButtonView.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import Adapt from 'core/js/adapt';
import wait from 'core/js/wait';
import { compile, templates } from 'core/js/reactHelpers';
import React from 'react';
import ReactDOM from 'react-dom';
import router from 'core/js/router';
import startController from 'core/js/startController';
import a11y from 'core/js/a11y';
import location from 'core/js/location';

export default class NavigationButtonView extends Backbone.View {

tagName() {
return 'button';
}

events() {
return {
click: 'triggerEvent'
};
}

className() {
if (this.isInjectedButton) {
return [
this.model.get('_showLabel') === false && 'hide-label'
].filter(Boolean).join(' ');
}
return [
'btn-icon nav__btn',
this.model.get('_classes'),
this.model.get('_showLabel') === false && 'hide-label'
].filter(Boolean).join(' ');
}

attributes() {
const attributes = this.model.toJSON();
if (this.isInjectedButton) {
return {
name: attributes._id,
'data-order': attributes._order,
'data-event': attributes._event
};
}
return {
name: attributes._id,
role: attributes._role === 'button' ? undefined : attributes._role,
'aria-label': attributes.ariaLabel,
'data-order': attributes._order,
'data-event': attributes._event
};
}

initialize({ el }) {
if (el) {
this.isInjectedButton = true;
} else {
this.isJSX = (this.constructor.template || '').includes('.jsx');
}
this._classSet = new Set(_.result(this, 'className').trim().split(/\s+/));
this._attributes = _.result(this, 'attributes');
this.listenTo(this.model, 'change', this.changed);
this.render();
}

static get template() {
return 'navButton.jsx';
}

render() {
if (this.isInjectedButton) {
this.changed();
} else if (this.isJSX) {
this.changed();
} else {
const data = this.model.toJSON();
data.view = this;
const template = Handlebars.templates[this.constructor.template];
this.$el.html(template(data));
}
return this;
}

updateViewProperties() {
const classesToAdd = _.result(this, 'className').trim().split(/\s+/);
classesToAdd.forEach(i => this._classSet.add(i));
const classesToRemove = [ ...this._classSet ].filter(i => !classesToAdd.includes(i));
classesToRemove.forEach(i => this._classSet.delete(i));
Object.keys(this._attributes).forEach(name => this.$el.removeAttr(name));
Object.entries(_.result(this, 'attributes')).forEach(([name, value]) => this.$el.attr(name, value));
this.$el.removeClass(classesToRemove).addClass(classesToAdd);
}

injectLabel() {
const textLabel = this.$el.find('> .nav__btn-label');
const ariaLabel = this.$el.attr('aria-label') ?? this.$el.find('.aria-label').text();
const text = this.model.get('text');
const output = compile(text ?? '', { ariaLabel });
if (!textLabel.length) {
this.$el.append(`<span class="nav__btn-label" aria-hidden="true">${output}</span>`);
return;
}
textLabel.html(output);
}

/**
* Re-render
* @param {string} eventName=null Backbone change event name
*/
changed(eventName = null) {
if (typeof eventName === 'string' && eventName.startsWith('bubble')) {
// Ignore bubbling events as they are outside of this view's scope
return;
}
if (this.isInjectedButton) {
this.updateViewProperties();
this.injectLabel();
return;
}
if (!this.isJSX) {
this.updateViewProperties();
return;
}
const props = {
// Add view own properties, bound functions etc
...this,
// Add model json data
...this.model.toJSON(),
// Add globals
_globals: Adapt.course?.get('_globals')
};
const Template = templates[this.constructor.template.replace('.jsx', '')];
this.updateViewProperties();
ReactDOM.render(<Template {...props} />, this.el);
}

triggerEvent(event) {
event.preventDefault();
const currentEvent = $(event.currentTarget).attr('data-event');
if (!currentEvent) return;
Adapt.trigger('navigation:' + currentEvent);
switch (currentEvent) {
case 'backButton':
router.navigateToPreviousRoute();
break;
case 'homeButton':
router.navigateToHomeRoute();
break;
case 'parentButton':
router.navigateToParent();
break;
case 'skipNavigation':
a11y.focusFirst('.' + location._contentType);
break;
case 'returnToStart':
startController.returnToStartLocation();
break;
}
}

remove() {
this._isRemoved = true;
this.stopListening();
wait.for(end => {
if (this.isJSX) {
ReactDOM.unmountComponentAtNode(this.el);
}
super.remove();
end();
});
return this;
}

}
4 changes: 2 additions & 2 deletions js/views/adaptView.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ class AdaptView extends Backbone.View {
this.isJSX = (this.constructor.template || '').includes('.jsx');
if (this.isJSX) {
this._classSet = new Set(_.result(this, 'className').trim().split(/\s+/));
this.listenTo(this.model, 'all', this.changed);
this.listenTo(this.model, 'change', this.changed);
const children = this.model?.getChildren?.();
children && this.listenTo(children, 'all', this.changed);
children && this.listenTo(children, 'change', this.changed);
// Facilitate adaptive react views
this.listenTo(Adapt, 'device:changed', this.changed);
}
Expand Down
Loading