Skip to content

Commit 3bc3d16

Browse files
authored
Drag object in to item (visjs#3506)
* Make items redraws return queues * Parallel initial items redraw * Seperate read and write actions in items * Parallel all items redraws * Remove comments * Fix linting comments * Fix redraws on actions * Seperate group read and write * Add objects that can be dropped to items * Move example * Fix group get/set group properties * Add documentation * Remove console.log * return Group.js changes * Fix review comments * Fix example * Update drag&drop.html
1 parent aa320ae commit 3bc3d16

File tree

5 files changed

+163
-84
lines changed

5 files changed

+163
-84
lines changed

docs/timeline/index.html

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -913,6 +913,14 @@ <h2 id="Configuration_Options">Configuration Options</h2>
913913
</td>
914914
</tr>
915915

916+
<tr>
917+
<td>onDropObjectOnItem</td>
918+
<td>function</td>
919+
<td>none</td>
920+
<td>Callback function triggered when an object containing <code>target:'item'</code> in its drag data is dropped in to a timeline item.
921+
</td>
922+
</tr>
923+
916924
<tr>
917925
<td>onUpdate</td>
918926
<td>function</td>
@@ -1823,6 +1831,7 @@ <h2 id="Editing_Items">Editing Items</h2>
18231831
<ul>
18241832
<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>
18251833
<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>
1834+
<li><code>onDropObjectOnItem(objectData, item)</code> Fired when an object is dropped in to an existing timeline item.</li>
18261835
<li><code>onMove(item, callback)</code> Fired when an item has been moved. If not implemented, the move action will be accepted.</li>
18271836
<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>
18281837
<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>

examples/timeline/other/drag&drop.html

Lines changed: 71 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,27 @@
2727
font-size: inherit;
2828
cursor: move;
2929
}
30+
li.object-item {
31+
list-style: none;
32+
width: 150px;
33+
color: #1A1A1A;
34+
background-color: #D5DDF6;
35+
border: 1px solid #97B0F8;
36+
border-radius: 2px;
37+
margin-bottom: 5px;
38+
padding: 5px 12px;
39+
}
40+
li.object-item:before {
41+
content: "≣";
42+
font-family: Arial, sans-serif;
43+
display: inline-block;
44+
font-size: inherit;
45+
cursor: move;
46+
}
47+
.items-panel {
48+
display: flex;
49+
justify-content: space-around;
50+
}
3051
</style>
3152
</head>
3253

@@ -37,23 +58,33 @@ <h1>Timeline Drag & Drop Example</h1>
3758
<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>
3859

3960
<div id="mytimeline" ></div>
40-
<div>
41-
<h3>Items:</h3>
42-
<ul class="items">
43-
<li draggable="true" class="item">
44-
item 1 - box
45-
</li>
46-
<li draggable="true" class="item">
47-
item 2 - point
48-
</li>
49-
<li draggable="true" class="item">
50-
item 3 - range
51-
</li>
52-
<li draggable="true" class="item">
53-
item 3 - range - fixed times - <br>
54-
(start: now, end: now + 10 min)
61+
62+
<div class='items-panel'>
63+
<div class='side'>
64+
<h3>Items:</h3>
65+
<ul class="items">
66+
<li draggable="true" class="item">
67+
item 1 - box
68+
</li>
69+
<li draggable="true" class="item">
70+
item 2 - point
71+
</li>
72+
<li draggable="true" class="item">
73+
item 3 - range
74+
</li>
75+
<li draggable="true" class="item">
76+
item 3 - range - fixed times - <br>
77+
(start: now, end: now + 10 min)
78+
</li>
79+
</ul>
80+
</div>
81+
82+
<div class='side'>
83+
<h3>Object with "target:'item'":</h3>
84+
<li draggable="true" class="object-item">
85+
object with target:'item'
5586
</li>
56-
</ul>
87+
</div>
5788
</div>
5889

5990
<script>
@@ -99,15 +130,19 @@ <h3>Items:</h3>
99130
start: new Date(),
100131
end: new Date(1000*60*60*24 + (new Date()).valueOf()),
101132
editable: true,
102-
orientation: 'top'
133+
orientation: 'top',
134+
onDropObjectOnItem: function(objectData, item, callback) {
135+
if (!item) { return; }
136+
alert('dropped object with content: "' + objectData.content + '" to item: "' + item.content + '"');
137+
}
103138
};
104139

105140
// create a Timeline
106141
var container = document.getElementById('mytimeline');
107142
timeline1 = new vis.Timeline(container, items, groups, options);
108143

109144
function handleDragStart(event) {
110-
dragSrcEl = event.target;
145+
var dragSrcEl = event.target;
111146

112147
event.dataTransfer.effectAllowed = 'move';
113148
var itemType = event.target.innerHTML.split('-')[1].trim();
@@ -122,17 +157,34 @@ <h3>Items:</h3>
122157
item.start = new Date();
123158
item.end = new Date(1000*60*10 + (new Date()).valueOf());
124159
}
125-
126160
event.dataTransfer.setData("text", JSON.stringify(item));
127161
}
128162

163+
function handleObjectItemDragStart(event) {
164+
var dragSrcEl = event.target;
165+
166+
event.dataTransfer.effectAllowed = 'move';
167+
var objectItem = {
168+
content: 'objectItemData',
169+
target: 'item'
170+
};
171+
event.dataTransfer.setData("text", JSON.stringify(objectItem));
172+
}
173+
129174
var items = document.querySelectorAll('.items .item');
130175

176+
var objectItems = document.querySelectorAll('.object-item');
177+
131178
for (var i = items.length - 1; i >= 0; i--) {
132179
var item = items[i];
133180
item.addEventListener('dragstart', handleDragStart.bind(this), false);
134181
}
135182

183+
for (var i = objectItems.length - 1; i >= 0; i--) {
184+
var objectItem = objectItems[i];
185+
objectItem.addEventListener('dragstart', handleObjectItemDragStart.bind(this), false);
186+
}
187+
136188
</script>
137189

138190
</body>

lib/timeline/Core.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -291,10 +291,10 @@ Core.prototype._create = function (container) {
291291
// prevent redirect to blank page - Firefox
292292
if(event.preventDefault) { event.preventDefault(); }
293293
if(event.stopPropagation) { event.stopPropagation(); }
294-
// return when dropping non-vis items
294+
// return when dropping non-vis items
295295
try {
296296
var itemData = JSON.parse(event.dataTransfer.getData("text"))
297-
if (!itemData.content) return
297+
if (!itemData || !itemData.content) return
298298
} catch (err) {
299299
return false;
300300
}
@@ -303,8 +303,13 @@ Core.prototype._create = function (container) {
303303
event.center = {
304304
x: event.clientX,
305305
y: event.clientY
306+
};
307+
308+
if (itemData.target !== 'item') {
309+
me.itemSet._onAddItem(event);
310+
} else {
311+
me.itemSet._onDropObjectOnItem(event);
306312
}
307-
me.itemSet._onAddItem(event);
308313
me.emit('drop', me.getEventProperties(event))
309314
return false;
310315
}

lib/timeline/component/ItemSet.js

Lines changed: 74 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,14 @@ function ItemSet(body, options) {
6161
order: false,
6262
add: false,
6363
remove: false
64-
},
65-
64+
},
65+
6666
snap: TimeStep.snap,
6767

68+
// Only called when `objectData.target === 'item'.
69+
onDropObjectOnItem: function(objectData, item, callback) {
70+
callback(item)
71+
},
6872
onAdd: function (item, callback) {
6973
callback(item);
7074
},
@@ -444,7 +448,7 @@ ItemSet.prototype.setOptions = function(options) {
444448
this.options[name] = fn;
445449
}
446450
}).bind(this);
447-
['onAdd', 'onUpdate', 'onRemove', 'onMove', 'onMoving', 'onAddGroup', 'onMoveGroup', 'onRemoveGroup'].forEach(addCallback);
451+
['onDropObjectOnItem', 'onAdd', 'onUpdate', 'onRemove', 'onMove', 'onMoving', 'onAddGroup', 'onMoveGroup', 'onRemoveGroup'].forEach(addCallback);
448452

449453
// force the itemSet to refresh: options like orientation and margins may be changed
450454
this.markDirty();
@@ -2060,7 +2064,19 @@ ItemSet.prototype._onUpdateItem = function (item) {
20602064
};
20612065

20622066
/**
2063-
* Handle creation of an item on double tap
2067+
* Handle drop event of data on item
2068+
* Only called when `objectData.target === 'item'.
2069+
* @param {Event} event The event
2070+
* @private
2071+
*/
2072+
ItemSet.prototype._onDropObjectOnItem = function(event) {
2073+
var item = this.itemFromTarget(event);
2074+
var objectData = JSON.parse(event.dataTransfer.getData("text"));
2075+
this.options.onDropObjectOnItem(objectData, item)
2076+
}
2077+
2078+
/**
2079+
* Handle creation of an item on double tap or drop of a drag event
20642080
* @param {Event} event The event
20652081
* @private
20662082
*/
@@ -2070,69 +2086,65 @@ ItemSet.prototype._onAddItem = function (event) {
20702086

20712087
var me = this;
20722088
var snap = this.options.snap || null;
2073-
var item = this.itemFromTarget(event);
2074-
2075-
if (!item) {
2076-
var xAbs;
2077-
var x;
2078-
// add item
2079-
if (this.options.rtl) {
2080-
xAbs = util.getAbsoluteRight(this.dom.frame);
2081-
x = xAbs - event.center.x;
2082-
} else {
2083-
xAbs = util.getAbsoluteLeft(this.dom.frame);
2084-
x = event.center.x - xAbs;
2089+
var xAbs;
2090+
var x;
2091+
// add item
2092+
if (this.options.rtl) {
2093+
xAbs = util.getAbsoluteRight(this.dom.frame);
2094+
x = xAbs - event.center.x;
2095+
} else {
2096+
xAbs = util.getAbsoluteLeft(this.dom.frame);
2097+
x = event.center.x - xAbs;
2098+
}
2099+
// var xAbs = util.getAbsoluteLeft(this.dom.frame);
2100+
// var x = event.center.x - xAbs;
2101+
var start = this.body.util.toTime(x);
2102+
var scale = this.body.util.getScale();
2103+
var step = this.body.util.getStep();
2104+
var end;
2105+
2106+
var newItemData;
2107+
if (event.type == 'drop') {
2108+
newItemData = JSON.parse(event.dataTransfer.getData("text"));
2109+
newItemData.content = newItemData.content ? newItemData.content : 'new item';
2110+
newItemData.start = newItemData.start ? newItemData.start : (snap ? snap(start, scale, step) : start);
2111+
newItemData.type = newItemData.type || 'box';
2112+
newItemData[this.itemsData._fieldId] = newItemData.id || util.randomUUID();
2113+
2114+
if (newItemData.type == 'range' && !newItemData.end) {
2115+
end = this.body.util.toTime(x + this.props.width / 5);
2116+
newItemData.end = snap ? snap(end, scale, step) : end;
20852117
}
2086-
// var xAbs = util.getAbsoluteLeft(this.dom.frame);
2087-
// var x = event.center.x - xAbs;
2088-
var start = this.body.util.toTime(x);
2089-
var scale = this.body.util.getScale();
2090-
var step = this.body.util.getStep();
2091-
var end;
2092-
2093-
var newItemData;
2094-
if (event.type == 'drop') {
2095-
newItemData = JSON.parse(event.dataTransfer.getData("text"));
2096-
newItemData.content = newItemData.content ? newItemData.content : 'new item';
2097-
newItemData.start = newItemData.start ? newItemData.start : (snap ? snap(start, scale, step) : start);
2098-
newItemData.type = newItemData.type || 'box';
2099-
newItemData[this.itemsData._fieldId] = newItemData.id || util.randomUUID();
2100-
2101-
if (newItemData.type == 'range' && !newItemData.end) {
2102-
end = this.body.util.toTime(x + this.props.width / 5);
2103-
newItemData.end = snap ? snap(end, scale, step) : end;
2104-
}
2105-
} else {
2106-
newItemData = {
2107-
start: snap ? snap(start, scale, step) : start,
2108-
content: 'new item'
2109-
};
2110-
newItemData[this.itemsData._fieldId] = util.randomUUID();
2111-
2112-
// when default type is a range, add a default end date to the new item
2113-
if (this.options.type === 'range') {
2114-
end = this.body.util.toTime(x + this.props.width / 5);
2115-
newItemData.end = snap ? snap(end, scale, step) : end;
2116-
}
2118+
} else {
2119+
newItemData = {
2120+
start: snap ? snap(start, scale, step) : start,
2121+
content: 'new item'
2122+
};
2123+
newItemData[this.itemsData._fieldId] = util.randomUUID();
2124+
2125+
// when default type is a range, add a default end date to the new item
2126+
if (this.options.type === 'range') {
2127+
end = this.body.util.toTime(x + this.props.width / 5);
2128+
newItemData.end = snap ? snap(end, scale, step) : end;
21172129
}
2130+
}
21182131

2119-
var group = this.groupFromTarget(event);
2120-
if (group) {
2121-
newItemData.group = group.groupId;
2122-
}
2132+
var group = this.groupFromTarget(event);
2133+
if (group) {
2134+
newItemData.group = group.groupId;
2135+
}
21232136

2124-
// execute async handler to customize (or cancel) adding an item
2125-
newItemData = this._cloneItemData(newItemData); // convert start and end to the correct type
2126-
this.options.onAdd(newItemData, function (item) {
2127-
if (item) {
2128-
me.itemsData.getDataSet().add(item);
2129-
if (event.type == 'drop') {
2130-
me.setSelection([item.id]);
2131-
}
2132-
// TODO: need to trigger a redraw?
2137+
// execute async handler to customize (or cancel) adding an item
2138+
newItemData = this._cloneItemData(newItemData); // convert start and end to the correct type
2139+
this.options.onAdd(newItemData, function (item) {
2140+
if (item) {
2141+
me.itemsData.getDataSet().add(item);
2142+
if (event.type == 'drop') {
2143+
me.setSelection([item.id]);
21332144
}
2134-
});
2135-
}
2145+
// TODO: need to trigger a redraw?
2146+
}
2147+
});
21362148
};
21372149

21382150
/**

lib/timeline/optionsTimeline.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ let allOptions = {
118118
multiselect: { 'boolean': bool},
119119
multiselectPerGroup: { 'boolean': bool},
120120
onAdd: {'function': 'function'},
121+
onDropObjectOnItem: {'function': 'function'},
121122
onUpdate: {'function': 'function'},
122123
onMove: {'function': 'function'},
123124
onMoving: {'function': 'function'},

0 commit comments

Comments
 (0)