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.

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