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.

344 lines
9.2 KiB

  1. var util = require('./util');
  2. var DataSet = require('./DataSet');
  3. /**
  4. * DataView
  5. *
  6. * a dataview offers a filtered view on a dataset or an other dataview.
  7. *
  8. * @param {DataSet | DataView} data
  9. * @param {Object} [options] Available options: see method get
  10. *
  11. * @constructor DataView
  12. */
  13. function DataView (data, options) {
  14. this._data = null;
  15. this._ids = {}; // ids of the items currently in memory (just contains a boolean true)
  16. this.length = 0; // number of items in the DataView
  17. this._options = options || {};
  18. this._fieldId = 'id'; // name of the field containing id
  19. this._subscribers = {}; // event subscribers
  20. var me = this;
  21. this.listener = function () {
  22. me._onEvent.apply(me, arguments);
  23. };
  24. this.setData(data);
  25. }
  26. // TODO: implement a function .config() to dynamically update things like configured filter
  27. // and trigger changes accordingly
  28. /**
  29. * Set a data source for the view
  30. * @param {DataSet | DataView} data
  31. */
  32. DataView.prototype.setData = function (data) {
  33. var ids, id, i, len;
  34. if (this._data) {
  35. // unsubscribe from current dataset
  36. if (this._data.off) {
  37. this._data.off('*', this.listener);
  38. }
  39. // trigger a remove of all items in memory
  40. ids = Object.keys(this._ids);
  41. this._ids = {};
  42. this.length = 0;
  43. this._trigger('remove', {items: ids});
  44. }
  45. this._data = data;
  46. if (this._data) {
  47. // update fieldId
  48. this._fieldId = this._options.fieldId ||
  49. (this._data && this._data.options && this._data.options.fieldId) ||
  50. 'id';
  51. // trigger an add of all added items
  52. ids = this._data.getIds({filter: this._options && this._options.filter});
  53. for (i = 0, len = ids.length; i < len; i++) {
  54. id = ids[i];
  55. this._ids[id] = true;
  56. }
  57. this.length = ids.length;
  58. this._trigger('add', {items: ids});
  59. // subscribe to new dataset
  60. if (this._data.on) {
  61. this._data.on('*', this.listener);
  62. }
  63. }
  64. };
  65. /**
  66. * Refresh the DataView. Useful when the DataView has a filter function
  67. * containing a variable parameter.
  68. */
  69. DataView.prototype.refresh = function () {
  70. var id, i, len;
  71. var ids = this._data.getIds({filter: this._options && this._options.filter});
  72. var oldIds = Object.keys(this._ids);
  73. var newIds = {};
  74. var added = [];
  75. var removed = [];
  76. // check for additions
  77. for (i = 0, len = ids.length; i < len; i++) {
  78. id = ids[i];
  79. newIds[id] = true;
  80. if (!this._ids[id]) {
  81. added.push(id);
  82. this._ids[id] = true;
  83. }
  84. }
  85. // check for removals
  86. for (i = 0, len = oldIds.length; i < len; i++) {
  87. id = oldIds[i];
  88. if (!newIds[id]) {
  89. removed.push(id);
  90. delete this._ids[id];
  91. }
  92. }
  93. this.length += added.length - removed.length;
  94. // trigger events
  95. if (added.length) {
  96. this._trigger('add', {items: added});
  97. }
  98. if (removed.length) {
  99. this._trigger('remove', {items: removed});
  100. }
  101. };
  102. /**
  103. * Get data from the data view
  104. *
  105. * Usage:
  106. *
  107. * get()
  108. * get(options: Object)
  109. * get(options: Object, data: Array | DataTable)
  110. *
  111. * get(id: Number)
  112. * get(id: Number, options: Object)
  113. * get(id: Number, options: Object, data: Array | DataTable)
  114. *
  115. * get(ids: Number[])
  116. * get(ids: Number[], options: Object)
  117. * get(ids: Number[], options: Object, data: Array | DataTable)
  118. *
  119. * Where:
  120. *
  121. * {Number | String} id The id of an item
  122. * {Number[] | String{}} ids An array with ids of items
  123. * {Object} options An Object with options. Available options:
  124. * {String} [type] Type of data to be returned. Can
  125. * be 'DataTable' or 'Array' (default)
  126. * {Object.<String, String>} [convert]
  127. * {String[]} [fields] field names to be returned
  128. * {function} [filter] filter items
  129. * {String | function} [order] Order the items by
  130. * a field name or custom sort function.
  131. * {Array | DataTable} [data] If provided, items will be appended to this
  132. * array or table. Required in case of Google
  133. * DataTable.
  134. * @param args
  135. */
  136. DataView.prototype.get = function (args) {
  137. var me = this;
  138. // parse the arguments
  139. var ids, options, data;
  140. var firstType = util.getType(arguments[0]);
  141. if (firstType == 'String' || firstType == 'Number' || firstType == 'Array') {
  142. // get(id(s) [, options] [, data])
  143. ids = arguments[0]; // can be a single id or an array with ids
  144. options = arguments[1];
  145. data = arguments[2];
  146. }
  147. else {
  148. // get([, options] [, data])
  149. options = arguments[0];
  150. data = arguments[1];
  151. }
  152. // extend the options with the default options and provided options
  153. var viewOptions = util.extend({}, this._options, options);
  154. // create a combined filter method when needed
  155. if (this._options.filter && options && options.filter) {
  156. viewOptions.filter = function (item) {
  157. return me._options.filter(item) && options.filter(item);
  158. }
  159. }
  160. // build up the call to the linked data set
  161. var getArguments = [];
  162. if (ids != undefined) {
  163. getArguments.push(ids);
  164. }
  165. getArguments.push(viewOptions);
  166. getArguments.push(data);
  167. return this._data && this._data.get.apply(this._data, getArguments);
  168. };
  169. /**
  170. * Get ids of all items or from a filtered set of items.
  171. * @param {Object} [options] An Object with options. Available options:
  172. * {function} [filter] filter items
  173. * {String | function} [order] Order the items by
  174. * a field name or custom sort function.
  175. * @return {Array} ids
  176. */
  177. DataView.prototype.getIds = function (options) {
  178. var ids;
  179. if (this._data) {
  180. var defaultFilter = this._options.filter;
  181. var filter;
  182. if (options && options.filter) {
  183. if (defaultFilter) {
  184. filter = function (item) {
  185. return defaultFilter(item) && options.filter(item);
  186. }
  187. }
  188. else {
  189. filter = options.filter;
  190. }
  191. }
  192. else {
  193. filter = defaultFilter;
  194. }
  195. ids = this._data.getIds({
  196. filter: filter,
  197. order: options && options.order
  198. });
  199. }
  200. else {
  201. ids = [];
  202. }
  203. return ids;
  204. };
  205. /**
  206. * Get the DataSet to which this DataView is connected. In case there is a chain
  207. * of multiple DataViews, the root DataSet of this chain is returned.
  208. * @return {DataSet} dataSet
  209. */
  210. DataView.prototype.getDataSet = function () {
  211. var dataSet = this;
  212. while (dataSet instanceof DataView) {
  213. dataSet = dataSet._data;
  214. }
  215. return dataSet || null;
  216. };
  217. /**
  218. * Event listener. Will propagate all events from the connected data set to
  219. * the subscribers of the DataView, but will filter the items and only trigger
  220. * when there are changes in the filtered data set.
  221. * @param {String} event
  222. * @param {Object | null} params
  223. * @param {String} senderId
  224. * @private
  225. */
  226. DataView.prototype._onEvent = function (event, params, senderId) {
  227. var i, len, id, item;
  228. var ids = params && params.items;
  229. var data = this._data;
  230. var updatedData = [];
  231. var added = [];
  232. var updated = [];
  233. var removed = [];
  234. if (ids && data) {
  235. switch (event) {
  236. case 'add':
  237. // filter the ids of the added items
  238. for (i = 0, len = ids.length; i < len; i++) {
  239. id = ids[i];
  240. item = this.get(id);
  241. if (item) {
  242. this._ids[id] = true;
  243. added.push(id);
  244. }
  245. }
  246. break;
  247. case 'update':
  248. // determine the event from the views viewpoint: an updated
  249. // item can be added, updated, or removed from this view.
  250. for (i = 0, len = ids.length; i < len; i++) {
  251. id = ids[i];
  252. item = this.get(id);
  253. if (item) {
  254. if (this._ids[id]) {
  255. updated.push(id);
  256. updatedData.push(params.data[i]);
  257. }
  258. else {
  259. this._ids[id] = true;
  260. added.push(id);
  261. }
  262. }
  263. else {
  264. if (this._ids[id]) {
  265. delete this._ids[id];
  266. removed.push(id);
  267. }
  268. else {
  269. // nothing interesting for me :-(
  270. }
  271. }
  272. }
  273. break;
  274. case 'remove':
  275. // filter the ids of the removed items
  276. for (i = 0, len = ids.length; i < len; i++) {
  277. id = ids[i];
  278. if (this._ids[id]) {
  279. delete this._ids[id];
  280. removed.push(id);
  281. }
  282. }
  283. break;
  284. }
  285. this.length += added.length - removed.length;
  286. if (added.length) {
  287. this._trigger('add', {items: added}, senderId);
  288. }
  289. if (updated.length) {
  290. this._trigger('update', {items: updated, data: updatedData}, senderId);
  291. }
  292. if (removed.length) {
  293. this._trigger('remove', {items: removed}, senderId);
  294. }
  295. }
  296. };
  297. // copy subscription functionality from DataSet
  298. DataView.prototype.on = DataSet.prototype.on;
  299. DataView.prototype.off = DataSet.prototype.off;
  300. DataView.prototype._trigger = DataSet.prototype._trigger;
  301. // TODO: make these functions deprecated (replaced with `on` and `off` since version 0.5)
  302. DataView.prototype.subscribe = DataView.prototype.on;
  303. DataView.prototype.unsubscribe = DataView.prototype.off;
  304. module.exports = DataView;