diff --git a/Jakefile.js b/Jakefile.js index 42857a70..c2961da0 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -35,7 +35,8 @@ task('build', {async: true}, function () { './src/timeline/component/css/itemset.css', './src/timeline/component/css/item.css', './src/timeline/component/css/timeaxis.css', - './src/timeline/component/css/currenttime.css' + './src/timeline/component/css/currenttime.css', + './src/timeline/component/css/customtime.css' ], header: '/* vis.js stylesheet */', separator: '\n' @@ -64,6 +65,7 @@ task('build', {async: true}, function () { './src/timeline/component/RootPanel.js', './src/timeline/component/TimeAxis.js', './src/timeline/component/CurrentTime.js', + './src/timeline/component/CustomTime.js', './src/timeline/component/ItemSet.js', './src/timeline/component/item/*.js', './src/timeline/component/Group.js', diff --git a/src/timeline/Timeline.js b/src/timeline/Timeline.js index 27c62282..4e22693f 100644 --- a/src/timeline/Timeline.js +++ b/src/timeline/Timeline.js @@ -18,6 +18,7 @@ function Timeline (container, items, options) { showMinorLabels: true, showMajorLabels: true, showCurrentTime: false, + showCustomTime: false, autoResize: false }, options); @@ -114,6 +115,10 @@ function Timeline (container, items, options) { this.currenttime = new CurrentTime(this.timeaxis, [], rootOptions); this.controller.add(this.currenttime); + // custom time bar + this.customtime = new CustomTime(this.timeaxis, [], rootOptions); + this.controller.add(this.customtime); + // create itemset or groupset this.setGroups(null); @@ -141,6 +146,22 @@ Timeline.prototype.setOptions = function (options) { this.controller.repaint(); }; +/** + * Set a custom time bar + * @param {Date} time + */ +Timeline.prototype.setCustomTime = function (time) { + this.customtime._setCustomTime(time); +}; + +/** + * Retrieve the current custom time. + * @return {Date} customTime + */ +Timeline.prototype.getCustomTime = function() { + return new Date(this.customtime.customTime.valueOf()); +}; + /** * Set items * @param {vis.DataSet | Array | DataTable | null} items diff --git a/src/timeline/component/CustomTime.js b/src/timeline/component/CustomTime.js new file mode 100644 index 00000000..9efe6892 --- /dev/null +++ b/src/timeline/component/CustomTime.js @@ -0,0 +1,255 @@ +/** + * A custom time bar + * @param {Component} parent + * @param {Component[]} [depends] Components on which this components depends + * (except for the parent) + * @param {Object} [options] Available parameters: + * {Boolean} [showCustomTime] + * @constructor CustomTime + * @extends Component + */ + +function CustomTime (parent, depends, options) { + this.id = util.randomUUID(); + this.parent = parent; + this.depends = depends; + + this.options = options || {}; + this.defaultOptions = { + showCustomTime: false + }; + + this.listeners = []; + this.customTime = new Date(); +} + +CustomTime.prototype = new Component(); + +CustomTime.prototype.setOptions = Component.prototype.setOptions; + +/** + * Get the container element of the bar, which can be used by a child to + * add its own widgets. + * @returns {HTMLElement} container + */ +CustomTime.prototype.getContainer = function () { + return this.frame; +}; + +/** + * Repaint the component + * @return {Boolean} changed + */ +CustomTime.prototype.repaint = function () { + var bar = this.frame, + parent = this.parent, + parentContainer = parent.parent.getContainer(); + + if (!parent) { + throw new Error('Cannot repaint bar: no parent attached'); + } + + if (!parentContainer) { + throw new Error('Cannot repaint bar: parent has no container element'); + } + + if (!this.getOption('showCustomTime')) { + if (bar) { + parentContainer.removeChild(bar); + delete this.frame; + } + + return; + } + + if (!bar) { + bar = document.createElement('div'); + bar.className = 'customtime'; + bar.style.position = 'absolute'; + bar.style.top = '0px'; + bar.style.height = '100%'; + + parentContainer.appendChild(bar); + + var drag = document.createElement('div'); + drag.style.position = 'relative'; + drag.style.top = '0px'; + drag.style.left = '-10px'; + drag.style.height = '100%'; + drag.style.width = '20px'; + bar.appendChild(drag); + + this.frame = bar; + + this.subscribe(this, 'movetime'); + } + + if (!parent.conversion) { + parent._updateConversion(); + } + + var x = parent.toScreen(this.customTime); + + bar.style.left = x + 'px'; + bar.title = 'Time: ' + this.customTime; + + return false; +}; + +/** + * Set custom time. + * @param {Date} time + */ +CustomTime.prototype._setCustomTime = function(time) { + this.customTime = new Date(time.valueOf()); + this.repaint(); +}; + +/** + * Retrieve the current custom time. + * @return {Date} customTime + */ +CustomTime.prototype._getCustomTime = function() { + return new Date(this.customTime.valueOf()); +}; + +/** + * Add listeners for mouse and touch events to the component + * @param {Component} component + */ +CustomTime.prototype.subscribe = function (component, event) { + var me = this; + var listener = { + component: component, + event: event, + callback: function (event) { + me._onMouseDown(event, listener); + }, + params: {} + }; + + component.on('mousedown', listener.callback); + me.listeners.push(listener); + +}; + +/** + * Event handler + * @param {String} event name of the event, for example 'click', 'mousemove' + * @param {function} callback callback handler, invoked with the raw HTML Event + * as parameter. + */ +CustomTime.prototype.on = function (event, callback) { + var bar = this.frame; + if (!bar) { + throw new Error('Cannot add event listener: no parent attached'); + } + + events.addListener(this, event, callback); + util.addEventListener(bar, event, callback); +}; + +/** + * Start moving horizontally + * @param {Event} event + * @param {Object} listener Listener containing the component and params + * @private + */ +CustomTime.prototype._onMouseDown = function(event, listener) { + event = event || window.event; + var params = listener.params; + + // only react on left mouse button down + var leftButtonDown = event.which ? (event.which == 1) : (event.button == 1); + if (!leftButtonDown) { + return; + } + + // get mouse position + params.mouseX = util.getPageX(event); + params.moved = false; + + params.customTime = this.customTime; + + // add event listeners to handle moving the custom time bar + var me = this; + if (!params.onMouseMove) { + params.onMouseMove = function (event) { + me._onMouseMove(event, listener); + }; + util.addEventListener(document, 'mousemove', params.onMouseMove); + } + if (!params.onMouseUp) { + params.onMouseUp = function (event) { + me._onMouseUp(event, listener); + }; + util.addEventListener(document, 'mouseup', params.onMouseUp); + } + + util.stopPropagation(event); + util.preventDefault(event); +}; + +/** + * Perform moving operating. + * This function activated from within the funcion CustomTime._onMouseDown(). + * @param {Event} event + * @param {Object} listener + * @private + */ +CustomTime.prototype._onMouseMove = function (event, listener) { + event = event || window.event; + var params = listener.params; + var parent = this.parent; + + // calculate change in mouse position + var mouseX = util.getPageX(event); + + if (params.mouseX === undefined) { + params.mouseX = mouseX; + } + + var diff = mouseX - params.mouseX; + + // if mouse movement is big enough, register it as a "moved" event + if (Math.abs(diff) >= 1) { + params.moved = true; + } + + var x = parent.toScreen(params.customTime); + var xnew = x + diff; + var time = parent.toTime(xnew); + this._setCustomTime(time); + + // fire a timechange event + events.trigger(this, 'timechange', {customTime: this.customTime}); + + util.preventDefault(event); +}; + +/** + * Stop moving operating. + * This function activated from within the function CustomTime._onMouseDown(). + * @param {event} event + * @param {Object} listener + * @private + */ +CustomTime.prototype._onMouseUp = function (event, listener) { + event = event || window.event; + var params = listener.params; + + // remove event listeners here, important for Safari + if (params.onMouseMove) { + util.removeEventListener(document, 'mousemove', params.onMouseMove); + params.onMouseMove = null; + } + if (params.onMouseUp) { + util.removeEventListener(document, 'mouseup', params.onMouseUp); + params.onMouseUp = null; + } + + if (params.moved) { + // fire a timechanged event + events.trigger(this, 'timechanged', {customTime: this.customTime}); + } +}; diff --git a/src/timeline/component/css/customtime.css b/src/timeline/component/css/customtime.css new file mode 100644 index 00000000..15a3792a --- /dev/null +++ b/src/timeline/component/css/customtime.css @@ -0,0 +1,6 @@ +.vis.timeline .customtime { + background-color: #6E94FF; + width: 2px; + cursor: move; + z-index: 9; +} \ No newline at end of file