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.

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