123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359 |
- /*
- This file is part of Ext JS 4
- Copyright (c) 2011 Sencha Inc
- Contact: http://www.sencha.com/contact
- GNU General Public License Usage
- This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
- If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
- */
- Ext.define('Ext.ux.BoxReorderer', {
- mixins: {
- observable: 'Ext.util.Observable'
- },
- /**
- * @cfg {String} itemSelector
- * <p>Optional. Defaults to <code>'.x-box-item'</code>
- * <p>A {@link Ext.DomQuery DomQuery} selector which identifies the encapsulating elements of child Components which participate in reordering.</p>
- */
- itemSelector: '.x-box-item',
- /**
- * @cfg {Mixed} animate
- * <p>Defaults to 300.</p>
- * <p>If truthy, child reordering is animated so that moved boxes slide smoothly into position.
- * If this option is numeric, it is used as the animation duration <b>in milliseconds</b>.</p>
- */
- animate: 100,
- constructor: function() {
- this.addEvents(
- /**
- * @event StartDrag
- * Fires when dragging of a child Component begins.
- * @param {BoxReorder} this
- * @param {Container} container The owning Container
- * @param {Component} dragCmp The Component being dragged
- * @param {Number} idx The start index of the Component being dragged.
- */
- 'StartDrag',
- /**
- * @event Drag
- * Fires during dragging of a child Component.
- * @param {BoxReorder} this
- * @param {Container} container The owning Container
- * @param {Component} dragCmp The Component being dragged
- * @param {Number} startIdx The index position from which the Component was initially dragged.
- * @param {Number} idx The current closest index to which the Component would drop.
- */
- 'Drag',
- /**
- * @event ChangeIndex
- * Fires when dragging of a child Component causes its drop index to change.
- * @param {BoxReorder} this
- * @param {Container} container The owning Container
- * @param {Component} dragCmp The Component being dragged
- * @param {Number} startIdx The index position from which the Component was initially dragged.
- * @param {Number} idx The current closest index to which the Component would drop.
- */
- 'ChangeIndex',
- /**
- * @event Drop
- * Fires when a child Component is dropped at a new index position.
- * @param {BoxReorder} this
- * @param {Container} container The owning Container
- * @param {Component} dragCmp The Component being dropped
- * @param {Number} startIdx The index position from which the Component was initially dragged.
- * @param {Number} idx The index at which the Component is being dropped.
- */
- 'Drop'
- );
- this.mixins.observable.constructor.apply(this, arguments);
- },
- init: function(container) {
- this.container = container;
- // Initialize the DD on first layout, when the innerCt has been created.
- this.container.afterLayout = Ext.Function.createSequence(this.container.afterLayout, this.afterFirstLayout, this);
- container.destroy = Ext.Function.createSequence(container.destroy, this.onContainerDestroy, this);
- },
- /**
- * @private Clear up on Container destroy
- */
- onContainerDestroy: function() {
- if (this.dd) {
- this.dd.unreg();
- }
- },
- afterFirstLayout: function() {
- var me = this,
- l = me.container.getLayout();
- // delete the sequence
- delete me.container.afterLayout;
- // Create a DD instance. Poke the handlers in.
- // TODO: Ext5's DD classes should apply config to themselves.
- // TODO: Ext5's DD classes should not use init internally because it collides with use as a plugin
- // TODO: Ext5's DD classes should be Observable.
- // TODO: When all the above are trus, this plugin should extend the DD class.
- me.dd = Ext.create('Ext.dd.DD', l.innerCt, me.container.id + '-reorderer');
- Ext.apply(me.dd, {
- animate: me.animate,
- reorderer: me,
- container: me.container,
- getDragCmp: this.getDragCmp,
- clickValidator: Ext.Function.createInterceptor(me.dd.clickValidator, me.clickValidator, me, false),
- onMouseDown: me.onMouseDown,
- startDrag: me.startDrag,
- onDrag: me.onDrag,
- endDrag: me.endDrag,
- getNewIndex: me.getNewIndex,
- doSwap: me.doSwap,
- findReorderable: me.findReorderable
- });
- // Decide which dimension we are measuring, and which measurement metric defines
- // the *start* of the box depending upon orientation.
- me.dd.dim = l.parallelPrefix;
- me.dd.startAttr = l.parallelBefore;
- me.dd.endAttr = l.parallelAfter;
- },
- getDragCmp: function(e) {
- return this.container.getChildByElement(e.getTarget(this.itemSelector, 10));
- },
- // check if the clicked component is reorderable
- clickValidator: function(e) {
- var cmp = this.getDragCmp(e);
- // If cmp is null, this expression MUST be coerced to boolean so that createInterceptor is able to test it against false
- return !!(cmp && cmp.reorderable !== false);
- },
- onMouseDown: function(e) {
- var me = this,
- container = me.container,
- containerBox,
- cmpEl,
- cmpBox;
- // Ascertain which child Component is being mousedowned
- me.dragCmp = me.getDragCmp(e);
- if (me.dragCmp) {
- cmpEl = me.dragCmp.getEl();
- me.startIndex = me.curIndex = container.items.indexOf(me.dragCmp);
- // Start position of dragged Component
- cmpBox = cmpEl.getPageBox();
- // Last tracked start position
- me.lastPos = cmpBox[this.startAttr];
- // Calculate constraints depending upon orientation
- // Calculate offset from mouse to dragEl position
- containerBox = container.el.getPageBox();
- if (me.dim === 'width') {
- me.minX = containerBox.left;
- me.maxX = containerBox.right - cmpBox.width;
- me.minY = me.maxY = cmpBox.top;
- me.deltaX = e.getPageX() - cmpBox.left;
- } else {
- me.minY = containerBox.top;
- me.maxY = containerBox.bottom - cmpBox.height;
- me.minX = me.maxX = cmpBox.left;
- me.deltaY = e.getPageY() - cmpBox.top;
- }
- me.constrainY = me.constrainX = true;
- }
- },
- startDrag: function() {
- var me = this;
- if (me.dragCmp) {
- // For the entire duration of dragging the *Element*, defeat any positioning of the dragged *Component*
- me.dragCmp.setPosition = Ext.emptyFn;
- // If the BoxLayout is not animated, animate it just for the duration of the drag operation.
- if (!me.container.layout.animate && me.animate) {
- me.container.layout.animate = me.animate;
- me.removeAnimate = true;
- }
- // We drag the Component element
- me.dragElId = me.dragCmp.getEl().id;
- me.reorderer.fireEvent('StartDrag', me, me.container, me.dragCmp, me.curIndex);
- // Suspend events, and set the disabled flag so that the mousedown and mouseup events
- // that are going to take place do not cause any other UI interaction.
- me.dragCmp.suspendEvents();
- me.dragCmp.disabled = true;
- me.dragCmp.el.setStyle('zIndex', 100);
- } else {
- me.dragElId = null;
- }
- },
- /**
- * @private
- * Find next or previous reorderable component index.
- * @param {Number} newIndex The initial drop index.
- * @return {Number} The index of the reorderable component.
- */
- findReorderable: function(newIndex) {
- var me = this,
- items = me.container.items,
- newItem;
- if (items.getAt(newIndex).reorderable === false) {
- newItem = items.getAt(newIndex);
- if (newIndex > me.startIndex) {
- while(newItem && newItem.reorderable === false) {
- newIndex++;
- newItem = items.getAt(newIndex);
- }
- } else {
- while(newItem && newItem.reorderable === false) {
- newIndex--;
- newItem = items.getAt(newIndex);
- }
- }
- }
- newIndex = Math.min(Math.max(newIndex, 0), items.getCount() - 1);
- if (items.getAt(newIndex).reorderable === false) {
- return -1;
- }
- return newIndex;
- },
- /**
- * @private
- * Swap 2 components.
- * @param {Number} newIndex The initial drop index.
- */
- doSwap: function(newIndex) {
- var me = this,
- items = me.container.items,
- orig, dest, tmpIndex;
- newIndex = me.findReorderable(newIndex);
- if (newIndex === -1) {
- return;
- }
- me.reorderer.fireEvent('ChangeIndex', me, me.container, me.dragCmp, me.startIndex, newIndex);
- orig = items.getAt(me.curIndex);
- dest = items.getAt(newIndex);
- items.remove(orig);
- tmpIndex = Math.min(Math.max(newIndex, 0), items.getCount() - 1);
- items.insert(tmpIndex, orig);
- items.remove(dest);
- items.insert(me.curIndex, dest);
- me.container.layout.layout();
- me.curIndex = newIndex;
- },
- onDrag: function(e) {
- var me = this,
- newIndex;
- newIndex = me.getNewIndex(e.getPoint());
- if ((newIndex !== undefined)) {
- me.reorderer.fireEvent('Drag', me, me.container, me.dragCmp, me.startIndex, me.curIndex);
- me.doSwap(newIndex);
- }
- },
- endDrag: function(e) {
- e.stopEvent();
- var me = this;
- if (me.dragCmp) {
- delete me.dragElId;
- if (me.animate) {
- me.container.layout.animate = {
- // Call afterBoxReflow after the animation finishes.
- callback: Ext.Function.bind(me.reorderer.afterBoxReflow, me)
- };
- }
- // Reinstate the Component's positioning method after mouseup.
- // Call the layout directly: Bypass the layoutBusy barrier
- delete me.dragCmp.setPosition;
- me.container.layout.layout();
- if (me.removeAnimate) {
- delete me.removeAnimate;
- delete me.container.layout.animate;
- } else {
- me.reorderer.afterBoxReflow.call(me);
- }
- me.reorderer.fireEvent('drop', me, me.container, me.dragCmp, me.startIndex, me.curIndex);
- }
- },
- /**
- * @private
- * Called after the boxes have been reflowed after the drop.
- */
- afterBoxReflow: function() {
- var me = this;
- me.dragCmp.el.setStyle('zIndex', '');
- me.dragCmp.disabled = false;
- me.dragCmp.resumeEvents();
- },
- /**
- * @private
- * Calculate drop index based upon the dragEl's position.
- */
- getNewIndex: function(pointerPos) {
- var me = this,
- dragEl = me.getDragEl(),
- dragBox = Ext.fly(dragEl).getPageBox(),
- targetEl,
- targetBox,
- targetMidpoint,
- i = 0,
- it = me.container.items.items,
- ln = it.length,
- lastPos = me.lastPos;
- me.lastPos = dragBox[me.startAttr];
- for (; i < ln; i++) {
- targetEl = it[i].getEl();
- // Only look for a drop point if this found item is an item according to our selector
- if (targetEl.is(me.reorderer.itemSelector)) {
- targetBox = targetEl.getPageBox();
- targetMidpoint = targetBox[me.startAttr] + (targetBox[me.dim] >> 1);
- if (i < me.curIndex) {
- if ((dragBox[me.startAttr] < lastPos) && (dragBox[me.startAttr] < (targetMidpoint - 5))) {
- return i;
- }
- } else if (i > me.curIndex) {
- if ((dragBox[me.startAttr] > lastPos) && (dragBox[me.endAttr] > (targetMidpoint + 5))) {
- return i;
- }
- }
- }
- }
- }
- });
|