Backend info: PHP 5 / MySQL
URL: http://github.com/downloads/vakata/jstree/jstree_pre1.0_fix_1.zip
Table structure for table discussions_tree
--
CREATE TABLE IF NOT EXISTS `discussions_tree` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`parent_id` int(11) NOT NULL DEFAULT '0',
`user_id` int(11) NOT NULL DEFAULT '0',
`label` varchar(16) DEFAULT NULL,
`position` bigint(20) unsigned NOT NULL DEFAULT '0',
`left` bigint(20) unsigned NOT NULL DEFAULT '0',
`right` bigint(20) unsigned NOT NULL DEFAULT '0',
`level` bigint(20) unsigned NOT NULL DEFAULT '0',
`type` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
`h_label` varchar(16) NOT NULL DEFAULT '',
`fulllabel` varchar(255) DEFAULT NULL,
UNIQUE KEY `uidx_3` (`id`),
KEY `idx_1` (`user_id`),
KEY `idx_2` (`parent_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=8 ;
/*The first element should in my understanding not even be shown*/
INSERT INTO `discussions_tree` (`id`, `parent_id`, `user_id`, `label`, `position`, `left`, `right`, `level`, `type`, `h_label`, `fulllabel`) VALUES
(0, 0, 0, 'Contacts', 0, 1, 1, 0, NULL, '', NULL);
INSERT INTO `discussions_tree` (`id`, `parent_id`, `user_id`, `label`, `position`, `left`, `right`, `level`, `type`, `h_label`, `fulllabel`) VALUES
(1, 0, 0, 'How to Tag', 1, 2, 2, 0, 'drive', '', NULL);
Front End :
I've simplified the logic, it has 6 trees actually inside of a panel and that works fine
$array = array("Discussions");
$id_arr = array("d");
$nid = 0;
foreach ($array as $k=> $value)
{
$nid++;
?>
<li id="<?=$value?>" class="label">
<a href='#<?=$value?>'><span> <?=$value?> </span></a>
<div class="sub-menu" style="height:auto; min-height:120px; background-color:#E5E5E5" >
<div class="menu" id="menu_<?=$id_arr[$k]?>" style="position:relative; margin-left:56%">
<img src="./js/jsTree/create.png" alt="" id="create" title="Create" >
<img src="./js/jsTree/rename.png" alt="" id="rename" title="Rename" >
<img src="./js/jsTree/remove.png" alt="" id="remove" title="Delete">
<img src="./js/jsTree/cut.png" alt="" id="cut" title="Cut" >
<img src="./js/jsTree/copy.png" alt="" id="copy" title="Copy">
<img src="./js/jsTree/paste.png" alt="" id="paste" title="Paste">
</div>
<div id="<?=$id_arr[$k]?>" class="jstree_container"></div>
</div>
</li>
<!-- JavaScript neccessary for this tree : <?=$value?> -->
<script type="text/javascript" >
jQuery(function ($) {
$("#<?=$id_arr[$k]?>").jstree({
// List of active plugins used
"plugins" : [ "themes", "json_data", "ui", "crrm" , "hotkeys" , "types" , "dnd", "contextmenu"],
// "ui" :{ "initially_select" : ["#node_"+ $nid ] } ,
"crrm": { "move": { "always_copy": "multitree" }, "input_width_limit":128 },
"core":{ "strings":{ "new_node" : "New Tag" }},
"themes": {"theme": "classic"},
"json_data" : {
"ajax" : {
"url" : "./js/jsTree/server-<?=$id_arr[$k]?>.php",
"data" : function (n) {
// the result is fed to the AJAX request `data` option
return {
"operation" : "get_children",
"id" : n.attr ? n.attr("id").replace("node_","") : 1,
"state" : "",
"user_id": <?=$uid?>
};
}
}
}
,
"types" : {
"max_depth" : -1,
"max_children" : -1,
"types" : {
// The default type
"default" : {
"hover_node":true,
"valid_children" : [ "default" ],
},
// The `drive` nodes
"drive" : {
// can have files and folders inside, but NOT other `drive` nodes
"valid_children" : [ "default", "folder" ],
"hover_node":true,
"icon" : {
"image" : "./js/jsTree/root.png"
},
// those prevent the functions with the same name to be used on `drive` nodes.. internally the `before` event is used
"start_drag" : false,
"move_node" : false,
"remove_node" : false
}
}
},
"contextmenu" : { "items" : customMenu , "select_node": true}
})
//Hover function binded to jstree
.bind("hover_node.jstree", function (e, data) {
$('ul li[rel="drive"], ul li[rel="default"], ul li[rel=""]').each(function(i) {
$(this).find("a").attr('href', $(this).attr("id")+".php" );
})
})
//Create function binded to jstree
.bind("create.jstree", function (e, data) {
$.post(
"./js/jsTree/server-<?=$id_arr[$k]?>.php",
{
"operation" : "create_node",
"id" : data.rslt.parent.attr("id").replace("node_",""),
"position" : data.rslt.position,
"label" : data.rslt.name,
"href" : data.rslt.obj.attr("href"),
"type" : data.rslt.obj.attr("rel"),
"user_id": <?=$uid?>
},
function (r) {
if(r.status) {
$(data.rslt.obj).attr("id", "node_" + r.id);
}
else {
$.jstree.rollback(data.rlbk);
}
}
);
})
//Remove operation
.bind("remove.jstree", function (e, data) {
data.rslt.obj.each(function () {
$.ajax({
async : false,
type: 'POST',
url: "./js/jsTree/server-<?=$id_arr[$k]?>.php",
data : {
"operation" : "remove_node",
"id" : this.id.replace("node_",""),
"user_id": <?=$uid?>
},
success : function (r) {
if(!r.status) {
data.inst.refresh();
}
}
});
});
})
//Rename operation
.bind("rename.jstree", function (e, data) {
data.rslt.obj.each(function () {
$.ajax({
async : true,
type: 'POST',
url: "./js/jsTree/server-<?=$id_arr[$k]?>.php",
data : {
"operation" : "rename_node",
"id" : this.id.replace("node_",""),
"label" : data.rslt.new_name,
"user_id": <?=$uid?>
},
success : function (r) {
if(!r.status) {
data.inst.refresh();
}
}
});
});
})
//Move operation
.bind("move_node.jstree", function (e, data) {
data.rslt.o.each(function (i) {
$.ajax({
async : false,
type: 'POST',
url: "./js/jsTree/server-<?=$id_arr[$k]?>.php",
data : {
"operation" : "move_node",
"id" : $(this).attr("id").replace("node_",""),
"ref" : data.rslt.cr === -1 ? 1 : data.rslt.np.attr("id").replace("node_",""),
"position" : data.rslt.cp + i,
"label" : data.rslt.name,
"copy" : data.rslt.cy ? 1 : 0,
"user_id": <?=$uid?>
},
success : function (r) {
if(!r.status) {
$.jstree.rollback(data.rlbk);
}
else {
$(data.rslt.oc).attr("id", "node_" + r.id);
if(data.rslt.cy && $(data.rslt.oc).children("UL").length) {
data.inst.refresh(data.inst._get_parent(data.rslt.oc));
}
}
}
});
});
});
// This is for the context menu to bind with operations on the right clicked node
function customMenu(node) {
// The default set of all items
var control;
var items = {
createItem: {
label: "Create",
action: function (node) { return {createItem: this.create(node) }; }
},
renameItem: {
label: "Rename",
action: function (node) { return {renameItem: this.rename(node) }; }
},
deleteItem: {
label: "Delete",
action: function (node) { return {deleteItem: this.remove(node) }; },
"separator_after": true
},
copyItem: {
label: "Copy",
action: function (node) { $(node).addClass("copy"); return {copyItem: this.copy(node) }; }
},
cutItem: {
label: "Cut",
action: function (node) { $(node).addClass("cut"); return {cutItem: this.cut(node) }; }
},
pasteItem: {
label: "Paste",
action: function (node) { $(node).addClass("paste"); return {pasteItem: this.paste(node) }; }
}
};
// We go over all the selected items as the context menu only takes action on the one that is right clicked
$.jstree._reference("#<?=$id_arr[$k]?>").get_selected(false, true).each(function(index,element)
{
if ( $(element).attr("id") != $(node).attr("id") )
{
// Let's deselect all nodes that are unrelated to the context menu -- selected but are not the one right clicked
$("#<?=$id_arr[$k]?>").jstree("deselect_node", '#'+$(element).attr("id") );
}
});
//if any previous click has the class for copy or cut
$("#<?=$id_arr[$k]?>").find("li").each(function(index,element)
{
if ($(element) != $(node) )
{
if( $(element).hasClass("copy") || $(element).hasClass("cut") ) control=1;
}
else if( $(node).hasClass("cut") || $(node).hasClass("copy"))
{
control=0;
}
});
//only remove the class for cut or copy if the current operation is to paste
if($(node).hasClass("paste") )
{
control=0;
// Let's loop through all elements and try to find if the paste operation was done already
$("#<?=$id_arr[$k]?>").find("li").each(function(index,element)
{
if( $(element).hasClass("copy") ) $(this).removeClass("copy");
if ( $(element).hasClass("cut") ) $(this).removeClass("cut");
if ( $(element).hasClass("paste") ) $(this).removeClass("paste");
});
}
switch (control)
{
//Remove the paste item from the context menu
case 0:
switch ($(node).attr("rel"))
{
case "drive":
delete items.renameItem;
delete items.deleteItem;
delete items.cutItem;
delete items.copyItem;
delete items.pasteItem;
break;
case "default":
delete items.pasteItem;
break;
}
break;
//Remove the paste item from the context menu only on the node that has either copy or cut added class
case 1:
if( $(node).hasClass("cut") || $(node).hasClass("copy") )
{
switch ($(node).attr("rel"))
{
case "drive":
delete items.renameItem;
delete items.deleteItem;
delete items.cutItem;
delete items.copyItem;
delete items.pasteItem;
break;
case "default":
delete items.pasteItem;
break;
}
}
else //Re-enable it on the clicked node that does not have the cut or copy class
{
switch ($(node).attr("rel"))
{
case "drive":
delete items.renameItem;
delete items.deleteItem;
delete items.cutItem;
delete items.copyItem;
break;
}
}
break;
//initial state don't show the paste option on any node
default: switch ($(node).attr("rel"))
{
case "drive":
delete items.renameItem;
delete items.deleteItem;
delete items.cutItem;
delete items.copyItem;
delete items.pasteItem;
break;
case "default":
delete items.pasteItem;
break;
}
break;
}
return items;
}
$("#menu_<?=$id_arr[$k]?> img").hover(
function () {
$(this).css({'cursor':'pointer','outline':'1px double teal'})
},
function () {
$(this).css({'cursor':'none','outline':'1px groove transparent'})
}
);
$("#menu_<?=$id_arr[$k]?> img").click(function () {
switch(this.id) {
//Create only the first element
case "create":
if ( $.jstree._reference("#<?=$id_arr[$k]?>").get_selected(false, true).length )
{
$.jstree._reference("#<?=$id_arr[$k]?>").get_selected(false, true).each(function(index,element){
switch(index)
{
case 0: $("#<?=$id_arr[$k]?>").jstree("create", '#'+$(element).attr("id"), null, /*{attr : {href: '#' }}*/null ,null, false);
break;
default: $("#<?=$id_arr[$k]?>").jstree("deselect_node", '#'+$(element).attr("id") );
break;
}
});
}
else
{
$.facebox('<p class=\'p_inner error bold\'>A selection needs to be made to work with this operation');
setTimeout(function(){
$.facebox.close();
}, 2000);
}
break;
//REMOVE
case "remove":
if ( $.jstree._reference("#<?=$id_arr[$k]?>").get_selected(false, true).length )
{
$.jstree._reference("#<?=$id_arr[$k]?>").get_selected(false, true).each(function(index,element){
//only execute if the current node is not the first one (drive)
if( $(element).attr("id") != $("div.jstree > ul > li").first().attr("id") )
{
$("#<?=$id_arr[$k]?>").jstree("remove",'#'+$(element).attr("id"));
}
else $("#<?=$id_arr[$k]?>").jstree("deselect_node", '#'+$(element).attr("id") );
});
}
else
{
$.facebox('<p class=\'p_inner error bold\'>A selection needs to be made to work with this operation');
setTimeout(function(){
$.facebox.close();
}, 2000);
}
break;
//RENAME NODE only one selection
case "rename":
if ( $.jstree._reference("#<?=$id_arr[$k]?>").get_selected(false, true).length )
{
$.jstree._reference("#<?=$id_arr[$k]?>").get_selected(false, true).each(function(index,element){
if( $(element).attr("id") != $("div.jstree > ul > li").first().attr("id") )
{
switch(index)
{
case 0: $("#<?=$id_arr[$k]?>").jstree("rename", '#'+$(element).attr("id") );
break;
default: $("#<?=$id_arr[$k]?>").jstree("deselect_node", '#'+$(element).attr("id") );
break;
}
}
else $("#<?=$id_arr[$k]?>").jstree("deselect_node", '#'+$(element).attr("id") );
});
}
else
{
$.facebox('<p class=\'p_inner error bold\'>A selection needs to be made to work with this operation');
setTimeout(function(){
$.facebox.close();
}, 2000);
}
break;
//Cut
case "cut":
if ( $.jstree._reference("#<?=$id_arr[$k]?>").get_selected(false, true).length )
{
$.jstree._reference("#<?=$id_arr[$k]?>").get_selected(false, true).each(function(index,element){
switch(index)
{
case 0:
$("#<?=$id_arr[$k]?>").jstree("cut", '#'+$(element).attr("id"));
$.facebox('<p class=\'p_inner teal\'>Operation "Cut" successfully done.<p class=\'p_inner teal bold\'>Where to place it?');
setTimeout(function(){
$.facebox.close();
$("#<?=$id_arr[$k]?>").jstree("deselect_node", '#'+$(element).attr("id"));
}, 2000);
break;
default: $("#<?=$id_arr[$k]?>").jstree("deselect_node", '#'+$(element).attr("id") );
break;
}
});
}
else
{
$.facebox('<p class=\'p_inner error bold\'>A selection needs to be made to work with this operation');
setTimeout(function(){
$.facebox.close();
}, 2000);
}
break;
//Copy
case "copy":
if ( $.jstree._reference("#<?=$id_arr[$k]?>").get_selected(false, true).length )
{
$.jstree._reference("#<?=$id_arr[$k]?>").get_selected(false, true).each(function(index,element){
switch(index)
{
case 0:
$("#<?=$id_arr[$k]?>").jstree("copy", '#'+$(element).attr("id"));
$.facebox('<p class=\'p_inner teal\'>Operation "Copy": Successfully done.<p class=\'p_inner teal bold\'>Where to place it?');
setTimeout(function(){
$.facebox.close();
$("#<?=$id_arr[$k]?>").jstree("deselect_node", '#'+$(element).attr("id") );
}, 2000);
break;
default: $("#<?=$id_arr[$k]?>").jstree("deselect_node", '#'+$(element).attr("id") );
break;
}
});
}
else
{
$.facebox('<p class=\'p_inner error bold\'>A selection needs to be made to work with this operation');
setTimeout(function(){
$.facebox.close();
}, 2000);
}
break;
case "paste":
if ( $.jstree._reference("#<?=$id_arr[$k]?>").get_selected(false, true).length )
{
$.jstree._reference("#<?=$id_arr[$k]?>").get_selected(false, true).each(function(index,element){
switch(index)
{
case 0:
$("#<?=$id_arr[$k]?>").jstree("paste", '#'+$(element).attr("id"));
break;
default: $("#<?=$id_arr[$k]?>").jstree("deselect_node", '#'+$(element).attr("id") );
break;
}
});
}
else
{
$.facebox('<p class=\'p_inner error bold\'>A selection needs to be made to work with this operation');
setTimeout(function(){
$.facebox.close();
}, 2000);
}
break;
}
});
<?
}
?>
server.php
$path='../../../..';
require_once "$path/phpfoo/dbif.class";
require_once "$path/global.inc";
// Database config & class
$db_config = array(
"servername"=> $dbHost,
"username" => $dbUser,
"password" => $dbPW,
"database" => $dbName
);
if(extension_loaded("mysqli")) require_once("_inc/class._database_i.php");
else require_once("_inc/class._database.php");
//Tree class
require_once("_inc/class.ctree.php");
$dbLink = new dbif();
$dbErr = $dbLink->connect($dbName,$dbUser,$dbPW,$dbHost);
$jstree = new json_tree();
if(isset($_GET["reconstruct"])) {
$jstree->_reconstruct();
die();
}
if(isset($_GET["analyze"])) {
echo $jstree->_analyze();
die();
}
$table = '`discussions_tree`';
if($_REQUEST["operation"] && strpos($_REQUEST["operation"], "_") !== 0 && method_exists($jstree, $_REQUEST["operation"])) {
foreach($_REQUEST as $k => $v)
{
switch($k)
{
case 'user_id':
//We are passing the user_id from the $_SESSION on each request and trying to pick up the min and max value from the table that matches the 'user_id'
$sql = "SELECT max(`right`) , min(`left`) FROM $table WHERE `user_id`=$v";
//If the select does not return any value then just let it be :P
if (!list($right, $left)=$dbLink->getRow($sql))
{
$sql = $dbLink->dbSubmit("UPDATE $table SET `user_id`=$v WHERE `id` = 1 AND `parent_id` = 0");
$sql = $dbLink->dbSubmit("UPDATE $table SET `user_id`=$v WHERE `parent_id` = 1 AND `label`='How to Tag' ");
}
else
{
$sql = $dbLink->dbSubmit("UPDATE $table SET `user_id`=$v, `right`=$right+2 WHERE `id` = 1 AND `parent_id` = 0");
$sql = $dbLink->dbSubmit("UPDATE $table SET `user_id`=$v, `left`=$left+1, `right`=$right+1 WHERE `parent_id` = 1 AND `label`='How to Tag' ");
}
break;
}
}
header("HTTP/1.0 200 OK");
header('Content-type: application/json; charset=utf-8');
header("Cache-Control: no-cache, must-revalidate");
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Pragma: no-cache");
echo $jstree->{$_REQUEST["operation"]}($_REQUEST);
die();
}
header("HTTP/1.0 404 Not Found");
?>
The problem: DND *(Drag and Drop) works, Delete works, Create works, Rename works, but Copy, Cut and Paste don't work