Extjs4 with DWRproxy - extjs

I use dwrproxy.js from https://code.google.com/p/extjs4dwr and I create a grid with store
Ext.onReady(function() {
Ext.define('Record', {
extend: 'Ext.data.Model',
fields : [
{name: 'clientName'},
{name: 'type'}
],
proxy : {
type: 'dwr',
dwrFunction : Manager.getList,
reader : {
type: 'json',
root: 'data',
totalProperty: 'count'
}
}
})
var store = Ext.create('Ext.data.Store', {
requires : ['Ext.ux.DwrProxy'],
model: 'Record'
});
var grid = new Ext.grid.GridPanel({
store : store,
columns: [
{header: "clientName", width: 260, sortable: true, dataIndex: 'clientName'},
{header: "type", width: 260, sortable: true, dataIndex: 'type'}
],
title:'Test view',
renderTo: 'container'
});
store.load();
});
Manager.getList looks
Manager.getList = function(p0, p1, callback) {
dwr.engine._execute(Manager._path, 'Manager', 'getList', p0, p1, callback);
}
And I receive data in dwr
throw 'allowScriptTagRemoting is false.';
//#DWR-INSERT
//#DWR-REPLY
var s0=[];var s2={};var s3={};
s2.clientName='Client1';s2.type='Type1';
s3.clientName='Client2';s3.type='Type2';
s1.descendingOrder=true;s1.pageNo=null;s1.pageSize=1;s1.sortField="lastEditTime";
dwr.engine._remoteHandleCallback('1','0',{data:s0,dataSize:2,fromIndex:0,fromIndexDisp:1,gridState:s1,metaData:null,moreData:null,pageNo:0,pageNumber:1});
Everything look good, but grid is still have a 'loading' status and there are no view.
Please, help.

I don't know anything about DWR, but by mocking your code we can see that the callback is passed as the first argument to your getList() method, but it expects it as the third argument.
The position of the callback argument will in fact depend on the dwrParams config option of the proxy:
// adding parameters if there are defined any in proxy
// configuration
if (typeof (me.dwrParams) === 'object') {
dwrParams = dwrParams.concat(me.dwrParams);
}
So, what you need to do is configure your params in the proxy:
proxy : {
type: 'dwr',
dwrFunction : Manager.getList,
// use params that are sensible for your function
dwrParams: [1,2],
// ... your reader config
}
Or I guess that would probably makes more sense to set these before loading your store instead:
store.proxy.dwrParams = [1,2];
store.load();
If you want to avoid this hackish way, you can override the proxy's code. For example, if you replace these lines (starting line 71 in my version):
// adding parameters if there are defined any in proxy
// configuration
if (typeof (me.dwrParams) === 'object') {
dwrParams = dwrParams.concat(me.dwrParams);
}
By these:
// adding parameters if there are defined any in proxy
// configuration
var loadParams = operation.dwrParams || me.dwrParams;
if (typeof (loadParams) === 'object') {
dwrParams = dwrParams.concat(loadParams);
}
You could then load your store in a clean way:
store.load({
dwrParams: [3,4]
});
As a side note, I saw an error in the DwrProxy's code. A bit further in the same function, there is this code that uses a dwrParams0 variable that does not exist:
case 'read':
me.dwrFunction.read.apply(null, dwrParams0);
break;
I guess the author probably meant dwrParams[0]. You may want to fix this if you ever need to use different functions for the different CRUD operation as in the DwrProxy example.

Related

Values not being added to combobox dynamically

I have a combobox whose values I need to populate via the json formatted string I am retrieving via AJAX call.
Here's the string which I have retrieved (stored in data)
{
"a2m":
[
"a2mMeeting",
"sugar"
]
}
The combobox must contain a2m (this is the only name in the string as of now)
Here's what I have been trying:
Approach 1:
this.initAjaxCall = Ext.Ajax.request({
url : 'indigo/restproxy/get/v1/applications/list',
method:'GET',
scope : this,
success : function(result, request) {
var data = Ext.decode(result.responseText);
Object.keys(data).forEach(function(key) {
console.log(key);
Ext.getCmp('appCombo').getStore().add({appName : key});
})
}
});
this.appcombo = Ext.create('Ext.form.field.ComboBox', {
id : 'appCombo',
store: this.appStore
});
this.appStore = Ext.create('Ext.data.Store', {
fields : [
{
name : 'appName',
type : 'string'
}
]
});
Approach 2:
this.appStore = Ext.create('Ext.data.Store', {
fields: ['appName'],
proxy: {
type: 'ajax',
url: 'indigo/restproxy/get/v1/applications/list',
method : 'GET'
},
listeners: {
load: function(store, records, successful, eOpts ) {
store.insert(0, {
'appName' : 'yellow' //Trying to populate randomly using this method.
})
}
}
})
this.appcombo = Ext.create('Ext.form.field.ComboBox', {
id : 'appCombo',
store: this.appStore
});
In both the cases the combobox doesn't load any value and I am unable to see any value in the dropdown menu.
EDIT 1: When I printed the store.getCount() in the console after adding one element, the value is shown as 1 (checked by adding two elements and it does show 2). This implies the values are being added to the store but not being shown in the combobox dropdown menu. Kindly suggest how to resolve this.
SOLUTION:
The solution as it turns out was by adding a statement queryMode : 'local'. I don't understand why this was creating an issue. Anyone willing to shed light on this is welcome to do so.
As you have proven the store is being populated. However you haven't set a display field on the combo box.
this.appcombo = Ext.create('Ext.form.field.ComboBox', {
id : 'appCombo',
store: this.appStore,
displayField: 'appName'
});
displayField: The underlying data field name to bind to this ComboBox. Defaults to: 'text'
valueField: The underlying data value name to bind to this ComboBox. Defaults to match the value of the displayField config.

EXTJS--treestore getCount() method returning undefined, any alternate methods to check if store has already loaded?

I have a card layout and on card "activate" event I load the store. To keep the store from loading every time that card is activated I check to see if getCount == 0 and if true, I load the store:
handleActivateGrid: function(){
if(this.myTreeGrid().getStore().getCount() == 0){
this.myTreeGrid().getStore().load();
}
I'm using this same approach elsewhere in my code and it works perfectly fine, the only difference is in this case its a Ext.data.TreeStore.
I debugged and getCount() was undefined, even AFTER the store had loaded.
Is there another approach or method I could use to implement the above?
Thanks
EDIT: just checked the docs, getCount() is not a method that exists for Ext.data.TreeStore, so that explains that
A TreeStore consists of a single root node, with child nodes.
To see if there are nodes under the root node, you can use the following
myTreeGrid().getStore().getRootNode().childNodes.length > 0
See http://docs-origin.sencha.com/extjs/4.2.2/#!/api/Ext.data.NodeInterface-property-childNodes
A more correct approach (since a load may not load any child nodes in theory) is to hookup a load event to the store, when it's initially created. From the load handler, you can set your isLoaded flag wherever it's convenient for your code.
Something like http://www.sencha.com/forum/showthread.php?91923-How-to-check-datastore-is-loaded/page2
var storeLoaded = false;
var store = Ext.create('Ext.data.TreeStore', {
proxy: {
type: 'ajax',
url: 'get-nodes.php'
},
root: {text: 'Ext JS',id: 'src', expanded: true},
listeners: {
load: function() {
storeLoaded = true;
}
}
});
var tree = Ext.create('Ext.tree.Panel', {
store: store,
renderTo: 'tree-div',
height: 300,
width: 250,
title: 'Files'
});
Just tried this code .
myTreeGrid().getStore().getRootNode().getChildAt(0) == undefined

How to populate an Ext.data.TreeStore without children or leaf information?

I have a service that returns an array of Things. A Thing simply has an id and a name.
I want to load these into an Ext.tree.Panel. For now, I've defined a data store that mimics a typical response from the service. You can play with a demo JSFiddle here.
Code included below as well:
Ext.define('Thing', {
extend: 'Ext.data.Model',
fields: ['id', 'name'],
});
var store = Ext.create('Ext.data.TreeStore', {
model: 'Thing',
// example service response (in reality, this would be JSON)
root: {
children: [
{
id: 1,
name: 'Thing 1',
},
{
id: 2,
name: 'Thing 2',
},
{
id: 3,
name: 'Thing 3',
},
],
},
listeners: {
append: function(thisNode, newChildNode, index, eOpts) {
if( !newChildNode.isRoot() ) {
newChildNode.set('text', newChildNode.get('name'));
}
},
},
});
Ext.create('Ext.tree.Panel', {
renderTo: Ext.getBody(),
store: store,
rootVisible: false,
});
As you can see, the service only returns a Thing's id and name, no tree node information like children or leaf, which is what an Ext.data.TreeStore normally expects.
I simply cannot change what the service returns as a response. I like that I can call the service and get a flat array of Things, without extraneous tree node information. There are many apps talking to this service that simply do not need such data, and I don't have permission from the powers-that-be to change it.
Unfortunately, without the children element defined for each Thing in the response, Ext raises the following error when I attempt to expand a node (you can produce this yourself in the JSFiddle):
Uncaught TypeError: Cannot call method 'indexOf' of undefined
So, my question is: how can I avoid having to send back children (or leaf) in my response and resolve this error? I'd simply like my Things to be loaded into the tree and to be expandable (yes, they can't be leaves).
Add those information by yourself!
Little example (You easily can try it in the console from your browser):
a = {b:23};
a.c = 25;
//a is now the same like {b:23, c:25}
In the afterRequest-method of the store you can for example do following:
proxy: {
type: 'ajax',
url: 'xxx',
listeners: {
exception: function(proxy, response, options) {
//error case
}
},
afterRequest: function(request, success) {
var resText = request.operation.response.responseText,
allThings = Ext.decode(resText), //Decodes (parses) a JSON string to an object
things = allThins.children, //Now we have an object an we can do what we want...
length = things.length;
for (var i = 0; i < length; i++) {
things[i].leaf = true;
things[i].expanded = true;
}
this.setRootNode(allThings);
}
}
This is just some pseudo-code, but it should work!
Set a debugger Statement (Link) and see what you are really getting back!
I hope this helps!
You might try iterating over your response store and creating children nodes.
Then append your children to your root node
store.each(function(rec) {
var childNode ;
//Create a new object and set it as a leaf node (leaf: true)
childNode = Ext.create('Model_Name', {
id: rec.data.id,
name: rec.data.name,
text : rec.data.name,
leaf : true,
});
// add/append this object to your root node
treepanel.getRootNode().appendChild(childNode );
});
Check this link for more details

Extjs4: How to share data between multiple stores or models?

I'm new to Ext and I'm struggling to figure out Models Stores and Proxies.
The server returns one large JSON object.
For example.
{
"responseHeader":{
"status":0,
"QTime":12,
"params":{
"facet":"true",
"facet.limit":"40"
}
},
"response":{
"numFound":3806,
"start":0,
"docs":[
{
//Lots of fields
"id":"1234",
...
//Some array properties
"testfield":[
"",
""
],
...
}
]
},
"facet_counts":{
"facet_queries":{
"email":3806
},
"facet_fields":{
"emailaddress":{
},
"subject":{
"candles":136,
"filter":130
},
"fromemail":{
},
//...
},
"facet_dates":{ },
"facet_ranges":{}
},
"highlighting":{
"some doc id":{
"emailtext":[ " Tel.: blah blah <em>blah</em>" ],
"combined":[ "<em>Email</em> To: blah blah blah" ]
}
}
}
I don't want to load this data more than once, I want to grab data from this object, for example the docs object, and put it into a grid. Then pull out another part to put into a selectbox.
How do I load this data once, yet create models and stores to give to grids and selectboxes, from it?
From what I read the proxy holds the servers response? So I tried creating a proxy out side of a store. Thinking I could use the same proxy with more than one store.
var myProxy1 = Ext.create('Ext.data.proxy.Proxy', {
type: 'ajax',
url : '../test',
reader: {
type: 'json',
root: 'responseHeader'
}
});
But when I pass myProxy1 to a store
Ext.define('Test', {
extend: 'Ext.data.Model',
fields: [
{name: 'status', type: 'int'},
{name: 'QTime', type: 'int'},
{name: 'param', type: 'auto'}
]
});
var myStore = Ext.create('Ext.data.Store', {
model: 'Test',
proxy: myProxy1,
autoLoad: true,
listeners:{
load: function( ths, records, successful, operation, eOpts ){
debugger;
}
}
});
It doesn't work. The load event is never fired. No data is loaded. I can see that the proxy made the request, I see the response from the server, but it doesn't load.
If I put the proxy inline it loads.
var myStore = Ext.create('Ext.data.Store', {
model: 'Test',
proxy:{
type: 'ajax',
url : '../test',
reader: {
type: 'json',
root: 'responseHeader'
}
},
autoLoad: true,
listeners:{
load:function( ths, records, successful, operation, eOpts ){
debugger;
}
}
});
I was thinking I could have one proxy, attach it to multiple stores, and just change the reader on it before I load the store.
You are pretty much there, and although I'm pretty sure you are understanding it all, for the benefit of others allow me to give an extended answer and a slightly modified solution to your problem.
Definitions:
The Model - primarily defines the fields a record has.
A Store - holds a collection of records.
A Proxy - facilitates server communication through a chosen method (Ajax, Direct, etc.) and maps CRUD (Create/Read/Update/Destroy) operations when such result from a change in the associated store or model.
A Reader - Tells a proxy how to interpret the data the server returns.
A Grid or Combobox - can display store records.
You scenario is not an uncommon one - while by default ExtJS loads each store separately, it is likely an application would prefer various stores to be loaded at once through a single read call; for example, when rendering one store-dependent component is dependent on another store.
Your code is not far off from achieving this, but here is how I do it. In effect, when a 'master' (Tasks) store loads, the server response also carries the data of a 'slave' (Tags) store, which is then manually loaded to that 'slave' store.
The 'slave' store (notice autoload: false and no read operation):
Ext.define('DL.store.Tags', {
extend: 'Ext.data.Store',
model: 'DL.model.Tag',
// Notice the tags are actually returned when the tasks are loaded and loaded into this store by the TasksStore.
autoLoad: false,
autoSync: true,
proxy: {
type: 'direct',
api: {
create: Tags.Create,
update: Tags.Update,
destroy: Tags.Destroy,
},
reader: {
type: 'json',
root: 'tags'
}
},
});
Then the 'master' store:
Ext.define('DL.store.Tasks', {
extend: 'Ext.data.TreeStore',
model: 'DL.model.Task',
autoLoad: true,
autoSync: true,
root: {
text: 'Root',
id: null,
expanded: true
},
proxy: {
type: 'direct',
api: {
create: Tasks.Create,
read: Tasks.Read,
update: Tasks.Update,
destroy: Tasks.Destroy,
},
},
onProxyLoad: function( aOperation )
{
// A read request for tasks will also return the user tags.
// So feed these tags into their store.
var iResult = aOperation.response.result,
iTagsStore = Ext.StoreManager.lookup( 'Tags' );
if ( Ext.isDefined( iResult.tags ) )
iTagsStore.loadRawData( iResult );
// We need this line for "Tasks" store to load its own data
this.callParent(arguments);
}
});
Basically all it does is it takes part of the server response and loads it manually to the 'slave' store.
The PHP server side code (for tasks read operation) involves:
return array(
'success' => true,
'children' => $iNodes,
'tags' => $iTags
);
Where children is the reader's root of the 'master' store, and tags is additional data that is then loaded into the 'slave' store.
I hope you can work how how to apply these concepts to your code.

Apply grid filter programmatically from function

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();
}

Resources