Commit 8642a56c authored by Thimo Kraemer's avatar Thimo Kraemer

Added DND plugin

parent e35322e8
/*
dnd.js - KSS HTML5 Drag and Drop plugin
Copyright (c) 2018, joonis new media
Author: Thimo Kraemer <thimo.kraemer@joonis.de>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
MA 02110-1301, USA.
*/
/*******************************************************************************************
* DND Plugin
*
* Based on the original Kukit plugin and modified to work with KSS-RPC.
* Should be reviewed in general.
*
* Brief documentation
* -------------------
*
* This plugin provides HTML5 DND functionality and is not meant to move some
* elements around the screen. The latter can be realized with the core KSS
* functionality.
*
* Main events to use:
*
* - "dragstart" on dragable elements
* - "drop" on target elements
*
* All bindings are done automatically (no need to set *draggable* attributes, etc).
* In *dragstart* events the most important thing is to set the drag data with the
* KSS action *setDragData*:
*
* .my-draggable:dragstart {
* kss-action-client: setDragData;
* type: "text/plain";
* value: outerHTML();
* }
*
* In *drop* events you can access the data with the paramater provider "dragData":
*
* #my-drop-target:drop {
* kss-action-client: appendHTML:
* html: dragData("text/plain");
* }
*
* An element is only dropable if all required drag data types are available.
* That is all requested types in *drop* must have been specified in *dragstart*
* previously. Dropping data from external resources is possible as well with
* the exception of files which isn't implemented yet.
*
******************************************************************************************/
kss.dnd = {
_dropEffect: 'move',
_isValidDropTarget: false,
_dataTransfer: null
};
// A wrapper around the original dataTransfer object of a DND event.
// It enables the possibility to use any data type within the document,
// even if not supported by the browser (eg. IE<10).
kss.dnd.DataTransfer = function() {
this.initialize = function(dataTransfer, external) {
this.data = {};
this.dataTransfer = dataTransfer;
this.external = external || false;
if (external) {
// IE<10 doesn't provide the "types" attribute,
// so we assume that we have a text and url value.
// Anyway the only two supported types by IE<10.
this.types = dataTransfer.types || ['text/plain', 'text/uri-list'];
}
else {
this.types = [];
}
};
this.normalizeType = function(type) {
// Type mapping for backwards compatibility (IE<10)
var map = {'text': 'text/plain', 'url': 'text/uri-list'};
type = type.toLowerCase();
return map[type] || type;
};
this.setData = function(type, value) {
type = this.normalizeType(type);
this.types.push(type);
this.data[type] = value;
// required by IE
if (type == 'text/plain') {
this.dataTransfer.setData('Text', value);
}
else if (type == 'text/uri-list') {
this.dataTransfer.setData('url', value);
}
try {
this.dataTransfer.setData(type, value);
}
catch (exc) {}
};
this.getData = function(type) {
type = this.normalizeType(type);
var value = this.data[type];
if (!value) {
// required by IE
if (type == 'text/plain') {
value = this.dataTransfer.getData('Text');
}
else if (type == 'text/uri-list') {
value = this.dataTransfer.getData('url');
}
}
if (!value) {
try {
value = this.dataTransfer.getData(type);
}
catch (exc) {}
}
return value || '';
};
this.initialize.apply(this, arguments);
};
kss.dnd.isValidDropTarget = function(handler) {
var dnd = kss.dnd;
var dataTransfer = dnd._dataTransfer;
if (!dataTransfer || dnd._dropEffect == 'none') {
dnd._isValidDropTarget = false;
}
else if (handler) {
// We want to know all required data types of each action that
// is processed when an object is dropped. For that we evaluate
// all parameter providers that contain the provider "dragData".
// The real job is then done by the parameter provider "dragData"
// itself, see below.
handler._requiredDragDataTypes = [];
kss.each(handler.rule.actions, function(action) {
kss.each(action.props, function(value) {
if (/\bdragData\s*\(/.test(value)) {
handler._evalParam(value);
}
});
kss.each(action.params, function(value) {
if (/\bdragData\s*\(/.test(value)) {
handler._evalParam(value);
}
});
});
var requiredDragData = handler._requiredDragDataTypes;
delete handler._requiredDragDataTypes;
// Now check if all requested data types are available.
// Only in this case we declare it as a valid drop target.
var isValid = true;
for (var i=0; i<requiredDragData.length; i++) {
isValid = false;
var type = dataTransfer.normalizeType(requiredDragData[i]);
for (var j=0; j<dataTransfer.types.length; j++) {
if (dataTransfer.types[j] == type) {
isValid = true;
break;
}
}
if (!isValid) {
break;
}
}
dnd._isValidDropTarget = isValid;
}
// Set it also as state variable.
kss._stateVars['validDropTarget'] = dnd._isValidDropTarget;
return dnd._isValidDropTarget;
};
// DND DragStart binder class
kss.dnd._DragStartEventBinder = function(handler) {
// Make the node draggable if there exists a "dragstart" event.
var node = handler.node;
var props = handler.propertyGetter({
effectallowed: 'all'
});
if (node.getAttribute('draggable') != 'true') {
// HTML5 way
node.setAttribute('draggable', 'true');
node.style.cursor = 'default';
try {
// required by Safari
node.style.webkitUserDrag = 'element';
node.style.khtmlUserDrag = 'element';
} catch (exc) {}
if (node.dragDrop) {
// required by IE<10
node.addEventListener('selectstart', function(e) {
node.dragDrop();
e.preventDefault();
}, false);
}
// Default behaviour on dragstart event.
// It must be executed before all others.
node.addEventListener('dragstart', function(e) {
if (node.getAttribute('draggable') != 'true') {
e.preventDefault();
return;
}
var effectallowed = props('effectallowed');
//~ if (effectallowed == 'shift') {
//~ e.preventDefault();
//~ return;
//~ }
// XXX Firefox through version 5 does not fire
// the drop event if effectAllowed is set.
e.dataTransfer.effectAllowed = effectallowed;
// Create and store our own dataTransfer object
kss.dnd._dataTransfer = new kss.dnd.DataTransfer(e.dataTransfer);
try {
// required at least by Firefox
e.dataTransfer.setData('_dummy', '');
} catch (exc) {}
}, true);
// Default behaviour on dragend event.
node.addEventListener('dragend', function(e) {
// Clear the dataTransfer object
kss.dnd._dataTransfer = null;
}, false);
}
};
kss.registerEventBinder('dragstart', kss.dnd._DragStartEventBinder);
// DND Drop binder class
kss.dnd._DropEventBinder = function(handler) {
var dnd = kss.dnd;
var node = handler.node;
if (!node.getAttribute('droptarget')) {
node.setAttribute('droptarget', 'true');
// Default behaviour on drop event.
node.addEventListener('drop', function(e) {
// We must renew the dataTransfer object on external drag actions
if (!dnd._dataTransfer || dnd._dataTransfer.external) {
dnd._dataTransfer = new dnd.DataTransfer(e.dataTransfer, true);
}
// By default Firefox opens any transfered data as URL
e.preventDefault();
}, false);
}
// Default behaviour on dragenter event.
// It must be executed before all others.
node.addEventListener('dragenter', function(e) {
// We must renew the dataTransfer object on external drag actions
if (!dnd._dataTransfer || dnd._dataTransfer.external) {
dnd._dataTransfer = new dnd.DataTransfer(e.dataTransfer, true);
}
if (kss.dnd.isValidDropTarget(handler)) {
// Init drop effect
dnd._dropEffect = e.dataTransfer.dropEffect = 'move';
// Make the node a valid drop target
e.preventDefault();
}
}, true);
// Default behaviour on dragover event.
node.addEventListener('dragover', function(e) {
if (kss.dnd.isValidDropTarget()) {
// Apply drop effect
e.dataTransfer.dropEffect = dnd._dropEffect;
// Make the node a valid drop target
e.preventDefault();
}
}, false);
};
kss.registerEventBinder('drop', kss.dnd._DropEventBinder);
// action provider setDropEffect
kss.registerActionProvider('setDropEffect', function(node, params) {
// value must be one of none, move, copy or link
if (this.eventname != 'dragenter' && this.eventname != 'dragover') {
this.error('Action provider setDropEffect is only '
+ 'available during events [dragenter|dragover].');
return;
}
kss.dnd._dropEffect = params.value.toLowerCase();
// update validDropTarget state variable
kss.dnd.isValidDropTarget();
});
// action provider setDragImage, sets the element given by kssSelector
kss.registerActionProvider('setDragImage', function(node, params) {
if (this.eventname != 'dragstart') {
this.error('Action provider setDragImage is only '
+ 'available during event [dragstart].');
return;
}
try {
// Set the drag image. Not supported by IE.
// Offsets are buggy in Opera and Firefox.
this.event.dataTransfer.setDragImage(
node, params.offsetX || 0, params.offsetY || 0);
// dataTransfer.addElement(elem) could be interesting, too.
// But at time not supported by Safari and IE.
} catch (exc) {}
});
// action provider setDragData
kss.registerActionProvider('setDragData', function(node, params) {
if (this.eventname != 'dragstart') {
this.error('Action provider setDragData is only '
+ 'available during event [dragstart].');
return;
}
if (!kss.dnd._dataTransfer) {
this.error('No dataTransfer object available');
return;
}
kss.dnd._dataTransfer.setData(params.type, params.value);
});
// parameter provider dragData
kss.registerParameterProvider('dragData', function(node, datatype) {
// Check if we just want to collect all requested data types.
// Used by kss.dnd.isValidDropTarget above.
if (typeof this._requiredDragDataTypes == 'object') {
this._requiredDragDataTypes.push(datatype);
return;
}
if (!kss.dnd._dataTransfer) {
this.error('No dataTransfer object available');
return;
}
return kss.dnd._dataTransfer.getData(datatype);
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment