1
0
mirror of https://github.com/Jermolene/TiddlyWiki5 synced 2024-11-27 20:10:03 +00:00
TiddlyWiki5/core/modules/widgets/droppable.js
Jermolene eba1c3c160 Improve support for drag and drop
Documentation TBD
2017-03-19 19:33:56 +00:00

145 lines
4.4 KiB
JavaScript

/*\
title: $:/core/modules/widgets/droppable.js
type: application/javascript
module-type: widget
Droppable widget
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var Widget = require("$:/core/modules/widgets/widget.js").widget;
var DroppableWidget = function(parseTreeNode,options) {
this.initialise(parseTreeNode,options);
};
/*
Inherit from the base widget class
*/
DroppableWidget.prototype = new Widget();
/*
Render this widget into the DOM
*/
DroppableWidget.prototype.render = function(parent,nextSibling) {
var self = this;
// Remember parent
this.parentDomNode = parent;
// Compute attributes and execute state
this.computeAttributes();
this.execute();
// Create element
var domNode = this.document.createElement("div");
domNode.className = "tc-droppable";
// Add event handlers
$tw.utils.addEventListeners(domNode,[
{name: "dragenter", handlerObject: this, handlerMethod: "handleDragEnterEvent"},
{name: "dragover", handlerObject: this, handlerMethod: "handleDragOverEvent"},
{name: "dragleave", handlerObject: this, handlerMethod: "handleDragLeaveEvent"},
{name: "drop", handlerObject: this, handlerMethod: "handleDropEvent"}
]);
// Insert element
parent.insertBefore(domNode,nextSibling);
this.renderChildren(domNode,null);
this.domNodes.push(domNode);
// Stack of outstanding enter/leave events
this.currentlyEntered = [];
};
DroppableWidget.prototype.enterDrag = function(event) {
if(this.currentlyEntered.indexOf(event.target) === -1) {
this.currentlyEntered.push(event.target);
}
// If we're entering for the first time we need to apply highlighting
$tw.utils.addClass(this.domNodes[0],"tc-dragover");
};
DroppableWidget.prototype.leaveDrag = function(event) {
var pos = this.currentlyEntered.indexOf(event.target);
if(pos !== -1) {
this.currentlyEntered.splice(pos,1);
}
// Remove highlighting if we're leaving externally. The hacky second condition is to resolve a problem with Firefox whereby there is an erroneous dragenter event if the node being dragged is within the dropzone
if(this.currentlyEntered.length === 0 || (this.currentlyEntered.length === 1 && this.currentlyEntered[0] === $tw.dragInProgress)) {
this.currentlyEntered = [];
$tw.utils.removeClass(this.domNodes[0],"tc-dragover");
}
};
DroppableWidget.prototype.handleDragEnterEvent = function(event) {
this.enterDrag(event);
// Tell the browser that we're ready to handle the drop
event.preventDefault();
// Tell the browser not to ripple the drag up to any parent drop handlers
event.stopPropagation();
};
DroppableWidget.prototype.handleDragOverEvent = function(event) {
// Check for being over a TEXTAREA or INPUT
if(["TEXTAREA","INPUT"].indexOf(event.target.tagName) !== -1) {
return false;
}
// Tell the browser that we're still interested in the drop
event.preventDefault();
event.dataTransfer.dropEffect = "copy"; // Explicitly show this is a copy
};
DroppableWidget.prototype.handleDragLeaveEvent = function(event) {
this.leaveDrag(event);
};
DroppableWidget.prototype.handleDropEvent = function(event) {
var self = this;
this.leaveDrag(event);
// Check for being over a TEXTAREA or INPUT
if(["TEXTAREA","INPUT"].indexOf(event.target.tagName) !== -1) {
return false;
}
var dataTransfer = event.dataTransfer;
// Remove highlighting
$tw.utils.removeClass(this.domNodes[0],"tc-dragover");
// Try to import the various data types we understand
$tw.utils.importDataTransfer(dataTransfer,null,function(fieldsArray) {
fieldsArray.forEach(function(fields) {
if(fields.title) {
self.performActions(fields.title,event);
}
});
});
// Tell the browser that we handled the drop
event.preventDefault();
// Stop the drop ripple up to any parent handlers
event.stopPropagation();
};
DroppableWidget.prototype.performActions = function(title,event) {
if(this.dropzoneActions) {
this.invokeActionString(this.dropzoneActions,this,event,{actionTiddler: title});
}
};
/*
Compute the internal state of the widget
*/
DroppableWidget.prototype.execute = function() {
this.dropzoneActions = this.getAttribute("actions");
// Make child widgets
this.makeChildWidgets();
};
/*
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/
DroppableWidget.prototype.refresh = function(changedTiddlers) {
return this.refreshChildren(changedTiddlers);
};
exports.droppable = DroppableWidget;
})();