diff --git a/HISTORY.md b/HISTORY.md index c8fcf0af..8181c40b 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -10,6 +10,7 @@ http://visjs.org - Fixed canceling moving an item to another group did not move the item back to the original group. - Added localization support. +- Implemented option `activatable`. ### Network @@ -23,6 +24,7 @@ http://visjs.org - Added two examples showing the two additions above. - Added 'customRange' for the Y axis and an example showing how it works. - Added localization support. +- Implemented option `activatable`. ## 2014-08-14, version 3.2.0 diff --git a/docs/timeline.html b/docs/timeline.html index d08677c8..27c6ca5a 100644 --- a/docs/timeline.html +++ b/docs/timeline.html @@ -356,6 +356,14 @@ var options = { Description + + activatable + boolean + false + When a Timeline is configured to be activatable, it will react to mouse and touch events only when active. + When active, a blue shadow border is displayed around the Timeline. The Timeline is set active by clicking on it, and is changed to inactive again by clicking outside the Timeline or by pressing the ESC key. + + align String diff --git a/examples/timeline/19_localization.html b/examples/timeline/19_localization.html index 3c2c18bd..6e11a309 100644 --- a/examples/timeline/19_localization.html +++ b/examples/timeline/19_localization.html @@ -10,7 +10,7 @@ } - + diff --git a/examples/timeline/20_activatable.html b/examples/timeline/20_activatable.html new file mode 100644 index 00000000..8e4926c1 --- /dev/null +++ b/examples/timeline/20_activatable.html @@ -0,0 +1,72 @@ + + + + Timeline | Activatable + + + + + + + +
+

Activatable timeline

+

+ The following timelines are activatable: before you can scroll and drag in the timeline, you first have to click in the timeline to activate. +

+
+ + + + \ No newline at end of file diff --git a/examples/timeline/index.html b/examples/timeline/index.html index 87d052f4..9571e79b 100644 --- a/examples/timeline/index.html +++ b/examples/timeline/index.html @@ -31,6 +31,7 @@

17_data_serialization.html

18_range_overflow.html

19_localization.html

+

20_activatable.html

requirejs_example.html

diff --git a/gulpfile.js b/gulpfile.js index f89d167a..64729d68 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -80,6 +80,7 @@ gulp.task('bundle-js', ['clean'], function (cb) { // bundle and minify css gulp.task('bundle-css', ['clean'], function () { var files = [ + './lib/shared/activator.css', './lib/timeline/component/css/timeline.css', './lib/timeline/component/css/panel.css', './lib/timeline/component/css/labelset.css', diff --git a/lib/shared/Activator.js b/lib/shared/Activator.js new file mode 100644 index 00000000..21432ffa --- /dev/null +++ b/lib/shared/Activator.js @@ -0,0 +1,89 @@ +var mousetrap = require('mousetrap'); +var Hammer = require('../module/hammer'); +var util = require('../util'); + +/** + * Turn an element into an activatable element. + * When not active, the element has a transparent overlay. When the overlay is + * clicked, the mode is changed to active. + * When active, the element is displayed with a blue border around it, and + * the interactive contents of the element can be used. When clicked outside + * the element, the elements mode is changed to inactive. + * @param {Element} container + * @constructor + */ +function Activator(container) { + this.active = false; + + this.dom = { + container: container + }; + + this.dom.overlay = document.createElement('div'); + this.dom.overlay.className = 'overlay'; + + this.dom.container.appendChild(this.dom.overlay); + + this.hammer = Hammer(this.dom.overlay, {prevent_default: false}); + this.hammer.on('tap', this._onTapOverlay.bind(this)); + + // attach a tap event to the window, in order to deactivate when clicking outside the timeline + this.windowHammer = Hammer(window, {prevent_default: false}); + this.windowHammer.on('tap', this.deactivate.bind(this)); + + // mousetrap listener only bounded when active) + this.escListener = this.deactivate.bind(this); +} + +// The currently active activator +Activator.current = null; + +/** + * Destroy the activator. Cleans up all created DOM and event listeners + */ +Activator.prototype.destroy = function () { + this.deactivate(); + + // remove dom + this.dom.overlay.parentNode.removeChild(this.dom.overlay); + + // cleanup hammer instances + this.hammer = null; + this.windowHammer = null; +}; + +/** + * Activate the element + * Overlay is hidden, element is decorated with a blue shadow border + */ +Activator.prototype.activate = function () { + // we allow only one active activator at a time + if (Activator.current) { + Activator.current.deactivate(); + } + Activator.current = this; + + this.active = true; + this.dom.overlay.style.display = 'none'; + util.addClassName(this.dom.container, 'vis-active'); + mousetrap.bind('esc', this.escListener); +}; + +/** + * Deactivate the element + * Overlay is displayed on top of the element + */ +Activator.prototype.deactivate = function () { + this.active = false; + this.dom.overlay.style.display = ''; + util.removeClassName(this.dom.container, 'vis-active'); + mousetrap.unbind('esc', this.escListener); +}; + +Activator.prototype._onTapOverlay = function (event) { + // activate the container + this.activate(); + event.stopPropagation(); +}; + +module.exports = Activator; diff --git a/lib/shared/activator.css b/lib/shared/activator.css new file mode 100644 index 00000000..b23acb43 --- /dev/null +++ b/lib/shared/activator.css @@ -0,0 +1,14 @@ +.vis .overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + + /* Must be displayed above for example selected Timeline items */ + z-index: 10; +} + +.vis-active { + box-shadow: 0 0 10px #86d5f8; +} diff --git a/lib/timeline/Core.js b/lib/timeline/Core.js index ed0897a5..4d589cba 100644 --- a/lib/timeline/Core.js +++ b/lib/timeline/Core.js @@ -8,6 +8,7 @@ var TimeAxis = require('./component/TimeAxis'); var CurrentTime = require('./component/CurrentTime'); var CustomTime = require('./component/CustomTime'); var ItemSet = require('./component/ItemSet'); +var Activator = require('../shared/Activator'); /** * Create a timeline visualization @@ -50,6 +51,7 @@ Core.prototype._create = function (container) { this.dom.shadowTopRight = document.createElement('div'); this.dom.shadowBottomRight = document.createElement('div'); + this.dom.root.className = 'vis timeline root'; this.dom.background.className = 'vispanel background'; this.dom.backgroundVertical.className = 'vispanel background vertical'; this.dom.backgroundHorizontal.className = 'vispanel background horizontal'; @@ -112,7 +114,9 @@ Core.prototype._create = function (container) { events.forEach(function (event) { var listener = function () { var args = [event].concat(Array.prototype.slice.call(arguments, 0)); - me.emit.apply(me, args); + if (!me.activator || me.activator.active) { + me.emit.apply(me, args); + } }; me.hammer.on(event, listener); me.listeners[event] = listener; @@ -141,6 +145,67 @@ Core.prototype._create = function (container) { container.appendChild(this.dom.root); }; +/** + * Set options. Options will be passed to all components loaded in the Timeline. + * @param {Object} [options] + * {String} orientation + * Vertical orientation for the Timeline, + * can be 'bottom' (default) or 'top'. + * {String | Number} width + * Width for the timeline, a number in pixels or + * a css string like '1000px' or '75%'. '100%' by default. + * {String | Number} height + * Fixed height for the Timeline, a number in pixels or + * a css string like '400px' or '75%'. If undefined, + * The Timeline will automatically size such that + * its contents fit. + * {String | Number} minHeight + * Minimum height for the Timeline, a number in pixels or + * a css string like '400px' or '75%'. + * {String | Number} maxHeight + * Maximum height for the Timeline, a number in pixels or + * a css string like '400px' or '75%'. + * {Number | Date | String} start + * Start date for the visible window + * {Number | Date | String} end + * End date for the visible window + */ +Core.prototype.setOptions = function (options) { + if (options) { + // copy the known options + var fields = ['width', 'height', 'minHeight', 'maxHeight', 'autoResize', 'start', 'end', 'orientation', 'activatable']; + util.selectiveExtend(fields, this.options, options); + + if ('activatable' in options) { + if (options.activatable) { + this.activator = new Activator(this.dom.root); + } + else { + if (this.activator) { + this.activator.destroy(); + delete this.activator; + } + } + } + + // enable/disable autoResize + this._initAutoResize(); + } + + // propagate options to all components + this.components.forEach(function (component) { + component.setOptions(options); + }); + + // TODO: remove deprecation error one day (deprecated since version 0.8.0) + if (options && options.order) { + throw new Error('Option order is deprecated. There is no replacement for this feature.'); + } + + // redraw everything + this.redraw(); +}; + /** * Destroy the Core, clean up all DOM elements and event listeners. */ @@ -160,6 +225,12 @@ Core.prototype.destroy = function () { } this.dom = null; + // remove Activator + if (this.activator) { + this.activator.destroy(); + delete this.activator; + } + // cleanup hammer touch events for (var event in this.listeners) { if (this.listeners.hasOwnProperty(event)) { @@ -321,7 +392,14 @@ Core.prototype.redraw = function() { if (!dom) return; // when destroyed // update class names - dom.root.className = 'vis timeline root ' + options.orientation; + if (options.orientation == 'top') { + util.addClassName(dom.root, 'top'); + util.removeClassName(dom.root, 'bottom'); + } + else { + util.removeClassName(dom.root, 'top'); + util.addClassName(dom.root, 'bottom'); + } // update root width and height options dom.root.style.maxHeight = util.option.asSize(options.maxHeight, ''); diff --git a/lib/timeline/Graph2d.js b/lib/timeline/Graph2d.js index 66288f93..61ab239d 100644 --- a/lib/timeline/Graph2d.js +++ b/lib/timeline/Graph2d.js @@ -105,56 +105,6 @@ function Graph2d (container, items, options, groups) { // Extend the functionality from Core Graph2d.prototype = new Core(); -/** - * Set options. Options will be passed to all components loaded in the Graph2d. - * @param {Object} [options] - * {String} orientation - * Vertical orientation for the Graph2d, - * can be 'bottom' (default) or 'top'. - * {String | Number} width - * Width for the timeline, a number in pixels or - * a css string like '1000px' or '75%'. '100%' by default. - * {String | Number} height - * Fixed height for the Graph2d, a number in pixels or - * a css string like '400px' or '75%'. If undefined, - * The Graph2d will automatically size such that - * its contents fit. - * {String | Number} minHeight - * Minimum height for the Graph2d, a number in pixels or - * a css string like '400px' or '75%'. - * {String | Number} maxHeight - * Maximum height for the Graph2d, a number in pixels or - * a css string like '400px' or '75%'. - * {Number | Date | String} start - * Start date for the visible window - * {Number | Date | String} end - * End date for the visible window - */ -Graph2d.prototype.setOptions = function (options) { - if (options) { - // copy the known options - var fields = ['width', 'height', 'minHeight', 'maxHeight', 'autoResize', 'start', 'end', 'orientation']; - util.selectiveExtend(fields, this.options, options); - - // enable/disable autoResize - this._initAutoResize(); - } - - // propagate options to all components - this.components.forEach(function (component) { - component.setOptions(options); - }); - - // TODO: remove deprecation error one day (deprecated since version 0.8.0) - if (options && options.order) { - throw new Error('Option order is deprecated. There is no replacement for this feature.'); - } - - // redraw everything - this.redraw(); -}; - - /** * Set items * @param {vis.DataSet | Array | google.visualization.DataTable | null} items diff --git a/lib/timeline/Timeline.js b/lib/timeline/Timeline.js index 670b6636..1d33031b 100644 --- a/lib/timeline/Timeline.js +++ b/lib/timeline/Timeline.js @@ -104,55 +104,6 @@ function Timeline (container, items, options) { // Extend the functionality from Core Timeline.prototype = new Core(); -/** - * Set options. Options will be passed to all components loaded in the Timeline. - * @param {Object} [options] - * {String} orientation - * Vertical orientation for the Timeline, - * can be 'bottom' (default) or 'top'. - * {String | Number} width - * Width for the timeline, a number in pixels or - * a css string like '1000px' or '75%'. '100%' by default. - * {String | Number} height - * Fixed height for the Timeline, a number in pixels or - * a css string like '400px' or '75%'. If undefined, - * The Timeline will automatically size such that - * its contents fit. - * {String | Number} minHeight - * Minimum height for the Timeline, a number in pixels or - * a css string like '400px' or '75%'. - * {String | Number} maxHeight - * Maximum height for the Timeline, a number in pixels or - * a css string like '400px' or '75%'. - * {Number | Date | String} start - * Start date for the visible window - * {Number | Date | String} end - * End date for the visible window - */ -Timeline.prototype.setOptions = function (options) { - if (options) { - // copy the known options - var fields = ['width', 'height', 'minHeight', 'maxHeight', 'autoResize', 'start', 'end', 'orientation', 'activatable']; - util.selectiveExtend(fields, this.options, options); - - // enable/disable autoResize - this._initAutoResize(); - } - - // propagate options to all components - this.components.forEach(function (component) { - component.setOptions(options); - }); - - // TODO: remove deprecation error one day (deprecated since version 0.8.0) - if (options && options.order) { - throw new Error('Option order is deprecated. There is no replacement for this feature.'); - } - - // redraw everything - this.redraw(); -}; - /** * Set items * @param {vis.DataSet | Array | google.visualization.DataTable | null} items