Browse Source

Implemented dragging items

css_transitions
josdejong 10 years ago
parent
commit
5801a0e770
9 changed files with 131 additions and 72 deletions
  1. +2
    -0
      src/timeline/Controller.js
  2. +28
    -8
      src/timeline/Range.js
  3. +7
    -7
      src/timeline/Stack.js
  4. +2
    -22
      src/timeline/Timeline.js
  5. +79
    -31
      src/timeline/component/ItemSet.js
  6. +9
    -0
      src/timeline/component/item/Item.js
  7. +1
    -1
      src/timeline/component/item/ItemBox.js
  8. +1
    -1
      src/timeline/component/item/ItemPoint.js
  9. +2
    -2
      src/timeline/component/item/ItemRange.js

+ 2
- 0
src/timeline/Controller.js View File

@ -61,6 +61,7 @@ Controller.prototype.remove = function remove(component) {
* @param {Boolean} [force] If true, an immediate reflow is forced. Default * @param {Boolean} [force] If true, an immediate reflow is forced. Default
* is false. * is false.
*/ */
// TODO: change requestReflow into an event
Controller.prototype.requestReflow = function requestReflow(force) { Controller.prototype.requestReflow = function requestReflow(force) {
if (force) { if (force) {
this.reflow(); this.reflow();
@ -81,6 +82,7 @@ Controller.prototype.requestReflow = function requestReflow(force) {
* @param {Boolean} [force] If true, an immediate repaint is forced. Default * @param {Boolean} [force] If true, an immediate repaint is forced. Default
* is false. * is false.
*/ */
// TODO: change requestReflow into an event
Controller.prototype.requestRepaint = function requestRepaint(force) { Controller.prototype.requestRepaint = function requestRepaint(force) {
if (force) { if (force) {
this.repaint(); this.repaint();

+ 28
- 8
src/timeline/Range.js View File

@ -71,6 +71,11 @@ Range.prototype.subscribe = function (controller, component, event, direction) {
controller.on('dragend', function (event) { controller.on('dragend', function (event) {
me._onDragEnd(event, component); me._onDragEnd(event, component);
}); });
// ignore dragging when holding
controller.on('hold', function (event) {
me._onHold();
});
} }
else if (event == 'zoom') { else if (event == 'zoom') {
// mouse wheel // mouse wheel
@ -82,7 +87,7 @@ Range.prototype.subscribe = function (controller, component, event, direction) {
// pinch // pinch
controller.on('touch', function (event) { controller.on('touch', function (event) {
me._onTouch();
me._onTouch(event);
}); });
controller.on('pinch', function (event) { controller.on('pinch', function (event) {
me._onPinch(event, component, direction); me._onPinch(event, component, direction);
@ -312,7 +317,7 @@ var touchParams = {};
Range.prototype._onDragStart = function(event, component) { Range.prototype._onDragStart = function(event, component) {
// refuse to drag when we where pinching to prevent the timeline make a jump // 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 // when releasing the fingers in opposite order from the touch screen
if (touchParams.pinching) return;
if (touchParams.ignore) return;
touchParams.start = this.start; touchParams.start = this.start;
touchParams.end = this.end; touchParams.end = this.end;
@ -335,7 +340,7 @@ Range.prototype._onDrag = function (event, component, direction) {
// refuse to drag when we where pinching to prevent the timeline make a jump // 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 // 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, var delta = (direction == 'horizontal') ? event.gesture.deltaX : event.gesture.deltaY,
interval = (touchParams.end - touchParams.start), interval = (touchParams.end - touchParams.start),
@ -357,7 +362,7 @@ Range.prototype._onDrag = function (event, component, direction) {
Range.prototype._onDragEnd = function (event, component) { Range.prototype._onDragEnd = function (event, component) {
// refuse to drag when we where pinching to prevent the timeline make a jump // 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 // when releasing the fingers in opposite order from the touch screen
if (touchParams.pinching) return;
if (touchParams.ignore) return;
if (component.frame) { if (component.frame) {
component.frame.style.cursor = 'auto'; component.frame.style.cursor = 'auto';
@ -418,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 * @private
*/ */
Range.prototype._onTouch = function () {
Range.prototype._onTouch = function (event) {
touchParams.start = this.start; touchParams.start = this.start;
touchParams.end = this.end; touchParams.end = this.end;
touchParams.pinching = false;
touchParams.ignore = false;
touchParams.center = null; 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;
}; };
/** /**
@ -436,7 +456,7 @@ Range.prototype._onTouch = function () {
* @private * @private
*/ */
Range.prototype._onPinch = function (event, component, direction) { Range.prototype._onPinch = function (event, component, direction) {
touchParams.pinching = true;
touchParams.ignore = true;
if (event.gesture.touches.length > 1) { if (event.gesture.touches.length > 1) {
if (!touchParams.center) { if (!touchParams.center) {

+ 7
- 7
src/timeline/Stack.js View File

@ -1,11 +1,11 @@
/** /**
* @constructor Stack * @constructor Stack
* Stacks items on top of each other. * Stacks items on top of each other.
* @param {ItemSet} parent
* @param {ItemSet} itemset
* @param {Object} [options] * @param {Object} [options]
*/ */
function Stack (parent, options) {
this.parent = parent;
function Stack (itemset, options) {
this.itemset = itemset;
this.options = options || {}; this.options = options || {};
this.defaultOptions = { this.defaultOptions = {
@ -43,14 +43,14 @@ function Stack (parent, options) {
/** /**
* Set options for the stack * Set options for the stack
* @param {Object} options Available options: * @param {Object} options Available options:
* {ItemSet} parent
* {ItemSet} itemset
* {Number} margin * {Number} margin
* {function} order Stacking order * {function} order Stacking order
*/ */
Stack.prototype.setOptions = function setOptions (options) { Stack.prototype.setOptions = function setOptions (options) {
util.extend(this.options, 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 * @private
*/ */
Stack.prototype._order = function _order () { Stack.prototype._order = function _order () {
var items = this.parent.items;
var items = this.itemset.items;
if (!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 // TODO: store the sorted items, to have less work later on

+ 2
- 22
src/timeline/Timeline.js View File

@ -378,7 +378,7 @@ Timeline.prototype.getSelection = function getSelection() {
*/ */
// TODO: move this function to ItemSet // TODO: move this function to ItemSet
Timeline.prototype._onSelectItem = function (event) { Timeline.prototype._onSelectItem = function (event) {
var item = this._itemFromTarget(event);
var item = ItemSet.itemFromTarget(event);
var selection = item ? [item.id] : []; var selection = item ? [item.id] : [];
this.setSelection(selection); this.setSelection(selection);
@ -398,7 +398,7 @@ Timeline.prototype._onSelectItem = function (event) {
// TODO: move this function to ItemSet // TODO: move this function to ItemSet
Timeline.prototype._onMultiSelectItem = function (event) { Timeline.prototype._onMultiSelectItem = function (event) {
var selection, var selection,
item = this._itemFromTarget(event);
item = ItemSet.itemFromTarget(event);
if (!item) { if (!item) {
// do nothing... // do nothing...
@ -423,23 +423,3 @@ Timeline.prototype._onMultiSelectItem = function (event) {
event.stopPropagation(); 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
*/
// TODO: move this function to ItemSet
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;
};

+ 79
- 31
src/timeline/component/ItemSet.js View File

@ -17,7 +17,7 @@ function ItemSet(parent, depends, options) {
this.depends = depends; this.depends = depends;
// event listeners // event listeners
this.listeners = {
this.eventListeners = {
dragstart: this._onDragStart.bind(this), dragstart: this._onDragStart.bind(this),
drag: this._onDrag.bind(this), drag: this._onDrag.bind(this),
dragend: this._onDragEnd.bind(this) dragend: this._onDragEnd.bind(this)
@ -42,6 +42,7 @@ function ItemSet(parent, depends, options) {
this.itemsData = null; // DataSet this.itemsData = null; // DataSet
this.range = null; // Range or Object {start: number, end: number} this.range = null; // Range or Object {start: number, end: number}
// data change listeners
this.listeners = { this.listeners = {
'add': function (event, params, senderId) { 'add': function (event, params, senderId) {
if (senderId != me.id) { if (senderId != me.id) {
@ -66,6 +67,8 @@ function ItemSet(parent, depends, options) {
this.stack = new Stack(this, Object.create(this.options)); this.stack = new Stack(this, Object.create(this.options));
this.conversion = null; this.conversion = null;
this.touchParams = {}; // stores properties while dragging
// TODO: ItemSet should also attach event listeners for rangechange and rangechanged, like timeaxis // TODO: ItemSet should also attach event listeners for rangechange and rangechanged, like timeaxis
} }
@ -117,9 +120,9 @@ ItemSet.prototype.setController = function setController (controller) {
// unregister old event listeners // unregister old event listeners
if (this.controller) { if (this.controller) {
for (event in this.listeners) {
if (this.listeners.hasOwnProperty(event)) {
this.controller.off(event, this.listeners[event]);
for (event in this.eventListeners) {
if (this.eventListeners.hasOwnProperty(event)) {
this.controller.off(event, this.eventListeners[event]);
} }
} }
} }
@ -128,9 +131,9 @@ ItemSet.prototype.setController = function setController (controller) {
// register new event listeners // register new event listeners
if (this.controller) { if (this.controller) {
for (event in this.listeners) {
if (this.listeners.hasOwnProperty(event)) {
this.controller.on(event, this.listeners[event]);
for (event in this.eventListeners) {
if (this.eventListeners.hasOwnProperty(event)) {
this.controller.on(event, this.eventListeners[event]);
} }
} }
} }
@ -251,6 +254,7 @@ ItemSet.prototype.repaint = function repaint() {
if (!frame) { if (!frame) {
frame = document.createElement('div'); frame = document.createElement('div');
frame.className = 'itemset'; frame.className = 'itemset';
frame['timeline-itemset'] = this;
var className = options.className; var className = options.className;
if (className) { if (className) {
@ -667,25 +671,21 @@ ItemSet.prototype.toScreen = function toScreen(time) {
return (time.valueOf() - conversion.offset) * conversion.scale; return (time.valueOf() - conversion.offset) * conversion.scale;
}; };
// global (private) object to store drag params
var touchParams = {};
/** /**
* Start dragging the selected events * Start dragging the selected events
* @param {Event} event * @param {Event} event
* @private * @private
*/ */
// TODO: move this function to ItemSet
ItemSet.prototype._onDragStart = function (event) { ItemSet.prototype._onDragStart = function (event) {
var item = this._itemFromTarget(event);
console.log('_onDragStart', event)
var itemSet = ItemSet.itemSetFromTarget(event),
item = ItemSet.itemFromTarget(event),
me = this;
if (item && item.selected) { if (item && item.selected) {
touchParams.items = [item];
//touchParams.items = this.getSelection(); // TODO: use the current selection
touchParams.itemsLeft = touchParams.items.map(function (item) {
return item.left;
this.touchParams.items = this.getSelection().map(function (id) {
return me.items[id];
}); });
console.log('_onDragStart', touchParams)
event.stopPropagation(); event.stopPropagation();
} }
}; };
@ -695,16 +695,21 @@ console.log('_onDragStart', event)
* @param {Event} event * @param {Event} event
* @private * @private
*/ */
// TODO: move this function to ItemSet
ItemSet.prototype._onDrag = function (event) { ItemSet.prototype._onDrag = function (event) {
if (touchParams.items) {
if (this.touchParams.items) {
var deltaX = event.gesture.deltaX; var deltaX = event.gesture.deltaX;
touchParams.items.forEach(function (item, i) {
item.left = touchParams.itemsLeft[i] + deltaX;
item.reposition();
// 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(); event.stopPropagation();
} }
}; };
@ -714,12 +719,38 @@ ItemSet.prototype._onDrag = function (event) {
* @param {Event} event * @param {Event} event
* @private * @private
*/ */
// TODO: move this function to ItemSet
ItemSet.prototype._onDragEnd = function (event) { ItemSet.prototype._onDragEnd = function (event) {
if (touchParams.items) {
// actually apply the new locations
if (this.touchParams.items) {
var deltaX = event.gesture.deltaX,
scale = this.conversion.scale;
touchParams.items = null;
// 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(); event.stopPropagation();
} }
@ -729,10 +760,9 @@ ItemSet.prototype._onDragEnd = function (event) {
* Find an item from an event target: * Find an item from an event target:
* searches for the attribute 'timeline-item' in the event target's element tree * searches for the attribute 'timeline-item' in the event target's element tree
* @param {Event} event * @param {Event} event
* @return {Item | null| item
* @private
* @return {Item | null} item
*/ */
ItemSet.prototype._itemFromTarget = function _itemFromTarget (event) {
ItemSet.itemFromTarget = function itemFromTarget (event) {
var target = event.target; var target = event.target;
while (target) { while (target) {
if (target.hasOwnProperty('timeline-item')) { if (target.hasOwnProperty('timeline-item')) {
@ -742,4 +772,22 @@ ItemSet.prototype._itemFromTarget = function _itemFromTarget (event) {
} }
return null; 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;
};

+ 9
- 0
src/timeline/component/item/Item.js View File

@ -20,6 +20,7 @@ function Item (parent, data, options, defaultOptions) {
this.left = 0; this.left = 0;
this.width = 0; this.width = 0;
this.height = 0; this.height = 0;
this.offset = 0;
} }
/** /**
@ -72,6 +73,14 @@ Item.prototype.reflow = function reflow() {
return false; 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 the items width
* @return {Number} width * @return {Number} width

+ 1
- 1
src/timeline/component/item/ItemBox.js View File

@ -187,7 +187,7 @@ ItemBox.prototype.reflow = function reflow() {
update = util.updateProperty; update = util.updateProperty;
props = this.props; props = this.props;
options = this.options; 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; align = options.align || this.defaultOptions.align;
margin = options.margin && options.margin.axis || this.defaultOptions.margin.axis; margin = options.margin && options.margin.axis || this.defaultOptions.margin.axis;
orientation = options.orientation || this.defaultOptions.orientation; orientation = options.orientation || this.defaultOptions.orientation;

+ 1
- 1
src/timeline/component/item/ItemPoint.js View File

@ -157,7 +157,7 @@ ItemPoint.prototype.reflow = function reflow() {
options = this.options; options = this.options;
orientation = options.orientation || this.defaultOptions.orientation; orientation = options.orientation || this.defaultOptions.orientation;
margin = options.margin && options.margin.axis || this.defaultOptions.margin.axis; 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, 'width', dom.point.offsetWidth);
changed += update(this, 'height', dom.point.offsetHeight); changed += update(this, 'height', dom.point.offsetHeight);

+ 2
- 2
src/timeline/component/item/ItemRange.js View File

@ -157,8 +157,8 @@ ItemRange.prototype.reflow = function reflow() {
props = this.props; props = this.props;
options = this.options; options = this.options;
parent = this.parent; 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; update = util.updateProperty;
box = dom.box; box = dom.box;
parentWidth = parent.width; parentWidth = parent.width;

Loading…
Cancel
Save