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.

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