/** * @class Ext.grid.RowSelectionModel * @extends Ext.grid.AbstractSelectionModel * The default SelectionModel used by {@link Ext.grid.GridPanel}. * It supports multiple selections and keyboard selection/navigation. The objects stored * as selections and returned by {@link #getSelected}, and {@link #getSelections} are * the {@link Ext.data.Record Record}s which provide the data for the selected rows. * @constructor * @param {Object} config */ Ext.grid.RowSelectionModel = function(config){ Ext.apply(this, config); this.selections = new Ext.util.MixedCollection(false, function(o){ return o.id; }); this.last = false; this.lastActive = false; this.addEvents( /** * @event selectionchange * Fires when the selection changes * @param {SelectionModel} this */ "selectionchange", /** * @event beforerowselect * Fires before a row is selected, return false to cancel the selection. * @param {SelectionModel} this * @param {Number} rowIndex The index to be selected * @param {Boolean} keepExisting False if other selections will be cleared * @param {Record} record The record to be selected */ "beforerowselect", /** * @event rowselect * Fires when a row is selected. * @param {SelectionModel} this * @param {Number} rowIndex The selected index * @param {Ext.data.Record} r The selected record */ "rowselect", /** * @event rowdeselect * Fires when a row is deselected. To prevent deselection * {@link Ext.grid.AbstractSelectionModel#lock lock the selections}. * @param {SelectionModel} this * @param {Number} rowIndex * @param {Record} record */ "rowdeselect" ); Ext.grid.RowSelectionModel.superclass.constructor.call(this); }; Ext.extend(Ext.grid.RowSelectionModel, Ext.grid.AbstractSelectionModel, { /** * @cfg {Boolean} singleSelect * true to allow selection of only one row at a time (defaults to false * allowing multiple selections) */ singleSelect : false, /** * @cfg {Boolean} moveEditorOnEnter * false to turn off moving the editor to the next row down when the enter key is pressed * or the next row up when shift + enter keys are pressed. */ // private initEvents : function(){ if(!this.grid.enableDragDrop && !this.grid.enableDrag){ this.grid.on("rowmousedown", this.handleMouseDown, this); } this.rowNav = new Ext.KeyNav(this.grid.getGridEl(), { "up" : function(e){ if(!e.shiftKey || this.singleSelect){ this.selectPrevious(false); }else if(this.last !== false && this.lastActive !== false){ var last = this.last; this.selectRange(this.last, this.lastActive-1); this.grid.getView().focusRow(this.lastActive); if(last !== false){ this.last = last; } }else{ this.selectFirstRow(); } }, "down" : function(e){ if(!e.shiftKey || this.singleSelect){ this.selectNext(false); }else if(this.last !== false && this.lastActive !== false){ var last = this.last; this.selectRange(this.last, this.lastActive+1); this.grid.getView().focusRow(this.lastActive); if(last !== false){ this.last = last; } }else{ this.selectFirstRow(); } }, scope: this }); var view = this.grid.view; view.on("refresh", this.onRefresh, this); view.on("rowupdated", this.onRowUpdated, this); view.on("rowremoved", this.onRemove, this); }, // private onRefresh : function(){ var ds = this.grid.store, index; var s = this.getSelections(); this.clearSelections(true); for(var i = 0, len = s.length; i < len; i++){ var r = s[i]; if((index = ds.indexOfId(r.id)) != -1){ this.selectRow(index, true); } } if(s.length != this.selections.getCount()){ this.fireEvent("selectionchange", this); } }, // private onRemove : function(v, index, r){ if(this.selections.remove(r) !== false){ this.fireEvent('selectionchange', this); } }, // private onRowUpdated : function(v, index, r){ if(this.isSelected(r)){ v.onRowSelect(index); } }, /** * Select records. * @param {Array} records The records to select * @param {Boolean} keepExisting (optional) true to keep existing selections */ selectRecords : function(records, keepExisting){ if(!keepExisting){ this.clearSelections(); } var ds = this.grid.store; for(var i = 0, len = records.length; i < len; i++){ this.selectRow(ds.indexOf(records[i]), true); } }, /** * Gets the number of selected rows. * @return {Number} */ getCount : function(){ return this.selections.length; }, /** * Selects the first row in the grid. */ selectFirstRow : function(){ this.selectRow(0); }, /** * Select the last row. * @param {Boolean} keepExisting (optional) true to keep existing selections */ selectLastRow : function(keepExisting){ this.selectRow(this.grid.store.getCount() - 1, keepExisting); }, /** * Selects the row immediately following the last selected row. * @param {Boolean} keepExisting (optional) true to keep existing selections * @return {Boolean} true if there is a next row, else false */ selectNext : function(keepExisting){ if(this.hasNext()){ this.selectRow(this.last+1, keepExisting); this.grid.getView().focusRow(this.last); return true; } return false; }, /** * Selects the row that precedes the last selected row. * @param {Boolean} keepExisting (optional) true to keep existing selections * @return {Boolean} true if there is a previous row, else false */ selectPrevious : function(keepExisting){ if(this.hasPrevious()){ this.selectRow(this.last-1, keepExisting); this.grid.getView().focusRow(this.last); return true; } return false; }, /** * Returns true if there is a next record to select * @return {Boolean} */ hasNext : function(){ return this.last !== false && (this.last+1) < this.grid.store.getCount(); }, /** * Returns true if there is a previous record to select * @return {Boolean} */ hasPrevious : function(){ return !!this.last; }, /** * Returns the selected records * @return {Array} Array of selected records */ getSelections : function(){ return [].concat(this.selections.items); }, /** * Returns the first selected record. * @return {Record} */ getSelected : function(){ return this.selections.itemAt(0); }, /** * Calls the passed function with each selection. If the function returns * false, iteration is stopped and this function returns * false. Otherwise it returns true. * @param {Function} fn * @param {Object} scope (optional) * @return {Boolean} true if all selections were iterated */ each : function(fn, scope){ var s = this.getSelections(); for(var i = 0, len = s.length; i < len; i++){ if(fn.call(scope || this, s[i], i) === false){ return false; } } return true; }, /** * Clears all selections if the selection model * {@link Ext.grid.AbstractSelectionModel#isLocked is not locked}. * @param {Boolean} fast (optional) true to bypass the * conditional checks and events described in {@link #deselectRow}. */ clearSelections : function(fast){ if(this.isLocked()){ return; } if(fast !== true){ var ds = this.grid.store; var s = this.selections; s.each(function(r){ this.deselectRow(ds.indexOfId(r.id)); }, this); s.clear(); }else{ this.selections.clear(); } this.last = false; }, /** * Selects all rows if the selection model * {@link Ext.grid.AbstractSelectionModel#isLocked is not locked}. */ selectAll : function(){ if(this.isLocked()){ return; } this.selections.clear(); for(var i = 0, len = this.grid.store.getCount(); i < len; i++){ this.selectRow(i, true); } }, /** * Returns true if there is a selection. * @return {Boolean} */ hasSelection : function(){ return this.selections.length > 0; }, /** * Returns true if the specified row is selected. * @param {Number/Record} index The record or index of the record to check * @return {Boolean} */ isSelected : function(index){ var r = Ext.isNumber(index) ? this.grid.store.getAt(index) : index; return (r && this.selections.key(r.id) ? true : false); }, /** * Returns true if the specified record id is selected. * @param {String} id The id of record to check * @return {Boolean} */ isIdSelected : function(id){ return (this.selections.key(id) ? true : false); }, // private handleMouseDown : function(g, rowIndex, e){ if(e.button !== 0 || this.isLocked()){ return; } var view = this.grid.getView(); if(e.shiftKey && !this.singleSelect && this.last !== false){ var last = this.last; this.selectRange(last, rowIndex, e.ctrlKey); this.last = last; // reset the last view.focusRow(rowIndex); }else{ var isSelected = this.isSelected(rowIndex); if(e.ctrlKey && isSelected){ this.deselectRow(rowIndex); }else if(!isSelected || this.getCount() > 1){ this.selectRow(rowIndex, e.ctrlKey || e.shiftKey); view.focusRow(rowIndex); } } }, /** * Selects multiple rows. * @param {Array} rows Array of the indexes of the row to select * @param {Boolean} keepExisting (optional) true to keep * existing selections (defaults to false) */ selectRows : function(rows, keepExisting){ if(!keepExisting){ this.clearSelections(); } for(var i = 0, len = rows.length; i < len; i++){ this.selectRow(rows[i], true); } }, /** * Selects a range of rows if the selection model * {@link Ext.grid.AbstractSelectionModel#isLocked is not locked}. * All rows in between startRow and endRow are also selected. * @param {Number} startRow The index of the first row in the range * @param {Number} endRow The index of the last row in the range * @param {Boolean} keepExisting (optional) True to retain existing selections */ selectRange : function(startRow, endRow, keepExisting){ var i; if(this.isLocked()){ return; } if(!keepExisting){ this.clearSelections(); } if(startRow <= endRow){ for(i = startRow; i <= endRow; i++){ this.selectRow(i, true); } }else{ for(i = startRow; i >= endRow; i--){ this.selectRow(i, true); } } }, /** * Deselects a range of rows if the selection model * {@link Ext.grid.AbstractSelectionModel#isLocked is not locked}. * All rows in between startRow and endRow are also deselected. * @param {Number} startRow The index of the first row in the range * @param {Number} endRow The index of the last row in the range */ deselectRange : function(startRow, endRow, preventViewNotify){ if(this.isLocked()){ return; } for(var i = startRow; i <= endRow; i++){ this.deselectRow(i, preventViewNotify); } }, /** * Selects a row. Before selecting a row, checks if the selection model * {@link Ext.grid.AbstractSelectionModel#isLocked is locked} and fires the * {@link #beforerowselect} event. If these checks are satisfied the row * will be selected and followed up by firing the {@link #rowselect} and * {@link #selectionchange} events. * @param {Number} row The index of the row to select * @param {Boolean} keepExisting (optional) true to keep existing selections * @param {Boolean} preventViewNotify (optional) Specify true to * prevent notifying the view (disables updating the selected appearance) */ selectRow : function(index, keepExisting, preventViewNotify){ if(this.isLocked() || (index < 0 || index >= this.grid.store.getCount()) || (keepExisting && this.isSelected(index))){ return; } var r = this.grid.store.getAt(index); if(r && this.fireEvent("beforerowselect", this, index, keepExisting, r) !== false){ if(!keepExisting || this.singleSelect){ this.clearSelections(); } this.selections.add(r); this.last = this.lastActive = index; if(!preventViewNotify){ this.grid.getView().onRowSelect(index); } this.fireEvent("rowselect", this, index, r); this.fireEvent("selectionchange", this); } }, /** * Deselects a row. Before deselecting a row, checks if the selection model * {@link Ext.grid.AbstractSelectionModel#isLocked is locked}. * If this check is satisfied the row will be deselected and followed up by * firing the {@link #rowdeselect} and {@link #selectionchange} events. * @param {Number} row The index of the row to deselect * @param {Boolean} preventViewNotify (optional) Specify true to * prevent notifying the view (disables updating the selected appearance) */ deselectRow : function(index, preventViewNotify){ if(this.isLocked()){ return; } if(this.last == index){ this.last = false; } if(this.lastActive == index){ this.lastActive = false; } var r = this.grid.store.getAt(index); if(r){ this.selections.remove(r); if(!preventViewNotify){ this.grid.getView().onRowDeselect(index); } this.fireEvent("rowdeselect", this, index, r); this.fireEvent("selectionchange", this); } }, // private restoreLast : function(){ if(this._last){ this.last = this._last; } }, // private acceptsNav : function(row, col, cm){ return !cm.isHidden(col) && cm.isCellEditable(col, row); }, // private onEditorKey : function(field, e){ var k = e.getKey(), newCell, g = this.grid, ed = g.activeEditor; var shift = e.shiftKey; if(k == e.TAB){ e.stopEvent(); ed.completeEdit(); if(shift){ 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); } }else if(k == e.ENTER){ e.stopEvent(); ed.completeEdit(); if(this.moveEditorOnEnter !== false){ if(shift){ newCell = g.walkCells(ed.row - 1, ed.col, -1, this.acceptsNav, this); }else{ newCell = g.walkCells(ed.row + 1, ed.col, 1, this.acceptsNav, this); } } }else if(k == e.ESC){ ed.cancelEdit(); } if(newCell){ g.startEditing(newCell[0], newCell[1]); } }, destroy: function(){ if(this.rowNav){ this.rowNav.disable(); this.rowNav = null; } Ext.grid.RowSelectionModel.superclass.destroy.call(this); } });