vis-rails 2.0.0 → 2.0.1

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 (73) hide show
  1. checksums.yaml +4 -4
  2. data/lib/vis/rails/version.rb +1 -1
  3. data/vendor/assets/javascripts/vis.js +26 -26
  4. metadata +16 -85
  5. data/vendor/assets/vis/DataSet.js +0 -926
  6. data/vendor/assets/vis/DataView.js +0 -283
  7. data/vendor/assets/vis/graph/Edge.js +0 -957
  8. data/vendor/assets/vis/graph/Graph.js +0 -2291
  9. data/vendor/assets/vis/graph/Groups.js +0 -80
  10. data/vendor/assets/vis/graph/Images.js +0 -41
  11. data/vendor/assets/vis/graph/Node.js +0 -966
  12. data/vendor/assets/vis/graph/Popup.js +0 -132
  13. data/vendor/assets/vis/graph/css/graph-manipulation.css +0 -128
  14. data/vendor/assets/vis/graph/css/graph-navigation.css +0 -66
  15. data/vendor/assets/vis/graph/dotparser.js +0 -829
  16. data/vendor/assets/vis/graph/graphMixins/ClusterMixin.js +0 -1143
  17. data/vendor/assets/vis/graph/graphMixins/HierarchicalLayoutMixin.js +0 -311
  18. data/vendor/assets/vis/graph/graphMixins/ManipulationMixin.js +0 -576
  19. data/vendor/assets/vis/graph/graphMixins/MixinLoader.js +0 -199
  20. data/vendor/assets/vis/graph/graphMixins/NavigationMixin.js +0 -205
  21. data/vendor/assets/vis/graph/graphMixins/SectorsMixin.js +0 -552
  22. data/vendor/assets/vis/graph/graphMixins/SelectionMixin.js +0 -648
  23. data/vendor/assets/vis/graph/graphMixins/physics/BarnesHut.js +0 -398
  24. data/vendor/assets/vis/graph/graphMixins/physics/HierarchialRepulsion.js +0 -64
  25. data/vendor/assets/vis/graph/graphMixins/physics/PhysicsMixin.js +0 -697
  26. data/vendor/assets/vis/graph/graphMixins/physics/Repulsion.js +0 -66
  27. data/vendor/assets/vis/graph/img/acceptDeleteIcon.png +0 -0
  28. data/vendor/assets/vis/graph/img/addNodeIcon.png +0 -0
  29. data/vendor/assets/vis/graph/img/backIcon.png +0 -0
  30. data/vendor/assets/vis/graph/img/connectIcon.png +0 -0
  31. data/vendor/assets/vis/graph/img/cross.png +0 -0
  32. data/vendor/assets/vis/graph/img/cross2.png +0 -0
  33. data/vendor/assets/vis/graph/img/deleteIcon.png +0 -0
  34. data/vendor/assets/vis/graph/img/downArrow.png +0 -0
  35. data/vendor/assets/vis/graph/img/editIcon.png +0 -0
  36. data/vendor/assets/vis/graph/img/leftArrow.png +0 -0
  37. data/vendor/assets/vis/graph/img/minus.png +0 -0
  38. data/vendor/assets/vis/graph/img/plus.png +0 -0
  39. data/vendor/assets/vis/graph/img/rightArrow.png +0 -0
  40. data/vendor/assets/vis/graph/img/upArrow.png +0 -0
  41. data/vendor/assets/vis/graph/img/zoomExtends.png +0 -0
  42. data/vendor/assets/vis/graph/shapes.js +0 -225
  43. data/vendor/assets/vis/graph3d/Graph3d.js +0 -3306
  44. data/vendor/assets/vis/module/exports.js +0 -65
  45. data/vendor/assets/vis/module/header.js +0 -24
  46. data/vendor/assets/vis/module/imports.js +0 -31
  47. data/vendor/assets/vis/shim.js +0 -252
  48. data/vendor/assets/vis/timeline/Range.js +0 -532
  49. data/vendor/assets/vis/timeline/TimeStep.js +0 -466
  50. data/vendor/assets/vis/timeline/Timeline.js +0 -851
  51. data/vendor/assets/vis/timeline/component/Component.js +0 -52
  52. data/vendor/assets/vis/timeline/component/CurrentTime.js +0 -128
  53. data/vendor/assets/vis/timeline/component/CustomTime.js +0 -182
  54. data/vendor/assets/vis/timeline/component/Group.js +0 -470
  55. data/vendor/assets/vis/timeline/component/ItemSet.js +0 -1332
  56. data/vendor/assets/vis/timeline/component/TimeAxis.js +0 -389
  57. data/vendor/assets/vis/timeline/component/css/animation.css +0 -33
  58. data/vendor/assets/vis/timeline/component/css/currenttime.css +0 -5
  59. data/vendor/assets/vis/timeline/component/css/customtime.css +0 -6
  60. data/vendor/assets/vis/timeline/component/css/item.css +0 -107
  61. data/vendor/assets/vis/timeline/component/css/itemset.css +0 -33
  62. data/vendor/assets/vis/timeline/component/css/labelset.css +0 -36
  63. data/vendor/assets/vis/timeline/component/css/panel.css +0 -71
  64. data/vendor/assets/vis/timeline/component/css/timeaxis.css +0 -48
  65. data/vendor/assets/vis/timeline/component/css/timeline.css +0 -2
  66. data/vendor/assets/vis/timeline/component/item/Item.js +0 -139
  67. data/vendor/assets/vis/timeline/component/item/ItemBox.js +0 -230
  68. data/vendor/assets/vis/timeline/component/item/ItemPoint.js +0 -190
  69. data/vendor/assets/vis/timeline/component/item/ItemRange.js +0 -262
  70. data/vendor/assets/vis/timeline/component/item/ItemRangeOverflow.js +0 -57
  71. data/vendor/assets/vis/timeline/img/delete.png +0 -0
  72. data/vendor/assets/vis/timeline/stack.js +0 -112
  73. data/vendor/assets/vis/util.js +0 -990
@@ -1,1332 +0,0 @@
1
- var UNGROUPED = '__ungrouped__'; // reserved group id for ungrouped items
2
-
3
- /**
4
- * An ItemSet holds a set of items and ranges which can be displayed in a
5
- * range. The width is determined by the parent of the ItemSet, and the height
6
- * is determined by the size of the items.
7
- * @param {{dom: Object, domProps: Object, emitter: Emitter, range: Range}} body
8
- * @param {Object} [options] See ItemSet.setOptions for the available options.
9
- * @constructor ItemSet
10
- * @extends Component
11
- */
12
- function ItemSet(body, options) {
13
- this.body = body;
14
-
15
- this.defaultOptions = {
16
- type: 'box',
17
- orientation: 'bottom', // 'top' or 'bottom'
18
- align: 'center', // alignment of box items
19
- stack: true,
20
- groupOrder: null,
21
-
22
- selectable: true,
23
- editable: {
24
- updateTime: false,
25
- updateGroup: false,
26
- add: false,
27
- remove: false
28
- },
29
-
30
- onAdd: function (item, callback) {
31
- callback(item);
32
- },
33
- onUpdate: function (item, callback) {
34
- callback(item);
35
- },
36
- onMove: function (item, callback) {
37
- callback(item);
38
- },
39
- onRemove: function (item, callback) {
40
- callback(item);
41
- },
42
-
43
- margin: {
44
- item: 10,
45
- axis: 20
46
- },
47
- padding: 5
48
- };
49
-
50
- // options is shared by this ItemSet and all its items
51
- this.options = util.extend({}, this.defaultOptions);
52
-
53
- // options for getting items from the DataSet with the correct type
54
- this.itemOptions = {
55
- type: {start: 'Date', end: 'Date'}
56
- };
57
-
58
- this.conversion = {
59
- toScreen: body.util.toScreen,
60
- toTime: body.util.toTime
61
- };
62
- this.dom = {};
63
- this.props = {};
64
- this.hammer = null;
65
-
66
- var me = this;
67
- this.itemsData = null; // DataSet
68
- this.groupsData = null; // DataSet
69
-
70
- // listeners for the DataSet of the items
71
- this.itemListeners = {
72
- 'add': function (event, params, senderId) {
73
- me._onAdd(params.items);
74
- },
75
- 'update': function (event, params, senderId) {
76
- me._onUpdate(params.items);
77
- },
78
- 'remove': function (event, params, senderId) {
79
- me._onRemove(params.items);
80
- }
81
- };
82
-
83
- // listeners for the DataSet of the groups
84
- this.groupListeners = {
85
- 'add': function (event, params, senderId) {
86
- me._onAddGroups(params.items);
87
- },
88
- 'update': function (event, params, senderId) {
89
- me._onUpdateGroups(params.items);
90
- },
91
- 'remove': function (event, params, senderId) {
92
- me._onRemoveGroups(params.items);
93
- }
94
- };
95
-
96
- this.items = {}; // object with an Item for every data item
97
- this.groups = {}; // Group object for every group
98
- this.groupIds = [];
99
-
100
- this.selection = []; // list with the ids of all selected nodes
101
- this.stackDirty = true; // if true, all items will be restacked on next redraw
102
-
103
- this.touchParams = {}; // stores properties while dragging
104
- // create the HTML DOM
105
-
106
- this._create();
107
-
108
- this.setOptions(options);
109
- }
110
-
111
- ItemSet.prototype = new Component();
112
-
113
- // available item types will be registered here
114
- ItemSet.types = {
115
- box: ItemBox,
116
- range: ItemRange,
117
- rangeoverflow: ItemRangeOverflow,
118
- point: ItemPoint
119
- };
120
-
121
- /**
122
- * Create the HTML DOM for the ItemSet
123
- */
124
- ItemSet.prototype._create = function(){
125
- var frame = document.createElement('div');
126
- frame.className = 'itemset';
127
- frame['timeline-itemset'] = this;
128
- this.dom.frame = frame;
129
-
130
- // create background panel
131
- var background = document.createElement('div');
132
- background.className = 'background';
133
- frame.appendChild(background);
134
- this.dom.background = background;
135
-
136
- // create foreground panel
137
- var foreground = document.createElement('div');
138
- foreground.className = 'foreground';
139
- frame.appendChild(foreground);
140
- this.dom.foreground = foreground;
141
-
142
- // create axis panel
143
- var axis = document.createElement('div');
144
- axis.className = 'axis';
145
- this.dom.axis = axis;
146
-
147
- // create labelset
148
- var labelSet = document.createElement('div');
149
- labelSet.className = 'labelset';
150
- this.dom.labelSet = labelSet;
151
-
152
- // create ungrouped Group
153
- this._updateUngrouped();
154
-
155
- // attach event listeners
156
- // Note: we bind to the centerContainer for the case where the height
157
- // of the center container is larger than of the ItemSet, so we
158
- // can click in the empty area to create a new item or deselect an item.
159
- this.hammer = Hammer(this.body.dom.centerContainer, {
160
- prevent_default: true
161
- });
162
-
163
- // drag items when selected
164
- this.hammer.on('touch', this._onTouch.bind(this));
165
- this.hammer.on('dragstart', this._onDragStart.bind(this));
166
- this.hammer.on('drag', this._onDrag.bind(this));
167
- this.hammer.on('dragend', this._onDragEnd.bind(this));
168
-
169
- // single select (or unselect) when tapping an item
170
- this.hammer.on('tap', this._onSelectItem.bind(this));
171
-
172
- // multi select when holding mouse/touch, or on ctrl+click
173
- this.hammer.on('hold', this._onMultiSelectItem.bind(this));
174
-
175
- // add item on doubletap
176
- this.hammer.on('doubletap', this._onAddItem.bind(this));
177
-
178
- // attach to the DOM
179
- this.show();
180
- };
181
-
182
- /**
183
- * Set options for the ItemSet. Existing options will be extended/overwritten.
184
- * @param {Object} [options] The following options are available:
185
- * {String} type
186
- * Default type for the items. Choose from 'box'
187
- * (default), 'point', or 'range'. The default
188
- * Style can be overwritten by individual items.
189
- * {String} align
190
- * Alignment for the items, only applicable for
191
- * ItemBox. Choose 'center' (default), 'left', or
192
- * 'right'.
193
- * {String} orientation
194
- * Orientation of the item set. Choose 'top' or
195
- * 'bottom' (default).
196
- * {Function} groupOrder
197
- * A sorting function for ordering groups
198
- * {Boolean} stack
199
- * If true (deafult), items will be stacked on
200
- * top of each other.
201
- * {Number} margin.axis
202
- * Margin between the axis and the items in pixels.
203
- * Default is 20.
204
- * {Number} margin.item
205
- * Margin between items in pixels. Default is 10.
206
- * {Number} margin
207
- * Set margin for both axis and items in pixels.
208
- * {Number} padding
209
- * Padding of the contents of an item in pixels.
210
- * Must correspond with the items css. Default is 5.
211
- * {Boolean} selectable
212
- * If true (default), items can be selected.
213
- * {Boolean} editable
214
- * Set all editable options to true or false
215
- * {Boolean} editable.updateTime
216
- * Allow dragging an item to an other moment in time
217
- * {Boolean} editable.updateGroup
218
- * Allow dragging an item to an other group
219
- * {Boolean} editable.add
220
- * Allow creating new items on double tap
221
- * {Boolean} editable.remove
222
- * Allow removing items by clicking the delete button
223
- * top right of a selected item.
224
- * {Function(item: Item, callback: Function)} onAdd
225
- * Callback function triggered when an item is about to be added:
226
- * when the user double taps an empty space in the Timeline.
227
- * {Function(item: Item, callback: Function)} onUpdate
228
- * Callback function fired when an item is about to be updated.
229
- * This function typically has to show a dialog where the user
230
- * change the item. If not implemented, nothing happens.
231
- * {Function(item: Item, callback: Function)} onMove
232
- * Fired when an item has been moved. If not implemented,
233
- * the move action will be accepted.
234
- * {Function(item: Item, callback: Function)} onRemove
235
- * Fired when an item is about to be deleted.
236
- * If not implemented, the item will be always removed.
237
- */
238
- ItemSet.prototype.setOptions = function(options) {
239
- if (options) {
240
- // copy all options that we know
241
- var fields = ['type', 'align', 'orientation', 'padding', 'stack', 'selectable', 'groupOrder'];
242
- util.selectiveExtend(fields, this.options, options);
243
-
244
- if ('margin' in options) {
245
- if (typeof options.margin === 'number') {
246
- this.options.margin.axis = options.margin;
247
- this.options.margin.item = options.margin;
248
- }
249
- else if (typeof options.margin === 'object'){
250
- util.selectiveExtend(['axis', 'item'], this.options.margin, options.margin);
251
- }
252
- }
253
-
254
- if ('editable' in options) {
255
- if (typeof options.editable === 'boolean') {
256
- this.options.editable.updateTime = options.editable;
257
- this.options.editable.updateGroup = options.editable;
258
- this.options.editable.add = options.editable;
259
- this.options.editable.remove = options.editable;
260
- }
261
- else if (typeof options.editable === 'object') {
262
- util.selectiveExtend(['updateTime', 'updateGroup', 'add', 'remove'], this.options.editable, options.editable);
263
- }
264
- }
265
-
266
- // callback functions
267
- var addCallback = (function (name) {
268
- if (name in options) {
269
- var fn = options[name];
270
- if (!(fn instanceof Function) || fn.length != 2) {
271
- throw new Error('option ' + name + ' must be a function ' + name + '(item, callback)');
272
- }
273
- this.options[name] = fn;
274
- }
275
- }).bind(this);
276
- ['onAdd', 'onUpdate', 'onRemove', 'onMove'].forEach(addCallback);
277
-
278
- // force the itemSet to refresh: options like orientation and margins may be changed
279
- this.markDirty();
280
- }
281
- };
282
-
283
- /**
284
- * Mark the ItemSet dirty so it will refresh everything with next redraw
285
- */
286
- ItemSet.prototype.markDirty = function() {
287
- this.groupIds = [];
288
- this.stackDirty = true;
289
- };
290
-
291
- /**
292
- * Destroy the ItemSet
293
- */
294
- ItemSet.prototype.destroy = function() {
295
- this.hide();
296
- this.setItems(null);
297
- this.setGroups(null);
298
-
299
- this.hammer = null;
300
-
301
- this.body = null;
302
- this.conversion = null;
303
- };
304
-
305
- /**
306
- * Hide the component from the DOM
307
- */
308
- ItemSet.prototype.hide = function() {
309
- // remove the frame containing the items
310
- if (this.dom.frame.parentNode) {
311
- this.dom.frame.parentNode.removeChild(this.dom.frame);
312
- }
313
-
314
- // remove the axis with dots
315
- if (this.dom.axis.parentNode) {
316
- this.dom.axis.parentNode.removeChild(this.dom.axis);
317
- }
318
-
319
- // remove the labelset containing all group labels
320
- if (this.dom.labelSet.parentNode) {
321
- this.dom.labelSet.parentNode.removeChild(this.dom.labelSet);
322
- }
323
- };
324
-
325
- /**
326
- * Show the component in the DOM (when not already visible).
327
- * @return {Boolean} changed
328
- */
329
- ItemSet.prototype.show = function() {
330
- // show frame containing the items
331
- if (!this.dom.frame.parentNode) {
332
- this.body.dom.center.appendChild(this.dom.frame);
333
- }
334
-
335
- // show axis with dots
336
- if (!this.dom.axis.parentNode) {
337
- this.body.dom.backgroundVertical.appendChild(this.dom.axis);
338
- }
339
-
340
- // show labelset containing labels
341
- if (!this.dom.labelSet.parentNode) {
342
- this.body.dom.left.appendChild(this.dom.labelSet);
343
- }
344
- };
345
-
346
- /**
347
- * Set selected items by their id. Replaces the current selection
348
- * Unknown id's are silently ignored.
349
- * @param {Array} [ids] An array with zero or more id's of the items to be
350
- * selected. If ids is an empty array, all items will be
351
- * unselected.
352
- */
353
- ItemSet.prototype.setSelection = function(ids) {
354
- var i, ii, id, item;
355
-
356
- if (ids) {
357
- if (!Array.isArray(ids)) {
358
- throw new TypeError('Array expected');
359
- }
360
-
361
- // unselect currently selected items
362
- for (i = 0, ii = this.selection.length; i < ii; i++) {
363
- id = this.selection[i];
364
- item = this.items[id];
365
- if (item) item.unselect();
366
- }
367
-
368
- // select items
369
- this.selection = [];
370
- for (i = 0, ii = ids.length; i < ii; i++) {
371
- id = ids[i];
372
- item = this.items[id];
373
- if (item) {
374
- this.selection.push(id);
375
- item.select();
376
- }
377
- }
378
- }
379
- };
380
-
381
- /**
382
- * Get the selected items by their id
383
- * @return {Array} ids The ids of the selected items
384
- */
385
- ItemSet.prototype.getSelection = function() {
386
- return this.selection.concat([]);
387
- };
388
-
389
- /**
390
- * Deselect a selected item
391
- * @param {String | Number} id
392
- * @private
393
- */
394
- ItemSet.prototype._deselect = function(id) {
395
- var selection = this.selection;
396
- for (var i = 0, ii = selection.length; i < ii; i++) {
397
- if (selection[i] == id) { // non-strict comparison!
398
- selection.splice(i, 1);
399
- break;
400
- }
401
- }
402
- };
403
-
404
- /**
405
- * Repaint the component
406
- * @return {boolean} Returns true if the component is resized
407
- */
408
- ItemSet.prototype.redraw = function() {
409
- var margin = this.options.margin,
410
- range = this.body.range,
411
- asSize = util.option.asSize,
412
- options = this.options,
413
- orientation = options.orientation,
414
- resized = false,
415
- frame = this.dom.frame,
416
- editable = options.editable.updateTime || options.editable.updateGroup;
417
-
418
- // update class name
419
- frame.className = 'itemset' + (editable ? ' editable' : '');
420
-
421
- // reorder the groups (if needed)
422
- resized = this._orderGroups() || resized;
423
-
424
- // check whether zoomed (in that case we need to re-stack everything)
425
- // TODO: would be nicer to get this as a trigger from Range
426
- var visibleInterval = range.end - range.start;
427
- var zoomed = (visibleInterval != this.lastVisibleInterval) || (this.props.width != this.props.lastWidth);
428
- if (zoomed) this.stackDirty = true;
429
- this.lastVisibleInterval = visibleInterval;
430
- this.props.lastWidth = this.props.width;
431
-
432
- // redraw all groups
433
- var restack = this.stackDirty,
434
- firstGroup = this._firstGroup(),
435
- firstMargin = {
436
- item: margin.item,
437
- axis: margin.axis
438
- },
439
- nonFirstMargin = {
440
- item: margin.item,
441
- axis: margin.item / 2
442
- },
443
- height = 0,
444
- minHeight = margin.axis + margin.item;
445
- util.forEach(this.groups, function (group) {
446
- var groupMargin = (group == firstGroup) ? firstMargin : nonFirstMargin;
447
- var groupResized = group.redraw(range, groupMargin, restack);
448
- resized = groupResized || resized;
449
- height += group.height;
450
- });
451
- height = Math.max(height, minHeight);
452
- this.stackDirty = false;
453
-
454
- // update frame height
455
- frame.style.height = asSize(height);
456
-
457
- // calculate actual size and position
458
- this.props.top = frame.offsetTop;
459
- this.props.left = frame.offsetLeft;
460
- this.props.width = frame.offsetWidth;
461
- this.props.height = height;
462
-
463
- // reposition axis
464
- this.dom.axis.style.top = asSize((orientation == 'top') ?
465
- (this.body.domProps.top.height + this.body.domProps.border.top) :
466
- (this.body.domProps.top.height + this.body.domProps.centerContainer.height));
467
- this.dom.axis.style.left = this.body.domProps.border.left + 'px';
468
-
469
- // check if this component is resized
470
- resized = this._isResized() || resized;
471
-
472
- return resized;
473
- };
474
-
475
- /**
476
- * Get the first group, aligned with the axis
477
- * @return {Group | null} firstGroup
478
- * @private
479
- */
480
- ItemSet.prototype._firstGroup = function() {
481
- var firstGroupIndex = (this.options.orientation == 'top') ? 0 : (this.groupIds.length - 1);
482
- var firstGroupId = this.groupIds[firstGroupIndex];
483
- var firstGroup = this.groups[firstGroupId] || this.groups[UNGROUPED];
484
-
485
- return firstGroup || null;
486
- };
487
-
488
- /**
489
- * Create or delete the group holding all ungrouped items. This group is used when
490
- * there are no groups specified.
491
- * @protected
492
- */
493
- ItemSet.prototype._updateUngrouped = function() {
494
- var ungrouped = this.groups[UNGROUPED];
495
-
496
- if (this.groupsData) {
497
- // remove the group holding all ungrouped items
498
- if (ungrouped) {
499
- ungrouped.hide();
500
- delete this.groups[UNGROUPED];
501
- }
502
- }
503
- else {
504
- // create a group holding all (unfiltered) items
505
- if (!ungrouped) {
506
- var id = null;
507
- var data = null;
508
- ungrouped = new Group(id, data, this);
509
- this.groups[UNGROUPED] = ungrouped;
510
-
511
- for (var itemId in this.items) {
512
- if (this.items.hasOwnProperty(itemId)) {
513
- ungrouped.add(this.items[itemId]);
514
- }
515
- }
516
-
517
- ungrouped.show();
518
- }
519
- }
520
- };
521
-
522
- /**
523
- * Get the element for the labelset
524
- * @return {HTMLElement} labelSet
525
- */
526
- ItemSet.prototype.getLabelSet = function() {
527
- return this.dom.labelSet;
528
- };
529
-
530
- /**
531
- * Set items
532
- * @param {vis.DataSet | null} items
533
- */
534
- ItemSet.prototype.setItems = function(items) {
535
- var me = this,
536
- ids,
537
- oldItemsData = this.itemsData;
538
-
539
- // replace the dataset
540
- if (!items) {
541
- this.itemsData = null;
542
- }
543
- else if (items instanceof DataSet || items instanceof DataView) {
544
- this.itemsData = items;
545
- }
546
- else {
547
- throw new TypeError('Data must be an instance of DataSet or DataView');
548
- }
549
-
550
- if (oldItemsData) {
551
- // unsubscribe from old dataset
552
- util.forEach(this.itemListeners, function (callback, event) {
553
- oldItemsData.off(event, callback);
554
- });
555
-
556
- // remove all drawn items
557
- ids = oldItemsData.getIds();
558
- this._onRemove(ids);
559
- }
560
-
561
- if (this.itemsData) {
562
- // subscribe to new dataset
563
- var id = this.id;
564
- util.forEach(this.itemListeners, function (callback, event) {
565
- me.itemsData.on(event, callback, id);
566
- });
567
-
568
- // add all new items
569
- ids = this.itemsData.getIds();
570
- this._onAdd(ids);
571
-
572
- // update the group holding all ungrouped items
573
- this._updateUngrouped();
574
- }
575
- };
576
-
577
- /**
578
- * Get the current items
579
- * @returns {vis.DataSet | null}
580
- */
581
- ItemSet.prototype.getItems = function() {
582
- return this.itemsData;
583
- };
584
-
585
- /**
586
- * Set groups
587
- * @param {vis.DataSet} groups
588
- */
589
- ItemSet.prototype.setGroups = function(groups) {
590
- var me = this,
591
- ids;
592
-
593
- // unsubscribe from current dataset
594
- if (this.groupsData) {
595
- util.forEach(this.groupListeners, function (callback, event) {
596
- me.groupsData.unsubscribe(event, callback);
597
- });
598
-
599
- // remove all drawn groups
600
- ids = this.groupsData.getIds();
601
- this.groupsData = null;
602
- this._onRemoveGroups(ids); // note: this will cause a redraw
603
- }
604
-
605
- // replace the dataset
606
- if (!groups) {
607
- this.groupsData = null;
608
- }
609
- else if (groups instanceof DataSet || groups instanceof DataView) {
610
- this.groupsData = groups;
611
- }
612
- else {
613
- throw new TypeError('Data must be an instance of DataSet or DataView');
614
- }
615
-
616
- if (this.groupsData) {
617
- // subscribe to new dataset
618
- var id = this.id;
619
- util.forEach(this.groupListeners, function (callback, event) {
620
- me.groupsData.on(event, callback, id);
621
- });
622
-
623
- // draw all ms
624
- ids = this.groupsData.getIds();
625
- this._onAddGroups(ids);
626
- }
627
-
628
- // update the group holding all ungrouped items
629
- this._updateUngrouped();
630
-
631
- // update the order of all items in each group
632
- this._order();
633
-
634
- this.body.emitter.emit('change');
635
- };
636
-
637
- /**
638
- * Get the current groups
639
- * @returns {vis.DataSet | null} groups
640
- */
641
- ItemSet.prototype.getGroups = function() {
642
- return this.groupsData;
643
- };
644
-
645
- /**
646
- * Remove an item by its id
647
- * @param {String | Number} id
648
- */
649
- ItemSet.prototype.removeItem = function(id) {
650
- var item = this.itemsData.get(id),
651
- dataset = this._myDataSet();
652
-
653
- if (item) {
654
- // confirm deletion
655
- this.options.onRemove(item, function (item) {
656
- if (item) {
657
- // remove by id here, it is possible that an item has no id defined
658
- // itself, so better not delete by the item itself
659
- dataset.remove(id);
660
- }
661
- });
662
- }
663
- };
664
-
665
- /**
666
- * Handle updated items
667
- * @param {Number[]} ids
668
- * @protected
669
- */
670
- ItemSet.prototype._onUpdate = function(ids) {
671
- var me = this;
672
-
673
- ids.forEach(function (id) {
674
- var itemData = me.itemsData.get(id, me.itemOptions),
675
- item = me.items[id],
676
- type = itemData.type ||
677
- (itemData.start && itemData.end && 'range') ||
678
- me.options.type ||
679
- 'box';
680
-
681
- var constructor = ItemSet.types[type];
682
-
683
- if (item) {
684
- // update item
685
- if (!constructor || !(item instanceof constructor)) {
686
- // item type has changed, delete the item and recreate it
687
- me._removeItem(item);
688
- item = null;
689
- }
690
- else {
691
- me._updateItem(item, itemData);
692
- }
693
- }
694
-
695
- if (!item) {
696
- // create item
697
- if (constructor) {
698
- item = new constructor(itemData, me.conversion, me.options);
699
- item.id = id; // TODO: not so nice setting id afterwards
700
- me._addItem(item);
701
- }
702
- else {
703
- throw new TypeError('Unknown item type "' + type + '"');
704
- }
705
- }
706
- });
707
-
708
- this._order();
709
- this.stackDirty = true; // force re-stacking of all items next redraw
710
- this.body.emitter.emit('change');
711
- };
712
-
713
- /**
714
- * Handle added items
715
- * @param {Number[]} ids
716
- * @protected
717
- */
718
- ItemSet.prototype._onAdd = ItemSet.prototype._onUpdate;
719
-
720
- /**
721
- * Handle removed items
722
- * @param {Number[]} ids
723
- * @protected
724
- */
725
- ItemSet.prototype._onRemove = function(ids) {
726
- var count = 0;
727
- var me = this;
728
- ids.forEach(function (id) {
729
- var item = me.items[id];
730
- if (item) {
731
- count++;
732
- me._removeItem(item);
733
- }
734
- });
735
-
736
- if (count) {
737
- // update order
738
- this._order();
739
- this.stackDirty = true; // force re-stacking of all items next redraw
740
- this.body.emitter.emit('change');
741
- }
742
- };
743
-
744
- /**
745
- * Update the order of item in all groups
746
- * @private
747
- */
748
- ItemSet.prototype._order = function() {
749
- // reorder the items in all groups
750
- // TODO: optimization: only reorder groups affected by the changed items
751
- util.forEach(this.groups, function (group) {
752
- group.order();
753
- });
754
- };
755
-
756
- /**
757
- * Handle updated groups
758
- * @param {Number[]} ids
759
- * @private
760
- */
761
- ItemSet.prototype._onUpdateGroups = function(ids) {
762
- this._onAddGroups(ids);
763
- };
764
-
765
- /**
766
- * Handle changed groups
767
- * @param {Number[]} ids
768
- * @private
769
- */
770
- ItemSet.prototype._onAddGroups = function(ids) {
771
- var me = this;
772
-
773
- ids.forEach(function (id) {
774
- var groupData = me.groupsData.get(id);
775
- var group = me.groups[id];
776
-
777
- if (!group) {
778
- // check for reserved ids
779
- if (id == UNGROUPED) {
780
- throw new Error('Illegal group id. ' + id + ' is a reserved id.');
781
- }
782
-
783
- var groupOptions = Object.create(me.options);
784
- util.extend(groupOptions, {
785
- height: null
786
- });
787
-
788
- group = new Group(id, groupData, me);
789
- me.groups[id] = group;
790
-
791
- // add items with this groupId to the new group
792
- for (var itemId in me.items) {
793
- if (me.items.hasOwnProperty(itemId)) {
794
- var item = me.items[itemId];
795
- if (item.data.group == id) {
796
- group.add(item);
797
- }
798
- }
799
- }
800
-
801
- group.order();
802
- group.show();
803
- }
804
- else {
805
- // update group
806
- group.setData(groupData);
807
- }
808
- });
809
-
810
- this.body.emitter.emit('change');
811
- };
812
-
813
- /**
814
- * Handle removed groups
815
- * @param {Number[]} ids
816
- * @private
817
- */
818
- ItemSet.prototype._onRemoveGroups = function(ids) {
819
- var groups = this.groups;
820
- ids.forEach(function (id) {
821
- var group = groups[id];
822
-
823
- if (group) {
824
- group.hide();
825
- delete groups[id];
826
- }
827
- });
828
-
829
- this.markDirty();
830
-
831
- this.body.emitter.emit('change');
832
- };
833
-
834
- /**
835
- * Reorder the groups if needed
836
- * @return {boolean} changed
837
- * @private
838
- */
839
- ItemSet.prototype._orderGroups = function () {
840
- if (this.groupsData) {
841
- // reorder the groups
842
- var groupIds = this.groupsData.getIds({
843
- order: this.options.groupOrder
844
- });
845
-
846
- var changed = !util.equalArray(groupIds, this.groupIds);
847
- if (changed) {
848
- // hide all groups, removes them from the DOM
849
- var groups = this.groups;
850
- groupIds.forEach(function (groupId) {
851
- groups[groupId].hide();
852
- });
853
-
854
- // show the groups again, attach them to the DOM in correct order
855
- groupIds.forEach(function (groupId) {
856
- groups[groupId].show();
857
- });
858
-
859
- this.groupIds = groupIds;
860
- }
861
-
862
- return changed;
863
- }
864
- else {
865
- return false;
866
- }
867
- };
868
-
869
- /**
870
- * Add a new item
871
- * @param {Item} item
872
- * @private
873
- */
874
- ItemSet.prototype._addItem = function(item) {
875
- this.items[item.id] = item;
876
-
877
- // add to group
878
- var groupId = this.groupsData ? item.data.group : UNGROUPED;
879
- var group = this.groups[groupId];
880
- if (group) group.add(item);
881
- };
882
-
883
- /**
884
- * Update an existing item
885
- * @param {Item} item
886
- * @param {Object} itemData
887
- * @private
888
- */
889
- ItemSet.prototype._updateItem = function(item, itemData) {
890
- var oldGroupId = item.data.group;
891
-
892
- item.data = itemData;
893
- if (item.displayed) {
894
- item.redraw();
895
- }
896
-
897
- // update group
898
- if (oldGroupId != item.data.group) {
899
- var oldGroup = this.groups[oldGroupId];
900
- if (oldGroup) oldGroup.remove(item);
901
-
902
- var groupId = this.groupsData ? item.data.group : UNGROUPED;
903
- var group = this.groups[groupId];
904
- if (group) group.add(item);
905
- }
906
- };
907
-
908
- /**
909
- * Delete an item from the ItemSet: remove it from the DOM, from the map
910
- * with items, and from the map with visible items, and from the selection
911
- * @param {Item} item
912
- * @private
913
- */
914
- ItemSet.prototype._removeItem = function(item) {
915
- // remove from DOM
916
- item.hide();
917
-
918
- // remove from items
919
- delete this.items[item.id];
920
-
921
- // remove from selection
922
- var index = this.selection.indexOf(item.id);
923
- if (index != -1) this.selection.splice(index, 1);
924
-
925
- // remove from group
926
- var groupId = this.groupsData ? item.data.group : UNGROUPED;
927
- var group = this.groups[groupId];
928
- if (group) group.remove(item);
929
- };
930
-
931
- /**
932
- * Create an array containing all items being a range (having an end date)
933
- * @param array
934
- * @returns {Array}
935
- * @private
936
- */
937
- ItemSet.prototype._constructByEndArray = function(array) {
938
- var endArray = [];
939
-
940
- for (var i = 0; i < array.length; i++) {
941
- if (array[i] instanceof ItemRange) {
942
- endArray.push(array[i]);
943
- }
944
- }
945
- return endArray;
946
- };
947
-
948
- /**
949
- * Register the clicked item on touch, before dragStart is initiated.
950
- *
951
- * dragStart is initiated from a mousemove event, which can have left the item
952
- * already resulting in an item == null
953
- *
954
- * @param {Event} event
955
- * @private
956
- */
957
- ItemSet.prototype._onTouch = function (event) {
958
- // store the touched item, used in _onDragStart
959
- this.touchParams.item = ItemSet.itemFromTarget(event);
960
- };
961
-
962
- /**
963
- * Start dragging the selected events
964
- * @param {Event} event
965
- * @private
966
- */
967
- ItemSet.prototype._onDragStart = function (event) {
968
- if (!this.options.editable.updateTime && !this.options.editable.updateGroup) {
969
- return;
970
- }
971
-
972
- var item = this.touchParams.item || null,
973
- me = this,
974
- props;
975
-
976
- if (item && item.selected) {
977
- var dragLeftItem = event.target.dragLeftItem;
978
- var dragRightItem = event.target.dragRightItem;
979
-
980
- if (dragLeftItem) {
981
- props = {
982
- item: dragLeftItem
983
- };
984
-
985
- if (me.options.editable.updateTime) {
986
- props.start = item.data.start.valueOf();
987
- }
988
- if (me.options.editable.updateGroup) {
989
- if ('group' in item.data) props.group = item.data.group;
990
- }
991
-
992
- this.touchParams.itemProps = [props];
993
- }
994
- else if (dragRightItem) {
995
- props = {
996
- item: dragRightItem
997
- };
998
-
999
- if (me.options.editable.updateTime) {
1000
- props.end = item.data.end.valueOf();
1001
- }
1002
- if (me.options.editable.updateGroup) {
1003
- if ('group' in item.data) props.group = item.data.group;
1004
- }
1005
-
1006
- this.touchParams.itemProps = [props];
1007
- }
1008
- else {
1009
- this.touchParams.itemProps = this.getSelection().map(function (id) {
1010
- var item = me.items[id];
1011
- var props = {
1012
- item: item
1013
- };
1014
-
1015
- if (me.options.editable.updateTime) {
1016
- if ('start' in item.data) props.start = item.data.start.valueOf();
1017
- if ('end' in item.data) props.end = item.data.end.valueOf();
1018
- }
1019
- if (me.options.editable.updateGroup) {
1020
- if ('group' in item.data) props.group = item.data.group;
1021
- }
1022
-
1023
- return props;
1024
- });
1025
- }
1026
-
1027
- event.stopPropagation();
1028
- }
1029
- };
1030
-
1031
- /**
1032
- * Drag selected items
1033
- * @param {Event} event
1034
- * @private
1035
- */
1036
- ItemSet.prototype._onDrag = function (event) {
1037
- if (this.touchParams.itemProps) {
1038
- var range = this.body.range,
1039
- snap = this.body.util.snap || null,
1040
- deltaX = event.gesture.deltaX,
1041
- scale = (this.props.width / (range.end - range.start)),
1042
- offset = deltaX / scale;
1043
-
1044
- // move
1045
- this.touchParams.itemProps.forEach(function (props) {
1046
- if ('start' in props) {
1047
- var start = new Date(props.start + offset);
1048
- props.item.data.start = snap ? snap(start) : start;
1049
- }
1050
-
1051
- if ('end' in props) {
1052
- var end = new Date(props.end + offset);
1053
- props.item.data.end = snap ? snap(end) : end;
1054
- }
1055
-
1056
- if ('group' in props) {
1057
- // drag from one group to another
1058
- var group = ItemSet.groupFromTarget(event);
1059
- if (group && group.groupId != props.item.data.group) {
1060
- var oldGroup = props.item.parent;
1061
- oldGroup.remove(props.item);
1062
- oldGroup.order();
1063
- group.add(props.item);
1064
- group.order();
1065
-
1066
- props.item.data.group = group.groupId;
1067
- }
1068
- }
1069
- });
1070
-
1071
- // TODO: implement onMoving handler
1072
-
1073
- this.stackDirty = true; // force re-stacking of all items next redraw
1074
- this.body.emitter.emit('change');
1075
-
1076
- event.stopPropagation();
1077
- }
1078
- };
1079
-
1080
- /**
1081
- * End of dragging selected items
1082
- * @param {Event} event
1083
- * @private
1084
- */
1085
- ItemSet.prototype._onDragEnd = function (event) {
1086
- if (this.touchParams.itemProps) {
1087
- // prepare a change set for the changed items
1088
- var changes = [],
1089
- me = this,
1090
- dataset = this._myDataSet();
1091
-
1092
- this.touchParams.itemProps.forEach(function (props) {
1093
- var id = props.item.id,
1094
- itemData = me.itemsData.get(id, me.itemOptions);
1095
-
1096
- var changed = false;
1097
- if ('start' in props.item.data) {
1098
- changed = (props.start != props.item.data.start.valueOf());
1099
- itemData.start = util.convert(props.item.data.start,
1100
- dataset._options.type && dataset._options.type.start || 'Date');
1101
- }
1102
- if ('end' in props.item.data) {
1103
- changed = changed || (props.end != props.item.data.end.valueOf());
1104
- itemData.end = util.convert(props.item.data.end,
1105
- dataset._options.type && dataset._options.type.end || 'Date');
1106
- }
1107
- if ('group' in props.item.data) {
1108
- changed = changed || (props.group != props.item.data.group);
1109
- itemData.group = props.item.data.group;
1110
- }
1111
-
1112
- // only apply changes when start or end is actually changed
1113
- if (changed) {
1114
- me.options.onMove(itemData, function (itemData) {
1115
- if (itemData) {
1116
- // apply changes
1117
- itemData[dataset._fieldId] = id; // ensure the item contains its id (can be undefined)
1118
- changes.push(itemData);
1119
- }
1120
- else {
1121
- // restore original values
1122
- if ('start' in props) props.item.data.start = props.start;
1123
- if ('end' in props) props.item.data.end = props.end;
1124
-
1125
- me.stackDirty = true; // force re-stacking of all items next redraw
1126
- me.body.emitter.emit('change');
1127
- }
1128
- });
1129
- }
1130
- });
1131
- this.touchParams.itemProps = null;
1132
-
1133
- // apply the changes to the data (if there are changes)
1134
- if (changes.length) {
1135
- dataset.update(changes);
1136
- }
1137
-
1138
- event.stopPropagation();
1139
- }
1140
- };
1141
-
1142
- /**
1143
- * Handle selecting/deselecting an item when tapping it
1144
- * @param {Event} event
1145
- * @private
1146
- */
1147
- ItemSet.prototype._onSelectItem = function (event) {
1148
- if (!this.options.selectable) return;
1149
-
1150
- var ctrlKey = event.gesture.srcEvent && event.gesture.srcEvent.ctrlKey;
1151
- var shiftKey = event.gesture.srcEvent && event.gesture.srcEvent.shiftKey;
1152
- if (ctrlKey || shiftKey) {
1153
- this._onMultiSelectItem(event);
1154
- return;
1155
- }
1156
-
1157
- var oldSelection = this.getSelection();
1158
-
1159
- var item = ItemSet.itemFromTarget(event);
1160
- var selection = item ? [item.id] : [];
1161
- this.setSelection(selection);
1162
-
1163
- var newSelection = this.getSelection();
1164
-
1165
- // emit a select event,
1166
- // except when old selection is empty and new selection is still empty
1167
- if (newSelection.length > 0 || oldSelection.length > 0) {
1168
- this.body.emitter.emit('select', {
1169
- items: this.getSelection()
1170
- });
1171
- }
1172
-
1173
- event.stopPropagation();
1174
- };
1175
-
1176
- /**
1177
- * Handle creation and updates of an item on double tap
1178
- * @param event
1179
- * @private
1180
- */
1181
- ItemSet.prototype._onAddItem = function (event) {
1182
- if (!this.options.selectable) return;
1183
- if (!this.options.editable.add) return;
1184
-
1185
- var me = this,
1186
- snap = this.body.util.snap || null,
1187
- item = ItemSet.itemFromTarget(event);
1188
-
1189
- if (item) {
1190
- // update item
1191
-
1192
- // execute async handler to update the item (or cancel it)
1193
- var itemData = me.itemsData.get(item.id); // get a clone of the data from the dataset
1194
- this.options.onUpdate(itemData, function (itemData) {
1195
- if (itemData) {
1196
- me.itemsData.update(itemData);
1197
- }
1198
- });
1199
- }
1200
- else {
1201
- // add item
1202
- var xAbs = vis.util.getAbsoluteLeft(this.dom.frame);
1203
- var x = event.gesture.center.pageX - xAbs;
1204
- var start = this.body.util.toTime(x);
1205
- var newItem = {
1206
- start: snap ? snap(start) : start,
1207
- content: 'new item'
1208
- };
1209
-
1210
- // when default type is a range, add a default end date to the new item
1211
- if (this.options.type === 'range' || this.options.type == 'rangeoverflow') {
1212
- var end = this.body.util.toTime(x + this.props.width / 5);
1213
- newItem.end = snap ? snap(end) : end;
1214
- }
1215
-
1216
- newItem[this.itemsData.fieldId] = util.randomUUID();
1217
-
1218
- var group = ItemSet.groupFromTarget(event);
1219
- if (group) {
1220
- newItem.group = group.groupId;
1221
- }
1222
-
1223
- // execute async handler to customize (or cancel) adding an item
1224
- this.options.onAdd(newItem, function (item) {
1225
- if (item) {
1226
- me.itemsData.add(newItem);
1227
- // TODO: need to trigger a redraw?
1228
- }
1229
- });
1230
- }
1231
- };
1232
-
1233
- /**
1234
- * Handle selecting/deselecting multiple items when holding an item
1235
- * @param {Event} event
1236
- * @private
1237
- */
1238
- ItemSet.prototype._onMultiSelectItem = function (event) {
1239
- if (!this.options.selectable) return;
1240
-
1241
- var selection,
1242
- item = ItemSet.itemFromTarget(event);
1243
-
1244
- if (item) {
1245
- // multi select items
1246
- selection = this.getSelection(); // current selection
1247
- var index = selection.indexOf(item.id);
1248
- if (index == -1) {
1249
- // item is not yet selected -> select it
1250
- selection.push(item.id);
1251
- }
1252
- else {
1253
- // item is already selected -> deselect it
1254
- selection.splice(index, 1);
1255
- }
1256
- this.setSelection(selection);
1257
-
1258
- this.body.emitter.emit('select', {
1259
- items: this.getSelection()
1260
- });
1261
-
1262
- event.stopPropagation();
1263
- }
1264
- };
1265
-
1266
- /**
1267
- * Find an item from an event target:
1268
- * searches for the attribute 'timeline-item' in the event target's element tree
1269
- * @param {Event} event
1270
- * @return {Item | null} item
1271
- */
1272
- ItemSet.itemFromTarget = function(event) {
1273
- var target = event.target;
1274
- while (target) {
1275
- if (target.hasOwnProperty('timeline-item')) {
1276
- return target['timeline-item'];
1277
- }
1278
- target = target.parentNode;
1279
- }
1280
-
1281
- return null;
1282
- };
1283
-
1284
- /**
1285
- * Find the Group from an event target:
1286
- * searches for the attribute 'timeline-group' in the event target's element tree
1287
- * @param {Event} event
1288
- * @return {Group | null} group
1289
- */
1290
- ItemSet.groupFromTarget = function(event) {
1291
- var target = event.target;
1292
- while (target) {
1293
- if (target.hasOwnProperty('timeline-group')) {
1294
- return target['timeline-group'];
1295
- }
1296
- target = target.parentNode;
1297
- }
1298
-
1299
- return null;
1300
- };
1301
-
1302
- /**
1303
- * Find the ItemSet from an event target:
1304
- * searches for the attribute 'timeline-itemset' in the event target's element tree
1305
- * @param {Event} event
1306
- * @return {ItemSet | null} item
1307
- */
1308
- ItemSet.itemSetFromTarget = function(event) {
1309
- var target = event.target;
1310
- while (target) {
1311
- if (target.hasOwnProperty('timeline-itemset')) {
1312
- return target['timeline-itemset'];
1313
- }
1314
- target = target.parentNode;
1315
- }
1316
-
1317
- return null;
1318
- };
1319
-
1320
- /**
1321
- * Find the DataSet to which this ItemSet is connected
1322
- * @returns {null | DataSet} dataset
1323
- * @private
1324
- */
1325
- ItemSet.prototype._myDataSet = function() {
1326
- // find the root DataSet
1327
- var dataset = this.itemsData;
1328
- while (dataset instanceof DataView) {
1329
- dataset = dataset.data;
1330
- }
1331
- return dataset;
1332
- };