vis-rails 0.0.4 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. checksums.yaml +5 -13
  2. data/lib/vis/rails/version.rb +1 -1
  3. data/vendor/assets/component/emitter.js +162 -0
  4. data/vendor/assets/javascripts/vis.js +1 -0
  5. data/vendor/assets/vis/DataSet.js +8 -2
  6. data/vendor/assets/vis/DataView.js +8 -4
  7. data/vendor/assets/vis/graph/Edge.js +210 -78
  8. data/vendor/assets/vis/graph/Graph.js +474 -652
  9. data/vendor/assets/vis/graph/Node.js +119 -82
  10. data/vendor/assets/vis/graph/css/graph-manipulation.css +128 -0
  11. data/vendor/assets/vis/graph/css/graph-navigation.css +62 -0
  12. data/vendor/assets/vis/graph/graphMixins/ClusterMixin.js +1141 -0
  13. data/vendor/assets/vis/graph/graphMixins/HierarchicalLayoutMixin.js +296 -0
  14. data/vendor/assets/vis/graph/graphMixins/ManipulationMixin.js +433 -0
  15. data/vendor/assets/vis/graph/graphMixins/MixinLoader.js +201 -0
  16. data/vendor/assets/vis/graph/graphMixins/NavigationMixin.js +173 -0
  17. data/vendor/assets/vis/graph/graphMixins/SectorsMixin.js +552 -0
  18. data/vendor/assets/vis/graph/graphMixins/SelectionMixin.js +558 -0
  19. data/vendor/assets/vis/graph/graphMixins/physics/BarnesHut.js +373 -0
  20. data/vendor/assets/vis/graph/graphMixins/physics/HierarchialRepulsion.js +64 -0
  21. data/vendor/assets/vis/graph/graphMixins/physics/PhysicsMixin.js +513 -0
  22. data/vendor/assets/vis/graph/graphMixins/physics/Repulsion.js +66 -0
  23. data/vendor/assets/vis/graph/img/acceptDeleteIcon.png +0 -0
  24. data/vendor/assets/vis/graph/img/addNodeIcon.png +0 -0
  25. data/vendor/assets/vis/graph/img/backIcon.png +0 -0
  26. data/vendor/assets/vis/graph/img/connectIcon.png +0 -0
  27. data/vendor/assets/vis/graph/img/cross.png +0 -0
  28. data/vendor/assets/vis/graph/img/cross2.png +0 -0
  29. data/vendor/assets/vis/graph/img/deleteIcon.png +0 -0
  30. data/vendor/assets/vis/graph/img/downArrow.png +0 -0
  31. data/vendor/assets/vis/graph/img/editIcon.png +0 -0
  32. data/vendor/assets/vis/graph/img/leftArrow.png +0 -0
  33. data/vendor/assets/vis/graph/img/rightArrow.png +0 -0
  34. data/vendor/assets/vis/graph/img/upArrow.png +0 -0
  35. data/vendor/assets/vis/module/exports.js +0 -2
  36. data/vendor/assets/vis/module/header.js +2 -2
  37. data/vendor/assets/vis/module/imports.js +1 -2
  38. data/vendor/assets/vis/timeline/Controller.js +56 -45
  39. data/vendor/assets/vis/timeline/Range.js +68 -62
  40. data/vendor/assets/vis/timeline/Stack.js +11 -13
  41. data/vendor/assets/vis/timeline/TimeStep.js +43 -38
  42. data/vendor/assets/vis/timeline/Timeline.js +215 -93
  43. data/vendor/assets/vis/timeline/component/Component.js +19 -3
  44. data/vendor/assets/vis/timeline/component/CurrentTime.js +1 -1
  45. data/vendor/assets/vis/timeline/component/CustomTime.js +39 -120
  46. data/vendor/assets/vis/timeline/component/GroupSet.js +35 -1
  47. data/vendor/assets/vis/timeline/component/ItemSet.js +272 -9
  48. data/vendor/assets/vis/timeline/component/RootPanel.js +59 -47
  49. data/vendor/assets/vis/timeline/component/TimeAxis.js +10 -0
  50. data/vendor/assets/vis/timeline/component/css/item.css +53 -22
  51. data/vendor/assets/vis/timeline/component/item/Item.js +40 -5
  52. data/vendor/assets/vis/timeline/component/item/ItemBox.js +3 -1
  53. data/vendor/assets/vis/timeline/component/item/ItemPoint.js +3 -1
  54. data/vendor/assets/vis/timeline/component/item/ItemRange.js +67 -3
  55. data/vendor/assets/vis/timeline/component/item/ItemRangeOverflow.js +37 -9
  56. data/vendor/assets/vis/timeline/img/delete.png +0 -0
  57. data/vendor/assets/vis/util.js +169 -30
  58. metadata +39 -12
@@ -20,7 +20,6 @@ function Component () {
20
20
  * set.
21
21
  * @param {Object} options Available parameters:
22
22
  * {String | function} [className]
23
- * {EventBus} [eventBus]
24
23
  * {String | Number | function} [left]
25
24
  * {String | Number | function} [top]
26
25
  * {String | Number | function} [width]
@@ -55,6 +54,23 @@ Component.prototype.getOption = function getOption(name) {
55
54
  return value;
56
55
  };
57
56
 
57
+ /**
58
+ * Set controller for this component, or remove current controller by passing
59
+ * null as parameter value.
60
+ * @param {Controller | null} controller
61
+ */
62
+ Component.prototype.setController = function setController (controller) {
63
+ this.controller = controller || null;
64
+ };
65
+
66
+ /**
67
+ * Get controller of this component
68
+ * @return {Controller} controller
69
+ */
70
+ Component.prototype.getController = function getController () {
71
+ return this.controller;
72
+ };
73
+
58
74
  /**
59
75
  * Get the container element of the component, which can be used by a child to
60
76
  * add its own widgets. Not all components do have a container for childs, in
@@ -126,7 +142,7 @@ Component.prototype.show = function show() {
126
142
  */
127
143
  Component.prototype.requestRepaint = function requestRepaint() {
128
144
  if (this.controller) {
129
- this.controller.requestRepaint();
145
+ this.controller.emit('request-repaint');
130
146
  }
131
147
  else {
132
148
  throw new Error('Cannot request a repaint: no controller configured');
@@ -139,7 +155,7 @@ Component.prototype.requestRepaint = function requestRepaint() {
139
155
  */
140
156
  Component.prototype.requestReflow = function requestReflow() {
141
157
  if (this.controller) {
142
- this.controller.requestReflow();
158
+ this.controller.emit('request-reflow');
143
159
  }
144
160
  else {
145
161
  throw new Error('Cannot request a reflow: no controller configured');
@@ -56,7 +56,7 @@ CurrentTime.prototype.repaint = function () {
56
56
  delete this.frame;
57
57
  }
58
58
 
59
- return;
59
+ return false;
60
60
  }
61
61
 
62
62
  if (!bar) {
@@ -19,12 +19,14 @@ function CustomTime (parent, depends, options) {
19
19
  showCustomTime: false
20
20
  };
21
21
 
22
- this.listeners = [];
23
22
  this.customTime = new Date();
23
+ this.eventParams = {}; // stores state parameters while dragging the bar
24
24
  }
25
25
 
26
26
  CustomTime.prototype = new Component();
27
27
 
28
+ Emitter(CustomTime.prototype);
29
+
28
30
  CustomTime.prototype.setOptions = Component.prototype.setOptions;
29
31
 
30
32
  /**
@@ -42,13 +44,13 @@ CustomTime.prototype.getContainer = function () {
42
44
  */
43
45
  CustomTime.prototype.repaint = function () {
44
46
  var bar = this.frame,
45
- parent = this.parent,
46
- parentContainer = parent.parent.getContainer();
47
+ parent = this.parent;
47
48
 
48
49
  if (!parent) {
49
50
  throw new Error('Cannot repaint bar: no parent attached');
50
51
  }
51
52
 
53
+ var parentContainer = parent.parent.getContainer();
52
54
  if (!parentContainer) {
53
55
  throw new Error('Cannot repaint bar: parent has no container element');
54
56
  }
@@ -59,7 +61,7 @@ CustomTime.prototype.repaint = function () {
59
61
  delete this.frame;
60
62
  }
61
63
 
62
- return;
64
+ return false;
63
65
  }
64
66
 
65
67
  if (!bar) {
@@ -81,7 +83,13 @@ CustomTime.prototype.repaint = function () {
81
83
 
82
84
  this.frame = bar;
83
85
 
84
- this.subscribe(this, 'movetime');
86
+ // attach event listeners
87
+ this.hammer = Hammer(bar, {
88
+ prevent_default: true
89
+ });
90
+ this.hammer.on('dragstart', this._onDragStart.bind(this));
91
+ this.hammer.on('drag', this._onDrag.bind(this));
92
+ this.hammer.on('dragend', this._onDragEnd.bind(this));
85
93
  }
86
94
 
87
95
  if (!parent.conversion) {
@@ -100,7 +108,7 @@ CustomTime.prototype.repaint = function () {
100
108
  * Set custom time.
101
109
  * @param {Date} time
102
110
  */
103
- CustomTime.prototype._setCustomTime = function(time) {
111
+ CustomTime.prototype.setCustomTime = function(time) {
104
112
  this.customTime = new Date(time.valueOf());
105
113
  this.repaint();
106
114
  };
@@ -109,147 +117,58 @@ CustomTime.prototype._setCustomTime = function(time) {
109
117
  * Retrieve the current custom time.
110
118
  * @return {Date} customTime
111
119
  */
112
- CustomTime.prototype._getCustomTime = function() {
120
+ CustomTime.prototype.getCustomTime = function() {
113
121
  return new Date(this.customTime.valueOf());
114
122
  };
115
123
 
116
- /**
117
- * Add listeners for mouse and touch events to the component
118
- * @param {Component} component
119
- */
120
- CustomTime.prototype.subscribe = function (component, event) {
121
- var me = this;
122
- var listener = {
123
- component: component,
124
- event: event,
125
- callback: function (event) {
126
- me._onMouseDown(event, listener);
127
- },
128
- params: {}
129
- };
130
-
131
- component.on('mousedown', listener.callback);
132
- me.listeners.push(listener);
133
-
134
- };
135
-
136
- /**
137
- * Event handler
138
- * @param {String} event name of the event, for example 'click', 'mousemove'
139
- * @param {function} callback callback handler, invoked with the raw HTML Event
140
- * as parameter.
141
- */
142
- CustomTime.prototype.on = function (event, callback) {
143
- var bar = this.frame;
144
- if (!bar) {
145
- throw new Error('Cannot add event listener: no parent attached');
146
- }
147
-
148
- events.addListener(this, event, callback);
149
- util.addEventListener(bar, event, callback);
150
- };
151
-
152
124
  /**
153
125
  * Start moving horizontally
154
126
  * @param {Event} event
155
- * @param {Object} listener Listener containing the component and params
156
127
  * @private
157
128
  */
158
- CustomTime.prototype._onMouseDown = function(event, listener) {
159
- event = event || window.event;
160
- var params = listener.params;
161
-
162
- // only react on left mouse button down
163
- var leftButtonDown = event.which ? (event.which == 1) : (event.button == 1);
164
- if (!leftButtonDown) {
165
- return;
166
- }
167
-
168
- // get mouse position
169
- params.mouseX = util.getPageX(event);
170
- params.moved = false;
171
-
172
- params.customTime = this.customTime;
173
-
174
- // add event listeners to handle moving the custom time bar
175
- var me = this;
176
- if (!params.onMouseMove) {
177
- params.onMouseMove = function (event) {
178
- me._onMouseMove(event, listener);
179
- };
180
- util.addEventListener(document, 'mousemove', params.onMouseMove);
181
- }
182
- if (!params.onMouseUp) {
183
- params.onMouseUp = function (event) {
184
- me._onMouseUp(event, listener);
185
- };
186
- util.addEventListener(document, 'mouseup', params.onMouseUp);
187
- }
129
+ CustomTime.prototype._onDragStart = function(event) {
130
+ this.eventParams.customTime = this.customTime;
188
131
 
189
- util.stopPropagation(event);
190
- util.preventDefault(event);
132
+ event.stopPropagation();
133
+ event.preventDefault();
191
134
  };
192
135
 
193
136
  /**
194
137
  * Perform moving operating.
195
- * This function activated from within the funcion CustomTime._onMouseDown().
196
138
  * @param {Event} event
197
- * @param {Object} listener
198
139
  * @private
199
140
  */
200
- CustomTime.prototype._onMouseMove = function (event, listener) {
201
- event = event || window.event;
202
- var params = listener.params;
203
- var parent = this.parent;
141
+ CustomTime.prototype._onDrag = function (event) {
142
+ var deltaX = event.gesture.deltaX,
143
+ x = this.parent.toScreen(this.eventParams.customTime) + deltaX,
144
+ time = this.parent.toTime(x);
204
145
 
205
- // calculate change in mouse position
206
- var mouseX = util.getPageX(event);
207
-
208
- if (params.mouseX === undefined) {
209
- params.mouseX = mouseX;
210
- }
211
-
212
- var diff = mouseX - params.mouseX;
213
-
214
- // if mouse movement is big enough, register it as a "moved" event
215
- if (Math.abs(diff) >= 1) {
216
- params.moved = true;
217
- }
218
-
219
- var x = parent.toScreen(params.customTime);
220
- var xnew = x + diff;
221
- var time = parent.toTime(xnew);
222
- this._setCustomTime(time);
146
+ this.setCustomTime(time);
223
147
 
224
148
  // fire a timechange event
225
- events.trigger(this, 'timechange', {customTime: this.customTime});
149
+ if (this.controller) {
150
+ this.controller.emit('timechange', {
151
+ time: this.customTime
152
+ })
153
+ }
226
154
 
227
- util.preventDefault(event);
155
+ event.stopPropagation();
156
+ event.preventDefault();
228
157
  };
229
158
 
230
159
  /**
231
160
  * Stop moving operating.
232
- * This function activated from within the function CustomTime._onMouseDown().
233
161
  * @param {event} event
234
- * @param {Object} listener
235
162
  * @private
236
163
  */
237
- CustomTime.prototype._onMouseUp = function (event, listener) {
238
- event = event || window.event;
239
- var params = listener.params;
240
-
241
- // remove event listeners here, important for Safari
242
- if (params.onMouseMove) {
243
- util.removeEventListener(document, 'mousemove', params.onMouseMove);
244
- params.onMouseMove = null;
245
- }
246
- if (params.onMouseUp) {
247
- util.removeEventListener(document, 'mouseup', params.onMouseUp);
248
- params.onMouseUp = null;
164
+ CustomTime.prototype._onDragEnd = function (event) {
165
+ // fire a timechanged event
166
+ if (this.controller) {
167
+ this.controller.emit('timechanged', {
168
+ time: this.customTime
169
+ })
249
170
  }
250
171
 
251
- if (params.moved) {
252
- // fire a timechanged event
253
- events.trigger(this, 'timechanged', {customTime: this.customTime});
254
- }
172
+ event.stopPropagation();
173
+ event.preventDefault();
255
174
  };
@@ -132,7 +132,7 @@ GroupSet.prototype.setGroups = function setGroups(groups) {
132
132
  // subscribe to new dataset
133
133
  var id = this.id;
134
134
  util.forEach(this.listeners, function (callback, event) {
135
- me.groupsData.subscribe(event, callback, id);
135
+ me.groupsData.on(event, callback, id);
136
136
  });
137
137
 
138
138
  // draw all new groups
@@ -216,6 +216,7 @@ GroupSet.prototype.repaint = function repaint() {
216
216
  if (!frame) {
217
217
  frame = document.createElement('div');
218
218
  frame.className = 'groupset';
219
+ frame['timeline-groupset'] = this;
219
220
  this.dom.frame = frame;
220
221
 
221
222
  var className = options.className;
@@ -544,3 +545,36 @@ GroupSet.prototype._toQueue = function _toQueue(ids, action) {
544
545
  this.requestRepaint();
545
546
  }
546
547
  };
548
+
549
+ /**
550
+ * Find the Group from an event target:
551
+ * searches for the attribute 'timeline-groupset' in the event target's element
552
+ * tree, then finds the right group in this groupset
553
+ * @param {Event} event
554
+ * @return {Group | null} group
555
+ */
556
+ GroupSet.groupFromTarget = function groupFromTarget (event) {
557
+ var groupset,
558
+ target = event.target;
559
+
560
+ while (target) {
561
+ if (target.hasOwnProperty('timeline-groupset')) {
562
+ groupset = target['timeline-groupset'];
563
+ break;
564
+ }
565
+ target = target.parentNode;
566
+ }
567
+
568
+ if (groupset) {
569
+ for (var groupId in groupset.groups) {
570
+ if (groupset.groups.hasOwnProperty(groupId)) {
571
+ var group = groupset.groups[groupId];
572
+ if (group.itemset && ItemSet.itemSetFromTarget(event) == group.itemset) {
573
+ return group;
574
+ }
575
+ }
576
+ }
577
+ }
578
+
579
+ return null;
580
+ };
@@ -16,6 +16,13 @@ function ItemSet(parent, depends, options) {
16
16
  this.parent = parent;
17
17
  this.depends = depends;
18
18
 
19
+ // event listeners
20
+ this.eventListeners = {
21
+ dragstart: this._onDragStart.bind(this),
22
+ drag: this._onDrag.bind(this),
23
+ dragend: this._onDragEnd.bind(this)
24
+ };
25
+
19
26
  // one options object is shared by this itemset and all its items
20
27
  this.options = options || {};
21
28
  this.defaultOptions = {
@@ -35,6 +42,7 @@ function ItemSet(parent, depends, options) {
35
42
  this.itemsData = null; // DataSet
36
43
  this.range = null; // Range or Object {start: number, end: number}
37
44
 
45
+ // data change listeners
38
46
  this.listeners = {
39
47
  'add': function (event, params, senderId) {
40
48
  if (senderId != me.id) {
@@ -59,6 +67,8 @@ function ItemSet(parent, depends, options) {
59
67
  this.stack = new Stack(this, Object.create(this.options));
60
68
  this.conversion = null;
61
69
 
70
+ this.touchParams = {}; // stores properties while dragging
71
+
62
72
  // TODO: ItemSet should also attach event listeners for rangechange and rangechanged, like timeaxis
63
73
  }
64
74
 
@@ -96,9 +106,61 @@ ItemSet.types = {
96
106
  * {Number} padding
97
107
  * Padding of the contents of an item in pixels.
98
108
  * Must correspond with the items css. Default is 5.
109
+ * {Function} snap
110
+ * Function to let items snap to nice dates when
111
+ * dragging items.
99
112
  */
100
113
  ItemSet.prototype.setOptions = Component.prototype.setOptions;
101
114
 
115
+
116
+
117
+ /**
118
+ * Set controller for this component
119
+ * @param {Controller | null} controller
120
+ */
121
+ ItemSet.prototype.setController = function setController (controller) {
122
+ var event;
123
+
124
+ // unregister old event listeners
125
+ if (this.controller) {
126
+ for (event in this.eventListeners) {
127
+ if (this.eventListeners.hasOwnProperty(event)) {
128
+ this.controller.off(event, this.eventListeners[event]);
129
+ }
130
+ }
131
+ }
132
+
133
+ this.controller = controller || null;
134
+
135
+ // register new event listeners
136
+ if (this.controller) {
137
+ for (event in this.eventListeners) {
138
+ if (this.eventListeners.hasOwnProperty(event)) {
139
+ this.controller.on(event, this.eventListeners[event]);
140
+ }
141
+ }
142
+ }
143
+ };
144
+
145
+ // attach event listeners for dragging items to the controller
146
+ (function (me) {
147
+ var _controller = null;
148
+ var _onDragStart = null;
149
+ var _onDrag = null;
150
+ var _onDragEnd = null;
151
+
152
+ Object.defineProperty(me, 'controller', {
153
+ get: function () {
154
+ return _controller;
155
+ },
156
+
157
+ set: function (controller) {
158
+
159
+ }
160
+ });
161
+ }) (this);
162
+
163
+
102
164
  /**
103
165
  * Set range (start and end).
104
166
  * @param {Range | Object} range A Range or an object containing start and end.
@@ -144,12 +206,6 @@ ItemSet.prototype.setSelection = function setSelection(ids) {
144
206
  }
145
207
  }
146
208
 
147
- // trigger a select event
148
- selection = this.selection.concat([]);
149
- events.trigger(this, 'select', {
150
- ids: selection
151
- });
152
-
153
209
  if (this.controller) {
154
210
  this.requestRepaint();
155
211
  }
@@ -195,6 +251,7 @@ ItemSet.prototype.repaint = function repaint() {
195
251
  if (!frame) {
196
252
  frame = document.createElement('div');
197
253
  frame.className = 'itemset';
254
+ frame['timeline-itemset'] = this;
198
255
 
199
256
  var className = options.className;
200
257
  if (className) {
@@ -389,8 +446,8 @@ ItemSet.prototype.getAxis = function getAxis() {
389
446
  ItemSet.prototype.reflow = function reflow () {
390
447
  var changed = 0,
391
448
  options = this.options,
392
- marginAxis = options.margin && options.margin.axis || this.defaultOptions.margin.axis,
393
- marginItem = options.margin && options.margin.item || this.defaultOptions.margin.item,
449
+ marginAxis = (options.margin && 'axis' in options.margin) ? options.margin.axis : this.defaultOptions.margin.axis,
450
+ marginItem = (options.margin && 'item' in options.margin) ? options.margin.item : this.defaultOptions.margin.item,
394
451
  update = util.updateProperty,
395
452
  asNumber = util.option.asNumber,
396
453
  asSize = util.option.asSize,
@@ -501,7 +558,7 @@ ItemSet.prototype.setItems = function setItems(items) {
501
558
  // subscribe to new dataset
502
559
  var id = this.id;
503
560
  util.forEach(this.listeners, function (callback, event) {
504
- me.itemsData.subscribe(event, callback, id);
561
+ me.itemsData.on(event, callback, id);
505
562
  });
506
563
 
507
564
  // draw all new items
@@ -518,6 +575,24 @@ ItemSet.prototype.getItems = function getItems() {
518
575
  return this.itemsData;
519
576
  };
520
577
 
578
+ /**
579
+ * Remove an item by its id
580
+ * @param {String | Number} id
581
+ */
582
+ ItemSet.prototype.removeItem = function removeItem (id) {
583
+ var item = this.itemsData.get(id),
584
+ dataset = this._myDataSet();
585
+
586
+ if (item) {
587
+ // confirm deletion
588
+ this.options.onRemove(item, function (item) {
589
+ if (item) {
590
+ dataset.remove(item);
591
+ }
592
+ });
593
+ }
594
+ };
595
+
521
596
  /**
522
597
  * Handle updated items
523
598
  * @param {Number[]} ids
@@ -610,3 +685,191 @@ ItemSet.prototype.toScreen = function toScreen(time) {
610
685
  var conversion = this.conversion;
611
686
  return (time.valueOf() - conversion.offset) * conversion.scale;
612
687
  };
688
+
689
+ /**
690
+ * Start dragging the selected events
691
+ * @param {Event} event
692
+ * @private
693
+ */
694
+ ItemSet.prototype._onDragStart = function (event) {
695
+ if (!this.options.editable) {
696
+ return;
697
+ }
698
+
699
+ var item = ItemSet.itemFromTarget(event),
700
+ me = this;
701
+
702
+ if (item && item.selected) {
703
+ var dragLeftItem = event.target.dragLeftItem;
704
+ var dragRightItem = event.target.dragRightItem;
705
+
706
+ if (dragLeftItem) {
707
+ this.touchParams.itemProps = [{
708
+ item: dragLeftItem,
709
+ start: item.data.start.valueOf()
710
+ }];
711
+ }
712
+ else if (dragRightItem) {
713
+ this.touchParams.itemProps = [{
714
+ item: dragRightItem,
715
+ end: item.data.end.valueOf()
716
+ }];
717
+ }
718
+ else {
719
+ this.touchParams.itemProps = this.getSelection().map(function (id) {
720
+ var item = me.items[id];
721
+ var props = {
722
+ item: item
723
+ };
724
+
725
+ if ('start' in item.data) {
726
+ props.start = item.data.start.valueOf()
727
+ }
728
+ if ('end' in item.data) {
729
+ props.end = item.data.end.valueOf()
730
+ }
731
+
732
+ return props;
733
+ });
734
+ }
735
+
736
+ event.stopPropagation();
737
+ }
738
+ };
739
+
740
+ /**
741
+ * Drag selected items
742
+ * @param {Event} event
743
+ * @private
744
+ */
745
+ ItemSet.prototype._onDrag = function (event) {
746
+ if (this.touchParams.itemProps) {
747
+ var snap = this.options.snap || null,
748
+ deltaX = event.gesture.deltaX,
749
+ offset = deltaX / this.conversion.scale;
750
+
751
+ // move
752
+ this.touchParams.itemProps.forEach(function (props) {
753
+ if ('start' in props) {
754
+ var start = new Date(props.start + offset);
755
+ props.item.data.start = snap ? snap(start) : start;
756
+ }
757
+ if ('end' in props) {
758
+ var end = new Date(props.end + offset);
759
+ props.item.data.end = snap ? snap(end) : end;
760
+ }
761
+ });
762
+
763
+ // TODO: implement onMoving handler
764
+
765
+ // TODO: implement dragging from one group to another
766
+
767
+ this.requestReflow();
768
+
769
+ event.stopPropagation();
770
+ }
771
+ };
772
+
773
+ /**
774
+ * End of dragging selected items
775
+ * @param {Event} event
776
+ * @private
777
+ */
778
+ ItemSet.prototype._onDragEnd = function (event) {
779
+ if (this.touchParams.itemProps) {
780
+ // prepare a change set for the changed items
781
+ var changes = [],
782
+ me = this,
783
+ dataset = this._myDataSet(),
784
+ type;
785
+
786
+ this.touchParams.itemProps.forEach(function (props) {
787
+ var id = props.item.id,
788
+ item = me.itemsData.get(id);
789
+
790
+ var changed = false;
791
+ if ('start' in props.item.data) {
792
+ changed = (props.start != props.item.data.start.valueOf());
793
+ item.start = util.convert(props.item.data.start, dataset.convert['start']);
794
+ }
795
+ if ('end' in props.item.data) {
796
+ changed = changed || (props.end != props.item.data.end.valueOf());
797
+ item.end = util.convert(props.item.data.end, dataset.convert['end']);
798
+ }
799
+
800
+ // only apply changes when start or end is actually changed
801
+ if (changed) {
802
+ me.options.onMove(item, function (item) {
803
+ if (item) {
804
+ // apply changes
805
+ changes.push(item);
806
+ }
807
+ else {
808
+ // restore original values
809
+ if ('start' in props) props.item.data.start = props.start;
810
+ if ('end' in props) props.item.data.end = props.end;
811
+ me.requestReflow();
812
+ }
813
+ });
814
+ }
815
+ });
816
+ this.touchParams.itemProps = null;
817
+
818
+ // apply the changes to the data (if there are changes)
819
+ if (changes.length) {
820
+ dataset.update(changes);
821
+ }
822
+
823
+ event.stopPropagation();
824
+ }
825
+ };
826
+
827
+ /**
828
+ * Find an item from an event target:
829
+ * searches for the attribute 'timeline-item' in the event target's element tree
830
+ * @param {Event} event
831
+ * @return {Item | null} item
832
+ */
833
+ ItemSet.itemFromTarget = function itemFromTarget (event) {
834
+ var target = event.target;
835
+ while (target) {
836
+ if (target.hasOwnProperty('timeline-item')) {
837
+ return target['timeline-item'];
838
+ }
839
+ target = target.parentNode;
840
+ }
841
+
842
+ return null;
843
+ };
844
+
845
+ /**
846
+ * Find the ItemSet from an event target:
847
+ * searches for the attribute 'timeline-itemset' in the event target's element tree
848
+ * @param {Event} event
849
+ * @return {ItemSet | null} item
850
+ */
851
+ ItemSet.itemSetFromTarget = function itemSetFromTarget (event) {
852
+ var target = event.target;
853
+ while (target) {
854
+ if (target.hasOwnProperty('timeline-itemset')) {
855
+ return target['timeline-itemset'];
856
+ }
857
+ target = target.parentNode;
858
+ }
859
+
860
+ return null;
861
+ };
862
+
863
+ /**
864
+ * Find the DataSet to which this ItemSet is connected
865
+ * @returns {null | DataSet} dataset
866
+ * @private
867
+ */
868
+ ItemSet.prototype._myDataSet = function _myDataSet() {
869
+ // find the root DataSet
870
+ var dataset = this.itemsData;
871
+ while (dataset instanceof DataView) {
872
+ dataset = dataset.data;
873
+ }
874
+ return dataset;
875
+ };