vis-rails 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.gitmodules +3 -0
  4. data/.project +11 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE +202 -0
  7. data/README.md +29 -0
  8. data/Rakefile +1 -0
  9. data/lib/vis/rails/engine.rb +6 -0
  10. data/lib/vis/rails/version.rb +5 -0
  11. data/lib/vis/rails.rb +7 -0
  12. data/vendor/assets/javascripts/vis.js +1 -0
  13. data/vendor/assets/stylesheets/vis.css +3 -0
  14. data/vendor/assets/vis/DataSet.js +936 -0
  15. data/vendor/assets/vis/DataView.js +281 -0
  16. data/vendor/assets/vis/EventBus.js +89 -0
  17. data/vendor/assets/vis/events.js +116 -0
  18. data/vendor/assets/vis/graph/ClusterMixin.js +1019 -0
  19. data/vendor/assets/vis/graph/Edge.js +620 -0
  20. data/vendor/assets/vis/graph/Graph.js +2111 -0
  21. data/vendor/assets/vis/graph/Groups.js +80 -0
  22. data/vendor/assets/vis/graph/Images.js +41 -0
  23. data/vendor/assets/vis/graph/NavigationMixin.js +245 -0
  24. data/vendor/assets/vis/graph/Node.js +978 -0
  25. data/vendor/assets/vis/graph/Popup.js +105 -0
  26. data/vendor/assets/vis/graph/SectorsMixin.js +547 -0
  27. data/vendor/assets/vis/graph/SelectionMixin.js +515 -0
  28. data/vendor/assets/vis/graph/dotparser.js +829 -0
  29. data/vendor/assets/vis/graph/img/downarrow.png +0 -0
  30. data/vendor/assets/vis/graph/img/leftarrow.png +0 -0
  31. data/vendor/assets/vis/graph/img/minus.png +0 -0
  32. data/vendor/assets/vis/graph/img/plus.png +0 -0
  33. data/vendor/assets/vis/graph/img/rightarrow.png +0 -0
  34. data/vendor/assets/vis/graph/img/uparrow.png +0 -0
  35. data/vendor/assets/vis/graph/img/zoomExtends.png +0 -0
  36. data/vendor/assets/vis/graph/shapes.js +225 -0
  37. data/vendor/assets/vis/module/exports.js +68 -0
  38. data/vendor/assets/vis/module/header.js +24 -0
  39. data/vendor/assets/vis/module/imports.js +32 -0
  40. data/vendor/assets/vis/shim.js +252 -0
  41. data/vendor/assets/vis/timeline/Controller.js +172 -0
  42. data/vendor/assets/vis/timeline/Range.js +553 -0
  43. data/vendor/assets/vis/timeline/Stack.js +192 -0
  44. data/vendor/assets/vis/timeline/TimeStep.js +449 -0
  45. data/vendor/assets/vis/timeline/Timeline.js +476 -0
  46. data/vendor/assets/vis/timeline/component/Component.js +148 -0
  47. data/vendor/assets/vis/timeline/component/ContentPanel.js +113 -0
  48. data/vendor/assets/vis/timeline/component/CurrentTime.js +101 -0
  49. data/vendor/assets/vis/timeline/component/CustomTime.js +255 -0
  50. data/vendor/assets/vis/timeline/component/Group.js +129 -0
  51. data/vendor/assets/vis/timeline/component/GroupSet.js +546 -0
  52. data/vendor/assets/vis/timeline/component/ItemSet.js +612 -0
  53. data/vendor/assets/vis/timeline/component/Panel.js +112 -0
  54. data/vendor/assets/vis/timeline/component/RootPanel.js +215 -0
  55. data/vendor/assets/vis/timeline/component/TimeAxis.js +522 -0
  56. data/vendor/assets/vis/timeline/component/css/currenttime.css +5 -0
  57. data/vendor/assets/vis/timeline/component/css/customtime.css +6 -0
  58. data/vendor/assets/vis/timeline/component/css/groupset.css +59 -0
  59. data/vendor/assets/vis/timeline/component/css/item.css +93 -0
  60. data/vendor/assets/vis/timeline/component/css/itemset.css +17 -0
  61. data/vendor/assets/vis/timeline/component/css/panel.css +14 -0
  62. data/vendor/assets/vis/timeline/component/css/timeaxis.css +41 -0
  63. data/vendor/assets/vis/timeline/component/css/timeline.css +2 -0
  64. data/vendor/assets/vis/timeline/component/item/Item.js +81 -0
  65. data/vendor/assets/vis/timeline/component/item/ItemBox.js +302 -0
  66. data/vendor/assets/vis/timeline/component/item/ItemPoint.js +237 -0
  67. data/vendor/assets/vis/timeline/component/item/ItemRange.js +251 -0
  68. data/vendor/assets/vis/timeline/component/item/ItemRangeOverflow.js +91 -0
  69. data/vendor/assets/vis/util.js +673 -0
  70. data/vis-rails.gemspec +47 -0
  71. metadata +142 -0
@@ -0,0 +1,476 @@
1
+ /**
2
+ * Create a timeline visualization
3
+ * @param {HTMLElement} container
4
+ * @param {vis.DataSet | Array | DataTable} [items]
5
+ * @param {Object} [options] See Timeline.setOptions for the available options.
6
+ * @constructor
7
+ */
8
+ function Timeline (container, items, options) {
9
+ var me = this;
10
+ var now = moment().hours(0).minutes(0).seconds(0).milliseconds(0);
11
+ this.options = {
12
+ orientation: 'bottom',
13
+ min: null,
14
+ max: null,
15
+ zoomMin: 10, // milliseconds
16
+ zoomMax: 1000 * 60 * 60 * 24 * 365 * 10000, // milliseconds
17
+ // moveable: true, // TODO: option moveable
18
+ // zoomable: true, // TODO: option zoomable
19
+ showMinorLabels: true,
20
+ showMajorLabels: true,
21
+ showCurrentTime: false,
22
+ showCustomTime: false,
23
+ autoResize: false
24
+ };
25
+
26
+ // controller
27
+ this.controller = new Controller();
28
+
29
+ // root panel
30
+ if (!container) {
31
+ throw new Error('No container element provided');
32
+ }
33
+ var rootOptions = Object.create(this.options);
34
+ rootOptions.height = function () {
35
+ // TODO: change to height
36
+ if (me.options.height) {
37
+ // fixed height
38
+ return me.options.height;
39
+ }
40
+ else {
41
+ // auto height
42
+ return (me.timeaxis.height + me.content.height) + 'px';
43
+ }
44
+ };
45
+ this.rootPanel = new RootPanel(container, rootOptions);
46
+ this.controller.add(this.rootPanel);
47
+
48
+ // item panel
49
+ var itemOptions = Object.create(this.options);
50
+ itemOptions.left = function () {
51
+ return me.labelPanel.width;
52
+ };
53
+ itemOptions.width = function () {
54
+ return me.rootPanel.width - me.labelPanel.width;
55
+ };
56
+ itemOptions.top = null;
57
+ itemOptions.height = null;
58
+ this.itemPanel = new Panel(this.rootPanel, [], itemOptions);
59
+ this.controller.add(this.itemPanel);
60
+
61
+ // label panel
62
+ var labelOptions = Object.create(this.options);
63
+ labelOptions.top = null;
64
+ labelOptions.left = null;
65
+ labelOptions.height = null;
66
+ labelOptions.width = function () {
67
+ if (me.content && typeof me.content.getLabelsWidth === 'function') {
68
+ return me.content.getLabelsWidth();
69
+ }
70
+ else {
71
+ return 0;
72
+ }
73
+ };
74
+ this.labelPanel = new Panel(this.rootPanel, [], labelOptions);
75
+ this.controller.add(this.labelPanel);
76
+
77
+ // range
78
+ var rangeOptions = Object.create(this.options);
79
+ this.range = new Range(rangeOptions);
80
+ this.range.setRange(
81
+ now.clone().add('days', -3).valueOf(),
82
+ now.clone().add('days', 4).valueOf()
83
+ );
84
+
85
+ // TODO: reckon with options moveable and zoomable
86
+ // TODO: put the listeners in setOptions, be able to dynamically change with options moveable and zoomable
87
+ this.range.subscribe(this.rootPanel, 'move', 'horizontal');
88
+ this.range.subscribe(this.rootPanel, 'zoom', 'horizontal');
89
+ this.range.on('rangechange', function (properties) {
90
+ var force = true;
91
+ me.controller.requestReflow(force);
92
+ me._trigger('rangechange', properties);
93
+ });
94
+ this.range.on('rangechanged', function (properties) {
95
+ var force = true;
96
+ me.controller.requestReflow(force);
97
+ me._trigger('rangechanged', properties);
98
+ });
99
+
100
+ // single select (or unselect) when tapping an item
101
+ // TODO: implement ctrl+click
102
+ this.rootPanel.on('tap', this._onSelectItem.bind(this));
103
+
104
+ // multi select when holding mouse/touch, or on ctrl+click
105
+ this.rootPanel.on('hold', this._onMultiSelectItem.bind(this));
106
+
107
+ // time axis
108
+ var timeaxisOptions = Object.create(rootOptions);
109
+ timeaxisOptions.range = this.range;
110
+ timeaxisOptions.left = null;
111
+ timeaxisOptions.top = null;
112
+ timeaxisOptions.width = '100%';
113
+ timeaxisOptions.height = null;
114
+ this.timeaxis = new TimeAxis(this.itemPanel, [], timeaxisOptions);
115
+ this.timeaxis.setRange(this.range);
116
+ this.controller.add(this.timeaxis);
117
+
118
+ // current time bar
119
+ this.currenttime = new CurrentTime(this.timeaxis, [], rootOptions);
120
+ this.controller.add(this.currenttime);
121
+
122
+ // custom time bar
123
+ this.customtime = new CustomTime(this.timeaxis, [], rootOptions);
124
+ this.controller.add(this.customtime);
125
+
126
+ // create groupset
127
+ this.setGroups(null);
128
+
129
+ this.itemsData = null; // DataSet
130
+ this.groupsData = null; // DataSet
131
+
132
+ // apply options
133
+ if (options) {
134
+ this.setOptions(options);
135
+ }
136
+
137
+ // create itemset and groupset
138
+ if (items) {
139
+ this.setItems(items);
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Set options
145
+ * @param {Object} options TODO: describe the available options
146
+ */
147
+ Timeline.prototype.setOptions = function (options) {
148
+ util.extend(this.options, options);
149
+
150
+ // force update of range (apply new min/max etc.)
151
+ // both start and end are optional
152
+ this.range.setRange(options.start, options.end);
153
+
154
+ this.controller.reflow();
155
+ this.controller.repaint();
156
+ };
157
+
158
+ /**
159
+ * Set a custom time bar
160
+ * @param {Date} time
161
+ */
162
+ Timeline.prototype.setCustomTime = function (time) {
163
+ this.customtime._setCustomTime(time);
164
+ };
165
+
166
+ /**
167
+ * Retrieve the current custom time.
168
+ * @return {Date} customTime
169
+ */
170
+ Timeline.prototype.getCustomTime = function() {
171
+ return new Date(this.customtime.customTime.valueOf());
172
+ };
173
+
174
+ /**
175
+ * Set items
176
+ * @param {vis.DataSet | Array | DataTable | null} items
177
+ */
178
+ Timeline.prototype.setItems = function(items) {
179
+ var initialLoad = (this.itemsData == null);
180
+
181
+ // convert to type DataSet when needed
182
+ var newItemSet;
183
+ if (!items) {
184
+ newItemSet = null;
185
+ }
186
+ else if (items instanceof DataSet) {
187
+ newItemSet = items;
188
+ }
189
+ if (!(items instanceof DataSet)) {
190
+ newItemSet = new DataSet({
191
+ convert: {
192
+ start: 'Date',
193
+ end: 'Date'
194
+ }
195
+ });
196
+ newItemSet.add(items);
197
+ }
198
+
199
+ // set items
200
+ this.itemsData = newItemSet;
201
+ this.content.setItems(newItemSet);
202
+
203
+ if (initialLoad && (this.options.start == undefined || this.options.end == undefined)) {
204
+ // apply the data range as range
205
+ var dataRange = this.getItemRange();
206
+
207
+ // add 5% space on both sides
208
+ var start = dataRange.min;
209
+ var end = dataRange.max;
210
+ if (start != null && end != null) {
211
+ var interval = (end.valueOf() - start.valueOf());
212
+ if (interval <= 0) {
213
+ // prevent an empty interval
214
+ interval = 24 * 60 * 60 * 1000; // 1 day
215
+ }
216
+ start = new Date(start.valueOf() - interval * 0.05);
217
+ end = new Date(end.valueOf() + interval * 0.05);
218
+ }
219
+
220
+ // override specified start and/or end date
221
+ if (this.options.start != undefined) {
222
+ start = util.convert(this.options.start, 'Date');
223
+ }
224
+ if (this.options.end != undefined) {
225
+ end = util.convert(this.options.end, 'Date');
226
+ }
227
+
228
+ // apply range if there is a min or max available
229
+ if (start != null || end != null) {
230
+ this.range.setRange(start, end);
231
+ }
232
+ }
233
+ };
234
+
235
+ /**
236
+ * Set groups
237
+ * @param {vis.DataSet | Array | DataTable} groups
238
+ */
239
+ Timeline.prototype.setGroups = function(groups) {
240
+ var me = this;
241
+ this.groupsData = groups;
242
+
243
+ // switch content type between ItemSet or GroupSet when needed
244
+ var Type = this.groupsData ? GroupSet : ItemSet;
245
+ if (!(this.content instanceof Type)) {
246
+ // remove old content set
247
+ if (this.content) {
248
+ this.content.hide();
249
+ if (this.content.setItems) {
250
+ this.content.setItems(); // disconnect from items
251
+ }
252
+ if (this.content.setGroups) {
253
+ this.content.setGroups(); // disconnect from groups
254
+ }
255
+ this.controller.remove(this.content);
256
+ }
257
+
258
+ // create new content set
259
+ var options = Object.create(this.options);
260
+ util.extend(options, {
261
+ top: function () {
262
+ if (me.options.orientation == 'top') {
263
+ return me.timeaxis.height;
264
+ }
265
+ else {
266
+ return me.itemPanel.height - me.timeaxis.height - me.content.height;
267
+ }
268
+ },
269
+ left: null,
270
+ width: '100%',
271
+ height: function () {
272
+ if (me.options.height) {
273
+ // fixed height
274
+ return me.itemPanel.height - me.timeaxis.height;
275
+ }
276
+ else {
277
+ // auto height
278
+ return null;
279
+ }
280
+ },
281
+ maxHeight: function () {
282
+ // TODO: change maxHeight to be a css string like '100%' or '300px'
283
+ if (me.options.maxHeight) {
284
+ if (!util.isNumber(me.options.maxHeight)) {
285
+ throw new TypeError('Number expected for property maxHeight');
286
+ }
287
+ return me.options.maxHeight - me.timeaxis.height;
288
+ }
289
+ else {
290
+ return null;
291
+ }
292
+ },
293
+ labelContainer: function () {
294
+ return me.labelPanel.getContainer();
295
+ }
296
+ });
297
+
298
+ this.content = new Type(this.itemPanel, [this.timeaxis], options);
299
+ if (this.content.setRange) {
300
+ this.content.setRange(this.range);
301
+ }
302
+ if (this.content.setItems) {
303
+ this.content.setItems(this.itemsData);
304
+ }
305
+ if (this.content.setGroups) {
306
+ this.content.setGroups(this.groupsData);
307
+ }
308
+ this.controller.add(this.content);
309
+ }
310
+ };
311
+
312
+ /**
313
+ * Get the data range of the item set.
314
+ * @returns {{min: Date, max: Date}} range A range with a start and end Date.
315
+ * When no minimum is found, min==null
316
+ * When no maximum is found, max==null
317
+ */
318
+ Timeline.prototype.getItemRange = function getItemRange() {
319
+ // calculate min from start filed
320
+ var itemsData = this.itemsData,
321
+ min = null,
322
+ max = null;
323
+
324
+ if (itemsData) {
325
+ // calculate the minimum value of the field 'start'
326
+ var minItem = itemsData.min('start');
327
+ min = minItem ? minItem.start.valueOf() : null;
328
+
329
+ // calculate maximum value of fields 'start' and 'end'
330
+ var maxStartItem = itemsData.max('start');
331
+ if (maxStartItem) {
332
+ max = maxStartItem.start.valueOf();
333
+ }
334
+ var maxEndItem = itemsData.max('end');
335
+ if (maxEndItem) {
336
+ if (max == null) {
337
+ max = maxEndItem.end.valueOf();
338
+ }
339
+ else {
340
+ max = Math.max(max, maxEndItem.end.valueOf());
341
+ }
342
+ }
343
+ }
344
+
345
+ return {
346
+ min: (min != null) ? new Date(min) : null,
347
+ max: (max != null) ? new Date(max) : null
348
+ };
349
+ };
350
+
351
+ /**
352
+ * Set selected items by their id. Replaces the current selection
353
+ * Unknown id's are silently ignored.
354
+ * @param {Array} [ids] An array with zero or more id's of the items to be
355
+ * selected. If ids is an empty array, all items will be
356
+ * unselected.
357
+ */
358
+ Timeline.prototype.setSelection = function setSelection (ids) {
359
+ if (this.content) this.content.setSelection(ids);
360
+ };
361
+
362
+ /**
363
+ * Get the selected items by their id
364
+ * @return {Array} ids The ids of the selected items
365
+ */
366
+ Timeline.prototype.getSelection = function getSelection() {
367
+ return this.content ? this.content.getSelection() : [];
368
+ };
369
+
370
+ /**
371
+ * Add event listener
372
+ * @param {String} event Event name. Available events:
373
+ * 'rangechange', 'rangechanged', 'select'
374
+ * @param {function} callback Callback function, invoked as callback(properties)
375
+ * where properties is an optional object containing
376
+ * event specific properties.
377
+ */
378
+ Timeline.prototype.on = function on (event, callback) {
379
+ var available = ['rangechange', 'rangechanged', 'select'];
380
+
381
+ if (available.indexOf(event) == -1) {
382
+ throw new Error('Unknown event "' + event + '". Choose from ' + available.join());
383
+ }
384
+
385
+ events.addListener(this, event, callback);
386
+ };
387
+
388
+ /**
389
+ * Remove an event listener
390
+ * @param {String} event Event name
391
+ * @param {function} callback Callback function
392
+ */
393
+ Timeline.prototype.off = function off (event, callback) {
394
+ events.removeListener(this, event, callback);
395
+ };
396
+
397
+ /**
398
+ * Trigger an event
399
+ * @param {String} event Event name, available events: 'rangechange',
400
+ * 'rangechanged', 'select'
401
+ * @param {Object} [properties] Event specific properties
402
+ * @private
403
+ */
404
+ Timeline.prototype._trigger = function _trigger(event, properties) {
405
+ events.trigger(this, event, properties || {});
406
+ };
407
+
408
+ /**
409
+ * Handle selecting/deselecting an item when tapping it
410
+ * @param {Event} event
411
+ * @private
412
+ */
413
+ Timeline.prototype._onSelectItem = function (event) {
414
+ var item = this._itemFromTarget(event);
415
+
416
+ var selection = item ? [item.id] : [];
417
+ this.setSelection(selection);
418
+
419
+ this._trigger('select', {
420
+ items: this.getSelection()
421
+ });
422
+
423
+ event.stopPropagation();
424
+ };
425
+
426
+ /**
427
+ * Handle selecting/deselecting multiple items when holding an item
428
+ * @param {Event} event
429
+ * @private
430
+ */
431
+ Timeline.prototype._onMultiSelectItem = function (event) {
432
+ var selection,
433
+ item = this._itemFromTarget(event);
434
+
435
+ if (!item) {
436
+ // do nothing...
437
+ return;
438
+ }
439
+
440
+ selection = this.getSelection(); // current selection
441
+ var index = selection.indexOf(item.id);
442
+ if (index == -1) {
443
+ // item is not yet selected -> select it
444
+ selection.push(item.id);
445
+ }
446
+ else {
447
+ // item is already selected -> deselect it
448
+ selection.splice(index, 1);
449
+ }
450
+ this.setSelection(selection);
451
+
452
+ this._trigger('select', {
453
+ items: this.getSelection()
454
+ });
455
+
456
+ event.stopPropagation();
457
+ };
458
+
459
+ /**
460
+ * Find an item from an event target:
461
+ * searches for the attribute 'timeline-item' in the event target's element tree
462
+ * @param {Event} event
463
+ * @return {Item | null| item
464
+ * @private
465
+ */
466
+ Timeline.prototype._itemFromTarget = function _itemFromTarget (event) {
467
+ var target = event.target;
468
+ while (target) {
469
+ if (target.hasOwnProperty('timeline-item')) {
470
+ return target['timeline-item'];
471
+ }
472
+ target = target.parentNode;
473
+ }
474
+
475
+ return null;
476
+ };
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Prototype for visual components
3
+ */
4
+ function Component () {
5
+ this.id = null;
6
+ this.parent = null;
7
+ this.depends = null;
8
+ this.controller = null;
9
+ this.options = null;
10
+
11
+ this.frame = null; // main DOM element
12
+ this.top = 0;
13
+ this.left = 0;
14
+ this.width = 0;
15
+ this.height = 0;
16
+ }
17
+
18
+ /**
19
+ * Set parameters for the frame. Parameters will be merged in current parameter
20
+ * set.
21
+ * @param {Object} options Available parameters:
22
+ * {String | function} [className]
23
+ * {EventBus} [eventBus]
24
+ * {String | Number | function} [left]
25
+ * {String | Number | function} [top]
26
+ * {String | Number | function} [width]
27
+ * {String | Number | function} [height]
28
+ */
29
+ Component.prototype.setOptions = function setOptions(options) {
30
+ if (options) {
31
+ util.extend(this.options, options);
32
+
33
+ if (this.controller) {
34
+ this.requestRepaint();
35
+ this.requestReflow();
36
+ }
37
+ }
38
+ };
39
+
40
+ /**
41
+ * Get an option value by name
42
+ * The function will first check this.options object, and else will check
43
+ * this.defaultOptions.
44
+ * @param {String} name
45
+ * @return {*} value
46
+ */
47
+ Component.prototype.getOption = function getOption(name) {
48
+ var value;
49
+ if (this.options) {
50
+ value = this.options[name];
51
+ }
52
+ if (value === undefined && this.defaultOptions) {
53
+ value = this.defaultOptions[name];
54
+ }
55
+ return value;
56
+ };
57
+
58
+ /**
59
+ * Get the container element of the component, which can be used by a child to
60
+ * add its own widgets. Not all components do have a container for childs, in
61
+ * that case null is returned.
62
+ * @returns {HTMLElement | null} container
63
+ */
64
+ // TODO: get rid of the getContainer and getFrame methods, provide these via the options
65
+ Component.prototype.getContainer = function getContainer() {
66
+ // should be implemented by the component
67
+ return null;
68
+ };
69
+
70
+ /**
71
+ * Get the frame element of the component, the outer HTML DOM element.
72
+ * @returns {HTMLElement | null} frame
73
+ */
74
+ Component.prototype.getFrame = function getFrame() {
75
+ return this.frame;
76
+ };
77
+
78
+ /**
79
+ * Repaint the component
80
+ * @return {Boolean} changed
81
+ */
82
+ Component.prototype.repaint = function repaint() {
83
+ // should be implemented by the component
84
+ return false;
85
+ };
86
+
87
+ /**
88
+ * Reflow the component
89
+ * @return {Boolean} resized
90
+ */
91
+ Component.prototype.reflow = function reflow() {
92
+ // should be implemented by the component
93
+ return false;
94
+ };
95
+
96
+ /**
97
+ * Hide the component from the DOM
98
+ * @return {Boolean} changed
99
+ */
100
+ Component.prototype.hide = function hide() {
101
+ if (this.frame && this.frame.parentNode) {
102
+ this.frame.parentNode.removeChild(this.frame);
103
+ return true;
104
+ }
105
+ else {
106
+ return false;
107
+ }
108
+ };
109
+
110
+ /**
111
+ * Show the component in the DOM (when not already visible).
112
+ * A repaint will be executed when the component is not visible
113
+ * @return {Boolean} changed
114
+ */
115
+ Component.prototype.show = function show() {
116
+ if (!this.frame || !this.frame.parentNode) {
117
+ return this.repaint();
118
+ }
119
+ else {
120
+ return false;
121
+ }
122
+ };
123
+
124
+ /**
125
+ * Request a repaint. The controller will schedule a repaint
126
+ */
127
+ Component.prototype.requestRepaint = function requestRepaint() {
128
+ if (this.controller) {
129
+ this.controller.requestRepaint();
130
+ }
131
+ else {
132
+ throw new Error('Cannot request a repaint: no controller configured');
133
+ // TODO: just do a repaint when no parent is configured?
134
+ }
135
+ };
136
+
137
+ /**
138
+ * Request a reflow. The controller will schedule a reflow
139
+ */
140
+ Component.prototype.requestReflow = function requestReflow() {
141
+ if (this.controller) {
142
+ this.controller.requestReflow();
143
+ }
144
+ else {
145
+ throw new Error('Cannot request a reflow: no controller configured');
146
+ // TODO: just do a reflow when no parent is configured?
147
+ }
148
+ };