DataSet.js 25 KB

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