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.

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