vis-rails 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+ };