diff --git a/examples/graph/20_navigation.html b/examples/graph/20_navigation.html
index 8bc2006a..742d85eb 100644
--- a/examples/graph/20_navigation.html
+++ b/examples/graph/20_navigation.html
@@ -130,10 +130,10 @@
Icons: |
- |
- |
- |
- |
+ |
+ |
+ |
+ |
|
|
|
diff --git a/package.json b/package.json
index 73365fad..943db31a 100644
--- a/package.json
+++ b/package.json
@@ -33,6 +33,7 @@
"moment": "latest",
"hammerjs": "1.0.5",
"mousetrap": "latest",
+ "emitter-component": "latest",
"node-watch": "latest"
}
}
diff --git a/src/graph/graphMixins/physics/barnesHut.js b/src/graph/graphMixins/physics/BarnesHut.js
similarity index 100%
rename from src/graph/graphMixins/physics/barnesHut.js
rename to src/graph/graphMixins/physics/BarnesHut.js
diff --git a/src/graph/graphMixins/physics/repulsion.js b/src/graph/graphMixins/physics/Repulsion.js
similarity index 100%
rename from src/graph/graphMixins/physics/repulsion.js
rename to src/graph/graphMixins/physics/Repulsion.js
diff --git a/dist/img/downarrow.png b/src/graph/img/downArrow.png
similarity index 100%
rename from dist/img/downarrow.png
rename to src/graph/img/downArrow.png
diff --git a/src/graph/img/downarrow.png b/src/graph/img/downarrow.png
deleted file mode 100644
index e77d5e6d..00000000
Binary files a/src/graph/img/downarrow.png and /dev/null differ
diff --git a/dist/img/leftarrow.png b/src/graph/img/leftArrow.png
similarity index 100%
rename from dist/img/leftarrow.png
rename to src/graph/img/leftArrow.png
diff --git a/src/graph/img/leftarrow.png b/src/graph/img/leftarrow.png
deleted file mode 100644
index 3823536e..00000000
Binary files a/src/graph/img/leftarrow.png and /dev/null differ
diff --git a/dist/img/rightarrow.png b/src/graph/img/rightArrow.png
similarity index 100%
rename from dist/img/rightarrow.png
rename to src/graph/img/rightArrow.png
diff --git a/src/graph/img/rightarrow.png b/src/graph/img/rightarrow.png
deleted file mode 100644
index c3a209d8..00000000
Binary files a/src/graph/img/rightarrow.png and /dev/null differ
diff --git a/dist/img/uparrow.png b/src/graph/img/upArrow.png
similarity index 100%
rename from dist/img/uparrow.png
rename to src/graph/img/upArrow.png
diff --git a/src/graph/img/uparrow.png b/src/graph/img/uparrow.png
deleted file mode 100644
index 8aedced7..00000000
Binary files a/src/graph/img/uparrow.png and /dev/null differ
diff --git a/src/module/imports.js b/src/module/imports.js
index ea8d8c92..bda6f792 100644
--- a/src/module/imports.js
+++ b/src/module/imports.js
@@ -6,6 +6,7 @@
// If not available there, load via require.
var moment = (typeof window !== 'undefined') && window['moment'] || require('moment');
+var Emitter = require('emitter-component');
var Hammer;
if (typeof window !== 'undefined') {
@@ -28,5 +29,3 @@ else {
throw Error('mouseTrap is only available in a browser, not in node.js.');
}
}
-
-
diff --git a/src/timeline/Controller.js b/src/timeline/Controller.js
index 185d341b..ebb3e494 100644
--- a/src/timeline/Controller.js
+++ b/src/timeline/Controller.js
@@ -11,6 +11,9 @@ function Controller () {
this.reflowTimer = undefined;
}
+// Extend controller with Emitter mixin
+Emitter(Controller.prototype);
+
/**
* Add a component to the controller
* @param {Component} component
@@ -26,7 +29,7 @@ Controller.prototype.add = function add(component) {
}
// add the component
- component.controller = this;
+ component.setController(this);
this.components[component.id] = component;
};
@@ -38,13 +41,17 @@ Controller.prototype.remove = function remove(component) {
var id;
for (id in this.components) {
if (this.components.hasOwnProperty(id)) {
- if (id == component || this.components[id] == component) {
+ if (id == component || this.components[id] === component) {
break;
}
}
}
if (id) {
+ // unregister the controller (gives the component the ability to unregister
+ // event listeners and clean up other stuff)
+ this.components[id].setController(null);
+
delete this.components[id];
}
};
@@ -54,6 +61,7 @@ Controller.prototype.remove = function remove(component) {
* @param {Boolean} [force] If true, an immediate reflow is forced. Default
* is false.
*/
+// TODO: change requestReflow into an event
Controller.prototype.requestReflow = function requestReflow(force) {
if (force) {
this.reflow();
@@ -74,6 +82,7 @@ Controller.prototype.requestReflow = function requestReflow(force) {
* @param {Boolean} [force] If true, an immediate repaint is forced. Default
* is false.
*/
+// TODO: change requestReflow into an event
Controller.prototype.requestRepaint = function requestRepaint(force) {
if (force) {
this.repaint();
diff --git a/src/timeline/Range.js b/src/timeline/Range.js
index 70c9083d..0bea4d63 100644
--- a/src/timeline/Range.js
+++ b/src/timeline/Range.js
@@ -48,42 +48,48 @@ function validateDirection (direction) {
/**
* Add listeners for mouse and touch events to the component
- * @param {Component} component
+ * @param {Controller} controller
+ * @param {Component} component Should be a rootpanel
* @param {String} event Available events: 'move', 'zoom'
* @param {String} direction Available directions: 'horizontal', 'vertical'
*/
-Range.prototype.subscribe = function (component, event, direction) {
+Range.prototype.subscribe = function (controller, component, event, direction) {
var me = this;
if (event == 'move') {
// drag start listener
- component.on('dragstart', function (event) {
+ controller.on('dragstart', function (event) {
me._onDragStart(event, component);
});
// drag listener
- component.on('drag', function (event) {
+ controller.on('drag', function (event) {
me._onDrag(event, component, direction);
});
// drag end listener
- component.on('dragend', function (event) {
+ controller.on('dragend', function (event) {
me._onDragEnd(event, component);
});
+
+ // ignore dragging when holding
+ controller.on('hold', function (event) {
+ me._onHold();
+ });
}
else if (event == 'zoom') {
// mouse wheel
function mousewheel (event) {
me._onMouseWheel(event, component, direction);
}
- component.on('mousewheel', mousewheel);
- component.on('DOMMouseScroll', mousewheel); // For FF
+ controller.on('mousewheel', mousewheel);
+ controller.on('DOMMouseScroll', mousewheel); // For FF
// pinch
- component.on('touch', function (event) {
- me._onTouch();
+ controller.on('touch', function (event) {
+ me._onTouch(event);
});
- component.on('pinch', function (event) {
+ controller.on('pinch', function (event) {
me._onPinch(event, component, direction);
});
}
@@ -311,7 +317,7 @@ var touchParams = {};
Range.prototype._onDragStart = function(event, component) {
// refuse to drag when we where pinching to prevent the timeline make a jump
// when releasing the fingers in opposite order from the touch screen
- if (touchParams.pinching) return;
+ if (touchParams.ignore) return;
touchParams.start = this.start;
touchParams.end = this.end;
@@ -334,7 +340,7 @@ Range.prototype._onDrag = function (event, component, direction) {
// refuse to drag when we where pinching to prevent the timeline make a jump
// when releasing the fingers in opposite order from the touch screen
- if (touchParams.pinching) return;
+ if (touchParams.ignore) return;
var delta = (direction == 'horizontal') ? event.gesture.deltaX : event.gesture.deltaY,
interval = (touchParams.end - touchParams.start),
@@ -356,7 +362,7 @@ Range.prototype._onDrag = function (event, component, direction) {
Range.prototype._onDragEnd = function (event, component) {
// refuse to drag when we where pinching to prevent the timeline make a jump
// when releasing the fingers in opposite order from the touch screen
- if (touchParams.pinching) return;
+ if (touchParams.ignore) return;
if (component.frame) {
component.frame.style.cursor = 'auto';
@@ -417,14 +423,29 @@ Range.prototype._onMouseWheel = function(event, component, direction) {
};
/**
- * On start of a touch gesture, initialize scale to 1
+ * Start of a touch gesture
* @private
*/
-Range.prototype._onTouch = function () {
+Range.prototype._onTouch = function (event) {
touchParams.start = this.start;
touchParams.end = this.end;
- touchParams.pinching = false;
+ touchParams.ignore = false;
touchParams.center = null;
+
+ // don't move the range when dragging a selected event
+ // TODO: it's not so neat to have to know about the state of the ItemSet
+ var item = ItemSet.itemFromTarget(event);
+ if (item && item.selected) {
+ touchParams.ignore = true;
+ }
+};
+
+/**
+ * On start of a hold gesture
+ * @private
+ */
+Range.prototype._onHold = function () {
+ touchParams.ignore = true;
};
/**
@@ -435,7 +456,7 @@ Range.prototype._onTouch = function () {
* @private
*/
Range.prototype._onPinch = function (event, component, direction) {
- touchParams.pinching = true;
+ touchParams.ignore = true;
if (event.gesture.touches.length > 1) {
if (!touchParams.center) {
diff --git a/src/timeline/Stack.js b/src/timeline/Stack.js
index 017c98ce..16ac34b7 100644
--- a/src/timeline/Stack.js
+++ b/src/timeline/Stack.js
@@ -1,11 +1,11 @@
/**
* @constructor Stack
* Stacks items on top of each other.
- * @param {ItemSet} parent
+ * @param {ItemSet} itemset
* @param {Object} [options]
*/
-function Stack (parent, options) {
- this.parent = parent;
+function Stack (itemset, options) {
+ this.itemset = itemset;
this.options = options || {};
this.defaultOptions = {
@@ -43,14 +43,14 @@ function Stack (parent, options) {
/**
* Set options for the stack
* @param {Object} options Available options:
- * {ItemSet} parent
+ * {ItemSet} itemset
* {Number} margin
* {function} order Stacking order
*/
Stack.prototype.setOptions = function setOptions (options) {
util.extend(this.options, options);
- // TODO: register on data changes at the connected parent itemset, and update the changed part only and immediately
+ // TODO: register on data changes at the connected itemset, and update the changed part only and immediately
};
/**
@@ -70,9 +70,9 @@ Stack.prototype.update = function update() {
* @private
*/
Stack.prototype._order = function _order () {
- var items = this.parent.items;
+ var items = this.itemset.items;
if (!items) {
- throw new Error('Cannot stack items: parent does not contain items');
+ throw new Error('Cannot stack items: ItemSet does not contain items');
}
// TODO: store the sorted items, to have less work later on
diff --git a/src/timeline/Timeline.js b/src/timeline/Timeline.js
index 38c19fae..664bc20c 100644
--- a/src/timeline/Timeline.js
+++ b/src/timeline/Timeline.js
@@ -1,7 +1,7 @@
/**
* Create a timeline visualization
* @param {HTMLElement} container
- * @param {vis.DataSet | Array | DataTable} [items]
+ * @param {vis.DataSet | Array | google.visualization.DataTable} [items]
* @param {Object} [options] See Timeline.setOptions for the available options.
* @constructor
*/
@@ -45,6 +45,13 @@ function Timeline (container, items, options) {
this.rootPanel = new RootPanel(container, rootOptions);
this.controller.add(this.rootPanel);
+ // single select (or unselect) when tapping an item
+ // TODO: implement ctrl+click
+ this.controller.on('tap', this._onSelectItem.bind(this));
+
+ // multi select when holding mouse/touch, or on ctrl+click
+ this.controller.on('hold', this._onMultiSelectItem.bind(this));
+
// item panel
var itemOptions = Object.create(this.options);
itemOptions.left = function () {
@@ -84,26 +91,20 @@ function Timeline (container, items, options) {
// TODO: reckon with options moveable and zoomable
// TODO: put the listeners in setOptions, be able to dynamically change with options moveable and zoomable
- this.range.subscribe(this.rootPanel, 'move', 'horizontal');
- this.range.subscribe(this.rootPanel, 'zoom', 'horizontal');
+ // TODO: enable moving again
+ this.range.subscribe(this.controller, this.rootPanel, 'move', 'horizontal');
+ this.range.subscribe(this.controller, this.rootPanel, 'zoom', 'horizontal');
this.range.on('rangechange', function (properties) {
var force = true;
me.controller.requestReflow(force);
- me._trigger('rangechange', properties);
+ me.emit('rangechange', properties);
});
this.range.on('rangechanged', function (properties) {
var force = true;
me.controller.requestReflow(force);
- me._trigger('rangechanged', properties);
+ me.emit('rangechanged', properties);
});
- // single select (or unselect) when tapping an item
- // TODO: implement ctrl+click
- this.rootPanel.on('tap', this._onSelectItem.bind(this));
-
- // multi select when holding mouse/touch, or on ctrl+click
- this.rootPanel.on('hold', this._onMultiSelectItem.bind(this));
-
// time axis
var timeaxisOptions = Object.create(rootOptions);
timeaxisOptions.range = this.range;
@@ -140,6 +141,9 @@ function Timeline (container, items, options) {
}
}
+// extend Timeline with the Emitter mixin
+Emitter(Timeline.prototype);
+
/**
* Set options
* @param {Object} options TODO: describe the available options
@@ -173,7 +177,7 @@ Timeline.prototype.getCustomTime = function() {
/**
* Set items
- * @param {vis.DataSet | Array | DataTable | null} items
+ * @param {vis.DataSet | Array | google.visualization.DataTable | null} items
*/
Timeline.prototype.setItems = function(items) {
var initialLoad = (this.itemsData == null);
@@ -234,7 +238,7 @@ Timeline.prototype.setItems = function(items) {
/**
* Set groups
- * @param {vis.DataSet | Array | DataTable} groups
+ * @param {vis.DataSet | Array | google.visualization.DataTable} groups
*/
Timeline.prototype.setGroups = function(groups) {
var me = this;
@@ -367,56 +371,19 @@ Timeline.prototype.getSelection = function getSelection() {
return this.content ? this.content.getSelection() : [];
};
-/**
- * Add event listener
- * @param {String} event Event name. Available events:
- * 'rangechange', 'rangechanged', 'select'
- * @param {function} callback Callback function, invoked as callback(properties)
- * where properties is an optional object containing
- * event specific properties.
- */
-Timeline.prototype.on = function on (event, callback) {
- var available = ['rangechange', 'rangechanged', 'select'];
-
- if (available.indexOf(event) == -1) {
- throw new Error('Unknown event "' + event + '". Choose from ' + available.join());
- }
-
- events.addListener(this, event, callback);
-};
-
-/**
- * Remove an event listener
- * @param {String} event Event name
- * @param {function} callback Callback function
- */
-Timeline.prototype.off = function off (event, callback) {
- events.removeListener(this, event, callback);
-};
-
-/**
- * Trigger an event
- * @param {String} event Event name, available events: 'rangechange',
- * 'rangechanged', 'select'
- * @param {Object} [properties] Event specific properties
- * @private
- */
-Timeline.prototype._trigger = function _trigger(event, properties) {
- events.trigger(this, event, properties || {});
-};
-
/**
* Handle selecting/deselecting an item when tapping it
* @param {Event} event
* @private
*/
+// TODO: move this function to ItemSet
Timeline.prototype._onSelectItem = function (event) {
- var item = this._itemFromTarget(event);
+ var item = ItemSet.itemFromTarget(event);
var selection = item ? [item.id] : [];
this.setSelection(selection);
- this._trigger('select', {
+ this.emit('select', {
items: this.getSelection()
});
@@ -428,9 +395,10 @@ Timeline.prototype._onSelectItem = function (event) {
* @param {Event} event
* @private
*/
+// TODO: move this function to ItemSet
Timeline.prototype._onMultiSelectItem = function (event) {
var selection,
- item = this._itemFromTarget(event);
+ item = ItemSet.itemFromTarget(event);
if (!item) {
// do nothing...
@@ -449,28 +417,9 @@ Timeline.prototype._onMultiSelectItem = function (event) {
}
this.setSelection(selection);
- this._trigger('select', {
+ this.emit('select', {
items: this.getSelection()
});
event.stopPropagation();
};
-
-/**
- * Find an item from an event target:
- * searches for the attribute 'timeline-item' in the event target's element tree
- * @param {Event} event
- * @return {Item | null| item
- * @private
- */
-Timeline.prototype._itemFromTarget = function _itemFromTarget (event) {
- var target = event.target;
- while (target) {
- if (target.hasOwnProperty('timeline-item')) {
- return target['timeline-item'];
- }
- target = target.parentNode;
- }
-
- return null;
-};
\ No newline at end of file
diff --git a/src/timeline/component/Component.js b/src/timeline/component/Component.js
index 8d15e0c6..c7c8cd3c 100644
--- a/src/timeline/component/Component.js
+++ b/src/timeline/component/Component.js
@@ -55,6 +55,23 @@ Component.prototype.getOption = function getOption(name) {
return value;
};
+/**
+ * Set controller for this component, or remove current controller by passing
+ * null as parameter value.
+ * @param {Controller | null} controller
+ */
+Component.prototype.setController = function setController (controller) {
+ this.controller = controller || null;
+};
+
+/**
+ * Get controller of this component
+ * @return {Controller} controller
+ */
+Component.prototype.getController = function getController () {
+ return this.controller;
+};
+
/**
* Get the container element of the component, which can be used by a child to
* add its own widgets. Not all components do have a container for childs, in
diff --git a/src/timeline/component/ItemSet.js b/src/timeline/component/ItemSet.js
index 20d7259b..e44d4c87 100644
--- a/src/timeline/component/ItemSet.js
+++ b/src/timeline/component/ItemSet.js
@@ -16,6 +16,13 @@ function ItemSet(parent, depends, options) {
this.parent = parent;
this.depends = depends;
+ // event listeners
+ this.eventListeners = {
+ dragstart: this._onDragStart.bind(this),
+ drag: this._onDrag.bind(this),
+ dragend: this._onDragEnd.bind(this)
+ };
+
// one options object is shared by this itemset and all its items
this.options = options || {};
this.defaultOptions = {
@@ -35,6 +42,7 @@ function ItemSet(parent, depends, options) {
this.itemsData = null; // DataSet
this.range = null; // Range or Object {start: number, end: number}
+ // data change listeners
this.listeners = {
'add': function (event, params, senderId) {
if (senderId != me.id) {
@@ -59,6 +67,8 @@ function ItemSet(parent, depends, options) {
this.stack = new Stack(this, Object.create(this.options));
this.conversion = null;
+ this.touchParams = {}; // stores properties while dragging
+
// TODO: ItemSet should also attach event listeners for rangechange and rangechanged, like timeaxis
}
@@ -99,6 +109,55 @@ ItemSet.types = {
*/
ItemSet.prototype.setOptions = Component.prototype.setOptions;
+
+
+/**
+ * Set controller for this component
+ * @param {Controller | null} controller
+ */
+ItemSet.prototype.setController = function setController (controller) {
+ var event;
+
+ // unregister old event listeners
+ if (this.controller) {
+ for (event in this.eventListeners) {
+ if (this.eventListeners.hasOwnProperty(event)) {
+ this.controller.off(event, this.eventListeners[event]);
+ }
+ }
+ }
+
+ this.controller = controller || null;
+
+ // register new event listeners
+ if (this.controller) {
+ for (event in this.eventListeners) {
+ if (this.eventListeners.hasOwnProperty(event)) {
+ this.controller.on(event, this.eventListeners[event]);
+ }
+ }
+ }
+};
+
+// attach event listeners for dragging items to the controller
+(function (me) {
+ var _controller = null;
+ var _onDragStart = null;
+ var _onDrag = null;
+ var _onDragEnd = null;
+
+ Object.defineProperty(me, 'controller', {
+ get: function () {
+ return _controller;
+ },
+
+ set: function (controller) {
+
+ }
+ });
+}) (this);
+
+
/**
* Set range (start and end).
* @param {Range | Object} range A Range or an object containing start and end.
@@ -195,6 +254,7 @@ ItemSet.prototype.repaint = function repaint() {
if (!frame) {
frame = document.createElement('div');
frame.className = 'itemset';
+ frame['timeline-itemset'] = this;
var className = options.className;
if (className) {
@@ -610,3 +670,124 @@ ItemSet.prototype.toScreen = function toScreen(time) {
var conversion = this.conversion;
return (time.valueOf() - conversion.offset) * conversion.scale;
};
+
+/**
+ * Start dragging the selected events
+ * @param {Event} event
+ * @private
+ */
+ItemSet.prototype._onDragStart = function (event) {
+ var itemSet = ItemSet.itemSetFromTarget(event),
+ item = ItemSet.itemFromTarget(event),
+ me = this;
+
+ if (item && item.selected) {
+ this.touchParams.items = this.getSelection().map(function (id) {
+ return me.items[id];
+ });
+
+ event.stopPropagation();
+ }
+};
+
+/**
+ * Drag selected items
+ * @param {Event} event
+ * @private
+ */
+ItemSet.prototype._onDrag = function (event) {
+ if (this.touchParams.items) {
+ var deltaX = event.gesture.deltaX;
+
+ // adjust the offset of the items being dragged
+ this.touchParams.items.forEach(function (item) {
+ item.setOffset(deltaX);
+ });
+
+ // TODO: implement snapping to nice dates
+
+ // TODO: implement dragging from one group to another
+
+ this.requestReflow();
+
+ event.stopPropagation();
+ }
+};
+
+/**
+ * End of dragging selected items
+ * @param {Event} event
+ * @private
+ */
+ItemSet.prototype._onDragEnd = function (event) {
+ if (this.touchParams.items) {
+ var deltaX = event.gesture.deltaX,
+ scale = this.conversion.scale;
+
+ // prepare a changeset for the changed items
+ var changes = this.touchParams.items.map(function (item) {
+ item.setOffset(0);
+
+ var change = {
+ id: item.id
+ };
+
+ if ('start' in item.data) {
+ change.start = new Date(item.data.start.valueOf() + deltaX / scale);
+ }
+ if ('end' in item.data) {
+ change.end = new Date(item.data.end.valueOf() + deltaX / scale);
+ }
+
+ return change;
+ });
+ this.touchParams.items = null;
+
+ // find the root DataSet from our DataSet/DataView
+ var data = this.itemsData;
+ while (data instanceof DataView) {
+ data = data.data;
+ }
+
+ // apply the changes to the data
+ data.update(changes);
+
+ event.stopPropagation();
+ }
+};
+
+/**
+ * Find an item from an event target:
+ * searches for the attribute 'timeline-item' in the event target's element tree
+ * @param {Event} event
+ * @return {Item | null} item
+ */
+ItemSet.itemFromTarget = function itemFromTarget (event) {
+ var target = event.target;
+ while (target) {
+ if (target.hasOwnProperty('timeline-item')) {
+ return target['timeline-item'];
+ }
+ target = target.parentNode;
+ }
+
+ return null;
+};
+
+/**
+ * Find the ItemSet from an event target:
+ * searches for the attribute 'timeline-itemset' in the event target's element tree
+ * @param {Event} event
+ * @return {ItemSet | null} item
+ */
+ItemSet.itemSetFromTarget = function itemSetFromTarget (event) {
+ var target = event.target;
+ while (target) {
+ if (target.hasOwnProperty('timeline-itemset')) {
+ return target['timeline-itemset'];
+ }
+ target = target.parentNode;
+ }
+
+ return null;
+};
diff --git a/src/timeline/component/RootPanel.js b/src/timeline/component/RootPanel.js
index 6bfd2db2..e4c6166d 100644
--- a/src/timeline/component/RootPanel.js
+++ b/src/timeline/component/RootPanel.js
@@ -10,12 +10,29 @@ function RootPanel(container, options) {
this.id = util.randomUUID();
this.container = container;
+ // create functions to be used as DOM event listeners
+ var me = this;
+ this.hammer = null;
+
+ // create listeners for all interesting events, these events will be emitted
+ // via the controller
+ var events = [
+ 'touch', 'pinch', 'tap', 'hold',
+ 'dragstart', 'drag', 'dragend',
+ 'mousewheel', 'DOMMouseScroll' // DOMMouseScroll is for Firefox
+ ];
+ this.listeners = {};
+ events.forEach(function (event) {
+ me.listeners[event] = function () {
+ var args = [event].concat(Array.prototype.slice.call(arguments, 0));
+ me.controller.emit.apply(me.controller, args);
+ };
+ });
+
this.options = options || {};
this.defaultOptions = {
autoResize: true
};
-
- this.listeners = {}; // event listeners
}
RootPanel.prototype = new Panel();
@@ -48,6 +65,8 @@ RootPanel.prototype.repaint = function () {
this.frame = frame;
+ this._registerListeners();
+
changed += 1;
}
if (!frame.parentNode) {
@@ -69,7 +88,6 @@ RootPanel.prototype.repaint = function () {
changed += update(frame.style, 'width', asSize(options.width, '100%'));
changed += update(frame.style, 'height', asSize(options.height, '100%'));
- this._updateEventEmitters();
this._updateWatch();
return (changed > 0);
@@ -158,58 +176,51 @@ RootPanel.prototype._unwatch = function () {
};
/**
- * 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.
+ * Set controller for this component, or remove current controller by passing
+ * null as parameter value.
+ * @param {Controller | null} controller
*/
-RootPanel.prototype.on = function (event, callback) {
- // register the listener at this component
- var arr = this.listeners[event];
- if (!arr) {
- arr = [];
- this.listeners[event] = arr;
- }
- arr.push(callback);
+RootPanel.prototype.setController = function setController (controller) {
+ this.controller = controller || null;
- this._updateEventEmitters();
+ if (this.controller) {
+ this._registerListeners();
+ }
+ else {
+ this._unregisterListeners();
+ }
};
/**
- * Update the event listeners for all event emitters
+ * Register event emitters emitted by the rootpanel
* @private
*/
-RootPanel.prototype._updateEventEmitters = function () {
- if (this.listeners) {
- var me = this;
- util.forEach(this.listeners, function (listeners, event) {
- if (!me.emitters) {
- me.emitters = {};
+RootPanel.prototype._registerListeners = function () {
+ if (this.frame && this.controller && !this.hammer) {
+ this.hammer = Hammer(this.frame, {
+ prevent_default: true
+ });
+
+ for (var event in this.listeners) {
+ if (this.listeners.hasOwnProperty(event)) {
+ this.hammer.on(event, this.listeners[event]);
}
- if (!(event in me.emitters)) {
- // create event
- var frame = me.frame;
- if (frame) {
- //console.log('Created a listener for event ' + event + ' on component ' + me.id); // TODO: cleanup logging
- var callback = function(event) {
- listeners.forEach(function (listener) {
- // TODO: filter on event target!
- listener(event);
- });
- };
- me.emitters[event] = callback;
-
- if (!me.hammer) {
- me.hammer = Hammer(frame, {
- prevent_default: true
- });
- }
- me.hammer.on(event, callback);
- }
+ }
+ }
+};
+
+/**
+ * Unregister event emitters from the rootpanel
+ * @private
+ */
+RootPanel.prototype._unregisterListeners = function () {
+ if (this.hammer) {
+ for (var event in this.listeners) {
+ if (this.listeners.hasOwnProperty(event)) {
+ this.hammer.off(event, this.listeners[event]);
}
- });
+ }
- // TODO: be able to delete event listeners
- // TODO: be able to move event listeners to a parent when available
+ this.hammer = null;
}
};
diff --git a/src/timeline/component/item/Item.js b/src/timeline/component/item/Item.js
index 590ee551..cdb69c76 100644
--- a/src/timeline/component/item/Item.js
+++ b/src/timeline/component/item/Item.js
@@ -20,6 +20,7 @@ function Item (parent, data, options, defaultOptions) {
this.left = 0;
this.width = 0;
this.height = 0;
+ this.offset = 0;
}
/**
@@ -72,10 +73,18 @@ Item.prototype.reflow = function reflow() {
return false;
};
+/**
+ * Give the item a display offset in pixels
+ * @param {Number} offset Offset on screen in pixels
+ */
+Item.prototype.setOffset = function setOffset(offset) {
+ this.offset = offset;
+};
+
/**
* Return the items width
- * @return {Integer} width
+ * @return {Number} width
*/
Item.prototype.getWidth = function getWidth() {
return this.width;
-}
+};
diff --git a/src/timeline/component/item/ItemBox.js b/src/timeline/component/item/ItemBox.js
index cde86f5a..8ea236c9 100644
--- a/src/timeline/component/item/ItemBox.js
+++ b/src/timeline/component/item/ItemBox.js
@@ -187,7 +187,7 @@ ItemBox.prototype.reflow = function reflow() {
update = util.updateProperty;
props = this.props;
options = this.options;
- start = this.parent.toScreen(this.data.start);
+ start = this.parent.toScreen(this.data.start) + this.offset;
align = options.align || this.defaultOptions.align;
margin = options.margin && options.margin.axis || this.defaultOptions.margin.axis;
orientation = options.orientation || this.defaultOptions.orientation;
diff --git a/src/timeline/component/item/ItemPoint.js b/src/timeline/component/item/ItemPoint.js
index 1a78a92b..2d5124e1 100644
--- a/src/timeline/component/item/ItemPoint.js
+++ b/src/timeline/component/item/ItemPoint.js
@@ -157,7 +157,7 @@ ItemPoint.prototype.reflow = function reflow() {
options = this.options;
orientation = options.orientation || this.defaultOptions.orientation;
margin = options.margin && options.margin.axis || this.defaultOptions.margin.axis;
- start = this.parent.toScreen(this.data.start);
+ start = this.parent.toScreen(this.data.start) + this.offset;
changed += update(this, 'width', dom.point.offsetWidth);
changed += update(this, 'height', dom.point.offsetHeight);
diff --git a/src/timeline/component/item/ItemRange.js b/src/timeline/component/item/ItemRange.js
index a2feec99..bb7b8386 100644
--- a/src/timeline/component/item/ItemRange.js
+++ b/src/timeline/component/item/ItemRange.js
@@ -157,8 +157,8 @@ ItemRange.prototype.reflow = function reflow() {
props = this.props;
options = this.options;
parent = this.parent;
- start = parent.toScreen(this.data.start);
- end = parent.toScreen(this.data.end);
+ start = parent.toScreen(this.data.start) + this.offset;
+ end = parent.toScreen(this.data.end) + this.offset;
update = util.updateProperty;
box = dom.box;
parentWidth = parent.width;