Initial
This commit is contained in:
168
resources/app/client/ui/dragdrop.js
vendored
Normal file
168
resources/app/client/ui/dragdrop.js
vendored
Normal file
@@ -0,0 +1,168 @@
|
||||
/**
|
||||
* @typedef {object} DragDropConfiguration
|
||||
* @property {string} dragSelector The CSS selector used to target draggable elements.
|
||||
* @property {string} dropSelector The CSS selector used to target viable drop targets.
|
||||
* @property {Record<string,Function>} permissions An object of permission test functions for each action
|
||||
* @property {Record<string,Function>} callbacks An object of callback functions for each action
|
||||
*/
|
||||
|
||||
/**
|
||||
* A controller class for managing drag and drop workflows within an Application instance.
|
||||
* The controller manages the following actions: dragstart, dragover, drop
|
||||
* @see {@link Application}
|
||||
*
|
||||
* @param {DragDropConfiguration}
|
||||
* @example Activate drag-and-drop handling for a certain set of elements
|
||||
* ```js
|
||||
* const dragDrop = new DragDrop({
|
||||
* dragSelector: ".item",
|
||||
* dropSelector: ".items",
|
||||
* permissions: { dragstart: this._canDragStart.bind(this), drop: this._canDragDrop.bind(this) },
|
||||
* callbacks: { dragstart: this._onDragStart.bind(this), drop: this._onDragDrop.bind(this) }
|
||||
* });
|
||||
* dragDrop.bind(html);
|
||||
* ```
|
||||
*/
|
||||
class DragDrop {
|
||||
constructor({dragSelector, dropSelector, permissions={}, callbacks={}} = {}) {
|
||||
|
||||
/**
|
||||
* The HTML selector which identifies draggable elements
|
||||
* @type {string}
|
||||
*/
|
||||
this.dragSelector = dragSelector;
|
||||
|
||||
/**
|
||||
* The HTML selector which identifies drop targets
|
||||
* @type {string}
|
||||
*/
|
||||
this.dropSelector = dropSelector;
|
||||
|
||||
/**
|
||||
* A set of permission checking functions for each action of the Drag and Drop workflow
|
||||
* @type {Object}
|
||||
*/
|
||||
this.permissions = permissions;
|
||||
|
||||
/**
|
||||
* A set of callback functions for each action of the Drag and Drop workflow
|
||||
* @type {Object}
|
||||
*/
|
||||
this.callbacks = callbacks;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Bind the DragDrop controller to an HTML application
|
||||
* @param {HTMLElement} html The HTML element to which the handler is bound
|
||||
*/
|
||||
bind(html) {
|
||||
|
||||
// Identify and activate draggable targets
|
||||
if ( this.can("dragstart", this.dragSelector) ) {
|
||||
const draggables = html.querySelectorAll(this.dragSelector);
|
||||
for (let el of draggables) {
|
||||
el.setAttribute("draggable", true);
|
||||
el.ondragstart = this._handleDragStart.bind(this);
|
||||
}
|
||||
}
|
||||
|
||||
// Identify and activate drop targets
|
||||
if ( this.can("drop", this.dropSelector) ) {
|
||||
const droppables = !this.dropSelector || html.matches(this.dropSelector) ? [html] :
|
||||
html.querySelectorAll(this.dropSelector);
|
||||
for ( let el of droppables ) {
|
||||
el.ondragover = this._handleDragOver.bind(this);
|
||||
el.ondrop = this._handleDrop.bind(this);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Execute a callback function associated with a certain action in the workflow
|
||||
* @param {DragEvent} event The drag event being handled
|
||||
* @param {string} action The action being attempted
|
||||
*/
|
||||
callback(event, action) {
|
||||
const fn = this.callbacks[action];
|
||||
if ( fn instanceof Function ) return fn(event);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Test whether the current user has permission to perform a step of the workflow
|
||||
* @param {string} action The action being attempted
|
||||
* @param {string} selector The selector being targeted
|
||||
* @return {boolean} Can the action be performed?
|
||||
*/
|
||||
can(action, selector) {
|
||||
const fn = this.permissions[action];
|
||||
if ( fn instanceof Function ) return fn(selector);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Handle the start of a drag workflow
|
||||
* @param {DragEvent} event The drag event being handled
|
||||
* @private
|
||||
*/
|
||||
_handleDragStart(event) {
|
||||
this.callback(event, "dragstart");
|
||||
if ( event.dataTransfer.items.length ) event.stopPropagation();
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Handle a dragged element over a droppable target
|
||||
* @param {DragEvent} event The drag event being handled
|
||||
* @private
|
||||
*/
|
||||
_handleDragOver(event) {
|
||||
event.preventDefault();
|
||||
this.callback(event, "dragover");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Handle a dragged element dropped on a droppable target
|
||||
* @param {DragEvent} event The drag event being handled
|
||||
* @private
|
||||
*/
|
||||
_handleDrop(event) {
|
||||
event.preventDefault();
|
||||
return this.callback(event, "drop");
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
static createDragImage(img, width, height) {
|
||||
let div = document.getElementById("drag-preview");
|
||||
|
||||
// Create the drag preview div
|
||||
if ( !div ) {
|
||||
div = document.createElement("div");
|
||||
div.setAttribute("id", "drag-preview");
|
||||
const img = document.createElement("img");
|
||||
img.classList.add("noborder");
|
||||
div.appendChild(img);
|
||||
document.body.appendChild(div);
|
||||
}
|
||||
|
||||
// Add the preview image
|
||||
const i = div.children[0];
|
||||
i.src = img.src;
|
||||
i.width = width;
|
||||
i.height = height;
|
||||
return div;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user