From 05583dfa447db721902c725808ae0457fc047f7f Mon Sep 17 00:00:00 2001 From: Ivo Sonderegger Date: Mon, 21 Oct 2019 16:34:22 +0200 Subject: [PATCH] NavigationButtons added --- src/meteoJS/timeline/NavigationButtons.js | 146 ++++++++++++++++++ .../timeline/navigationButtons.test.js | 96 ++++++++++++ 2 files changed, 242 insertions(+) create mode 100644 src/meteoJS/timeline/NavigationButtons.js create mode 100644 test/meteoJS/timeline/navigationButtons.test.js diff --git a/src/meteoJS/timeline/NavigationButtons.js b/src/meteoJS/timeline/NavigationButtons.js new file mode 100644 index 00000000..8fa6fba2 --- /dev/null +++ b/src/meteoJS/timeline/NavigationButtons.js @@ -0,0 +1,146 @@ +/** + * @module meteoJS/timeline/navigationButtons + */ +import addEventFunctions from '../Events.js'; +import Timeline from '../Timeline.js'; + +/** + * Determines how the time is chosen, when a button for time navigation is + * clicked. On "exact" the time in the timeline is only changed if the time + * exists. In all other cases the time will be changed and a suitable timestamp + * is chosen. + * + * @typedef {string="exact","nearest","before","later"} + * module:meteoJS/timeline/navigationButtons~findTimeBy + */ + +/** + * Options for NavigationButtons. + * + * @typedef {Object} module:meteoJS/timeline/navigationButtons~options + * @param {module:meteoJS/timeline~Timeline} timeline - Timeline object. + * @param {module:meteoJS/timeline/navigationButtons~findTimeBy} findTimeBy + * Determines how the time is chosen, when a button is clicked. + * @param {string|undefined} buttonClass - Default button class. + */ + +/** + * @classdesc Class to create buttons and insert them into the DOM to navigate + * through the times of the passed timeline. + + * Events, wenn gedrückt (und vorallem wenn Time nicht vorhanden) + * Wenn Zeit nicht vorhanden, wähle nächster Zeitpunkt/früherer/späterere/kein Zeitpunkt + * In Verbindung mit keyboardNavigation -> Tooltip dazu + * + * @fires module:meteoJS/timeline/navigationButtons#click:button + */ +export class NavigationButtons { + + /** + * @param {module:meteoJS/timeline/navigationButtons~options} [options] + * Options. + */ + constructor({ timeline, + findTimeBy = "exact", + buttonClass, + } = {}) { + /** + * @type module:meteoJS/timeline~Timeline + */ + this.timeline = timeline; + + /** + * @type module:meteoJS/timeline/navigationButtons~findTimeBy + */ + this.findTimeBy = findTimeBy; + + /** + * @type string|undefined + */ + this.buttonClass = buttonClass; + } + + /** + * @typedef {Object} module:meteoJS/timeline/navigationButtons~buttonDefinition + * @param {string|undefined} [buttonClass} - Class. + * @param {string="first","last","prev","next","nextAllEnabled","prevAllEnabled","add","sub"} + * methodName - Method to execute on timeline, when button is clicked. + * @param {integer} [timeAmount] - Required when methodName is "add" or "sub." + * @param {string} [timeKey] - Required when methodName is "add" or "sub." + * @param {string} [text] - Text for button. + * @param {string} [title] - Title for button. + */ + + /** + * Creates button HTMLElements and append them to the passed node. + * + * @param {HTMLElement} node - Node to insert the buttons into it. + * @param {...module:meteoJS/timeline/navigationButtons~buttonDefinition} + * buttons - Button defintions to insert. + */ + insertButtonInto(node, ...buttons) { + buttons.forEach(({ buttonClass, + methodName, + timeAmount, + timeKey, + text, + title } = {}) => { + if (!/^(first|last|prev|next|nextAllEnabled|prevAllEnabled|add|sub)$/ + .test(methodName)) + return; + if (text === undefined) + switch (methodName) { + case 'first': + text = '|«'; + break; + case 'last': + text = '»|'; + break; + case 'prev': + text = '«'; + break; + case 'next': + text = '»'; + break; + case 'nextAllEnabled': + text = '»'; + break; + case 'prevAllEnabled': + text = '«'; + break; + case 'add': + text = `+${timeAmount}${timeKey}`; + break; + case 'sub': + text = `-${timeAmount}${timeKey}`; + break; + } + let button = document.createElement('button'); + button.appendChild(document.createTextNode(text)); + button.setAttribute('type', 'button'); + if (typeof buttonClass == 'string') + button.classList.add(buttonClass.split(' ')); + else if (typeof this.buttonClass == 'string') + button.classList.add(this.buttonClass.split(' ')); + if (title !== undefined) + button.setAttribute('title', title); + button.addEventListener('onclick', () => { + switch (methodName) { + case 'add': + this.timeline.add(timeAmount, timeKey); + break; + case 'sub': + this.timeline.sub(timeAmount, timeKey); + break; + default: + this.timeline[methodName](); + } + this.trigger('click:button'); + }); + node.appendChild(button); + }); + } + +} +addEventFunctions(NavigationButtons.prototype); +export default NavigationButtons; \ No newline at end of file diff --git a/test/meteoJS/timeline/navigationButtons.test.js b/test/meteoJS/timeline/navigationButtons.test.js new file mode 100644 index 00000000..5882e2b7 --- /dev/null +++ b/test/meteoJS/timeline/navigationButtons.test.js @@ -0,0 +1,96 @@ +import assert from 'assert'; +import 'jsdom-global/register'; +import Timeline from '../../../src/meteoJS/Timeline.js'; +import NavigationButtons + from '../../../src/meteoJS/timeline/NavigationButtons.js'; +import { NavigationButtons as NavigationButtonsClass } + from '../../../src/meteoJS/timeline/NavigationButtons.js'; + +describe('NavigationButtons', () => { + let timeline = new Timeline(); + timeline.setTimesBySetID('', + [...Array(17).keys()] + .map(i => i*3) + .map(i => new Date(Date.UTC(2019, 10, 21 + Math.trunc(i/24), i % 24)))); + it('Defaults', () => { + let clickCounter = 0; + let nB = new NavigationButtons({ timeline }); + nB.on('click:button', () => clickCounter++); + let node = document.createElement('div'); + nB.insertButtonInto(node, + { methodName: 'first' }, + { methodName: 'prevAllEnabled' }, + { methodName: 'prev', + title: 'Previous' }, + { methodName: 'sub', + timeAmount: 12, + timeKey: 'h' }, + { methodName: 'sub', + timeAmount: 6, + timeKey: 'h', + text: 'ABC' }, + { methodName: 'sub', + timeAmount: 3, + timeKey: 'h' }, + { methodName: 'add', + timeAmount: 3, + timeKey: 'h' }, + { methodName: 'add', + timeAmount: 12, + timeKey: 'h' }, + { methodName: 'next', + buttonClass: 'btn' }, + { methodName: 'nextAllEnabled' }, + { methodName: 'last' }); + assert.equal(node.childElementCount, 11, 'children count'); + [['', undefined, '|«'], + ['', undefined, '«'], + ['', 'Previous', '«'], + ['', undefined, '-12h'], + ['', undefined, 'ABC'], + ['', undefined, '-3h'], + ['', undefined, '+3h'], + ['', undefined, '+12h'], + ['btn', undefined, '»'], + ['', undefined, '»'], + ['', undefined, '»|']].forEach((result, i) => { + assert.equal(node.children[i].className, result[0], `${i}: class`); + assert.equal(node.children[i].getAttribute('title'), result[1], `${i}: title`); + assert.equal(node.children[i].textContent, result[2], `${i}: textContent`); + }); + node.children[10].dispatchEvent(new CustomEvent('onclick')); // last + assert.equal(timeline.getSelectedTime(), timeline.getTimes()[16], 'selected time 16'); + node.children[6].dispatchEvent(new CustomEvent('onclick')); // +3h + assert.equal(timeline.getSelectedTime(), timeline.getTimes()[16], 'selected time 16'); + node.children[2].dispatchEvent(new CustomEvent('onclick')); // Prev + assert.equal(timeline.getSelectedTime(), timeline.getTimes()[15], 'selected time 15'); + node.children[3].dispatchEvent(new CustomEvent('onclick')); // -12h + assert.equal(timeline.getSelectedTime().valueOf(), timeline.getTimes()[11].valueOf(), 'selected time 11'); + assert.equal(clickCounter, 4, 'click counter'); + }); + it('Constructor options: buttonClass', () => { + let nB = new NavigationButtons({ + timeline, + buttonClass: 'btn btn-secondary' + }); + let node = document.createElement('div'); + nB.insertButtonInto(node, + { methodName: 'first' }, + { methodName: 'last' }); + assert.equal(node.childElementCount, 2, 'children count'); + assert.equal(node.children[0].className, 'btn,btn-secondary', '0: class'); + assert.equal(node.children[0].getAttribute('title'), undefined, '0: title'); + assert.equal(node.children[0].textContent, '|«', '0: textContent'); + assert.equal(node.children[1].className, 'btn,btn-secondary', '0: class'); + assert.equal(node.children[1].getAttribute('title'), undefined, '0: title'); + assert.equal(node.children[1].textContent, '»|', '0: textContent'); + }); + it('named import', () => { + let nB = new NavigationButtons({ + timeline + }); + let node = document.createElement('div'); + nB.insertButtonInto(node, { methodName: 'first' }); + assert.equal(node.childElementCount, 1, 'children count'); + }); +}); \ No newline at end of file