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.
 
 
 

1136 lines
30 KiB

var UNGROUPED = '__ungrouped__'; // reserved group id for ungrouped items
/**
* An ItemSet holds a set of items and ranges which can be displayed in a
* range. The width is determined by the parent of the ItemSet, and the height
* is determined by the size of the items.
* @param {Panel} backgroundPanel Panel which can be used to display the
* vertical lines of box items.
* @param {Panel} axisPanel Panel on the axis where the dots of box-items
* can be displayed.
* @param {Panel} sidePanel Left side panel holding labels
* @param {Object} [options] See ItemSet.setOptions for the available options.
* @constructor ItemSet
* @extends Panel
*/
function ItemSet(backgroundPanel, axisPanel, sidePanel, options) {
this.id = util.randomUUID();
// one options object is shared by this itemset and all its items
this.options = options || {};
this.backgroundPanel = backgroundPanel;
this.axisPanel = axisPanel;
this.sidePanel = sidePanel;
this.itemOptions = Object.create(this.options);
this.dom = {};
this.hammer = null;
var me = this;
this.itemsData = null; // DataSet
this.groupsData = null; // DataSet
this.range = null; // Range or Object {start: number, end: number}
// listeners for the DataSet of the items
this.itemListeners = {
'add': function (event, params, senderId) {
if (senderId != me.id) me._onAdd(params.items);
},
'update': function (event, params, senderId) {
if (senderId != me.id) me._onUpdate(params.items);
},
'remove': function (event, params, senderId) {
if (senderId != me.id) me._onRemove(params.items);
}
};
// listeners for the DataSet of the groups
this.groupListeners = {
'add': function (event, params, senderId) {
if (senderId != me.id) me._onAddGroups(params.items);
},
'update': function (event, params, senderId) {
if (senderId != me.id) me._onUpdateGroups(params.items);
},
'remove': function (event, params, senderId) {
if (senderId != me.id) me._onRemoveGroups(params.items);
}
};
this.items = {}; // object with an Item for every data item
this.groups = {}; // Group object for every group
this.groupIds = [];
this.selection = []; // list with the ids of all selected nodes
this.stackDirty = true; // if true, all items will be restacked on next repaint
this.touchParams = {}; // stores properties while dragging
// create the HTML DOM
this._create();
}
ItemSet.prototype = new Panel();
// available item types will be registered here
ItemSet.types = {
box: ItemBox,
range: ItemRange,
rangeoverflow: ItemRangeOverflow,
point: ItemPoint
};
/**
* Create the HTML DOM for the ItemSet
*/
ItemSet.prototype._create = function _create(){
var frame = document.createElement('div');
frame['timeline-itemset'] = this;
this.frame = frame;
// create background panel
var background = document.createElement('div');
background.className = 'background';
this.backgroundPanel.frame.appendChild(background);
this.dom.background = background;
// create foreground panel
var foreground = document.createElement('div');
foreground.className = 'foreground';
frame.appendChild(foreground);
this.dom.foreground = foreground;
// create axis panel
var axis = document.createElement('div');
axis.className = 'axis';
this.dom.axis = axis;
this.axisPanel.frame.appendChild(axis);
// create labelset
var labelSet = document.createElement('div');
labelSet.className = 'labelset';
this.dom.labelSet = labelSet;
this.sidePanel.frame.appendChild(labelSet);
// create ungrouped Group
this._updateUngrouped();
// attach event listeners
// TODO: use event listeners from the rootpanel to improve performance?
this.hammer = Hammer(frame, {
prevent_default: true
});
this.hammer.on('dragstart', this._onDragStart.bind(this));
this.hammer.on('drag', this._onDrag.bind(this));
this.hammer.on('dragend', this._onDragEnd.bind(this));
};
/**
* Set options for the ItemSet. Existing options will be extended/overwritten.
* @param {Object} [options] The following options are available:
* {String | function} [className]
* class name for the itemset
* {String} [type]
* Default type for the items. Choose from 'box'
* (default), 'point', or 'range'. The default
* Style can be overwritten by individual items.
* {String} align
* Alignment for the items, only applicable for
* ItemBox. Choose 'center' (default), 'left', or
* 'right'.
* {String} orientation
* Orientation of the item set. Choose 'top' or
* 'bottom' (default).
* {Number} margin.axis
* Margin between the axis and the items in pixels.
* Default is 20.
* {Number} margin.item
* Margin between items in pixels. Default is 10.
* {Number} padding
* Padding of the contents of an item in pixels.
* Must correspond with the items css. Default is 5.
* {Function} snap
* Function to let items snap to nice dates when
* dragging items.
*/
ItemSet.prototype.setOptions = function setOptions(options) {
Component.prototype.setOptions.call(this, options);
};
/**
* Mark the ItemSet dirty so it will refresh everything with next repaint
*/
ItemSet.prototype.markDirty = function markDirty() {
this.groupIds = [];
this.stackDirty = true;
};
/**
* Hide the component from the DOM
*/
ItemSet.prototype.hide = function hide() {
// remove the axis with dots
if (this.dom.axis.parentNode) {
this.dom.axis.parentNode.removeChild(this.dom.axis);
}
// remove the background with vertical lines
if (this.dom.background.parentNode) {
this.dom.background.parentNode.removeChild(this.dom.background);
}
// remove the labelset containing all group labels
if (this.dom.labelSet.parentNode) {
this.dom.labelSet.parentNode.removeChild(this.dom.labelSet);
}
};
/**
* Show the component in the DOM (when not already visible).
* @return {Boolean} changed
*/
ItemSet.prototype.show = function show() {
// show axis with dots
if (!this.dom.axis.parentNode) {
this.axisPanel.frame.appendChild(this.dom.axis);
}
// show background with vertical lines
if (!this.dom.background.parentNode) {
this.backgroundPanel.frame.appendChild(this.dom.background);
}
// show labelset containing labels
if (!this.dom.labelSet.parentNode) {
this.sidePanel.frame.appendChild(this.dom.labelSet);
}
};
/**
* Set range (start and end).
* @param {Range | Object} range A Range or an object containing start and end.
*/
ItemSet.prototype.setRange = function setRange(range) {
if (!(range instanceof Range) && (!range || !range.start || !range.end)) {
throw new TypeError('Range must be an instance of Range, ' +
'or an object containing start and end.');
}
this.range = range;
};
/**
* 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.
*/
ItemSet.prototype.setSelection = function setSelection(ids) {
var i, ii, id, item;
if (ids) {
if (!Array.isArray(ids)) {
throw new TypeError('Array expected');
}
// unselect currently selected items
for (i = 0, ii = this.selection.length; i < ii; i++) {
id = this.selection[i];
item = this.items[id];
if (item) item.unselect();
}
// select items
this.selection = [];
for (i = 0, ii = ids.length; i < ii; i++) {
id = ids[i];
item = this.items[id];
if (item) {
this.selection.push(id);
item.select();
}
}
}
};
/**
* Get the selected items by their id
* @return {Array} ids The ids of the selected items
*/
ItemSet.prototype.getSelection = function getSelection() {
return this.selection.concat([]);
};
/**
* Deselect a selected item
* @param {String | Number} id
* @private
*/
ItemSet.prototype._deselect = function _deselect(id) {
var selection = this.selection;
for (var i = 0, ii = selection.length; i < ii; i++) {
if (selection[i] == id) { // non-strict comparison!
selection.splice(i, 1);
break;
}
}
};
/**
* Return the item sets frame
* @returns {HTMLElement} frame
*/
ItemSet.prototype.getFrame = function getFrame() {
return this.frame;
};
/**
* Repaint the component
* @return {boolean} Returns true if the component is resized
*/
ItemSet.prototype.repaint = function repaint() {
var margin = this.options.margin,
range = this.range,
asSize = util.option.asSize,
asString = util.option.asString,
options = this.options,
orientation = this.getOption('orientation'),
resized = false,
frame = this.frame;
// TODO: document this feature to specify one margin for both item and axis distance
if (typeof margin === 'number') {
margin = {
item: margin,
axis: margin
};
}
// update className
frame.className = 'itemset' + (options.className ? (' ' + asString(options.className)) : '');
// reorder the groups (if needed)
resized = this._orderGroups() || resized;
// check whether zoomed (in that case we need to re-stack everything)
// TODO: would be nicer to get this as a trigger from Range
var visibleInterval = this.range.end - this.range.start;
var zoomed = (visibleInterval != this.lastVisibleInterval) || (this.width != this.lastWidth);
if (zoomed) this.stackDirty = true;
this.lastVisibleInterval = visibleInterval;
this.lastWidth = this.width;
// repaint all groups
var restack = this.stackDirty,
firstGroup = this._firstGroup(),
firstMargin = {
item: margin.item,
axis: margin.axis
},
nonFirstMargin = {
item: margin.item,
axis: margin.item / 2
},
height = 0,
minHeight = margin.axis + margin.item;
util.forEach(this.groups, function (group) {
var groupMargin = (group == firstGroup) ? firstMargin : nonFirstMargin;
resized = group.repaint(range, groupMargin, restack) || resized;
height += group.height;
});
height = Math.max(height, minHeight);
this.stackDirty = false;
// reposition frame
frame.style.left = asSize(options.left, '');
frame.style.right = asSize(options.right, '');
frame.style.top = asSize((orientation == 'top') ? '0' : '');
frame.style.bottom = asSize((orientation == 'top') ? '' : '0');
frame.style.width = asSize(options.width, '100%');
frame.style.height = asSize(height);
//frame.style.height = asSize('height' in options ? options.height : height); // TODO: reckon with height
// calculate actual size and position
this.top = frame.offsetTop;
this.left = frame.offsetLeft;
this.width = frame.offsetWidth;
this.height = height;
// reposition axis
this.dom.axis.style.left = asSize(options.left, '0');
this.dom.axis.style.right = asSize(options.right, '');
this.dom.axis.style.width = asSize(options.width, '100%');
this.dom.axis.style.height = asSize(0);
this.dom.axis.style.top = asSize((orientation == 'top') ? '0' : '');
this.dom.axis.style.bottom = asSize((orientation == 'top') ? '' : '0');
// check if this component is resized
resized = this._isResized() || resized;
return resized;
};
/**
* Get the first group, aligned with the axis
* @return {Group | null} firstGroup
* @private
*/
ItemSet.prototype._firstGroup = function _firstGroup() {
var firstGroupIndex = (this.options.orientation == 'top') ? 0 : (this.groupIds.length - 1);
var firstGroupId = this.groupIds[firstGroupIndex];
var firstGroup = this.groups[firstGroupId] || this.groups[UNGROUPED];
return firstGroup || null;
};
/**
* Create or delete the group holding all ungrouped items. This group is used when
* there are no groups specified.
* @protected
*/
ItemSet.prototype._updateUngrouped = function _updateUngrouped() {
var ungrouped = this.groups[UNGROUPED];
if (this.groupsData) {
// remove the group holding all ungrouped items
if (ungrouped) {
ungrouped.hide();
delete this.groups[UNGROUPED];
}
}
else {
// create a group holding all (unfiltered) items
if (!ungrouped) {
var id = null;
var data = null;
ungrouped = new Group(id, data, this);
this.groups[UNGROUPED] = ungrouped;
for (var itemId in this.items) {
if (this.items.hasOwnProperty(itemId)) {
ungrouped.add(this.items[itemId]);
}
}
ungrouped.show();
}
}
};
/**
* Get the foreground container element
* @return {HTMLElement} foreground
*/
ItemSet.prototype.getForeground = function getForeground() {
return this.dom.foreground;
};
/**
* Get the background container element
* @return {HTMLElement} background
*/
ItemSet.prototype.getBackground = function getBackground() {
return this.dom.background;
};
/**
* Get the axis container element
* @return {HTMLElement} axis
*/
ItemSet.prototype.getAxis = function getAxis() {
return this.dom.axis;
};
/**
* Get the element for the labelset
* @return {HTMLElement} labelSet
*/
ItemSet.prototype.getLabelSet = function getLabelSet() {
return this.dom.labelSet;
};
/**
* Set items
* @param {vis.DataSet | null} items
*/
ItemSet.prototype.setItems = function setItems(items) {
var me = this,
ids,
oldItemsData = this.itemsData;
// replace the dataset
if (!items) {
this.itemsData = null;
}
else if (items instanceof DataSet || items instanceof DataView) {
this.itemsData = items;
}
else {
throw new TypeError('Data must be an instance of DataSet or DataView');
}
if (oldItemsData) {
// unsubscribe from old dataset
util.forEach(this.itemListeners, function (callback, event) {
oldItemsData.unsubscribe(event, callback);
});
// remove all drawn items
ids = oldItemsData.getIds();
this._onRemove(ids);
}
if (this.itemsData) {
// subscribe to new dataset
var id = this.id;
util.forEach(this.itemListeners, function (callback, event) {
me.itemsData.on(event, callback, id);
});
// add all new items
ids = this.itemsData.getIds();
this._onAdd(ids);
// update the group holding all ungrouped items
this._updateUngrouped();
}
};
/**
* Get the current items
* @returns {vis.DataSet | null}
*/
ItemSet.prototype.getItems = function getItems() {
return this.itemsData;
};
/**
* Set groups
* @param {vis.DataSet} groups
*/
ItemSet.prototype.setGroups = function setGroups(groups) {
var me = this,
ids;
// unsubscribe from current dataset
if (this.groupsData) {
util.forEach(this.groupListeners, function (callback, event) {
me.groupsData.unsubscribe(event, callback);
});
// remove all drawn groups
ids = this.groupsData.getIds();
this.groupsData = null;
this._onRemoveGroups(ids); // note: this will cause a repaint
}
// replace the dataset
if (!groups) {
this.groupsData = null;
}
else if (groups instanceof DataSet || groups instanceof DataView) {
this.groupsData = groups;
}
else {
throw new TypeError('Data must be an instance of DataSet or DataView');
}
if (this.groupsData) {
// subscribe to new dataset
var id = this.id;
util.forEach(this.groupListeners, function (callback, event) {
me.groupsData.on(event, callback, id);
});
// draw all ms
ids = this.groupsData.getIds();
this._onAddGroups(ids);
}
// update the group holding all ungrouped items
this._updateUngrouped();
// update the order of all items in each group
this._order();
this.emit('change');
};
/**
* Get the current groups
* @returns {vis.DataSet | null} groups
*/
ItemSet.prototype.getGroups = function getGroups() {
return this.groupsData;
};
/**
* Remove an item by its id
* @param {String | Number} id
*/
ItemSet.prototype.removeItem = function removeItem (id) {
var item = this.itemsData.get(id),
dataset = this._myDataSet();
if (item) {
// confirm deletion
this.options.onRemove(item, function (item) {
if (item) {
// remove by id here, it is possible that an item has no id defined
// itself, so better not delete by the item itself
dataset.remove(id);
}
});
}
};
/**
* Handle updated items
* @param {Number[]} ids
* @protected
*/
ItemSet.prototype._onUpdate = function _onUpdate(ids) {
var me = this,
items = this.items,
itemOptions = this.itemOptions;
ids.forEach(function (id) {
var itemData = me.itemsData.get(id),
item = items[id],
type = itemData.type ||
(itemData.start && itemData.end && 'range') ||
me.options.type ||
'box';
var constructor = ItemSet.types[type];
if (item) {
// update item
if (!constructor || !(item instanceof constructor)) {
// item type has changed, delete the item and recreate it
me._removeItem(item);
item = null;
}
else {
me._updateItem(item, itemData);
}
}
if (!item) {
// create item
if (constructor) {
item = new constructor(itemData, me.options, itemOptions);
item.id = id; // TODO: not so nice setting id afterwards
me._addItem(item);
}
else {
throw new TypeError('Unknown item type "' + type + '"');
}
}
});
this._order();
this.stackDirty = true; // force re-stacking of all items next repaint
this.emit('change');
};
/**
* Handle added items
* @param {Number[]} ids
* @protected
*/
ItemSet.prototype._onAdd = ItemSet.prototype._onUpdate;
/**
* Handle removed items
* @param {Number[]} ids
* @protected
*/
ItemSet.prototype._onRemove = function _onRemove(ids) {
var count = 0;
var me = this;
ids.forEach(function (id) {
var item = me.items[id];
if (item) {
count++;
me._removeItem(item);
}
});
if (count) {
// update order
this._order();
this.stackDirty = true; // force re-stacking of all items next repaint
this.emit('change');
}
};
/**
* Update the order of item in all groups
* @private
*/
ItemSet.prototype._order = function _order() {
// reorder the items in all groups
// TODO: optimization: only reorder groups affected by the changed items
util.forEach(this.groups, function (group) {
group.order();
});
};
/**
* Handle updated groups
* @param {Number[]} ids
* @private
*/
ItemSet.prototype._onUpdateGroups = function _onUpdateGroups(ids) {
this._onAddGroups(ids);
};
/**
* Handle changed groups
* @param {Number[]} ids
* @private
*/
ItemSet.prototype._onAddGroups = function _onAddGroups(ids) {
var me = this;
ids.forEach(function (id) {
var groupData = me.groupsData.get(id);
var group = me.groups[id];
if (!group) {
// check for reserved ids
if (id == UNGROUPED) {
throw new Error('Illegal group id. ' + id + ' is a reserved id.');
}
var groupOptions = Object.create(me.options);
util.extend(groupOptions, {
height: null
});
group = new Group(id, groupData, me);
me.groups[id] = group;
// add items with this groupId to the new group
for (var itemId in me.items) {
if (me.items.hasOwnProperty(itemId)) {
var item = me.items[itemId];
if (item.data.group == id) {
group.add(item);
}
}
}
group.order();
group.show();
}
else {
// update group
group.setData(groupData);
}
});
this.emit('change');
};
/**
* Handle removed groups
* @param {Number[]} ids
* @private
*/
ItemSet.prototype._onRemoveGroups = function _onRemoveGroups(ids) {
var groups = this.groups;
ids.forEach(function (id) {
var group = groups[id];
if (group) {
group.hide();
delete groups[id];
}
});
this.markDirty();
this.emit('change');
};
/**
* Reorder the groups if needed
* @return {boolean} changed
* @private
*/
ItemSet.prototype._orderGroups = function () {
if (this.groupsData) {
// reorder the groups
var groupIds = this.groupsData.getIds({
order: this.options.groupOrder
});
var changed = !util.equalArray(groupIds, this.groupIds);
if (changed) {
// hide all groups, removes them from the DOM
var groups = this.groups;
groupIds.forEach(function (groupId) {
groups[groupId].hide();
});
// show the groups again, attach them to the DOM in correct order
groupIds.forEach(function (groupId) {
groups[groupId].show();
});
this.groupIds = groupIds;
}
return changed;
}
else {
return false;
}
};
/**
* Add a new item
* @param {Item} item
* @private
*/
ItemSet.prototype._addItem = function _addItem(item) {
this.items[item.id] = item;
// add to group
var groupId = this.groupsData ? item.data.group : UNGROUPED;
var group = this.groups[groupId];
if (group) group.add(item);
};
/**
* Update an existing item
* @param {Item} item
* @param {Object} itemData
* @private
*/
ItemSet.prototype._updateItem = function _updateItem(item, itemData) {
var oldGroupId = item.data.group;
item.data = itemData;
if (item.displayed) {
item.repaint();
}
// update group
if (oldGroupId != item.data.group) {
var oldGroup = this.groups[oldGroupId];
if (oldGroup) oldGroup.remove(item);
var groupId = this.groupsData ? item.data.group : UNGROUPED;
var group = this.groups[groupId];
if (group) group.add(item);
}
};
/**
* Delete an item from the ItemSet: remove it from the DOM, from the map
* with items, and from the map with visible items, and from the selection
* @param {Item} item
* @private
*/
ItemSet.prototype._removeItem = function _removeItem(item) {
// remove from DOM
item.hide();
// remove from items
delete this.items[item.id];
// remove from selection
var index = this.selection.indexOf(item.id);
if (index != -1) this.selection.splice(index, 1);
// remove from group
var groupId = this.groupsData ? item.data.group : UNGROUPED;
var group = this.groups[groupId];
if (group) group.remove(item);
};
/**
* Create an array containing all items being a range (having an end date)
* @param array
* @returns {Array}
* @private
*/
ItemSet.prototype._constructByEndArray = function _constructByEndArray(array) {
var endArray = [];
for (var i = 0; i < array.length; i++) {
if (array[i] instanceof ItemRange) {
endArray.push(array[i]);
}
}
return endArray;
};
/**
* Get the width of the group labels
* @return {Number} width
*/
ItemSet.prototype.getLabelsWidth = function getLabelsWidth() {
var width = 0;
util.forEach(this.groups, function (group) {
width = Math.max(width, group.getLabelWidth());
});
return width;
};
/**
* Get the height of the itemsets background
* @return {Number} height
*/
ItemSet.prototype.getBackgroundHeight = function getBackgroundHeight() {
return this.height;
};
/**
* Start dragging the selected events
* @param {Event} event
* @private
*/
ItemSet.prototype._onDragStart = function (event) {
if (!this.options.editable.updateTime && !this.options.editable.updateGroup) {
return;
}
var item = ItemSet.itemFromTarget(event),
me = this,
props;
if (item && item.selected) {
var dragLeftItem = event.target.dragLeftItem;
var dragRightItem = event.target.dragRightItem;
if (dragLeftItem) {
props = {
item: dragLeftItem
};
if (me.options.editable.updateTime) {
props.start = item.data.start.valueOf();
}
if (me.options.editable.updateGroup) {
if ('group' in item.data) props.group = item.data.group;
}
this.touchParams.itemProps = [props];
}
else if (dragRightItem) {
props = {
item: dragRightItem
};
if (me.options.editable.updateTime) {
props.end = item.data.end.valueOf();
}
if (me.options.editable.updateGroup) {
if ('group' in item.data) props.group = item.data.group;
}
this.touchParams.itemProps = [props];
}
else {
this.touchParams.itemProps = this.getSelection().map(function (id) {
var item = me.items[id];
var props = {
item: item
};
if (me.options.editable.updateTime) {
if ('start' in item.data) props.start = item.data.start.valueOf();
if ('end' in item.data) props.end = item.data.end.valueOf();
}
if (me.options.editable.updateGroup) {
if ('group' in item.data) props.group = item.data.group;
}
return props;
});
}
event.stopPropagation();
}
};
/**
* Drag selected items
* @param {Event} event
* @private
*/
ItemSet.prototype._onDrag = function (event) {
if (this.touchParams.itemProps) {
var snap = this.options.snap || null,
deltaX = event.gesture.deltaX,
scale = (this.width / (this.range.end - this.range.start)),
offset = deltaX / scale;
// move
this.touchParams.itemProps.forEach(function (props) {
if ('start' in props) {
var start = new Date(props.start + offset);
props.item.data.start = snap ? snap(start) : start;
}
if ('end' in props) {
var end = new Date(props.end + offset);
props.item.data.end = snap ? snap(end) : end;
}
if ('group' in props) {
// drag from one group to another
var group = ItemSet.groupFromTarget(event);
if (group && group.groupId != props.item.data.group) {
var oldGroup = props.item.parent;
oldGroup.remove(props.item);
oldGroup.order();
group.add(props.item);
group.order();
props.item.data.group = group.groupId;
}
}
});
// TODO: implement onMoving handler
this.stackDirty = true; // force re-stacking of all items next repaint
this.emit('change');
event.stopPropagation();
}
};
/**
* End of dragging selected items
* @param {Event} event
* @private
*/
ItemSet.prototype._onDragEnd = function (event) {
if (this.touchParams.itemProps) {
// prepare a change set for the changed items
var changes = [],
me = this,
dataset = this._myDataSet();
this.touchParams.itemProps.forEach(function (props) {
var id = props.item.id,
itemData = me.itemsData.get(id);
var changed = false;
if ('start' in props.item.data) {
changed = (props.start != props.item.data.start.valueOf());
itemData.start = util.convert(props.item.data.start, dataset.convert['start']);
}
if ('end' in props.item.data) {
changed = changed || (props.end != props.item.data.end.valueOf());
itemData.end = util.convert(props.item.data.end, dataset.convert['end']);
}
if ('group' in props.item.data) {
changed = changed || (props.group != props.item.data.group);
itemData.group = props.item.data.group;
}
// only apply changes when start or end is actually changed
if (changed) {
me.options.onMove(itemData, function (itemData) {
if (itemData) {
// apply changes
itemData[dataset.fieldId] = id; // ensure the item contains its id (can be undefined)
changes.push(itemData);
}
else {
// restore original values
if ('start' in props) props.item.data.start = props.start;
if ('end' in props) props.item.data.end = props.end;
me.stackDirty = true; // force re-stacking of all items next repaint
me.emit('change');
}
});
}
});
this.touchParams.itemProps = null;
// apply the changes to the data (if there are changes)
if (changes.length) {
dataset.update(changes);
}
event.stopPropagation();
}
};
/**
* Find an item from an event target:
* searches for the attribute 'timeline-item' in the event target's element tree
* @param {Event} event
* @return {Item | null} item
*/
ItemSet.itemFromTarget = function itemFromTarget (event) {
var target = event.target;
while (target) {
if (target.hasOwnProperty('timeline-item')) {
return target['timeline-item'];
}
target = target.parentNode;
}
return null;
};
/**
* Find the Group from an event target:
* searches for the attribute 'timeline-group' in the event target's element tree
* @param {Event} event
* @return {Group | null} group
*/
ItemSet.groupFromTarget = function groupFromTarget (event) {
var target = event.target;
while (target) {
if (target.hasOwnProperty('timeline-group')) {
return target['timeline-group'];
}
target = target.parentNode;
}
return null;
};
/**
* Find the ItemSet from an event target:
* searches for the attribute 'timeline-itemset' in the event target's element tree
* @param {Event} event
* @return {ItemSet | null} item
*/
ItemSet.itemSetFromTarget = function itemSetFromTarget (event) {
var target = event.target;
while (target) {
if (target.hasOwnProperty('timeline-itemset')) {
return target['timeline-itemset'];
}
target = target.parentNode;
}
return null;
};
/**
* Find the DataSet to which this ItemSet is connected
* @returns {null | DataSet} dataset
* @private
*/
ItemSet.prototype._myDataSet = function _myDataSet() {
// find the root DataSet
var dataset = this.itemsData;
while (dataset instanceof DataView) {
dataset = dataset.data;
}
return dataset;
};