Browse Source

Implemented a new callback function `onMoving(item, callback)`

v3_develop
jos 10 years ago
parent
commit
7672da4a78
5 changed files with 177 additions and 82 deletions
  1. +1
    -0
      HISTORY.md
  2. +1
    -0
      docs/timeline.html
  3. +78
    -63
      examples/timeline/08_edit_items.html
  4. +45
    -18
      lib/timeline/component/ItemSet.js
  5. +52
    -1
      test/timeline_groups.html

+ 1
- 0
HISTORY.md View File

@ -19,6 +19,7 @@ http://visjs.org
- Implemented animated range change for functions `fit`, `focus`, `setSelection`,
and `setWindow`.
- Implemented functions `setCurrentTime(date)` and `getCurrentTime()`.
- Implemented a new callback function `onMoving(item, callback)`.
- Fixed the `change` event sometimes being fired twice on IE10.
- Fixed canceling moving an item to another group did not move the item
back to the original group.

+ 1
- 0
docs/timeline.html View File

@ -1030,6 +1030,7 @@ var options = {
<li><code>onAdd(item, callback)</code> Fired when a new item is about to be added. If not implemented, the item will be added with default text contents.</li>
<li><code>onUpdate(item, callback)</code> Fired when an item is about to be updated. This function typically has to show a dialog where the user change the item. If not implemented, nothing happens.</li>
<li><code>onMove(item, callback)</code> Fired when an item has been moved. If not implemented, the move action will be accepted.</li>
<li><code>onMoving(item, callback)</code> Fired repeatedly while an item is being moved (dragged). Can be used to adjust the items start, end, and/or group to allowed regions.</li>
<li><code>onRemove(item, callback)</code> Fired when an item is about to be deleted. If not implemented, the item will be always removed.</li>
</ul>

+ 78
- 63
examples/timeline/08_edit_items.html View File

@ -6,6 +6,7 @@
<style type="text/css">
body, html {
font-family: sans-serif;
font-size: 12pt;
}
</style>
@ -13,79 +14,93 @@
<link href="../../dist/vis.css" rel="stylesheet" type="text/css" />
</head>
<body>
<div id="visualization"></div>
<p></p>
<div id="log"></div>
<p style="max-width: 800px;">
This example shows how to use callback functions <code>onAdd</code>, <code>onMove</code>, <code>onMoving</code>, <code>onUpdate</code>, and <code>onRemove</code>. The <code>onMoving</code> function updates an item while dragging, and can be used to prevent the item from being drawn at disallowed or infeasible timeslots. In this example, the items cannot be moved outside of the month April 2013. The other callback functions are called after an add, move, update, or remove action has taken place, and can be used to cancel these actions.
</p>
<script type="text/javascript">
// note that months are zero-based in the JavaScript Date object, so month 3 is April
var items = new vis.DataSet([
{id: 1, content: 'item 1', start: new Date(2013, 3, 20)},
{id: 2, content: 'item 2', start: new Date(2013, 3, 14)},
{id: 3, content: 'item 3', start: new Date(2013, 3, 18)},
{id: 4, content: 'item 4', start: new Date(2013, 3, 16), end: new Date(2013, 3, 19)},
{id: 5, content: 'item 5', start: new Date(2013, 3, 25)},
{id: 6, content: 'item 6', start: new Date(2013, 3, 27)}
]);
<div id="visualization"></div>
<p></p>
<div id="log"></div>
var container = document.getElementById('visualization');
var options = {
editable: true,
<script type="text/javascript">
// note that months are zero-based in the JavaScript Date object, so month 3 is April
var items = new vis.DataSet([
{id: 1, content: 'item 1', start: new Date(2013, 3, 20)},
{id: 2, content: 'item 2', start: new Date(2013, 3, 14)},
{id: 3, content: 'item 3', start: new Date(2013, 3, 18)},
{id: 4, content: 'item 4', start: new Date(2013, 3, 16), end: new Date(2013, 3, 19)},
{id: 5, content: 'item 5', start: new Date(2013, 3, 25)},
{id: 6, content: 'item 6', start: new Date(2013, 3, 27)}
]);
onAdd: function (item, callback) {
item.content = prompt('Enter text content for new item:', item.content);
if (item.content != null) {
callback(item); // send back adjusted new item
}
else {
callback(null); // cancel item creation
}
},
var min = new Date(2013, 3, 1); // 1 april
var max = new Date(2013, 3, 30, 23, 59, 59); // 30 april
onMove: function (item, callback) {
if (confirm('Do you really want to move the item to\n' +
'start: ' + item.start + '\n' +
'end: ' + item.end + '?')) {
callback(item); // send back item as confirmation (can be changed
}
else {
callback(null); // cancel editing item
}
},
var container = document.getElementById('visualization');
var options = {
editable: true,
onUpdate: function (item, callback) {
item.content = prompt('Edit items text:', item.content);
if (item.content != null) {
callback(item); // send back adjusted item
}
else {
callback(null); // cancel updating the item
}
},
onAdd: function (item, callback) {
item.content = prompt('Enter text content for new item:', item.content);
if (item.content != null) {
callback(item); // send back adjusted new item
}
else {
callback(null); // cancel item creation
}
},
onRemove: function (item, callback) {
if (confirm('Remove item ' + item.content + '?')) {
callback(item); // confirm deletion
}
else {
callback(null); // cancel deletion
}
onMove: function (item, callback) {
if (confirm('Do you really want to move the item to\n' +
'start: ' + item.start + '\n' +
'end: ' + item.end + '?')) {
callback(item); // send back item as confirmation (can be changed)
}
else {
callback(null); // cancel editing item
}
};
var timeline = new vis.Timeline(container, items, options);
},
items.on('*', function (event, properties) {
logEvent(event, properties);
});
onMoving: function (item, callback) {
if (item.start < min) item.start = min;
if (item.start > max) item.start = max;
function logEvent(event, properties) {
var log = document.getElementById('log');
var msg = document.createElement('div');
msg.innerHTML = 'event=' + JSON.stringify(event) + ', ' +
'properties=' + JSON.stringify(properties);
log.firstChild ? log.insertBefore(msg, log.firstChild) : log.appendChild(msg);
callback(item); // send back the (possibly) changed item
},
onUpdate: function (item, callback) {
item.content = prompt('Edit items text:', item.content);
if (item.content != null) {
callback(item); // send back adjusted item
}
else {
callback(null); // cancel updating the item
}
},
onRemove: function (item, callback) {
if (confirm('Remove item ' + item.content + '?')) {
callback(item); // confirm deletion
}
else {
callback(null); // cancel deletion
}
}
};
var timeline = new vis.Timeline(container, items, options);
items.on('*', function (event, properties) {
logEvent(event, properties);
});
function logEvent(event, properties) {
var log = document.getElementById('log');
var msg = document.createElement('div');
msg.innerHTML = 'event=' + JSON.stringify(event) + ', ' +
'properties=' + JSON.stringify(properties);
log.firstChild ? log.insertBefore(msg, log.firstChild) : log.appendChild(msg);
}
</script>
</script>
</body>
</html>

+ 45
- 18
lib/timeline/component/ItemSet.js View File

@ -47,6 +47,7 @@ function ItemSet(body, options) {
onMove: function (item, callback) {
callback(item);
},
onMoving: null,
onRemove: function (item, callback) {
callback(item);
},
@ -303,7 +304,7 @@ ItemSet.prototype.setOptions = function(options) {
this.options[name] = fn;
}
}).bind(this);
['onAdd', 'onUpdate', 'onRemove', 'onMove'].forEach(addCallback);
['onAdd', 'onUpdate', 'onRemove', 'onMove', 'onMoving'].forEach(addCallback);
// force the itemSet to refresh: options like orientation and margins may be changed
this.markDirty();
@ -1094,28 +1095,44 @@ ItemSet.prototype._onDragStart = function (event) {
*/
ItemSet.prototype._onDrag = function (event) {
if (this.touchParams.itemProps) {
var range = this.body.range,
snap = this.body.util.snap || null,
deltaX = event.gesture.deltaX,
scale = (this.props.width / (range.end - range.start)),
offset = deltaX / scale;
var me = this;
var range = this.body.range;
var snap = this.body.util.snap || null;
var deltaX = event.gesture.deltaX;
var scale = (this.props.width / (range.end - range.start));
var offset = deltaX / scale;
// move
this.touchParams.itemProps.forEach(function (props) {
var newProps = {};
if ('start' in props) {
var start = new Date(props.start + offset);
props.item.data.start = snap ? snap(start) : start;
newProps.start = snap ? snap(start) : start;
}
if ('end' in props) {
var end = new Date(props.end + offset);
props.item.data.end = snap ? snap(end) : end;
newProps.end = snap ? snap(end) : end;
}
if ('group' in props) {
// drag from one group to another
var group = ItemSet.groupFromTarget(event);
_moveToGroup(props.item, group);
newProps.group = group && group.groupId;
}
if (me.options.onMoving) {
var itemData = util.extend({}, props.item.data, newProps);
me.options.onMoving(itemData, function (itemData) {
if (itemData) {
me._updateItemProps(props.item, itemData);
}
});
}
else {
me._updateItemProps(props.item, newProps);
}
});
@ -1128,13 +1145,28 @@ ItemSet.prototype._onDrag = function (event) {
}
};
/**
* Update an items properties
* @param {Item} item
* @param {Object} props Can contain properties start, end, and group.
* @private
*/
ItemSet.prototype._updateItemProps = function(item, props) {
if ('start' in props) item.data.start = props.start;
if ('end' in props) item.data.end = props.end;
if ('group' in props && item.data.group != props.group) {
this._moveToGroup(item, props.group)
}
};
/**
* Move an item to another group
* @param {Item} item
* @param {Group} group
* @param {String | Number} groupId
* @private
*/
function _moveToGroup (item, group) {
ItemSet.prototype._moveToGroup = function(item, groupId) {
var group = this.groups[groupId];
if (group && group.groupId != item.data.group) {
var oldGroup = item.parent;
oldGroup.remove(item);
@ -1144,7 +1176,7 @@ function _moveToGroup (item, group) {
item.data.group = group.groupId;
}
}
};
/**
* End of dragging selected items
@ -1190,12 +1222,7 @@ ItemSet.prototype._onDragEnd = function (event) {
}
else {
// restore original values
if ('start' in props) props.item.data.start = props.start;
if ('end' in props) props.item.data.end = props.end;
if ('group' in props && props.item.data.group != props.group) {
var group = me.groups[props.group];
_moveToGroup(props.item, group);
}
me._updateItemProps(props.item, props);
me.stackDirty = true; // force re-stacking of all items next redraw
me.body.emitter.emit('change');

+ 52
- 1
test/timeline_groups.html View File

@ -57,7 +57,7 @@
// create a dataset with items
var items = new vis.DataSet();
for (var i = 0; i < itemCount; i++) {
var start = now.clone().add('hours', Math.random() * 200);
var start = now.clone().add(Math.random() * 200, 'hours');
var group = Math.floor(Math.random() * groupCount);
items.add({
id: i,
@ -80,6 +80,57 @@
updateTime: true,
updateGroup: true
},
onAdd: function (item, callback) {
item.content = prompt('Enter text content for new item:', item.content);
if (item.content != null) {
callback(item); // send back adjusted new item
}
else {
callback(null); // cancel item creation
}
},
onMove: function (item, callback) {
if (confirm('Do you really want to move the item to\n' +
'start: ' + item.start + '\n' +
'end: ' + item.end + '?')) {
callback(item); // send back item as confirmation (can be changed)
}
else {
callback(null); // cancel editing item
}
},
onMoving: function (item, callback) {
var min = moment().minutes(0).seconds(0).milliseconds(0).add(2, 'day').toDate();
if (item.start < min) {
item.start = min;
}
callback(item); // send back item as confirmation (can be changed)
},
onUpdate: function (item, callback) {
item.content = prompt('Edit items text:', item.content);
if (item.content != null) {
callback(item); // send back adjusted item
}
else {
callback(null); // cancel updating the item
}
},
onRemove: function (item, callback) {
if (confirm('Remove item ' + item.content + '?')) {
callback(item); // confirm deletion
}
else {
callback(null); // cancel deletion
}
},
//stack: false,
//height: 200,
groupOrder: 'content'

Loading…
Cancel
Save