I've posted this over on the Sencha forums, wanted to also post it here just in case:
I have a GridPanel that utilizes a PagingToolbar and a CheckboxSelectionModel. I want to keep track of selections across pages. I'm nearly there, but I'm running into issues with the PagingToolbar controls (such as next page) firing a 'selectionchange' event on the my selection model.
Here's a simplified sample of my code:
Code:
var sm = Ext.create('Ext.selection.CheckboxModel', {
listeners:{
selectionchange: function(selectionModel, selectedRecords, options){
console.log("Selection Change!!");
// CODE HERE TO KEEP TRACK OF SELECTIONS/DESELECTIONS
}
}
});
var grid = Ext.create('Ext.grid.Panel', {
autoScroll:true,
store: store,
defaults: {
sortable:true
},
selModel: sm,
dockedItems: [{
xtype: 'pagingtoolbar',
store: store,
dock: 'bottom',
displayInfo: true
}],
listeners: {'beforerender' : {fn:function(){
store.load({params:params});
}}}
});
store.on('load', function() {
console.log('loading');
console.log(params);
console.log('selecting...');
var records = this.getNewRecords();
var recordsToSelect = getRecordsToSelect(records);
sm.select(recordsToSelect, true, true);
});
I assumed that I could select the records on the load event and not trigger any events.
What's happening here is that the selectionchange event is being triggered on changing the page of data and I don't want that to occur. Ideally, only user clicking would be tracked as 'selectionchange' events, not any other component's events bubbling up and triggering the event on my selection model. Looking at the source code, the only event I could see that fires on the PagingToolbar is 'change'. I was trying to follow how that is handled by the GridPanel, TablePanel, Gridview, etc, but I'm just not seeing the path of the event. Even then, I'm not sure how to suppress events from the PagingToolbar to the SelectionModel.
Thanks in advance,
Tom
I've managed to handle that. The key is to detect where page changes. Easiest solution is to set buffer for selection listener and check for Store.loading property.
Here is my implementation of selection model:
var selModel = Ext.create('Ext.selection.CheckboxModel', {
multipageSelection: {},
listeners:{
selectionchange: function(selectionModel, selectedRecords, options){
// do not change selection on page change
if (selectedRecords.length == 0 && this.store.loading == true && this.store.currentPage != this.page) {
return;
}
// remove selection on refresh
if (this.store.loading == true) {
this.multipageSelection = {};
return;
}
// remove old selection from this page
this.store.data.each(function(i) {
delete this.multipageSelection[i.id];
}, this);
// select records
Ext.each(selectedRecords, function(i) {
this.multipageSelection[i.id] = true;
}, this);
},
buffer: 5
},
restoreSelection: function() {
this.store.data.each(function(i) {
if (this.multipageSelection[i.id] == true) {
this.select(i, true, true);
}
}, this);
this.page = this.store.currentPage;
}
And additional binding to store is required:
store.on('load', grid.getSelectionModel().restoreSelection, grid.getSelectionModel());
Working sample: http://jsfiddle.net/pqVmb/
Lolo's solution is great but it seems that it doesn't work anymore with ExtJS 4.2.1.
Instead of 'selectionchange' use this:
deselect: function( selectionModel, record, index, eOpts ) {
delete this.multipageSelection[i.id];
},
select: function( selectionModel, record, index, eOpts ) {
this.multipageSelection[i.id] = true;
},
This is a solution for ExtJs5, utilizing MVVC create a local store named 'selectedObjects' in the View Model with the same model as the paged grid.
Add select and deselect listeners on the checkboxmodel. In these functions add or remove the selected or deselected record from this local store.
onCheckboxModelSelect: function(rowmodel, record, index, eOpts) {
// Add selected record to the view model store
this.getViewModel().getStore('selectedObjects').add(record);
},
onCheckboxModelDeselect: function(rowmodel, record, index, eOpts) {
// Remove selected record from the view model store
this.getViewModel().getStore('selectedObjects').remove(record);
},
On the pagingtoolbar, add a change listener to reselect previously seleted records when appear in the page.
onPagingtoolbarChange: function(pagingtoolbar, pageData, eOpts) {
// Select any records on the page that have been previously selected
var checkboxSelectionModel = this.lookupReference('grid').getSelectionModel(),
selectedObjects = this.getViewModel().getStore('selectedObjects').getRange();
// true, true params. keepselections if any and suppresses select event. Don't want infinite loop listeners.
checkboxSelectionModel.select(selectedObjects, true, true);
},
After whatever action is complete where these selections are no longer needed. Call deselectAll on the checkboxmodel and removeAll from the local store if it will not be a destroyed view. (Windows being closed, they default set to call destroy and will take care of local store data cleanup, if that is your case)
Related
Hi I have added one context menu on my grid which will perform the enable and disable functionality for selected row. I am new to ExtJs. I have added below listener for the grid. How to add enable and disable functionality for the grid row?
listeners: {
itemcontextmenu: function (grid, record, item, index, e) {
var contextMenu = Ext.create('Ext.menu.Menu', {
controller: 'grid-controller',
width: 165,
plain: true,
items: [{
text: 'Disable',
listeners: {
click: {fn: 'disable', extra: record}
},
}]
});
e.stopEvent();
contextMenu.showAt(e.getXY());
}
}
This is not a copy-paste answer, but going through the following steps with doing your own research you can solve your problem.
1. Create the context menu only once and destroy it
In you code, the context menu is created every time when the user opens up the menu on the grid. This is not good. Instead, create the context menu only once when the grid is created, and destroy it when the grid is destroyed. Something like this:
Ext.define('MyGrid', {
extend: 'Ext.grid.Panel',
initComponent : function() {
this.callParent();
this.MyMenu = Ext.create('Ext.menu.Menu', {
items: [...]
});
this.on({
scope : this,
itemcontextmenu : this.onItemContextMenu
});
},
onDestroy : function() {
if (this.MyMenu) {
this.MyMenu.destroy();
}
},
onItemContextMenu : function(view, rec, item,index, event) {
event.stopEvent();
this.MyMenu.showAt(event.getXY());
}
});
2. Store enabled / disabled state in the record
For the next step to work, records in your grid must contain whether the corresponding row is enabled or disabled. In the context menu, when user selects enabled / disabled, store this status like this, get record of the row where the context menu was displayed from:
record.set('myDisabledState', true); // or false
It is important to store the disabled state (and not enabled), because when your grid initially is rendered, these values won't be in the records, so record.get('myDisabledState') will evaluate to FALSE, and that is the desired behaviour, if you want to start with every row being able to be selected.
3. Disable selection
Now you can add a beforeselect listener to your grid, see documentation. This listeners receives record as parameter, and if you return false from this listener, the selection will be canceled. So in this listener simply add:
listeners: {
beforeselect: function ( grid, record, index, eOpts ) {
return !record.get('myDisabledState');
}
}
4. Apply formatting - OPTIONAL
It is likely that you want to add different formatting for disabled rows, for example grey colour. The easiest way to do it is to add a custom CSS style to your Application.scss file:
.my-disabled-row .x-grid-cell-inner {
color: gray;
}
Finally add getRowClass configuration to your grid, it will receive the current record being rendered, and you can return the above custom CSS style when the row is disabled:
Ext.define('MyGrid', {
// your grid definition
,
viewConfig: {
getRowClass: function (record, rowIndex, rowParams, store) {
if (record.get('myDisabledState')) {
return "my-disabled-row";
}
}
}
});
In this last part, when row is not disabled, it will return nothing, so default formatting will be used.
I want to create checkbox group from store populated from an array.
Here is my store.
var checklistStore = new Ext.data.Store({
data: arraySubT,
fields: ['id', 'boxLabel']
});
and currently my checkbox group in only getting displayed from an array and not store.
xtype: 'checkboxgroup',
fieldLabel: 'Checklist',
columns: 1,
vertical: true,
listeners: {
change: function(field, newValue, oldValue, eOpts){
}
},
items: checkboxconfigs
However I want to make it displayed from store.How can I achieve this?
[EDIT]
For your and my convenience, I made a general component which you can use. It may need some tuning regarding the store events that it reacts to. Find it in this fiddle.
[/EDIT]
You have to do it manually:
renderCheckboxes:function() {
checkboxgroup.removeAll();
checkboxgroup.add(
checklistStore.getRange().map(function(storeItem) {
return {
// map the storeItem to a valid checkbox config
}
})
);
}
and repeat that over and over and over again when the store data changes. That is, you have to attach to the store events:
checklistStore.on({
load:renderCheckboxes,
update:renderCheckboxes,
datachanged:renderCheckboxes,
filterchange:renderCheckboxes,
...
})
Maybe you will overlook some events you have to attach to, but sooner or later you will have all edge cases covered.
Here is working fiddle for you.
Just loop through store data with Ext.data.Store.each() method and setup your checkbox group items.
var _checboxGroupUpdated = function() {
// Use whatever selector you want
var myCheckboxGroup = Ext.ComponentQuery.query('panel checkboxgroup')[0];
myCheckboxGroup.removeAll();
myStore.each(function(record) {
myCheckboxGroup.add({
boxLabel: record.get('fieldLabel'),
inputValue: record.get('value'),
name: 'myGroup'
});
});
}
// Add all listeners you need here
myStore.on({
load: _checboxGroupUpdated,
update: _checboxGroupUpdated,
datachanged: _checboxGroupUpdated
// etc
});
I'm using instances of a combobox for multiple user interfaces. So I need to reset combobox stores when user focuses on it. Combo's store is locally sorted; so I execute clearFilter() function of Ext.data.Store class - it works as it is expected except that typing query does not work anymore.
Here is my combobox configuration:
forceSelection: true,
autoSelect: false,
typeAhead: false,
triggerAction: 'all'
Store configuration:
autoLoad: false,
autoSync: false,
remoteSort: false,
proxy: {
type: 'ajax'
// other configs
}
Edit: fiddle
Note: Store of combobox used in fiddle is populated with static data when the original one uses an AJAX proxy
I think i solved your problem by looking at this post:
ExtJs: Search / Filter within a ComboBox
Adding a custom filter on each keystroke on the combobox seems to resolve your issue:
enableKeyEvents:true,
listeners: {
'keyup': function() {
this.getStore().clearFilter();
this.getStore().filter('name', this.getRawValue(), true, false);
},
'beforequery': function(queryEvent) {
queryEvent.combo.onLoad();
}
}
It is not beautiful, but i think it works. Take a look at this extended fiddle:
https://fiddle.sencha.com/#fiddle/teg
As I understand from your fiddle, you have trouble that implementing filter to different comboboxes. I think the problem you currently use same store for comboboxes. That's why you should also change the store for each combobox. Add this code to your fiddle.
combo1.getStore().filter('nationality', 'USA');
container.on('tabchange', function(tabpanel, newCard, oldCard, eOpts) {
combo2.getStore().clearFilter();
combo1.getStore().filter('nationality', 'USA');
if (combo2.getValue()) {
combo1.getStore().clearFilter();
combo1.getStore().filter('userId', combo2.getValue())
}
});
Btw, I put the filter before tabchange event because of static store. But, you might want to add the filter store listener if you use proxy(AJAX) store. Like this:
listeners: {
load: function(store, records) {
store.filter('nationality', 'USA');
}
}
Edit:
var store = combo1.getStore();
store.filter('nationality', 'USA');
container.on('tabchange', function(tabpanel, newCard, oldCard, eOpts) {
if (newCard.title == 'Panel 1') {
store.clearFilter();
store.filter('nationality', 'USA');
} else if (newCard.title == 'Panel 2') {
store.clearFilter();
// Or whatever filter because you have to change the store
// dynamically if you wanna use same store for different combos
}
});
Hope this helps. Good luck.
I'm implementing a multi-sort for a grid. I want to use the sortchange event for this, but I have to cancel the event so I can call the store with my own sorting config.
This didn't work:
oGrid.on('sortchange', function(oColumnContainer, oColumn, strSortOrder){
//...
return false;
})
Found a solution by myself.
When I need my multi-sort, I set all columns on the grid to sortable: false at creation time (seems like it is not possible to do this on-the-fly)
Then I set a on('headerclick, function() {...})` to all the column-objects right after I created the grid.
The sortable: false prevents the click event on the header from sorting the table, but later I can still call sort() on the store programatically with the saved columns.
var oGrid = Ext.create( 'Ext.grid.Panel', {
...
columns: [
{ ..., sortable: false }
]
});
for( i in oGrid.columns ) {
oGrid.columns[i].on('headerclick', function(){...});
}
I'd like a single event listener that fires whenever any field in a form (i.e., Ext.form.Panel) changes. The Ext.form.Panel class doesn't fire an event for this itself, however.
What's the best way to listen for 'change' events for all fields in a form?
Update: Added a 3rd option based on tip in comments (thanks #innerJL!)
Ok, looks like there are at least two fairly simple ways to do it.
Option 1) Add a 'change' listener to each field that is added to the form:
Ext.define('myapp.MyFormPanel', {
extend: 'Ext.form.Panel',
alias: 'myapp.MyFormPanel',
...
handleFieldChanged: function() {
// Do something
},
listeners: {
add: function(me, component, index) {
if( component.isFormField ) {
component.on('change', me.handleFieldChanged, me);
}
}
}
});
This has at least one big drawback; if you "wrap" some fields in other containers and then add those containers to the form, it won't recognize the nested fields. In other words, it doesn't do a "deep" search through the component to see if it contains form field that need 'change' listeners.
Option 2) Use a component query to listen to all 'change' events fired from fields in a container.
Ext.define('myapp.MyController', {
extend: 'Ext.app.Controller',
...
init: function(application) {
me.control({
'[xtype="myapp.MyFormPanel"] field': {
change: function() {
// Do something
}
}
});
}
});
Option 3) Listen for the 'dirtychange' fired from the form panel's underlying 'basic' form (Ext.form.Basic). Important: You need to make sure you must enable 'trackResetOnLoad' by ensuring that {trackResetOnLoad:true} is passed to your form panel constructor.
Ext.define('myapp.MyFormPanel', {
extend: 'Ext.form.Panel',
alias: 'myapp.MyFormPanel',
constructor: function(config) {
config = config || {};
config.trackResetOnLoad = true;
me.callParent([config]);
me.getForm().on('dirtychange', function(form, isDirty) {
if( isDirty ) {
// Unsaved changes exist
}
else {
// NO unsaved changes exist
}
});
}
});
This approach is the "smartest"; it allows you to know when the form has been modified, but also if the user modifies it back to it's original state. For example, if they change a text field from "Foo" to "Bar", the 'dirtychange' event will fire with 'true' for the isDirty param. But if the user then changes the field back to "Foo", the 'dirtychange' event will fire again and isDirty will be false.
I want to complement Clint's answer. There is one more approach (and I think it's the best for your problem). Just add change listener to form's defaults config:
Ext.create('Ext.form.Panel', {
// ...
defaults: {
listeners: {
change: function(field, newVal, oldVal) {
//alert(newVal);
}
}
},
// ...
});