devextreme
Version:
HTML5 JavaScript Component Suite for Responsive Web Development
304 lines (303 loc) • 12.4 kB
JavaScript
/**
* DevExtreme (ui/list/ui.list.edit.decorator.reorder.js)
* Version: 18.1.3
* Build date: Tue May 15 2018
*
* Copyright (c) 2012 - 2018 Developer Express Inc. ALL RIGHTS RESERVED
* Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
*/
"use strict";
var $ = require("../../core/renderer"),
each = require("../../core/utils/iterator").each,
eventsEngine = require("../../events/core/events_engine"),
translator = require("../../animation/translator"),
fx = require("../../animation/fx"),
dragEvents = require("../../events/drag"),
mathUtils = require("../../core/utils/math"),
Animator = require("../scroll_view/animator"),
eventUtils = require("../../events/utils"),
registerDecorator = require("./ui.list.edit.decorator_registry").register,
EditDecorator = require("./ui.list.edit.decorator");
var ReorderScrollAnimator = Animator.inherit({
ctor: function(strategy) {
this.callBase();
this._strategy = strategy
},
_isFinished: function() {
return this._strategy.scrollFinished()
},
_step: function() {
this._strategy.scrollByStep()
}
});
var LIST_EDIT_DECORATOR = "dxListEditDecorator",
DRAG_START_EVENT_NAME = eventUtils.addNamespace(dragEvents.start, LIST_EDIT_DECORATOR),
DRAG_UPDATE_EVENT_NAME = eventUtils.addNamespace(dragEvents.move, LIST_EDIT_DECORATOR),
DRAG_END_EVENT_NAME = eventUtils.addNamespace(dragEvents.end, LIST_EDIT_DECORATOR),
REORDER_HANDLE_CONTAINER_CLASS = "dx-list-reorder-handle-container",
REORDER_HANDLE_CLASS = "dx-list-reorder-handle",
REOREDERING_ITEM_CLASS = "dx-list-item-reordering",
REOREDERING_ITEM_GHOST_CLASS = "dx-list-item-ghost-reordering";
registerDecorator("reorder", "default", EditDecorator.inherit({
_init: function() {
this._groupedEnabled = this._list.option("grouped");
this._initAnimator()
},
_initAnimator: function() {
this._scrollAnimator = new ReorderScrollAnimator(this)
},
_startAnimator: function() {
if (!this._scrollAnimator.inProgress()) {
this._scrollAnimator.start()
}
},
_stopAnimator: function() {
this._scrollAnimator.stop()
},
afterBag: function(config) {
var $itemElement = config.$itemElement,
$container = config.$container;
var $handle = $("<div>").addClass(REORDER_HANDLE_CLASS);
var lockedDrag = false;
eventsEngine.on($handle, "dxpointerdown", function(e) {
lockedDrag = !eventUtils.isMouseEvent(e)
});
eventsEngine.on($handle, "dxhold", {
timeout: 30
}, function(e) {
e.cancel = true;
lockedDrag = false
});
eventsEngine.on($handle, DRAG_START_EVENT_NAME, {
direction: "vertical",
immediate: true
}, function(e) {
if (lockedDrag) {
e.cancel = true;
return
}
this._dragStartHandler($itemElement, e)
}.bind(this));
eventsEngine.on($handle, DRAG_UPDATE_EVENT_NAME, this._dragHandler.bind(this, $itemElement));
eventsEngine.on($handle, DRAG_END_EVENT_NAME, this._dragEndHandler.bind(this, $itemElement));
$container.addClass(REORDER_HANDLE_CONTAINER_CLASS);
$container.append($handle)
},
_dragStartHandler: function($itemElement, e) {
if ($itemElement.is(".dx-state-disabled, .dx-state-disabled *")) {
e.cancel = true;
return
}
this._stopPreviousAnimation();
e.targetElements = [];
this._cacheItemsPositions();
this._startPointerOffset = e.pageY - $itemElement.offset().top;
this._elementHeight = $itemElement.outerHeight();
var itemIndex = this._list.getFlatIndexByItemElement($itemElement);
this._startIndex = itemIndex;
this._lastIndex = itemIndex;
this._cacheScrollData();
var that = this;
this._createGhostTimeout = setTimeout(function() {
that._createGhost($itemElement);
that._updateGhostPosition();
$itemElement.addClass(REOREDERING_ITEM_CLASS)
})
},
_stopPreviousAnimation: function() {
fx.stop(this._$ghostItem, true)
},
_cacheItemsPositions: function() {
var itemPositions = this._itemPositions = [];
each(this._list.itemElements(), function(index, item) {
var cachedPosition = null;
itemPositions.push(function() {
cachedPosition = null === cachedPosition ? $(item).position().top : cachedPosition;
return cachedPosition
})
})
},
_getDraggingElementPosition: function() {
return this._itemPositions[this._startIndex]()
},
_getLastElementPosition: function() {
return this._itemPositions[this._lastIndex]()
},
_cacheScrollData: function() {
this._list.updateDimensions();
this._startScrollTop = this._list.scrollTop();
this._scrollOffset = 0;
this._scrollHeight = this._list.scrollHeight();
this._clientHeight = this._list.clientHeight()
},
_scrollTop: function() {
return this._startScrollTop + this._scrollOffset
},
_createGhost: function($itemElement) {
this._$ghostItem = $itemElement.clone();
this._$ghostItem.addClass(REOREDERING_ITEM_GHOST_CLASS).appendTo(this._list.itemsContainer());
this._startGhostPosition = this._getDraggingElementPosition() - this._$ghostItem.position().top;
translator.move(this._$ghostItem, {
top: this._startGhostPosition
})
},
_dragHandler: function($itemElement, e) {
this._topOffset = e.offset.y;
this._updateItemPositions();
var pointerPosition = this._getPointerPosition();
this._toggleScroll(pointerPosition)
},
_getPointerPosition: function() {
return this._getDraggingElementPosition() + this._startPointerOffset + this._scrollOffset + this._topOffset
},
_toggleScroll: function(pointerPosition) {
if (this._scrollHeight <= this._clientHeight) {
return
}
var minOffset = .7 * this._elementHeight,
topOffset = this._clientHeight - (pointerPosition - this._scrollTop()),
topOffsetRatio = topOffset / minOffset,
bottomOffset = pointerPosition - this._scrollTop(),
bottomOffsetRatio = bottomOffset / minOffset;
if (topOffsetRatio < 1) {
this._stepSize = this._adjustRationIntoRange(topOffsetRatio);
this._startAnimator()
} else {
if (bottomOffsetRatio < 1) {
this._stepSize = -this._adjustRationIntoRange(bottomOffsetRatio);
this._startAnimator()
} else {
this._stopAnimator()
}
}
},
_adjustRationIntoRange: function(ratio) {
return mathUtils.fitIntoRange(Math.round(7 * Math.abs(ratio - 1)), 1, 7)
},
_updateItemPositions: function() {
this._updateGhostPosition();
this._updateOthersPositions()
},
_updateGhostPosition: function() {
if (!this._$ghostItem) {
return
}
translator.move(this._$ghostItem, {
top: this._startGhostPosition + this._scrollOffset + this._topOffset
})
},
_updateOthersPositions: function() {
var currentIndex = this._findItemIndexByPosition(this._getPointerPosition());
if (this._lastIndex === currentIndex || this._groupedEnabled && !this._sameParent(currentIndex)) {
return
}
var currentIndexOffset = currentIndex - this._startIndex,
currentDirection = mathUtils.sign(currentIndexOffset),
minIndex = Math.min(currentIndex, this._lastIndex),
maxIndex = Math.max(currentIndex, this._lastIndex);
for (var itemIndex = minIndex; itemIndex <= maxIndex; itemIndex++) {
if (itemIndex === this._startIndex) {
continue
}
var $item = this._list.getItemElementByFlatIndex(itemIndex),
itemIndexOffset = itemIndex - this._startIndex,
itemDirection = mathUtils.sign(itemIndexOffset),
offsetsDifference = Math.abs(itemIndexOffset) <= Math.abs(currentIndexOffset),
sameDirections = currentDirection === itemDirection,
setupPosition = offsetsDifference && sameDirections,
resetPosition = !offsetsDifference || !sameDirections;
fx.stop($item);
if (setupPosition) {
fx.animate($item, {
type: "slide",
to: {
top: this._elementHeight * -currentDirection
},
duration: 300
})
}
if (resetPosition) {
fx.animate($item, {
type: "slide",
to: {
top: 0
},
duration: 300
})
}
}
this._lastIndex = currentIndex
},
_sameParent: function(index) {
var $dragging = this._list.getItemElementByFlatIndex(this._startIndex),
$over = this._list.getItemElementByFlatIndex(index);
return $over.parent().get(0) === $dragging.parent().get(0)
},
scrollByStep: function() {
this._scrollOffset += this._stepSize;
this._list.scrollBy(this._stepSize);
this._updateItemPositions()
},
scrollFinished: function() {
var scrollTop = this._scrollTop(),
rejectScrollTop = scrollTop <= 0 && this._stepSize < 0,
rejectScrollBottom = scrollTop >= this._scrollHeight - this._clientHeight && this._stepSize > 0;
return rejectScrollTop || rejectScrollBottom
},
_dragEndHandler: function($itemElement) {
this._scrollAnimator.stop();
fx.animate(this._$ghostItem, {
type: "slide",
to: {
top: this._startGhostPosition + this._getLastElementPosition() - this._getDraggingElementPosition()
},
duration: 300
}).done(function() {
$itemElement.removeClass(REOREDERING_ITEM_CLASS);
this._resetPositions();
this._list.reorderItem($itemElement, this._list.getItemElementByFlatIndex(this._lastIndex));
this._deleteGhost()
}.bind(this))
},
_deleteGhost: function() {
if (!this._$ghostItem) {
return
}
this._$ghostItem.remove()
},
_resetPositions: function() {
var minIndex = Math.min(this._startIndex, this._lastIndex),
maxIndex = Math.max(this._startIndex, this._lastIndex);
for (var itemIndex = minIndex; itemIndex <= maxIndex; itemIndex++) {
var $item = this._list.getItemElementByFlatIndex(itemIndex);
translator.resetPosition($item)
}
},
_findItemIndexByPosition: function(position) {
var minIndex = 0;
var maxIndex = this._itemPositions.length - 1;
var currentIndex;
var currentPosition;
while (minIndex <= maxIndex) {
currentIndex = (minIndex + maxIndex) / 2 | 0;
currentPosition = this._itemPositions[currentIndex]();
if (currentPosition < position) {
minIndex = currentIndex + 1
} else {
if (currentPosition > position) {
maxIndex = currentIndex - 1
} else {
return currentIndex
}
}
}
return mathUtils.fitIntoRange(minIndex, 0, Math.max(maxIndex, 0))
},
getExcludedSelectors: function(selectors) {
selectors.push("." + REOREDERING_ITEM_GHOST_CLASS)
},
dispose: function() {
clearTimeout(this._createGhostTimeout);
this.callBase.apply(this, arguments)
}
}));