Browse Source

Drag and drop support for the timeline (#2238)

* Add initial experiment
* Register drag and drop events
* Add initial drag and drop support
* Add drag and drop support for all types of objects
* Fix example
* Clean up code and add comments
* Fix example
* remove font awesome
* Fix example style
* Add meta tag to example and add selected when dropped
codeClimate
yotamberk 8 years ago
committed by Alexander Wunschik
parent
commit
dd324da2a2
3 changed files with 199 additions and 10 deletions
  1. +131
    -0
      examples/timeline/other/drag&drop.html
  2. +40
    -0
      lib/timeline/Core.js
  3. +28
    -10
      lib/timeline/component/ItemSet.js

+ 131
- 0
examples/timeline/other/drag&drop.html View File

@ -0,0 +1,131 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8"/>
<title>Timeline | Drag & Drop</title>
<script src="../../../dist/vis.js"></script>
<link href="../../../dist/vis.css" rel="stylesheet" type="text/css" />
<script src="../../googleAnalytics.js"></script>
<style type="text/css">
li.item {
list-style: none;
width: 150px;
color: #1A1A1A;
background-color: #D5DDF6;
border: 1px solid #97B0F8;
border-radius: 2px;
margin-bottom: 5px;
padding: 5px 12px;
}
li.item:before {
content: "≣";
font-family: Arial, sans-serif;
display: inline-block;
font-size: inherit;
cursor: move;
}
</style>
</head>
<body>
<h1>Timeline Drag & Drop Example</h1>
<p>For this to work, you will have to define your own <code>'dragstart'</code> eventListener on each item in your list of items (make sure that any new item added to the list is attached to this eventListener 'dragstart' handler). This 'dragstart' handler must set <code>dataTransfer</code> - notice you can set the item's information as you want this way.</p>
<div id="mytimeline" ></div>
<div>
<h3>Items:</h3>
<ul class="items">
<li draggable="true" class="item">
item 1 - box
</li>
<li draggable="true" class="item">
item 2 - point
</li>
<li draggable="true" class="item">
item 3 - range
</li>
</ul>
</div>
<script>
// create groups
var numberOfGroups = 3;
var groups = new vis.DataSet()
for (var i = 0; i < numberOfGroups; i++) {
groups.add({
id: i,
content: 'Truck&nbsp;' + i
})
}
// create items
var numberOfItems = 10;
var items = new vis.DataSet();
var itemsPerGroup = Math.round(numberOfItems/numberOfGroups);
for (var truck = 0; truck < numberOfGroups; truck++) {
var date = new Date();
for (var order = 0; order < itemsPerGroup; order++) {
date.setHours(date.getHours() + 4 * (Math.random() < 0.2));
var start = new Date(date);
date.setHours(date.getHours() + 2 + Math.floor(Math.random()*4));
var end = new Date(date);
items.add({
id: order + itemsPerGroup * truck,
group: truck,
start: start,
end: end,
content: 'Order ' + order
});
}
}
// specify options
var options = {
stack: true,
start: new Date(),
end: new Date(1000*60*60*24 + (new Date()).valueOf()),
editable: true,
orientation: 'top'
};
// create a Timeline
var container = document.getElementById('mytimeline');
timeline1 = new vis.Timeline(container, items, groups, options);
function handleDragStart(event) {
dragSrcEl = event.target;
event.dataTransfer.effectAllowed = 'move';
var itemType = event.target.innerHTML.split('-')[1].trim();
var item = {
id: new Date(),
type: itemType,
content: event.target.innerHTML.split('-')[0].trim(),
start: new Date(),
end: new Date(1000*60*60*24 + (new Date()).valueOf()),
};
event.dataTransfer.setData("text/plain", JSON.stringify(item));
}
var items = document.querySelectorAll('.items .item');
for (var i = items.length - 1; i >= 0; i--) {
var item = items[i];
item.addEventListener('dragstart', handleDragStart.bind(this), false);
}
</script>
</body>
</html>

+ 40
- 0
lib/timeline/Core.js View File

@ -231,6 +231,46 @@ Core.prototype._create = function (container) {
this.dom.left.parentNode.addEventListener('scroll', onMouseScrollSide.bind(this));
this.dom.right.parentNode.addEventListener('scroll', onMouseScrollSide.bind(this));
var itemAddedToTimeline = false;
function handleDragOver(event) {
if (event.preventDefault) {
event.preventDefault(); // Necessary. Allows us to drop.
}
// make sure your target is a vis element
if (!event.target.className.includes('vis')) return;
// make sure only one item is added every time you're over the timeline
if (itemAddedToTimeline) return;
event.dataTransfer.dropEffect = 'move';
itemAddedToTimeline = true;
return false;
}
function handleDrop(event) {
// return when dropping non-vis items
try {
var itemData = JSON.parse(event.dataTransfer.getData("text/plain"))
if (!itemData.content) return
} catch (err) {
return false;
}
itemAddedToTimeline = false;
event.center = {
x: event.x,
y: event.y
}
me.itemSet._onAddItem(event);
return false;
}
this.dom.center.addEventListener('dragover', handleDragOver.bind(this), false);
this.dom.center.addEventListener('drop', handleDrop.bind(this), false);
this.customTimes = [];
// store state information needed for touch events

+ 28
- 10
lib/timeline/component/ItemSet.js View File

@ -95,9 +95,7 @@ function ItemSet(body, options) {
// options is shared by this ItemSet and all its items
this.options = util.extend({}, this.defaultOptions);
if (options) {
this.options.rtl = options.rtl; // required to determine from the initial creation if rtl
}
this.options.rtl = options.rtl;
// options for getting items from the DataSet with the correct type
this.itemOptions = {
@ -320,7 +318,7 @@ ItemSet.prototype.setOptions = function(options) {
// copy all options that we know
var fields = ['type', 'rtl', 'align', 'order', 'stack', 'selectable', 'multiselect', 'itemsAlwaysDraggable', 'multiselectPerGroup', 'groupOrder', 'dataAttributes', 'template', 'groupTemplate', 'hide', 'snap', 'groupOrderSwap'];
util.selectiveExtend(fields, this.options, options);
if ('orientation' in options) {
if (typeof options.orientation === 'string') {
this.options.orientation.item = options.orientation === 'top' ? 'top' : 'bottom';
@ -1825,13 +1823,30 @@ ItemSet.prototype._onAddItem = function (event) {
content: 'new item'
};
// when default type is a range, add a default end date to the new item
if (this.options.type === 'range') {
var end = this.body.util.toTime(x + this.props.width / 5);
newItemData.end = snap ? snap(end, scale, step) : end;
}
if (event.type == 'drop') {
var itemData = JSON.parse(event.dataTransfer.getData("text/plain"))
newItemData.content = itemData.content; // content is required
newItemData.type = itemData.type || 'box';
newItemData[this.itemsData._fieldId] = itemData.id || util.randomUUID();
if (itemData.type == 'range' || (itemData.end && itemData.start)) {
if (!itemData.end) {
var end = this.body.util.toTime(x + this.props.width / 5);
newItemData.end = snap ? snap(end, scale, step) : end;
} else {
newItemData.end = new Date(newItemData.start._i).getTime() + new Date(itemData.end).getTime() - new Date(itemData.start).getTime();
}
}
} else {
newItemData[this.itemsData._fieldId] = util.randomUUID();
newItemData[this.itemsData._fieldId] = util.randomUUID();
// when default type is a range, add a default end date to the new item
if (this.options.type === 'range') {
var end = this.body.util.toTime(x + this.props.width / 5);
newItemData.end = snap ? snap(end, scale, step) : end;
}
}
var group = this.groupFromTarget(event);
if (group) {
@ -1843,6 +1858,9 @@ ItemSet.prototype._onAddItem = function (event) {
this.options.onAdd(newItemData, function (item) {
if (item) {
me.itemsData.getDataSet().add(item);
if (event.type == 'drop') {
me.setSelection([item.id]);
}
// TODO: need to trigger a redraw?
}
});

Loading…
Cancel
Save