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.

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