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,663
    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,649
    Vote Rating
    6
    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

film izle

hd film izle

film sitesi

takipci kazanma sitesi

takipci kazanma sitesi

güzel olan herşey

takipci alma sitesi

komik eğlenceli videolar