vis.js is a dynamic, browser-based visualization library
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

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