Browse Source

Implemented dragging items from one group to another. Implemented detailed configuration of editable actions.

css_transitions
jos 10 years ago
parent
commit
e79c0772ad
13 changed files with 319 additions and 107 deletions
  1. +3
    -0
      HISTORY.md
  2. +144
    -47
      dist/vis.js
  3. +1
    -1
      dist/vis.min.css
  4. +10
    -10
      dist/vis.min.js
  5. +53
    -6
      docs/timeline.html
  6. +17
    -11
      examples/timeline/02_interactive.html
  7. +1
    -1
      examples/timeline/index.html
  8. +20
    -2
      src/timeline/Timeline.js
  9. +58
    -23
      src/timeline/component/ItemSet.js
  10. +2
    -1
      src/timeline/component/RootPanel.js
  11. +1
    -1
      src/timeline/component/item/Item.js
  12. +2
    -2
      src/timeline/component/item/ItemRange.js
  13. +7
    -2
      test/timeline_groups.html

+ 3
- 0
HISTORY.md View File

@ -9,6 +9,9 @@ http://visjs.org
- Large refactoring of the Timeline, simplifying the code.
- Great performance improvements.
- Improved layout of box-items inside groups.
- Items can now be dragged from one group to another.
- Option `editable` can now be used to enable/disable individual manipulation
actions (`add`, `updateTime`, `updateGroup`, `remove`).
- Function `setWindow` now accepts an object with properties `start` and `end`.
- Fixed option `autoResize` forcing a repaint of the Timeline with every check
rather than when the Timeline is actually resized.

+ 144
- 47
dist/vis.js View File

@ -3881,7 +3881,8 @@ RootPanel.prototype.getFrame = function getFrame() {
RootPanel.prototype.repaint = function repaint() {
// update class name
var options = this.options;
var className = 'vis timeline rootpanel ' + options.orientation + (options.editable ? ' editable' : '');
var editable = options.editable.updateTime || options.editable.updateGroup;
var className = 'vis timeline rootpanel ' + options.orientation + (editable ? ' editable' : '');
if (options.className) className += ' ' + util.option.asString(className);
this.frame.className = className;
@ -4808,7 +4809,17 @@ ItemSet.prototype._create = function _create(){
* Function to let items snap to nice dates when
* dragging items.
*/
ItemSet.prototype.setOptions = Component.prototype.setOptions;
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
@ -4943,27 +4954,49 @@ ItemSet.prototype.repaint = function repaint() {
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 = zoomed || this.stackDirty;
var height = 0;
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) {
resized = group.repaint(range, margin, restack) || resized;
var groupMargin = (group == firstGroup) ? firstMargin : nonFirstMargin;
resized = group.repaint(range, groupMargin, restack) || resized;
height += group.height;
});
height = Math.max(height, minHeight);
this.stackDirty = false;
// reorder the groups (if needed)
resized = this._orderGroups() || resized;
// reposition frame
frame.style.left = asSize(options.left, '');
frame.style.right = asSize(options.right, '');
@ -4993,6 +5026,19 @@ ItemSet.prototype.repaint = function repaint() {
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.
@ -5379,7 +5425,8 @@ ItemSet.prototype._orderGroups = function () {
// hide all groups, removes them from the DOM
var groups = this.groups;
groupIds.forEach(function (groupId) {
groups[groupId].hide();
var group = groups[groupId];
group.hide();
});
// show the groups again, attach them to the DOM in correct order
@ -5502,28 +5549,45 @@ ItemSet.prototype.getBackgroundHeight = function getBackgroundHeight() {
* @private
*/
ItemSet.prototype._onDragStart = function (event) {
if (!this.options.editable) {
if (!this.options.editable.updateTime && !this.options.editable.updateGroup) {
return;
}
var item = ItemSet.itemFromTarget(event),
me = this;
me = this,
props;
if (item && item.selected) {
var dragLeftItem = event.target.dragLeftItem;
var dragRightItem = event.target.dragRightItem;
if (dragLeftItem) {
this.touchParams.itemProps = [{
item: dragLeftItem,
start: item.data.start.valueOf()
}];
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) {
this.touchParams.itemProps = [{
item: dragRightItem,
end: item.data.end.valueOf()
}];
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) {
@ -5532,11 +5596,12 @@ ItemSet.prototype._onDragStart = function (event) {
item: item
};
if ('start' in item.data) {
props.start = item.data.start.valueOf()
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 ('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;
@ -5565,16 +5630,29 @@ ItemSet.prototype._onDrag = function (event) {
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
// TODO: implement dragging from one group to another
this.stackDirty = true; // force re-stacking of all items next repaint
this.emit('change');
@ -5596,25 +5674,29 @@ ItemSet.prototype._onDragEnd = function (event) {
this.touchParams.itemProps.forEach(function (props) {
var id = props.item.id,
item = me.itemsData.get(id);
itemData = me.itemsData.get(id);
var changed = false;
if ('start' in props.item.data) {
changed = (props.start != props.item.data.start.valueOf());
item.start = util.convert(props.item.data.start, dataset.convert['start']);
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());
item.end = util.convert(props.item.data.end, dataset.convert['end']);
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(item, function (item) {
if (item) {
me.options.onMove(itemData, function (itemData) {
if (itemData) {
// apply changes
item[dataset.fieldId] = id; // ensure the item contains its id (can be undefined)
changes.push(item);
itemData[dataset.fieldId] = id; // ensure the item contains its id (can be undefined)
changes.push(itemData);
}
else {
// restore original values
@ -5817,7 +5899,7 @@ Item.prototype.repositionY = function repositionY() {
* @protected
*/
Item.prototype._repaintDeleteButton = function (anchor) {
if (this.selected && this.options.editable && !this.dom.deleteButton) {
if (this.selected && this.options.editable.remove && !this.dom.deleteButton) {
// create and show button
var me = this;
@ -6476,7 +6558,7 @@ ItemRange.prototype.repositionY = function repositionY() {
* @protected
*/
ItemRange.prototype._repaintDragLeft = function () {
if (this.selected && this.options.editable && !this.dom.dragLeft) {
if (this.selected && this.options.editable.updateTime && !this.dom.dragLeft) {
// create and show drag area
var dragLeft = document.createElement('div');
dragLeft.className = 'drag-left';
@ -6506,7 +6588,7 @@ ItemRange.prototype._repaintDragLeft = function () {
* @protected
*/
ItemRange.prototype._repaintDragRight = function () {
if (this.selected && this.options.editable && !this.dom.dragRight) {
if (this.selected && this.options.editable.updateTime && !this.dom.dragRight) {
// create and show drag area
var dragRight = document.createElement('div');
dragRight.className = 'drag-right';
@ -6703,28 +6785,21 @@ Group.prototype.getLabelWidth = function getLabelWidth() {
/**
* Repaint this group
* @param {{start: number, end: number}} range
* @param {number | {item: number, axis: number}} margin
* @param {{item: number, axis: number}} margin
* @param {boolean} [restack=false] Force restacking of all items
* @return {boolean} Returns true if the group is resized
*/
Group.prototype.repaint = function repaint(range, margin, restack) {
var resized = false;
if (typeof margin === 'number') {
margin = {
item: margin,
axis: margin
};
}
// update visible items
this.visibleItems = this._updateVisibleItems(this.orderedItems, this.visibleItems, range);
// reposition visible items vertically
stack.stack(this.visibleItems, margin, restack);
this.stackDirty = false;
for (var i = 0, ii = this.visibleItems.length; i < ii; i++) {
this.visibleItems[i].repositionY();
var item = this.visibleItems[i];
item.repositionY();
}
// recalculate the height of the group
@ -6742,6 +6817,7 @@ Group.prototype.repaint = function repaint(range, margin, restack) {
else {
height = margin.axis + margin.item;
}
height = Math.max(height, this.props.label.height);
// calculate actual size and position
var foreground = this.dom.foreground;
@ -7059,7 +7135,14 @@ function Timeline (container, items, options) {
orientation: 'bottom',
direction: 'horizontal', // 'horizontal' or 'vertical'
autoResize: true,
editable: false,
editable: {
updateTime: false,
updateGroup: false,
add: false,
remove: false
},
selectable: true,
snap: null, // will be specified after timeaxis is created
@ -7318,6 +7401,17 @@ Emitter(Timeline.prototype);
Timeline.prototype.setOptions = function (options) {
util.extend(this.options, options);
if ('editable' in options) {
var isBoolean = typeof options.editable === 'boolean';
this.options.editable = {
updateTime: isBoolean ? options.editable : (options.editable.updateTime || false),
updateGroup: isBoolean ? options.editable : (options.editable.updateGroup || false),
add: isBoolean ? options.editable : (options.editable.add || false),
remove: isBoolean ? options.editable : (options.editable.remove || false)
};
}
// force update of range (apply new min/max etc.)
// both start and end are optional
this.range.setRange(options.start, options.end);
@ -7333,6 +7427,9 @@ Timeline.prototype.setOptions = function (options) {
}
}
// force the itemSet to refresh: options like orientation and margins may be changed
this.itemSet.markDirty();
// validate the callback functions
var validateCallback = (function (fn) {
if (!(this.options[fn] instanceof Function) || this.options[fn].length != 2) {
@ -7630,7 +7727,7 @@ Timeline.prototype._onSelectItem = function (event) {
*/
Timeline.prototype._onAddItem = function (event) {
if (!this.options.selectable) return;
if (!this.options.editable) return;
if (!this.options.editable.add) return;
var me = this,
item = ItemSet.itemFromTarget(event);

+ 1
- 1
dist/vis.min.css
File diff suppressed because it is too large
View File


+ 10
- 10
dist/vis.min.js
File diff suppressed because it is too large
View File


+ 53
- 6
docs/timeline.html View File

@ -347,12 +347,41 @@ var options = {
<tr>
<td>editable</td>
<td>Boolean</td>
<td>Boolean | Object</td>
<td>false</td>
<td>If true, the items on the timeline can be dragged. Only applicable when option <code>selectable</code> is <code>true</code>. See also the callbacks <code>onAdd</code>, <code>onUpdate</code>, <code>onMove</code>, and <code>onRemove</code>, described in detail in section <a href="#Editing_Items">Editing Items</a>.
<td>If true, the items in the timeline can be manipulated. Only applicable when option <code>selectable</code> is <code>true</code>. See also the callbacks <code>onAdd</code>, <code>onUpdate</code>, <code>onMove</code>, and <code>onRemove</code>. When <code>editable</code> is an object, one can enable or disable individual manipulation actions.
See section <a href="#Editing_Items">Editing Items</a> for a detailed explanation.
</td>
</tr>
<tr>
<td>editable.add</td>
<td>Boolean</td>
<td>false</td>
<td>If true, new items can be created by double tapping an empty space in the Timeline. See section <a href="#Editing_Items">Editing Items</a> for a detailed explanation.</td>
</tr>
<tr>
<td>editable.remove</td>
<td>Boolean</td>
<td>false</td>
<td>If true, items can be deleted by first selecting them, and then clicking the delete button on the top right of the item. See section <a href="#Editing_Items">Editing Items</a> for a detailed explanation.</td>
</tr>
<tr>
<td>editable.updateGroup</td>
<td>Boolean</td>
<td>false</td>
<td>If true, items can be dragged from one group to another. Only applicable when the Timeline has groups. See section <a href="#Editing_Items">Editing Items</a> for a detailed explanation.</td>
</tr>
<tr>
<td>editable.updateTime</td>
<td>Boolean</td>
<td>false</td>
<td>If true, items can be dragged to another moment in time. See section <a href="#Editing_Items">Editing Items</a> for a detailed explanation.</td>
</tr>
<tr>
<td>end</td>
<td>Date | Number | String</td>
@ -428,7 +457,7 @@ var options = {
<td>onAdd</td>
<td>Function</td>
<td>none</td>
<td>Callback function triggered when an item is about to be added: when the user double taps an empty space in the Timeline. See section <a href="#Editing_Items">Editing Items</a> for more information. Only applicable when both options <code>selectable</code> and <code>editable</code> are set <code>true</code>.
<td>Callback function triggered when an item is about to be added: when the user double taps an empty space in the Timeline. See section <a href="#Editing_Items">Editing Items</a> for more information. Only applicable when both options <code>selectable</code> and <code>editable.add</code> are set <code>true</code>.
</td>
</tr>
@ -436,7 +465,7 @@ var options = {
<td>onUpdate</td>
<td>Function</td>
<td>none</td>
<td>Callback function triggered when an item is about to be updated, when the user double taps an item in the Timeline. See section <a href="#Editing_Items">Editing Items</a> for more information. Only applicable when both options <code>selectable</code> and <code>editable</code> are set <code>true</code>.
<td>Callback function triggered when an item is about to be updated, when the user double taps an item in the Timeline. See section <a href="#Editing_Items">Editing Items</a> for more information. Only applicable when both options <code>selectable</code> and <code>editable.updateTime</code> or <code>editable.updateGroup</code> are set <code>true</code>.
</td>
</tr>
@ -444,7 +473,7 @@ var options = {
<td>onMove</td>
<td>Function</td>
<td>none</td>
<td>Callback function triggered when an item has been moved: after the user has dragged the item to an other position. See section <a href="#Editing_Items">Editing Items</a> for more information. Only applicable when both options <code>selectable</code> and <code>editable</code> are set <code>true</code>.
<td>Callback function triggered when an item has been moved: after the user has dragged the item to an other position. See section <a href="#Editing_Items">Editing Items</a> for more information. Only applicable when both options <code>selectable</code> and <code>editable.updateTime</code> or <code>editable.updateGroup</code> are set <code>true</code>.
</td>
</tr>
@ -452,7 +481,7 @@ var options = {
<td>onRemove</td>
<td>Function</td>
<td>none</td>
<td>Callback function triggered when an item is about to be removed: when the user tapped the delete button on the top right of a selected item. See section <a href="#Editing_Items">Editing Items</a> for more information. Only applicable when both options <code>selectable</code> and <code>editable</code> are set <code>true</code>.
<td>Callback function triggered when an item is about to be removed: when the user tapped the delete button on the top right of a selected item. See section <a href="#Editing_Items">Editing Items</a> for more information. Only applicable when both options <code>selectable</code> and <code>editable.remove</code> are set <code>true</code>.
</td>
</tr>
@ -792,6 +821,24 @@ timeline.off('select', onSelect);
When the Timeline is configured to be editable (both options <code>selectable</code> and <code>editable</code> are <code>true</code>), the user can move items by dragging them, can create a new item by double tapping on an empty space, can update an item by double tapping it, and can delete a selected item by clicking the delete button on the top right.
</p>
<p>Option <code>editable</code> accepts a boolean or an object. When <code>editable</code> is a boolean, all manipulation actions will be either enabled or disabled. When <code>editable</code> is an object, one can enable individual manipulation actions:</p>
<pre class="prettyprint lang-js">// enable or disable all manipulation actions
var options = {
editable: true // true or false
};
// enable or disable individual manipulation actions
var options = {
editable: {
add: true, // add new items by double tapping
updateTime: true, // drag items horizontally
updateGroup: true, // drag items from one group to another
remove: true // delete an item by tapping the delete button top right
}
};</pre>
<p>
One can specify callback functions to validate changes made by the user. There are a number of callback functions for this purpose:
</p>

examples/timeline/02_dataset.html → examples/timeline/02_interactive.html View File

@ -1,21 +1,12 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Timeline | Dataset example</title>
<title>Timeline | Interactive example</title>
<style>
body, html {
font-family: arial, sans-serif;
font-size: 11pt;
height: 100%;
margin: 0;
padding: 0;
}
#visualization {
box-sizing: border-box;
width: 100%;
height: 100%;
}
</style>
@ -23,6 +14,9 @@
<link href="../../dist/vis.css" rel="stylesheet" type="text/css" />
</head>
<body>
<p>Drag items around, create new items, and remove items.</p>
<div id="visualization"></div>
<script>
@ -47,8 +41,20 @@
start: '2014-01-10',
end: '2014-02-10',
orientation: 'top',
height: '100%',
height: '300px',
editable: true,
/* alternatively, enable/disable individual actions:
editable: {
add: true,
updateTime: true,
updateGroup: true,
remove: true
},
*/
showCurrentTime: true
};

+ 1
- 1
examples/timeline/index.html View File

@ -13,7 +13,7 @@
<h1>vis.js timeline examples</h1>
<p><a href="01_basic.html">01_basic.html</a></p>
<p><a href="02_dataset.html">02_dataset.html</a></p>
<p><a href="02_interactive.html">02_dataset.html</a></p>
<p><a href="03_much_data.html">03_much_data.html</a></p>
<p><a href="04_html_data.html">04_html_data.html</a></p>
<p><a href="05_groups.html">05_groups.html</a></p>

+ 20
- 2
src/timeline/Timeline.js View File

@ -15,7 +15,14 @@ function Timeline (container, items, options) {
orientation: 'bottom',
direction: 'horizontal', // 'horizontal' or 'vertical'
autoResize: true,
editable: false,
editable: {
updateTime: false,
updateGroup: false,
add: false,
remove: false
},
selectable: true,
snap: null, // will be specified after timeaxis is created
@ -274,6 +281,17 @@ Emitter(Timeline.prototype);
Timeline.prototype.setOptions = function (options) {
util.extend(this.options, options);
if ('editable' in options) {
var isBoolean = typeof options.editable === 'boolean';
this.options.editable = {
updateTime: isBoolean ? options.editable : (options.editable.updateTime || false),
updateGroup: isBoolean ? options.editable : (options.editable.updateGroup || false),
add: isBoolean ? options.editable : (options.editable.add || false),
remove: isBoolean ? options.editable : (options.editable.remove || false)
};
}
// force update of range (apply new min/max etc.)
// both start and end are optional
this.range.setRange(options.start, options.end);
@ -589,7 +607,7 @@ Timeline.prototype._onSelectItem = function (event) {
*/
Timeline.prototype._onAddItem = function (event) {
if (!this.options.selectable) return;
if (!this.options.editable) return;
if (!this.options.editable.add) return;
var me = this,
item = ItemSet.itemFromTarget(event);

+ 58
- 23
src/timeline/component/ItemSet.js View File

@ -892,28 +892,45 @@ ItemSet.prototype.getBackgroundHeight = function getBackgroundHeight() {
* @private
*/
ItemSet.prototype._onDragStart = function (event) {
if (!this.options.editable) {
if (!this.options.editable.updateTime && !this.options.editable.updateGroup) {
return;
}
var item = ItemSet.itemFromTarget(event),
me = this;
me = this,
props;
if (item && item.selected) {
var dragLeftItem = event.target.dragLeftItem;
var dragRightItem = event.target.dragRightItem;
if (dragLeftItem) {
this.touchParams.itemProps = [{
item: dragLeftItem,
start: item.data.start.valueOf()
}];
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) {
this.touchParams.itemProps = [{
item: dragRightItem,
end: item.data.end.valueOf()
}];
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) {
@ -922,11 +939,12 @@ ItemSet.prototype._onDragStart = function (event) {
item: item
};
if ('start' in item.data) {
props.start = item.data.start.valueOf()
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 ('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;
@ -955,16 +973,29 @@ ItemSet.prototype._onDrag = function (event) {
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
// TODO: implement dragging from one group to another
this.stackDirty = true; // force re-stacking of all items next repaint
this.emit('change');
@ -986,25 +1017,29 @@ ItemSet.prototype._onDragEnd = function (event) {
this.touchParams.itemProps.forEach(function (props) {
var id = props.item.id,
item = me.itemsData.get(id);
itemData = me.itemsData.get(id);
var changed = false;
if ('start' in props.item.data) {
changed = (props.start != props.item.data.start.valueOf());
item.start = util.convert(props.item.data.start, dataset.convert['start']);
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());
item.end = util.convert(props.item.data.end, dataset.convert['end']);
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(item, function (item) {
if (item) {
me.options.onMove(itemData, function (itemData) {
if (itemData) {
// apply changes
item[dataset.fieldId] = id; // ensure the item contains its id (can be undefined)
changes.push(item);
itemData[dataset.fieldId] = id; // ensure the item contains its id (can be undefined)
changes.push(itemData);
}
else {
// restore original values

+ 2
- 1
src/timeline/component/RootPanel.js View File

@ -91,7 +91,8 @@ RootPanel.prototype.getFrame = function getFrame() {
RootPanel.prototype.repaint = function repaint() {
// update class name
var options = this.options;
var className = 'vis timeline rootpanel ' + options.orientation + (options.editable ? ' editable' : '');
var editable = options.editable.updateTime || options.editable.updateGroup;
var className = 'vis timeline rootpanel ' + options.orientation + (editable ? ' editable' : '');
if (options.className) className += ' ' + util.option.asString(className);
this.frame.className = className;

+ 1
- 1
src/timeline/component/item/Item.js View File

@ -110,7 +110,7 @@ Item.prototype.repositionY = function repositionY() {
* @protected
*/
Item.prototype._repaintDeleteButton = function (anchor) {
if (this.selected && this.options.editable && !this.dom.deleteButton) {
if (this.selected && this.options.editable.remove && !this.dom.deleteButton) {
// create and show button
var me = this;

+ 2
- 2
src/timeline/component/item/ItemRange.js View File

@ -207,7 +207,7 @@ ItemRange.prototype.repositionY = function repositionY() {
* @protected
*/
ItemRange.prototype._repaintDragLeft = function () {
if (this.selected && this.options.editable && !this.dom.dragLeft) {
if (this.selected && this.options.editable.updateTime && !this.dom.dragLeft) {
// create and show drag area
var dragLeft = document.createElement('div');
dragLeft.className = 'drag-left';
@ -237,7 +237,7 @@ ItemRange.prototype._repaintDragLeft = function () {
* @protected
*/
ItemRange.prototype._repaintDragRight = function () {
if (this.selected && this.options.editable && !this.dom.dragRight) {
if (this.selected && this.options.editable.updateTime && !this.dom.dragRight) {
// create and show drag area
var dragRight = document.createElement('div');
dragRight.className = 'drag-right';

+ 7
- 2
test/timeline_groups.html View File

@ -49,7 +49,7 @@
var itemCount = 20;
// create a data set with groups
var names = ['John', 'Alston', 'Lee', 'Grant'];
var names = ['John (0)', 'Alston (1)', 'Lee (2)', 'Grant (3)'];
var groups = new vis.DataSet();
for (var g = 0; g < groupCount; g++) {
groups.add({id: g, content: names[g]});
@ -73,7 +73,12 @@
// create visualization
var container = document.getElementById('visualization');
var options = {
editable: true,
editable: {
add: true,
//remove: true,
updateTime: true,
updateGroup: true
},
//height: 200,
groupOrder: 'content'
};

Loading…
Cancel
Save