/** * Drop elements around * @module Ink.UI.Droppable_1 * @version 1 */ Ink.createModule("Ink.UI.Droppable","1",["Ink.Dom.Element_1", "Ink.Dom.Event_1", "Ink.Dom.Css_1", "Ink.UI.Common_1", "Ink.Util.Array_1", "Ink.Dom.Selector_1"], function( InkElement, InkEvent, Css, Common, InkArray, Selector) { 'use strict'; // Higher order functions var hAddClassName = function (element) { return function (className) {return Css.addClassName(element, className);}; }; var hRemoveClassName = function (element) { return function (className) {return Css.removeClassName(element, className);}; }; /** * @namespace Ink.UI.Droppable * @version 1 * @static */ var Droppable = { /** * Flag to activate debug mode * * @property debug * @type {Boolean} * @private */ debug: false, /** * Array with the data of each element (`{element: ..., data: ..., options: ...}`) * * @property _droppables * @type {Array} * @private */ _droppables: [], /** * Array of data for each draggable. (`{element: ..., data: ...}`) * * @property _draggables * @type {Array} * @private */ _draggables: [], /** * Makes an element droppable. * This method adds it to the stack of droppable elements. * Can consider it a constructor of droppable elements, but where no Droppable object is returned. * * The onHover, onDrop, and onDropOut options below can be: * * - 'move', 'copy': Move or copy the draggable element into this droppable. * - 'revert': Make the draggable go back to where it came from. * - A function (draggableElement, droppableElement), defining what you want to do in this case. * * @method add * @param {String|DOMElement} element Target element * @param {Object} [options] Options object * @param {String} [options.hoverClass] Classname(s) applied when an acceptable draggable element is hovering the element * @param {String} [options.accept] Selector for choosing draggables which can be dropped in this droppable. * @param {Function} [options.onHover] Called when an acceptable element is hovering the droppable (see above for string options). * @param {Function|String} [options.onDrop] Called when an acceptable element is dropped (see above for string options). * @param {Function|String} [options.onDropOut] Called when a droppable is dropped outside this droppable (see above for string options). * @public * * @sample Ink_UI_Droppable_1.html * */ add: function(element, options) { element = Common.elOrSelector(element, 'Droppable.add target element'); var opt = Ink.extendObj({ hoverClass: options.hoverclass /* old name */ || false, accept: false, onHover: false, onDrop: false, onDropOut: false }, options || {}, InkElement.data(element)); if (typeof opt.hoverClass === 'string') { opt.hoverClass = opt.hoverClass.split(/\s+/); } function cleanStyle(draggable) { draggable.style.position = 'inherit'; } var that = this; var namedEventHandlers = { move: function (draggable, droppable/*, event*/) { cleanStyle(draggable); droppable.appendChild(draggable); }, copy: function (draggable, droppable/*, event*/) { cleanStyle(draggable); droppable.appendChild(draggable.cloneNode(true)); }, revert: function (draggable/*, droppable, event*/) { that._findDraggable(draggable).originalParent.appendChild(draggable); cleanStyle(draggable); } }; var name; if (typeof opt.onHover === 'string') { name = opt.onHover; opt.onHover = namedEventHandlers[name]; if (opt.onHover === undefined) { throw new Error('Unknown hover event handler: ' + name); } } if (typeof opt.onDrop === 'string') { name = opt.onDrop; opt.onDrop = namedEventHandlers[name]; if (opt.onDrop === undefined) { throw new Error('Unknown drop event handler: ' + name); } } if (typeof opt.onDropOut === 'string') { name = opt.onDropOut; opt.onDropOut = namedEventHandlers[name]; if (opt.onDropOut === undefined) { throw new Error('Unknown dropOut event handler: ' + name); } } var elementData = { element: element, data: {}, options: opt }; this._droppables.push(elementData); this._update(elementData); }, /** * Finds droppable data about `element`. this data is added in `.add` * * @method _findData * @param {DOMElement} element Needle * @return {object} Droppable data of the element * @private */ _findData: function (element) { var elms = this._droppables; for (var i = 0, len = elms.length; i < len; i++) { if (elms[i].element === element) { return elms[i]; } } }, /** * Finds draggable data about `element` * * @method _findDraggable * @param {DOMElement} element Needle * @return {Object} Draggable data queried * @private */ _findDraggable: function (element) { var elms = this._draggables; for (var i = 0, len = elms.length; i < len; i++) { if (elms[i].element === element) { return elms[i]; } } }, /** * Invoke every time a drag starts * * @method updateAll * @private */ updateAll: function() { InkArray.each(this._droppables, Droppable._update); }, /** * Updates location and size of droppable element * * @method update * @param {String|DOMElement} element Target element * @public */ update: function(element) { this._update(this._findData(element)); }, _update: function(elementData) { var data = elementData.data; var element = elementData.element; data.left = InkElement.offsetLeft(element); data.top = InkElement.offsetTop( element); data.right = data.left + InkElement.elementWidth( element); data.bottom = data.top + InkElement.elementHeight(element); }, /** * Removes an element from the droppable stack and removes the droppable behavior * * @method remove * @param {String|DOMElement} elOrSelector Droppable element to disable. * @return {Boolean} Whether the object was found and deleted * @public */ remove: function(el) { el = Common.elOrSelector(el); var len = this._droppables.length; for (var i = 0; i < len; i++) { if (this._droppables[i].element === el) { this._droppables.splice(i, 1); break; } } return len !== this._droppables.length; }, /** * Executes an action on a droppable * * @method action * @param {Object} coords Coordinates where the action happened * @param {String} type Type of action. 'drag' or 'drop'. * @param {Object} ev Event object * @param {Object} draggable Draggable element * @private */ action: function(coords, type, ev, draggable) { // check all droppable elements InkArray.each(this._droppables, Ink.bind(function(elementData) { var data = elementData.data; var opt = elementData.options; var element = elementData.element; if (opt.accept && !Selector.matches(opt.accept, [draggable]).length) { return; } if (type === 'drag' && !this._findDraggable(draggable)) { this._draggables.push({ element: draggable, originalParent: draggable.parentNode }); } // check if our draggable is over our droppable if (coords.x >= data.left && coords.x <= data.right && coords.y >= data.top && coords.y <= data.bottom) { // INSIDE if (type === 'drag') { if (opt.hoverClass) { InkArray.each(opt.hoverClass, hAddClassName(element)); } if (opt.onHover) { opt.onHover(draggable, element); } } else if (type === 'drop') { if (opt.hoverClass) { InkArray.each(opt.hoverClass, hRemoveClassName(element)); } if (opt.onDrop) { opt.onDrop(draggable, element, ev); } } } else { // OUTSIDE if (type === 'drag' && opt.hoverClass) { InkArray.each(opt.hoverClass, hRemoveClassName(element)); } else if (type === 'drop') { if(opt.onDropOut){ opt.onDropOut(draggable, element, ev); } } } }, this)); } }; return Droppable; });