The view looks like so:
Ext.define('GridGeneral.view.GridGeneralPanel',{
extend:'Ext.grid.Panel',
alias:'widget.GridGeneralPanel',
initComponent:function(){
var me = this;
Ext.applyIf(me,{
layout:'fit',
border:false,
columnLines:true,
loadMask:true,
viewConfig:{},
columns:[]
});
me.callParent(arguments);
}
});
The store is following:
Ext.define('GridGeneral.store.GridGeneralStore',{
extend:'Ext.data.BufferedStore',
model:'GridGeneral.model.GridGeneralModel',
constructor:function(cfg){
var me = this;
cfg = cfg || {};
me.callParent([
Ext.apply({
autoLoad:true,
leadingBufferZone:300,
pageSize:100,
proxy:{
type:'ajax',
url:'/modules/grid/data/',
extraParams:{
format:'json'
},
reader:{
rootProperty:'data',
totalProperty:'total'
}
}
},cfg)
])
}
});
This is the controller:
Ext.define('GridGeneral.controller.GridGeneralController',{
extend:'Ext.app.Controller',
models:['GridGeneralModel'],
stores:['GridGeneralStore'],
views:['GridGeneralPanel'],
refs:[{
ref:'gridPanel',
selector:'GridGeneralPanel'
}],
init:function(){
Ext.getStore('GridGeneralStore').addListener('metachange',
this.metaChanged, this);
},
metaChanged:function(store, meta){
this.getGridPanel().reconfigure(store, meta.columns);
}
});
And, finally, this is what the server returns to the client:
{"msg":"","total":"160","data":[{"stream_name":[...],"stream_size":[0],"_version_":"123456",
"id":"abcd-efgh-hijk-lmno"},.....],"metaData":{...}]}
Almost everything seems ok - grid is rendered, columns are rendered and so on. But the only problem I see is that store not loaded to the view - I see no grid rows at all. So, what am I doing wrong?
EDIT
So, nobody in the world can create mvc with dynamic infinite grid?
EDIT
It is obviously some bug. I moved to extjs 6.0 and now everything works fine.
Related
My server-side api follows a classic results-paging model, e.g.
/api/transactions/ => page 1 (10 items default limit)
/api/transactions/?p=2 => page 2
I want to build a infinite-scrolling system with Backbone views.
I already have non-paging collection+views setup. The parent view looks like this:
Backbone.View.extend({
initialize: function() {
this.collection = TransactionCollection;
this.fetch();
this.listenTo( this.collection, 'reset', this.renderEntries );
this.listenTo( this.collection, 'add', this.fetch );
this.rowViews = [];
this.render();
},
fetch: function() {
this.collection.fetch({ reset:true });
},
render: function() {
this.$el.html( template() );
this.renderEntries();
return this;
},
renderEntries: function() {
this.clearEntryRows();
this.collection.each(function(item) {
var row = new TransactionItemView( item );
this.rowViews.push( row );
this.$el.find('.entry-list').append( row.render().el );
}, this);
},
clearEntryRows: function() {
this.rowViews.forEach(function(v) {
if (v.close) v.close();
});
this.rowViews = [];
},
// ...
}
This is the relevant part of the view code (child views are simple item views, rendering themselves with a template).
The collection is still very basic:
var TransactionCollection = Backbone.Collection.extend({
model: Transaction,
url: '/api/transactions'
});
Now it's time to add pagination. I think I'm going to add a button "MORE...", after each renderEntries() call. That button will fetch for the next page (without resetting the collection) and another renderEntries is called. The this.clearEntryRows() will be moved to the reset callback.
My question is: how can I fetch the second page and add models without resetting the collection and intercept just that event, to render next entry pages?
I've read something about 'sync' event: in my understanding, 'reset' gets fired only when I fetch with reset:true, 'sync' gets fired every time I fetch the collection, anyway.
So, if this is correct, I can clear entry rows only on reset event and display rows in sync. But how can I display only the newly added (e.g. page 2) rows to my list?
I'm a little confused.
this.collection.fetch({ add: true, remove: false, merge: false, data: {p: 2} });
this allow you to fetch with specified get parameters and only add not existing entries to collection.
In your view:
initialize: function () {
this.listenTo(this.collection, 'add', handlerForRenderingNewEntries);
}
To render only new models, you can return them with specified attribute, extra property 'page' for example. Filter them by this attribute and send to rendrer.
Using the example from https://help.rallydev.com/apps/2.0rc2/doc/#!/guide/timebox_filtering for a timebox required app, how do I convert the cardBoard view into a grid?
This is the base code:
Ext.define('Rally.guide.ReleaseFilteredBoard', {
extend: 'Rally.app.TimeboxScopedApp',
scopeType: 'release',
onScopeChange: function(scope) {
if(!this.board) {
this.board = this.add({
xtype: 'rallycardboard',
storeConfig: {
filters: [scope.getQueryFilter()]
}
});
} else {
this.board.refresh({
storeConfig: {
filters: [scope.getQueryFilter()]
}
});
}
}
});
It seems that I can simply change the xtype to 'rallygrid' and based on docs it should work but it seems to need a model defined as well - how do I get the model details out of the timebox scope to feed into the grid?
You may wish to check out the example code for Rally.ui.grid.Grid.
Here's a quick sample of how one might apply the Timebox filter to a grid:
Ext.define('CustomApp', {
extend: 'Rally.app.App',
componentCls: 'app',
grid: null,
launch: function() {
var filters = [];
var timeboxScope = this.getContext().getTimeboxScope();
if(timeboxScope) {
filters.push(timeboxScope.getQueryFilter());
}
this.getFilteredStoryModel(filters);
},
onTimeboxScopeChange: function(newTimeboxScope) {
var newFilters = [];
var updatedTimeboxScope = this.getContext().getTimeboxScope();
if (this.grid) {
this.grid.destroy();
}
if (updatedTimeboxScope) {
newFilters.push(newTimeboxScope.getQueryFilter());
}
this.getFilteredStoryModel(newFilters);
},
getFilteredStoryModel: function(queryFilters) {
Rally.data.ModelFactory.getModel({
type: 'UserStory',
success: function(model) {
this.grid = this.add({
xtype: 'rallygrid',
model: model,
columnCfgs: [
'FormattedID',
'Name',
'Owner',
'Iteration'
],
storeConfig: {
filters: queryFilters
}
});
},
scope: this
});
}
});
To show the timebox filter, choose the type of filter you desire when first setting up your Custom Page in Rally. The "onTimeboxScopeChange" callback responds to events triggered by the timebox selector setup and configured on the Custom Page container itself. No code is needed to setup the timebox selector, rather you do it via the Rally UI when creating a new Custom Page:
Select the type of filter (Release or Iteration):
(1) Note that the filter shows on the "My New Custom Page" Container. (2) Any app that you add to "My New Custom Page" will then have the timebox filter available/applied:
Add Custom HTML App:
Paste in Code and Save:
Iteration-filtered Grid:
Alternatively, if you don't want a timebox filter that applies to an entire Custom Page container, you can elect to use Rally.ui.combobox.ReleaseComboBox or Rally.ui.combobox.IterationComboBox
In your App code itself, and manage the filtering via callbacks from either of these components. This filtering would be totally "within-app" and wouldn't rely on the Custom Page-wide timebox component.
I am running a weird problem when I try to set Grid Filter list dynamically.
Let me explain by my code snippets
I have a column with filter list is defined as
{
text : 'Client',
dataIndex : 'topAccount',
itemId : 'exTopAccount',
filter: {
type: 'list',
options:[]
}
}
I initialize list from store in 'viewready'
viewready: function(cmp,eOpts){
cmp.getHeaderCt().child('#exTopAccount').initialConfig.filter.options = clientsStore.collect('topAccount');
}
===> WORKS GOOD
Now, I have to build the new client store based on the records when user moves to next page. Therefore I build the store in the 'change' event of paging
listeners: {
'change' :function( toolbar, pageData, eOpts ) {
var store = Ext.StoreManager.get('ExceptionRecords');
clientsStore.removeAll(true);
store.each(function(record){
if(clientsStore.findRecord('topAccount',record.data.topAccount.trim()) == null ) {
clientsStore.add({topAccount: record.data.topAccount.trim()})
}
})
Ext.getCmp('exceptionGridContainer').view.refresh;
Ext.getCmp('exceptionGridContainer').view.getHeaderCt().doLayout;
console.log(clientsStore);
Ext.getCmp('exceptionGridContainer').view.getHeaderCt().child('#exTopAccount').initialConfig.filter.options = clientsStore.collect('topAccount');
}
}
I can now see the new data in clientsStore . But Grid filter list is not updated. still showing old data. I tried refresh,layout etc. Nothing helps
Any help will be appreciated
Thanks
Tharahan
Just changing the value of a property does not affect the component rendered or computed state. The menu is created when the list is first initialized. The first time you do that, it works because that's before the initialization, but the second time, that's too late.
If you can grab a reference to the instantiated ListFilter, I think you could force the recreation of the menu this way:
listFilter.menu = listFilter.createMenu({
options: [ ... ] // new options
// rest of the filter config
});
So, supposing you have a reference to your target grid, you could change the options for the column with dataIndex of "topAccount" by a call similar to this:
var listFilter = grid
.findFeature('filters') // access filters feature of the grid
.get('topAccount'); // access the filter for column
listFilter.menu = listFilter.createMenu({
options: [ ... ] // new options
// rest of the filter config
});
--- Edit ---
OK, complete example. Tested, working.
Ext.widget('grid', {
renderTo: Ext.getBody()
,height: 400
,features: [{
ftype: 'filters'
,local: true
}]
,columns: [{
dataIndex: 'a'
,text: 'Column A'
,filter: {
type: 'list'
,options: ['Foo', 'Bar']
}
},{
dataIndex: 'b'
,text: 'Column B'
},{
dataIndex: 'c'
,text: 'Column C'
}]
,store: {
fields: ['a', 'b', 'c']
,autoLoad: true
,proxy: {
type: 'memory'
,reader: 'array'
,data: [
['Foo', 1, 'Bar']
,['Bar', 2, 'Baz']
,['Baz', 1, 'Bar']
,['Bat', 2, 'Baz']
]
}
}
,tbar: [{
text: 'Change list options'
,handler: function() {
var grid = this.up('grid'),
// forget about getFeature, I read the doc and found something!
filterFeature = grid.filters,
colAFilter = filterFeature.getFilter('a');
// If the filter has never been used, it won't be available
if (!colAFilter) {
// someone commented that this is the way to initialize filter
filterFeature.view.headerCt.getMenu();
colAFilter = filterFeature.getFilter('a');
}
// ok, we've got the ref, now let's try to recreate the menu
colAFilter.menu = colAFilter.createMenu({
options: ['Baz', 'Bat']
});
}
}]
});
I was solving similar problem and answers to this question helped me a lot. Local List filter menu is in fact lazy loaded (only created when clicked) and I needed to set filter menu to be reloaded if the grid store has been reloaded with different data. Solved it by destroying of menu after each reload, so on next click menu is recreated:
var on_load = function() {
var grid_header = me.gridPanel.filters.view.headerCt
if (grid_header.menu) {
grid_header.menu.destroy();
grid_header.menu = null;
}
}
I have a extjs 4 grid panel with remote stores.
On the renderer of column there is function, which change ID to NAME.
Mostly times it works fine, but sometimes (~40%) the grid shows with empty columns.
I've tried to debug - the store is defined, but its items are empty.
To my mind store not loaded yet, or already destroyed (if it is possible).
How to correctly show grid, with full data?
There is my simplified code:
Loading needed components
Ext.Loader.setConfig({
enabled:true
});
Ext.Loader.setPath('Ext.ux', '/js/ux');
Ext.require([
'Ext.grid.*',
'Ext.data.*'
]);
Ext.onReady(function () {
Create models
Ext.define('Expense', {
extend:'Ext.data.Model',
fields:[
{name:'id', type:'number'},
{name:'cost_id', type:'string'},
{name:'comment', type:'string'}
]
});
Ext.define('Cost', {
extend:'Ext.data.Model',
fields:[
{name:'id', type:'string'},
{name:'name', type:'string'},
{name:'displayname', type:'string'}
]
});
Create stores
store = Ext.create('Ext.data.Store', {
autoDestroy:true,
model:'Expense',
autoLoad:true,
autoSync:true,
pageSize:30,
proxy:{
type:'ajax',
url:'/expenses/gettable',
reader:{
type:'json',
root:'data',
record:'Expense'
}, writer:{
type:'json'
},
api:{
create:'/expenses/create',
update:'/expenses/update',
destroy:'/expenses/delete'
}
}
});
costsStore = Ext.create('Ext.data.Store', {
autoDestroy:true,
model:'Cost',
autoLoad:true,
autoSync:true,
proxy:{
type:'ajax',
url:'/costs/gettable',
reader:{
type:'json',
record:'Cost'
}, writer:{
type:'json',
allowSingle:false
},
api:{
create:'/costs/create',
update:'/costs/update'
}
}
});
Configure grid panel
grid = Ext.create('Ext.grid.Panel', {
store:store,
autoSync:true,
columns:[
{
id:'comment',
header:'Задача',
dataIndex:'comment',
flex:5
},
{
id:'cost',
header:'Cost',
dataIndex:'cost_id',
flex:5,
editor:{
xtype:'combobox',
store:costsStore,
valueField:'id',
displayField:'name',
name:'cost_id'
},
renderer:function (value, meta, record) {
if (value != '') {
ind = costsStore.find('id', value);
elem = costsStore.getAt(ind);
if (elem) {
return elem.data['name'];
}
}
}
}
],
renderTo:'editor-grid'
});
});
I've tried to manually load stores on grid render event:
grid = Ext.create('Ext.grid.Panel', {
listeners:{
beforerender:function(){
store.load();
costsStore.load();
}
}
...
});
It is decrease empty table to ~10% cases, but not 0%...
The only way I've found is to set delay after loading all stores, and then show grid, something like this:
listeners:{
'load':function(){
window.setTimeout("getGrid()", 1000);
}
}
Where getGrid() is the function with creating grid.panel.
What I have to do, to show grid only after all stores loaded, or show error if data can't load?
As sra suggested - your problem seems to stem from using a store within a renderer, while you cannot be sure that store is loaded.
I have described what I think is the most elegant solution to this problem in this answer, with some code examples.
I am extending GridPanel with Ext.define() (Ext v4).
I need to get the row data when a grid row is double clicked. At this point I cannot even get the event listener working:
Ext.define('Application.usersGrid', {
extend: 'Ext.grid.GridPanel',
alias: 'widget.usersgrid',
viewConfig: {
listeners: {
dblclick: function(dataview, index, item, e) {
alert('dblclick');
}
}
},
...
What is wrong here?
If anyone needs the answer- this is the right way:
Ext.define('Application.usersGrid', {
extend: 'Ext.grid.Panel',
alias: 'widget.usersgrid',
viewConfig: {
listeners: {
itemdblclick: function(dataview, record, item, index, e) {
alert('itemdblclick');
}
}
},
...
http://dev.sencha.com/new/ext-js/4-0/api/Ext.grid.GridView#event-itemdblclick
You don't need to put the listener in the viewconfig. Here is my working configuration:
listeners : {
itemdblclick: function(dv, record, item, index, e) {
alert('working');
}
},
Another thing is, you seems to have used Ext.grid.GridPanel in the extend property. But in documentation it's Ext.grid.Panel. But even with gridpanel, everything seems to work fine.
I would suggest to use the Ext JS 4 terms as it might cause to application breakage later in other 4.x versions.
Now, if you are using the new MVC architecture, you will want to move these actions to the controller rather than the view. You can refer to the MVC Architecture guide for details.
With the MVC approach in ExtJS 4 there's another smart way too to define such handlers. Some example code:
Ext.define('App.controller.Documents', {
extend: 'Ext.app.Controller',
stores: ['Documents'],
models: ['Document'],
views: [
'document.List',
'document.Edit',
'document.Preview'
],
init: function() {
this.control({
/*
* a cool way to select stuff in ExtJS 4
*/
'documentlist': {
itemdblclick: this.editDocument
},
/*
* simple access to components
*/
'documentedit button[action=save]': {
click: this.updateDocument
},
});
},
editDocument: function(grid, record) {
var view = Ext.widget('documentedit');
view.down('form').loadRecord(record);
},
updateDocument: function(button) {
var win = button.up('window'), // new selection API
form = win.down('form'), // in ExtJS 4
record = form.getRecord(),
values = form.getValues();
record.set(values);
win.close();
}
});
listeners: {
select: 'xxxx',
itemdblclick: function (dv, record, item, index, e) {
var myBtn = Ext.getCmp('btnEdit');
myBtn.onClick();
}
},