zoukankan      html  css  js  c++  java
  • ExtJS 5.1 TabReorderer plugin

    Description

    在 ExtJS 5.1 中使用 tabpanel,要求每个 tab 可以拖动,顺序可以改变。tabpanel 默认是不支持的,不过可以加上一个 plugin - Ext.ux.TabReorderer。在 ExtJS官网(http://docs.sencha.com/extjs/5.1/5.1.0-apidocs/)搜索 TabReorderer 可以查看其源码,发现其继承自 Ext.ux.BoxReorderer, 把两个源码 js 都放到项目中去。见附录。

    Solution

    先把把'Ext.ux.TabReorderer' require 进来,在创建 tabpanel 的时候 加上 plugins: Ext.create('Ext.ux.TabReorderer'), 

     1 Ext.define('MyApp.view.CenterTabs', {
     2         extend: 'Ext.tab.Panel',
     3         plugins: Ext.create('Ext.ux.TabReorderer'),
     4         items: [{
     5             xtype: 'panel',
     6             title: 'Tab 1',
     7             html : 'Test 1',
     8             closable: true
     9         }, {
    10             xtype: 'panel',
    11             title: 'Tab 2',
    12             html : 'Test 2',
    13             closable: true
    14         },{
    15             xtype: 'panel',
    16             title: 'Tab 3',
    17             html : 'Test 3',
    18             closable: true
    19         },{
    20             xtype: 'panel',
    21             title: 'Tab 4',
    22             html : 'Test 4',
    23             closable: true
    24         }]
    25     });

    Reference

    • https://www.sencha.com/forum/showthread.php?211867-Using-TabReorderer-as-a-plugin
    • http://docs.sencha.com/extjs/5.1/5.1.0-apidocs/source/TabReorderer.html#Ext-ux-TabReorderer
    • http://docs.sencha.com/extjs/5.1/5.1.0-apidocs/source/BoxReorderer.html#Ext-ux-BoxReorderer

    Appendix

    /**
     * Base class from Ext.ux.TabReorderer.
     */
    Ext.define('Ext.ux.BoxReorderer', {
        requires: [
            'Ext.dd.DD'
        ],
    
        mixins: {
            observable: 'Ext.util.Observable'
        },
    
        /**
         * @cfg {String} itemSelector
         * A {@link Ext.DomQuery DomQuery} selector which identifies the encapsulating elements of child
         * Components which participate in reordering.
         */
        itemSelector: '.x-box-item',
    
        /**
         * @cfg {Mixed} animate
         * 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 in milliseconds.
         */
        animate: 100,
    
        /**
         * @event StartDrag
         * Fires when dragging of a child Component begins.
         * @param {Ext.ux.BoxReorderer} this
         * @param {Ext.container.Container} container The owning Container
         * @param {Ext.Component} dragCmp The Component being dragged
         * @param {Number} idx The start index of the Component being dragged.
         */
    
        /**
         * @event Drag
         * Fires during dragging of a child Component.
         * @param {Ext.ux.BoxReorderer} this
         * @param {Ext.container.Container} container The owning Container
         * @param {Ext.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.
         */
    
        /**
         * @event ChangeIndex
         * Fires when dragging of a child Component causes its drop index to change.
         * @param {Ext.ux.BoxReorderer} this
         * @param {Ext.container.Container} container The owning Container
         * @param {Ext.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.
         */
    
        /**
         * @event Drop
         * Fires when a child Component is dropped at a new index position.
         * @param {Ext.ux.BoxReorderer} this
         * @param {Ext.container.Container} container The owning Container
         * @param {Ext.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.
         */
    
        constructor: function() {
            this.mixins.observable.constructor.apply(this, arguments);
        },
    
        init: function(container) {
            var me = this;
    
            me.container = container;
    
            // Set our animatePolicy to animate the start position (ie x for HBox, y for VBox)
            me.animatePolicy = {};
            me.animatePolicy[container.getLayout().names.x] = true;
    
    
    
            // Initialize the DD on first layout, when the innerCt has been created.
            me.container.on({
                scope: me,
                boxready: me.onBoxReady,
                beforedestroy: me.onContainerDestroy
            });
        },
    
        /**
         * @private Clear up on Container destroy
         */
        onContainerDestroy: function() {
            var dd = this.dd;
            if (dd) {
                dd.unreg();
                this.dd = null;
            }
        },
    
        onBoxReady: function() {
            var me = this,
                layout = me.container.getLayout(),
                names = layout.names,
                dd;
    
            // 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.
            dd = me.dd = new Ext.dd.DD(layout.innerCt, me.container.id + '-reorderer');
            Ext.apply(dd, {
                animate: me.animate,
                reorderer: me,
                container: me.container,
                getDragCmp: me.getDragCmp,
                clickValidator: Ext.Function.createInterceptor(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.
            dd.dim = names.width;
            dd.startAttr = names.beforeX;
            dd.endAttr = names.afterX;
        },
    
        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.getBox();
    
                // Last tracked start position
                me.lastPos = cmpBox[me.startAttr];
    
                // Calculate constraints depending upon orientation
                // Calculate offset from mouse to dragEl position
                containerBox = container.el.getBox();
                if (me.dim === 'width') {
                    me.minX = containerBox.left;
                    me.maxX = containerBox.right - cmpBox.width;
                    me.minY = me.maxY = cmpBox.top;
                    me.deltaX = e.getX() - cmpBox.left;
                } else {
                    me.minY = containerBox.top;
                    me.maxY = containerBox.bottom - cmpBox.height;
                    me.minX = me.maxX = cmpBox.left;
                    me.deltaY = e.getY() - cmpBox.top;
                }
                me.constrainY = me.constrainX = true;
            }
        },
    
        startDrag: function() {
            var me = this,
                dragCmp = me.dragCmp;
    
            if (dragCmp) {
                // For the entire duration of dragging the *Element*, defeat any positioning and animation of the dragged *Component*
                dragCmp.setPosition = Ext.emptyFn;
                dragCmp.animate = false;
    
                // Animate the BoxLayout just for the duration of the drag operation.
                if (me.animate) {
                    me.container.getLayout().animatePolicy = me.reorderer.animatePolicy;
                }
                // We drag the Component element
                me.dragElId = dragCmp.getEl().id;
                me.reorderer.fireEvent('StartDrag', me, me.container, 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.
                dragCmp.suspendEvents();
                dragCmp.disabled = true;
                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,
                container = me.container,
                wasRoot = me.container._isLayoutRoot,
                orig, dest, tmpIndex;
    
            newIndex = me.findReorderable(newIndex);
    
            if (newIndex === -1) {
                return;
            }
    
            me.reorderer.fireEvent('ChangeIndex', 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);
    
            // Make the Box Container the topmost layout participant during the layout.
            container._isLayoutRoot = true;
            container.updateLayout();
            container._isLayoutRoot = wasRoot;
            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) {
            if (e) {
                e.stopEvent();
            }
            var me = this,
                layout = me.container.getLayout(),
                temp;
    
            if (me.dragCmp) {
                delete me.dragElId;
    
                // Reinstate the Component's positioning method after mouseup, and allow the layout system to animate it.
                delete me.dragCmp.setPosition;
                me.dragCmp.animate = true;
    
                // Ensure the lastBox is correct for the animation system to restore to when it creates the "from" animation frame
                me.dragCmp.lastBox[layout.names.x] = me.dragCmp.getPosition(true)[layout.names.widthIndex];
    
                // Make the Box Container the topmost layout participant during the layout.
                me.container._isLayoutRoot = true;
                me.container.updateLayout();
                me.container._isLayoutRoot = undefined;
    
                // Attempt to hook into the afteranimate event of the drag Component to call the cleanup
                temp = Ext.fx.Manager.getFxQueue(me.dragCmp.el.id)[0];
                if (temp) {
                    temp.on({
                        afteranimate: me.reorderer.afterBoxReflow,
                        scope: me
                    });
                }
                // If not animated, clean up after the mouseup has happened so that we don't click the thing being dragged
                else {
                    Ext.Function.defer(me.reorderer.afterBoxReflow, 1, me);
                }
    
                if (me.animate) {
                    delete layout.animatePolicy;
                }
                me.reorderer.fireEvent('drop', me, me.container, me.dragCmp, me.startIndex, me.curIndex);
            }
        },
    
        /**
         * @private
         * Called after the boxes have been reflowed after the drop.
         * Re-enabled the dragged Component.
         */
        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).getBox(),
                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.getBox();
                    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;
                        }
                    }
                }
            }
        }
    });
    /**
     * This plugin allow you to reorder tabs of a TabPanel.
     */
    Ext.define('Ext.ux.TabReorderer', {
    
        extend: 'Ext.ux.BoxReorderer',
        alias: 'plugin.tabreorderer',
    
        itemSelector: '.' + Ext.baseCSSPrefix + 'tab',
    
        init: function(tabPanel) {
            var me = this;
            
            me.callParent([tabPanel.getTabBar()]);
    
            // Ensure reorderable property is copied into dynamically added tabs
            tabPanel.onAdd = Ext.Function.createSequence(tabPanel.onAdd, me.onAdd);
        },
    
        onBoxReady: function() {
            var tabs,
                len,
                i = 0,
                tab;
    
            this.callParent(arguments);
    
            // Copy reorderable property from card into tab
            for (tabs = this.container.items.items, len = tabs.length; i < len; i++) {
                tab = tabs[i];
                if (tab.card) {
                    tab.reorderable = tab.card.reorderable;
                }
            }
        },
    
        onAdd: function(card, index) {
            card.tab.reorderable = card.reorderable;
        },
    
        afterBoxReflow: function() {
            var me = this;
    
            // Cannot use callParent, this is not called in the scope of this plugin, but that of its Ext.dd.DD object
            Ext.ux.BoxReorderer.prototype.afterBoxReflow.apply(me, arguments);
    
            // Move the associated card to match the tab order
            if (me.dragCmp) {
                me.container.tabPanel.setActiveTab(me.dragCmp.card);
                me.container.tabPanel.move(me.startIndex, me.curIndex);
            }
        }
    });
  • 相关阅读:
    【转-整理】win764bit plsql 登录oracle11g ora-12154 问题汇总
    【转-整理】log4j 简单解释,配置
    sparsity and density
    转:Recsys2013论文导读
    学院研究生论坛-如何做研究
    推荐系统开源软件列表
    linux下如何用GDB调试c++程序
    全国大学生数据挖掘邀请赛中的NDCG
    网络科学自学资料
    科普文:从人人网看网络科学(Network Science)的X个经典问题
  • 原文地址:https://www.cnblogs.com/yuxiaoqi/p/5124344.html
Copyright © 2011-2022 走看看