vis.js is a dynamic, browser-based visualization library
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

989 lines
26 KiB

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