1. #1
    Sencha User Lobos's Avatar
    Join Date
    Oct 2007
    Location
    Sao Paulo, Brazil
    Posts
    461
    Vote Rating
    -1
    Lobos is an unknown quantity at this point

      0  

    Thumbs up [SOLVED 2.x, 3.x]Grid Drag and Drop reorder rows

    [SOLVED 2.x, 3.x]Grid Drag and Drop reorder rows


    As of September 6th 2009 here is the latest working version - I confirm that it works as I just used it!

    I did not write this plugin, it is the product of other more brighter buttons than me! Looking thru this thread you will see the ones that contributed!

    Quote Originally Posted by dongryphon View Post
    I converted the code into a true plugin, trying to preserve everything as it was. Some small changes were made:
    • I renamed the class to "GridDragDropRowOrder" because it "has a" DropTarget and isn't one itself.
    • I separated the config so the class would behave like other objects: the config constructor parameter applies properties to that instance. There is now a "targetCfg" property for properties that should be applied to the DropTarget.
    • There is also a "scrollable" property which defaults to false to control whether or not to do the ScrollManager stuff. If enabled, the necessary cleanup will also be done.
    The usage for the true plugin version is:
    Change Log

    sep 6, 2009 - added jamiro's fix for events not firing

    Example:
    Code:
    {
        xtype: "grid",
        plugins: [new Ext.ux.dd.GridDragDropRowOrder(
        {
            copy: true // false by default
            scrollable: true, // enable scrolling support (default is false)
            targetCfg: { ... } // any properties to apply to the actual DropTarget
        })]
    }
    Plugin:
    Code:
    Ext.namespace('Ext.ux.dd');
    
    Ext.ux.dd.GridDragDropRowOrder = Ext.extend(Ext.util.Observable,
    {
        copy: false,
    
        scrollable: false,
    
        constructor : function(config)
        {
            if (config)
                Ext.apply(this, config);
    
            this.addEvents(
            {
                beforerowmove: true,
                afterrowmove: true,
                beforerowcopy: true,
                afterrowcopy: true
            });
    
           Ext.ux.dd.GridDragDropRowOrder.superclass.constructor.call(this);
        },
    
        init : function (grid)
        {
            this.grid = grid;
            grid.enableDragDrop = true;
    
            grid.on({
                render: { fn: this.onGridRender, scope: this, single: true }
            });
        },
    
        onGridRender : function (grid)
        {
            var self = this;
    
            this.target = new Ext.dd.DropTarget(grid.getEl(),
            {
                ddGroup: grid.ddGroup || 'GridDD',
                grid: grid,
                gridDropTarget: this,
    
                notifyDrop: function(dd, e, data)
                {
                    // Remove drag lines. The 'if' condition prevents null error when drop occurs without dragging out of the selection area
                    if (this.currentRowEl)
                    {
                        this.currentRowEl.removeClass('grid-row-insert-below');
                        this.currentRowEl.removeClass('grid-row-insert-above');
                    }
    
                    // determine the row
                    var t = Ext.lib.Event.getTarget(e);
                    var rindex = this.grid.getView().findRowIndex(t);
                    if (rindex === false || rindex == data.rowIndex)
                    {
                        return false;
                    }
                    // fire the before move/copy event
                    if (this.gridDropTarget.fireEvent(self.copy ? 'beforerowcopy' : 'beforerowmove', this.gridDropTarget, data.rowIndex, rindex, data.selections, 123) === false)
                    {
                        return false;
                    }
    
                    // update the store
                    var ds = this.grid.getStore();
    
                    // Changes for multiselction by Spirit
                    var selections = new Array();
                    var keys = ds.data.keys;
                    for (var key in keys)
                    {
                        for (var i = 0; i < data.selections.length; i++)
                        {
                            if (keys[key] == data.selections[i].id)
                            {
                                // Exit to prevent drop of selected records on itself.
                                if (rindex == key)
                                {
                                    return false;
                                }
                                selections.push(data.selections[i]);
                            }
                        }
                    }
    
                    // fix rowindex based on before/after move
                    if (rindex > data.rowIndex && this.rowPosition < 0)
                    {
                        rindex--;
                    }
                    if (rindex < data.rowIndex && this.rowPosition > 0)
                    {
                        rindex++;
                    }
    
                    // fix rowindex for multiselection
                    if (rindex > data.rowIndex && data.selections.length > 1)
                    {
                        rindex = rindex - (data.selections.length - 1);
                    }
    
                    // we tried to move this node before the next sibling, we stay in place
                    if (rindex == data.rowIndex)
                    {
                        return false;
                    }
    
                    // fire the before move/copy event
                    /* dupe - does it belong here or above???
                    if (this.gridDropTarget.fireEvent(self.copy ? 'beforerowcopy' : 'beforerowmove', this.gridDropTarget, data.rowIndex, rindex, data.selections, 123) === false)
                    {
                        return false;
                    }
                    */
    
                    if (!self.copy)
                    {
                        for (var i = 0; i < data.selections.length; i++)
                        {
                            ds.remove(ds.getById(data.selections[i].id));
                        }
                    }
    
                    for (var i = selections.length - 1; i >= 0; i--)
                    {
                        var insertIndex = rindex;
                        ds.insert(insertIndex, selections[i]);
                    }
    
                    // re-select the row(s)
                    var sm = this.grid.getSelectionModel();
                    if (sm)
                    {
                        sm.selectRecords(data.selections);
                    }
    
                    // fire the after move/copy event
                    this.gridDropTarget.fireEvent(self.copy ? 'afterrowcopy' : 'afterrowmove', this.gridDropTarget, data.rowIndex, rindex, data.selections);
                    return true;
                },
    
                notifyOver: function(dd, e, data)
                {
                    var t = Ext.lib.Event.getTarget(e);
                    var rindex = this.grid.getView().findRowIndex(t);
    
                    // Similar to the code in notifyDrop. Filters for selected rows and quits function if any one row matches the current selected row.
                    var ds = this.grid.getStore();
                    var keys = ds.data.keys;
                    for (var key in keys)
                    {
                        for (var i = 0; i < data.selections.length; i++)
                        {
                            if (keys[key] == data.selections[i].id)
                            {
                                if (rindex == key)
                                {
                                    if (this.currentRowEl)
                                    {
                                        this.currentRowEl.removeClass('grid-row-insert-below');
                                        this.currentRowEl.removeClass('grid-row-insert-above');
                                    }
                                    return this.dropNotAllowed;
                                }
                            }
                        }
                    }
    
                    // If on first row, remove upper line. Prevents negative index error as a result of rindex going negative.
                    if (rindex < 0 || rindex === false)
                    {
                        this.currentRowEl.removeClass('grid-row-insert-above');
                        return this.dropNotAllowed;
                    }
    
                    try
                    {
                        var currentRow = this.grid.getView().getRow(rindex);
                        // Find position of row relative to page (adjusting for grid's scroll position)
                        var resolvedRow = new Ext.Element(currentRow).getY() - this.grid.getView().scroller.dom.scrollTop;
                        var rowHeight = currentRow.offsetHeight;
    
                        // Cursor relative to a row. -ve value implies cursor is above the row's middle and +ve value implues cursor is below the row's middle.
                        this.rowPosition = e.getPageY() - resolvedRow - (rowHeight/2);
    
                        // Clear drag line.
                        if (this.currentRowEl)
                        {
                            this.currentRowEl.removeClass('grid-row-insert-below');
                            this.currentRowEl.removeClass('grid-row-insert-above');
                        }
    
                        if (this.rowPosition > 0)
                        {
                            // If the pointer is on the bottom half of the row.
                            this.currentRowEl = new Ext.Element(currentRow);
                            this.currentRowEl.addClass('grid-row-insert-below');
                        }
                        else
                        {
                            // If the pointer is on the top half of the row.
                            if (rindex - 1 >= 0)
                            {
                                var previousRow = this.grid.getView().getRow(rindex - 1);
                                this.currentRowEl = new Ext.Element(previousRow);
                                this.currentRowEl.addClass('grid-row-insert-below');
                            }
                            else
                            {
                                // If the pointer is on the top half of the first row.
                                this.currentRowEl.addClass('grid-row-insert-above');
                            }
                        }
                    }
                    catch (err)
                    {
                        console.warn(err);
                        rindex = false;
                    }
                    return (rindex === false)? this.dropNotAllowed : this.dropAllowed;
                },
    
                notifyOut: function(dd, e, data)
                {
                    // Remove drag lines when pointer leaves the gridView.
                    if (this.currentRowEl)
                    {
                        this.currentRowEl.removeClass('grid-row-insert-above');
                        this.currentRowEl.removeClass('grid-row-insert-below');
                    }
                }
            });
    
            if (this.targetCfg)
            {
                Ext.apply(this.target, this.targetCfg);
            }
    
            if (this.scrollable)
            {
                Ext.dd.ScrollManager.register(grid.getView().getEditorParent());
                grid.on({
                    beforedestroy: this.onBeforeDestroy,
                    scope: this,
                    single: true
                });
            }
        },
    
        getTarget: function()
        {
            return this.target;
        },
    
        getGrid: function()
        {
            return this.grid;
        },
    
        getCopy: function()
        {
            return this.copy ? true : false;
        },
    
        setCopy: function(b)
        {
            this.copy = b ? true : false;
        },
    
        onBeforeDestroy : function (grid)
        {
            // if we previously registered with the scroll manager, unregister
            // it (if we don't it will lead to problems in IE)
            Ext.dd.ScrollManager.unregister(grid.getView().getEditorParent());
        }
    });
    php example of reordering on the server side - you can think of "$views" as rows. Main thing to take note of how we handle things a bit differently when moving something up or down. ['n_order'] is the original order taken from the db or whereever

    PHP Code:
    foreach($views as $k=>$view){
                
                
    //moving up in order
                
    if($oldIndex $newIndex){                
                    
    //this is the one we are changing so we 
                    //just give it the new index
                    
    if($view['n_order'] == $oldIndex){
                        
    $views[$k]['n_order'] = $newIndex;
                    
    //if the order is greater that or equal to the new index it will
                    //need to be incremented to make room for the change
                    
    } else if($view['n_order'] >= $newIndex && $view['n_order'] < $oldIndex){
                        
    $views[$k]['n_order'] = $view['n_order']+1;
                    } 
                
    //moving down in order    - reverse the above process
                
    } else {
                    if(
    $view['n_order'] == $oldIndex){
                        
    $views[$k]['n_order'] = $newIndex;
        
                    } else if(
    $view['n_order'] <= $newIndex && $view['n_order'] > $oldIndex){
                        
    $views[$k]['n_order'] = $view['n_order']-1;
                    } 
                }
            } 
    Last edited by Lobos; 6 Sep 2009 at 12:37 PM. Reason: Fixed events not firing issue

  2. #2
    Sencha User Lobos's Avatar
    Join Date
    Oct 2007
    Location
    Sao Paulo, Brazil
    Posts
    461
    Vote Rating
    -1
    Lobos is an unknown quantity at this point

      0  

    Default


    PHP Code:
    Ext.BLANK_IMAGE_URL "js/{.$extjs_version.}/resources/images/default/s.gif";    
            
            
    Ext.onReady(function(){
                
    Ext.QuickTips.init();
            
                function 
    formatDate(value){
                    return 
    value value.dateFormat('M d, Y') : '';
                };
                
    // shorthand alias
                
    var fm Ext.form;
            
                
    // custom column plugin example
                
    var checkColumn = new Ext.grid.CheckColumn({
                   
    header"Indoor?",
                   
    dataIndex'indoor',
                   
    width55
                
    });
            
                
    // the column model has information about grid columns
                // dataIndex maps the column to the specific data field in
                // the data store (created below)
                
    var cm = new Ext.grid.ColumnModel([{
                       
    id:'common',
                       
    header"Common Name",
                       
    dataIndex'common',
                       
    width220,
                       
    editor: new fm.TextField({
                           
    allowBlankfalse
                       
    })
                    },{
                       
    header"Light",
                       
    dataIndex'light',
                       
    width130,
                       
    editor: new Ext.form.ComboBox({
                           
    typeAheadtrue,
                           
    triggerAction'all',
                           
    transform:'light',
                           
    lazyRender:true,
                           
    listClass'x-combo-list-small'
                        
    })
                    },{
                       
    header"Price",
                       
    dataIndex'price',
                       
    width70,
                       
    align'right',
                       
    renderer'usMoney',
                       
    editor: new fm.NumberField({
                           
    allowBlankfalse,
                           
    allowNegativefalse,
                           
    maxValue100000
                       
    })
                    },{
                       
    header"Available",
                       
    dataIndex'availDate',
                       
    width95,
                       
    rendererformatDate,
                       
    editor: new fm.DateField({
                            
    format'm/d/y',
                            
    minValue'01/01/06',
                            
    disabledDays: [06],
                            
    disabledDaysText'Plants are not available on the weekends'
                        
    })
                    },
                    
    checkColumn
                
    ]);
            
                
    // by default columns are sortable
                
    cm.defaultSortable true;
            
                
    // this could be inline, but we want to define the Plant record
                // type so we can add records dynamically
                
    var Plant Ext.data.Record.create([
                       
    // the "name" below matches the tag name to read, except "availDate"
                       // which is mapped to the tag "availability"
                       
    {name'common'type'string'},
                       {
    name'botanical'type'string'},
                       {
    name'light'},
                       {
    name'price'type'float'},             // automatic date conversions
                       
    {name'availDate'mapping'availability'type'date'dateFormat'm/d/Y'},
                       {
    name'indoor'type'bool'}
                  ]);
            
                
    // create the Data Store
                
    var store = new Ext.data.Store({
                    
    // load using HTTP
                    
    url'http://localhost/framework/js/ext-2.0/examples/grid/plants.xml',
            
                    
    // the return will be XML, so lets set up a reader
                    
    reader: new Ext.data.XmlReader({
                           
    // records will have a "plant" tag
                           
    record'plant'
                       
    }, Plant),
            
                    
    sortInfo:{field:'common'direction:'ASC'}
                });
            
                
    // create the editor grid
                
    var grid = new Ext.grid.EditorGridPanel({
                    
    storestore,
                    
    cmcm,
                    
    renderTo'editor-grid',
                    
    width:600,
                    
    height:300,
                    
    autoExpandColumn:'common',
                    
    title:'Edit Plants?',
                    
    frame:true,
                    
    plugins:checkColumn,
                    
    clicksToEdit:1,
            
    ddGroup'GridDD',
                    
    ddText'drag and drop to change order'
                    
    tbar: [{
                        
    text'Add Plant',
                        
    handler : function(){
                            var 
    = new Plant({
                                
    common'New Plant 1',
                                
    light'Mostly Shade',
                                
    price0,
                                
    availDate: (new Date()).clearTime(),
                                
    indoorfalse
                            
    });
                            
    grid.stopEditing();
                            
    store.insert(0p);
                            
    grid.startEditing(00);
                        }
                    }]
                });
            
                
    // trigger the data store load
                
    store.load();
            });
            
            
    Ext.grid.CheckColumn = function(config){
                
    Ext.apply(thisconfig);
                if(!
    this.id){
                    
    this.id Ext.id();
                }
                
    this.renderer this.renderer.createDelegate(this);
            };
            
            
    Ext.grid.CheckColumn.prototype ={
                
    init : function(grid){
                    
    this.grid grid;
                    
    this.grid.on('render', function(){
                        var 
    view this.grid.getView();
                        
    view.mainBody.on('mousedown'this.onMouseDownthis);
                    }, 
    this);
                },
            
                
    onMouseDown : function(et){
                    if(
    t.className && t.className.indexOf('x-grid3-cc-'+this.id) != -1){
                        
    e.stopEvent();
                        var 
    index this.grid.getView().findRowIndex(t);
                        var 
    record this.grid.store.getAt(index);
                        
    record.set(this.dataIndex, !record.data[this.dataIndex]);
                    }
                },
            
                
    renderer : function(vprecord){
                    
    p.css += ' x-grid3-check-col-td'
                    return 
    '<div class="x-grid3-check-col'+(v?'-on':'')+' x-grid3-cc-'+this.id+'"> </div>';
                }
            };
            
             var 
    ddrow = new Ext.dd.DropTarget(grid.getView.mainBody, {
                    
    ddGroup 'GridDD',
                    
    copy:false,
                    
    notifyDrop : function(ddedata){
                        var 
    sm=grid.getSelectionModel();
                        var 
    rows=sm.getSelections();
                
                        var 
    cindex=dd.getDragData(e).rowIndex;
                        for (
    0rows.lengthi++) {
                            
    rowData=ds.getById(rows[i].id);
                            if(!
    this.copy) {
                                
    ds.remove(ds.getById(rows[i].id));
                                
    ds.insert(cindex,rowData);
                            }
                        };
                    }
                }); 
    notice the code at the bottom, I got this from another post, but I am not sure if I am moving in the right direction:

    PHP Code:
    var ddrow = new Ext.dd.DropTarget(grid.getView.mainBody, {
                    
    ddGroup 'GridDD',
                    
    copy:false,
                    
    notifyDrop : function(ddedata){
                        var 
    sm=grid.getSelectionModel();
                        var 
    rows=sm.getSelections();
                
                        var 
    cindex=dd.getDragData(e).rowIndex;
                        for (
    0rows.lengthi++) {
                            
    rowData=ds.getById(rows[i].id);
                            if(!
    this.copy) {
                                
    ds.remove(ds.getById(rows[i].id));
                                
    ds.insert(cindex,rowData);
                            }
                        };
                    }
                }); 
    But unfortunately it is not working for me... the columns drag and drop reorder, but I can't get the rows to do this The above is based on the "editior grid" standard example btw. Has anyone got this type of thing working before?

    Would it be better for me to use some kind of implementation of the tree instead?

    Thanks.

    -Lobos

  3. #3

    Default


    Yeah, I'd love to see an example too.

  4. #4

  5. #5
    Ext User
    Join Date
    Dec 2007
    Posts
    214
    Vote Rating
    0
    Radziu is an unknown quantity at this point

      0  

    Default


    and I'd love to see. i have this problem too

  6. #6
    Sencha User Lobos's Avatar
    Join Date
    Oct 2007
    Location
    Sao Paulo, Brazil
    Posts
    461
    Vote Rating
    -1
    Lobos is an unknown quantity at this point

      0  

    Default


    Seems everyone wants to know how to drag and drop reorder grid rows! Like I said before I would just like to know at this stage if it is possible, ie to see an example of this working... from this I am sure I can back engineer the code, but... I first I need to see it!

    http://extjs.com/deploy/dev/examples/grid/grouping.html

    This would be perfect as an example, ie being able to drag rows into different groups!

    -Lobos

  7. #7
    Ext User
    Join Date
    Dec 2007
    Posts
    62
    Vote Rating
    0
    colinexl is on a distinguished road

      0  

    Default


    I'm not sure drag and drop a row to different groups would make sense. For example, since the grid is grouping by industries, if I drag Intel from the computer industry into the food industry with McDonalds, does that make any sense? Probably not.

    But I understand and agree with you about someone building a working example of a simple grid drag and drop reordering.


    --Colin

  8. #8
    Sencha User
    Join Date
    Oct 2007
    Location
    Berlin, Germany
    Posts
    888
    Vote Rating
    9
    wm003 will become famous soon enough

      0  

    Default


    Quote Originally Posted by Lobos View Post
    This would be perfect as an example, ie being able to drag rows into different groups!

    -Lobos
    i agree with colinexl. The better example-base (at first to see ANY kind of solution at all) for dragdrop grid reordering in my pov is the basic array-grid:

    http://extjs.com/deploy/dev/examples...rray-grid.html

  9. #9
    Ext User
    Join Date
    Dec 2007
    Posts
    214
    Vote Rating
    0
    Radziu is an unknown quantity at this point

      0  

    Default


    ok, your're right but how to make "basic grid" from example drag and drop grid

  10. #10
    Sencha User
    Join Date
    Dec 2007
    Location
    Grand Rapids, MI USA
    Posts
    18
    Vote Rating
    0
    clarkke8 is on a distinguished road

      0  

    Default I got it to work

    I got it to work


    PHP Code:
    // this code goes in a javascript include file somewhere
    Ext.namespace('Ext.ux.dd');

    Ext.ux.dd.GridReorderDropTarget = function(gridconfig) {
        
    this.target = new Ext.dd.DropTarget(grid.getEl(), {
            
    ddGroupgrid.ddGroup || 'GridDD'
            
    ,gridgrid
            
    ,gridDropTargetthis
            
    ,notifyDrop: function(ddedata){
                
    // determine the row
                
    var Ext.lib.Event.getTarget(e);
                var 
    rindex this.grid.getView().findRowIndex(t);
                if (
    rindex === false) return false;
                if (
    rindex == data.rowIndex) return false;

                
    // fire the before move/copy event
                
    if (this.gridDropTarget.fireEvent(this.copy?'beforerowcopy':'beforerowmove'this.gridDropTargetdata.rowIndexrindexdata.selections) === false) return false;

                
    // update the store
                
    var ds this.grid.getStore();
                if (!
    this.copy) {
                    for(
    0data.selections.lengthi++) {
                        
    ds.remove(ds.getById(data.selections[i].id));
                    }
                }
                
    ds.insert(rindex,data.selections);

                
    // re-select the row(s)
                
    var sm this.grid.getSelectionModel();
                if (
    smsm.selectRecords(data.selections);

                
    // fire the after move/copy event
                
    this.gridDropTarget.fireEvent(this.copy?'afterrowcopy':'afterrowmove'this.gridDropTargetdata.rowIndexrindexdata.selections);

                return 
    true;
            }
            ,
    notifyOver: function(ddedata) {
                var 
    Ext.lib.Event.getTarget(e);
                var 
    rindex this.grid.getView().findRowIndex(t);
                if (
    rindex == data.rowIndexrindex false;

                return (
    rindex === false)? this.dropNotAllowed this.dropAllowed;
            }
        });
        if (
    config) {
            
    Ext.apply(this.targetconfig);
            if (
    config.listenersExt.apply(this,{listenersconfig.listeners});
        }

        
    this.addEvents({
            
    "beforerowmove"true
            
    ,"afterrowmove"true
            
    ,"beforerowcopy"true
            
    ,"afterrowcopy"true
        
    });

        
    Ext.ux.dd.GridReorderDropTarget.superclass.constructor.call(this);
    };

    Ext.extend(Ext.ux.dd.GridReorderDropTargetExt.util.Observable, {
        
    getTarget: function() {
            return 
    this.target;
        }
        ,
    getGrid: function() {
            return 
    this.target.grid;
        }
        ,
    getCopy: function() {
            return 
    this.target.copy?true:false;
        }
        ,
    setCopy: function(b) {
            
    this.target.copy b?true:false;
        }
    }); 
    PHP Code:
    // here is an example of how you use it
    var grid = new Ext.grid.GridPanel({
        
    ddGroup'testDDGroup'
        
    ,enableDragDroptrue
        
    ,autoHeightfalse
        
    ,height375
        
    ,autoScrolltrue
        
    ,el'content_div'
        
    ,listeners: {
            
    render: function(g) {
                
    // Best to create the drop target after render, so we don't need to worry about whether grid.el is null

                // constructor parameters:
                //    grid (required): GridPanel or EditorGridPanel (with enableDragDrop set to true and optionally a value specified for ddGroup, which defaults to 'GridDD')
                //    config (optional): config object
                // valid config params:
                //    anything accepted by DropTarget
                //    listeners: listeners object. There are 4 valid listeners, all listed in the example below
                //    copy: boolean. Determines whether to move (false) or copy (true) the row(s) (defaults to false for move)
                
    var ddrow = new Ext.ux.dd.GridReorderDropTarget(g, {
                    
    copyfalse
                    
    ,listeners: {
                        
    beforerowmove: function(objThisoldIndexnewIndexrecords) {
                            
    // code goes here
                            // return false to cancel the move
                        
    }
                        ,
    afterrowmove: function(objThisoldIndexnewIndexrecords) {
                            
    // code goes here
                        
    }
                        ,
    beforerowcopy: function(objThisoldIndexnewIndexrecords) {
                            
    // code goes here
                            // return false to cancel the copy
                        
    }
                        ,
    afterrowcopy: function(objThisoldIndexnewIndexrecords) {
                            
    // code goes here
                        
    }
                    }
                });

                
    // if you need scrolling, register the grid view's scroller with the scroll manager
                
    Ext.dd.ScrollManager.register(g.getView().getEditorParent());
            }
            ,
    beforedestroy: function(g) {
                
    // if you previously registered with the scroll manager, unregister it (if you don't it will lead to problems in IE)
                
    Ext.dd.ScrollManager.unregister(g.getView().getEditorParent());
            }
        }
        
    // ... the rest of the setup for the grid
    });

    grid.render(); 
    Last edited by clarkke8; 17 Jan 2008 at 3:18 PM. Reason: In the example code, moved creation of the drop target to the grid's "render" event, added ScrollManager unregister call