Drag and drop using Ext JS with the CakePHP Tree Behavior
If you've ever needed to store nested or recursive data you'll realise how much of a pain it can be. Fortunately for us cake bakers we no longer need to shy away from these data structures to maintain our sanity. With CakePHP's Tree Behavior you can easily add this functionality to any of your models!
Getting data in and out of our tree models is fairly easy using the methods provided, but re-ordering existing data can be frustrating without a GUI. Enter stage left... Ext JS!
This tutorial will explain how to use the Ext JS Tree component to allow you to re-order your tree data using drag-and-drop operations.
Requirements
There are a few dependencies this tutorial relies upon.
Important: Ext JS is a massive javascript library, but you can create a custom build to contain only the functionality required. I have included a text file in the downloadable source files outlining the build options I selected when creating this tutorial.
To make things easy I have included all of the files in a zip archive, including a cut-down version of the Ext JS library.
Download the source files used in this tutorial
If you wish to use both Prototype AND Ext JS, my sample files will not work for you!
Setting up the database and model
The Tree Behavior uses three fields to describe the structure of the data - parent_id, lft and rght. It is possible to customise these but I'm sticking with the defaults.
The example I will use in this tutorial is employee heirarchy.
CREATE TABLE `employees` (
`id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`parent_id` int(10) UNSIGNED DEFAULT NULL,
`lft` int(10) UNSIGNED DEFAULT NULL,
`rght` int(10) UNSIGNED DEFAULT NULL,
PRIMARY KEY (`id`),
KEY parent_id (parent_id),
KEY rght (rght),
KEY lft (lft, rght)
) ENGINE=MyISAM;
/app/model/employee.php
<?php
class Employee extends AppModel {
var $name = 'Employee';
var $actsAs = array('Tree');
var $order = 'Employee.lft ASC';
}
?>
Now your table is setup, you need some data to play with. The easiest way to achieve this is to use a temporary model method and controller action. Add the following method to your Employee model.
/app/model/employee.php
function populate(){
$this->create(array('name' => 'Harry Potter'));
$this->save();
$parent_id = $this->id;
$this->create(array('parent_id' => $parent_id, 'name' => 'Ron Weasley'));
$this->save();
$this->create(array('parent_id' => $parent_id, 'name' => 'Hermione Granger'));
$this->save();
$this->create(array('parent_id' => $parent_id, 'name' => 'Adam Royle'));
$this->save();
$this->create(array('parent_id' => $this->id, 'name' => 'Lord Voldemort'));
$this->save();
$this->create(array('name' => 'Albus Dumbledore'));
$this->save();
$parent_id = $this->id;
$this->create(array('parent_id' => $parent_id, 'name' => 'Professor McGonagall'));
$this->save();
$this->create(array('parent_id' => $this->id, 'name' => 'Professor Flitwick'));
$this->save();
$this->create(array('parent_id' => $parent_id, 'name' => 'Severus Snape'));
$this->save();
$this->create(array('parent_id' => $parent_id, 'name' => 'Hagrid'));
$this->save();
}
It's time to create your controller. We will add some more meat to it later.
/app/controllers/employees_controller.php
<?php
class EmployeesController extends AppController {
var $name = 'Employees';
var $components = array('RequestHandler','Security');
var $helpers = array('Form','Html','Javascript');
function populate() {
$this->Employee->populate();
echo 'Population complete';
exit;
}
function index() {
}
}
?>
Try accessing /employees/populate/ in your browser and you should notice a few records appear in your employees table, with the lft and rght fields automatically populated with their correct values. You should disable or remove the populate() method once you have imported the data.
Displaying Ext JS Tree using CakePHP Tree Behavior
You need to include the Ext JS javascript and css files in your layout.
/app/views/layouts/default.ctp
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="content-type" content="text/html; charset=iso-8859-1" />
<title><?php echo h($title_for_layout) ?></title>
<?php echo $html->css('/js/ext-2.0.1/resources/css/ext-custom.css'); ?>
<?php echo $javascript->link('/js/ext-2.0.1/ext-custom.js'); ?>
</head>
<body>
<div style="margin:40px;">
<?php echo $content_for_layout ?>
</div>
</body>
</html>
Create a view for your index action. Ideally the majority of this would be moved to an external javascript file, however to keep this example simple I am including it as inline code.
/app/views/employees/index.ctp
<script type="text/javascript">
Ext.BLANK_IMAGE_URL = '<?php echo $html->url('/js/ext-2.0.1/resources/images/default/s.gif') ?>';
Ext.onReady(function(){
var getnodesUrl = '<?php echo $html->url('/employees/getnodes/') ?>';
var reorderUrl = '<?php echo $html->url('/employees/reorder/') ?>';
var reparentUrl = '<?php echo $html->url('/employees/reparent/') ?>';
var Tree = Ext.tree;
var tree = new Tree.TreePanel({
el:'tree-div',
autoScroll:true,
animate:true,
enableDD:true,
containerScroll: true,
rootVisible: true,
loader: new Ext.tree.TreeLoader({
dataUrl:getnodesUrl
})
});
var root = new Tree.AsyncTreeNode({
text:'Employees',
draggable:false,
id:'root'
});
tree.setRootNode(root);
tree.render();
root.expand();
});
</script>
<div id="tree-div" style="height:400px;"></div>
The above snippet covers the basics needed to get a tree to display using Ext JS. If you visit /employees/ you will see it only shows the root node Employees. This is because you haven't defined the /employees/getnodes/ action that is specified in the javascript. Let's implement this now.
/app/controllers/employees_controller.php
function getnodes(){
// retrieve the node id that Ext JS posts via ajax
$parent = intval($this->params['form']['node']);
// find all the nodes underneath the parent node defined above
// the second parameter (true) means we only want direct children
$nodes = $this->Employee->children($parent, true);
// send the nodes to our view
$this->set(compact('nodes'));
}
Create a view for the getnodes method. This constructs an array to output as a JSON string, using CakePHP's Javascript Helper.
/app/views/employees/getnodes.ctp
<?php
$data = array();
foreach ($nodes as $node){
$data[] = array(
"text" => $node['Employee']['name'],
"id" => $node['Employee']['id'],
"cls" => "folder",
"leaf" => ($node['Employee']['lft'] + 1 == $node['Employee']['rght'])
);
}
echo $javascript->object($data);
?>
Finally, you need to create an ajax view.
/app/views/layouts/ajax.ctp
<?php
echo $content_for_layout;
Configure::write('debug', 0);
?>
Refresh your browser and you should see your data being loaded correctly as you open each folder in the tree. Try dragging the nodes around. If you refresh the page, you'll find the tree is restored to it's original structure. Now it's time to add some ajax callbacks to deal with saving these drag and drop operations.
Updating the tree structure using Ajax
Let's get cooking! Add the following methods to your controller. I've defined a beforeFilter method that implements some basic security, but the reorder and reparent methods do all the work.
/app/controllers/employees_controller.php
function beforeFilter(){
parent::beforeFilter();
// ensure our ajax methods are posted
$this->Security->requirePost('getnodes', 'reorder', 'reparent');
}
function reorder(){
// retrieve the node instructions from javascript
// delta is the difference in position (1 = next node, -1 = previous node)
$node = intval($this->params['form']['node']);
$delta = intval($this->params['form']['delta']);
if ($delta > 0) {
$this->Employee->movedown($node, abs($delta));
} elseif ($delta < 0) {
$this->Employee->moveup($node, abs($delta));
}
// send success response
exit('1');
}
function reparent(){
$node = intval($this->params['form']['node']);
$parent = intval($this->params['form']['parent']);
$position = intval($this->params['form']['position']);
// save the employee node with the new parent id
// this will move the employee node to the bottom of the parent list
$this->Employee->id = $node;
$this->Employee->saveField('parent_id', $parent);
// If position == 0, then we move it straight to the top
// otherwise we calculate the distance to move ($delta).
// We have to check if $delta > 0 before moving due to a bug
// in the tree behavior (https://trac.cakephp.org/ticket/4037)
if ($position == 0){
$this->Employee->moveup($node, true);
} else {
$count = $this->Employee->childcount($parent, true);
$delta = $count-$position-1;
if ($delta > 0){
$this->Employee->moveup($node, $delta);
}
}
// send success response
exit('1');
}
I've split up the functionality into two different methods, one for moving the node to a different parent node, the other for re-ordering the node within the existing parent node.
The exit('1'); negates the need to create a view for these methods. Be aware that using this technique will prevent the beforeRender and afterFilter callbacks from being executed. In our case, that does not matter.
The remaining bit of code that pieces everything together is some javascript! Add this snippet above the tree.render(); line in your view, and go and get yourself a beer!
/app/views/employees/index.ctp
// track what nodes are moved and send to server to save
var oldPosition = null;
var oldNextSibling = null;
tree.on('startdrag', function(tree, node, event){
oldPosition = node.parentNode.indexOf(node);
oldNextSibling = node.nextSibling;
});
tree.on('movenode', function(tree, node, oldParent, newParent, position){
if (oldParent == newParent){
var url = reorderUrl;
var params = {'node':node.id, 'delta':(position-oldPosition)};
} else {
var url = reparentUrl;
var params = {'node':node.id, 'parent':newParent.id, 'position':position};
}
// we disable tree interaction until we've heard a response from the server
// this prevents concurrent requests which could yield unusual results
tree.disable();
Ext.Ajax.request({
url:url,
params:params,
success:function(response, request) {
// if the first char of our response is zero, then we fail the operation,
// otherwise we re-enable the tree
if (response.responseText.charAt(0) != 1){
request.failure();
} else {
tree.enable();
}
},
failure:function() {
// we move the node back to where it was beforehand and
// we suspendEvents() so that we don't get stuck in a possible infinite loop
tree.suspendEvents();
oldParent.appendChild(node);
if (oldNextSibling){
oldParent.insertBefore(node, oldNextSibling);
}
tree.resumeEvents();
tree.enable();
alert("Oh no! Your changes could not be saved!");
}
});
});
What if something bad happens?
Life was never meant to be this easy! There are a few pitfalls with using the above example "as-is".
Wrap the save/update calls in a transaction
The Tree Behavior doesn't natively use transactions when saving the tree data. This is something that you should think about implementing if data integrity is paramount to your application. A lot of queries are executed when adding new nodes or moving nodes around. The slightest error can corrupt your tree data, so it is better to be safe than sorry! However, the verify and recover methods of the Tree Behavior are at your disposal if something goes terribly wrong.
Validate tree data before saving
If there are multiple users updating the tree structure at the same time, you are bound to run into issues as your Ext JS trees become unsynchronised with any changes that have been made by other users.
If you feel this could happen in your situation, I would suggest implementing some validation before saving nodes to their new locations. You could use a modified datetime field to detect when something has changed, and tell the user to refresh their tree and make the change again. With some cunning code it would be possible to do this behind the scenes with Ext JS without alerting the user unless there is a direct conflict.
Extending this example
Inline tree node editing
Ext JS already ships with the ability to edit the node names inside a tree (Ext.tree.TreeEditor). Adding some ajax code to save these edits would be a piece of cake!
Styling the tree with icons, and display as "leaf"
Modify getnodes.ctp and customise the tree icons using the "cls" attribute. If you add
Conclusion
It's been a nice journey but this tutorial has come to an end. I would really appreciate feedback as to whether this was helpful for you, or if there is anything that requires further explanation.
A huge thanks goes out to AD7six for creating the Tree Behavior, as well as the helpful tutorial on his own blog and playing devil's advocate for me in IRC.
Comments
85 Responses to “Drag and drop using Ext JS with the CakePHP Tree Behavior”
Leave a Reply

That was really nice, Thank you for all the effort you spent on this.
Adman,
you did a great job and your documentation is great.
Thank you!
Really thanks for this tutorial it is Great to see How to implement cool stuff with cakephp and Ext
Pretty simple and clean implementation. Awsome I might say.
Articles like these are a pleasure to read. :D
Wow you saved my life :P I was using other events 'beforenodedrop' and another one, but those returned the point for the drop (above, below, append), which isn't ideal for is in combination with the tree behavior.
Nicely written article!
Great tutorial. It really helped me.
I don't know if you're aware that the current stable version of Ext JS won't work with this tutorial. You need to use the files supplied on this page.
@Martin - I've just tried a custom build with 2.0.2 (the current stable release) and everything works as expected. Here are the components I selected in the custom build.
http://extjs.com/download/build
Page 1
No Adapter (Ext Stand-alone)
Page 2
Ext Core
Core - Components
Core - Drag and Drop
- Overflow Scrolling Support
Data - Core
Data - JSON Support
Tree - Core
- Sorting Support
- Filtering Support
- Drag and Drop Support
Tree - Ajax Loading Support
Very nice. Help me a lot. Thanks.
Has anyone tried to integrate with cake the FileTreePanel (Ext 2.x)
http://filetree.extjs.eu/
why not use the 'beforenodemove' event? that way you'd not have to move the node back to it's original position, or keep track of old siblings...
ah. make that 'beforemovenode'.
@Joshua McNeese: Yes, it would be cleaner to use the beforemovenode event, and I did try it initially, but couldn't figure out how to do a synchronous ajax request using ExtJS for the saving/validation in the beforemovenode event. Any suggestions?
why does it need to be synchronous?
listeners: {
'startDrag': function(tree, node) {
node.oldPosition = node.parentNode.indexOf(node);
node.oldNextSibling = node.nextSibling;
},
'beforemovenode': function(tree, node, oldParent, newParent, index) {
var params = {
id: node.id,
position: index,
delta: (index - node.oldPosition),
parent_id: (newParent.id == tree.root.id) ? '' : newParent.id
};
var url = whateverYourUrlIs + (oldParent == newParent ? 'reorder' : 'reparent');
Ext.Ajax.request({
url: url,
params: params,
success: function (response) {
var res = Ext.decode(response.responseText);
if(!res.success) {
Ext.Msg.show({
closable: false,
title: 'ERROR',
icon: Ext.Msg.ERROR,
msg: res.message,
buttons: Ext.Msg.OK
});
return false;
}
return true;
},
failure: function (res) {
Ext.Msg.show({
closable: false,
title: 'ERROR',
icon: Ext.Msg.ERROR,
msg: 'Failed to contact server.',
buttons: Ext.Msg.OK
});
return false;
}
});
return true;
}
}
@Joshua McNeese: Thanks for taking the time to post the above code, however it doesn't solve the problem explained in my previous comment. The 'beforemovenode' handler returns true before the ajax request has received a response from the server. If the request fails then your local tree is now out of date with the server tree.
oops. i clipped too much in that example (pesky IP).
in any case, you are correct. you can use the oldPosition/oldNextSibling to in a failure callback to move the node back to it's original location, or else...
are you aware of the ajax override for synchronous calls? http://extjs.com/forum/showthread.php?t=21681&highlight=async
the only reason i threw my .02 in was that beforemovenode is the better callback, in that you can do processing and/or logic before the node is actually moved, instead of moving it and then deciding to move it back. the async nature of ajax does throw a monkeywrench into that notion, but the very handy callbacks give you the power to revert whatever change may have happened in the interim request.
all this does make me wish for a tree with a backing store even worse than before...
Thanks for this tutorial, it was very clear!
Would it be possible to load the entire tree on page load? ie, have no ajax calls on expanding the tree nodes?
Also you mentioned the "leaf = true" snippet, how do you if the node is in fact the last one, thus required the leaf = true bit?
Many thanks in adv.
Cheers,
Trav.
Finally one f*ckin true tutorial that actually works like a cream cake when it comes to extJs and related UI libraries.
See you on IRC?
Thank you very much.
Hi
is there a way to make extJs sit easy with things like prototype / mootools (other...) ? Nice tutorial - goes well with the other tree stuff in the cake book/manual.
@Luke: Yes, you just need to include the correct adapter. Supported frameworks (afaik) are Yahoo UI, jQuery and Prototype/Scriptaculous. See the Ext JS site for more details.
Just wanted to give my compliments on the article as well. Really saved me a ton of time!
I am using prototype for my application.
I want to implement tree behaviour as mentioned above in the tutorial, as u said that the above implementation will not work if protoype library is used.
Is there a work around. I am a total newbie and want to implement tree behaviour in my application.
I had problems using the Ext 2.2 libraries with this example - the issue was the Ext wasn't a valid object. Hmph.
I found the solution to my problem on the Ext Forums: the ext-base.js file needs to be included. To apply that to this example, open the default.ctp file and insert these two lines:
All I had to do is add one line in default.ctp before the ext-all.js link:
echo $javascript->link('/js/ext-2.2/adapter/ext/ext-base.js');
echo $javascript->link('/js/ext-2.2/ext-all.js');
[Ed: Yes, you need to include the base js file if you aren't building it from the ext js site, which now lives at http://extjs.com/products/extjs/build/ ]
Pleas how i can setting, then tree will be open, all list.. Now i must click on node, but i dont want it. THX
@Pitrsonek: This is how I did it in one of my projects...
function admin_getnodes() {
$nodes = $this->Page->find('threaded', array('fields' => array('id','parent_id','title')));
$this->set(compact('nodes'));
}
And in my view...
< ?php
prune($nodes);
echo $javascript->object($nodes);
function prune(&$nodes){
foreach ($nodes as &$node){
$node['id'] = $node['Page']['id'];
$node['text'] = $node['Page']['title'];
$node['expanded'] = true;
if (!empty($node['children'])){
prune($node['children']);
}
unset($node['Page']);
}
}
?>
Perfect, its work ok. THX!
Hey Adam,
I downloaded your code and test it with a new installed cakephp 1.2, but there's an error saying:
Undefined variable: javascript [APP\views\layouts\default.ctp, line 8]
Fatal error: Call to a member function link() on a non-object in D:\xampp\htdocs\cake\tree\views\layouts\default.ctp on line 8.
I swear i did not change one bit of the code, any ideas what's going on?
Thanks a lot.
BTW i'm using a cakephp rc3 with xampp on windows XP.
@Antonio:
In your employee controller you must have defined the javascripthelper:
var $helpers = array('Form','Html','Javascript');
But i have a problem with the node icons - they will not be displayed.
This is the code from getnodes.ctp:
$data = array();
foreach ($nodes as $node){
$data[] = array(
"text" => $node['Employee']['name'],
"id" => $node['Employee']['id'],
"cls" => "folder",
);
}
echo $javascript->object($data);
Have somebody a hint for me?
Excellent tutorial. Thanks for sharing this.
Is there a way of loading the entire tree at once, without the need of ajax?
I noticed that when the tree is loaded, all nodes are shown as folders, even if they are leafs. Is there a workaround for that?
Thanks!
Wow!! Amazing work I'm just about to tackle this right now and then I stumble upon your excellent tutorial. Fantastic!
Hello Adam, This works great..
Please update with add/edit/delete function...
So many Thanksssssssssssssssssss.. :)
This really is excellent and useful. Thanks so much for taking the time to put it together.
I'm having the same problem as JanTe on Nov. 20. I'll bang on it for a while and post if I come up with anything.
N.B. -- my environment is different in that I'm using extJS 2.2 and applying the example to my own table structure.
Well, I found *a* problem preventing the tree from expanding in my code, but it may not have been the only one. (I've been playing with it for about 6 hours now, and I've changed a lot of stuff.)
Anyway, the last change I made was to remove all my debug statements on the server side. Ajax didn't like it when the output generated by "echo $javascript->object($data);" in views/getnodes.ctp was accompanied by helpful comments.
Probably this is a dumb mistake, but I figured it was worth mentioning. I can't be the only n00b out here.
Again, thanks for a great tutorial.
Thanks, you saved me lots of time ;)
The AJAX tree component I have to use must be fed with the tree data in XML format.
It does not look to me like your code works with the data structure in XML form.
I am not yet conformtable with the different classes in CakePHP.
Could you give me a hint / a pointer?
Thanks!
@jerome: This tutorial really only covers integrating the Ext JS tree, as any other trees might require a different implementation (such as yours). However the concept might be the same. Just instead of outputting json you would output xml. I would prob use Router::parseExtensions('xml') and use the XmlHelper, but that is beyond the scope of this comment box. Hope you get it sorted!
Many thanks for your insight Adam, many thanks for your great work.
Thanks for an excellent article, it works perfectly.
With some help from the cakephp channel and the sitepoint article, I've figured out how to make leafs appear as leafs. Turns out that this was also a piece of cake.
All you need to do, in your foreach loop in getnode.ctp. Is figure out if ['Employee']['lft']+1 equals ['Employee']['rght'], and if it does, set "leaf"=>true.
Example:
foreach ($nodes as $node){
$leaf = false;
if ($node['Rule']['lft'] + 1 == $node['Rule']['rght']) { $leaf = true; }
$data[] = array(
"text" => $node['Rule']['title'],
"id" => $node['Rule']['id'],
"cls" => "folder",
"leaf" => $leaf
);
}
@Ben: That's such a useful addition, I've added it to the article. Thanks for your comment, it's good to see this article is still helpful a year after it was written.
Hi!!
I've tried with uuid's and doesnt seem to work, getnodes doesnt return anything.
I've changed in getnodes()
$parent = intval($this->params['form']['node']);
for
$parent = $this->params['form']['node'];
but still doesnt work.
Any ideas ?
JM,
if you are using UUID, you will need to do two things:
the first you already did, as a UUID is a string, and intval() will return incorrect data.
the second is only applicable if you have a root node with a null parent_id in your DB. if so, you need to add a listener to your tree loader:
root = new Ext.tree.AsyncTreeNode();
loader = new Ext.tree.TreeLoader({
url: your.apps.url + '/tree_getnodes',
baseParams: {node: null},
listeners: {
'beforeload': function(loader, node) {
loader.baseParams.node = (node == root) ? null : node.id;
}
}
});
bruno said: "Is there a way of loading the entire tree at once, without the need of ajax?"
yes, there is. you have to configure your tree loader with preloadChildren: true, and you need to set up your action in cake to get the entire tree. (try looking at Model::find('threaded')).
also, use ben's suggestions above to determine if each node is a leaf or not, and you are set.
Joshua :
thanks for your quick reply, i don't full get it ... must i substitute your code for that one ?
var root = new Tree.AsyncTreeNode({
text:'Groups',
draggable:false,
id:'root'
});
or must i merge it ?
JM,
oh, you don't need to merge my root code, just use yours. i only included it, since i referenced it in my listener example.
Let's see if i'm getting it ....
in the example code there is a TreeLoader called loader inside the tree TreePanel instance.
Is in that one where i must add the following ? :
baseParams: {node: null},
listeners: {
'beforeload': function(loader, node) {
loader.baseParams.node = (node == root) ? null : node.id;
}
}
im afraid i have no idea of JS ... so i can only imagine what actually the code will do
JM,
yes. the only thing you'd have to do different from the example is define the root *before* the loader. then you can just set the root into the tree config, instead of using setRootNode().
Joshua : OK, do you mean that way ? :
var root = new Tree.AsyncTreeNode({
text:'Groups',
draggable:false,
id:'root'
});
var tree = new Tree.TreePanel({
el:'tree-div',
autoScroll:true,
animate:true,
enableDD:true,
containerScroll: true,
rootVisible: true,
loader: new Ext.tree.TreeLoader({
dataUrl:getnodesUrl,
baseParams: {node: null},
listeners: {'beforeload': function(loader, node) {loader.baseParams.node = (node == root) ? null : node.id;}}
})
});
// tree.setRootNode(root);
now the root have dissapeared, the box is empty ...
JM,
you'll need to set the root in the tree config, like:
var tree = new Tree.TreePanel({
el:'tree-div',
autoScroll:true,
animate:true,
enableDD:true,
containerScroll: true,
rootVisible: true,
root: root,
loader: new Ext.tree.TreeLoader({
dataUrl:getnodesUrl,
baseParams: {node: null},
listeners: {'beforeload': function(loader, node) {loader.baseParams.node = (node == root) ? null : node.id;}}
})
});
Joshua,
just added :
root:root,
now i have an "Employee" root again , but with nothing as child.
I think i understand th elogic, but cant figure out how make it work.
Would be easiest if i set "root" as default to the parent_id field instead of null ?
Hey Joshua,
finally i'ts working, i used another approach after spending some time debugging the passed values to understand how it works.
As i don't know JS i decided to leave untouched the index.ctp from the example, and to patch the getnodes,reorder,reparent methods in the employees controller to transform to null the $parents with value "root", and taking out all the intval's from $node and $parent.
I haven't tested deeply, but seems to work OK.
Thank you anyway for your kindness and patience, also to Adam for this well explained tutorial.
JM
@JM: Glad you got your issue sorted :)
@Joshua: Thanks for taking the time to help JM.
I managed to create new employees with the right button (inside the employee that is clicked) that way :
tree.on('contextmenu', function(node, event){
var url = addUrl;
var params = {'node':node.id};
tree.disable();
Ext.Ajax.request({
url:url,
params:params,
success:function(response, request) {},
failure:function(response, request) {},
});
tree.enable();
});
in addUrl there is a function that actually do the insert and if succesfull returns 1.
The problem is that the three is not refreshing, i guess i have to do something in "sucess:" like calling again getnodes or so ... i tried with tree.render but dont work. Also i havent found any method in the TreePanel api to do the refresh.
Anyone can help me?
i solved it with :
success:function(response, request) {root.reload();tree.expandAll();},
not really quick but it works.
Thanks a lot for the tutorial, it works like a charm. However if I try to move the javascript part (in between the script tags) to an external js file, the whole tree doesn't get rendered any more (probably it is giving an error which i cannot see). Anybody got any ideas how to solve the solution or getting some more debug information?
@Sjiep: The reason for your error is because the javascript is relying on some php code to generate the correct paths to post to, etc. You'll either need to hardcode your urls in the javascript, or find a better way to dynamically assign these.
I am new to cakePHP and Javascript.. If we move the javascript part to an external js file, how do we call the it in our index.ctp?
I can call Javascript from cakePHP now.. But how to dynamically assign the url? Can anyone help? I am stuck here~
Even I hardcode the url also it is not showing any nodes.. help~
@palangdao: You can hardcode the url in the javascript if you're sure it's going your cake app will remain in the same place. I played it safe in case people put their cake apps in a subdirectory.
I've tried hardcode it, but it is not showing any nodes~
I have no idea on how am i suppose to make it dynamic?
I would like to display child count beside the node, but i can't seem to get it. I tried adding a count function in the getnode.ctp file, but it seems to count the parent's child instead of counting child for itself. Any help on this?
I mean function in the controller.. under getnode()...
I'm the firs time user of CakePHP and Ext JS, please help me at the example setup
I installed the CakePHP into sample.tld/cake,
replaced the app folder with this one, and
started the application with sample.tld/cake
and get a message
Notice (8): Undefined variable: javascript [APP/views/layouts/default.ctp, line 8]
css('/js/ext-2.0.1/resources/css/ext-custom.css'); ?>
link('/js/ext-2.0.1/ext-custom.js'); ?>
.....
Fatal error: Call to a member function link() on a non-object in /wwwhome/cake/app/views/layouts/default.ctp on line 8
Nice tutorial with drag and drop:)
I also would like to know if there is an easy way to drag and drop nodes of the same level of the tree only?
For instance if a tree has 3 levels:
A1
B1
C1
C2
A2
B2
C3
C4
C5
I would like users to drag and drop C3 anywhere in B2 or B1 but not in A1 or in A2.
Is there any easy way to do that?
Sorry I have to redo the tree...
A1
...B1
......C1
......C2
A2
...B2
......C3
......C4
......C5
I would like users to drag and drop C3 anywhere in B2 or B1 but not in A1 or in A2.
Is there any easy way to do that?
@Xavier: I'm sure you can do that. Check out the great Ext JS documentation - you'll probably want to capture the beforemove event and check the targets, and return false if they attempt to drop a child node onto a base node.
Thanks Adam.
I actually tried with the following code:
beforemovenode: function(tree, node , oldParent , newParent) {
return (oldParent.getDepth() == newParent.getDepth());
}
It's working fine but it's still displaying a "+" icon when dropping an element on a level where dropping is no longer allowed.
I guess I have to learn more about Ext.tree.TreeDropZone to find a good solution which is not an easy way :)
I fixed it overriding the isValidDropPoint of the TreeDropZone. It's working as expected, here is the solution:
isValidDropPoint: function(n, pt, dd, e, data) {
var result = MyTreeDropZone.superclass.isValidDropPoint.call(this, n, pt, dd, e, data);
if (result) {
var targetNode = n.node;
var dropNode = data.node;
if(pt == "append") {
result = targetNode.getDepth() == dropNode.getDepth()-1;
} else {
result = targetNode.getDepth() == dropNode.getDepth();
}
}
return result;
}
@Xavier: Thanks for posting the solution. Glad you got it working!
After puzzling over this this for a solid 24 hours, I've realised that there is a bug when using Tree's with SQL Server (mssql).
By setting:
var $order = 'Employee.lft ASC';
You can no longer save new nodes properly. The node is inserted but the lft, rght columns remain zero.
The tree structures do still seem to work without using the explicit ordering in the model.
I need a link url when I click on the child node? any ideas?
I already answer my own question, just try to put "href".
I want to give add,edit and link to each parent and children.How can I do.
Beware, I had white space in my response and was getting the, "Your changes could not be saved" everytime.
I had the same whitespace problem when moving nodes in the tree. I solved it by stripping whitespace from the variable.
response.responseText.replace(/^\s+|\s+$/g,"").charAt(0)
success:function(response, request) {
// if the first char of our response is zero, then we fail the operation,
// otherwise we re-enable the tree
if (response.responseText.replace(/^\s+|\s+$/g,"").charAt(0) != 1){
request.failure();
} else {
tree.enable();
}
Such a great tutorial, Thanks a zillion.