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.

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