vis-rails 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.gitmodules +3 -0
- data/.project +11 -0
- data/Gemfile +4 -0
- data/LICENSE +202 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/lib/vis/rails/engine.rb +6 -0
- data/lib/vis/rails/version.rb +5 -0
- data/lib/vis/rails.rb +7 -0
- data/vendor/assets/javascripts/vis.js +1 -0
- data/vendor/assets/stylesheets/vis.css +3 -0
- data/vendor/assets/vis/DataSet.js +936 -0
- data/vendor/assets/vis/DataView.js +281 -0
- data/vendor/assets/vis/EventBus.js +89 -0
- data/vendor/assets/vis/events.js +116 -0
- data/vendor/assets/vis/graph/ClusterMixin.js +1019 -0
- data/vendor/assets/vis/graph/Edge.js +620 -0
- data/vendor/assets/vis/graph/Graph.js +2111 -0
- data/vendor/assets/vis/graph/Groups.js +80 -0
- data/vendor/assets/vis/graph/Images.js +41 -0
- data/vendor/assets/vis/graph/NavigationMixin.js +245 -0
- data/vendor/assets/vis/graph/Node.js +978 -0
- data/vendor/assets/vis/graph/Popup.js +105 -0
- data/vendor/assets/vis/graph/SectorsMixin.js +547 -0
- data/vendor/assets/vis/graph/SelectionMixin.js +515 -0
- data/vendor/assets/vis/graph/dotparser.js +829 -0
- data/vendor/assets/vis/graph/img/downarrow.png +0 -0
- data/vendor/assets/vis/graph/img/leftarrow.png +0 -0
- data/vendor/assets/vis/graph/img/minus.png +0 -0
- data/vendor/assets/vis/graph/img/plus.png +0 -0
- data/vendor/assets/vis/graph/img/rightarrow.png +0 -0
- data/vendor/assets/vis/graph/img/uparrow.png +0 -0
- data/vendor/assets/vis/graph/img/zoomExtends.png +0 -0
- data/vendor/assets/vis/graph/shapes.js +225 -0
- data/vendor/assets/vis/module/exports.js +68 -0
- data/vendor/assets/vis/module/header.js +24 -0
- data/vendor/assets/vis/module/imports.js +32 -0
- data/vendor/assets/vis/shim.js +252 -0
- data/vendor/assets/vis/timeline/Controller.js +172 -0
- data/vendor/assets/vis/timeline/Range.js +553 -0
- data/vendor/assets/vis/timeline/Stack.js +192 -0
- data/vendor/assets/vis/timeline/TimeStep.js +449 -0
- data/vendor/assets/vis/timeline/Timeline.js +476 -0
- data/vendor/assets/vis/timeline/component/Component.js +148 -0
- data/vendor/assets/vis/timeline/component/ContentPanel.js +113 -0
- data/vendor/assets/vis/timeline/component/CurrentTime.js +101 -0
- data/vendor/assets/vis/timeline/component/CustomTime.js +255 -0
- data/vendor/assets/vis/timeline/component/Group.js +129 -0
- data/vendor/assets/vis/timeline/component/GroupSet.js +546 -0
- data/vendor/assets/vis/timeline/component/ItemSet.js +612 -0
- data/vendor/assets/vis/timeline/component/Panel.js +112 -0
- data/vendor/assets/vis/timeline/component/RootPanel.js +215 -0
- data/vendor/assets/vis/timeline/component/TimeAxis.js +522 -0
- data/vendor/assets/vis/timeline/component/css/currenttime.css +5 -0
- data/vendor/assets/vis/timeline/component/css/customtime.css +6 -0
- data/vendor/assets/vis/timeline/component/css/groupset.css +59 -0
- data/vendor/assets/vis/timeline/component/css/item.css +93 -0
- data/vendor/assets/vis/timeline/component/css/itemset.css +17 -0
- data/vendor/assets/vis/timeline/component/css/panel.css +14 -0
- data/vendor/assets/vis/timeline/component/css/timeaxis.css +41 -0
- data/vendor/assets/vis/timeline/component/css/timeline.css +2 -0
- data/vendor/assets/vis/timeline/component/item/Item.js +81 -0
- data/vendor/assets/vis/timeline/component/item/ItemBox.js +302 -0
- data/vendor/assets/vis/timeline/component/item/ItemPoint.js +237 -0
- data/vendor/assets/vis/timeline/component/item/ItemRange.js +251 -0
- data/vendor/assets/vis/timeline/component/item/ItemRangeOverflow.js +91 -0
- data/vendor/assets/vis/util.js +673 -0
- data/vis-rails.gemspec +47 -0
- 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
|
+
};
|