vis-rails 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+ };