From 13a4b0e2b5e9fcc8180ee639b624a7b14a55e2a0 Mon Sep 17 00:00:00 2001 From: Piotr Nalepa Date: Sat, 6 May 2017 23:31:22 +0200 Subject: [PATCH] Dragster as CommonJS module, AMD module, ES6 module and global --- bower.json | 2 +- dragster-comment.js | 12 + dragster-script.js | 953 +++++++++++++++++++++++++ dragster.es6.js | 969 ++++++++++++++++++++++++++ dragster.js | 1621 ++++++++++++++++++++++--------------------- dragster.min.js | 6 +- dragster.min.js.gz | Bin 2918 -> 3007 bytes dragster.style.css | 4 +- index.html | 8 +- module-generator.js | 94 +++ package.json | 6 +- template.common.js | 18 + template.es6.js | 4 + 13 files changed, 2894 insertions(+), 803 deletions(-) create mode 100644 dragster-comment.js create mode 100644 dragster-script.js create mode 100644 dragster.es6.js create mode 100644 module-generator.js create mode 100644 template.common.js create mode 100644 template.es6.js diff --git a/bower.json b/bower.json index 7c5e2d8..5aefd2f 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "dragsterjs", - "version": "1.4.2", + "version": "1.5.0", "homepage": "https://github.com/sunpietro/dragster", "authors": [ "Piotr Nalepa " diff --git a/dragster-comment.js b/dragster-comment.js new file mode 100644 index 0000000..d6730e3 --- /dev/null +++ b/dragster-comment.js @@ -0,0 +1,12 @@ +/*@preserve + * Dragster - drag'n'drop library v1.5.0 + * https://github.com/sunpietro/dragster + * + * Copyright 2015-2017 Piotr Nalepa + * http://blog.piotrnalepa.pl + * + * Released under the MIT license + * https://github.com/sunpietro/dragster/blob/master/LICENSE + * + * Date: 2017-05-06T22:30Z + */ diff --git a/dragster-script.js b/dragster-script.js new file mode 100644 index 0000000..0fd152b --- /dev/null +++ b/dragster-script.js @@ -0,0 +1,953 @@ +var Dragster = function (params) { + var PREFIX_CLASS_DRAGSTER = 'dragster-', + CLASS_DRAGGING = 'is-dragging', + CLASS_DRAGOVER = 'is-drag-over', + CLASS_DRAGGABLE = PREFIX_CLASS_DRAGSTER + 'draggable', + CLASS_REGION = PREFIX_CLASS_DRAGSTER + 'drag-region', + CLASS_PLACEHOLDER = PREFIX_CLASS_DRAGSTER + 'drop-placeholder', + CLASS_TEMP_ELEMENT = PREFIX_CLASS_DRAGSTER + 'temp', + CLASS_TEMP_CONTAINER = CLASS_TEMP_ELEMENT + '-container', + CLASS_HIDDEN = PREFIX_CLASS_DRAGSTER + 'is-hidden', + CLASS_REPLACABLE = PREFIX_CLASS_DRAGSTER + 'replacable', + EVT_TOUCHSTART = 'touchstart', + EVT_TOUCHMOVE = 'touchmove', + EVT_TOUCHEND = 'touchend', + EVT_MOUSEDOWN = 'mousedown', + EVT_MOUSEMOVE = 'mousemove', + EVT_MOUSEUP = 'mouseup', + POS_TOP = 'top', + POS_BOTTOM = 'bottom', + UNIT = 'px', + DIV = 'div', + FALSE = false, + TRUE = true, + NULL = null, + dummyCallback = function () {}, + finalParams = { + elementSelector: '.dragster-block', + regionSelector: '.dragster-region', + dragHandleCssClass: FALSE, + dragOnlyRegionCssClass: PREFIX_CLASS_DRAGSTER + 'region--drag-only', + replaceElements: FALSE, + updateRegionsHeight: TRUE, + minimumRegionHeight: 60, + onBeforeDragStart: dummyCallback, + onAfterDragStart: dummyCallback, + onBeforeDragMove: dummyCallback, + onAfterDragMove: dummyCallback, + onBeforeDragEnd: dummyCallback, + onAfterDragEnd: dummyCallback, + onAfterDragDrop: dummyCallback, + scrollWindowOnDrag: FALSE, + dragOnlyRegionsEnabled: FALSE, + cloneElements: FALSE, + wrapDraggableElements: TRUE, + shadowElementUnderMouse: FALSE, + }, + visiblePlaceholder = { + top: FALSE, + bottom: FALSE, + }, + defaultDragsterEventInfo = { + drag: { + /** + * Contains drag node reference + * + * @property node + * @type {HTMLElement} + */ + node: NULL, + }, + drop: { + /** + * Contains drop node reference + * + * @property node + * @type {HTMLElement} + */ + node: NULL, + }, + shadow: { + /** + * Contains shadow element node reference + * + * @property node + * @type {HTMLElement} + */ + node: NULL, + /** + * Contains top position value of shadow element + * + * @property top + * @type {Number} + */ + top: 0, + /** + * Contains left position value of shadow element + * + * @property left + * @type {Number} + */ + left: 0 + }, + placeholder: { + /** + * Contains placeholder node reference + * + * @property node + * @type {HTMLElement} + */ + node: NULL, + /** + * Contains position type of placeholder + * + * @property position + * @type {String} + * @example 'top' or 'bottom' + */ + position: NULL, + }, + /** + * Reference to dropped element + * + * @property dropped + * @type {HTMLElement} + */ + dropped: NULL, + /** + * Reference to cloned element + * + * @property clonedFrom + * @type {HTMLElement} + */ + clonedFrom: NULL, + /** + * Reference to dropped cloned element + * + * @property clonedTo + * @type {HTMLElement} + */ + clonedTo: NULL, + }, + dragsterEventInfo = {}, + key, + regions, + getElement, + shadowElement, + shadowElementRegion, + tempContainer, + draggedElement, + draggableElements, + regionEventHandlers, + isPlaceholderCallback, + isDraggableCallback, + isInDragOnlyRegionCallback, + insertAfter, + insertBefore, + createElementWrapper, + createShadowElement, + createPlaceholder, + hideShadowElementTimeout, + removeElements, + cleanWorkspace, + cleanReplacables, + findDraggableElements, + findRegionElements, + wrapDraggableElements, + updateRegionsHeight, + scrollWindow, + discoverWindowHeight, + resetDragsterWorkspace, + dropActions, + moveActions, + shadowElementPositionXDiff, + shadowElementPositionYDiff, + addEventListenersToRegions, + windowHeight = window.innerHeight, + dragsterId = Math.floor((1 + Math.random()) * 0x10000).toString(16); + + // merge the object with default config with an object with params provided by a developer + for (key in params) { + if (params.hasOwnProperty(key)) { + finalParams[key] = params[key]; + } + } + + /* + * Find all draggable elements on the page + * + * @private + * @method findDraggableElements + * @return {Array} + */ + findDraggableElements = function () { + return [].slice.call(document.querySelectorAll(finalParams.elementSelector)); + }; + + /* + * Find all regions elements on the page + * + * @private + * @method findRegionElements + * @return {Array} + */ + findRegionElements = function () { + return [].slice.call(document.querySelectorAll(finalParams.regionSelector)); + }; + + /* + * Wrap all elements from the `elements` param with a draggable wrapper + * + * @private + * @method findDraggableElements + * @param elements {Array} + * @return {Array} + */ + wrapDraggableElements = function (elements) { + if (finalParams.wrapDraggableElements === FALSE) { + console.warn( + 'You have disabled the default behavior of wrapping the draggable elements. ' + + 'If you want Dragster.js to work properly you still will have to do this manually.\n' + + '\n' + + 'More info: https://github.com/sunpietro/dragster/blob/master/README.md#user-content-wrapdraggableelements---boolean' + ); + + return FALSE; + } + + elements.forEach(function (draggableElement) { + var wrapper = createElementWrapper(), + draggableParent = draggableElement.parentNode; + + if (draggableParent.classList.contains(CLASS_DRAGGABLE)) { + return FALSE; + } + + draggableParent.insertBefore(wrapper, draggableElement); + draggableParent.removeChild(draggableElement); + wrapper.appendChild(draggableElement); + }); + }; + + draggableElements = findDraggableElements(); + regions = findRegionElements(); + + if (finalParams.replaceElements) { + tempContainer = document.createElement(DIV); + + tempContainer.classList.add(CLASS_HIDDEN); + tempContainer.classList.add(CLASS_TEMP_CONTAINER); + + document.body.appendChild(tempContainer); + } + + /* + * Check whether a given element meets the requirements from the callback. + * The callback should always return Boolean value - true or false. + * The function allows to find a correct element within the DOM. + * If the element doesn't meet the requirements then the function tests its parent node. + * + * @private + * @method getElement + * @param element {HTMLElement} DOM element + * @param callback {Function} testing function + * @return {HTMLElement} + */ + getElement = function (element, callback) { + var parent = element.parentNode; + + if (!parent || + (element.classList && + element.classList.contains(CLASS_REGION) && + !element.classList.contains(finalParams.dragOnlyRegionCssClass)) + ) { return undefined; } + + if (callback(element)) { return element; } + + return callback(parent) ? parent : getElement(parent, callback); + }; + + /* + * Removes all elements defined by a selector from the DOM + * + * @private + * @method removeElements + * @param element {HTMLElement} DOM element + */ + removeElements = function (selector) { + var elements = [].slice.call(document.getElementsByClassName(selector)); + + elements.forEach(function (element) { + if (element.dataset.dragsterId !== dragsterId) { + return; + } + element.parentNode.removeChild(element); + }); + }; + + /* + * Removes all visible placeholders, shadow elements, empty draggable nodes + * and removes `mousemove` event listeners from regions + * + * @private + * @method cleanWorkspace + * @param element {HTMLElement} DOM element + * @param eventName {String} name of the event to stop listening to + */ + cleanWorkspace = function (element, eventName) { + if (eventName) { + regions.forEach(function (region) { + region.removeEventListener(eventName, regionEventHandlers.mousemove); + }); + + document.body.removeEventListener(eventName, regionEventHandlers.mousemove); + } + + if (element) { + element.classList.remove(CLASS_DRAGGING); + } + + // remove all empty draggable nodes + [].slice.call(document.getElementsByClassName(CLASS_DRAGGABLE)).forEach(function (dragEl) { + if (!dragEl.firstChild) { + dragEl.parentNode.removeChild(dragEl); + } + }); + + removeElements(CLASS_PLACEHOLDER); + removeElements(CLASS_TEMP_ELEMENT); + updateRegionsHeight(); + }; + + /* + * Removes replacable classname from all replacable elements + * + * @private + * @method cleanReplacables + */ + cleanReplacables = function () { + ([].slice.call(document.getElementsByClassName(CLASS_REPLACABLE))).forEach(function (elem) { + elem.classList.remove(CLASS_REPLACABLE); + }); + }; + + /* + * Creates a wrapper for a draggable element + * + * @private + * @method createElementWrapper + * @return {HTMLElement} DOM element + */ + createElementWrapper = function () { + var wrapper = document.createElement(DIV); + + wrapper.classList.add(CLASS_DRAGGABLE); + wrapper.dataset.dragsterId = dragsterId; + + return wrapper; + }; + + /* + * Creates a placeholder where dragged element can be dropped into + * + * @private + * @method createPlaceholder + * @return {HTMLElement} DOM element + */ + createPlaceholder = function () { + var placeholder = document.createElement(DIV); + + placeholder.classList.add(CLASS_PLACEHOLDER); + placeholder.dataset.dragsterId = dragsterId; + + return placeholder; + }; + + /* + * Creates a copy of dragged element that follows the cursor movement + * + * @private + * @method createShadowElement + * @return {HTMLElement} DOM element + */ + createShadowElement = function () { + var element = document.createElement(DIV); + + element.classList.add(CLASS_TEMP_ELEMENT); + element.classList.add(CLASS_HIDDEN); + + element.style.position = 'fixed'; + element.dataset.dragsterId = dragsterId; + + document.body.appendChild(element); + + return element; + }; + + /* + * Insert an element after a selected element + * + * @private + * @method insertAfter + * @param elementTarget {HTMLElement} dragged element + * @param elementAfter {HTMLElement} dragged element will be placed after this element + */ + insertAfter = function (elementTarget, elementAfter) { + if (elementTarget && elementTarget.parentNode) { + var refChild = finalParams.wrapDraggableElements === FALSE ? elementTarget : elementTarget.nextSibling; + + elementTarget.parentNode.insertBefore(elementAfter, refChild); + } + }; + + /* + * Insert an element before a selected element + * + * @private + * @method insertBefore + * @param elementTarget {HTMLElement} dragged element + * @param elementBefore {HTMLElement} dragged element will be placed before this element + */ + insertBefore = function (elementTarget, elementBefore) { + if (elementTarget && elementTarget.parentNode) { + elementTarget.parentNode.insertBefore(elementBefore, elementTarget); + } + }; + + /* + * Test whether an element is a draggable element + * + * @private + * @method isDraggableCallback + * @param element {HTMLElement} + * @return {Boolean} + */ + isDraggableCallback = function (element) { + return ( + (element.classList && element.classList.contains(CLASS_DRAGGABLE)) && + element.dataset.dragsterId === dragsterId + ); + }; + + /* + * Test whether an element is a placeholder where a user can drop a dragged element + * + * @private + * @method isPlaceholderCallback + * @param element {HTMLElement} + * @return {Boolean} + */ + isPlaceholderCallback = function (element) { return (element.classList && element.classList.contains(CLASS_PLACEHOLDER)); }; + + /* + * Test whether an element belongs to drag only region + * + * @private + * @method isInDragOnlyRegionCallback + * @param element {HTMLElement} + * @return {Boolean} + */ + isInDragOnlyRegionCallback = function (element) { return (element.classList && element.classList.contains(finalParams.dragOnlyRegionCssClass)); }; //jshint ignore:line + + /* + * Update the height of the regions dynamically + * + * @private + * @method isPlaceholderCallback + * @param element {HTMLElement} + * @return {Boolean} + */ + updateRegionsHeight = function () { + if (finalParams.updateRegionsHeight) { + var regions = [].slice.call(document.getElementsByClassName(CLASS_REGION)); + + regions.forEach(function (region) { + var elements = [].slice.call(region.querySelectorAll(finalParams.elementSelector)), + regionHeight = finalParams.minimumRegionHeight; + + if (!elements.length) { + return; + } + + elements.forEach(function (element) { + var styles = window.getComputedStyle(element); + + regionHeight += element.offsetHeight + parseInt(styles.marginTop, 10) + parseInt(styles.marginBottom, 10); + }); + + region.style.height = regionHeight + UNIT; + }); + } + }; + + /** + * Resets DragsterJS workspace by removing mouseup/touchend event listeners + * + * @method resetDragsterWorkspace + * @private + * @param moveEvent {String} move event name (either mousemove or touchmove) + * @param upEvent {String} up event name (either mouseup or touchend) + */ + resetDragsterWorkspace = function (moveEvent, upEvent) { + cleanWorkspace(draggedElement, moveEvent); + cleanWorkspace(draggedElement, upEvent); + }; + + regionEventHandlers = { + /* + * `mousedown` or `touchstart` event handler. + * When user starts dragging an element the function adds a listener to either `mousemove` or `touchmove` + * events. Creates a shadow element that follows a movement of the cursor. + * + * @private + * @method regionEventHandlers.mousedown + * @param event {Object} event object + */ + mousedown: function (event) { + if (finalParams.dragHandleCssClass && + (typeof finalParams.dragHandleCssClass !== 'string' || + !event.target.classList.contains(finalParams.dragHandleCssClass))) { + return FALSE; + } + + var targetRegion, + moveEvent, + upEvent, + isTouch = event.type === EVT_TOUCHSTART, + eventObject = event.changedTouches ? event.changedTouches[0] : event; + + dragsterEventInfo = JSON.parse(JSON.stringify(defaultDragsterEventInfo)); + event.dragster = dragsterEventInfo; + + if (finalParams.onBeforeDragStart(event) === FALSE || event.which === 3 /* detect right click */) { + return FALSE; + } + + event.preventDefault(); + + draggedElement = getElement(event.target, isDraggableCallback); + + if (!draggedElement) { + return FALSE; + } + + moveEvent = isTouch ? EVT_TOUCHMOVE : EVT_MOUSEMOVE; + upEvent = isTouch ? EVT_TOUCHEND : EVT_MOUSEUP; + + regions.forEach(function (region) { + region.addEventListener(moveEvent, regionEventHandlers.mousemove, FALSE); + region.addEventListener(upEvent, regionEventHandlers.mouseup, FALSE); + }); + + document.body.addEventListener(moveEvent, regionEventHandlers.mousemove, FALSE); + document.body.addEventListener(upEvent, regionEventHandlers.mouseup, FALSE); + + targetRegion = draggedElement.getBoundingClientRect(); + + shadowElementPositionXDiff = targetRegion.left - eventObject.clientX; + shadowElementPositionYDiff = targetRegion.top - eventObject.clientY; + + shadowElement = createShadowElement(); + shadowElement.innerHTML = draggedElement.innerHTML; + shadowElement.style.width = targetRegion.width + UNIT; + shadowElement.style.height = targetRegion.height + UNIT; + shadowElement.dataset.dragsterId = dragsterId; + shadowElementRegion = shadowElement.getBoundingClientRect(); + + draggedElement.classList.add(CLASS_DRAGGING); + + dragsterEventInfo.drag.node = draggedElement; + dragsterEventInfo.shadow.node = shadowElement; + + event.dragster = dragsterEventInfo; + + finalParams.onAfterDragStart(event); + }, + /* + * `mousemove` or `touchmove` event handler. + * When user is moving an element the function checks whether the element is above any other draggable element. + * In case when it is above any draggable element, the function adds a temporary placeholder before or after the given element, + * so a user is able to drop a dragged element onto the placeholder. + * In case when in a region there's no draggable element it just adds a placeholder to the region. + * Updates a position of shadow element following the cursor. + * + * @private + * @method regionEventHandlers.mousemove + * @param event {Object} event object + */ + mousemove: function (event) { + event.dragster = dragsterEventInfo; + + if (finalParams.onBeforeDragMove(event) === FALSE || !shadowElementRegion) { + return FALSE; + } + + event.preventDefault(); + + var eventObject = event.changedTouches ? event.changedTouches[0] : event, + pageXOffset = eventObject.view ? eventObject.view.pageXOffset : 0, + pageYOffset = eventObject.view ? eventObject.view.pageYOffset : 0, + elementPositionY = eventObject.clientY + pageYOffset, + elementPositionX = eventObject.clientX + pageXOffset, + unknownTarget = document.elementFromPoint(eventObject.clientX, eventObject.clientY), + dropTarget = getElement(unknownTarget, isDraggableCallback), + top = finalParams.shadowElementUnderMouse ? eventObject.clientY + shadowElementPositionYDiff : eventObject.clientY, + left = finalParams.shadowElementUnderMouse ? + elementPositionX + shadowElementPositionXDiff : + elementPositionX - (shadowElementRegion.width / 2), + isDragNodeAvailable = dragsterEventInfo.drag.node && dragsterEventInfo.drag.node.dataset, + isInDragOnlyRegion = !!(dropTarget && getElement(dropTarget, isInDragOnlyRegionCallback)), + isAllowedTarget = unknownTarget.dataset.dragsterId === dragsterId, + isTargetRegion = unknownTarget.classList.contains(CLASS_REGION) && isAllowedTarget, + isTargetRegionDragOnly = unknownTarget.classList.contains(finalParams.dragOnlyRegionCssClass) && isAllowedTarget, + isTargetPlaceholder = unknownTarget.classList.contains(CLASS_PLACEHOLDER), + hasTargetDraggaBleElements = unknownTarget.getElementsByClassName(CLASS_DRAGGABLE).length > 0, + hasTargetPlaceholders = unknownTarget.getElementsByClassName(CLASS_PLACEHOLDER).length > 0; + + clearTimeout(hideShadowElementTimeout); + + shadowElement.style.top = top + UNIT; + shadowElement.style.left = left + UNIT; + shadowElement.classList.remove(CLASS_HIDDEN); + + dragsterEventInfo.shadow.top = top; + dragsterEventInfo.shadow.left = left; + + if (!isDragNodeAvailable && !isTargetRegion && !isTargetPlaceholder) { + moveActions.removePlaceholders(); + } else if (dropTarget && dropTarget !== draggedElement && !isInDragOnlyRegion) { + moveActions.removePlaceholders(); + moveActions.addPlaceholderOnTarget(dropTarget, elementPositionY, pageYOffset); + } else if (isTargetRegion && !isTargetRegionDragOnly && !hasTargetDraggaBleElements && !hasTargetPlaceholders) { + moveActions.removePlaceholders(); + moveActions.addPlaceholderInRegion(unknownTarget); + } else if (isTargetRegion && !isTargetRegionDragOnly && hasTargetDraggaBleElements && !hasTargetPlaceholders) { + moveActions.removePlaceholders(); + moveActions.addPlaceholderInRegionBelowTargets(unknownTarget); + } + + if (finalParams.scrollWindowOnDrag) { + scrollWindow(event); + } + + updateRegionsHeight(); + finalParams.onAfterDragMove(event); + }, + /* + * `mouseup` or `touchend` event handler. + * When user is dropping an element, the function checks whether the element is above any other draggable element. + * In case when it is above any draggable element, the function places the dragged element before of after the element below. + * Removes a listener to either `mousemove` or `touchmove` event. + * Removes placeholders. + * Removes a shadow element. + * + * @private + * @method regionEventHandlers.mouseup + * @param event {Object} event object + */ + mouseup: function (event) { + event.dragster = dragsterEventInfo; + + var isTouch = event.type === EVT_TOUCHSTART, + moveEvent = isTouch ? EVT_TOUCHMOVE : EVT_MOUSEMOVE, + upEvent = isTouch ? EVT_TOUCHEND : EVT_MOUSEUP, + findByClass, + dropTarget, + dropDraggableTarget, + isFromDragOnlyRegion, + canBeCloned; + + if (finalParams.onBeforeDragEnd(event) === FALSE) { + resetDragsterWorkspace(moveEvent, upEvent); + + return FALSE; + } + + findByClass = finalParams.replaceElements ? CLASS_REPLACABLE : CLASS_PLACEHOLDER; + dropTarget = document.getElementsByClassName(findByClass)[0]; + isFromDragOnlyRegion = !!(draggedElement && getElement(draggedElement, isInDragOnlyRegionCallback)), + canBeCloned = finalParams.cloneElements && isFromDragOnlyRegion; + + hideShadowElementTimeout = setTimeout(resetDragsterWorkspace, 200); + + cleanReplacables(); + + if (!draggedElement || !dropTarget) { + resetDragsterWorkspace(moveEvent, upEvent); + + return FALSE; + } + + dropDraggableTarget = getElement(dropTarget, isDraggableCallback); + dropDraggableTarget = dropDraggableTarget || dropTarget; + + if (draggedElement !== dropDraggableTarget) { + if (!finalParams.replaceElements && !canBeCloned) { + event.dragster = dropActions.moveElement(event.dragster, dropTarget, dropDraggableTarget); + + finalParams.onAfterDragDrop(event); + } else if (finalParams.replaceElements && !canBeCloned) { + event.dragster = dropActions.replaceElements(event.dragster, dropDraggableTarget); + + finalParams.onAfterDragDrop(event); + } else if (!finalParams.replaceElements && canBeCloned) { + event.dragster = dropActions.cloneElements(event.dragster, dropTarget, dropDraggableTarget); + + finalParams.onAfterDragDrop(event); + } + + dropDraggableTarget.classList.remove(CLASS_DRAGOVER); + } + + resetDragsterWorkspace(moveEvent, upEvent); + + finalParams.onAfterDragEnd(event); + } + }; + + moveActions = { + /** + * Adds a new placeholder in relation to drop target + * + * @method moveActions.addPlaceholderOnTarget + * @private + * @param dropTarget {HTMLElement} a drop target element + * @param elementPositionY {Number} position Y of dragged element + * @param pageYOffset {Number} position of the scroll bar + */ + addPlaceholderOnTarget: function (dropTarget, elementPositionY, pageYOffset) { + var dropTargetRegion = dropTarget.getBoundingClientRect(), + placeholder = createPlaceholder(), + maxDistance = dropTargetRegion.height / 2; + + cleanReplacables(); + + if (!finalParams.replaceElements) { + if ((elementPositionY - pageYOffset - dropTargetRegion.top) < maxDistance && !visiblePlaceholder.top) { + removeElements(CLASS_PLACEHOLDER); + placeholder.dataset.placeholderPosition = POS_TOP; + insertBefore(dropTarget.firstChild, placeholder); + + dragsterEventInfo.placeholder.position = POS_TOP; + } else if ((dropTargetRegion.bottom - (elementPositionY - pageYOffset)) < maxDistance && !visiblePlaceholder.bottom) { + removeElements(CLASS_PLACEHOLDER); + placeholder.dataset.placeholderPosition = POS_BOTTOM; + dropTarget.appendChild(placeholder); + + dragsterEventInfo.placeholder.position = POS_BOTTOM; + } + } else { + dropTarget.classList.add(CLASS_REPLACABLE); + } + + dragsterEventInfo.placeholder.node = placeholder; + dragsterEventInfo.drop.node = dropTarget; + }, + + /** + * Adds a new placeholder in an empty region + * + * @method moveActions.addPlaceholderInRegion + * @private + * @param regionTarget {HTMLElement} a region drop target + */ + addPlaceholderInRegion: function (regionTarget) { + var placeholder = createPlaceholder(); + + regionTarget.appendChild(placeholder); + + dragsterEventInfo.placeholder.position = POS_BOTTOM; + dragsterEventInfo.placeholder.node = placeholder; + dragsterEventInfo.drop.node = regionTarget; + }, + + /** + * Adds a new placeholder in an empty region + * + * @method moveActions.addPlaceholderInRegion + * @private + * @param regionTarget {HTMLElement} a region drop target + */ + addPlaceholderInRegionBelowTargets: function (regionTarget) { + var elementsInRegion = [].slice.call(regionTarget.getElementsByClassName(CLASS_DRAGGABLE)), + filteredElements = elementsInRegion.filter(function (elementInRegion) { + return elementInRegion.dataset.dragsterId === dragsterId; + }), + dropTarget = filteredElements[filteredElements.length - 1], + placeholder = createPlaceholder(); + + placeholder.dataset.placeholderPosition = POS_BOTTOM; + removeElements(CLASS_PLACEHOLDER); + dropTarget.appendChild(placeholder); + + dragsterEventInfo.placeholder.position = POS_BOTTOM; + dragsterEventInfo.placeholder.node = placeholder; + dragsterEventInfo.drop.node = dropTarget; + }, + + /** + * Removes all placeholders from regions + * + * @method moveActions.removePlaceholders + * @private + */ + removePlaceholders: function () { + if (!finalParams.replaceElements) { + removeElements(CLASS_PLACEHOLDER); + } else { + cleanReplacables(); + } + }, + }; + + dropActions = { + /** + * Moves element to the final position on drop + * + * @method dropActions.moveElement + * @private + * @param dragsterEvent {Object} dragster properties from event + * @param dropTarget {HTMLElement} region where dragged element will be placed after drop + * @param dropDraggableTarget {HTMLElement} final destination of dragged element + * @return {Object} updated event info + */ + moveElement: function (dragsterEvent, dropTarget, dropDraggableTarget) { + var dropTemp = finalParams.wrapDraggableElements === FALSE ? draggedElement : createElementWrapper(), + placeholderPosition = dropTarget.dataset.placeholderPosition; + + if (placeholderPosition === POS_TOP) { + insertBefore(dropDraggableTarget, dropTemp); + } else { + if (finalParams.wrapDraggableElements === FALSE) { + insertAfter(dropTemp, dropDraggableTarget); + } else { + insertAfter(dropDraggableTarget, dropTemp); + } + } + + if (draggedElement.firstChild && finalParams.wrapDraggableElements === TRUE) { + dropTemp.appendChild(draggedElement.firstChild); + } + + dragsterEvent.dropped = dropTemp; + + return dragsterEvent; + }, + + /** + * Replaces element with target element on drop + * + * @method dropActions.replaceElements + * @private + * @param dragsterEvent {Object} dragster properties from event + * @param dropDraggableTarget {HTMLElement} final destination of dragged element + * @return {Object} updated event info + */ + replaceElements: function (dragsterEvent, dropDraggableTarget) { + var dropTemp = document.getElementsByClassName(CLASS_TEMP_CONTAINER)[0]; + + dropTemp.innerHTML = draggedElement.innerHTML; + + draggedElement.innerHTML = dropDraggableTarget.innerHTML; + dropDraggableTarget.innerHTML = dropTemp.innerHTML; + dropTemp.innerHTML = ''; + dragsterEvent.dropped = dropTemp; + + return dragsterEvent; + }, + + /** + * Clones element to the final position on drop + * + * @method dropActions.cloneElements + * @private + * @param dragsterEvent {Object} dragster properties from event + * @param dropTarget {HTMLElement} region where dragged element will be placed after drop + * @param dropDraggableTarget {HTMLElement} final destination of dragged element + * @return {Object} updated event info + */ + cloneElements: function (dragsterEvent, dropTarget, dropDraggableTarget) { + var dropTemp = draggedElement.cloneNode(true), + placeholderPosition = dropTarget.dataset.placeholderPosition; + + if (placeholderPosition === POS_TOP) { + insertBefore(dropDraggableTarget, dropTemp); + } else { + insertAfter(dropDraggableTarget, dropTemp); + } + + cleanWorkspace(dropTemp); + + dragsterEvent.clonedFrom = draggedElement; + dragsterEvent.clonedTo = dropTemp; + + return dragsterEvent; + }, + }; + + /** + * Scrolls window while dragging an element + * + * @method scrollWindow + * @private + * @param event {Object} event object + */ + scrollWindow = function (event) { + var eventObject = event.changedTouches ? event.changedTouches[0] : event, + diffSize = 60; + + if (windowHeight - eventObject.clientY < diffSize) { + window.scrollBy(0, 10); + } else if (eventObject.clientY < diffSize) { + window.scrollBy(0, -10); + } + }; + + /** + * Discovers window height + * + * @method discoverWindowHeight + * @private + */ + discoverWindowHeight = function () { + windowHeight = window.innerHeight; + }; + + wrapDraggableElements(draggableElements); + + /** + * Adds event listeners to the regions + * + * @method addEventListenersToRegions + * @private + */ + addEventListenersToRegions = function () { + // add `mousedown`/`touchstart` and `mouseup`/`touchend` event listeners to regions + regions.forEach(function (region) { + region.classList.add(CLASS_REGION); + region.dataset.dragsterId = dragsterId; + + region.addEventListener(EVT_MOUSEDOWN, regionEventHandlers.mousedown, FALSE); + region.addEventListener(EVT_TOUCHSTART, regionEventHandlers.mousedown, FALSE); + }); + }; + + addEventListenersToRegions(); + + window.addEventListener('resize', discoverWindowHeight, false); + + return { + update: function () { + draggableElements = findDraggableElements(); + + wrapDraggableElements(draggableElements); + updateRegionsHeight(); + discoverWindowHeight(); + }, + updateRegions: function () { + regions = findRegionElements(); + + addEventListenersToRegions(); + } + }; +}; diff --git a/dragster.es6.js b/dragster.es6.js new file mode 100644 index 0000000..37ea20f --- /dev/null +++ b/dragster.es6.js @@ -0,0 +1,969 @@ +/*@preserve + * Dragster - drag'n'drop library v1.5.0 + * https://github.com/sunpietro/dragster + * + * Copyright 2015-2017 Piotr Nalepa + * http://blog.piotrnalepa.pl + * + * Released under the MIT license + * https://github.com/sunpietro/dragster/blob/master/LICENSE + * + * Date: 2017-05-06T22:30Z + */ + +var Dragster = function (params) { + var PREFIX_CLASS_DRAGSTER = 'dragster-', + CLASS_DRAGGING = 'is-dragging', + CLASS_DRAGOVER = 'is-drag-over', + CLASS_DRAGGABLE = PREFIX_CLASS_DRAGSTER + 'draggable', + CLASS_REGION = PREFIX_CLASS_DRAGSTER + 'drag-region', + CLASS_PLACEHOLDER = PREFIX_CLASS_DRAGSTER + 'drop-placeholder', + CLASS_TEMP_ELEMENT = PREFIX_CLASS_DRAGSTER + 'temp', + CLASS_TEMP_CONTAINER = CLASS_TEMP_ELEMENT + '-container', + CLASS_HIDDEN = PREFIX_CLASS_DRAGSTER + 'is-hidden', + CLASS_REPLACABLE = PREFIX_CLASS_DRAGSTER + 'replacable', + EVT_TOUCHSTART = 'touchstart', + EVT_TOUCHMOVE = 'touchmove', + EVT_TOUCHEND = 'touchend', + EVT_MOUSEDOWN = 'mousedown', + EVT_MOUSEMOVE = 'mousemove', + EVT_MOUSEUP = 'mouseup', + POS_TOP = 'top', + POS_BOTTOM = 'bottom', + UNIT = 'px', + DIV = 'div', + FALSE = false, + TRUE = true, + NULL = null, + dummyCallback = function () {}, + finalParams = { + elementSelector: '.dragster-block', + regionSelector: '.dragster-region', + dragHandleCssClass: FALSE, + dragOnlyRegionCssClass: PREFIX_CLASS_DRAGSTER + 'region--drag-only', + replaceElements: FALSE, + updateRegionsHeight: TRUE, + minimumRegionHeight: 60, + onBeforeDragStart: dummyCallback, + onAfterDragStart: dummyCallback, + onBeforeDragMove: dummyCallback, + onAfterDragMove: dummyCallback, + onBeforeDragEnd: dummyCallback, + onAfterDragEnd: dummyCallback, + onAfterDragDrop: dummyCallback, + scrollWindowOnDrag: FALSE, + dragOnlyRegionsEnabled: FALSE, + cloneElements: FALSE, + wrapDraggableElements: TRUE, + shadowElementUnderMouse: FALSE, + }, + visiblePlaceholder = { + top: FALSE, + bottom: FALSE, + }, + defaultDragsterEventInfo = { + drag: { + /** + * Contains drag node reference + * + * @property node + * @type {HTMLElement} + */ + node: NULL, + }, + drop: { + /** + * Contains drop node reference + * + * @property node + * @type {HTMLElement} + */ + node: NULL, + }, + shadow: { + /** + * Contains shadow element node reference + * + * @property node + * @type {HTMLElement} + */ + node: NULL, + /** + * Contains top position value of shadow element + * + * @property top + * @type {Number} + */ + top: 0, + /** + * Contains left position value of shadow element + * + * @property left + * @type {Number} + */ + left: 0 + }, + placeholder: { + /** + * Contains placeholder node reference + * + * @property node + * @type {HTMLElement} + */ + node: NULL, + /** + * Contains position type of placeholder + * + * @property position + * @type {String} + * @example 'top' or 'bottom' + */ + position: NULL, + }, + /** + * Reference to dropped element + * + * @property dropped + * @type {HTMLElement} + */ + dropped: NULL, + /** + * Reference to cloned element + * + * @property clonedFrom + * @type {HTMLElement} + */ + clonedFrom: NULL, + /** + * Reference to dropped cloned element + * + * @property clonedTo + * @type {HTMLElement} + */ + clonedTo: NULL, + }, + dragsterEventInfo = {}, + key, + regions, + getElement, + shadowElement, + shadowElementRegion, + tempContainer, + draggedElement, + draggableElements, + regionEventHandlers, + isPlaceholderCallback, + isDraggableCallback, + isInDragOnlyRegionCallback, + insertAfter, + insertBefore, + createElementWrapper, + createShadowElement, + createPlaceholder, + hideShadowElementTimeout, + removeElements, + cleanWorkspace, + cleanReplacables, + findDraggableElements, + findRegionElements, + wrapDraggableElements, + updateRegionsHeight, + scrollWindow, + discoverWindowHeight, + resetDragsterWorkspace, + dropActions, + moveActions, + shadowElementPositionXDiff, + shadowElementPositionYDiff, + addEventListenersToRegions, + windowHeight = window.innerHeight, + dragsterId = Math.floor((1 + Math.random()) * 0x10000).toString(16); + + // merge the object with default config with an object with params provided by a developer + for (key in params) { + if (params.hasOwnProperty(key)) { + finalParams[key] = params[key]; + } + } + + /* + * Find all draggable elements on the page + * + * @private + * @method findDraggableElements + * @return {Array} + */ + findDraggableElements = function () { + return [].slice.call(document.querySelectorAll(finalParams.elementSelector)); + }; + + /* + * Find all regions elements on the page + * + * @private + * @method findRegionElements + * @return {Array} + */ + findRegionElements = function () { + return [].slice.call(document.querySelectorAll(finalParams.regionSelector)); + }; + + /* + * Wrap all elements from the `elements` param with a draggable wrapper + * + * @private + * @method findDraggableElements + * @param elements {Array} + * @return {Array} + */ + wrapDraggableElements = function (elements) { + if (finalParams.wrapDraggableElements === FALSE) { + console.warn( + 'You have disabled the default behavior of wrapping the draggable elements. ' + + 'If you want Dragster.js to work properly you still will have to do this manually.\n' + + '\n' + + 'More info: https://github.com/sunpietro/dragster/blob/master/README.md#user-content-wrapdraggableelements---boolean' + ); + + return FALSE; + } + + elements.forEach(function (draggableElement) { + var wrapper = createElementWrapper(), + draggableParent = draggableElement.parentNode; + + if (draggableParent.classList.contains(CLASS_DRAGGABLE)) { + return FALSE; + } + + draggableParent.insertBefore(wrapper, draggableElement); + draggableParent.removeChild(draggableElement); + wrapper.appendChild(draggableElement); + }); + }; + + draggableElements = findDraggableElements(); + regions = findRegionElements(); + + if (finalParams.replaceElements) { + tempContainer = document.createElement(DIV); + + tempContainer.classList.add(CLASS_HIDDEN); + tempContainer.classList.add(CLASS_TEMP_CONTAINER); + + document.body.appendChild(tempContainer); + } + + /* + * Check whether a given element meets the requirements from the callback. + * The callback should always return Boolean value - true or false. + * The function allows to find a correct element within the DOM. + * If the element doesn't meet the requirements then the function tests its parent node. + * + * @private + * @method getElement + * @param element {HTMLElement} DOM element + * @param callback {Function} testing function + * @return {HTMLElement} + */ + getElement = function (element, callback) { + var parent = element.parentNode; + + if (!parent || + (element.classList && + element.classList.contains(CLASS_REGION) && + !element.classList.contains(finalParams.dragOnlyRegionCssClass)) + ) { return undefined; } + + if (callback(element)) { return element; } + + return callback(parent) ? parent : getElement(parent, callback); + }; + + /* + * Removes all elements defined by a selector from the DOM + * + * @private + * @method removeElements + * @param element {HTMLElement} DOM element + */ + removeElements = function (selector) { + var elements = [].slice.call(document.getElementsByClassName(selector)); + + elements.forEach(function (element) { + if (element.dataset.dragsterId !== dragsterId) { + return; + } + element.parentNode.removeChild(element); + }); + }; + + /* + * Removes all visible placeholders, shadow elements, empty draggable nodes + * and removes `mousemove` event listeners from regions + * + * @private + * @method cleanWorkspace + * @param element {HTMLElement} DOM element + * @param eventName {String} name of the event to stop listening to + */ + cleanWorkspace = function (element, eventName) { + if (eventName) { + regions.forEach(function (region) { + region.removeEventListener(eventName, regionEventHandlers.mousemove); + }); + + document.body.removeEventListener(eventName, regionEventHandlers.mousemove); + } + + if (element) { + element.classList.remove(CLASS_DRAGGING); + } + + // remove all empty draggable nodes + [].slice.call(document.getElementsByClassName(CLASS_DRAGGABLE)).forEach(function (dragEl) { + if (!dragEl.firstChild) { + dragEl.parentNode.removeChild(dragEl); + } + }); + + removeElements(CLASS_PLACEHOLDER); + removeElements(CLASS_TEMP_ELEMENT); + updateRegionsHeight(); + }; + + /* + * Removes replacable classname from all replacable elements + * + * @private + * @method cleanReplacables + */ + cleanReplacables = function () { + ([].slice.call(document.getElementsByClassName(CLASS_REPLACABLE))).forEach(function (elem) { + elem.classList.remove(CLASS_REPLACABLE); + }); + }; + + /* + * Creates a wrapper for a draggable element + * + * @private + * @method createElementWrapper + * @return {HTMLElement} DOM element + */ + createElementWrapper = function () { + var wrapper = document.createElement(DIV); + + wrapper.classList.add(CLASS_DRAGGABLE); + wrapper.dataset.dragsterId = dragsterId; + + return wrapper; + }; + + /* + * Creates a placeholder where dragged element can be dropped into + * + * @private + * @method createPlaceholder + * @return {HTMLElement} DOM element + */ + createPlaceholder = function () { + var placeholder = document.createElement(DIV); + + placeholder.classList.add(CLASS_PLACEHOLDER); + placeholder.dataset.dragsterId = dragsterId; + + return placeholder; + }; + + /* + * Creates a copy of dragged element that follows the cursor movement + * + * @private + * @method createShadowElement + * @return {HTMLElement} DOM element + */ + createShadowElement = function () { + var element = document.createElement(DIV); + + element.classList.add(CLASS_TEMP_ELEMENT); + element.classList.add(CLASS_HIDDEN); + + element.style.position = 'fixed'; + element.dataset.dragsterId = dragsterId; + + document.body.appendChild(element); + + return element; + }; + + /* + * Insert an element after a selected element + * + * @private + * @method insertAfter + * @param elementTarget {HTMLElement} dragged element + * @param elementAfter {HTMLElement} dragged element will be placed after this element + */ + insertAfter = function (elementTarget, elementAfter) { + if (elementTarget && elementTarget.parentNode) { + var refChild = finalParams.wrapDraggableElements === FALSE ? elementTarget : elementTarget.nextSibling; + + elementTarget.parentNode.insertBefore(elementAfter, refChild); + } + }; + + /* + * Insert an element before a selected element + * + * @private + * @method insertBefore + * @param elementTarget {HTMLElement} dragged element + * @param elementBefore {HTMLElement} dragged element will be placed before this element + */ + insertBefore = function (elementTarget, elementBefore) { + if (elementTarget && elementTarget.parentNode) { + elementTarget.parentNode.insertBefore(elementBefore, elementTarget); + } + }; + + /* + * Test whether an element is a draggable element + * + * @private + * @method isDraggableCallback + * @param element {HTMLElement} + * @return {Boolean} + */ + isDraggableCallback = function (element) { + return ( + (element.classList && element.classList.contains(CLASS_DRAGGABLE)) && + element.dataset.dragsterId === dragsterId + ); + }; + + /* + * Test whether an element is a placeholder where a user can drop a dragged element + * + * @private + * @method isPlaceholderCallback + * @param element {HTMLElement} + * @return {Boolean} + */ + isPlaceholderCallback = function (element) { return (element.classList && element.classList.contains(CLASS_PLACEHOLDER)); }; + + /* + * Test whether an element belongs to drag only region + * + * @private + * @method isInDragOnlyRegionCallback + * @param element {HTMLElement} + * @return {Boolean} + */ + isInDragOnlyRegionCallback = function (element) { return (element.classList && element.classList.contains(finalParams.dragOnlyRegionCssClass)); }; //jshint ignore:line + + /* + * Update the height of the regions dynamically + * + * @private + * @method isPlaceholderCallback + * @param element {HTMLElement} + * @return {Boolean} + */ + updateRegionsHeight = function () { + if (finalParams.updateRegionsHeight) { + var regions = [].slice.call(document.getElementsByClassName(CLASS_REGION)); + + regions.forEach(function (region) { + var elements = [].slice.call(region.querySelectorAll(finalParams.elementSelector)), + regionHeight = finalParams.minimumRegionHeight; + + if (!elements.length) { + return; + } + + elements.forEach(function (element) { + var styles = window.getComputedStyle(element); + + regionHeight += element.offsetHeight + parseInt(styles.marginTop, 10) + parseInt(styles.marginBottom, 10); + }); + + region.style.height = regionHeight + UNIT; + }); + } + }; + + /** + * Resets DragsterJS workspace by removing mouseup/touchend event listeners + * + * @method resetDragsterWorkspace + * @private + * @param moveEvent {String} move event name (either mousemove or touchmove) + * @param upEvent {String} up event name (either mouseup or touchend) + */ + resetDragsterWorkspace = function (moveEvent, upEvent) { + cleanWorkspace(draggedElement, moveEvent); + cleanWorkspace(draggedElement, upEvent); + }; + + regionEventHandlers = { + /* + * `mousedown` or `touchstart` event handler. + * When user starts dragging an element the function adds a listener to either `mousemove` or `touchmove` + * events. Creates a shadow element that follows a movement of the cursor. + * + * @private + * @method regionEventHandlers.mousedown + * @param event {Object} event object + */ + mousedown: function (event) { + if (finalParams.dragHandleCssClass && + (typeof finalParams.dragHandleCssClass !== 'string' || + !event.target.classList.contains(finalParams.dragHandleCssClass))) { + return FALSE; + } + + var targetRegion, + moveEvent, + upEvent, + isTouch = event.type === EVT_TOUCHSTART, + eventObject = event.changedTouches ? event.changedTouches[0] : event; + + dragsterEventInfo = JSON.parse(JSON.stringify(defaultDragsterEventInfo)); + event.dragster = dragsterEventInfo; + + if (finalParams.onBeforeDragStart(event) === FALSE || event.which === 3 /* detect right click */) { + return FALSE; + } + + event.preventDefault(); + + draggedElement = getElement(event.target, isDraggableCallback); + + if (!draggedElement) { + return FALSE; + } + + moveEvent = isTouch ? EVT_TOUCHMOVE : EVT_MOUSEMOVE; + upEvent = isTouch ? EVT_TOUCHEND : EVT_MOUSEUP; + + regions.forEach(function (region) { + region.addEventListener(moveEvent, regionEventHandlers.mousemove, FALSE); + region.addEventListener(upEvent, regionEventHandlers.mouseup, FALSE); + }); + + document.body.addEventListener(moveEvent, regionEventHandlers.mousemove, FALSE); + document.body.addEventListener(upEvent, regionEventHandlers.mouseup, FALSE); + + targetRegion = draggedElement.getBoundingClientRect(); + + shadowElementPositionXDiff = targetRegion.left - eventObject.clientX; + shadowElementPositionYDiff = targetRegion.top - eventObject.clientY; + + shadowElement = createShadowElement(); + shadowElement.innerHTML = draggedElement.innerHTML; + shadowElement.style.width = targetRegion.width + UNIT; + shadowElement.style.height = targetRegion.height + UNIT; + shadowElement.dataset.dragsterId = dragsterId; + shadowElementRegion = shadowElement.getBoundingClientRect(); + + draggedElement.classList.add(CLASS_DRAGGING); + + dragsterEventInfo.drag.node = draggedElement; + dragsterEventInfo.shadow.node = shadowElement; + + event.dragster = dragsterEventInfo; + + finalParams.onAfterDragStart(event); + }, + /* + * `mousemove` or `touchmove` event handler. + * When user is moving an element the function checks whether the element is above any other draggable element. + * In case when it is above any draggable element, the function adds a temporary placeholder before or after the given element, + * so a user is able to drop a dragged element onto the placeholder. + * In case when in a region there's no draggable element it just adds a placeholder to the region. + * Updates a position of shadow element following the cursor. + * + * @private + * @method regionEventHandlers.mousemove + * @param event {Object} event object + */ + mousemove: function (event) { + event.dragster = dragsterEventInfo; + + if (finalParams.onBeforeDragMove(event) === FALSE || !shadowElementRegion) { + return FALSE; + } + + event.preventDefault(); + + var eventObject = event.changedTouches ? event.changedTouches[0] : event, + pageXOffset = eventObject.view ? eventObject.view.pageXOffset : 0, + pageYOffset = eventObject.view ? eventObject.view.pageYOffset : 0, + elementPositionY = eventObject.clientY + pageYOffset, + elementPositionX = eventObject.clientX + pageXOffset, + unknownTarget = document.elementFromPoint(eventObject.clientX, eventObject.clientY), + dropTarget = getElement(unknownTarget, isDraggableCallback), + top = finalParams.shadowElementUnderMouse ? eventObject.clientY + shadowElementPositionYDiff : eventObject.clientY, + left = finalParams.shadowElementUnderMouse ? + elementPositionX + shadowElementPositionXDiff : + elementPositionX - (shadowElementRegion.width / 2), + isDragNodeAvailable = dragsterEventInfo.drag.node && dragsterEventInfo.drag.node.dataset, + isInDragOnlyRegion = !!(dropTarget && getElement(dropTarget, isInDragOnlyRegionCallback)), + isAllowedTarget = unknownTarget.dataset.dragsterId === dragsterId, + isTargetRegion = unknownTarget.classList.contains(CLASS_REGION) && isAllowedTarget, + isTargetRegionDragOnly = unknownTarget.classList.contains(finalParams.dragOnlyRegionCssClass) && isAllowedTarget, + isTargetPlaceholder = unknownTarget.classList.contains(CLASS_PLACEHOLDER), + hasTargetDraggaBleElements = unknownTarget.getElementsByClassName(CLASS_DRAGGABLE).length > 0, + hasTargetPlaceholders = unknownTarget.getElementsByClassName(CLASS_PLACEHOLDER).length > 0; + + clearTimeout(hideShadowElementTimeout); + + shadowElement.style.top = top + UNIT; + shadowElement.style.left = left + UNIT; + shadowElement.classList.remove(CLASS_HIDDEN); + + dragsterEventInfo.shadow.top = top; + dragsterEventInfo.shadow.left = left; + + if (!isDragNodeAvailable && !isTargetRegion && !isTargetPlaceholder) { + moveActions.removePlaceholders(); + } else if (dropTarget && dropTarget !== draggedElement && !isInDragOnlyRegion) { + moveActions.removePlaceholders(); + moveActions.addPlaceholderOnTarget(dropTarget, elementPositionY, pageYOffset); + } else if (isTargetRegion && !isTargetRegionDragOnly && !hasTargetDraggaBleElements && !hasTargetPlaceholders) { + moveActions.removePlaceholders(); + moveActions.addPlaceholderInRegion(unknownTarget); + } else if (isTargetRegion && !isTargetRegionDragOnly && hasTargetDraggaBleElements && !hasTargetPlaceholders) { + moveActions.removePlaceholders(); + moveActions.addPlaceholderInRegionBelowTargets(unknownTarget); + } + + if (finalParams.scrollWindowOnDrag) { + scrollWindow(event); + } + + updateRegionsHeight(); + finalParams.onAfterDragMove(event); + }, + /* + * `mouseup` or `touchend` event handler. + * When user is dropping an element, the function checks whether the element is above any other draggable element. + * In case when it is above any draggable element, the function places the dragged element before of after the element below. + * Removes a listener to either `mousemove` or `touchmove` event. + * Removes placeholders. + * Removes a shadow element. + * + * @private + * @method regionEventHandlers.mouseup + * @param event {Object} event object + */ + mouseup: function (event) { + event.dragster = dragsterEventInfo; + + var isTouch = event.type === EVT_TOUCHSTART, + moveEvent = isTouch ? EVT_TOUCHMOVE : EVT_MOUSEMOVE, + upEvent = isTouch ? EVT_TOUCHEND : EVT_MOUSEUP, + findByClass, + dropTarget, + dropDraggableTarget, + isFromDragOnlyRegion, + canBeCloned; + + if (finalParams.onBeforeDragEnd(event) === FALSE) { + resetDragsterWorkspace(moveEvent, upEvent); + + return FALSE; + } + + findByClass = finalParams.replaceElements ? CLASS_REPLACABLE : CLASS_PLACEHOLDER; + dropTarget = document.getElementsByClassName(findByClass)[0]; + isFromDragOnlyRegion = !!(draggedElement && getElement(draggedElement, isInDragOnlyRegionCallback)), + canBeCloned = finalParams.cloneElements && isFromDragOnlyRegion; + + hideShadowElementTimeout = setTimeout(resetDragsterWorkspace, 200); + + cleanReplacables(); + + if (!draggedElement || !dropTarget) { + resetDragsterWorkspace(moveEvent, upEvent); + + return FALSE; + } + + dropDraggableTarget = getElement(dropTarget, isDraggableCallback); + dropDraggableTarget = dropDraggableTarget || dropTarget; + + if (draggedElement !== dropDraggableTarget) { + if (!finalParams.replaceElements && !canBeCloned) { + event.dragster = dropActions.moveElement(event.dragster, dropTarget, dropDraggableTarget); + + finalParams.onAfterDragDrop(event); + } else if (finalParams.replaceElements && !canBeCloned) { + event.dragster = dropActions.replaceElements(event.dragster, dropDraggableTarget); + + finalParams.onAfterDragDrop(event); + } else if (!finalParams.replaceElements && canBeCloned) { + event.dragster = dropActions.cloneElements(event.dragster, dropTarget, dropDraggableTarget); + + finalParams.onAfterDragDrop(event); + } + + dropDraggableTarget.classList.remove(CLASS_DRAGOVER); + } + + resetDragsterWorkspace(moveEvent, upEvent); + + finalParams.onAfterDragEnd(event); + } + }; + + moveActions = { + /** + * Adds a new placeholder in relation to drop target + * + * @method moveActions.addPlaceholderOnTarget + * @private + * @param dropTarget {HTMLElement} a drop target element + * @param elementPositionY {Number} position Y of dragged element + * @param pageYOffset {Number} position of the scroll bar + */ + addPlaceholderOnTarget: function (dropTarget, elementPositionY, pageYOffset) { + var dropTargetRegion = dropTarget.getBoundingClientRect(), + placeholder = createPlaceholder(), + maxDistance = dropTargetRegion.height / 2; + + cleanReplacables(); + + if (!finalParams.replaceElements) { + if ((elementPositionY - pageYOffset - dropTargetRegion.top) < maxDistance && !visiblePlaceholder.top) { + removeElements(CLASS_PLACEHOLDER); + placeholder.dataset.placeholderPosition = POS_TOP; + insertBefore(dropTarget.firstChild, placeholder); + + dragsterEventInfo.placeholder.position = POS_TOP; + } else if ((dropTargetRegion.bottom - (elementPositionY - pageYOffset)) < maxDistance && !visiblePlaceholder.bottom) { + removeElements(CLASS_PLACEHOLDER); + placeholder.dataset.placeholderPosition = POS_BOTTOM; + dropTarget.appendChild(placeholder); + + dragsterEventInfo.placeholder.position = POS_BOTTOM; + } + } else { + dropTarget.classList.add(CLASS_REPLACABLE); + } + + dragsterEventInfo.placeholder.node = placeholder; + dragsterEventInfo.drop.node = dropTarget; + }, + + /** + * Adds a new placeholder in an empty region + * + * @method moveActions.addPlaceholderInRegion + * @private + * @param regionTarget {HTMLElement} a region drop target + */ + addPlaceholderInRegion: function (regionTarget) { + var placeholder = createPlaceholder(); + + regionTarget.appendChild(placeholder); + + dragsterEventInfo.placeholder.position = POS_BOTTOM; + dragsterEventInfo.placeholder.node = placeholder; + dragsterEventInfo.drop.node = regionTarget; + }, + + /** + * Adds a new placeholder in an empty region + * + * @method moveActions.addPlaceholderInRegion + * @private + * @param regionTarget {HTMLElement} a region drop target + */ + addPlaceholderInRegionBelowTargets: function (regionTarget) { + var elementsInRegion = [].slice.call(regionTarget.getElementsByClassName(CLASS_DRAGGABLE)), + filteredElements = elementsInRegion.filter(function (elementInRegion) { + return elementInRegion.dataset.dragsterId === dragsterId; + }), + dropTarget = filteredElements[filteredElements.length - 1], + placeholder = createPlaceholder(); + + placeholder.dataset.placeholderPosition = POS_BOTTOM; + removeElements(CLASS_PLACEHOLDER); + dropTarget.appendChild(placeholder); + + dragsterEventInfo.placeholder.position = POS_BOTTOM; + dragsterEventInfo.placeholder.node = placeholder; + dragsterEventInfo.drop.node = dropTarget; + }, + + /** + * Removes all placeholders from regions + * + * @method moveActions.removePlaceholders + * @private + */ + removePlaceholders: function () { + if (!finalParams.replaceElements) { + removeElements(CLASS_PLACEHOLDER); + } else { + cleanReplacables(); + } + }, + }; + + dropActions = { + /** + * Moves element to the final position on drop + * + * @method dropActions.moveElement + * @private + * @param dragsterEvent {Object} dragster properties from event + * @param dropTarget {HTMLElement} region where dragged element will be placed after drop + * @param dropDraggableTarget {HTMLElement} final destination of dragged element + * @return {Object} updated event info + */ + moveElement: function (dragsterEvent, dropTarget, dropDraggableTarget) { + var dropTemp = finalParams.wrapDraggableElements === FALSE ? draggedElement : createElementWrapper(), + placeholderPosition = dropTarget.dataset.placeholderPosition; + + if (placeholderPosition === POS_TOP) { + insertBefore(dropDraggableTarget, dropTemp); + } else { + if (finalParams.wrapDraggableElements === FALSE) { + insertAfter(dropTemp, dropDraggableTarget); + } else { + insertAfter(dropDraggableTarget, dropTemp); + } + } + + if (draggedElement.firstChild && finalParams.wrapDraggableElements === TRUE) { + dropTemp.appendChild(draggedElement.firstChild); + } + + dragsterEvent.dropped = dropTemp; + + return dragsterEvent; + }, + + /** + * Replaces element with target element on drop + * + * @method dropActions.replaceElements + * @private + * @param dragsterEvent {Object} dragster properties from event + * @param dropDraggableTarget {HTMLElement} final destination of dragged element + * @return {Object} updated event info + */ + replaceElements: function (dragsterEvent, dropDraggableTarget) { + var dropTemp = document.getElementsByClassName(CLASS_TEMP_CONTAINER)[0]; + + dropTemp.innerHTML = draggedElement.innerHTML; + + draggedElement.innerHTML = dropDraggableTarget.innerHTML; + dropDraggableTarget.innerHTML = dropTemp.innerHTML; + dropTemp.innerHTML = ''; + dragsterEvent.dropped = dropTemp; + + return dragsterEvent; + }, + + /** + * Clones element to the final position on drop + * + * @method dropActions.cloneElements + * @private + * @param dragsterEvent {Object} dragster properties from event + * @param dropTarget {HTMLElement} region where dragged element will be placed after drop + * @param dropDraggableTarget {HTMLElement} final destination of dragged element + * @return {Object} updated event info + */ + cloneElements: function (dragsterEvent, dropTarget, dropDraggableTarget) { + var dropTemp = draggedElement.cloneNode(true), + placeholderPosition = dropTarget.dataset.placeholderPosition; + + if (placeholderPosition === POS_TOP) { + insertBefore(dropDraggableTarget, dropTemp); + } else { + insertAfter(dropDraggableTarget, dropTemp); + } + + cleanWorkspace(dropTemp); + + dragsterEvent.clonedFrom = draggedElement; + dragsterEvent.clonedTo = dropTemp; + + return dragsterEvent; + }, + }; + + /** + * Scrolls window while dragging an element + * + * @method scrollWindow + * @private + * @param event {Object} event object + */ + scrollWindow = function (event) { + var eventObject = event.changedTouches ? event.changedTouches[0] : event, + diffSize = 60; + + if (windowHeight - eventObject.clientY < diffSize) { + window.scrollBy(0, 10); + } else if (eventObject.clientY < diffSize) { + window.scrollBy(0, -10); + } + }; + + /** + * Discovers window height + * + * @method discoverWindowHeight + * @private + */ + discoverWindowHeight = function () { + windowHeight = window.innerHeight; + }; + + wrapDraggableElements(draggableElements); + + /** + * Adds event listeners to the regions + * + * @method addEventListenersToRegions + * @private + */ + addEventListenersToRegions = function () { + // add `mousedown`/`touchstart` and `mouseup`/`touchend` event listeners to regions + regions.forEach(function (region) { + region.classList.add(CLASS_REGION); + region.dataset.dragsterId = dragsterId; + + region.addEventListener(EVT_MOUSEDOWN, regionEventHandlers.mousedown, FALSE); + region.addEventListener(EVT_TOUCHSTART, regionEventHandlers.mousedown, FALSE); + }); + }; + + addEventListenersToRegions(); + + window.addEventListener('resize', discoverWindowHeight, false); + + return { + update: function () { + draggableElements = findDraggableElements(); + + wrapDraggableElements(draggableElements); + updateRegionsHeight(); + discoverWindowHeight(); + }, + updateRegions: function () { + regions = findRegionElements(); + + addEventListenersToRegions(); + } + }; +}; + + +export default Dragster; diff --git a/dragster.js b/dragster.js index ca62590..6414b93 100644 --- a/dragster.js +++ b/dragster.js @@ -1,5 +1,5 @@ /*@preserve - * Dragster - drag'n'drop library v1.4.2 + * Dragster - drag'n'drop library v1.5.0 * https://github.com/sunpietro/dragster * * Copyright 2015-2017 Piotr Nalepa @@ -8,915 +8,946 @@ * Released under the MIT license * https://github.com/sunpietro/dragster/blob/master/LICENSE * - * Date: 2017-03-30T16:30Z + * Date: 2017-05-06T22:30Z */ -(function (window, document) { + +;(function (root, moduleName, factory) { 'use strict'; - window.Dragster = function (params) { - var PREFIX_CLASS_DRAGSTER = 'dragster-', - CLASS_DRAGGING = 'is-dragging', - CLASS_DRAGOVER = 'is-drag-over', - CLASS_DRAGGABLE = PREFIX_CLASS_DRAGSTER + 'draggable', - CLASS_REGION = PREFIX_CLASS_DRAGSTER + 'drag-region', - CLASS_PLACEHOLDER = PREFIX_CLASS_DRAGSTER + 'drop-placeholder', - CLASS_TEMP_ELEMENT = PREFIX_CLASS_DRAGSTER + 'temp', - CLASS_TEMP_CONTAINER = CLASS_TEMP_ELEMENT + '-container', - CLASS_HIDDEN = PREFIX_CLASS_DRAGSTER + 'is-hidden', - CLASS_REPLACABLE = PREFIX_CLASS_DRAGSTER + 'replacable', - EVT_TOUCHSTART = 'touchstart', - EVT_TOUCHMOVE = 'touchmove', - EVT_TOUCHEND = 'touchend', - EVT_MOUSEDOWN = 'mousedown', - EVT_MOUSEMOVE = 'mousemove', - EVT_MOUSEUP = 'mouseup', - POS_TOP = 'top', - POS_BOTTOM = 'bottom', - UNIT = 'px', - DIV = 'div', - FALSE = false, - TRUE = true, - NULL = null, - dummyCallback = function () {}, - finalParams = { - elementSelector: '.dragster-block', - regionSelector: '.dragster-region', - dragHandleCssClass: FALSE, - dragOnlyRegionCssClass: PREFIX_CLASS_DRAGSTER + 'region--drag-only', - replaceElements: FALSE, - updateRegionsHeight: TRUE, - minimumRegionHeight: 60, - onBeforeDragStart: dummyCallback, - onAfterDragStart: dummyCallback, - onBeforeDragMove: dummyCallback, - onAfterDragMove: dummyCallback, - onBeforeDragEnd: dummyCallback, - onAfterDragEnd: dummyCallback, - onAfterDragDrop: dummyCallback, - scrollWindowOnDrag: FALSE, - dragOnlyRegionsEnabled: FALSE, - cloneElements: FALSE, - wrapDraggableElements: TRUE, - shadowElementUnderMouse: FALSE, - }, - visiblePlaceholder = { - top: FALSE, - bottom: FALSE, + if (typeof define === 'function' && define.amd) { + define(moduleName, factory); + } else if (typeof exports === 'object') { + exports = module.exports = factory(); + } else { + root[moduleName] = factory(); + } +})(this, 'Dragster', function () { + 'use strict'; + +var Dragster = function (params) { + var PREFIX_CLASS_DRAGSTER = 'dragster-', + CLASS_DRAGGING = 'is-dragging', + CLASS_DRAGOVER = 'is-drag-over', + CLASS_DRAGGABLE = PREFIX_CLASS_DRAGSTER + 'draggable', + CLASS_REGION = PREFIX_CLASS_DRAGSTER + 'drag-region', + CLASS_PLACEHOLDER = PREFIX_CLASS_DRAGSTER + 'drop-placeholder', + CLASS_TEMP_ELEMENT = PREFIX_CLASS_DRAGSTER + 'temp', + CLASS_TEMP_CONTAINER = CLASS_TEMP_ELEMENT + '-container', + CLASS_HIDDEN = PREFIX_CLASS_DRAGSTER + 'is-hidden', + CLASS_REPLACABLE = PREFIX_CLASS_DRAGSTER + 'replacable', + EVT_TOUCHSTART = 'touchstart', + EVT_TOUCHMOVE = 'touchmove', + EVT_TOUCHEND = 'touchend', + EVT_MOUSEDOWN = 'mousedown', + EVT_MOUSEMOVE = 'mousemove', + EVT_MOUSEUP = 'mouseup', + POS_TOP = 'top', + POS_BOTTOM = 'bottom', + UNIT = 'px', + DIV = 'div', + FALSE = false, + TRUE = true, + NULL = null, + dummyCallback = function () {}, + finalParams = { + elementSelector: '.dragster-block', + regionSelector: '.dragster-region', + dragHandleCssClass: FALSE, + dragOnlyRegionCssClass: PREFIX_CLASS_DRAGSTER + 'region--drag-only', + replaceElements: FALSE, + updateRegionsHeight: TRUE, + minimumRegionHeight: 60, + onBeforeDragStart: dummyCallback, + onAfterDragStart: dummyCallback, + onBeforeDragMove: dummyCallback, + onAfterDragMove: dummyCallback, + onBeforeDragEnd: dummyCallback, + onAfterDragEnd: dummyCallback, + onAfterDragDrop: dummyCallback, + scrollWindowOnDrag: FALSE, + dragOnlyRegionsEnabled: FALSE, + cloneElements: FALSE, + wrapDraggableElements: TRUE, + shadowElementUnderMouse: FALSE, + }, + visiblePlaceholder = { + top: FALSE, + bottom: FALSE, + }, + defaultDragsterEventInfo = { + drag: { + /** + * Contains drag node reference + * + * @property node + * @type {HTMLElement} + */ + node: NULL, }, - defaultDragsterEventInfo = { - drag: { - /** - * Contains drag node reference - * - * @property node - * @type {HTMLElement} - */ - node: NULL, - }, - drop: { - /** - * Contains drop node reference - * - * @property node - * @type {HTMLElement} - */ - node: NULL, - }, - shadow: { - /** - * Contains shadow element node reference - * - * @property node - * @type {HTMLElement} - */ - node: NULL, - /** - * Contains top position value of shadow element - * - * @property top - * @type {Number} - */ - top: 0, - /** - * Contains left position value of shadow element - * - * @property left - * @type {Number} - */ - left: 0 - }, - placeholder: { - /** - * Contains placeholder node reference - * - * @property node - * @type {HTMLElement} - */ - node: NULL, - /** - * Contains position type of placeholder - * - * @property position - * @type {String} - * @example 'top' or 'bottom' - */ - position: NULL, - }, + drop: { /** - * Reference to dropped element + * Contains drop node reference * - * @property dropped + * @property node * @type {HTMLElement} */ - dropped: NULL, + node: NULL, + }, + shadow: { /** - * Reference to cloned element + * Contains shadow element node reference * - * @property clonedFrom + * @property node * @type {HTMLElement} */ - clonedFrom: NULL, + node: NULL, /** - * Reference to dropped cloned element + * Contains top position value of shadow element * - * @property clonedTo + * @property top + * @type {Number} + */ + top: 0, + /** + * Contains left position value of shadow element + * + * @property left + * @type {Number} + */ + left: 0 + }, + placeholder: { + /** + * Contains placeholder node reference + * + * @property node * @type {HTMLElement} */ - clonedTo: NULL, + node: NULL, + /** + * Contains position type of placeholder + * + * @property position + * @type {String} + * @example 'top' or 'bottom' + */ + position: NULL, }, - dragsterEventInfo = {}, - key, - regions, - getElement, - shadowElement, - shadowElementRegion, - tempContainer, - draggedElement, - draggableElements, - regionEventHandlers, - isPlaceholderCallback, - isDraggableCallback, - isInDragOnlyRegionCallback, - insertAfter, - insertBefore, - createElementWrapper, - createShadowElement, - createPlaceholder, - hideShadowElementTimeout, - removeElements, - cleanWorkspace, - cleanReplacables, - findDraggableElements, - wrapDraggableElements, - updateRegionsHeight, - scrollWindow, - discoverWindowHeight, - resetDragsterWorkspace, - dropActions, - moveActions, - shadowElementPositionXDiff, - shadowElementPositionYDiff, - windowHeight = window.innerHeight, - dragsterId = Math.floor((1 + Math.random()) * 0x10000).toString(16); - - // merge the object with default config with an object with params provided by a developer - for (key in params) { - if (params.hasOwnProperty(key)) { - finalParams[key] = params[key]; - } + /** + * Reference to dropped element + * + * @property dropped + * @type {HTMLElement} + */ + dropped: NULL, + /** + * Reference to cloned element + * + * @property clonedFrom + * @type {HTMLElement} + */ + clonedFrom: NULL, + /** + * Reference to dropped cloned element + * + * @property clonedTo + * @type {HTMLElement} + */ + clonedTo: NULL, + }, + dragsterEventInfo = {}, + key, + regions, + getElement, + shadowElement, + shadowElementRegion, + tempContainer, + draggedElement, + draggableElements, + regionEventHandlers, + isPlaceholderCallback, + isDraggableCallback, + isInDragOnlyRegionCallback, + insertAfter, + insertBefore, + createElementWrapper, + createShadowElement, + createPlaceholder, + hideShadowElementTimeout, + removeElements, + cleanWorkspace, + cleanReplacables, + findDraggableElements, + findRegionElements, + wrapDraggableElements, + updateRegionsHeight, + scrollWindow, + discoverWindowHeight, + resetDragsterWorkspace, + dropActions, + moveActions, + shadowElementPositionXDiff, + shadowElementPositionYDiff, + addEventListenersToRegions, + windowHeight = window.innerHeight, + dragsterId = Math.floor((1 + Math.random()) * 0x10000).toString(16); + + // merge the object with default config with an object with params provided by a developer + for (key in params) { + if (params.hasOwnProperty(key)) { + finalParams[key] = params[key]; } + } + + /* + * Find all draggable elements on the page + * + * @private + * @method findDraggableElements + * @return {Array} + */ + findDraggableElements = function () { + return [].slice.call(document.querySelectorAll(finalParams.elementSelector)); + }; - /* - * Find all draggable elements on the page - * - * @private - * @method findDraggableElements - * @return {Array} - */ - findDraggableElements = function () { - return [].slice.call(document.querySelectorAll(finalParams.elementSelector)); - }; - - /* - * Wrap all elements from the `elements` param with a draggable wrapper - * - * @private - * @method findDraggableElements - * @param elements {Array} - * @return {Array} - */ - wrapDraggableElements = function (elements) { - if (finalParams.wrapDraggableElements === FALSE) { - console.warn( - 'You have disabled the default behavior of wrapping the draggable elements. ' + - 'If you want Dragster.js to work properly you still will have to do this manually.\n' + - '\n' + - 'More info: https://github.com/sunpietro/dragster/blob/master/README.md#user-content-wrapdraggableelements---boolean' - ); - - return FALSE; - } - - elements.forEach(function (draggableElement) { - var wrapper = createElementWrapper(), - draggableParent = draggableElement.parentNode; - - if (draggableParent.classList.contains(CLASS_DRAGGABLE)) { - return FALSE; - } - - draggableParent.insertBefore(wrapper, draggableElement); - draggableParent.removeChild(draggableElement); - wrapper.appendChild(draggableElement); - }); - }; - - draggableElements = findDraggableElements(); - regions = [].slice.call(document.querySelectorAll(finalParams.regionSelector)); - - if (finalParams.replaceElements) { - tempContainer = document.createElement(DIV); + /* + * Find all regions elements on the page + * + * @private + * @method findRegionElements + * @return {Array} + */ + findRegionElements = function () { + return [].slice.call(document.querySelectorAll(finalParams.regionSelector)); + }; - tempContainer.classList.add(CLASS_HIDDEN); - tempContainer.classList.add(CLASS_TEMP_CONTAINER); + /* + * Wrap all elements from the `elements` param with a draggable wrapper + * + * @private + * @method findDraggableElements + * @param elements {Array} + * @return {Array} + */ + wrapDraggableElements = function (elements) { + if (finalParams.wrapDraggableElements === FALSE) { + console.warn( + 'You have disabled the default behavior of wrapping the draggable elements. ' + + 'If you want Dragster.js to work properly you still will have to do this manually.\n' + + '\n' + + 'More info: https://github.com/sunpietro/dragster/blob/master/README.md#user-content-wrapdraggableelements---boolean' + ); - document.body.appendChild(tempContainer); + return FALSE; } - /* - * Check whether a given element meets the requirements from the callback. - * The callback should always return Boolean value - true or false. - * The function allows to find a correct element within the DOM. - * If the element doesn't meet the requirements then the function tests its parent node. - * - * @private - * @method getElement - * @param element {HTMLElement} DOM element - * @param callback {Function} testing function - * @return {HTMLElement} - */ - getElement = function (element, callback) { - var parent = element.parentNode; + elements.forEach(function (draggableElement) { + var wrapper = createElementWrapper(), + draggableParent = draggableElement.parentNode; - if (!parent || - (element.classList && - element.classList.contains(CLASS_REGION) && - !element.classList.contains(finalParams.dragOnlyRegionCssClass)) - ) { return undefined; } + if (draggableParent.classList.contains(CLASS_DRAGGABLE)) { + return FALSE; + } - if (callback(element)) { return element; } + draggableParent.insertBefore(wrapper, draggableElement); + draggableParent.removeChild(draggableElement); + wrapper.appendChild(draggableElement); + }); + }; - return callback(parent) ? parent : getElement(parent, callback); - }; + draggableElements = findDraggableElements(); + regions = findRegionElements(); + + if (finalParams.replaceElements) { + tempContainer = document.createElement(DIV); + + tempContainer.classList.add(CLASS_HIDDEN); + tempContainer.classList.add(CLASS_TEMP_CONTAINER); + + document.body.appendChild(tempContainer); + } + + /* + * Check whether a given element meets the requirements from the callback. + * The callback should always return Boolean value - true or false. + * The function allows to find a correct element within the DOM. + * If the element doesn't meet the requirements then the function tests its parent node. + * + * @private + * @method getElement + * @param element {HTMLElement} DOM element + * @param callback {Function} testing function + * @return {HTMLElement} + */ + getElement = function (element, callback) { + var parent = element.parentNode; + + if (!parent || + (element.classList && + element.classList.contains(CLASS_REGION) && + !element.classList.contains(finalParams.dragOnlyRegionCssClass)) + ) { return undefined; } + + if (callback(element)) { return element; } + + return callback(parent) ? parent : getElement(parent, callback); + }; - /* - * Removes all elements defined by a selector from the DOM - * - * @private - * @method removeElements - * @param element {HTMLElement} DOM element - */ - removeElements = function (selector) { - var elements = [].slice.call(document.getElementsByClassName(selector)); + /* + * Removes all elements defined by a selector from the DOM + * + * @private + * @method removeElements + * @param element {HTMLElement} DOM element + */ + removeElements = function (selector) { + var elements = [].slice.call(document.getElementsByClassName(selector)); + + elements.forEach(function (element) { + if (element.dataset.dragsterId !== dragsterId) { + return; + } + element.parentNode.removeChild(element); + }); + }; - elements.forEach(function (element) { - if (element.dataset.dragsterId !== dragsterId) { - return; - } - element.parentNode.removeChild(element); + /* + * Removes all visible placeholders, shadow elements, empty draggable nodes + * and removes `mousemove` event listeners from regions + * + * @private + * @method cleanWorkspace + * @param element {HTMLElement} DOM element + * @param eventName {String} name of the event to stop listening to + */ + cleanWorkspace = function (element, eventName) { + if (eventName) { + regions.forEach(function (region) { + region.removeEventListener(eventName, regionEventHandlers.mousemove); }); - }; - /* - * Removes all visible placeholders, shadow elements, empty draggable nodes - * and removes `mousemove` event listeners from regions - * - * @private - * @method cleanWorkspace - * @param element {HTMLElement} DOM element - * @param eventName {String} name of the event to stop listening to - */ - cleanWorkspace = function (element, eventName) { - if (eventName) { - regions.forEach(function (region) { - region.removeEventListener(eventName, regionEventHandlers.mousemove); - }); + document.body.removeEventListener(eventName, regionEventHandlers.mousemove); + } - document.body.removeEventListener(eventName, regionEventHandlers.mousemove); - } + if (element) { + element.classList.remove(CLASS_DRAGGING); + } - if (element) { - element.classList.remove(CLASS_DRAGGING); + // remove all empty draggable nodes + [].slice.call(document.getElementsByClassName(CLASS_DRAGGABLE)).forEach(function (dragEl) { + if (!dragEl.firstChild) { + dragEl.parentNode.removeChild(dragEl); } + }); - // remove all empty draggable nodes - [].slice.call(document.getElementsByClassName(CLASS_DRAGGABLE)).forEach(function (dragEl) { - if (!dragEl.firstChild) { - dragEl.parentNode.removeChild(dragEl); - } - }); + removeElements(CLASS_PLACEHOLDER); + removeElements(CLASS_TEMP_ELEMENT); + updateRegionsHeight(); + }; - removeElements(CLASS_PLACEHOLDER); - removeElements(CLASS_TEMP_ELEMENT); - updateRegionsHeight(); - }; + /* + * Removes replacable classname from all replacable elements + * + * @private + * @method cleanReplacables + */ + cleanReplacables = function () { + ([].slice.call(document.getElementsByClassName(CLASS_REPLACABLE))).forEach(function (elem) { + elem.classList.remove(CLASS_REPLACABLE); + }); + }; - /* - * Removes replacable classname from all replacable elements - * - * @private - * @method cleanReplacables - */ - cleanReplacables = function () { - ([].slice.call(document.getElementsByClassName(CLASS_REPLACABLE))).forEach(function (elem) { - elem.classList.remove(CLASS_REPLACABLE); - }); - }; + /* + * Creates a wrapper for a draggable element + * + * @private + * @method createElementWrapper + * @return {HTMLElement} DOM element + */ + createElementWrapper = function () { + var wrapper = document.createElement(DIV); - /* - * Creates a wrapper for a draggable element - * - * @private - * @method createElementWrapper - * @return {HTMLElement} DOM element - */ - createElementWrapper = function () { - var wrapper = document.createElement(DIV); + wrapper.classList.add(CLASS_DRAGGABLE); + wrapper.dataset.dragsterId = dragsterId; - wrapper.classList.add(CLASS_DRAGGABLE); - wrapper.dataset.dragsterId = dragsterId; + return wrapper; + }; - return wrapper; - }; + /* + * Creates a placeholder where dragged element can be dropped into + * + * @private + * @method createPlaceholder + * @return {HTMLElement} DOM element + */ + createPlaceholder = function () { + var placeholder = document.createElement(DIV); - /* - * Creates a placeholder where dragged element can be dropped into - * - * @private - * @method createPlaceholder - * @return {HTMLElement} DOM element - */ - createPlaceholder = function () { - var placeholder = document.createElement(DIV); + placeholder.classList.add(CLASS_PLACEHOLDER); + placeholder.dataset.dragsterId = dragsterId; - placeholder.classList.add(CLASS_PLACEHOLDER); - placeholder.dataset.dragsterId = dragsterId; + return placeholder; + }; - return placeholder; - }; + /* + * Creates a copy of dragged element that follows the cursor movement + * + * @private + * @method createShadowElement + * @return {HTMLElement} DOM element + */ + createShadowElement = function () { + var element = document.createElement(DIV); - /* - * Creates a copy of dragged element that follows the cursor movement - * - * @private - * @method createShadowElement - * @return {HTMLElement} DOM element - */ - createShadowElement = function () { - var element = document.createElement(DIV); + element.classList.add(CLASS_TEMP_ELEMENT); + element.classList.add(CLASS_HIDDEN); - element.classList.add(CLASS_TEMP_ELEMENT); - element.classList.add(CLASS_HIDDEN); + element.style.position = 'fixed'; + element.dataset.dragsterId = dragsterId; - element.style.position = 'fixed'; - element.dataset.dragsterId = dragsterId; + document.body.appendChild(element); - document.body.appendChild(element); + return element; + }; - return element; - }; + /* + * Insert an element after a selected element + * + * @private + * @method insertAfter + * @param elementTarget {HTMLElement} dragged element + * @param elementAfter {HTMLElement} dragged element will be placed after this element + */ + insertAfter = function (elementTarget, elementAfter) { + if (elementTarget && elementTarget.parentNode) { + var refChild = finalParams.wrapDraggableElements === FALSE ? elementTarget : elementTarget.nextSibling; + + elementTarget.parentNode.insertBefore(elementAfter, refChild); + } + }; - /* - * Insert an element after a selected element - * - * @private - * @method insertAfter - * @param elementTarget {HTMLElement} dragged element - * @param elementAfter {HTMLElement} dragged element will be placed after this element - */ - insertAfter = function (elementTarget, elementAfter) { - if (elementTarget && elementTarget.parentNode) { - var refChild = finalParams.wrapDraggableElements === FALSE ? elementTarget : elementTarget.nextSibling; + /* + * Insert an element before a selected element + * + * @private + * @method insertBefore + * @param elementTarget {HTMLElement} dragged element + * @param elementBefore {HTMLElement} dragged element will be placed before this element + */ + insertBefore = function (elementTarget, elementBefore) { + if (elementTarget && elementTarget.parentNode) { + elementTarget.parentNode.insertBefore(elementBefore, elementTarget); + } + }; - elementTarget.parentNode.insertBefore(elementAfter, refChild); - } - }; + /* + * Test whether an element is a draggable element + * + * @private + * @method isDraggableCallback + * @param element {HTMLElement} + * @return {Boolean} + */ + isDraggableCallback = function (element) { + return ( + (element.classList && element.classList.contains(CLASS_DRAGGABLE)) && + element.dataset.dragsterId === dragsterId + ); + }; - /* - * Insert an element before a selected element - * - * @private - * @method insertBefore - * @param elementTarget {HTMLElement} dragged element - * @param elementBefore {HTMLElement} dragged element will be placed before this element - */ - insertBefore = function (elementTarget, elementBefore) { - if (elementTarget && elementTarget.parentNode) { - elementTarget.parentNode.insertBefore(elementBefore, elementTarget); - } - }; + /* + * Test whether an element is a placeholder where a user can drop a dragged element + * + * @private + * @method isPlaceholderCallback + * @param element {HTMLElement} + * @return {Boolean} + */ + isPlaceholderCallback = function (element) { return (element.classList && element.classList.contains(CLASS_PLACEHOLDER)); }; + + /* + * Test whether an element belongs to drag only region + * + * @private + * @method isInDragOnlyRegionCallback + * @param element {HTMLElement} + * @return {Boolean} + */ + isInDragOnlyRegionCallback = function (element) { return (element.classList && element.classList.contains(finalParams.dragOnlyRegionCssClass)); }; //jshint ignore:line + + /* + * Update the height of the regions dynamically + * + * @private + * @method isPlaceholderCallback + * @param element {HTMLElement} + * @return {Boolean} + */ + updateRegionsHeight = function () { + if (finalParams.updateRegionsHeight) { + var regions = [].slice.call(document.getElementsByClassName(CLASS_REGION)); + + regions.forEach(function (region) { + var elements = [].slice.call(region.querySelectorAll(finalParams.elementSelector)), + regionHeight = finalParams.minimumRegionHeight; + + if (!elements.length) { + return; + } - /* - * Test whether an element is a draggable element - * - * @private - * @method isDraggableCallback - * @param element {HTMLElement} - * @return {Boolean} - */ - isDraggableCallback = function (element) { - return ( - (element.classList && element.classList.contains(CLASS_DRAGGABLE)) && - element.dataset.dragsterId === dragsterId - ); - }; + elements.forEach(function (element) { + var styles = window.getComputedStyle(element); - /* - * Test whether an element is a placeholder where a user can drop a dragged element - * - * @private - * @method isPlaceholderCallback - * @param element {HTMLElement} - * @return {Boolean} - */ - isPlaceholderCallback = function (element) { return (element.classList && element.classList.contains(CLASS_PLACEHOLDER)); }; + regionHeight += element.offsetHeight + parseInt(styles.marginTop, 10) + parseInt(styles.marginBottom, 10); + }); - /* - * Test whether an element belongs to drag only region - * - * @private - * @method isInDragOnlyRegionCallback - * @param element {HTMLElement} - * @return {Boolean} - */ - isInDragOnlyRegionCallback = function (element) { return (element.classList && element.classList.contains(finalParams.dragOnlyRegionCssClass)); }; //jshint ignore:line + region.style.height = regionHeight + UNIT; + }); + } + }; + + /** + * Resets DragsterJS workspace by removing mouseup/touchend event listeners + * + * @method resetDragsterWorkspace + * @private + * @param moveEvent {String} move event name (either mousemove or touchmove) + * @param upEvent {String} up event name (either mouseup or touchend) + */ + resetDragsterWorkspace = function (moveEvent, upEvent) { + cleanWorkspace(draggedElement, moveEvent); + cleanWorkspace(draggedElement, upEvent); + }; + regionEventHandlers = { /* - * Update the height of the regions dynamically + * `mousedown` or `touchstart` event handler. + * When user starts dragging an element the function adds a listener to either `mousemove` or `touchmove` + * events. Creates a shadow element that follows a movement of the cursor. * * @private - * @method isPlaceholderCallback - * @param element {HTMLElement} - * @return {Boolean} + * @method regionEventHandlers.mousedown + * @param event {Object} event object */ - updateRegionsHeight = function () { - if (finalParams.updateRegionsHeight) { - var regions = [].slice.call(document.getElementsByClassName(CLASS_REGION)); - - regions.forEach(function (region) { - var elements = [].slice.call(region.querySelectorAll(finalParams.elementSelector)), - regionHeight = finalParams.minimumRegionHeight; - - if (!elements.length) { - return; - } - - elements.forEach(function (element) { - var styles = window.getComputedStyle(element); - - regionHeight += element.offsetHeight + parseInt(styles.marginTop, 10) + parseInt(styles.marginBottom, 10); - }); - - region.style.height = regionHeight + UNIT; - }); + mousedown: function (event) { + if (finalParams.dragHandleCssClass && + (typeof finalParams.dragHandleCssClass !== 'string' || + !event.target.classList.contains(finalParams.dragHandleCssClass))) { + return FALSE; } - }; - /** - * Resets DragsterJS workspace by removing mouseup/touchend event listeners - * - * @method resetDragsterWorkspace - * @private - * @param moveEvent {String} move event name (either mousemove or touchmove) - * @param upEvent {String} up event name (either mouseup or touchend) - */ - resetDragsterWorkspace = function (moveEvent, upEvent) { - cleanWorkspace(draggedElement, moveEvent); - cleanWorkspace(draggedElement, upEvent); - }; - - regionEventHandlers = { - /* - * `mousedown` or `touchstart` event handler. - * When user starts dragging an element the function adds a listener to either `mousemove` or `touchmove` - * events. Creates a shadow element that follows a movement of the cursor. - * - * @private - * @method regionEventHandlers.mousedown - * @param event {Object} event object - */ - mousedown: function (event) { - if (finalParams.dragHandleCssClass && - (typeof finalParams.dragHandleCssClass !== 'string' || - !event.target.classList.contains(finalParams.dragHandleCssClass))) { - return FALSE; - } - - var targetRegion, - moveEvent, - upEvent, - isTouch = event.type === EVT_TOUCHSTART, - eventObject = event.changedTouches ? event.changedTouches[0] : event; + var targetRegion, + moveEvent, + upEvent, + isTouch = event.type === EVT_TOUCHSTART, + eventObject = event.changedTouches ? event.changedTouches[0] : event; - dragsterEventInfo = JSON.parse(JSON.stringify(defaultDragsterEventInfo)); - event.dragster = dragsterEventInfo; - - if (finalParams.onBeforeDragStart(event) === FALSE || event.which === 3 /* detect right click */) { - return FALSE; - } + dragsterEventInfo = JSON.parse(JSON.stringify(defaultDragsterEventInfo)); + event.dragster = dragsterEventInfo; - event.preventDefault(); - - draggedElement = getElement(event.target, isDraggableCallback); - - if (!draggedElement) { - return FALSE; - } - - moveEvent = isTouch ? EVT_TOUCHMOVE : EVT_MOUSEMOVE; - upEvent = isTouch ? EVT_TOUCHEND : EVT_MOUSEUP; - - regions.forEach(function (region) { - region.addEventListener(moveEvent, regionEventHandlers.mousemove, FALSE); - region.addEventListener(upEvent, regionEventHandlers.mouseup, FALSE); - }); - - document.body.addEventListener(moveEvent, regionEventHandlers.mousemove, FALSE); - document.body.addEventListener(upEvent, regionEventHandlers.mouseup, FALSE); + if (finalParams.onBeforeDragStart(event) === FALSE || event.which === 3 /* detect right click */) { + return FALSE; + } - targetRegion = draggedElement.getBoundingClientRect(); + event.preventDefault(); - shadowElementPositionXDiff = targetRegion.left - eventObject.clientX; - shadowElementPositionYDiff = targetRegion.top - eventObject.clientY; + draggedElement = getElement(event.target, isDraggableCallback); - shadowElement = createShadowElement(); - shadowElement.innerHTML = draggedElement.innerHTML; - shadowElement.style.width = targetRegion.width + UNIT; - shadowElement.style.height = targetRegion.height + UNIT; - shadowElement.dataset.dragsterId = dragsterId; - shadowElementRegion = shadowElement.getBoundingClientRect(); + if (!draggedElement) { + return FALSE; + } - draggedElement.classList.add(CLASS_DRAGGING); + moveEvent = isTouch ? EVT_TOUCHMOVE : EVT_MOUSEMOVE; + upEvent = isTouch ? EVT_TOUCHEND : EVT_MOUSEUP; - dragsterEventInfo.drag.node = draggedElement; - dragsterEventInfo.shadow.node = shadowElement; + regions.forEach(function (region) { + region.addEventListener(moveEvent, regionEventHandlers.mousemove, FALSE); + region.addEventListener(upEvent, regionEventHandlers.mouseup, FALSE); + }); - event.dragster = dragsterEventInfo; + document.body.addEventListener(moveEvent, regionEventHandlers.mousemove, FALSE); + document.body.addEventListener(upEvent, regionEventHandlers.mouseup, FALSE); - finalParams.onAfterDragStart(event); - }, - /* - * `mousemove` or `touchmove` event handler. - * When user is moving an element the function checks whether the element is above any other draggable element. - * In case when it is above any draggable element, the function adds a temporary placeholder before or after the given element, - * so a user is able to drop a dragged element onto the placeholder. - * In case when in a region there's no draggable element it just adds a placeholder to the region. - * Updates a position of shadow element following the cursor. - * - * @private - * @method regionEventHandlers.mousemove - * @param event {Object} event object - */ - mousemove: function (event) { - event.dragster = dragsterEventInfo; + targetRegion = draggedElement.getBoundingClientRect(); - if (finalParams.onBeforeDragMove(event) === FALSE || !shadowElementRegion) { - return FALSE; - } + shadowElementPositionXDiff = targetRegion.left - eventObject.clientX; + shadowElementPositionYDiff = targetRegion.top - eventObject.clientY; - event.preventDefault(); - - var eventObject = event.changedTouches ? event.changedTouches[0] : event, - pageXOffset = eventObject.view ? eventObject.view.pageXOffset : 0, - pageYOffset = eventObject.view ? eventObject.view.pageYOffset : 0, - elementPositionY = eventObject.clientY + pageYOffset, - elementPositionX = eventObject.clientX + pageXOffset, - unknownTarget = document.elementFromPoint(eventObject.clientX, eventObject.clientY), - dropTarget = getElement(unknownTarget, isDraggableCallback), - top = finalParams.shadowElementUnderMouse ? eventObject.clientY + shadowElementPositionYDiff : eventObject.clientY, - left = finalParams.shadowElementUnderMouse ? - elementPositionX + shadowElementPositionXDiff : - elementPositionX - (shadowElementRegion.width / 2), - isDragNodeAvailable = dragsterEventInfo.drag.node && dragsterEventInfo.drag.node.dataset, - isInDragOnlyRegion = !!(dropTarget && getElement(dropTarget, isInDragOnlyRegionCallback)), - isAllowedTarget = unknownTarget.dataset.dragsterId === dragsterId, - isTargetRegion = unknownTarget.classList.contains(CLASS_REGION) && isAllowedTarget, - isTargetRegionDragOnly = unknownTarget.classList.contains(finalParams.dragOnlyRegionCssClass) && isAllowedTarget, - isTargetPlaceholder = unknownTarget.classList.contains(CLASS_PLACEHOLDER), - hasTargetDraggaBleElements = unknownTarget.getElementsByClassName(CLASS_DRAGGABLE).length > 0, - hasTargetPlaceholders = unknownTarget.getElementsByClassName(CLASS_PLACEHOLDER).length > 0; - - clearTimeout(hideShadowElementTimeout); - - shadowElement.style.top = top + UNIT; - shadowElement.style.left = left + UNIT; - shadowElement.classList.remove(CLASS_HIDDEN); - - dragsterEventInfo.shadow.top = top; - dragsterEventInfo.shadow.left = left; - - if (!isDragNodeAvailable && !isTargetRegion && !isTargetPlaceholder) { - moveActions.removePlaceholders(); - } else if (dropTarget && dropTarget !== draggedElement && !isInDragOnlyRegion) { - moveActions.removePlaceholders(); - moveActions.addPlaceholderOnTarget(dropTarget, elementPositionY, pageYOffset); - } else if (isTargetRegion && !isTargetRegionDragOnly && !hasTargetDraggaBleElements && !hasTargetPlaceholders) { - moveActions.removePlaceholders(); - moveActions.addPlaceholderInRegion(unknownTarget); - } else if (isTargetRegion && !isTargetRegionDragOnly && hasTargetDraggaBleElements && !hasTargetPlaceholders) { - moveActions.removePlaceholders(); - moveActions.addPlaceholderInRegionBelowTargets(unknownTarget); - } + shadowElement = createShadowElement(); + shadowElement.innerHTML = draggedElement.innerHTML; + shadowElement.style.width = targetRegion.width + UNIT; + shadowElement.style.height = targetRegion.height + UNIT; + shadowElement.dataset.dragsterId = dragsterId; + shadowElementRegion = shadowElement.getBoundingClientRect(); - if (finalParams.scrollWindowOnDrag) { - scrollWindow(event); - } + draggedElement.classList.add(CLASS_DRAGGING); - updateRegionsHeight(); - finalParams.onAfterDragMove(event); - }, - /* - * `mouseup` or `touchend` event handler. - * When user is dropping an element, the function checks whether the element is above any other draggable element. - * In case when it is above any draggable element, the function places the dragged element before of after the element below. - * Removes a listener to either `mousemove` or `touchmove` event. - * Removes placeholders. - * Removes a shadow element. - * - * @private - * @method regionEventHandlers.mouseup - * @param event {Object} event object - */ - mouseup: function (event) { - event.dragster = dragsterEventInfo; - - var isTouch = event.type === EVT_TOUCHSTART, - moveEvent = isTouch ? EVT_TOUCHMOVE : EVT_MOUSEMOVE, - upEvent = isTouch ? EVT_TOUCHEND : EVT_MOUSEUP, - findByClass, - dropTarget, - dropDraggableTarget, - isFromDragOnlyRegion, - canBeCloned; - - if (finalParams.onBeforeDragEnd(event) === FALSE) { - resetDragsterWorkspace(moveEvent, upEvent); - - return FALSE; - } + dragsterEventInfo.drag.node = draggedElement; + dragsterEventInfo.shadow.node = shadowElement; - findByClass = finalParams.replaceElements ? CLASS_REPLACABLE : CLASS_PLACEHOLDER; - dropTarget = document.getElementsByClassName(findByClass)[0]; - isFromDragOnlyRegion = !!(draggedElement && getElement(draggedElement, isInDragOnlyRegionCallback)), - canBeCloned = finalParams.cloneElements && isFromDragOnlyRegion; + event.dragster = dragsterEventInfo; - hideShadowElementTimeout = setTimeout(resetDragsterWorkspace, 200); + finalParams.onAfterDragStart(event); + }, + /* + * `mousemove` or `touchmove` event handler. + * When user is moving an element the function checks whether the element is above any other draggable element. + * In case when it is above any draggable element, the function adds a temporary placeholder before or after the given element, + * so a user is able to drop a dragged element onto the placeholder. + * In case when in a region there's no draggable element it just adds a placeholder to the region. + * Updates a position of shadow element following the cursor. + * + * @private + * @method regionEventHandlers.mousemove + * @param event {Object} event object + */ + mousemove: function (event) { + event.dragster = dragsterEventInfo; - cleanReplacables(); + if (finalParams.onBeforeDragMove(event) === FALSE || !shadowElementRegion) { + return FALSE; + } - if (!draggedElement || !dropTarget) { - resetDragsterWorkspace(moveEvent, upEvent); + event.preventDefault(); - return FALSE; - } + var eventObject = event.changedTouches ? event.changedTouches[0] : event, + pageXOffset = eventObject.view ? eventObject.view.pageXOffset : 0, + pageYOffset = eventObject.view ? eventObject.view.pageYOffset : 0, + elementPositionY = eventObject.clientY + pageYOffset, + elementPositionX = eventObject.clientX + pageXOffset, + unknownTarget = document.elementFromPoint(eventObject.clientX, eventObject.clientY), + dropTarget = getElement(unknownTarget, isDraggableCallback), + top = finalParams.shadowElementUnderMouse ? eventObject.clientY + shadowElementPositionYDiff : eventObject.clientY, + left = finalParams.shadowElementUnderMouse ? + elementPositionX + shadowElementPositionXDiff : + elementPositionX - (shadowElementRegion.width / 2), + isDragNodeAvailable = dragsterEventInfo.drag.node && dragsterEventInfo.drag.node.dataset, + isInDragOnlyRegion = !!(dropTarget && getElement(dropTarget, isInDragOnlyRegionCallback)), + isAllowedTarget = unknownTarget.dataset.dragsterId === dragsterId, + isTargetRegion = unknownTarget.classList.contains(CLASS_REGION) && isAllowedTarget, + isTargetRegionDragOnly = unknownTarget.classList.contains(finalParams.dragOnlyRegionCssClass) && isAllowedTarget, + isTargetPlaceholder = unknownTarget.classList.contains(CLASS_PLACEHOLDER), + hasTargetDraggaBleElements = unknownTarget.getElementsByClassName(CLASS_DRAGGABLE).length > 0, + hasTargetPlaceholders = unknownTarget.getElementsByClassName(CLASS_PLACEHOLDER).length > 0; + + clearTimeout(hideShadowElementTimeout); + + shadowElement.style.top = top + UNIT; + shadowElement.style.left = left + UNIT; + shadowElement.classList.remove(CLASS_HIDDEN); + + dragsterEventInfo.shadow.top = top; + dragsterEventInfo.shadow.left = left; + + if (!isDragNodeAvailable && !isTargetRegion && !isTargetPlaceholder) { + moveActions.removePlaceholders(); + } else if (dropTarget && dropTarget !== draggedElement && !isInDragOnlyRegion) { + moveActions.removePlaceholders(); + moveActions.addPlaceholderOnTarget(dropTarget, elementPositionY, pageYOffset); + } else if (isTargetRegion && !isTargetRegionDragOnly && !hasTargetDraggaBleElements && !hasTargetPlaceholders) { + moveActions.removePlaceholders(); + moveActions.addPlaceholderInRegion(unknownTarget); + } else if (isTargetRegion && !isTargetRegionDragOnly && hasTargetDraggaBleElements && !hasTargetPlaceholders) { + moveActions.removePlaceholders(); + moveActions.addPlaceholderInRegionBelowTargets(unknownTarget); + } - dropDraggableTarget = getElement(dropTarget, isDraggableCallback); - dropDraggableTarget = dropDraggableTarget || dropTarget; + if (finalParams.scrollWindowOnDrag) { + scrollWindow(event); + } - if (draggedElement !== dropDraggableTarget) { - if (!finalParams.replaceElements && !canBeCloned) { - event.dragster = dropActions.moveElement(event.dragster, dropTarget, dropDraggableTarget); + updateRegionsHeight(); + finalParams.onAfterDragMove(event); + }, + /* + * `mouseup` or `touchend` event handler. + * When user is dropping an element, the function checks whether the element is above any other draggable element. + * In case when it is above any draggable element, the function places the dragged element before of after the element below. + * Removes a listener to either `mousemove` or `touchmove` event. + * Removes placeholders. + * Removes a shadow element. + * + * @private + * @method regionEventHandlers.mouseup + * @param event {Object} event object + */ + mouseup: function (event) { + event.dragster = dragsterEventInfo; + + var isTouch = event.type === EVT_TOUCHSTART, + moveEvent = isTouch ? EVT_TOUCHMOVE : EVT_MOUSEMOVE, + upEvent = isTouch ? EVT_TOUCHEND : EVT_MOUSEUP, + findByClass, + dropTarget, + dropDraggableTarget, + isFromDragOnlyRegion, + canBeCloned; + + if (finalParams.onBeforeDragEnd(event) === FALSE) { + resetDragsterWorkspace(moveEvent, upEvent); - finalParams.onAfterDragDrop(event); - } else if (finalParams.replaceElements && !canBeCloned) { - event.dragster = dropActions.replaceElements(event.dragster, dropDraggableTarget); + return FALSE; + } - finalParams.onAfterDragDrop(event); - } else if (!finalParams.replaceElements && canBeCloned) { - event.dragster = dropActions.cloneElements(event.dragster, dropTarget, dropDraggableTarget); + findByClass = finalParams.replaceElements ? CLASS_REPLACABLE : CLASS_PLACEHOLDER; + dropTarget = document.getElementsByClassName(findByClass)[0]; + isFromDragOnlyRegion = !!(draggedElement && getElement(draggedElement, isInDragOnlyRegionCallback)), + canBeCloned = finalParams.cloneElements && isFromDragOnlyRegion; - finalParams.onAfterDragDrop(event); - } + hideShadowElementTimeout = setTimeout(resetDragsterWorkspace, 200); - dropDraggableTarget.classList.remove(CLASS_DRAGOVER); - } + cleanReplacables(); + if (!draggedElement || !dropTarget) { resetDragsterWorkspace(moveEvent, upEvent); - finalParams.onAfterDragEnd(event); + return FALSE; } - }; - moveActions = { - /** - * Adds a new placeholder in relation to drop target - * - * @method moveActions.addPlaceholderOnTarget - * @private - * @param dropTarget {HTMLElement} a drop target element - * @param elementPositionY {Number} position Y of dragged element - * @param pageYOffset {Number} position of the scroll bar - */ - addPlaceholderOnTarget: function (dropTarget, elementPositionY, pageYOffset) { - var dropTargetRegion = dropTarget.getBoundingClientRect(), - placeholder = createPlaceholder(), - maxDistance = dropTargetRegion.height / 2; + dropDraggableTarget = getElement(dropTarget, isDraggableCallback); + dropDraggableTarget = dropDraggableTarget || dropTarget; - cleanReplacables(); + if (draggedElement !== dropDraggableTarget) { + if (!finalParams.replaceElements && !canBeCloned) { + event.dragster = dropActions.moveElement(event.dragster, dropTarget, dropDraggableTarget); - if (!finalParams.replaceElements) { - if ((elementPositionY - pageYOffset - dropTargetRegion.top) < maxDistance && !visiblePlaceholder.top) { - removeElements(CLASS_PLACEHOLDER); - placeholder.dataset.placeholderPosition = POS_TOP; - insertBefore(dropTarget.firstChild, placeholder); + finalParams.onAfterDragDrop(event); + } else if (finalParams.replaceElements && !canBeCloned) { + event.dragster = dropActions.replaceElements(event.dragster, dropDraggableTarget); - dragsterEventInfo.placeholder.position = POS_TOP; - } else if ((dropTargetRegion.bottom - (elementPositionY - pageYOffset)) < maxDistance && !visiblePlaceholder.bottom) { - removeElements(CLASS_PLACEHOLDER); - placeholder.dataset.placeholderPosition = POS_BOTTOM; - dropTarget.appendChild(placeholder); + finalParams.onAfterDragDrop(event); + } else if (!finalParams.replaceElements && canBeCloned) { + event.dragster = dropActions.cloneElements(event.dragster, dropTarget, dropDraggableTarget); - dragsterEventInfo.placeholder.position = POS_BOTTOM; - } - } else { - dropTarget.classList.add(CLASS_REPLACABLE); + finalParams.onAfterDragDrop(event); } - dragsterEventInfo.placeholder.node = placeholder; - dragsterEventInfo.drop.node = dropTarget; - }, - - /** - * Adds a new placeholder in an empty region - * - * @method moveActions.addPlaceholderInRegion - * @private - * @param regionTarget {HTMLElement} a region drop target - */ - addPlaceholderInRegion: function (regionTarget) { - var placeholder = createPlaceholder(); + dropDraggableTarget.classList.remove(CLASS_DRAGOVER); + } - regionTarget.appendChild(placeholder); + resetDragsterWorkspace(moveEvent, upEvent); - dragsterEventInfo.placeholder.position = POS_BOTTOM; - dragsterEventInfo.placeholder.node = placeholder; - dragsterEventInfo.drop.node = regionTarget; - }, + finalParams.onAfterDragEnd(event); + } + }; - /** - * Adds a new placeholder in an empty region - * - * @method moveActions.addPlaceholderInRegion - * @private - * @param regionTarget {HTMLElement} a region drop target - */ - addPlaceholderInRegionBelowTargets: function (regionTarget) { - var elementsInRegion = [].slice.call(regionTarget.getElementsByClassName(CLASS_DRAGGABLE)), - filteredElements = elementsInRegion.filter(function (elementInRegion) { - return elementInRegion.dataset.dragsterId === dragsterId; - }), - dropTarget = filteredElements[filteredElements.length - 1], - placeholder = createPlaceholder(); - - placeholder.dataset.placeholderPosition = POS_BOTTOM; - removeElements(CLASS_PLACEHOLDER); - dropTarget.appendChild(placeholder); + moveActions = { + /** + * Adds a new placeholder in relation to drop target + * + * @method moveActions.addPlaceholderOnTarget + * @private + * @param dropTarget {HTMLElement} a drop target element + * @param elementPositionY {Number} position Y of dragged element + * @param pageYOffset {Number} position of the scroll bar + */ + addPlaceholderOnTarget: function (dropTarget, elementPositionY, pageYOffset) { + var dropTargetRegion = dropTarget.getBoundingClientRect(), + placeholder = createPlaceholder(), + maxDistance = dropTargetRegion.height / 2; - dragsterEventInfo.placeholder.position = POS_BOTTOM; - dragsterEventInfo.placeholder.node = placeholder; - dragsterEventInfo.drop.node = dropTarget; - }, + cleanReplacables(); - /** - * Removes all placeholders from regions - * - * @method moveActions.removePlaceholders - * @private - */ - removePlaceholders: function () { - if (!finalParams.replaceElements) { + if (!finalParams.replaceElements) { + if ((elementPositionY - pageYOffset - dropTargetRegion.top) < maxDistance && !visiblePlaceholder.top) { removeElements(CLASS_PLACEHOLDER); - } else { - cleanReplacables(); - } - }, - }; + placeholder.dataset.placeholderPosition = POS_TOP; + insertBefore(dropTarget.firstChild, placeholder); - dropActions = { - /** - * Moves element to the final position on drop - * - * @method dropActions.moveElement - * @private - * @param dragsterEvent {Object} dragster properties from event - * @param dropTarget {HTMLElement} region where dragged element will be placed after drop - * @param dropDraggableTarget {HTMLElement} final destination of dragged element - * @return {Object} updated event info - */ - moveElement: function (dragsterEvent, dropTarget, dropDraggableTarget) { - var dropTemp = finalParams.wrapDraggableElements === FALSE ? draggedElement : createElementWrapper(), - placeholderPosition = dropTarget.dataset.placeholderPosition; - - if (placeholderPosition === POS_TOP) { - insertBefore(dropDraggableTarget, dropTemp); - } else { - if (finalParams.wrapDraggableElements === FALSE) { - insertAfter(dropTemp, dropDraggableTarget); - } else { - insertAfter(dropDraggableTarget, dropTemp); - } - } + dragsterEventInfo.placeholder.position = POS_TOP; + } else if ((dropTargetRegion.bottom - (elementPositionY - pageYOffset)) < maxDistance && !visiblePlaceholder.bottom) { + removeElements(CLASS_PLACEHOLDER); + placeholder.dataset.placeholderPosition = POS_BOTTOM; + dropTarget.appendChild(placeholder); - if (draggedElement.firstChild && finalParams.wrapDraggableElements === TRUE) { - dropTemp.appendChild(draggedElement.firstChild); + dragsterEventInfo.placeholder.position = POS_BOTTOM; } + } else { + dropTarget.classList.add(CLASS_REPLACABLE); + } - dragsterEvent.dropped = dropTemp; + dragsterEventInfo.placeholder.node = placeholder; + dragsterEventInfo.drop.node = dropTarget; + }, - return dragsterEvent; - }, + /** + * Adds a new placeholder in an empty region + * + * @method moveActions.addPlaceholderInRegion + * @private + * @param regionTarget {HTMLElement} a region drop target + */ + addPlaceholderInRegion: function (regionTarget) { + var placeholder = createPlaceholder(); - /** - * Replaces element with target element on drop - * - * @method dropActions.replaceElements - * @private - * @param dragsterEvent {Object} dragster properties from event - * @param dropDraggableTarget {HTMLElement} final destination of dragged element - * @return {Object} updated event info - */ - replaceElements: function (dragsterEvent, dropDraggableTarget) { - var dropTemp = document.getElementsByClassName(CLASS_TEMP_CONTAINER)[0]; + regionTarget.appendChild(placeholder); - dropTemp.innerHTML = draggedElement.innerHTML; + dragsterEventInfo.placeholder.position = POS_BOTTOM; + dragsterEventInfo.placeholder.node = placeholder; + dragsterEventInfo.drop.node = regionTarget; + }, - draggedElement.innerHTML = dropDraggableTarget.innerHTML; - dropDraggableTarget.innerHTML = dropTemp.innerHTML; - dropTemp.innerHTML = ''; - dragsterEvent.dropped = dropTemp; + /** + * Adds a new placeholder in an empty region + * + * @method moveActions.addPlaceholderInRegion + * @private + * @param regionTarget {HTMLElement} a region drop target + */ + addPlaceholderInRegionBelowTargets: function (regionTarget) { + var elementsInRegion = [].slice.call(regionTarget.getElementsByClassName(CLASS_DRAGGABLE)), + filteredElements = elementsInRegion.filter(function (elementInRegion) { + return elementInRegion.dataset.dragsterId === dragsterId; + }), + dropTarget = filteredElements[filteredElements.length - 1], + placeholder = createPlaceholder(); + + placeholder.dataset.placeholderPosition = POS_BOTTOM; + removeElements(CLASS_PLACEHOLDER); + dropTarget.appendChild(placeholder); - return dragsterEvent; - }, + dragsterEventInfo.placeholder.position = POS_BOTTOM; + dragsterEventInfo.placeholder.node = placeholder; + dragsterEventInfo.drop.node = dropTarget; + }, - /** - * Clones element to the final position on drop - * - * @method dropActions.cloneElements - * @private - * @param dragsterEvent {Object} dragster properties from event - * @param dropTarget {HTMLElement} region where dragged element will be placed after drop - * @param dropDraggableTarget {HTMLElement} final destination of dragged element - * @return {Object} updated event info - */ - cloneElements: function (dragsterEvent, dropTarget, dropDraggableTarget) { - var dropTemp = draggedElement.cloneNode(true), - placeholderPosition = dropTarget.dataset.placeholderPosition; + /** + * Removes all placeholders from regions + * + * @method moveActions.removePlaceholders + * @private + */ + removePlaceholders: function () { + if (!finalParams.replaceElements) { + removeElements(CLASS_PLACEHOLDER); + } else { + cleanReplacables(); + } + }, + }; - if (placeholderPosition === POS_TOP) { - insertBefore(dropDraggableTarget, dropTemp); + dropActions = { + /** + * Moves element to the final position on drop + * + * @method dropActions.moveElement + * @private + * @param dragsterEvent {Object} dragster properties from event + * @param dropTarget {HTMLElement} region where dragged element will be placed after drop + * @param dropDraggableTarget {HTMLElement} final destination of dragged element + * @return {Object} updated event info + */ + moveElement: function (dragsterEvent, dropTarget, dropDraggableTarget) { + var dropTemp = finalParams.wrapDraggableElements === FALSE ? draggedElement : createElementWrapper(), + placeholderPosition = dropTarget.dataset.placeholderPosition; + + if (placeholderPosition === POS_TOP) { + insertBefore(dropDraggableTarget, dropTemp); + } else { + if (finalParams.wrapDraggableElements === FALSE) { + insertAfter(dropTemp, dropDraggableTarget); } else { insertAfter(dropDraggableTarget, dropTemp); } + } - cleanWorkspace(dropTemp); + if (draggedElement.firstChild && finalParams.wrapDraggableElements === TRUE) { + dropTemp.appendChild(draggedElement.firstChild); + } - dragsterEvent.clonedFrom = draggedElement; - dragsterEvent.clonedTo = dropTemp; + dragsterEvent.dropped = dropTemp; - return dragsterEvent; - }, - }; + return dragsterEvent; + }, /** - * Scrolls window while dragging an element + * Replaces element with target element on drop * - * @method scrollWindow + * @method dropActions.replaceElements * @private - * @param event {Object} event object + * @param dragsterEvent {Object} dragster properties from event + * @param dropDraggableTarget {HTMLElement} final destination of dragged element + * @return {Object} updated event info */ - scrollWindow = function (event) { - var eventObject = event.changedTouches ? event.changedTouches[0] : event, - diffSize = 60; + replaceElements: function (dragsterEvent, dropDraggableTarget) { + var dropTemp = document.getElementsByClassName(CLASS_TEMP_CONTAINER)[0]; - if (windowHeight - eventObject.clientY < diffSize) { - window.scrollBy(0, 10); - } else if (eventObject.clientY < diffSize) { - window.scrollBy(0, -10); - } - }; + dropTemp.innerHTML = draggedElement.innerHTML; + + draggedElement.innerHTML = dropDraggableTarget.innerHTML; + dropDraggableTarget.innerHTML = dropTemp.innerHTML; + dropTemp.innerHTML = ''; + dragsterEvent.dropped = dropTemp; + + return dragsterEvent; + }, /** - * Discovers window height + * Clones element to the final position on drop * - * @method discoverWindowHeight + * @method dropActions.cloneElements * @private + * @param dragsterEvent {Object} dragster properties from event + * @param dropTarget {HTMLElement} region where dragged element will be placed after drop + * @param dropDraggableTarget {HTMLElement} final destination of dragged element + * @return {Object} updated event info */ - discoverWindowHeight = function () { - windowHeight = window.innerHeight; - }; + cloneElements: function (dragsterEvent, dropTarget, dropDraggableTarget) { + var dropTemp = draggedElement.cloneNode(true), + placeholderPosition = dropTarget.dataset.placeholderPosition; + + if (placeholderPosition === POS_TOP) { + insertBefore(dropDraggableTarget, dropTemp); + } else { + insertAfter(dropDraggableTarget, dropTemp); + } - wrapDraggableElements(draggableElements); + cleanWorkspace(dropTemp); + dragsterEvent.clonedFrom = draggedElement; + dragsterEvent.clonedTo = dropTemp; + + return dragsterEvent; + }, + }; + + /** + * Scrolls window while dragging an element + * + * @method scrollWindow + * @private + * @param event {Object} event object + */ + scrollWindow = function (event) { + var eventObject = event.changedTouches ? event.changedTouches[0] : event, + diffSize = 60; + + if (windowHeight - eventObject.clientY < diffSize) { + window.scrollBy(0, 10); + } else if (eventObject.clientY < diffSize) { + window.scrollBy(0, -10); + } + }; + + /** + * Discovers window height + * + * @method discoverWindowHeight + * @private + */ + discoverWindowHeight = function () { + windowHeight = window.innerHeight; + }; + + wrapDraggableElements(draggableElements); + + /** + * Adds event listeners to the regions + * + * @method addEventListenersToRegions + * @private + */ + addEventListenersToRegions = function () { // add `mousedown`/`touchstart` and `mouseup`/`touchend` event listeners to regions regions.forEach(function (region) { region.classList.add(CLASS_REGION); @@ -924,19 +955,29 @@ region.addEventListener(EVT_MOUSEDOWN, regionEventHandlers.mousedown, FALSE); region.addEventListener(EVT_TOUCHSTART, regionEventHandlers.mousedown, FALSE); - }); + }; - window.addEventListener('resize', discoverWindowHeight, false); + addEventListenersToRegions(); - return { - update: function () { - draggableElements = findDraggableElements(); + window.addEventListener('resize', discoverWindowHeight, false); - wrapDraggableElements(draggableElements); - updateRegionsHeight(); - discoverWindowHeight(); - } - }; + return { + update: function () { + draggableElements = findDraggableElements(); + + wrapDraggableElements(draggableElements); + updateRegionsHeight(); + discoverWindowHeight(); + }, + updateRegions: function () { + regions = findRegionElements(); + + addEventListenersToRegions(); + } }; -})(window, window.document); +}; + + + return Dragster; +}); diff --git a/dragster.min.js b/dragster.min.js index fffa8c4..3668a57 100644 --- a/dragster.min.js +++ b/dragster.min.js @@ -1,5 +1,5 @@ /*@preserve - * Dragster - drag'n'drop library v1.4.2 + * Dragster - drag'n'drop library v1.5.0 * https://github.com/sunpietro/dragster * * Copyright 2015-2017 Piotr Nalepa @@ -8,6 +8,6 @@ * Released under the MIT license * https://github.com/sunpietro/dragster/blob/master/LICENSE * - * Date: 2017-03-30T16:30Z + * Date: 2017-05-06T22:30Z */ -!function(e,t){"use strict";e.Dragster=function(n){var r,a,o,s,l,d,i,c,g,u,m,p,h,f,v,E,L,C,y,w,D,b,B,T,H,M,N,P,R,S,I,A="dragster-",O="is-dragging",Y="is-drag-over",X=A+"draggable",q=A+"drag-region",x=A+"drop-placeholder",F=A+"temp",U=F+"-container",k=A+"is-hidden",J=A+"replacable",W="touchstart",j="touchmove",z="touchend",G="mousedown",K="mousemove",Q="mouseup",V="top",Z="bottom",$="px",_="div",ee=!1,te=!0,ne=null,re=function(){},ae={elementSelector:".dragster-block",regionSelector:".dragster-region",dragHandleCssClass:ee,dragOnlyRegionCssClass:A+"region--drag-only",replaceElements:ee,updateRegionsHeight:te,minimumRegionHeight:60,onBeforeDragStart:re,onAfterDragStart:re,onBeforeDragMove:re,onAfterDragMove:re,onBeforeDragEnd:re,onAfterDragEnd:re,onAfterDragDrop:re,scrollWindowOnDrag:ee,dragOnlyRegionsEnabled:ee,cloneElements:ee,wrapDraggableElements:te,shadowElementUnderMouse:ee},oe={top:ee,bottom:ee},se={drag:{node:ne},drop:{node:ne},shadow:{node:ne,top:0,left:0},placeholder:{node:ne,position:ne},dropped:ne,clonedFrom:ne,clonedTo:ne},le={},de=e.innerHeight,ie=Math.floor(65536*(1+Math.random())).toString(16);for(r in n)n.hasOwnProperty(r)&&(ae[r]=n[r]);return b=function(){return[].slice.call(t.querySelectorAll(ae.elementSelector))},B=function(e){return ae.wrapDraggableElements===ee?(console.warn("You have disabled the default behavior of wrapping the draggable elements. If you want Dragster.js to work properly you still will have to do this manually.\n\nMore info: https://github.com/sunpietro/dragster/blob/master/README.md#user-content-wrapdraggableelements---boolean"),ee):void e.forEach(function(e){var t=v(),n=e.parentNode;return n.classList.contains(X)?ee:(n.insertBefore(t,e),n.removeChild(e),void t.appendChild(e))})},c=b(),a=[].slice.call(t.querySelectorAll(ae.regionSelector)),ae.replaceElements&&(d=t.createElement(_),d.classList.add(k),d.classList.add(U),t.body.appendChild(d)),o=function(e,t){var n=e.parentNode;if(n&&(!e.classList||!e.classList.contains(q)||e.classList.contains(ae.dragOnlyRegionCssClass)))return t(e)?e:t(n)?n:o(n,t)},y=function(e){var n=[].slice.call(t.getElementsByClassName(e));n.forEach(function(e){e.dataset.dragsterId===ie&&e.parentNode.removeChild(e)})},w=function(e,n){n&&(a.forEach(function(e){e.removeEventListener(n,g.mousemove)}),t.body.removeEventListener(n,g.mousemove)),e&&e.classList.remove(O),[].slice.call(t.getElementsByClassName(X)).forEach(function(e){e.firstChild||e.parentNode.removeChild(e)}),y(x),y(F),T()},D=function(){[].slice.call(t.getElementsByClassName(J)).forEach(function(e){e.classList.remove(J)})},v=function(){var e=t.createElement(_);return e.classList.add(X),e.dataset.dragsterId=ie,e},L=function(){var e=t.createElement(_);return e.classList.add(x),e.dataset.dragsterId=ie,e},E=function(){var e=t.createElement(_);return e.classList.add(F),e.classList.add(k),e.style.position="fixed",e.dataset.dragsterId=ie,t.body.appendChild(e),e},h=function(e,t){if(e&&e.parentNode){var n=ae.wrapDraggableElements===ee?e:e.nextSibling;e.parentNode.insertBefore(t,n)}},f=function(e,t){e&&e.parentNode&&e.parentNode.insertBefore(t,e)},m=function(e){return e.classList&&e.classList.contains(X)&&e.dataset.dragsterId===ie},u=function(e){return e.classList&&e.classList.contains(x)},p=function(e){return e.classList&&e.classList.contains(ae.dragOnlyRegionCssClass)},T=function(){if(ae.updateRegionsHeight){var n=[].slice.call(t.getElementsByClassName(q));n.forEach(function(t){var n=[].slice.call(t.querySelectorAll(ae.elementSelector)),r=ae.minimumRegionHeight;n.length&&(n.forEach(function(t){var n=e.getComputedStyle(t);r+=t.offsetHeight+parseInt(n.marginTop,10)+parseInt(n.marginBottom,10)}),t.style.height=r+$)})}},N=function(e,t){w(i,e),w(i,t)},g={mousedown:function(e){if(ae.dragHandleCssClass&&("string"!=typeof ae.dragHandleCssClass||!e.target.classList.contains(ae.dragHandleCssClass)))return ee;var n,r,d,c=e.type===W,u=e.changedTouches?e.changedTouches[0]:e;return le=JSON.parse(JSON.stringify(se)),e.dragster=le,ae.onBeforeDragStart(e)===ee||3===e.which?ee:(e.preventDefault(),(i=o(e.target,m))?(r=c?j:K,d=c?z:Q,a.forEach(function(e){e.addEventListener(r,g.mousemove,ee),e.addEventListener(d,g.mouseup,ee)}),t.body.addEventListener(r,g.mousemove,ee),t.body.addEventListener(d,g.mouseup,ee),n=i.getBoundingClientRect(),S=n.left-u.clientX,I=n.top-u.clientY,s=E(),s.innerHTML=i.innerHTML,s.style.width=n.width+$,s.style.height=n.height+$,s.dataset.dragsterId=ie,l=s.getBoundingClientRect(),i.classList.add(O),le.drag.node=i,le.shadow.node=s,e.dragster=le,void ae.onAfterDragStart(e)):ee)},mousemove:function(e){if(e.dragster=le,ae.onBeforeDragMove(e)===ee||!l)return ee;e.preventDefault();var n=e.changedTouches?e.changedTouches[0]:e,r=n.view?n.view.pageXOffset:0,a=n.view?n.view.pageYOffset:0,d=n.clientY+a,c=n.clientX+r,g=t.elementFromPoint(n.clientX,n.clientY),u=o(g,m),h=ae.shadowElementUnderMouse?n.clientY+I:n.clientY,f=ae.shadowElementUnderMouse?c+S:c-l.width/2,v=le.drag.node&&le.drag.node.dataset,E=!(!u||!o(u,p)),L=g.dataset.dragsterId===ie,y=g.classList.contains(q)&&L,w=g.classList.contains(ae.dragOnlyRegionCssClass)&&L,D=g.classList.contains(x),b=g.getElementsByClassName(X).length>0,B=g.getElementsByClassName(x).length>0;clearTimeout(C),s.style.top=h+$,s.style.left=f+$,s.classList.remove(k),le.shadow.top=h,le.shadow.left=f,v||y||D?u&&u!==i&&!E?(R.removePlaceholders(),R.addPlaceholderOnTarget(u,d,a)):!y||w||b||B?y&&!w&&b&&!B&&(R.removePlaceholders(),R.addPlaceholderInRegionBelowTargets(g)):(R.removePlaceholders(),R.addPlaceholderInRegion(g)):R.removePlaceholders(),ae.scrollWindowOnDrag&&H(e),T(),ae.onAfterDragMove(e)},mouseup:function(e){e.dragster=le;var n,r,a,s,l,d=e.type===W,c=d?j:K,g=d?z:Q;return ae.onBeforeDragEnd(e)===ee?(N(c,g),ee):(n=ae.replaceElements?J:x,r=t.getElementsByClassName(n)[0],s=!(!i||!o(i,p)),l=ae.cloneElements&&s,C=setTimeout(N,200),D(),i&&r?(a=o(r,m),a=a||r,i!==a&&(ae.replaceElements||l?ae.replaceElements&&!l?(e.dragster=P.replaceElements(e.dragster,a),ae.onAfterDragDrop(e)):!ae.replaceElements&&l&&(e.dragster=P.cloneElements(e.dragster,r,a),ae.onAfterDragDrop(e)):(e.dragster=P.moveElement(e.dragster,r,a),ae.onAfterDragDrop(e)),a.classList.remove(Y)),N(c,g),void ae.onAfterDragEnd(e)):(N(c,g),ee))}},R={addPlaceholderOnTarget:function(e,t,n){var r=e.getBoundingClientRect(),a=L(),o=r.height/2;D(),ae.replaceElements?e.classList.add(J):t-n-r.top0,b=c.getElementsByClassName(q).length>0;clearTimeout(E),r.style.top=p+$,r.style.left=f+$,r.classList.remove(j),le.shadow.top=p,le.shadow.left=f,h||L||C?m&&m!==l&&!v?(P.removePlaceholders(),P.addPlaceholderOnTarget(m,d,s)):!L||y||D||b?L&&!y&&D&&!b&&(P.removePlaceholders(),P.addPlaceholderInRegionBelowTargets(c)):(P.removePlaceholders(),P.addPlaceholderInRegion(c)):P.removePlaceholders(),re.scrollWindowOnDrag&&T(e),B(),re.onAfterDragMove(e)},mouseup:function(e){e.dragster=le;var t,n,r,a,s,d=e.type===J,i=d?W:K,c=d?z:Q;return re.onBeforeDragEnd(e)===ee?(M(i,c),ee):(t=re.replaceElements?k:q,n=document.getElementsByClassName(t)[0],a=!(!l||!o(l,u)),s=re.cloneElements&&a,E=setTimeout(M,200),y(),l&&n?(r=o(n,g),r=r||n,l!==r&&(re.replaceElements||s?re.replaceElements&&!s?(e.dragster=N.replaceElements(e.dragster,r),re.onAfterDragDrop(e)):!re.replaceElements&&s&&(e.dragster=N.cloneElements(e.dragster,n,r),re.onAfterDragDrop(e)):(e.dragster=N.moveElement(e.dragster,n,r),re.onAfterDragDrop(e)),r.classList.remove(Y)),M(i,c),void re.onAfterDragEnd(e)):(M(i,c),ee))}},P={addPlaceholderOnTarget:function(e,t,n){var o=e.getBoundingClientRect(),r=v(),a=o.height/2;y(),re.replaceElements?e.classList.add(k):t-n-o.topFU^n*k^U3kS(b?5ee#4=sn2Rj_-042= zbYEOQd*=Ss{Qz6L-?fGl?yFF6ixEY*{b){7W~54nzA}4ev1?MQW(gBRBVfajvvxcC zcf2_0XO9*hcTF+)%;0E=&mIyXRoc)0X)Jr09x>8l;H;|s1l5F+e-#yz-a90mac61TXP49{n`yrHHN z6Mvj4Pb!oAkuPK5$s~X0E5-wp{7TJOfZPHx1wy~&s|@k?d^G_c-r*tm|3J-wP)fum z`JS4|gGv4ZfrR%aVT`u+h=SiP;f(T06cJI;fc>~2lF>)7<(P4G1^>Ph(lwn@o?xH; ztw}P;G$9l+L%?f~2N63+(}T!MQRM>52>1Y8MyFaVPRPM;GS!ig(Nj;2ona(|v|c=a{?m&e zt-T!$lVDRKwrtyWl(>RO!bjHL3%dt;v6K;VgW1d(d+GU<;GAr!=o{4G<*I35}^pf>fmDig8uIW?3prb6Bti57P>(^#nrqsW-zkG>v|0%2fg zFk+8SKo&FSPyQ!A1$P3i4Tbv+3G(vj<>Bd(69@kRo0mHL0;?T-lkx=${yUw{K!994 zZrTtCZTDV;fx#TGn^I;%8pWP|`is zsCVGWQ&7dJGMya<;8h_MN>u^Ww25jvsYy+NeE5btveQTRKn-QWAnt*ggie{(fnrTb zUp@jhjZ7{jogJ~xZSr-}d4Ry$%=a*qsnRcvI^7};GPUmT=ao(Np&lf&YE=9>Zf-Yn zv-YO9S`owQU0H#-(O(L8W;Gvq0AV&+LdXatuKR!ChP$mdjQ@ojUf~UE(SSLrnn7+T z@-;Pw;RAyja}zh4V+BMpU^TAUJ%omuvlJ0zd)9)A#o_EhU4?@Pa!_x@zG?xL+Y2Z| zY8TgTtvT#kNM4XpQ);b}eGg(RR{M%|pPB7$2!I#MS25o!y!jlfd(Vh?9w8!4(=jAoTbS zWHB)ug5ENkJ76FwI|esWPV7nO4zEQ*_PX}^#=fqfac{A7qOqnz`Ofzkc^2ffW|AjX zgvlTOFmZ+Sv25eqD(9JE*83%RRWsEEuGuR3@J2+Pe4&+rx87nQt7n#Jma$&ecBjy6 z`yeB{3e5QpSR`m~?C}v6?bwY%|6c=q?0#~~vIt80H&^FpjuxM#A2a^L;mk^Pjw(Hd zMhxqlwGIsgq9ek5{uBN?({bpJbzub#Eg5FO!>o9P0?CS~u!>hFBirsndF1#1aDRh3 z%J2W}{!TU}VF-w;=`^jTQ!Efk15!{RO%g;|%hOMw`h^3v8;GJ2TJye`@Bk!v5QTt! z3GM=~e@W3RhN?3GwIlq7TmcCBwG#54NIJ$+GViahPfvhg`3&&PmZxE$#&A~u?|fgv za~oIrgWhS*6dsiu8I5Y02#S&@GX)1bNE+cXYdbT5tXYb#m$kX9_U>5Axu^(I3c2oD zTTKod`=!OTyoyh(n_K24PxJ|hWXib@*|eYifm4jw&AARg&`qZeyYI{0fT}`&J77WO z>Sl+7hn5zUhfi#;FGQ%rRbdC^c^gu+uzYY1NSU&b^4h?lzr66uEuW#9+Hxe^xpc#h zm)XqjGbmfCN^Q5RtHNlYdTLp%7-TK1m`orfOsT&qokCUZH#ZgS_7tkh#?8(75Dy(R zAA)-95Oy|}#Ch`hZx=f9jc9jEw4M)*ro0YgCML={vP*vg-=s;!ztDZ@Py^Nq+Rt{y zE%d?4Iy*?l^ZChqe$bEG?YKp0)NZ%#`_@HH;$k_gNx=;-(D^E`bAGKOCukxdkP+M# z(3;KXhx7TMe*&at?e-!34ImRek>oMYguc%rG0mu_mJg)AmViFA;SAd3+C;70zQ&q# zKijVj@2oOJOcJ-&@hzi6uNXgP!{?xy3qu;{yaMird8N0U#ngs^ML6kOr&dV(Y}Q~Y zy57w9Z{0fx6JIK{lnns|!YlYqr2Qt+ev=}_l|f;nn+{4vqF01 z<*J1F7SgLEbl05sN>3xu_6z8mB}3ddGKbyQ;%G0;9(-9%#?g5vXtJ27Q^yD>L1TqBDF)KpvHz zA4P_p;}Uh*MZU9L;bb%Bjs-W`#bp@`6#Ik=KVZRCX)w3k_hD@-uNrj95lObh5qM*4 zao76>kZ^DHdbQ>H#@p2sZ?A^N22SdI)8;XaE-{8-1a@dOwvL;+R2=g0k37#-oxM+) ziS@puAFV|3WFi@eQ7}eew{wL+xW~+sl{X z9?rb$ClEx1?QW+VTLQnN#u^tY;H4+E+gna5D4bSBTs@j~Q2O%<6*zyXeHLrj@g#O2;%SY;$CL^#fP4+M+B0;QYpFb-ztTx%5vgk&VNO zuTju(6w7O&CAlEi4TI$;*ZMEIy?}Mf!uc28&nFRCp|U@-x;Q;>D;Q9YuAy{r!a!bE z$2N^$$QEQ^fiWCc5jSijh1b004$E Bv5)`& literal 2918 zcmV-s3z_sEiwFpr_S{$i17vbxXLEFAaxQIYZZ2wb0Hqmgm)f@Sdw+%T?V)m3c-f>$ z&oTAoW_L-m>At$VNj6DydW0=NjgcH#hNTPt{f=a8j4_m^U$8wIjYjjH;qb}-Vo4J! zC-gh(iFGQ;Fi})mEh~iAANUWU6tNYtz9e#HO^)0j-KQFHq*R=EhlfL^Mrq#-#P~2t zdCaJi;xJrN0Yr^^BI223!;!L{wvV2-;O8gnnh7PXOA^tTxpA~4 zazi6Z5*k`54}m{5qSnRP9dH&q%Ek{FM8vH)A^{zVDAXHYX|ST>*v4=D zR|j@05L^+)_1YJ`2Iq`e7*ZhdMt@7H_h+ZP_iZK8V3a5#m5u*gevW}B8~?TZqCB+m zFTOn%Ah=LWfzYqZuMF|G&C8TG&g*?Fwf-g4CBvXfw%S;1a6COtNBuP#p zk|Z9b271M#*^R*}R*Y;kq_tEGpy)%4xO*8lW`7!oU~Sp%|pp+Ju~f$!Jg#ND)Qv83z?! zalNuBi=@Z3VT5`mhy-7|_EeJiG`G-V0WO}52+&+Y-f9oO(B=Sp7Fd8L!65Yh%pMFh z0jNINd*mXd9tV)N?9yjOvjAXCqm3gvP+oh1OCGR7iABP+1r_9CxCmcfBzz^|?BeTA z7_12R09@)*mvL~JOlz1?|AMHIJBWmk=*9Er&t5!1M+XKb!JNbxIgaBhaSKs|57E&J zrvoZM(qi1=4tGZ+xtj88;DJgtL(*wB5uu;tXP?85(~(rAl3V?<)n~9zpWQ@T( zgp~U=rE->=_)7p0>Q+3%aTfS^MTzF5EI_T*neY3Qb`b=ML_~l?BuDlKky;}%p;pKe zV*@5ihjc*FNLhUfYfMN>3@lAEhHGaCIdN;rRpMG_18WA%I%AAw#H@% z#|VEt^J;Nb1jPo3!`ThGB08U!zgBYl>dfc03viqIkPc3|RD=Stb*ZNyw|BWG5C?V^ zc;;7?$v5OVq$-!u@yt-YBx9uDM&uHrs-(9K5GI?jau5b2?x$QRbRj zeLDnzXXruyymIgz0&$#{qvM`zyxGde#@*f+nNP}xXbYejABSG z=l_QE!%ot@e?$6}CcWX+)J@b3vO%6jeS5$jC{%vi*r@S&2w1?}sN(hD;uYJ^eQkGw zf&#+j^g-RSegx^Jvt~S%*vXy60uL$&S5B??-$wLZ;Bj3REy-%#M@j)*FKqGp1x~+> z>H{zof7{mW0daxv%BBOV0o_w zWT^P^;CmfW7WlGahEv3JZr2Z;bcX(8(Mfq_^=8W0Y+2xzc47)myWy)@OrZv=MKp;M zx(T@T4y#z-vr3Vac8u7tgnl;w;pwBmgx`Y!gW^Vn4|UzF8^WYp1$=6M_KK{f4*YQHXx)iBXmlqptsO4N{mRrLjg?rP@8nZNse4 zgo=`B`#lyZ4?%V(5d-WS@C~@|t*>2Rpjs&?TEp-08GxWkDj*+l;`abGS#~ye7w15* zcn5f9y;Byd5$rY3gYOIY(yICL#jMmPg2+#{GRi861`3KOQ@*R)HlOLwtk=r`NyRoy z0c;Fpy}&ZS#*rkzHnyXlpUHKLLiY#LZ6k0<+h0fa5?P8gkbvj@1rkltUJ zfCDWosagF{tcE_%^>;wP3-ho01280rTgxD+o7ro@Ok~OppxEeu!wNJ6KY&zBa`A4o z=iSvwXI}9RNp#1S;NaE^T2W>!hfkq;Dr>XZEPv(J0>w%LHBu0=Kq-#F`_KL1wrmL{ z`LMqBZ#K`NK(1Zfp6>Lf)A~(NVD({TYdO12G5>8tZ@m@mVTINSpkYSJ%G$sf$d(s#BIPv-O4e16(Zo6WQVGHW&)y)L?0lDJ+?MiTJ9 z8|`%^*cHDsK@v0(VgfGI09w=eyg#2GcV|Fq+HCgWaSTakPm*Um6Z$cY#5ALxpdpa{ zrv%KVEqk<0Zp^Qm&DXl*yvx=rqcT*)hBWpn{n;uk6e*8ngWhu53;fWekRd!Fg>+U^ zmC6V%k0V`li2^*#rUuBAx0Not`^I~K_^`E(;tqrm2;AT>%s31)4ijloYxB5fGr=c5 zI8bhIm-uPB?ch@#bDK@sMFe7i)G>hgWImUefvpfThN?0(pGV!gfj}ebmSgC36{EBW zrc_aZo;sQs-KZlS0V8Xq*DhBkyo>ZY3De^)o1gs%KvA&afgb?8)M8EQ$&?KzuN18- zz8n9s-p#ynC5ZF?6|%y#HeV9|9O=1fcAt5 zJzZqFYElH;(&+R$Z}r|`oh?_5x)zyk1y`u6w(yZIL=gh=9f2SX#M7g(;_R*1C;LX_XmAcUyD+lBEG%Mg_ z*Sc>iTzrdX?VczwZ(@8*?Yevs=V8yg?f^k{aJ=1gM-XYbypgB7{1hVmFhoH>%+qA{#!H4Tb)_*tH~`rLFwJ+C3xxGFYU!;I<{Tqcl{5f zdVR4h^6P;NYP7#iH%qZkk<12L`M*8TT;!j3!mzZ!ul>z2fvT4ERe&z*75LGMb|<8* zqE!A_cJr#_c!t_~D&UnC^=ziJ+R{Si_BfN-<9{3UNL z_0(ZoSNpz-w5Ub1B~930)W#RMapd5~Y2.2 Second region - independent diff --git a/module-generator.js b/module-generator.js new file mode 100644 index 0000000..565a527 --- /dev/null +++ b/module-generator.js @@ -0,0 +1,94 @@ +var fs = require('fs'); +var promises = [ + new Promise(function (resolve, reject) { + fs.readFile('dragster-script.js', 'utf8', function (error, data) { + if (error) { + reject(error); + + return; + } + + resolve(data); + }) + }), + new Promise(function (resolve, reject) { + fs.readFile('dragster-comment.js', 'utf8', function (error, data) { + if (error) { + reject(error); + + return; + } + + resolve(data); + }) + }), + new Promise(function (resolve, reject) { + fs.readFile('template.common.js', 'utf8', function (error, data) { + if (error) { + reject(error); + + return; + } + + resolve(data); + }); + }), + new Promise(function (resolve, reject) { + fs.readFile('template.es6.js', 'utf8', function (error, data) { + if (error) { + reject(error); + + return; + } + + resolve(data); + }); + }) +]; + +Promise.all(promises).then(function (files) { + var scriptContent = files[0]; + var scriptComment = files[1]; + var commonTemplate = files[2]; + var es6Template = files[3]; + var writes = [ + new Promise(function (resolve, reject) { + fs.writeFile( + 'dragster.js', + commonTemplate.replace('[DRAGSTER]', scriptContent).replace('[COMMENT]', scriptComment), + 'utf8', + function (error) { + if (error) { + reject(error); + + return; + } + + resolve(); + } + ); + }), + new Promise(function (resolve, reject) { + fs.writeFile( + 'dragster.es6.js', + es6Template.replace('[DRAGSTER]', scriptContent).replace('[COMMENT]', scriptComment), + 'utf8', + function (error) { + if (error) { + reject(error); + + return; + } + + resolve(); + } + ); + }), + ]; + + return Promise.all(writes); +}).catch(function (error) { + console.log('[ERROR]'); + console.log(error); + console.log('======='); +}); diff --git a/package.json b/package.json index baeb96a..c5466d2 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,13 @@ { "name": "dragsterjs", - "version": "1.4.2", + "version": "1.5.0", "description": "Tiny vanilla JS plugin that enables drag'n'drop interactions to a user of your website", "main": "dragster.js", "scripts": { + "generate-modules": "node module-generator.js", "minify": "uglifyjs dragster.js --mangle --compress --comments --output dragster.min.js", - "gzip": "gzip -c dragster.min.js > dragster.min.js.gz" + "gzip": "gzip -c dragster.min.js > dragster.min.js.gz", + "deploy": "npm run generate-modules && npm run minify && npm run gzip" }, "repository": { "type": "git", diff --git a/template.common.js b/template.common.js new file mode 100644 index 0000000..2800b65 --- /dev/null +++ b/template.common.js @@ -0,0 +1,18 @@ +[COMMENT] +;(function (root, moduleName, factory) { + 'use strict'; + + if (typeof define === 'function' && define.amd) { + define(moduleName, factory); + } else if (typeof exports === 'object') { + exports = module.exports = factory(); + } else { + root[moduleName] = factory(); + } +})(this, 'Dragster', function () { + 'use strict'; + +[DRAGSTER] + + return Dragster; +}); diff --git a/template.es6.js b/template.es6.js new file mode 100644 index 0000000..8c9b5cb --- /dev/null +++ b/template.es6.js @@ -0,0 +1,4 @@ +[COMMENT] +[DRAGSTER] + +export default Dragster;