UNPKG

devextreme

Version:

HTML5 JavaScript Component Suite for Responsive Web Development

504 lines (412 loc) • 17.4 kB
"use strict"; var $ = require("../../core/renderer"), eventsEngine = require("../../events/core/events_engine"), isDefined = require("../../core/utils/type").isDefined, extend = require("../../core/utils/extend").extend, each = require("../../core/utils/iterator").each, eventUtils = require("../../events/utils"), addNamespace = eventUtils.addNamespace, registerComponent = require("../../core/component_registrator"), DOMComponent = require("../../core/dom_component"), dragEvents = require("../../events/drag"); var SORTABLE_NAMESPACE = "dxSortable", SORTABLE_CLASS = "dx-sortable", SCROLL_STEP = 2, START_SCROLL_OFFSET = 20, SCROLL_TIMEOUT = 10; function elementHasPoint(element, x, y) { var $item = $(element), offset = $item.offset(); if (x >= offset.left && x <= offset.left + $item.outerWidth(true)) { if (y >= offset.top && y <= offset.top + $item.outerHeight(true)) { return true; } } } function checkHorizontalPosition(position, itemOffset, rtl) { if (isDefined(itemOffset.posHorizontal)) { return rtl ? position > itemOffset.posHorizontal : position < itemOffset.posHorizontal; } else { return true; } } function getIndex($items, $item) { var index = -1, itemElement = $item.get(0); each($items, function (elementIndex, element) { var $element = $(element); if (!($element.attr("item-group") && $element.attr("item-group") === $items.eq(elementIndex - 1).attr("item-group"))) { index++; } if (element === itemElement) { return false; } }); return index === $items.length ? -1 : index; } function getTargetGroup(e, $groups) { var result; each($groups, function () { if (elementHasPoint(this, e.pageX, e.pageY)) { result = $(this); } }); return result; } function getItemsOffset($elements, isVertical, $itemsContainer) { var result = [], $item = []; for (var i = 0; i < $elements.length; i += $item.length) { $item = $elements.eq(i); if ($item.attr("item-group")) { $item = $itemsContainer.find("[item-group='" + $item.attr("item-group") + "']"); } if ($item.is(':visible')) { var offset = { item: $item, index: result.length, posHorizontal: isVertical ? undefined : ($item.last().outerWidth(true) + $item.last().offset().left + $item.offset().left) / 2 }; if (isVertical) { offset.posVertical = ($item.last().offset().top + $item.offset().top + $item.last().outerHeight(true)) / 2; } else { offset.posVertical = $item.last().outerHeight(true) + $item.last().offset().top; } result.push(offset); } } return result; } function getScrollWrapper(scrollable) { var timeout = null, scrollTop = scrollable.scrollTop(), $element = scrollable.$element(), top = $element.offset().top, height = $element.height(), delta = 0; function onScroll(e) { scrollTop = e.scrollOffset.top; } scrollable.on("scroll", onScroll); function move() { stop(); scrollable.scrollTo(scrollTop += delta); timeout = setTimeout(move, SCROLL_TIMEOUT); } function stop() { clearTimeout(timeout); } function moveIfNeed(event) { if (event.pageY <= top + START_SCROLL_OFFSET) { delta = -SCROLL_STEP; } else if (event.pageY >= top + height - START_SCROLL_OFFSET) { delta = SCROLL_STEP; } else { delta = 0; stop(); return; } move(); } return { moveIfNeed: moveIfNeed, element: function element() { return $element; }, dispose: function dispose() { stop(); scrollable.off("scroll", onScroll); } }; } var Sortable = DOMComponent.inherit({ _getDefaultOptions: function _getDefaultOptions() { return extend(this.callBase(), { onChanged: null, onDragging: null, itemRender: null, groupSelector: null, itemSelector: ".dx-sort-item", itemContainerSelector: ".dx-sortable", sourceClass: "dx-drag-source", dragClass: "dx-drag", targetClass: "dx-drag-target", direction: "vertical", allowDragging: true, groupFilter: null, useIndicator: false }); }, _renderItem: function _renderItem($sourceItem, target) { var itemRender = this.option("itemRender"), $item; if (itemRender) { $item = itemRender($sourceItem, target); } else { $item = $sourceItem.clone(); $item.css({ width: $sourceItem.width(), height: $sourceItem.height() }); } return $item; }, _renderIndicator: function _renderIndicator($item, isVertical, $targetGroup, isLast) { var height = $item.outerHeight(true), width = $item.outerWidth(true), top = $item.offset().top - $targetGroup.offset().top, left = $item.offset().left - $targetGroup.offset().left; this._indicator.css({ "position": "absolute", "top": isLast && isVertical ? top + height : top, "left": isLast && !isVertical ? left + width : left }).toggleClass("dx-position-indicator-horizontal", !isVertical).toggleClass("dx-position-indicator-vertical", !!isVertical).toggleClass("dx-position-indicator-last", !!isLast).height("").width("").appendTo($targetGroup); isVertical ? this._indicator.width(width) : this._indicator.height(height); }, _renderDraggable: function _renderDraggable($sourceItem) { this._$draggable && this._$draggable.remove(); this._$draggable = this._renderItem($sourceItem, 'drag').addClass(this.option("dragClass")).appendTo("body").css({ zIndex: 1000000, position: "absolute" }); }, _detachEventHandlers: function _detachEventHandlers() { var dragEventsString = [dragEvents.move, dragEvents.start, dragEvents.end, dragEvents.enter, dragEvents.leave, dragEvents.drop].join(" "); eventsEngine.off(this._getEventListener(), addNamespace(dragEventsString, SORTABLE_NAMESPACE)); }, _getItemOffset: function _getItemOffset(isVertical, itemsOffset, e) { for (var i = 0; i < itemsOffset.length; i++) { var shouldInsert = isVertical ? e.pageY < itemsOffset[i].posVertical : checkHorizontalPosition(e.pageX, itemsOffset[i], this.option("rtlEnabled")); if (shouldInsert) { return itemsOffset[i]; } } }, _getEventListener: function _getEventListener() { var groupSelector = this.option("groupSelector"), element = this.$element(); return groupSelector ? element.find(groupSelector) : element; }, _attachEventHandlers: function _attachEventHandlers() { var that = this, itemSelector = that.option("itemSelector"), itemContainerSelector = that.option("itemContainerSelector"), groupSelector = that.option("groupSelector"), sourceClass = that.option("sourceClass"), targetClass = that.option("targetClass"), onDragging = that.option("onDragging"), groupFilter = that.option("groupFilter"), $sourceItem, sourceIndex, $targetItem, $targetGroup, startPositions, sourceGroup, element = that.$element(), $groups, scrollWrapper = null, targetIndex = -1; var setStartPositions = function setStartPositions() { startPositions = []; each($sourceItem, function (_, item) { startPositions.push($(item).offset()); }); }; var createGroups = function createGroups() { if (!groupSelector) { return element; } else { return groupFilter ? $(groupSelector).filter(groupFilter) : element.find(groupSelector); } }; var disposeScrollWrapper = function disposeScrollWrapper() { scrollWrapper && scrollWrapper.dispose(); scrollWrapper = null; }; var invokeOnDraggingEvent = function invokeOnDraggingEvent() { var draggingArgs = { sourceGroup: sourceGroup, sourceIndex: sourceIndex, sourceElement: $sourceItem, targetGroup: $targetGroup.attr("group"), targetIndex: $targetGroup.find(itemSelector).index($targetItem) }; onDragging && onDragging(draggingArgs); if (draggingArgs.cancel) { $targetGroup = undefined; } }; that._detachEventHandlers(); if (that.option("allowDragging")) { var $eventListener = that._getEventListener(); eventsEngine.on($eventListener, addNamespace(dragEvents.start, SORTABLE_NAMESPACE), itemSelector, function (e) { $sourceItem = $(e.currentTarget); var $sourceGroup = $sourceItem.closest(groupSelector); sourceGroup = $sourceGroup.attr("group"); sourceIndex = getIndex((groupSelector ? $sourceGroup : element).find(itemSelector), $sourceItem); if ($sourceItem.attr("item-group")) { $sourceItem = $sourceGroup.find("[item-group='" + $sourceItem.attr("item-group") + "']"); } that._renderDraggable($sourceItem); $targetItem = that._renderItem($sourceItem, 'target').addClass(targetClass); $sourceItem.addClass(sourceClass); setStartPositions(); $groups = createGroups(); that._indicator = $("<div>").addClass("dx-position-indicator"); }); eventsEngine.on($eventListener, addNamespace(dragEvents.move, SORTABLE_NAMESPACE), function (e) { var $item, $itemContainer, $items, $lastItem, itemsOffset = [], isVertical, itemOffset, $prevItem; if (!$sourceItem) { return; } targetIndex = -1; that._indicator.detach(); each(that._$draggable, function (index, draggableElement) { $(draggableElement).css({ top: startPositions[index].top + e.offset.y, left: startPositions[index].left + e.offset.x }); }); $targetGroup && $targetGroup.removeClass(targetClass); $targetGroup = getTargetGroup(e, $groups); $targetGroup && invokeOnDraggingEvent(); if ($targetGroup && scrollWrapper && $targetGroup.get(0) !== scrollWrapper.element().get(0)) { disposeScrollWrapper(); } scrollWrapper && scrollWrapper.moveIfNeed(e); if (!$targetGroup) { $targetItem.detach(); return; } if (!scrollWrapper && $targetGroup.attr("allow-scrolling")) { scrollWrapper = getScrollWrapper($targetGroup.dxScrollable("instance")); } $targetGroup.addClass(targetClass); $itemContainer = $targetGroup.find(itemContainerSelector); $items = $itemContainer.find(itemSelector); var targetSortable = $targetGroup.closest("." + SORTABLE_CLASS).data("dxSortable"), useIndicator = targetSortable.option("useIndicator"); isVertical = (targetSortable || that).option("direction") === "vertical"; itemsOffset = getItemsOffset($items, isVertical, $itemContainer); itemOffset = that._getItemOffset(isVertical, itemsOffset, e); if (itemOffset) { $item = itemOffset.item; $prevItem = itemsOffset[itemOffset.index - 1] && itemsOffset[itemOffset.index - 1].item; if ($item.hasClass(sourceClass) || $prevItem && $prevItem.hasClass(sourceClass) && $prevItem.is(":visible")) { $targetItem.detach(); return; } targetIndex = itemOffset.index; if (!useIndicator) { $targetItem.insertBefore($item); return; } var isAnotherGroup = $targetGroup.attr("group") !== sourceGroup, isSameIndex = targetIndex === sourceIndex, isNextIndex = targetIndex === sourceIndex + 1; if (isAnotherGroup) { that._renderIndicator($item, isVertical, $targetGroup, that.option("rtlEnabled") && !isVertical); return; } if (!isSameIndex && !isNextIndex) { that._renderIndicator($item, isVertical, $targetGroup, that.option("rtlEnabled") && !isVertical); } } else { $lastItem = $items.last(); if ($lastItem.is(":visible") && $lastItem.hasClass(sourceClass)) { return; } if ($itemContainer.length) { targetIndex = itemsOffset.length ? itemsOffset[itemsOffset.length - 1].index + 1 : 0; } if (useIndicator) { $items.length && that._renderIndicator($lastItem, isVertical, $targetGroup, !that.option("rtlEnabled") || isVertical); } else { $targetItem.appendTo($itemContainer); } } }); eventsEngine.on($eventListener, addNamespace(dragEvents.end, SORTABLE_NAMESPACE), function () { disposeScrollWrapper(); if (!$sourceItem) { return; } var onChanged = that.option("onChanged"), changedArgs = { sourceIndex: sourceIndex, sourceElement: $sourceItem, sourceGroup: sourceGroup, targetIndex: targetIndex, removeSourceElement: true, removeTargetElement: false, removeSourceClass: true }; if ($targetGroup) { $targetGroup.removeClass(targetClass); changedArgs.targetGroup = $targetGroup.attr("group"); if (sourceGroup !== changedArgs.targetGroup || targetIndex > -1) { onChanged && onChanged(changedArgs); changedArgs.removeSourceElement && $sourceItem.remove(); } } that._indicator.detach(); changedArgs.removeSourceClass && $sourceItem.removeClass(sourceClass); $sourceItem = null; that._$draggable.remove(); that._$draggable = null; changedArgs.removeTargetElement && $targetItem.remove(); $targetItem.removeClass(targetClass); $targetItem = null; }); } }, _init: function _init() { this.callBase(); this._attachEventHandlers(); }, _render: function _render() { this.callBase(); this.$element().addClass(SORTABLE_CLASS); }, _dispose: function _dispose() { var that = this; that.callBase.apply(that, arguments); that._$draggable && that._$draggable.detach(); that._indicator && that._indicator.detach(); }, _optionChanged: function _optionChanged(args) { var that = this; switch (args.name) { case "onDragging": case "onChanged": case "itemRender": case "groupSelector": case "itemSelector": case "itemContainerSelector": case "sourceClass": case "targetClass": case "dragClass": case "allowDragging": case "groupFilter": case "useIndicator": that._attachEventHandlers(); break; case "direction": break; default: that.callBase(args); } } }); ///#DEBUG Sortable.prototype.__SCROLL_STEP = SCROLL_STEP; ///#ENDDEBUG registerComponent("dxSortable", Sortable); module.exports = Sortable;