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

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

Related

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

Extjs4 with DWRproxy

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.

Handling Subsidiary Views in Backbone.js

I have a basic Backbone application which obtain an array of JSON objects from a remote service and displays them: all good so far. However, each JSON object has an array of tags and I want to display the tags in a separate area of the webpage.
My question is: what is the most Backbone-friendly way of doing this? I could parse the existing data again in a second view, which is cleaner but takes up more computation (processing the entire array twice).
An alternative is gathering up the tag information in the primary view as it is working through the array and then passing it along to the subsidiary view, but then I'm linking the views together.
Finally, I'd like to filter based on those tags (so the tags will become toggle buttons and turning those buttons on/off will filter the information in the primary view); does this make any difference to how this should be laid out?
Bonus points for code snippets.
Hm. I'm not sure if this is the Backbone-friendly way, but I'll put the logic to retrieve a list of tags (I think that's what you meant by "parse") in the collection.
Both the main view and the subview will "listen" to the same collection, and the subview will just call collection.getTags() to get a list of tags it needs.
// Model that represents the list data
var ListDataModel = Backbone.Model.extend({
defaults: function() {
return {
name: null,
tags: []
};
}
});
// Collection of list data
var ListDataCollection = Backbone.Collection.extend({
model: ListDataModel,
initialize: function() {
var me = this;
// Expires tag collection on reset/change
this.on('reset', this.expireTagCache, this);
this.on('change', this.expireTagCache, this);
},
/**
* Expires tag cache
* #private
*/
expireTagCache: function() {
this._cachedTags = null;
},
/**
* Retrieves an array of tags in collection
*
* #return {Array}
*/
getTags: function() {
if (this._cachedTags === null) {
this._cachedTags = _.union.apply(this, this.pluck('tags'));
}
return this._cachedTags;
},
sync: function(method, model, options) {
if (method === 'read') {
var me = this;
// Make an XHR request to get data for this demo
Backbone.ajax({
url: '/echo/json/',
method: 'POST',
data: {
// Feed mock data into JSFiddle's mock XHR response
json: JSON.stringify([
{ id: 1, name: 'one', tags: [ 'number', 'first', 'odd' ] },
{ id: 2, name: 'two', tags: [ 'number', 'even' ] },
{ id: 3, name: 'a', tags: [ 'alphabet', 'first' ] }
]),
},
success: function(resp) {
options.success(me, resp, options);
},
error: function() {
if (options.error) {
options.error();
}
}
});
}
else {
// Call the default sync method for other sync method
Backbone.Collection.prototype.sync.apply(this, arguments);
}
}
});
var listColl = new ListDataCollection();
listColl.fetch({
success: function() {
console.log(listColl.getTags());
}
});
I guess two reasons for handling this in the collection:
It keeps the View code cleaner (This is given that we are not doing very complex logic in the tag extraction - It's just a simple _.pluck() and _.union().
It has 0 business logic involved - It can arguably belong to the data layer.
To address the performance issue:
It does go through the collection twice - However, if the amont of data you are consuming is too much for the client to process even in this case, you may want to consider asking the Backend to provide an API endpoint for this. (Even 500 pieces of data with a total of 1000 tags shouldn't bee too much for a somewhat modern browser to handle nowadays.)
Hmm. Does this help?
JSFiddle to go with this with the collection and the model: http://jsfiddle.net/dashk/G8LaB/ (And, a log statement to demonstrate the result of .getTags()).

Populating the value of a Label

I am using the MVC architecture (i have gone through the docs on MVC, but i am still lost here) and i need to know how to populate the records on to my Label. I know that i have to get this done by Stores.
I have loaded the values from the store, but unable to display it on my Panel for some reason. here's my code;
Most of the examples/books demonstrates how to display in a grid, but not a label (I know it has to be the same logic, but i am lost). And it shows how to write to a DB/JSON file and not display the values.
I need to display the COUNTRYNAME in the Label text. This is an example code that i am doing to understand this, can some one help me ?
Ext.define ('ProjectDisplayExample.model.Country',{
extend: 'Ext.data.Model',
//
fields:['countryid','countryname']
//
});
STORE
Ext.define('ProjectDisplayExample.store.Country',{
extend:'Ext.data.Store',
model:'ProjectDisplayExample.model.Country',
remoteGroup:true,
proxy: {
actionMethods : {
read : 'POST',
},
type: 'ajax',
url : '/server/country.php'
}
});
VIEW
Ext.define('ProjectDisplayExample.view.CountryWindow', {
extend: 'Ext.window.Window',
alias: 'widget.countrywindow',
...........
initComponent: function() {
var st = Ext.getStore('Country');
st.load();
this.items = [
{
items: [
{
xtype: 'panel',
region: 'north',
items: [{
xtype: 'label',
// NEED TO DISPLAY COUNTRT NAME FROM THE STORE HERE
}]
}]
}
UPDATE
var store = ... // Your store
store.on('load', function() {
// here store is loaded and you can do anything you want with it
console.log('store is loaded. number of records = ', store.getCount());
}, this, { single: true });
store.load; // I ADDED THIS LINE.................... <---------
UPDATE 2
this.items = [
{
items: [
{
xtype: 'panel',
region: 'north',
items: [{
xtype: 'label',
name : f
}]
}]
I will not post a code sample to exactly solve your question, but I will give you couple points:
Store contains array or records. So you can't just say give me country name from the store. You need first to get a record, for example: var r = store.getAt(0), and only after that you can get countyname field var f = r.get('countryname').
Load() method is asynchronous, so you can just execute it somewhere in the code and assume that for the very next line your store is ready. You need to subscribe to the load event, something like:
var store = ... // Your store
store.on('load', function() {
// here store is loaded and you can do anything you want with it
console.log('store is loaded. number of records = ', store.getCount());
}, this, { single: true });
store.load();
Labels as in xtype: label are actually very rarely used in ExtJs. What exactly are you trying to display in that panel? But anyhow... after you get data out of the store you can use something like update() or setValue() or any other method to update component.
Hope this helps...

Return extra data besides tree data from ExtJS TreeLoader dataUrl?

I asked this question in the Ext JS forums, but I received no responses, so I am asking here.
I have a TreePanel (code below) that uses a TreeLoader and an AsyncTreeNode. In my API method specified by the TreeLoader's dataUrl, I return a JSON array to populate the tree.
This works great, of course. However, I need to return an additional item--an integer--in addition to the array, and I need to display that value somewhere else in my UI. Is this possible? If not, what else would be a good solution?
Here's the code I have currently:
tree = new Ext.tree.TreePanel({
enableDD: true,
rootVisible: false,
useArrows: true,
loader: new Ext.tree.TreeLoader({
dataUrl: '/api/method'
}),
root: new Ext.tree.AsyncTreeNode()
});
I want to return one single integer value for the entire response--not per node. Basically my API method will create a database record, and I need to return a value from that database record.
EDIT Thanks to Mike, I have solved this problem. I extended the Ext.tree.TreeLoader class like so:
TreeLoaderWithMetaData = Ext.extend(Ext.tree.TreeLoader, {
processResponse : function(response, node, callback) {
var json = response.responseText;
try {
var o = eval("("+json+")");
metaData = o.shift();
node.beginUpdate();
for(var i=0, len=o.length; i<len; i++) {
var n = this.createNode(o[i]);
if (n) {
node.appendChild(n);
}
}
node.endUpdate();
if(typeof callback == "function") {
callback(this, node);
}
}
catch (e) {
this.handleFailure(response);
}
}
});
And then I can reference variables in the meta data like public members: metaData.my_variable1, metaData.my_variable2. My AJAX data from the server just has an extra array item:
[{"my_variable1":"value1","my_variable2":"value2"},{"id":"node1","text":"Node 1",children:[{"id":"node1nodeA","text":"Node 1 Node A"}]]
You need to override the processResponse function in TreePanel and then you'll be able to return whatever format JSON you'd like:
From the ExtJS forums:
http://www.extjs.com/forum/showthread.php?32772-Passing-JSON-string-from-Grails-to-populate-TreePanel
The code at the bottom of that thread will help you.
As far as i understood, you want to pass additional parametrs with json and display it somewhere else when tree is loaded.
In this case you can simply return from server modified JSON like this
[{
id: 1,
yourParmam : 'val',
text: 'A leaf Node',
leaf: true
},{
id: 2,
yourParmam : 'val',
text: 'A folder Node',
children: [{
id: 3,
yourParmam : 'val',
text: 'A child Node',
leaf: true
}]
}]
Then subscribe to even load : ( Object This, Object node, Object response ) and simply parse response to find out you parm and do whatever you need

Resources