diff --git a/src/timeline/Range.js b/src/timeline/Range.js index d90dc840..d50e8d13 100644 --- a/src/timeline/Range.js +++ b/src/timeline/Range.js @@ -3,31 +3,31 @@ * A Range controls a numeric range with a start and end value. * The Range adjusts the range based on mouse events or programmatic changes, * and triggers events when the range is changing or has been changed. - * @param {{dom: Object, props: Object, emitter: Emitter, range: Range}} timeline + * @param {{dom: Object, props: Object, emitter: Emitter}} body * @param {Object} [options] See description at Range.setOptions */ -function Range(timeline, options) { +function Range(body, options) { this.start = null; // Number this.end = null; // Number - this.timeline = timeline; + this.body = body; this.options = options || {}; // drag listeners for dragging - this.timeline.emitter.on('dragstart', this._onDragStart.bind(this)); - this.timeline.emitter.on('drag', this._onDrag.bind(this)); - this.timeline.emitter.on('dragend', this._onDragEnd.bind(this)); + this.body.emitter.on('dragstart', this._onDragStart.bind(this)); + this.body.emitter.on('drag', this._onDrag.bind(this)); + this.body.emitter.on('dragend', this._onDragEnd.bind(this)); // ignore dragging when holding - this.timeline.emitter.on('hold', this._onHold.bind(this)); + this.body.emitter.on('hold', this._onHold.bind(this)); // mouse wheel for zooming - this.timeline.emitter.on('mousewheel', this._onMouseWheel.bind(this)); - this.timeline.emitter.on('DOMMouseScroll', this._onMouseWheel.bind(this)); // For FF + this.body.emitter.on('mousewheel', this._onMouseWheel.bind(this)); + this.body.emitter.on('DOMMouseScroll', this._onMouseWheel.bind(this)); // For FF // pinch to zoom - this.timeline.emitter.on('touch', this._onTouch.bind(this)); - this.timeline.emitter.on('pinch', this._onPinch.bind(this)); + this.body.emitter.on('touch', this._onTouch.bind(this)); + this.body.emitter.on('pinch', this._onPinch.bind(this)); this.setOptions(options); } @@ -74,8 +74,8 @@ Range.prototype.setRange = function(start, end) { start: new Date(this.start), end: new Date(this.end) }; - this.timeline.emitter.emit('rangechange', params); - this.timeline.emitter.emit('rangechanged', params); + this.body.emitter.emit('rangechange', params); + this.body.emitter.emit('rangechanged', params); } }; @@ -252,8 +252,8 @@ Range.prototype._onDragStart = function(event) { touchParams.start = this.start; touchParams.end = this.end; - if (this.timeline.dom.root) { - this.timeline.dom.root.style.cursor = 'move'; + if (this.body.dom.root) { + this.body.dom.root.style.cursor = 'move'; } }; @@ -275,12 +275,12 @@ Range.prototype._onDrag = function (event) { var delta = (direction == 'horizontal') ? event.gesture.deltaX : event.gesture.deltaY, interval = (touchParams.end - touchParams.start), - width = (direction == 'horizontal') ? this.timeline.props.center.width : this.timeline.props.center.height, + width = (direction == 'horizontal') ? this.body.props.center.width : this.body.props.center.height, diffRange = -delta / width * interval; this._applyRange(touchParams.start + diffRange, touchParams.end + diffRange); - this.timeline.emitter.emit('rangechange', { + this.body.emitter.emit('rangechange', { start: new Date(this.start), end: new Date(this.end) }); @@ -298,12 +298,12 @@ Range.prototype._onDragEnd = function (event) { // TODO: reckon with option movable - if (this.timeline.dom.root) { - this.timeline.dom.root.style.cursor = 'auto'; + if (this.body.dom.root) { + this.body.dom.root.style.cursor = 'auto'; } // fire a rangechanged event - this.timeline.emitter.emit('rangechanged', { + this.body.emitter.emit('rangechanged', { start: new Date(this.start), end: new Date(this.end) }); @@ -346,7 +346,7 @@ Range.prototype._onMouseWheel = function(event) { // calculate center, the date to zoom around var gesture = util.fakeGesture(this, event), - pointer = getPointer(gesture.center, this.timeline.dom.center), + pointer = getPointer(gesture.center, this.body.dom.center), pointerDate = this._pointerToDate(pointer); this.zoom(scale, pointerDate); @@ -395,7 +395,7 @@ Range.prototype._onPinch = function (event) { if (event.gesture.touches.length > 1) { if (!touchParams.center) { - touchParams.center = getPointer(event.gesture.center, this.timeline.dom.center); + touchParams.center = getPointer(event.gesture.center, this.body.dom.center); } var scale = 1 / event.gesture.scale, @@ -423,12 +423,12 @@ Range.prototype._pointerToDate = function (pointer) { validateDirection(direction); if (direction == 'horizontal') { - var width = this.timeline.props.center.width; + var width = this.body.props.center.width; conversion = this.conversion(width); return pointer.x / conversion.scale + conversion.offset; } else { - var height = this.timeline.props.center.height; + var height = this.body.props.center.height; conversion = this.conversion(height); return pointer.y / conversion.scale + conversion.offset; } diff --git a/src/timeline/Timeline.js b/src/timeline/Timeline.js index 91fffc49..5ffea046 100644 --- a/src/timeline/Timeline.js +++ b/src/timeline/Timeline.js @@ -89,31 +89,38 @@ function Timeline (container, items, options) { // all components listed here will be repainted automatically this.components = []; + this.body = { + dom: this.dom, + props: this.props, + emitter: this + }; + // range - this.range = new Range(this, this.options); + this.range = new Range(this.body, this.options); this.range.setRange( now.clone().add('days', -3).valueOf(), now.clone().add('days', 4).valueOf() ); + this.body.range = this.range; // time axis - this.timeAxis = new TimeAxis(this, this.options); + this.timeAxis = new TimeAxis(this.body, this.options); this.components.push(this.timeAxis); this.options.snap = this.timeAxis.snap.bind(this.timeAxis); // TODO: not nice adding snap to options // current time bar - this.currentTime = new CurrentTime(this, this.options); + this.currentTime = new CurrentTime(this.body, this.options); this.components.push(this.currentTime); // custom time bar // Note: time bar will be attached in this.setOptions when selected - this.customTime = new CustomTime(this, this.options); + this.customTime = new CustomTime(this.body, this.options); this.components.push(this.customTime); // item set - this.itemSet = new ItemSet(this, this.options); + this.itemSet = new ItemSet(this.body, this.options); this.components.push(this.itemSet); - this.emitter.on('change', this.redraw.bind(this)); + this.on('change', this.redraw.bind(this)); this.itemsData = null; // DataSet this.groupsData = null; // DataSet @@ -178,10 +185,7 @@ Timeline.prototype._create = function () { this.dom.leftContainer.appendChild(this.dom.left); this.dom.rightContainer.appendChild(this.dom.right); - // create a central event bus - this.emitter = this; - - this.emitter.on('rangechange', this.redraw.bind(this)); + this.on('rangechange', this.redraw.bind(this)); // create event listeners for all interesting events, these events will be // emitted via emitter @@ -200,7 +204,7 @@ Timeline.prototype._create = function () { events.forEach(function (event) { var listener = function () { var args = [event].concat(Array.prototype.slice.call(arguments, 0)); - me.emitter.emit.apply(me.emitter, args); + me.emit.apply(me, args); }; me.hammer.on(event, listener); me.listeners[event] = listener; @@ -730,7 +734,7 @@ Timeline.prototype._startAutoResize = function () { me.props.lastWidth = me.dom.root.clientWidth; me.props.lastHeight = me.dom.root.clientHeight; - me.emitter.emit('change'); + me.emit('change'); } } } diff --git a/src/timeline/component/Component.js b/src/timeline/component/Component.js index 341070dd..5dfe5342 100644 --- a/src/timeline/component/Component.js +++ b/src/timeline/component/Component.js @@ -1,7 +1,9 @@ /** * Prototype for visual components + * @param {{dom: Object, props: Object, emitter: Emitter, range: Range}} body + * @param {Object} [options] */ -function Component () { +function Component (body, options) { this.options = null; this.props = null; } diff --git a/src/timeline/component/CurrentTime.js b/src/timeline/component/CurrentTime.js index f622b0d4..ca0008f4 100644 --- a/src/timeline/component/CurrentTime.js +++ b/src/timeline/component/CurrentTime.js @@ -1,14 +1,14 @@ /** * A current time bar - * @param {{range: Range, dom: Object}} timeline + * @param {{range: Range, dom: Object}} body * @param {Object} [options] Available parameters: * {Boolean} [showCurrentTime] * @constructor CurrentTime * @extends Component */ -function CurrentTime (timeline, options) { - this.timeline = timeline; +function CurrentTime (body, options) { + this.body = body; this.options = options || {}; @@ -37,7 +37,7 @@ CurrentTime.prototype._create = function() { */ CurrentTime.prototype.redraw = function() { if (this.options.showCurrentTime) { - var parent = this.timeline.dom.backgroundVertical; + var parent = this.body.dom.backgroundVertical; if (this.bar.parentNode != parent) { // attach to the dom if (this.bar.parentNode) { @@ -75,7 +75,7 @@ CurrentTime.prototype.start = function() { me.stop(); // determine interval to refresh - var scale = me.timeline.range.conversion(me.timeline.props.center.width).scale; + var scale = me.body.range.conversion(me.body.props.center.width).scale; var interval = 1 / scale / 10; if (interval < 30) interval = 30; if (interval > 1000) interval = 1000; diff --git a/src/timeline/component/CustomTime.js b/src/timeline/component/CustomTime.js index 31ef8dd3..4cd9e96d 100644 --- a/src/timeline/component/CustomTime.js +++ b/src/timeline/component/CustomTime.js @@ -1,14 +1,14 @@ /** * A custom time bar - * @param {{range: Range, dom: Object}} timeline + * @param {{range: Range, dom: Object}} body * @param {Object} [options] Available parameters: * {Boolean} [showCustomTime] * @constructor CustomTime * @extends Component */ -function CustomTime (timeline, options) { - this.timeline = timeline; +function CustomTime (body, options) { + this.body = body; this.options = options || {}; this.customTime = new Date(); @@ -55,7 +55,7 @@ CustomTime.prototype._create = function() { */ CustomTime.prototype.redraw = function () { if (this.options.showCustomTime) { - var parent = this.timeline.dom.backgroundVertical; + var parent = this.body.dom.backgroundVertical; if (this.bar.parentNode != parent) { // attach to the dom if (this.bar.parentNode) { @@ -124,7 +124,7 @@ CustomTime.prototype._onDrag = function (event) { this.setCustomTime(time); // fire a timechange event - this.timeline.emitter.emit('timechange', { + this.body.emitter.emit('timechange', { time: new Date(this.customTime.valueOf()) }); @@ -141,7 +141,7 @@ CustomTime.prototype._onDragEnd = function (event) { if (!this.eventParams.dragging) return; // fire a timechanged event - this.timeline.emitter.emit('timechanged', { + this.body.emitter.emit('timechanged', { time: new Date(this.customTime.valueOf()) }); diff --git a/src/timeline/component/Group.js b/src/timeline/component/Group.js index 78da0c39..343ba185 100644 --- a/src/timeline/component/Group.js +++ b/src/timeline/component/Group.js @@ -231,7 +231,7 @@ Group.prototype.add = function(item) { item.setParent(this); if (item instanceof ItemRange && this.visibleItems.indexOf(item) == -1) { - var range = this.itemSet.timeline.range; // TODO: not nice accessing the range like this + var range = this.itemSet.body.range; // TODO: not nice accessing the range like this this._checkIfVisible(item, this.visibleItems, range); } }; diff --git a/src/timeline/component/ItemSet.js b/src/timeline/component/ItemSet.js index 0948a67c..4b926c62 100644 --- a/src/timeline/component/ItemSet.js +++ b/src/timeline/component/ItemSet.js @@ -4,13 +4,13 @@ var UNGROUPED = '__ungrouped__'; // reserved group id for ungrouped items * An ItemSet holds a set of items and ranges which can be displayed in a * range. The width is determined by the parent of the ItemSet, and the height * is determined by the size of the items. - * @param {{dom: Object}} timeline + * @param {{dom: Object, props: Object, emitter: Emitter, range: Range}} body * @param {Object} [options] See ItemSet.setOptions for the available options. * @constructor ItemSet * @extends Component */ -function ItemSet(timeline, options) { - this.timeline = timeline; +function ItemSet(body, options) { + this.body = body; // one options object is shared by this itemset and all its items this.options = options || {}; @@ -194,17 +194,17 @@ ItemSet.prototype.hide = function() { ItemSet.prototype.show = function() { // show frame containing the items if (!this.dom.frame.parentNode) { - this.timeline.dom.center.appendChild(this.dom.frame); + this.body.dom.center.appendChild(this.dom.frame); } // show axis with dots if (!this.dom.axis.parentNode) { - this.timeline.dom.backgroundVertical.appendChild(this.dom.axis); + this.body.dom.backgroundVertical.appendChild(this.dom.axis); } // show labelset containing labels if (!this.dom.labelSet.parentNode) { - this.timeline.dom.left.appendChild(this.dom.labelSet); + this.body.dom.left.appendChild(this.dom.labelSet); } }; @@ -272,7 +272,7 @@ ItemSet.prototype._deselect = function(id) { */ ItemSet.prototype.redraw = function() { var margin = this.options.margin, - range = this.timeline.range, + range = this.body.range, asSize = util.option.asSize, options = this.options, orientation = options.orientation, @@ -337,9 +337,9 @@ ItemSet.prototype.redraw = function() { // reposition axis this.dom.axis.style.top = asSize((orientation == 'top') ? - (this.timeline.props.top.height + this.timeline.props.border.top) : - (this.timeline.props.top.height + this.timeline.props.centerContainer.height)); - this.dom.axis.style.left = this.timeline.props.border.left + 'px'; + (this.body.props.top.height + this.body.props.border.top) : + (this.body.props.top.height + this.body.props.centerContainer.height)); + this.dom.axis.style.left = this.body.props.border.left + 'px'; // check if this component is resized resized = this._isResized() || resized; @@ -506,7 +506,7 @@ ItemSet.prototype.setGroups = function(groups) { // update the order of all items in each group this._order(); - this.timeline.emitter.emit('change'); + this.body.emitter.emit('change'); }; /** @@ -584,7 +584,7 @@ ItemSet.prototype._onUpdate = function(ids) { this._order(); this.stackDirty = true; // force re-stacking of all items next redraw - this.timeline.emitter.emit('change'); + this.body.emitter.emit('change'); }; /** @@ -614,7 +614,7 @@ ItemSet.prototype._onRemove = function(ids) { // update order this._order(); this.stackDirty = true; // force re-stacking of all items next redraw - this.timeline.emitter.emit('change'); + this.body.emitter.emit('change'); } }; @@ -684,7 +684,7 @@ ItemSet.prototype._onAddGroups = function(ids) { } }); - this.timeline.emitter.emit('change'); + this.body.emitter.emit('change'); }; /** @@ -705,7 +705,7 @@ ItemSet.prototype._onRemoveGroups = function(ids) { this.markDirty(); - this.timeline.emitter.emit('change'); + this.body.emitter.emit('change'); }; /** @@ -912,7 +912,7 @@ ItemSet.prototype._onDragStart = function (event) { */ ItemSet.prototype._onDrag = function (event) { if (this.touchParams.itemProps) { - var range = this.timeline.range, + var range = this.body.range, snap = this.options.snap || null, deltaX = event.gesture.deltaX, scale = (this.props.width / (range.end - range.start)), @@ -948,7 +948,7 @@ ItemSet.prototype._onDrag = function (event) { // TODO: implement onMoving handler this.stackDirty = true; // force re-stacking of all items next redraw - this.timeline.emitter.emit('change'); + this.body.emitter.emit('change'); event.stopPropagation(); } @@ -998,7 +998,7 @@ ItemSet.prototype._onDragEnd = function (event) { if ('end' in props) props.item.data.end = props.end; me.stackDirty = true; // force re-stacking of all items next redraw - me.timeline.emitter.emit('change'); + me.body.emitter.emit('change'); } }); } @@ -1040,7 +1040,7 @@ ItemSet.prototype._onSelectItem = function (event) { // emit a select event, // except when old selection is empty and new selection is still empty if (newSelection.length > 0 || oldSelection.length > 0) { - this.timeline.emitter.emit('select', { + this.body.emitter.emit('select', { items: this.getSelection() }); } @@ -1131,7 +1131,7 @@ ItemSet.prototype._onMultiSelectItem = function (event) { } this.setSelection(selection); - this.timeline.emitter.emit('select', { + this.body.emitter.emit('select', { items: this.getSelection() }); diff --git a/src/timeline/component/TimeAxis.js b/src/timeline/component/TimeAxis.js index e459b70b..a14591ad 100644 --- a/src/timeline/component/TimeAxis.js +++ b/src/timeline/component/TimeAxis.js @@ -1,12 +1,12 @@ /** * A horizontal time axis - * @param {{dom: Object, props: Object, emitter: Emitter, range: Range}} timeline + * @param {{dom: Object, props: Object, emitter: Emitter, range: Range}} body * @param {Object} [options] See TimeAxis.setOptions for the available * options. * @constructor TimeAxis * @extends Component */ -function TimeAxis (timeline, options) { +function TimeAxis (body, options) { this.dom = { foreground: null, majorLines: [], @@ -37,7 +37,7 @@ function TimeAxis (timeline, options) { showMajorLabels: true }; - this.timeline = timeline; + this.body = body; // create the HTML DOM this._create(); @@ -77,7 +77,7 @@ TimeAxis.prototype.redraw = function () { background = this.dom.background; // determine the correct parent DOM element (depending on option orientation) - var parent = (options.orientation == 'top') ? this.timeline.dom.top : this.timeline.dom.bottom; + var parent = (options.orientation == 'top') ? this.body.dom.top : this.body.dom.bottom; var parentChanged = (foreground.parentNode !== parent); // calculate character width and height @@ -94,8 +94,8 @@ TimeAxis.prototype.redraw = function () { props.height = props.minorLabelHeight + props.majorLabelHeight; props.width = foreground.offsetWidth; - props.minorLineHeight = this.timeline.props.root.height - props.majorLabelHeight - - (options.orientation == 'top' ? this.timeline.props.bottom.height : this.timeline.props.top.height); + props.minorLineHeight = this.body.props.root.height - props.majorLabelHeight - + (options.orientation == 'top' ? this.body.props.bottom.height : this.body.props.top.height); props.minorLineWidth = 1; // TODO: really calculate width props.majorLineHeight = props.minorLineHeight + props.majorLabelHeight; props.majorLineWidth = 1; // TODO: really calculate width @@ -118,10 +118,10 @@ TimeAxis.prototype.redraw = function () { parent.appendChild(foreground) } if (backgroundNextSibling) { - this.timeline.dom.backgroundVertical.insertBefore(background, backgroundNextSibling); + this.body.dom.backgroundVertical.insertBefore(background, backgroundNextSibling); } else { - this.timeline.dom.backgroundVertical.appendChild(background) + this.body.dom.backgroundVertical.appendChild(background) } return this._isResized() || parentChanged; @@ -135,8 +135,8 @@ TimeAxis.prototype._repaintLabels = function () { var orientation = this.getOption('orientation'); // calculate range and step (step such that we have space for 7 characters per label) - var start = util.convert(this.timeline.range.start, 'Number'), - end = util.convert(this.timeline.range.end, 'Number'), + var start = util.convert(this.body.range.start, 'Number'), + end = util.convert(this.body.range.end, 'Number'), minimumStep = this.options.toTime((this.props.minorCharWidth || 10) * 7).valueOf() -this.options.toTime(0).valueOf(); var step = new TimeStep(new Date(start), new Date(end), minimumStep); @@ -301,7 +301,7 @@ TimeAxis.prototype._repaintMinorLine = function (x, orientation) { line.style.top = props.majorLabelHeight + 'px'; } else { - line.style.top = this.timeline.props.top.height + 'px'; + line.style.top = this.body.props.top.height + 'px'; } line.style.height = props.minorLineHeight + 'px'; line.style.left = (x - props.minorLineWidth / 2) + 'px'; @@ -330,7 +330,7 @@ TimeAxis.prototype._repaintMajorLine = function (x, orientation) { line.style.top = '0'; } else { - line.style.top = this.timeline.props.top.height + 'px'; + line.style.top = this.body.props.top.height + 'px'; } line.style.left = (x - props.majorLineWidth / 2) + 'px'; line.style.height = props.majorLineHeight + 'px';