1. #1
    Sencha User
    Join Date
    Sep 2008
    Location
    Hechingen, Germany
    Posts
    7
    Vote Rating
    0
    thomashoffmann is on a distinguished road

      0  

    Default MultiCellSelectionModel

    MultiCellSelectionModel


    Hi,

    today i have written a MultiCellSelectionModel. There are some bugs in keyboard selection.
    Code:
    /**
    * @class Ext.ux.MultiCellSelectionModel
    * @extends Ext.grid.AbstractSelectionModel
    * This class provides an implementation for <i>multible</i> <b>cell</b> selection in a grid.
    * 
    * Mouse selection is supported, keyboard selection is bugy
    * 
    * <div class="mdetail-params"><ul>
    * <li><b>cells</b> : see {@link #getSelections} 
    * <li><b>cell</b> : see {@link #getSelectedCell} 
    * </ul></div>
    * @constructor
    * @param {Object} config The object containing the configuration of this model.
    */
    Ext.ux.MultiCellSelectionModel = function(config) {
        Ext.apply(this, config);
    
         
        this.selections = new Ext.util.MixedCollection(false,
        function(o) {
            return o.id;
        });
    
        this.selection = null;
    
        this.addEvents(
        /**
    * @event beforecellselect
    * Fires before a cell is selected.
    * @param {SelectionModel} this
    * @param {Number} rowIndex The selected row index
    * @param {Number} colIndex The selected cell index
    */
        "beforecellselect",
        /**
    * @event cellselect
    * Fires when a cell is selected.
    * @param {SelectionModel} this
    * @param {Number} rowIndex The selected row index
    * @param {Number} colIndex The selected cell index
    */
        "cellselect",
        /**
    * @event selectionchange
    * Fires when the active selection changes.
    * @param {SelectionModel} this
    * @param {Object} selection null for no selection or an object with two properties
    * <div class="mdetail-params"><ul>
    * <li><b>cell</b> : see {@link #getSelectedCell} 
    * <li><b>record</b> : Ext.data.record<p class="sub-desc">The {@link Ext.data.Record Record}
    * which provides the data for the row containing the selection</p></li>
    * </ul></div>
    */
        "selectionchange");
    
        Ext.ux.MultiCellSelectionModel.superclass.constructor.call(this);
    };
    
    Ext.extend(Ext.ux.MultiCellSelectionModel, Ext.grid.AbstractSelectionModel, {
    
        /** @ignore */
        initEvents: function() {
            this.grid.on("cellmousedown", this.handleMouseDown, this);
            this.grid.getGridEl().on(Ext.isIE || Ext.isSafari3 || Ext.isChrome ? "keydown": "keypress", this.handleKeyDown, this);
            var view = this.grid.view;
            view.on("refresh", this.onViewChange, this);
            view.on("rowupdated", this.onRowUpdated, this);
            view.on("beforerowremoved", this.clearSelections, this);
            view.on("beforerowsinserted", this.clearSelections, this);
            if (this.grid.isEditor) {
                this.grid.on("beforeedit", this.beforeEdit, this);
            }
        },
    
        //private
        beforeEdit: function(e) {
            this.select(e.row, e.column, false, true, e.record);
        },
    
        //private
        onRowUpdated: function(v, index, r) {
            if (this.selection && this.selection.record == r) {
                v.onCellSelect(index, this.selection.cell[1]);
            }
        },
    
        //private
        onViewChange: function() {
            this.clearSelections(true);
        },
    
        /**
    * Returns an array containing the row and column indexes of the currently selected cell
    * (e.g., [0, 0]), or null if none selected. The array has elements:
    * <div class="mdetail-params"><ul>
    * <li><b>rowIndex</b> : Number<p class="sub-desc">The index of the selected row</p></li>
    * <li><b>cellIndex</b> : Number<p class="sub-desc">The index of the selected cell. 
    * Due to possible column reordering, the cellIndex should <b>not</b> be used as an
    * index into the Record's data. Instead, use the cellIndex to determine the <i>name</i>
    * of the selected cell and use the field name to retrieve the data value from the record:<pre><code>
    * </code></pre></p></li>
    * </ul></div>
    * @return {Array} An array containing the row and column indexes of the selected cell, or null if none selected.
    */
        getSelectedCell: function() {
            return this.selection ? this.selection.cell: null;
        },
    
        /**
    * Returns the selected records
    * @return {MixedCollection} MixedCollection of selected records, each item contain a cell property (an array containing the row and column indexes) 
    */
        getSelections: function() {
            return this.selections;
        },
    
        /**
    * Clears the single selection.
    * @param {Boolean} true to prevent the gridview from being notified about the change.
    */
        clearSelection: function(preventNotify) {
            var s = this.selection;
            if (s) {
                if (preventNotify !== true) {
                    this.grid.view.onCellDeselect(s.cell[0], s.cell[1]);
                }
                this.selection = null;
                this.fireEvent("selectionchange", this, null);
            }
        },
    
        /**
    * Clears all selections.
    * @param {Boolean} true to prevent the gridview from being notified about the change.
    */
        clearSelections: function(preventNotify) {
            var s = this.selections;
            //alert('cs');
            for (var i = 0, len = s.length; i < len; i++) {
                if (preventNotify !== true) {
                    this.grid.view.onCellDeselect(s.get(i).cell[0], s.get(i).cell[1]);
                }
                //this.selection = null;
            }
            this.selections.clear();
        },
    
        /**
    * Returns true if there is a selection.
    * @return {Boolean}
    */
        hasSelection: function() {
            return this.selection ? true: false;
        },
    
        /** @ignore */
        stop_events: false,
        /** @ignore */
        m_select: false,
        /** @ignore */
        handleMouseDown: function(g, row, cell, e) {
            if (e.button !== 0 || this.isLocked()) {
                return;
            };
            this._xselect(row, cell, e, false);
        },
    
        _xselect: function(row, cell, e, x) {
            if ((!e.shiftKey && !e.ctrlKey)) {
                this.clearSelections();
                this.clearSelection();
                this.select(row, cell);
                this.m_select = false;
            } else {
                if (e.ctrlKey) {
                    this.select(row, cell);
                }
                else {
    
                    if (this.m_select && (x == false)) {
                        var so = this.selection;
                        this.clearSelections();
                        this.clearSelection();
                        this.selection = so;
    
                    }
    
                    var r1 = 0;
                    var c1 = 0;
                    if (this.selection != 0) {
                        r1 = this.selection.cell[0];
                        c1 = this.selection.cell[1];
                    }
                    var r2 = row;
                    var c2 = cell;
    
                    if (c1 > c2) {
                        c_start = c2;
                        c_end = c1;
                    }
                    else {
                        c_start = c1;
                        c_end = c2;
                    }
    
                    if (r1 > r2) {
                        r_start = r2;
                        r_end = r1;
                    }
                    else {
                        r_start = r1;
                        r_end = r2;
                    }
                    this.stop_events = true;
                    for (r = r_start; r <= r_end; r++) {
                        for (c = c_start; c <= c_end; c++) {
                            this.select(r, c, false, true);
                        }
                    }
                    this.stop_events = false;
                    this.m_select = true;
                    //this.select(row, cell,false,true);
                    this.fireEvent("cellselect", this, r_end, c_end);
                    this.fireEvent("selectionchange", this, this.selection);
    
                }
            }
        },
    
        /**
    * Selects a cell.
    * @param {Number} rowIndex
    * @param {Number} collIndex
    */
        select: function(rowIndex, colIndex, preventViewNotify, preventFocus,
        /*internal*/
        r) {
            if (this.fireEvent("beforecellselect", this, rowIndex, colIndex) !== false) {
                //this.clearSelection();
                //r = r || this.grid.store.getAt(rowIndex);
                this.selection = {
                    //record : r,
                    cell: [rowIndex, colIndex]
                };
                this.selections.add(this.selection);
                if (!preventViewNotify) {
                    var v = this.grid.getView();
                    v.onCellSelect(rowIndex, colIndex);
                    if (preventFocus !== true) {
                        v.focusCell(rowIndex, colIndex);
                    }
                }
                if (!this.stop_events) {
                    this.fireEvent("cellselect", this, rowIndex, colIndex);
                    this.fireEvent("selectionchange", this, this.selection);
                }
            }
        },
    
        //private
        isSelectable: function(rowIndex, colIndex, cm) {
            return ! cm.isHidden(colIndex);
        },
    
        /** @ignore */
        _getMinMax: function() {
            var sel = this.getSelections();
            var selc = sel.getCount();
            var min_row = 9999999;
            var max_row = 0;
    
            var min_col = 9999999;
            var max_col = 0;
    
            for (var j = 0; j < selc; j++) {
                if (sel.get(j).cell[0] < min_row) {
                    min_row = sel.get(j).cell[0];
                }
                if (sel.get(j).cell[0] > max_row) {
                    max_row = sel.get(j).cell[0];
                }
    
                if (sel.get(j).cell[1] < min_col) {
                    min_col = sel.get(j).cell[1];
                }
                if (sel.get(j).cell[1] > max_col) {
                    max_col = sel.get(j).cell[1];
                }
            }
    
            return {
                minRow: min_row,
                maxRow: max_row,
    
                minCol: min_col,
                maxCol: min_col
            };
        },
        /** @ignore */
        handleKeyDown: function(e) {
            if (!e.isNavKeyPress()) {
                return;
            }
            var g = this.grid,
            s = this.selection;
            if (!s) {
                e.stopEvent();
                var cell = g.walkCells(0, 0, 1, this.isSelectable, this);
                if (cell) {
                    this._xselect(cell[0], cell[1], e, true);
                    //this.clearSelections();
                    //this.select(cell[0], cell[1]);
                }
                return;
            }
            var sm = this;
            var walk = function(row, col, step) {
                return g.walkCells(row, col, step, sm.isSelectable, sm);
            };
            var k = e.getKey(),
            r = s.cell[0],
            c = s.cell[1];
            var newCell;
    
            switch (k) {
            case e.TAB:
                if (e.shiftKey) {
                    newCell = walk(r, c - 1, -1);
                } else {
                    newCell = walk(r, c + 1, 1);
                }
                break;
            case e.DOWN:
                newCell = walk(r + 1, c, 1);
                break;
            case e.UP:
                newCell = walk(r - 1, c, -1);
                break;
            case e.RIGHT:
                newCell = walk(r, c + 1, 1);
                break;
            case e.LEFT:
                newCell = walk(r, c - 1, -1);
                break;
            case e.ENTER:
                if (g.isEditor && !g.editing) {
                    g.startEditing(r, c);
                    e.stopEvent();
                    return;
                }
                break;
            };
            if (newCell) {
                this._xselect(newCell[0], newCell[1], e, true);
                //this.clearSelections();
                //this.select(newCell[0], newCell[1]);
                e.stopEvent();
            }
        },
    
        acceptsNav: function(row, col, cm) {
            return ! cm.isHidden(col) && cm.isCellEditable(col, row);
        },
    
        onEditorKey: function(field, e) {
            var k = e.getKey(),
            newCell,
            g = this.grid,
            ed = g.activeEditor;
            if (k == e.TAB) {
                if (e.shiftKey) {
                    newCell = g.walkCells(ed.row, ed.col - 1, -1, this.acceptsNav, this);
                } else {
                    newCell = g.walkCells(ed.row, ed.col + 1, 1, this.acceptsNav, this);
                }
                e.stopEvent();
            } else if (k == e.ENTER) {
                ed.completeEdit();
                e.stopEvent();
            } else if (k == e.ESC) {
                e.stopEvent();
                ed.cancelEdit();
            }
            if (newCell) {
                g.startEditing(newCell[0], newCell[1]);
            }
        }
    });
    Sample use:

    Code:
     
    var sel = grid.getSelectionModel().getSelections();
    var selc = sel.getCount();
    for (var j = 0; j < selc; j++) {
        var row = sel.get(j).cell[0];
        var col = sel.get(j).cell[1];
        // ...
    }
    Last edited by mystix; 4 Jun 2009 at 8:00 PM. Reason: RAN UGLY CODE THROUGH jsbeautifier.org

  2. #2
    Sencha User MD's Avatar
    Join Date
    Mar 2007
    Posts
    178
    Vote Rating
    0
    MD is on a distinguished road

      0  

    Default


    Live demo?

    MD

  3. #3
    Sencha User tobiu's Avatar
    Join Date
    May 2007
    Location
    Munich (Germany)
    Posts
    2,669
    Vote Rating
    110
    tobiu is a name known to all tobiu is a name known to all tobiu is a name known to all tobiu is a name known to all tobiu is a name known to all tobiu is a name known to all

      0  

    Default


    hi thomas,

    i am a bit short in time right now, so i do not have the time to test your ux.
    but the multicelection-ux of ext2.2 works perfect with me and the 3.0-rc2.

    kind regards, tobiu

  4. #4
    Ext User
    Join Date
    Mar 2010
    Posts
    11
    Vote Rating
    0
    j0452 is on a distinguished road

      0  

    Default


    Hey Thomas,

    Just came across this and found it useful, thanks!

    Any progress since your original post (10 months ago)?

    I'm working on a MultiCellSelection model that mimics Google Spreadsheets' behavior, and I'm making good progress. Interested in picking back up the thread?

    Josh

  5. #5
    Sencha User
    Join Date
    Sep 2008
    Location
    Hechingen, Germany
    Posts
    7
    Vote Rating
    0
    thomashoffmann is on a distinguished road

      0  

    Default


    Hi,

    my SelectionModel fits me needs, but sure i'm interested.

  6. #6
    Ext User
    Join Date
    Mar 2010
    Posts
    11
    Vote Rating
    0
    j0452 is on a distinguished road

      0  

    Default


    So far I've added just-start-typing-to-edit, and hitting tab or enter while editing keeps you in edit mode. I'm also working on getting it to select the entire column when you click a column header instead of re-sorting, and selecting the entire row when you click a row number.

    Do you know how I can find out if there's a plan to support a multi-cell selection model in Ext core? With all the different grid demos in the example gallery, I was surprised it didn't support a standard spreadsheet behavior grid out of the box. Maybe this is Coming Soon? Or is there an official repo for user-contributed extensions with proper releases, like jQuery has? (Sorry I'm new to the Ext community.)

    Note: I've been posting to the thread at http://www.extjs.com/forum/showthread.php?p=455563 (which is how i was referred to this thread) with my progress, in case you want to follow that one.

    Have you had any luck with the keyboard navigation?

  7. #7
    Sencha User steffenk's Avatar
    Join Date
    Jul 2007
    Location
    Haan, Germany
    Posts
    2,664
    Vote Rating
    7
    steffenk has a spectacular aura about steffenk has a spectacular aura about steffenk has a spectacular aura about

      0  

    Default


    If you want to see a very good implementation, test the spreadsheet at http://www.feyasoft.com/ (demo/demo)
    vg Steffen
    --------------------------------------
    Release Manager of TYPO3 4.5

Turkiyenin en sevilen filmlerinin yer aldigi xnxx internet sitemiz olan ve porn sex tarzi bir site olan mobil porno izle sitemiz gercekten dillere destan bir durumda herkesin sevdigi bir site olarak tarihe gececege benziyor. Sitenin en belirgin ozelliklerinden birisi de Turkiyede gercekten kaliteli ve muntazam, duzenli porno izle siteleri olmamasidir. Bu yuzden iste. Ayrica en net goruntu kalitesine sahip adresinde yayinlanmaktadir. Mesela diğer sitelerimizden bahsedecek olursak, en iyi hd porno video arşivine sahip bir siteyiz. "The Best anal porn videos and slut anus, big asses movies set..." hd porno faketaxi