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.

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