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.

281 lines
7.5 KiB

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