Moving nodes in a tree in EXTJS 4 - extjs

Am working on moving nodes in a tree, and as they are moved, their ID's are updated in the database through C# in the back-end. Till now, i have managed to update the position of the node that is being moved, but after its moved, the position of the other nodes in the tree should also get updated. Could anyone tell me how to proceed with this?
itemmove : {
fn : function(v, oldParent, newParent, index, eOpts) {
var nodeID = v.data.id;
var oldParent = oldParent.data.id;
var newParent = newParent.data.id;
var index = index;
var level = v.data.level;
movenode(nodeID, oldParent, newParent, index, level);
}
}
function movenode(nodeID, oldParent, newParent, index, level) {
Ext.Ajax.request({
url : 'data/pages.aspx',
params : {
UserID : USER.UserID,
mode : 'MOVENODE',
currentNode : nodeID,
oldParentNode : oldParent,
newParentNode : newParent,
index : index,
level : level,
ProjDB : projDB
},
success : function() {
loadTREEst();
genMessage(LANG.Suc, LANG.SaveOK, 'tick', false);
},
failure : function() {
genMessage(LANG.Warn, LANG.GenWarn, 'warn', false);
}
});
}
So, i send the parameters to SQl, and then update the index of the node that has been moved.
For example, if i move a node from position 8 to 1, the index of the 8th node changes to 1 in the database, but the index of the first node remains at 1. And because of this, the tree also doesnt get updated. i want all the other nodes in the tree to get updated as well. So, in this case, the node in index 1 will become index 2, the node in index 2 will become index 3, and so on. Can anyone explain me how to do this.
Is there any other method other than autosync or sync to do this?
Thanks in advance.

Here is some sample code that works with 4.1:
Ext.define('BS.model.ItemCategory', {
extend: 'Ext.data.Model',
fields: [
{name: 'name' , type: 'string'},
{name: 'iconCls' , type: 'string', defaultValue: 'treenode-no-icon'},
{name: 'leaf' , type: 'boolean', defaultValue: false},
{name: 'expanded' , type: 'boolean', defaultValue: true, persist: false},
{name: 'index' , type: 'int'},
],
proxy: {
type: 'direct',
api: {
create: ItemCategories.Create,
read: ItemCategories.Read,
update: ItemCategories.Update,
destroy: ItemCategories.Delete,
},
},
});
You won't be having a direct proxy as your server isn't php, but just put the corresponding server scripts (see docs).
Then my store:
Ext.define('BS.store.ItemCategories', {
extend: 'Ext.data.TreeStore',
model: 'BS.model.ItemCategory',
autoSync: true,
root: {
text: 'Root',
id: 'NULL',
expanded: true
},
clearOnLoad: true,
});
And the view:
Ext.define('BS.view.admin.pages.item-categories.ItemCategories' ,
{
extend: 'Ext.tree.Panel',
alias : 'widget.item-categories-tree',
store: 'ItemCategories',
displayField: 'name',
rootVisible: false,
useArrows: true,
multiSelect: false,
singleExpand: false,
allowDeselect: true,
plugins: {
ptype: 'treeviewdragdrop',
},
});
That's pretty much it! When you move a node you'll see ExtJS sends an update request to the server with an array of all the nodes whose index (sort) has changed.

This is the code I had for 4.0.7, to my memory it solved this problem.
// This happens after drag and drop
onItemMove: function(aNode, aOldParent, aNewParent, aIndex, aOptions)
{
// Update the indeces of all the parent children (not just the one that moved)
for ( i = 0; i < aNewParent.childNodes.length; i++ )
{
aNewParent.childNodes[i].data.sort = i;
aNewParent.childNodes[i].setDirty();
}
this.mStore.sync();
},
Notice that you won't have mStore.sync() as you are sending your own request to the server, which only involves one record. By setting child nodes as dirty, sync sends more than one record to the server's update call (in my case, I'm using autosync and direct).
You should consider what happens when the user moves a node from a parent node to the parent's sibling - the index (sort) of some nodes in both the old and new parent will change. So you might end up with 6 records that need updating on the server.
All I'm saying is that given 4.1 already handles node moving correctly, you might be better off upgrading to 4.1 and take advantage of the native support for this than coding it yourself. If you stick to 4.0.7, just remember to consider that a moving a node could require the updating of many nodes.

Related

extjs treestore with proxy type of rest appending -1 to model name

Having a peculiar problem with TreeStore where the rest type treestore adds -1 to name of model which breaks my backend routes.
TreeStore is defined as
Ext.define('Gateway.store.Company', {
extend: 'Ext.data.TreeStore',
model: 'Gateway.model.Company',
proxy: {
type: 'rest',
url: 'http://gateway-email-mock/',
reader: {
type: 'json'
}
}
});
where company model is defined as
Ext.define('Gateway.model.Company', {
extend: 'Ext.data.Model',
fields: [
{name: 'id', type: 'int' },
{name: 'name', type: 'string' }
]
});
The view interested in the store is
Ext.define('Gateway.view.company.Company', {
extend: 'Ext.tree.Panel',
alias: 'widget.company-list',
store: 'Gateway.store.Company',
rootVisible: false
});
When loading the widget the store creates URL
http://gateway-email-mock/Gateway.model.Company-1
How does -1 get there after the model name? I do not suppose this is intentional?
Thank you for any tips.
Rixo is correct, that is the tree store trying to load its root node. The default id generator generates its ID in the following way
idgen: {
isGenerator: true,
type: 'default',
generate: function () {
return null;
},
getRecId: function (rec) {
return rec.modelName + '-' + rec.internalId;
}
}
Which means your root record is rec.modelName + '-' + rec.internalId, which is exactly the request that is being made: http://gateway-email-mock/Gateway.model.Company-1
The solution is to make sure you create a root node with an ID, so that the tree's store will make a request for the correct ID , right it's generating the root node for you and giving it an automatic ID. See http://docs-origin.sencha.com/extjs/4.2.2/source/TreeStore.html#Ext-data-TreeStore-method-load and http://docs-origin.sencha.com/extjs/4.2.2/#!/api/Ext.data.TreeStore-cfg-root

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

extjs tree empty folder node show root again

I try to show in a Tree panel a Files System.
I feed my tree with json data and it's work perfectly!
But when I have an empty folder, the tree folder (when it's deploy) show me the complete tree again.
In my server I send a json file which doesn't have children properties.
This is my tree code
Ext.onReady(function () {
var treeStore = Ext.create('Ext.data.TreeStore', {
proxy: {
type: 'ajax',
url: '/File/Tree'
},
root: {
text: 'Files root',
id: 'root',
expanded: true
}
});
var treeUp = Ext.create('Ext.tree.Panel', {
id: 'TreeFileSystem',
title: 'TEST',
useArrows: true,
store: treeStore,
rootVisible: false,
renderTo: 'Tree',
height: 350,
listeners: {
itemClick: function (view, record) {
}
}
});
});
and there is an example of my json code
[
{"cls":"first-level","expanded":"false","children":[
{"cls":"first-level","expanded":"false","children":[
{"leaf":"true","text":"..."},
{"leaf":"true","text":"..."}
],"text":"..."}
],"text":"..."},
{"text":"..."}]
Anybody has an idea for help me?
Thanks!
David
EDIT:
an other example of my JSON
[
{"path":"...","cls":"first-level","expanded":"false","children":[
{"path":"...","cls":"first-level","expanded":"false","children":[
{"path":"...","cls":"first-level","expanded":"false","children":[],"text":"BOBFOLDER1"},
{"path":"...","cls":"first-level","expanded":"false","children":[],"text":"BOBFOLDER2"},
{"path":"...","cls":"first-level","expanded":"false","children":[],"text":"BOBFOLDER3"}
],"text":"BOB"},
{"path":"...","cls":"first-level","expanded":"false","children":[
{"path":"...","expanded":"false","text":"OTHERFOLDER1"}
],"text":"OTHER"}
],"text":"20508322"}]
I explain with that -> BOBFOLDER1, BOBFOLDER2, BOBFOLDER3 contains files and don't have arrow (I can't deploy it and it's the good behaviour).
But OTHERFOLDER1 don't contains files and have an arrow which display root again (same of my picture i give in comments) it's the problem!
the node named OTHER, is shown as a non-leaf node. put "leaf":"true" for the corresponding JSON data.
ie
[
{"cls":"first-level","expanded":"false","children":[
{"cls":"first-level","expanded":"false","children":[
{"leaf":"true","text":"..."},
{"leaf":"true","text":"..."}
],"text":"..."}
],"text":"BOB"},
{"text":"OTHER","leaf":"true"}]
try setting putting loaded: true in all your nodes (or if you use a model config, create field in the model for "loaded" and default it to true.

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.

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