vis-rails 0.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 (71) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.gitmodules +3 -0
  4. data/.project +11 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE +202 -0
  7. data/README.md +29 -0
  8. data/Rakefile +1 -0
  9. data/lib/vis/rails/engine.rb +6 -0
  10. data/lib/vis/rails/version.rb +5 -0
  11. data/lib/vis/rails.rb +7 -0
  12. data/vendor/assets/javascripts/vis.js +1 -0
  13. data/vendor/assets/stylesheets/vis.css +3 -0
  14. data/vendor/assets/vis/DataSet.js +936 -0
  15. data/vendor/assets/vis/DataView.js +281 -0
  16. data/vendor/assets/vis/EventBus.js +89 -0
  17. data/vendor/assets/vis/events.js +116 -0
  18. data/vendor/assets/vis/graph/ClusterMixin.js +1019 -0
  19. data/vendor/assets/vis/graph/Edge.js +620 -0
  20. data/vendor/assets/vis/graph/Graph.js +2111 -0
  21. data/vendor/assets/vis/graph/Groups.js +80 -0
  22. data/vendor/assets/vis/graph/Images.js +41 -0
  23. data/vendor/assets/vis/graph/NavigationMixin.js +245 -0
  24. data/vendor/assets/vis/graph/Node.js +978 -0
  25. data/vendor/assets/vis/graph/Popup.js +105 -0
  26. data/vendor/assets/vis/graph/SectorsMixin.js +547 -0
  27. data/vendor/assets/vis/graph/SelectionMixin.js +515 -0
  28. data/vendor/assets/vis/graph/dotparser.js +829 -0
  29. data/vendor/assets/vis/graph/img/downarrow.png +0 -0
  30. data/vendor/assets/vis/graph/img/leftarrow.png +0 -0
  31. data/vendor/assets/vis/graph/img/minus.png +0 -0
  32. data/vendor/assets/vis/graph/img/plus.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/graph/img/zoomExtends.png +0 -0
  36. data/vendor/assets/vis/graph/shapes.js +225 -0
  37. data/vendor/assets/vis/module/exports.js +68 -0
  38. data/vendor/assets/vis/module/header.js +24 -0
  39. data/vendor/assets/vis/module/imports.js +32 -0
  40. data/vendor/assets/vis/shim.js +252 -0
  41. data/vendor/assets/vis/timeline/Controller.js +172 -0
  42. data/vendor/assets/vis/timeline/Range.js +553 -0
  43. data/vendor/assets/vis/timeline/Stack.js +192 -0
  44. data/vendor/assets/vis/timeline/TimeStep.js +449 -0
  45. data/vendor/assets/vis/timeline/Timeline.js +476 -0
  46. data/vendor/assets/vis/timeline/component/Component.js +148 -0
  47. data/vendor/assets/vis/timeline/component/ContentPanel.js +113 -0
  48. data/vendor/assets/vis/timeline/component/CurrentTime.js +101 -0
  49. data/vendor/assets/vis/timeline/component/CustomTime.js +255 -0
  50. data/vendor/assets/vis/timeline/component/Group.js +129 -0
  51. data/vendor/assets/vis/timeline/component/GroupSet.js +546 -0
  52. data/vendor/assets/vis/timeline/component/ItemSet.js +612 -0
  53. data/vendor/assets/vis/timeline/component/Panel.js +112 -0
  54. data/vendor/assets/vis/timeline/component/RootPanel.js +215 -0
  55. data/vendor/assets/vis/timeline/component/TimeAxis.js +522 -0
  56. data/vendor/assets/vis/timeline/component/css/currenttime.css +5 -0
  57. data/vendor/assets/vis/timeline/component/css/customtime.css +6 -0
  58. data/vendor/assets/vis/timeline/component/css/groupset.css +59 -0
  59. data/vendor/assets/vis/timeline/component/css/item.css +93 -0
  60. data/vendor/assets/vis/timeline/component/css/itemset.css +17 -0
  61. data/vendor/assets/vis/timeline/component/css/panel.css +14 -0
  62. data/vendor/assets/vis/timeline/component/css/timeaxis.css +41 -0
  63. data/vendor/assets/vis/timeline/component/css/timeline.css +2 -0
  64. data/vendor/assets/vis/timeline/component/item/Item.js +81 -0
  65. data/vendor/assets/vis/timeline/component/item/ItemBox.js +302 -0
  66. data/vendor/assets/vis/timeline/component/item/ItemPoint.js +237 -0
  67. data/vendor/assets/vis/timeline/component/item/ItemRange.js +251 -0
  68. data/vendor/assets/vis/timeline/component/item/ItemRangeOverflow.js +91 -0
  69. data/vendor/assets/vis/util.js +673 -0
  70. data/vis-rails.gemspec +47 -0
  71. metadata +142 -0
@@ -0,0 +1,936 @@
1
+ /**
2
+ * DataSet
3
+ *
4
+ * Usage:
5
+ * var dataSet = new DataSet({
6
+ * fieldId: '_id',
7
+ * convert: {
8
+ * // ...
9
+ * }
10
+ * });
11
+ *
12
+ * dataSet.add(item);
13
+ * dataSet.add(data);
14
+ * dataSet.update(item);
15
+ * dataSet.update(data);
16
+ * dataSet.remove(id);
17
+ * dataSet.remove(ids);
18
+ * var data = dataSet.get();
19
+ * var data = dataSet.get(id);
20
+ * var data = dataSet.get(ids);
21
+ * var data = dataSet.get(ids, options, data);
22
+ * dataSet.clear();
23
+ *
24
+ * A data set can:
25
+ * - add/remove/update data
26
+ * - gives triggers upon changes in the data
27
+ * - can import/export data in various data formats
28
+ *
29
+ * @param {Object} [options] Available options:
30
+ * {String} fieldId Field name of the id in the
31
+ * items, 'id' by default.
32
+ * {Object.<String, String} convert
33
+ * A map with field names as key,
34
+ * and the field type as value.
35
+ * @constructor DataSet
36
+ */
37
+ // TODO: add a DataSet constructor DataSet(data, options)
38
+ function DataSet (options) {
39
+ this.id = util.randomUUID();
40
+
41
+ this.options = options || {};
42
+ this.data = {}; // map with data indexed by id
43
+ this.fieldId = this.options.fieldId || 'id'; // name of the field containing id
44
+ this.convert = {}; // field types by field name
45
+ this.showInternalIds = this.options.showInternalIds || false; // show internal ids with the get function
46
+
47
+ if (this.options.convert) {
48
+ for (var field in this.options.convert) {
49
+ if (this.options.convert.hasOwnProperty(field)) {
50
+ var value = this.options.convert[field];
51
+ if (value == 'Date' || value == 'ISODate' || value == 'ASPDate') {
52
+ this.convert[field] = 'Date';
53
+ }
54
+ else {
55
+ this.convert[field] = value;
56
+ }
57
+ }
58
+ }
59
+ }
60
+
61
+ // event subscribers
62
+ this.subscribers = {};
63
+
64
+ this.internalIds = {}; // internally generated id's
65
+ }
66
+
67
+ /**
68
+ * Subscribe to an event, add an event listener
69
+ * @param {String} event Event name. Available events: 'put', 'update',
70
+ * 'remove'
71
+ * @param {function} callback Callback method. Called with three parameters:
72
+ * {String} event
73
+ * {Object | null} params
74
+ * {String | Number} senderId
75
+ */
76
+ DataSet.prototype.subscribe = function (event, callback) {
77
+ var subscribers = this.subscribers[event];
78
+ if (!subscribers) {
79
+ subscribers = [];
80
+ this.subscribers[event] = subscribers;
81
+ }
82
+
83
+ subscribers.push({
84
+ callback: callback
85
+ });
86
+ };
87
+
88
+ /**
89
+ * Unsubscribe from an event, remove an event listener
90
+ * @param {String} event
91
+ * @param {function} callback
92
+ */
93
+ DataSet.prototype.unsubscribe = function (event, callback) {
94
+ var subscribers = this.subscribers[event];
95
+ if (subscribers) {
96
+ this.subscribers[event] = subscribers.filter(function (listener) {
97
+ return (listener.callback != callback);
98
+ });
99
+ }
100
+ };
101
+
102
+ /**
103
+ * Trigger an event
104
+ * @param {String} event
105
+ * @param {Object | null} params
106
+ * @param {String} [senderId] Optional id of the sender.
107
+ * @private
108
+ */
109
+ DataSet.prototype._trigger = function (event, params, senderId) {
110
+ if (event == '*') {
111
+ throw new Error('Cannot trigger event *');
112
+ }
113
+
114
+ var subscribers = [];
115
+ if (event in this.subscribers) {
116
+ subscribers = subscribers.concat(this.subscribers[event]);
117
+ }
118
+ if ('*' in this.subscribers) {
119
+ subscribers = subscribers.concat(this.subscribers['*']);
120
+ }
121
+
122
+ for (var i = 0; i < subscribers.length; i++) {
123
+ var subscriber = subscribers[i];
124
+ if (subscriber.callback) {
125
+ subscriber.callback(event, params, senderId || null);
126
+ }
127
+ }
128
+ };
129
+
130
+ /**
131
+ * Add data.
132
+ * Adding an item will fail when there already is an item with the same id.
133
+ * @param {Object | Array | DataTable} data
134
+ * @param {String} [senderId] Optional sender id
135
+ * @return {Array} addedIds Array with the ids of the added items
136
+ */
137
+ DataSet.prototype.add = function (data, senderId) {
138
+ var addedIds = [],
139
+ id,
140
+ me = this;
141
+
142
+ if (data instanceof Array) {
143
+ // Array
144
+ for (var i = 0, len = data.length; i < len; i++) {
145
+ id = me._addItem(data[i]);
146
+ addedIds.push(id);
147
+ }
148
+ }
149
+ else if (util.isDataTable(data)) {
150
+ // Google DataTable
151
+ var columns = this._getColumnNames(data);
152
+ for (var row = 0, rows = data.getNumberOfRows(); row < rows; row++) {
153
+ var item = {};
154
+ for (var col = 0, cols = columns.length; col < cols; col++) {
155
+ var field = columns[col];
156
+ item[field] = data.getValue(row, col);
157
+ }
158
+
159
+ id = me._addItem(item);
160
+ addedIds.push(id);
161
+ }
162
+ }
163
+ else if (data instanceof Object) {
164
+ // Single item
165
+ id = me._addItem(data);
166
+ addedIds.push(id);
167
+ }
168
+ else {
169
+ throw new Error('Unknown dataType');
170
+ }
171
+
172
+ if (addedIds.length) {
173
+ this._trigger('add', {items: addedIds}, senderId);
174
+ }
175
+
176
+ return addedIds;
177
+ };
178
+
179
+ /**
180
+ * Update existing items. When an item does not exist, it will be created
181
+ * @param {Object | Array | DataTable} data
182
+ * @param {String} [senderId] Optional sender id
183
+ * @return {Array} updatedIds The ids of the added or updated items
184
+ */
185
+ DataSet.prototype.update = function (data, senderId) {
186
+ var addedIds = [],
187
+ updatedIds = [],
188
+ me = this,
189
+ fieldId = me.fieldId;
190
+
191
+ var addOrUpdate = function (item) {
192
+ var id = item[fieldId];
193
+ if (me.data[id]) {
194
+ // update item
195
+ id = me._updateItem(item);
196
+ updatedIds.push(id);
197
+ }
198
+ else {
199
+ // add new item
200
+ id = me._addItem(item);
201
+ addedIds.push(id);
202
+ }
203
+ };
204
+
205
+ if (data instanceof Array) {
206
+ // Array
207
+ for (var i = 0, len = data.length; i < len; i++) {
208
+ addOrUpdate(data[i]);
209
+ }
210
+ }
211
+ else if (util.isDataTable(data)) {
212
+ // Google DataTable
213
+ var columns = this._getColumnNames(data);
214
+ for (var row = 0, rows = data.getNumberOfRows(); row < rows; row++) {
215
+ var item = {};
216
+ for (var col = 0, cols = columns.length; col < cols; col++) {
217
+ var field = columns[col];
218
+ item[field] = data.getValue(row, col);
219
+ }
220
+
221
+ addOrUpdate(item);
222
+ }
223
+ }
224
+ else if (data instanceof Object) {
225
+ // Single item
226
+ addOrUpdate(data);
227
+ }
228
+ else {
229
+ throw new Error('Unknown dataType');
230
+ }
231
+
232
+ if (addedIds.length) {
233
+ this._trigger('add', {items: addedIds}, senderId);
234
+ }
235
+ if (updatedIds.length) {
236
+ this._trigger('update', {items: updatedIds}, senderId);
237
+ }
238
+
239
+ return addedIds.concat(updatedIds);
240
+ };
241
+
242
+ /**
243
+ * Get a data item or multiple items.
244
+ *
245
+ * Usage:
246
+ *
247
+ * get()
248
+ * get(options: Object)
249
+ * get(options: Object, data: Array | DataTable)
250
+ *
251
+ * get(id: Number | String)
252
+ * get(id: Number | String, options: Object)
253
+ * get(id: Number | String, options: Object, data: Array | DataTable)
254
+ *
255
+ * get(ids: Number[] | String[])
256
+ * get(ids: Number[] | String[], options: Object)
257
+ * get(ids: Number[] | String[], options: Object, data: Array | DataTable)
258
+ *
259
+ * Where:
260
+ *
261
+ * {Number | String} id The id of an item
262
+ * {Number[] | String{}} ids An array with ids of items
263
+ * {Object} options An Object with options. Available options:
264
+ * {String} [type] Type of data to be returned. Can
265
+ * be 'DataTable' or 'Array' (default)
266
+ * {Object.<String, String>} [convert]
267
+ * {String[]} [fields] field names to be returned
268
+ * {function} [filter] filter items
269
+ * {String | function} [order] Order the items by
270
+ * a field name or custom sort function.
271
+ * {Array | DataTable} [data] If provided, items will be appended to this
272
+ * array or table. Required in case of Google
273
+ * DataTable.
274
+ *
275
+ * @throws Error
276
+ */
277
+ DataSet.prototype.get = function (args) {
278
+ var me = this;
279
+ var globalShowInternalIds = this.showInternalIds;
280
+
281
+ // parse the arguments
282
+ var id, ids, options, data;
283
+ var firstType = util.getType(arguments[0]);
284
+ if (firstType == 'String' || firstType == 'Number') {
285
+ // get(id [, options] [, data])
286
+ id = arguments[0];
287
+ options = arguments[1];
288
+ data = arguments[2];
289
+ }
290
+ else if (firstType == 'Array') {
291
+ // get(ids [, options] [, data])
292
+ ids = arguments[0];
293
+ options = arguments[1];
294
+ data = arguments[2];
295
+ }
296
+ else {
297
+ // get([, options] [, data])
298
+ options = arguments[0];
299
+ data = arguments[1];
300
+ }
301
+
302
+ // determine the return type
303
+ var type;
304
+ if (options && options.type) {
305
+ type = (options.type == 'DataTable') ? 'DataTable' : 'Array';
306
+
307
+ if (data && (type != util.getType(data))) {
308
+ throw new Error('Type of parameter "data" (' + util.getType(data) + ') ' +
309
+ 'does not correspond with specified options.type (' + options.type + ')');
310
+ }
311
+ if (type == 'DataTable' && !util.isDataTable(data)) {
312
+ throw new Error('Parameter "data" must be a DataTable ' +
313
+ 'when options.type is "DataTable"');
314
+ }
315
+ }
316
+ else if (data) {
317
+ type = (util.getType(data) == 'DataTable') ? 'DataTable' : 'Array';
318
+ }
319
+ else {
320
+ type = 'Array';
321
+ }
322
+
323
+ // we allow the setting of this value for a single get request.
324
+ if (options != undefined) {
325
+ if (options.showInternalIds != undefined) {
326
+ this.showInternalIds = options.showInternalIds;
327
+ }
328
+ }
329
+
330
+ // build options
331
+ var convert = options && options.convert || this.options.convert;
332
+ var filter = options && options.filter;
333
+ var items = [], item, itemId, i, len;
334
+
335
+ // convert items
336
+ if (id != undefined) {
337
+ // return a single item
338
+ item = me._getItem(id, convert);
339
+ if (filter && !filter(item)) {
340
+ item = null;
341
+ }
342
+ }
343
+ else if (ids != undefined) {
344
+ // return a subset of items
345
+ for (i = 0, len = ids.length; i < len; i++) {
346
+ item = me._getItem(ids[i], convert);
347
+ if (!filter || filter(item)) {
348
+ items.push(item);
349
+ }
350
+ }
351
+ }
352
+ else {
353
+ // return all items
354
+ for (itemId in this.data) {
355
+ if (this.data.hasOwnProperty(itemId)) {
356
+ item = me._getItem(itemId, convert);
357
+ if (!filter || filter(item)) {
358
+ items.push(item);
359
+ }
360
+ }
361
+ }
362
+ }
363
+
364
+ // restore the global value of showInternalIds
365
+ this.showInternalIds = globalShowInternalIds;
366
+
367
+ // order the results
368
+ if (options && options.order && id == undefined) {
369
+ this._sort(items, options.order);
370
+ }
371
+
372
+ // filter fields of the items
373
+ if (options && options.fields) {
374
+ var fields = options.fields;
375
+ if (id != undefined) {
376
+ item = this._filterFields(item, fields);
377
+ }
378
+ else {
379
+ for (i = 0, len = items.length; i < len; i++) {
380
+ items[i] = this._filterFields(items[i], fields);
381
+ }
382
+ }
383
+ }
384
+
385
+ // return the results
386
+ if (type == 'DataTable') {
387
+ var columns = this._getColumnNames(data);
388
+ if (id != undefined) {
389
+ // append a single item to the data table
390
+ me._appendRow(data, columns, item);
391
+ }
392
+ else {
393
+ // copy the items to the provided data table
394
+ for (i = 0, len = items.length; i < len; i++) {
395
+ me._appendRow(data, columns, items[i]);
396
+ }
397
+ }
398
+ return data;
399
+ }
400
+ else {
401
+ // return an array
402
+ if (id != undefined) {
403
+ // a single item
404
+ return item;
405
+ }
406
+ else {
407
+ // multiple items
408
+ if (data) {
409
+ // copy the items to the provided array
410
+ for (i = 0, len = items.length; i < len; i++) {
411
+ data.push(items[i]);
412
+ }
413
+ return data;
414
+ }
415
+ else {
416
+ // just return our array
417
+ return items;
418
+ }
419
+ }
420
+ }
421
+ };
422
+
423
+ /**
424
+ * Get ids of all items or from a filtered set of items.
425
+ * @param {Object} [options] An Object with options. Available options:
426
+ * {function} [filter] filter items
427
+ * {String | function} [order] Order the items by
428
+ * a field name or custom sort function.
429
+ * @return {Array} ids
430
+ */
431
+ DataSet.prototype.getIds = function (options) {
432
+ var data = this.data,
433
+ filter = options && options.filter,
434
+ order = options && options.order,
435
+ convert = options && options.convert || this.options.convert,
436
+ i,
437
+ len,
438
+ id,
439
+ item,
440
+ items,
441
+ ids = [];
442
+
443
+ if (filter) {
444
+ // get filtered items
445
+ if (order) {
446
+ // create ordered list
447
+ items = [];
448
+ for (id in data) {
449
+ if (data.hasOwnProperty(id)) {
450
+ item = this._getItem(id, convert);
451
+ if (filter(item)) {
452
+ items.push(item);
453
+ }
454
+ }
455
+ }
456
+
457
+ this._sort(items, order);
458
+
459
+ for (i = 0, len = items.length; i < len; i++) {
460
+ ids[i] = items[i][this.fieldId];
461
+ }
462
+ }
463
+ else {
464
+ // create unordered list
465
+ for (id in data) {
466
+ if (data.hasOwnProperty(id)) {
467
+ item = this._getItem(id, convert);
468
+ if (filter(item)) {
469
+ ids.push(item[this.fieldId]);
470
+ }
471
+ }
472
+ }
473
+ }
474
+ }
475
+ else {
476
+ // get all items
477
+ if (order) {
478
+ // create an ordered list
479
+ items = [];
480
+ for (id in data) {
481
+ if (data.hasOwnProperty(id)) {
482
+ items.push(data[id]);
483
+ }
484
+ }
485
+
486
+ this._sort(items, order);
487
+
488
+ for (i = 0, len = items.length; i < len; i++) {
489
+ ids[i] = items[i][this.fieldId];
490
+ }
491
+ }
492
+ else {
493
+ // create unordered list
494
+ for (id in data) {
495
+ if (data.hasOwnProperty(id)) {
496
+ item = data[id];
497
+ ids.push(item[this.fieldId]);
498
+ }
499
+ }
500
+ }
501
+ }
502
+
503
+ return ids;
504
+ };
505
+
506
+ /**
507
+ * Execute a callback function for every item in the dataset.
508
+ * The order of the items is not determined.
509
+ * @param {function} callback
510
+ * @param {Object} [options] Available options:
511
+ * {Object.<String, String>} [convert]
512
+ * {String[]} [fields] filter fields
513
+ * {function} [filter] filter items
514
+ * {String | function} [order] Order the items by
515
+ * a field name or custom sort function.
516
+ */
517
+ DataSet.prototype.forEach = function (callback, options) {
518
+ var filter = options && options.filter,
519
+ convert = options && options.convert || this.options.convert,
520
+ data = this.data,
521
+ item,
522
+ id;
523
+
524
+ if (options && options.order) {
525
+ // execute forEach on ordered list
526
+ var items = this.get(options);
527
+
528
+ for (var i = 0, len = items.length; i < len; i++) {
529
+ item = items[i];
530
+ id = item[this.fieldId];
531
+ callback(item, id);
532
+ }
533
+ }
534
+ else {
535
+ // unordered
536
+ for (id in data) {
537
+ if (data.hasOwnProperty(id)) {
538
+ item = this._getItem(id, convert);
539
+ if (!filter || filter(item)) {
540
+ callback(item, id);
541
+ }
542
+ }
543
+ }
544
+ }
545
+ };
546
+
547
+ /**
548
+ * Map every item in the dataset.
549
+ * @param {function} callback
550
+ * @param {Object} [options] Available options:
551
+ * {Object.<String, String>} [convert]
552
+ * {String[]} [fields] filter fields
553
+ * {function} [filter] filter items
554
+ * {String | function} [order] Order the items by
555
+ * a field name or custom sort function.
556
+ * @return {Object[]} mappedItems
557
+ */
558
+ DataSet.prototype.map = function (callback, options) {
559
+ var filter = options && options.filter,
560
+ convert = options && options.convert || this.options.convert,
561
+ mappedItems = [],
562
+ data = this.data,
563
+ item;
564
+
565
+ // convert and filter items
566
+ for (var id in data) {
567
+ if (data.hasOwnProperty(id)) {
568
+ item = this._getItem(id, convert);
569
+ if (!filter || filter(item)) {
570
+ mappedItems.push(callback(item, id));
571
+ }
572
+ }
573
+ }
574
+
575
+ // order items
576
+ if (options && options.order) {
577
+ this._sort(mappedItems, options.order);
578
+ }
579
+
580
+ return mappedItems;
581
+ };
582
+
583
+ /**
584
+ * Filter the fields of an item
585
+ * @param {Object} item
586
+ * @param {String[]} fields Field names
587
+ * @return {Object} filteredItem
588
+ * @private
589
+ */
590
+ DataSet.prototype._filterFields = function (item, fields) {
591
+ var filteredItem = {};
592
+
593
+ for (var field in item) {
594
+ if (item.hasOwnProperty(field) && (fields.indexOf(field) != -1)) {
595
+ filteredItem[field] = item[field];
596
+ }
597
+ }
598
+
599
+ return filteredItem;
600
+ };
601
+
602
+ /**
603
+ * Sort the provided array with items
604
+ * @param {Object[]} items
605
+ * @param {String | function} order A field name or custom sort function.
606
+ * @private
607
+ */
608
+ DataSet.prototype._sort = function (items, order) {
609
+ if (util.isString(order)) {
610
+ // order by provided field name
611
+ var name = order; // field name
612
+ items.sort(function (a, b) {
613
+ var av = a[name];
614
+ var bv = b[name];
615
+ return (av > bv) ? 1 : ((av < bv) ? -1 : 0);
616
+ });
617
+ }
618
+ else if (typeof order === 'function') {
619
+ // order by sort function
620
+ items.sort(order);
621
+ }
622
+ // TODO: extend order by an Object {field:String, direction:String}
623
+ // where direction can be 'asc' or 'desc'
624
+ else {
625
+ throw new TypeError('Order must be a function or a string');
626
+ }
627
+ };
628
+
629
+ /**
630
+ * Remove an object by pointer or by id
631
+ * @param {String | Number | Object | Array} id Object or id, or an array with
632
+ * objects or ids to be removed
633
+ * @param {String} [senderId] Optional sender id
634
+ * @return {Array} removedIds
635
+ */
636
+ DataSet.prototype.remove = function (id, senderId) {
637
+ var removedIds = [],
638
+ i, len, removedId;
639
+
640
+ if (id instanceof Array) {
641
+ for (i = 0, len = id.length; i < len; i++) {
642
+ removedId = this._remove(id[i]);
643
+ if (removedId != null) {
644
+ removedIds.push(removedId);
645
+ }
646
+ }
647
+ }
648
+ else {
649
+ removedId = this._remove(id);
650
+ if (removedId != null) {
651
+ removedIds.push(removedId);
652
+ }
653
+ }
654
+
655
+ if (removedIds.length) {
656
+ this._trigger('remove', {items: removedIds}, senderId);
657
+ }
658
+
659
+ return removedIds;
660
+ };
661
+
662
+ /**
663
+ * Remove an item by its id
664
+ * @param {Number | String | Object} id id or item
665
+ * @returns {Number | String | null} id
666
+ * @private
667
+ */
668
+ DataSet.prototype._remove = function (id) {
669
+ if (util.isNumber(id) || util.isString(id)) {
670
+ if (this.data[id]) {
671
+ delete this.data[id];
672
+ delete this.internalIds[id];
673
+ return id;
674
+ }
675
+ }
676
+ else if (id instanceof Object) {
677
+ var itemId = id[this.fieldId];
678
+ if (itemId && this.data[itemId]) {
679
+ delete this.data[itemId];
680
+ delete this.internalIds[itemId];
681
+ return itemId;
682
+ }
683
+ }
684
+ return null;
685
+ };
686
+
687
+ /**
688
+ * Clear the data
689
+ * @param {String} [senderId] Optional sender id
690
+ * @return {Array} removedIds The ids of all removed items
691
+ */
692
+ DataSet.prototype.clear = function (senderId) {
693
+ var ids = Object.keys(this.data);
694
+
695
+ this.data = {};
696
+ this.internalIds = {};
697
+
698
+ this._trigger('remove', {items: ids}, senderId);
699
+
700
+ return ids;
701
+ };
702
+
703
+ /**
704
+ * Find the item with maximum value of a specified field
705
+ * @param {String} field
706
+ * @return {Object | null} item Item containing max value, or null if no items
707
+ */
708
+ DataSet.prototype.max = function (field) {
709
+ var data = this.data,
710
+ max = null,
711
+ maxField = null;
712
+
713
+ for (var id in data) {
714
+ if (data.hasOwnProperty(id)) {
715
+ var item = data[id];
716
+ var itemField = item[field];
717
+ if (itemField != null && (!max || itemField > maxField)) {
718
+ max = item;
719
+ maxField = itemField;
720
+ }
721
+ }
722
+ }
723
+
724
+ return max;
725
+ };
726
+
727
+ /**
728
+ * Find the item with minimum value of a specified field
729
+ * @param {String} field
730
+ * @return {Object | null} item Item containing max value, or null if no items
731
+ */
732
+ DataSet.prototype.min = function (field) {
733
+ var data = this.data,
734
+ min = null,
735
+ minField = null;
736
+
737
+ for (var id in data) {
738
+ if (data.hasOwnProperty(id)) {
739
+ var item = data[id];
740
+ var itemField = item[field];
741
+ if (itemField != null && (!min || itemField < minField)) {
742
+ min = item;
743
+ minField = itemField;
744
+ }
745
+ }
746
+ }
747
+
748
+ return min;
749
+ };
750
+
751
+ /**
752
+ * Find all distinct values of a specified field
753
+ * @param {String} field
754
+ * @return {Array} values Array containing all distinct values. If the data
755
+ * items do not contain the specified field, an array
756
+ * containing a single value undefined is returned.
757
+ * The returned array is unordered.
758
+ */
759
+ DataSet.prototype.distinct = function (field) {
760
+ var data = this.data,
761
+ values = [],
762
+ fieldType = this.options.convert[field],
763
+ count = 0;
764
+
765
+ for (var prop in data) {
766
+ if (data.hasOwnProperty(prop)) {
767
+ var item = data[prop];
768
+ var value = util.convert(item[field], fieldType);
769
+ var exists = false;
770
+ for (var i = 0; i < count; i++) {
771
+ if (values[i] == value) {
772
+ exists = true;
773
+ break;
774
+ }
775
+ }
776
+ if (!exists) {
777
+ values[count] = value;
778
+ count++;
779
+ }
780
+ }
781
+ }
782
+
783
+ return values;
784
+ };
785
+
786
+ /**
787
+ * Add a single item. Will fail when an item with the same id already exists.
788
+ * @param {Object} item
789
+ * @return {String} id
790
+ * @private
791
+ */
792
+ DataSet.prototype._addItem = function (item) {
793
+ var id = item[this.fieldId];
794
+
795
+ if (id != undefined) {
796
+ // check whether this id is already taken
797
+ if (this.data[id]) {
798
+ // item already exists
799
+ throw new Error('Cannot add item: item with id ' + id + ' already exists');
800
+ }
801
+ }
802
+ else {
803
+ // generate an id
804
+ id = util.randomUUID();
805
+ item[this.fieldId] = id;
806
+ this.internalIds[id] = item;
807
+ }
808
+
809
+ var d = {};
810
+ for (var field in item) {
811
+ if (item.hasOwnProperty(field)) {
812
+ var fieldType = this.convert[field]; // type may be undefined
813
+ d[field] = util.convert(item[field], fieldType);
814
+ }
815
+ }
816
+ this.data[id] = d;
817
+
818
+ return id;
819
+ };
820
+
821
+ /**
822
+ * Get an item. Fields can be converted to a specific type
823
+ * @param {String} id
824
+ * @param {Object.<String, String>} [convert] field types to convert
825
+ * @return {Object | null} item
826
+ * @private
827
+ */
828
+ DataSet.prototype._getItem = function (id, convert) {
829
+ var field, value;
830
+
831
+ // get the item from the dataset
832
+ var raw = this.data[id];
833
+ if (!raw) {
834
+ return null;
835
+ }
836
+
837
+ // convert the items field types
838
+ var converted = {},
839
+ fieldId = this.fieldId,
840
+ internalIds = this.internalIds;
841
+ if (convert) {
842
+ for (field in raw) {
843
+ if (raw.hasOwnProperty(field)) {
844
+ value = raw[field];
845
+ // output all fields, except internal ids
846
+ if ((field != fieldId) || (!(value in internalIds) || this.showInternalIds)) {
847
+ converted[field] = util.convert(value, convert[field]);
848
+ }
849
+ }
850
+ }
851
+ }
852
+ else {
853
+ // no field types specified, no converting needed
854
+ for (field in raw) {
855
+ if (raw.hasOwnProperty(field)) {
856
+ value = raw[field];
857
+ // output all fields, except internal ids
858
+ if ((field != fieldId) || (!(value in internalIds) || this.showInternalIds)) {
859
+ converted[field] = value;
860
+ }
861
+ }
862
+ }
863
+ }
864
+ return converted;
865
+ };
866
+
867
+ /**
868
+ * Update a single item: merge with existing item.
869
+ * Will fail when the item has no id, or when there does not exist an item
870
+ * with the same id.
871
+ * @param {Object} item
872
+ * @return {String} id
873
+ * @private
874
+ */
875
+ DataSet.prototype._updateItem = function (item) {
876
+ var id = item[this.fieldId];
877
+ if (id == undefined) {
878
+ throw new Error('Cannot update item: item has no id (item: ' + JSON.stringify(item) + ')');
879
+ }
880
+ var d = this.data[id];
881
+ if (!d) {
882
+ // item doesn't exist
883
+ throw new Error('Cannot update item: no item with id ' + id + ' found');
884
+ }
885
+
886
+ // merge with current item
887
+ for (var field in item) {
888
+ if (item.hasOwnProperty(field)) {
889
+ var fieldType = this.convert[field]; // type may be undefined
890
+ d[field] = util.convert(item[field], fieldType);
891
+ }
892
+ }
893
+
894
+ return id;
895
+ };
896
+
897
+ /**
898
+ * check if an id is an internal or external id
899
+ * @param id
900
+ * @returns {boolean}
901
+ * @private
902
+ */
903
+ DataSet.prototype.isInternalId = function(id) {
904
+ return (id in this.internalIds);
905
+ };
906
+
907
+
908
+ /**
909
+ * Get an array with the column names of a Google DataTable
910
+ * @param {DataTable} dataTable
911
+ * @return {String[]} columnNames
912
+ * @private
913
+ */
914
+ DataSet.prototype._getColumnNames = function (dataTable) {
915
+ var columns = [];
916
+ for (var col = 0, cols = dataTable.getNumberOfColumns(); col < cols; col++) {
917
+ columns[col] = dataTable.getColumnId(col) || dataTable.getColumnLabel(col);
918
+ }
919
+ return columns;
920
+ };
921
+
922
+ /**
923
+ * Append an item as a row to the dataTable
924
+ * @param dataTable
925
+ * @param columns
926
+ * @param item
927
+ * @private
928
+ */
929
+ DataSet.prototype._appendRow = function (dataTable, columns, item) {
930
+ var row = dataTable.addRow();
931
+
932
+ for (var col = 0, cols = columns.length; col < cols; col++) {
933
+ var field = columns[col];
934
+ dataTable.setValue(row, col, item[field]);
935
+ }
936
+ };