Related
I have a store :
var store = new Ext.data.JsonStore({
root: 'list',
fields: rec,
totalProperty:'totalCount',
pruneModifiedRecords:true
});
And want to do paging.
my paging toolbar :
var paging = new Ext.PagingToolbar({
pageSize : limit,
width : 600,
store : store,
displayInfo : true,
displayMsg : 'Total: {2}, (Show:{0}-{1})',
emptyMsg : "No data"
});
and my grid :
var grid = new Ext.grid.EditorGridPanel({
region:'center',
stripeRows: true,
frame: false,
border:false,
loadMask: {msg : 'Loading...'},
trackMouseOver:false,
store: store,
cm: cm,
sm: sm,
tbar:gridTbar,
bbar: paging,
viewConfig: {enableRowBody:true,emptyText: 'no data'}
});
And when button pressed load data from database :
var limit= 100;
function storeDoldur(){
Ext.Ajax.request({
url: '../project/listData.ajax',
params:{
date:date.getRawValue(),
start:0,
limit:limit
},
success:function(response,options){
var res= Ext.decode(response.responseText);
if(res.success){
store.loadData(res);
grid.getView().refresh();
grid.doLayout();
}
else{
Ext.MessageBox.alert("result",res.message);
}
globalMask.hide();
},
failure: function(response,options) {
globalMask.hide();
Ext.MessageBox.alert(error);
}
});
}
Here is the problem :
Total count is 108. First 100 resut is showing with success but when i press next page button ext says :
TypeError: this.proxy is undefined
...ueUrl(this.proxy,e)){c.params.xaction=e}this.proxy.request(Ext.data.Api.actions[...
How can fix this problem?
Edit :
var store = new Ext.data.JsonStore({
root: 'list',
**url: '../project/listData.ajax',**
fields: rec,
totalProperty:'totalCount',
pruneModifiedRecords:true,
autoLoad : false,
});
ajax request post this params : date,start,limit
I click second page button, in backend code database returns null because of date is null. Second page request params are start and limit values. Don't send date parameter??
In order for the paging toolbar to work, your store must be configured with a proxy with a url property, so that when you click the next\previous button on the toolbar the store can fetch the required page of data from the server.
In your code, your are making a separate Ajax request and so your store has no idea about the url to use for fetching the next page.
Take a look at the example here => http://docs.sencha.com/extjs/4.2.0/#!/example/grid/paging.html and look at the paging.js source code linked to at the top of that page. That should give you enough to proceed.
You should also listen to the store load event to handle the successful fetch of data.
EDIT
For ExtJS 3 you should make use of baseParams in your store config object (http://docs.sencha.com/extjs/3.4.0/#!/api/Ext.data.Store-cfg-baseParams). You can also set these dynamically, so in your case in your storeDoldur function rather than the Ajax approach you would do something like this:
var limit= 100;
function storeDoldur(){
store.setBaseParam('date', date.getRawValue());
store.load({
params: {
start: 0,
limit: limit
},
callback: function() {
}
});
}
Again, do not use an Ajax request, instead use a properly configured store with a proxy as that's what the paging toolbar expects in order to work properly. In the edit above, setting an extra base parameter is necessary so it gets passed in subsequent request when using the paging toolbar next\previous buttons.
Notice when calling load on the store, I have added an empty stub for a callback function which you can use to handle the response further when its returned back from the server - http://docs.sencha.com/extjs/3.4.0/#!/api/Ext.data.Store-method-load
You wont need to use the code in your success handler from the Ajax request you originally used, since loading data (including other pages using the paging toolbar) into the store will cause the grid to update automatically.
Using Ext.ux.grid.FiltersFeature, I have remote filters and I am trying to write a function to apply a date filter on a grid column programmatically (rather than clicking on the filter drop down menu in the column header). The first time I run the function the grid store gets reloaded without the filter. When I run the function a second time (and every time thereafter) it works totally fine, the store reloads with the filters. Here is the gist of the function I have:
// a filter object for testing
aFilter = {type: 'date', field: 'a_date_field', comparison: 'gt', value: '2012-03-08 00:00:00'}
var grid = Ext.create('Ext.grid.Panel', {
store: store,
features: [{
ftype: 'filters',
}],
columns[{
header: 'ID',
dataIndex: 'id',
itemId: 'id',
width: 40,
}, {
xtype: 'datecolumn',
header: 'Date',
dataIndex: 'a_date_field',
itemId: 'a_date_field',
width: 75,
format:'j-M-Y',
filterable: true
}],
listeners: {
'afterrender': function() {
// Need to create the filters as soon as the grid renders
// rather than waiting for the user to click on the header
grid.filters.createFilters();
}
},
bbar: [{
text: 'Do a filter',
handler: function() {
// get the filter that is attached to the grid
var gridFilter = grid.filters.getFilter(aFilter.field);
// have to do this to create a menu for this filter
gridFilter.init({dataIndex: aFilter.field, type: aFilter.type, active: true});
// if this column is a date filter column
if (gridFilter.type == 'date') {
var dateValue = Ext.Date.parse(aFilter.value, 'Y-m-d H:i:s');
if (filter.comparison == 'gt') {
gridFilter.setValue({after: dateValue});
} else {
gridFilter.setValue({before: dateValue});
}
}
}
}
});
I also found that this function works the first time if I click on any grid header menu before I run the function.
I've been trying to find out what changes are made to the grid which make the filter work after the first attempt fails or what clicking on any grid header does to make it work. But nothing I add seems to fix it so it will run the first time. Has anyone implemented this successfully?
I have workaround:
bbar: [{
text: 'Do a filter',
handler: function() {
var grid = this.up('grid');
var dateValue = Ext.Date.parse(aFilter.value, 'Y-m-d H:i:s');
var value = aFilter.comparison == 'gt' ? {after: dateValue} : {before: dateValue};
var gridFilter = grid.filters.getFilter(aFilter.field);
if (!gridFilter) {
gridFilter = grid.filters.addFilter({
active: true,
type: aFilter.type,
dataIndex: aFilter.dataIndex,
});
gridFilter.menu.show();
gridFilter.setValue(value);
gridFilter.menu.hide();
} else {
gridFilter.setActive(true);
}
Ext.Function.defer(function(){
gridFilter = grid.filters.getFilter(aFilter.field);
gridFilter.setValue(value);
}, 10);
}
}]
As you can see I actually apply filter 2 times.
As an update, I expanded this function and modified it to work with ExtJS 4.1.1
Here is an example of the function to set grid filters dynamically (without the user needing to click on the menu items). Afterwards, the filtered items will be visible to the user in the grid column header menus as if he clicked on them and set them manually.
The "grid" argument is a grid with FiltersFeature that you want to filter. The other argument is an array of "filter" objects (I'll show an example below), the function simply applies all the passed "filter" objects to the grid.
doGridFilter: function(grid, filters) {
// for each filter object in the array
Ext.each(filters, function(filter) {
var gridFilter = grid.filters.getFilter(filter.field);
gridFilter.setActive(true);
switch(filter.data.type) {
case 'date':
var dateValue = Ext.Date.parse(filter.data.value, 'm/d/Y'),
value;
switch (filter.data.comparison) {
case 'gt' :
value = {after: dateValue};
break;
case 'lt' :
value = {before: dateValue};
break;
case 'eq' :
value = {on: dateValue};
break;
}
gridFilter = log.filters.getFilter(filter.field);
gridFilter.setValue(value);
gridFilter.setActive(true);
break;
case 'numeric':
var value;
switch (filter.data.comparison) {
case 'gt' :
value = {gt: filter.data.value};
break;
case 'lt' :
value = {lt: filter.data.value};
break;
case 'eq' :
value = {eq: filter.data.value};
break;
}
gridFilter = log.filters.getFilter(filter.field);
gridFilter.setValue(value);
gridFilter.setActive(true);
break;
case 'list':
gridFilter = log.filters.getFilter(filter.field);
gridFilter.menu.setSelected(gridFilter.menu.selected, false);
gridFilter.menu.setSelected(filter.data.value.split(','), true);
break;
default :
gridFilter = log.filters.getFilter(filter.field);
gridFilter.setValue(filter.data.value);
break;
}
});
}
Here's an example of a "filter" object array.
// an example of a "filters" argument
[{
field: 'some_list_column_data_index',
data: {
type: 'list',
value: 'item1,item2,item3,item4,item5,item6,item7'
}
}, {
field: 'some_date_column_data_index',
data: {
type: 'date',
comparison: 'gt',
value: '07/07/2007'
}
}]
One caveat, you need to "create" the filters manually before using this function. Normally FiltersFeature grid filters are "created" the first time a user clicks on one of them, that may not happen if the user just wants to apply one of these predefined filters.
That can be handled easily by including this afterrender listener in the gridpanel.
listeners: {
// must create the filters after grid is rendered
afterrender: function(grid) {
grid.filters.createFilters();
}
}
Just add
filter: true
to grid columns description like this:
me.columns = [
{header:"Name", dataIndex:"name", editor:"textfield", filter: true},
];
if you want to get the filter work after the first attempt, first instance create.
Here is something that may be worth looking into. It seems that the filters plugin is listening for menucreate event to initialize the filters. I wonder if menu create event is deferred until necessary and hence the filters don't get initialized?
/**
* #private Handle creation of the grid's header menu. Initializes the filters and listens
* for the menu being shown.
*/
onMenuCreate: function(headerCt, menu) {
var me = this;
me.createFilters(); //<------
menu.on('beforeshow', me.onMenuBeforeShow, me);
},
Do you want to apply grid filter or may be store.filter() capability would suit you better? In this case just filter the store, and grid will display filtered records.
I discovered another way to implement this. It appears that grid features are only bound to the grid after the grid is rendered. This means that any setup of the filter will not take effect until after the grid is rendered. The initial load of the store appears to be initiated before the grid is rendered.
I solved my problem by creating my store with a memory proxy containing no data.
me.store = Ext.create('Ext.data.Store', {
model: 'SummaryData',
data: [],
proxy: {
type: 'memory',
reader: 'array'
},
remoteSort: true,
remoteFilter: true
});
Then set up an afterrender handler on the grid to poke in the correct proxy and initiate a load of the store.
afterrender: function () {
var me = this;
me.store.setProxy({
type: 'ajax',
url : '/print_unallocated/change_site__data',
reader: {
type: 'json',
root: 'rows'
},
listeners: {
exception: function (proxy, response) {
Max.reportException(response);
}
}
});
me.filters.createFilters();
me.store.load();
},
In the source, you can see a comment related to this.
// Call getMenu() to ensure the menu is created, and so, also are the filters. We cannot call
// createFilters() withouth having a menu because it will cause in a recursion to applyState()
// that ends up to clear all the filter values. This is likely to happen when we reorder a column
// and then add a new filter before the menu is recreated.
me.view.headerCt.getMenu();
You can test whether the menu has been created before applying your filter. If it hasn't, do it yourself.
if(!grid.getView().headerCt.menu){
grid.getView().headerCt.getMenu();
}
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)
I have an EXT form that opens a window containing an GridPanel where the data store needs to get updated each time the form is submitted. The grid displays a list of files that are uploaded from the client computer. All of this works fine until I close the window and select a different group of files. The problem I am having is when the window containing the grid is closed and then re-opened, it is displaying the first set of files loaded previously instead of the new list of files submitted from the form. Hopefully this makes sense.
I have tried grid.getView().refresh() but that doesn't update the grid. Below is my code for the window, form, grid, and JSON store. If anyone has seen this before I would sure appreciate some advice. (apologies for the code formatting...had a lot of trouble getting it to be readable this text box)
//window that holds grid
SPST.window.POGCheckInWindow = Ext.extend(SPST.window.WindowBaseCls, {
id: 'POGCheckInWindow'
,initComponent:function() {
var config = {
title: 'Check In POG Files'
,width : 1000
,height : 500
,maximizable: false
,shadow : false
,closeable: true
,closeAction: 'hide'
,items: [{
xtype : 'checkin_results_grid'
,id : 'POGCheckInGrid'
,height : 450
,width : 990
,frame : true }
]};
Ext.apply(this, Ext.apply(this.initialConfig, config));
SPST.window.POGCheckInWindow.superclass.initComponent.apply(this, arguments);
}
});
Ext.reg('pog_checkin_window',SPST.window.POGCheckInWindow);
//function to submit the form, load the store and open the window containing the gridpanel
checkin:function() {
if (this.getForm().isValid()){
this.getForm().submit({
url: 'components/POG.cfc?method=uploadPOGTemp' + '&dsn=' + SPST_dsn
,method: 'GET'
,scope:this
,success: function(form, action){
var chkinwin = new SPST.window.POGCheckInWindow();
Ext.getCmp('POGCheckInGrid').getStore().load({params: {dsn: SPST_dsn,
pogfiles: action.result.pogfiles, project_id: action.result.project_id}
,callback: function(rec, options, success){
Ext.getCmp('POGCheckInGrid').getView().refresh();
}
});
chkinwin.show();
}
,failure:this.showError
,waitMsg:'Uploading files...'
});
}
}
// create the data store for grid
var chkstore = new Ext.data.JsonStore({
// JsonReader configuration
root : 'jsonlist'
,fields : chkfields
,id : 'chkstoreid'
// JsonStore configuration
,autoLoad : true
,remoteSort : true
,autoDestroy: true
,sortInfo: {
field: 'POGNAME',
direction: 'ASC'
}
,url : 'components/POG.cfc?method=getPOGTempFiles' + '&dsn=' + SPST_dsn
});
//grid that loads inside ext window
SPST.grid.POGCheckInGrid = Ext.extend(SPST.grid.GridPanelBaseCls, {
view: new Ext.grid.GridView({
getRowClass: function(rec, idx, rowPrms, ds)
{
return rec.data.VERSION_DSC === 'INVALID VERSION#' ? 'invalid-cls' : '';
}
}),
initComponent : function() {
var config = {
colModel : chkcolModel
,selModel : chkselModel
,store : chkstore
,autoDestroy: true
,xtype : 'checkin_results_grid'
,buttons: [
{
text: 'Upload'
,handler: this.loadPOGCheckin
,scope : this
},
{
text: 'Cancel'
,handler: this.checkinwindowclose
,scope : this
}
]
}
Ext.apply(this, Ext.apply(this.initialConfig, config));
SPST.grid.POGCheckInGrid.superclass.initComponent.apply(this, arguments);
}
,onRender : function() {
this.getBottomToolbar().bindStore(chkstore);
SPST.grid.POGCheckInGrid.superclass.onRender.apply(this, arguments);
}
From what I understood, you don't need to only refresh the view, but also reload the store according to the different set of files that you selected..
So, instead of doing
grid.getView().refresh() use grid.getStore().reload([new-options-if-required]);.. This will reload the store according to the options passed, if any, or reload the store according to the last load options.
This should be fairly simple but I haven't found a way to do it yet.
I am using a ExtJs v.3.3.
I have a grid panel that allows record deletion with context menu.
The grid has a paging toolbar that is attached to the panel store.
The deletion process sends an ajax request to the server, on success I remove the record from the store (using the remove method).
The thing is that the paging toolbar does not reflect the change in the store , that is the total amount of records is unchanged until the store is reloaded.
Is there any way to set the total amount of records in the paging toolbar?
Thanks
This works like a charm for ExtJs ver 4.1.3.
gridStore.add(record); //Add the record to the store
gridStore.totalCount = gridStore.count(); //update the totalCount property of Store
pagingToolbar.onLoad(); //Refresh the display message on paging tool bar
Are you not able to return the totalProperty value in the response after the data has been deleted in the DB?
EDIT:
You'll need to get your response constructed properly first. This is how it should look according to the API Docs for the Paging Toolbar.
If using store's autoLoad configuration:
var myStore = new Ext.data.Store({
reader: new Ext.data.JsonReader({
totalProperty: 'results',
...
}),
...
});
var myStore = new Ext.data.Store({
autoLoad: {params:{start: 0, limit: 25}},
...
});
The packet sent back from the server would have this form:
{
"success": true,
"results": 2000,
"rows": [ // *Note: this must be an Array
{ "id": 1, "name": "Bill", "occupation": "Gardener" },
{ "id": 2, "name": "Ben", "occupation": "Horticulturalist" },
...
{ "id": 25, "name": "Sue", "occupation": "Botanist" }
]
}
This worked fine for me:
me.gridStore.add(data);
// Manually update the paging toolbar.
me.gridStore.totalCount = 500;
me.pagingToolbar.onLoad();
I had a similar problem when getting results from a third party api which had a separate url for the item count. I created a new class inheriting from the pagingtoolbar with an additional updatePager() function:
updatePager : function(){
var me = this,
pageData,
currPage,
pageCount,
afterText,
count,
isEmpty;
count = me.store.getCount();
isEmpty = count === 0;
if (!isEmpty) {
pageData = me.getPageData();
currPage = pageData.currentPage;
pageCount = pageData.pageCount;
afterText = Ext.String.format(me.afterPageText, isNaN(pageCount) ? 1 : pageCount);
} else {
currPage = 0;
pageCount = 0;
afterText = Ext.String.format(me.afterPageText, 0);
}
Ext.suspendLayouts();
me.child('#afterTextItem').setText(afterText);
me.child('#inputItem').setDisabled(isEmpty).setValue(currPage);
me.child('#first').setDisabled(currPage === 1 || isEmpty);
me.child('#prev').setDisabled(currPage === 1 || isEmpty);
me.child('#next').setDisabled(currPage === pageCount || isEmpty);
me.child('#last').setDisabled(currPage === pageCount || isEmpty);
me.child('#refresh').enable();
me.updateInfo();
Ext.resumeLayouts(true);
if (me.rendered) {
me.fireEvent('change', me, pageData);
}
}
});
I added an itemId to it when adding to the dock
dockedItems: [{
xtype: 'dynamicpagingtoolbar',
itemId: 'pager_id',
dock: 'bottom',
store: 'CompoundPharmacologyPaginatedStore',
displayInfo: true
}],
I added a setTotalCount() function to the associated store:
setTotalCount: function(count) {
this.totalCount = count;
}
Then when you want to update it call store.setTotalCount(total) and then pager.updatePager(). Remember that you will have get the pager first using something like
pager = grid_view.down('#pager_id');
"The deletion process sends an ajax request to the server, on success I remove the record from the store (using the remove method)..." - this suggest that you got method that handle "delete" action - and if you using Ext.PagingToolbar - just add one more line like this:
(this).YourPagingToolbar.doRefresh()
I put (this) in () because you did not provide any code example so I not sure how you defined it
store.totalLength = store.totalLength - 1;
this would change the number of total rows in the store, but I am not sure if this change would be reflected by the paging toolbar.
I had a similar situation when trying to use multiple paging toolbars (top/bottom of grid). The only place in the paging toolbar that updates the display gets called on store 'load'. So you can fire the event manually (but beware of unintended consequences!). In my case this worked well when run from the beforechange listener of one of my toolbars:
myStore.fireEvent('load', myStore, myStore.data.items, myStore.lastOptions);
Or ... you could override or extend the PagingToolbar to add a public method which would call or override the onLoad function
If you have multiple pages in paging toolbar, and perform insert/remove operation locally from store then use below snippet.
updatePagingToolbar: function (pagingToolbar) {
var store = pagingToolbar.getStore()
, affectedChanges = store.getCount() - store.config.pageSize;
if (pagingToolbar.store.totalCount > store.config.pageSize)
pagingToolbar.store.totalCount += affectedChanges;
else
pagingToolbar.store.totalCount = store.getCount();
pagingToolbar.onLoad();
}