| @ -1,467 +0,0 @@ | |||
| // TODO: remove groupset | |||
| /** | |||
| * An GroupSet holds a set of groups | |||
| * @param {Panel} contentPanel Panel where the ItemSets will be created | |||
| * @param {Panel} labelPanel Panel where the labels will be created | |||
| * @param {Panel} backgroundPanel Panel where the vertical lines of box | |||
| * items are created | |||
| * @param {Panel} axisPanel Panel on the axis where the dots of box | |||
| * items will be created | |||
| * @param {Object} [options] See GroupSet.setOptions for the available | |||
| * options. | |||
| * @constructor GroupSet | |||
| * @extends Panel | |||
| */ | |||
| function GroupSet(contentPanel, labelPanel, backgroundPanel, axisPanel, options) { | |||
| this.id = util.randomUUID(); | |||
| this.contentPanel = contentPanel; | |||
| this.labelPanel = labelPanel; | |||
| this.backgroundPanel = backgroundPanel; | |||
| this.axisPanel = axisPanel; | |||
| this.options = options || {}; | |||
| this.range = null; // Range or Object {start: number, end: number} | |||
| this.itemsData = null; // DataSet with items | |||
| this.groupsData = null; // DataSet with groups | |||
| this.groups = {}; // map with groups | |||
| this.groupIds = []; // list with ordered group ids | |||
| this.dom = {}; | |||
| this.props = { | |||
| labels: { | |||
| width: 0 | |||
| } | |||
| }; | |||
| // TODO: implement right orientation of the labels (left/right) | |||
| var me = this; | |||
| this.listeners = { | |||
| 'add': function (event, params) { | |||
| me._onAdd(params.items); | |||
| }, | |||
| 'update': function (event, params) { | |||
| me._onUpdate(params.items); | |||
| }, | |||
| 'remove': function (event, params) { | |||
| me._onRemove(params.items); | |||
| } | |||
| }; | |||
| // create HTML DOM | |||
| this._create(); | |||
| } | |||
| GroupSet.prototype = new Panel(); | |||
| /** | |||
| * Create the HTML DOM elements for the GroupSet | |||
| * @private | |||
| */ | |||
| GroupSet.prototype._create = function _create () { | |||
| // TODO: reimplement groupSet DOM elements | |||
| var frame = document.createElement('div'); | |||
| frame.className = 'groupset'; | |||
| frame['timeline-groupset'] = this; | |||
| this.frame = frame; | |||
| this.labelSet = new Panel({ | |||
| className: 'labelset', | |||
| width: '100%', | |||
| height: '100%' | |||
| }); | |||
| this.labelPanel.appendChild(this.labelSet); | |||
| }; | |||
| /** | |||
| * Get the frame element of component | |||
| * @returns {null} Get frame is not supported by GroupSet | |||
| */ | |||
| GroupSet.prototype.getFrame = function getFrame() { | |||
| return this.frame; | |||
| }; | |||
| /** | |||
| * Set options for the GroupSet. Existing options will be extended/overwritten. | |||
| * @param {Object} [options] The following options are available: | |||
| * {String | function} groupsOrder | |||
| * TODO: describe options | |||
| */ | |||
| GroupSet.prototype.setOptions = Component.prototype.setOptions; | |||
| /** | |||
| * Set range (start and end). | |||
| * @param {Range | Object} range A Range or an object containing start and end. | |||
| */ | |||
| GroupSet.prototype.setRange = function (range) { | |||
| this.range = range; | |||
| for (var id in this.groups) { | |||
| if (this.groups.hasOwnProperty(id)) { | |||
| this.groups[id].setRange(range); | |||
| } | |||
| } | |||
| }; | |||
| /** | |||
| * Set items | |||
| * @param {vis.DataSet | null} items | |||
| */ | |||
| GroupSet.prototype.setItems = function setItems(items) { | |||
| this.itemsData = items; | |||
| for (var id in this.groups) { | |||
| if (this.groups.hasOwnProperty(id)) { | |||
| var group = this.groups[id]; | |||
| // TODO: every group will emit a change event, causing a lot of unnecessary repaints. improve this. | |||
| group.setItems(items); | |||
| } | |||
| } | |||
| }; | |||
| /** | |||
| * Get items | |||
| * @return {vis.DataSet | null} items | |||
| */ | |||
| GroupSet.prototype.getItems = function getItems() { | |||
| return this.itemsData; | |||
| }; | |||
| /** | |||
| * Set range (start and end). | |||
| * @param {Range | Object} range A Range or an object containing start and end. | |||
| */ | |||
| GroupSet.prototype.setRange = function setRange(range) { | |||
| this.range = range; | |||
| }; | |||
| /** | |||
| * Set groups | |||
| * @param {vis.DataSet} groups | |||
| */ | |||
| GroupSet.prototype.setGroups = function setGroups(groups) { | |||
| var me = this, | |||
| ids; | |||
| // unsubscribe from current dataset | |||
| if (this.groupsData) { | |||
| util.forEach(this.listeners, function (callback, event) { | |||
| me.groupsData.unsubscribe(event, callback); | |||
| }); | |||
| // remove all drawn groups | |||
| ids = this.groupsData.getIds(); | |||
| this._onRemove(ids); | |||
| } | |||
| // replace the dataset | |||
| if (!groups) { | |||
| this.groupsData = null; | |||
| } | |||
| else if (groups instanceof DataSet) { | |||
| this.groupsData = groups; | |||
| } | |||
| else { | |||
| this.groupsData = new DataSet({ | |||
| convert: { | |||
| start: 'Date', | |||
| end: 'Date' | |||
| } | |||
| }); | |||
| this.groupsData.add(groups); | |||
| } | |||
| if (this.groupsData) { | |||
| // subscribe to new dataset | |||
| var id = this.id; | |||
| util.forEach(this.listeners, function (callback, event) { | |||
| me.groupsData.on(event, callback, id); | |||
| }); | |||
| // draw all new groups | |||
| ids = this.groupsData.getIds(); | |||
| this._onAdd(ids); | |||
| } | |||
| this.emit('change'); | |||
| }; | |||
| /** | |||
| * Get groups | |||
| * @return {vis.DataSet | null} groups | |||
| */ | |||
| GroupSet.prototype.getGroups = function getGroups() { | |||
| return this.groupsData; | |||
| }; | |||
| /** | |||
| * Set selected items by their id. Replaces the current selection. | |||
| * Unknown id's are silently ignored. | |||
| * @param {Array} [ids] An array with zero or more id's of the items to be | |||
| * selected. If ids is an empty array, all items will be | |||
| * unselected. | |||
| */ | |||
| GroupSet.prototype.setSelection = function setSelection(ids) { | |||
| var selection = [], | |||
| groups = this.groups; | |||
| // iterate over each of the groups | |||
| for (var id in groups) { | |||
| if (groups.hasOwnProperty(id)) { | |||
| var group = groups[id]; | |||
| group.setSelection(ids); | |||
| } | |||
| } | |||
| return selection; | |||
| }; | |||
| /** | |||
| * Get the selected items by their id | |||
| * @return {Array} ids The ids of the selected items | |||
| */ | |||
| GroupSet.prototype.getSelection = function getSelection() { | |||
| var selection = [], | |||
| groups = this.groups; | |||
| // iterate over each of the groups | |||
| for (var id in groups) { | |||
| if (groups.hasOwnProperty(id)) { | |||
| var group = groups[id]; | |||
| selection = selection.concat(group.getSelection()); | |||
| } | |||
| } | |||
| return selection; | |||
| }; | |||
| /** | |||
| * Repaint the component | |||
| * @return {boolean} Returns true if the component was resized since previous repaint | |||
| */ | |||
| GroupSet.prototype.repaint = function repaint() { | |||
| var i, id, group, | |||
| asSize = util.option.asSize, | |||
| asString = util.option.asString, | |||
| options = this.options, | |||
| orientation = this.getOption('orientation'), | |||
| frame = this.frame, | |||
| resized = false, | |||
| groups = this.groups; | |||
| // repaint all groups in order | |||
| this.groupIds.forEach(function (id) { | |||
| var groupResized = groups[id].repaint(); | |||
| resized = resized || groupResized; | |||
| }); | |||
| // reposition the labels and calculate the maximum label width | |||
| var maxWidth = 0; | |||
| for (id in groups) { | |||
| if (groups.hasOwnProperty(id)) { | |||
| group = groups[id]; | |||
| maxWidth = Math.max(maxWidth, group.props.label.width); | |||
| } | |||
| } | |||
| resized = util.updateProperty(this.props.labels, 'width', maxWidth) || resized; | |||
| // recalculate the height of the groupset, and recalculate top positions of the groups | |||
| var fixedHeight = (asSize(options.height) != null); | |||
| var height; | |||
| if (!fixedHeight) { | |||
| // height is not specified, calculate the sum of the height of all groups | |||
| height = 0; | |||
| this.groupIds.forEach(function (id) { | |||
| var group = groups[id]; | |||
| group.top = height; | |||
| if (group.itemSet) group.itemSet.top = group.top; // TODO: this is an ugly hack | |||
| height += group.height; | |||
| }); | |||
| } | |||
| // update classname | |||
| frame.className = 'groupset' + (options.className ? (' ' + asString(options.className)) : ''); | |||
| // calculate actual size and position | |||
| this.top = frame.offsetTop; | |||
| this.left = frame.offsetLeft; | |||
| this.width = frame.offsetWidth; | |||
| this.height = height; | |||
| return resized; | |||
| }; | |||
| /** | |||
| * Update the groupIds. Requires a repaint afterwards | |||
| * @private | |||
| */ | |||
| GroupSet.prototype._updateGroupIds = function () { | |||
| // reorder the groups | |||
| this.groupIds = this.groupsData.getIds({ | |||
| order: this.options.groupOrder | |||
| }); | |||
| // hide the groups now, they will be shown again in the next repaint | |||
| // in correct order | |||
| var groups = this.groups; | |||
| this.groupIds.forEach(function (id) { | |||
| groups[id].hide(); | |||
| }); | |||
| }; | |||
| /** | |||
| * Get the width of the group labels | |||
| * @return {Number} width | |||
| */ | |||
| GroupSet.prototype.getLabelsWidth = function getLabelsWidth() { | |||
| return this.props.labels.width; | |||
| }; | |||
| /** | |||
| * Hide the component from the DOM | |||
| */ | |||
| GroupSet.prototype.hide = function hide() { | |||
| // hide labelset | |||
| this.labelPanel.removeChild(this.labelSet); | |||
| // hide each of the groups | |||
| for (var groupId in this.groups) { | |||
| if (this.groups.hasOwnProperty(groupId)) { | |||
| this.groups[groupId].hide(); | |||
| } | |||
| } | |||
| }; | |||
| /** | |||
| * Show the component in the DOM (when not already visible). | |||
| * @return {Boolean} changed | |||
| */ | |||
| GroupSet.prototype.show = function show() { | |||
| // show label set | |||
| if (!this.labelPanel.hasChild(this.labelSet)) { | |||
| this.labelPanel.removeChild(this.labelSet); | |||
| } | |||
| // show each of the groups | |||
| for (var groupId in this.groups) { | |||
| if (this.groups.hasOwnProperty(groupId)) { | |||
| this.groups[groupId].show(); | |||
| } | |||
| } | |||
| }; | |||
| /** | |||
| * Handle updated groups | |||
| * @param {Number[]} ids | |||
| * @private | |||
| */ | |||
| GroupSet.prototype._onUpdate = function _onUpdate(ids) { | |||
| this._onAdd(ids); | |||
| }; | |||
| /** | |||
| * Handle changed groups | |||
| * @param {Number[]} ids | |||
| * @private | |||
| */ | |||
| GroupSet.prototype._onAdd = function _onAdd(ids) { | |||
| var me = this; | |||
| ids.forEach(function (id) { | |||
| var group = me.groups[id]; | |||
| if (!group) { | |||
| var groupOptions = Object.create(me.options); | |||
| util.extend(groupOptions, { | |||
| height: null | |||
| }); | |||
| group = new Group(me, me.labelSet, me.backgroundPanel, me.axisPanel, id, groupOptions); | |||
| group.on('change', me.emit.bind(me, 'change')); // propagate change event | |||
| group.setRange(me.range); | |||
| group.setItems(me.itemsData); // attach items data | |||
| me.groups[id] = group; | |||
| group.parent = me; | |||
| } | |||
| // update group data | |||
| group.setData(me.groupsData.get(id)); | |||
| }); | |||
| this._updateGroupIds(); | |||
| this.emit('change'); | |||
| }; | |||
| /** | |||
| * Handle removed groups | |||
| * @param {Number[]} ids | |||
| * @private | |||
| */ | |||
| GroupSet.prototype._onRemove = function _onRemove(ids) { | |||
| var groups = this.groups; | |||
| ids.forEach(function (id) { | |||
| var group = groups[id]; | |||
| if (group) { | |||
| group.setItems(); // detach items data | |||
| group.hide(); // FIXME: for some reason when doing setItems after hide, setItems again makes the label visible | |||
| delete groups[id]; | |||
| } | |||
| }); | |||
| this._updateGroupIds(); | |||
| this.emit('change'); | |||
| }; | |||
| /** | |||
| * Find the GroupSet from an event target: | |||
| * searches for the attribute 'timeline-groupset' in the event target's element | |||
| * tree, then finds the right group in this groupset | |||
| * @param {Event} event | |||
| * @return {Group | null} group | |||
| */ | |||
| GroupSet.groupSetFromTarget = function groupSetFromTarget (event) { | |||
| var target = event.target; | |||
| while (target) { | |||
| if (target.hasOwnProperty('timeline-groupset')) { | |||
| return target['timeline-groupset']; | |||
| } | |||
| target = target.parentNode; | |||
| } | |||
| return null; | |||
| }; | |||
| /** | |||
| * Find the Group from an event target: | |||
| * searches for the two elements having attributes 'timeline-groupset' and | |||
| * 'timeline-itemset' in the event target's element, then finds the right group. | |||
| * @param {Event} event | |||
| * @return {Group | null} group | |||
| */ | |||
| GroupSet.groupFromTarget = function groupFromTarget (event) { | |||
| // find the groupSet | |||
| var groupSet = GroupSet.groupSetFromTarget(event); | |||
| // find the ItemSet | |||
| var itemSet = ItemSet.itemSetFromTarget(event); | |||
| // find the right group | |||
| if (groupSet && itemSet) { | |||
| for (var groupId in groupSet.groups) { | |||
| if (groupSet.groups.hasOwnProperty(groupId)) { | |||
| var group = groupSet.groups[groupId]; | |||
| if (group.itemSet == itemSet) { | |||
| return group; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| return null; | |||
| }; | |||