diff --git a/code/LeftAndMain.php b/code/LeftAndMain.php
index 2dd9d7a1..ca8ac450 100644
--- a/code/LeftAndMain.php
+++ b/code/LeftAndMain.php
@@ -247,8 +247,8 @@ class LeftAndMain extends Controller {
Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/scriptaculous/dragdrop.js');
Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/scriptaculous/controls.js');
- Requirements::javascript(THIRDPARTY_DIR . '/tree/tree.js');
- Requirements::css(THIRDPARTY_DIR . '/tree/tree.css');
+ Requirements::javascript(THIRDPARTY_DIR . '/jstree/jquery.jstree.js');
+ Requirements::css(THIRDPARTY_DIR . '/jstree/themes/default/style.css');
Requirements::javascript(CMS_DIR . '/javascript/LeftAndMain.js');
Requirements::javascript(CMS_DIR . '/javascript/LeftAndMain.Tree.js');
@@ -299,8 +299,8 @@ class LeftAndMain extends Controller {
'sapphire/thirdparty/scriptaculous/dragdrop.js',
'sapphire/thirdparty/scriptaculous/controls.js',
'cms/javascript/LeftAndMain.js',
- 'sapphire/javascript/tree/tree.js',
- 'sapphire/javascript/TreeSelectorField.js',
+ 'sapphire/thirdparty/jstree/jquery.jstree.js',
+ 'sapphire/javascript/TreeDropdownField.js',
'cms/javascript/ThumbnailStripField.js',
)
);
@@ -523,7 +523,7 @@ class LeftAndMain extends Controller {
// getChildrenAsUL is a flexible and complex way of traversing the tree
$titleEval = '
- "
ID\" class=\"" . $child->CMSTreeClasses($extraArg) . "\">" .
+ "ID\" data-id=\"$child->ID\" class=\"" . $child->CMSTreeClasses($extraArg) . "\">" .
"Link(),0,-1), "show", $child->ID) . "\" class=\"" . $child->CMSTreeClasses($extraArg) . "\" title=\"'
. _t('LeftAndMain.PAGETYPE','Page type: ')
. '".$child->class."\" >" . ($child->TreeTitle) .
diff --git a/javascript/LeftAndMain.Tree.js b/javascript/LeftAndMain.Tree.js
index 0e855517..2a268bd1 100755
--- a/javascript/LeftAndMain.Tree.js
+++ b/javascript/LeftAndMain.Tree.js
@@ -2,650 +2,689 @@
* File: LeftAndMain.Tree.js
*/
-/**
- * LeftAndMain_left.js
- * Code for supporting the left-hand panel of all the 2-pane admin windows
- * This includes code for the action panel at the top, and the draggable, ajax-linked tree.
- */
-
-if(typeof SiteTreeHandlers == 'undefined') SiteTreeHandlers = {};
-SiteTreeHandlers.parentChanged_url = 'admin/ajaxupdateparent';
-SiteTreeHandlers.orderChanged_url = 'admin/ajaxupdatesort';
-SiteTreeHandlers.showRecord_url = 'admin/show/';
-SiteTreeHandlers.controller_url = 'admin';
-
-var _HANDLER_FORMS = {
- addpage : 'Form_AddForm',
- batchactions : 'batchactionsforms',
- search : 'search_options'
-};
-
(function($) {
- $(window).bind('load', function(e) {
- // behaviour.js load handlers need to be fired before this event, so we artificially delay it
- setTimeout(function() {
- // make sure current ID of loaded form is actually selected in tree
- var tree = $('#sitetree')[0], id = $('#Form_EditForm :input[name=ID]').val();
- if(!id) id = 0;
- if(tree) tree.setCurrentByIdx(id);
- }, 200);
- });
-}(jQuery));
-
-/**
- * Overload this with a real context menu if necessary
- */
-var TreeContextMenu = null;
-
-/**
- * Class: TreeAPI
- *
- * Extra methods for the tree when used in the LHS of the CMS
- */
-TreeAPI = Class.create();
-TreeAPI.prototype = {
-
- setCustomURL: function(url, arguments) {
- this.customURL = url;
- this.customArguments = $H(arguments);
- },
-
- clearCustomURL: function() {
- this.customURL = this.customArguments = null;
- },
-
- url: function(args) {
- var args = $H(args).merge(this.customArguments);
-
- var url = this.customURL ? this.customURL : SiteTreeHandlers.loadTree_url;
- url = url + (url.match(/\?/) ? '&' : '?') + args.toQueryString();
-
- return url;
- },
-
- reload: function(options) {
- this.innerHTML = 'Loading...';
-
- var args = {ajax:1, ID:0};
- if ($('LangSelector')) args.locale = $('LangSelector').value;
-
- var url = this.url(args);
-
- var self = this;
- jQuery.get(
- url,
- function(data, status){
- self.innerHTML = data;
- self.castAsTreeNode(self.firstChild);
- if (options && options.onSuccess) options.onSuccess(data);
+ $(document).ready(function() {
+ $('#sitetree_ul').jstree({
+ 'core': {
+ 'initially_open': ['record-0']
},
- 'html'
- );
- },
-
- /**
- * Perform the given code on the each tree node with the given index.
- * There could be more than one :-)
- * @param idx The index of the tree node
- * @param code A method to be executed, that will be passed the tree node
- */
- performOnTreeNode: function(idx, code) {
- var treeNode = this.getTreeNodeByIdx(idx);
- if(!treeNode) return;
-
- if(treeNode.className.indexOf('manyparents') == -1) {
- code(treeNode);
-
- } else {
- var i,item,allNodes = this.getElementsByTagName('li');
- for(i=0;item=allNodes[i];i++) {
- if(treeNode.id == item.id) code(item);
- }
- }
- },
-
- getTreeNodeByIdx : function(idx) {
- if(!idx) idx = "0";
- var node = document.getElementById('record-' + idx);
- if(idx == "0" && !node) node = document.getElementById('record-root');
- return node;
- },
- getIdxOf : function(treeNode) {
- if(treeNode && treeNode.id && treeNode.id.match(/record-([0-9a-zA-Z\-]+)$/))
- return RegExp.$1;
- },
- createTreeNode : function(idx, title, pageType) {
- var i;
- var node = document.createElement('li');
- node.id = 'record-' + idx;
- node.className = pageType;
-
- var aTag = document.createElement('a');
- aTag.href = SiteTreeHandlers.showRecord_url + idx;
- aTag.title = 'Page type: ' + pageType;
- aTag.innerHTML = title;
- node.appendChild(aTag);
-
- SiteTreeNode.create(node, this.options);
-
- return node;
- },
-
- setNodeIdx : function(idx, newIdx) {
- this.performOnTreeNode(idx, function(treeNode) {
- treeNode.id = 'record-' + newIdx;
- var aTag = treeNode.getElementsByTagName('a')[0];
- aTag.href = aTag.href.replace(idx, newIdx);
+ 'html_data': {
+ // TODO Hack to avoid ajax load on init, see http://code.google.com/p/jstree/issues/detail?id=911
+ 'data': $('#sitetree_ul').html(),
+ 'ajax': {
+ 'url': 'admin/getsubtree',
+ 'data': function(node) {
+ return { ID : $(node).data("id") ? $(node).data("id") : 0 , ajax: 1};
+ }
+ }
+ },
+ 'ui': {
+ "select_limit" : 1,
+ 'initially_select': [$('#sitetree_ul').find('.current').attr('id')]
+ },
+ 'plugins': ['themes', 'html_data', 'ui']
});
-
- var treeNode = this.getTreeNodeByIdx(idx);
- },
-
- setNodeTitle : function(idx, title) {
- this.performOnTreeNode(idx, function(treeNode) {
- var aTag = treeNode.getElementsByTagName('a')[0];
- aTag.innerHTML = title;
+
+ $('#sitetree_ul').bind('select_node.jstree', function(e, data) {
+ var node = data.rslt.obj;
+ var url = $(node).find('a:first').attr('href');
+ if(url && url != '#') {
+ var xmlhttp = $('#Form_EditForm').entwine('ss').loadForm(
+ url,
+ function(response) {}
+ );
+
+ // TODO Mark node as loading
+ // if(xmlhttp) this.addNodeClass('loading');
+ } else {
+ jQuery('#Form_EditForm').entwine('ss').removeForm();
+ }
});
- },
-
- setNodeIcon: function(idx, newClassName) {
- this.performOnTreeNode(idx, function(treeNode) {
- treeNode.className = treeNode.className.replace(/(class-)[^\s]*/,'$1' + newClassName);
- treeNode.aSpan.className = 'a ' + treeNode.className.replace('closed','spanClosed');
- var aTag = treeNode.getElementsByTagName('a')[0];
- aTag.title = 'Page type: ' + newClassName;
- treeNode.setIconByClass();
- });
- },
-
- /**
- * Set the parent ID of a tree node
- */
- setNodeParentID: function (idx, parentID) {
- var treeNode = this.getTreeNodeByIdx(idx);
- var parentNode = this.getTreeNodeByIdx(parentID);
- var currentParentNode = jQuery(treeNode).parents('li')[0];
- // Only change parent node if its different than the current,
- // otherwise we affect the sort order unnecessarily due to
- // appendTreeNode() not looking at existing sorts
- if(!currentParentNode || parentNode != currentParentNode) parentNode.appendTreeNode(treeNode);
- },
-
- setCurrentByIdx : function(idx) {
- if(this.selected) {
- var i,item;
- for(i=0;item=this.selected[i];i++) {
- item.removeNodeClass('current');
- }
- }
-
- __tree = this;
- __tree.selected = [];
-
- this.performOnTreeNode(idx, function(treeNode) {
- __tree.selected.push(treeNode);
- treeNode.expose();
- treeNode.addNodeClass('current');
- });
- },
-
- changeCurrentTo : function(newNode) {
- if(this.selected) {
- var i,item;
- for(i=0;item=this.selected[i];i++) {
- item.removeNodeClass('current');
- }
- }
- newNode.addNodeClass('current');
+ });
- this.selected = [newNode];
- newNode.expose();
- },
-
- firstSelected : function() {
- if(this.selected) return this.selected[0];
- }
-};
-
-/**
- * Extra methods for the tree node when used in the LHS of the CMS
- */
-TreeNodeAPI = Class.create();
-TreeNodeAPI.prototype = {
- selectTreeNode : function() {
- var url = jQuery(this).find('a').attr('href');
- if(url && url != '#') {
- jQuery('#sitetree').trigger('selectionchanged', {node: this});
- // don't get page if either event was cancelled,
- // or the tree is currently in a selectable state.
- if($('sitetree').notify('SelectionChanged', this) && !jQuery(this.tree).hasClass('multiselect')) {
- this.getPageFromServer();
- }
- } else {
- jQuery('#Form_EditForm').entwine('ss').removeForm();
- }
- },
-
- getPageFromServer : function() {
- var self = this;
- var xmlhttp = jQuery('#Form_EditForm').entwine('ss').loadForm(
- jQuery(this).find('a').attr('href'),
- function(response) {
- self.removeNodeClass('loading');
-
- var pageID = jQuery(this).find(':input[name=ID]').val();
- jQuery('#sitetree')[0].setCurrentByIdx(pageID);
- }
- );
-
- if(xmlhttp) this.addNodeClass('loading');
- },
- ajaxExpansion : function() {
- this.addNodeClass('loading');
- var ul = this.treeNodeHolder(false);
- ul.innerHTML = 'loading...';
-
- // Any attempts to add children to this page should, in fact, cue them up for insertion later
- ul.cuedNewNodes = [];
- ul.appendTreeNode = function(node) {
- this.cuedNewNodes[this.cuedNewNodes.length] = node;
- }
-
- var args = {ajax:1, ID:this.getIdx()};
-
- // Add current locale for any subtree selection
- if ($('LangSelector')) args.locale = $('LangSelector').value;
-
- // If the tree is selectable, we have to show all available children without
- // artificial limitations from the serverside (minNodeCount). This is a measure
- // to ensure no unexpanded nodes are missed in batch selection
- if(Element.hasClassName('sitetree', 'multiselect')) args.minNodeCount = 0;
-
- url = this.tree.url(args);
-
- new Ajax.Request(url, {
- onSuccess : this.installSubtree.bind(this),
- onFailure : this.showSubtreeLoadingError
- });
- },
- showSubtreeLoadingError: function(response) {
- errorMessage('error loading subtree', response);
- },
-
- /**
- * Context menu
- */
- oncontextmenu: function(event) {
- if(TreeContextMenu) {
- if(!event) event = window.event;
- createContextMenu(event, this, TreeContextMenu);
- Event.stop(event);
- return false;
- }
- },
- duplicatePage: function() {
- // Pass the parent ID to the duplicator, which helps ensure that multi-parent pages are duplicated into the node that the user clicked
- var parentClause = "";
- if(this.parentTreeNode && this.parentTreeNode.getIdx) {
- parentClause = "&parentID=" + this.parentTreeNode.getIdx();
- }
-
- new Ajax.Request(jQuery('base').attr('href') + 'admin/duplicate/' + this.getIdx() + '?ajax=1' + parentClause, {
- method : 'get',
- onSuccess : Ajax.Evaluator,
- onFailure : function(response) {
- errorMessage('Error: ', response);
- }
- });
- },
- duplicatePageWithChildren: function() {
- new Ajax.Request(jQuery('base').attr('href') + 'admin/duplicatewithchildren/' + this.getIdx() + '?ajax=1', {
- method : 'get',
- onSuccess : Ajax.Evaluator,
- onFailure : function(response) {
- errorMessage('Error: ', response);
- }
- });
- }
-}
-
-
-
-
-/**
- * In the case of Tree & DraggableTree, the root tree and the sub-trees all use the same class.
- * In this case, however, SiteTree has a much bigger API and so SiteSubTree is smaller.
- */
-SiteSubTree = Class.extend('Tree').extend('TreeAPI');
-SiteSubTree.prototype = {
- castAsTreeNode: function(li) {
- behaveAs(li, SiteTreeNode, this.options);
- }
-}
+}(jQuery));
+
-/**
- * Our SiteTree class extends the tree object with a richer manipulation API.
- * The server will send a piece javascript that uses these functions. In this way, the server
- * has flexibility over its operation, but the Script->HTML interface is kept client-side.
- */
-SiteTree = Class.extend('SiteSubTree');
-SiteTree.prototype = {
- initialize : function() {
- this.Tree.initialize();
-
- /*
- if(!this.tree.selected) this.tree.selected = [];
- var els = this.getElementsByTagName('li');
- for(var i=0;i -1) {
- this.tree.selected.push(els[i]);
- break;
- }
- */
-
- this.observeMethod('SelectionChanged', this.interruptLoading.bind(this) );
-
- jQuery('#Form_EditForm').bind('loadnewpage', this.onLoadNewPage.bind(this));
- },
- destroy: function () {
- if(this.Tree) this.Tree.destroy();
- this.Tree = null;
- this.SiteSubTree = null;
- this.TreeAPI = null;
- this.selected = null;
- },
-
- /**
- * Stop the currently loading node from loading.
- */
- interruptLoading: function( newLoadingNode ) {
- if( this.loadingNode ) this.loadingNode.removeNodeClass('loading');
- this.loadingNode = newLoadingNode;
- },
-
- /**
- * Assumes to be triggered by a form element with the following input fields:
- * ID, ParentID, TreeTitle (or Title), ClassName
- */
- onLoadNewPage: function(e, eventData) {
- // finds a certain value in an array generated by jQuery.serializeArray()
- var findInSerializedArray = function(arr, name) {
- for(var i=0; i -1) {
- if(!this.tree.selected) this.tree.selected = [];
- this.tree.selected.push(this);
- }
-
- if(!this.hints) this.hints = {}
- },
-
- destroy: function () {
- if(this.TreeNode) this.TreeNode.destroy();
- this.TreeNode = null;
- this.TreeNodeAPI = null;
- },
-
- castAsTree: function(childUL) {
- behaveAs(childUL, SiteSubTree, this.options);
- if(this.draggableObj) childUL.makeDraggable();
- },
-
- onselect: function() {
- this.selectTreeNode();
- return false;
- },
-
-
-
- /**
- * Drag'n'drop handlers - Ajax saving
- */
- onParentChanged : function(node, oldParent, newParent) {
- var self = this;
-
- if(newParent.id.match(/^record-new/)) {
- alert("You must save the page before dragging children into it");
- return false;
- }
-
- if( node == newParent || node.getIdx() == newParent.getIdx() ) {
- alert("You cannot add a page to itself");
- return false;
- }
-
- if(node.innerHTML.toLowerCase().indexOf(' -1) {
- alert("You can't moved deleted pages");
- return false;
- }
-
- if( Element.hasClassName( newParent, 'nochildren' ) ) {
- alert("You can't add children to that node");
- return false;
- }
-
- jQuery.post(
- SiteTreeHandlers.parentChanged_url,
- 'ID=' + node.getIdx() + '&ParentID=' + newParent.getIdx(),
- function(data, status) {
- // TODO This should use a more common serialization in a new tree library
- if(data.modified) {
- for(var id in data.modified) {
- self.tree.setNodeTitle(id, data.modified[id]['TreeTitle']);
- }
- }
-
- // Check if current page still exists, and refresh it.
- // Otherwise remove the current form
- var selectedNode = self.tree.firstSelected();
- if(selectedNode) {
- var selectedNodeId = self.tree.getIdxOf(selectedNode);
- if(data.modified[selectedNode.getIdx()]) {
- // only if the current page was modified
- selectedNode.selectTreeNode();
- }
- }
- },
- 'json'
- );
-
- return true;
- },
-
- /**
- * Called when the tree has been resorted
- * nodeList is a list of all the nodes in the correct rder
- * movedNode is the node that actually got moved to trigger this resorting
- */
- onOrderChanged : function(nodeList, movedNode) {
- var self = this;
-
- var i, parts = Array();
- sort = 0;
-
- for(i=0;i method; context-menu operations to get called
- */
-function createContextMenu(event, owner, menuItems) {
- if(_CURRENT_CONTEXT_MENU) {
- document.body.removeChild(_CURRENT_CONTEXT_MENU);
- _CURRENT_CONTEXT_MENU = null;
- }
-
- var menu = document.createElement("ul");
- menu.className = 'contextMenu';
- menu.style.position = 'absolute';
- menu.style.left = event.clientX + 'px';
- menu.style.top = event.clientY + 'px';
-
- var menuItemName, menuItemTag, menuATag;
- for(menuItemName in menuItems) {
- menuItemTag = document.createElement("li");
-
- menuATag = document.createElement("a");
- menuATag.href = "#";
- menuATag.onclick = menuATag.oncontextmenu = contextmenu_onclick;
- menuATag.innerHTML = menuItemName;
- menuATag.handler = menuItems[menuItemName];
- menuATag.owner = owner;
-
- menuItemTag.appendChild(menuATag);
- menu.appendChild(menuItemTag);
- }
-
- document.body.appendChild(menu);
-
- document.body.onclick = contextmenu_close;
-
- _CURRENT_CONTEXT_MENU = menu;
-
- return menu;
-}
-
-function contextmenu_close() {
- if(_CURRENT_CONTEXT_MENU) {
- document.body.removeChild(_CURRENT_CONTEXT_MENU);
- _CURRENT_CONTEXT_MENU = null;
- }
-}
-
-function contextmenu_onclick() {
- this.handler(this.owner);
- contextmenu_close();
- return false;
-}
\ No newline at end of file
+// if(typeof SiteTreeHandlers == 'undefined') SiteTreeHandlers = {};
+// SiteTreeHandlers.parentChanged_url = 'admin/ajaxupdateparent';
+// SiteTreeHandlers.orderChanged_url = 'admin/ajaxupdatesort';
+// SiteTreeHandlers.showRecord_url = 'admin/show/';
+// SiteTreeHandlers.controller_url = 'admin';
+//
+// var _HANDLER_FORMS = {
+// addpage : 'Form_AddForm',
+// batchactions : 'batchactionsforms',
+// search : 'search_options'
+// };
+//
+// (function($) {
+// $(window).bind('load', function(e) {
+// // behaviour.js load handlers need to be fired before this event, so we artificially delay it
+// setTimeout(function() {
+// // make sure current ID of loaded form is actually selected in tree
+// var tree = $('#sitetree')[0], id = $('#Form_EditForm :input[name=ID]').val();
+// if(!id) id = 0;
+// if(tree) tree.setCurrentByIdx(id);
+// }, 200);
+// });
+// }(jQuery));
+//
+// /**
+// * Overload this with a real context menu if necessary
+// */
+// var TreeContextMenu = null;
+//
+// /**
+// * Class: TreeAPI
+// *
+// * Extra methods for the tree when used in the LHS of the CMS
+// */
+// TreeAPI = Class.create();
+// TreeAPI.prototype = {
+//
+// setCustomURL: function(url, arguments) {
+// this.customURL = url;
+// this.customArguments = $H(arguments);
+// },
+//
+// clearCustomURL: function() {
+// this.customURL = this.customArguments = null;
+// },
+//
+// url: function(args) {
+// var args = $H(args).merge(this.customArguments);
+//
+// var url = this.customURL ? this.customURL : SiteTreeHandlers.loadTree_url;
+// url = url + (url.match(/\?/) ? '&' : '?') + args.toQueryString();
+//
+// return url;
+// },
+//
+// reload: function(options) {
+// this.innerHTML = 'Loading...';
+//
+// var args = {ajax:1, ID:0};
+// if ($('LangSelector')) args.locale = $('LangSelector').value;
+//
+// var url = this.url(args);
+//
+// var self = this;
+// jQuery.get(
+// url,
+// function(data, status){
+// self.innerHTML = data;
+// self.castAsTreeNode(self.firstChild);
+// if (options && options.onSuccess) options.onSuccess(data);
+// },
+// 'html'
+// );
+// },
+//
+// /**
+// * Perform the given code on the each tree node with the given index.
+// * There could be more than one :-)
+// * @param idx The index of the tree node
+// * @param code A method to be executed, that will be passed the tree node
+// */
+// performOnTreeNode: function(idx, code) {
+// var treeNode = this.getTreeNodeByIdx(idx);
+// if(!treeNode) return;
+//
+// if(treeNode.className.indexOf('manyparents') == -1) {
+// code(treeNode);
+//
+// } else {
+// var i,item,allNodes = this.getElementsByTagName('li');
+// for(i=0;item=allNodes[i];i++) {
+// if(treeNode.id == item.id) code(item);
+// }
+// }
+// },
+//
+// getTreeNodeByIdx : function(idx) {
+// if(!idx) idx = "0";
+// var node = document.getElementById('record-' + idx);
+// if(idx == "0" && !node) node = document.getElementById('record-root');
+// return node;
+// },
+// getIdxOf : function(treeNode) {
+// if(treeNode && treeNode.id && treeNode.id.match(/record-([0-9a-zA-Z\-]+)$/))
+// return RegExp.$1;
+// },
+// createTreeNode : function(idx, title, pageType) {
+// var i;
+// var node = document.createElement('li');
+// node.id = 'record-' + idx;
+// node.className = pageType;
+//
+// var aTag = document.createElement('a');
+// aTag.href = SiteTreeHandlers.showRecord_url + idx;
+// aTag.title = 'Page type: ' + pageType;
+// aTag.innerHTML = title;
+// node.appendChild(aTag);
+//
+// SiteTreeNode.create(node, this.options);
+//
+// return node;
+// },
+//
+// setNodeIdx : function(idx, newIdx) {
+// this.performOnTreeNode(idx, function(treeNode) {
+// treeNode.id = 'record-' + newIdx;
+// var aTag = treeNode.getElementsByTagName('a')[0];
+// aTag.href = aTag.href.replace(idx, newIdx);
+// });
+//
+// var treeNode = this.getTreeNodeByIdx(idx);
+// },
+//
+// setNodeTitle : function(idx, title) {
+// this.performOnTreeNode(idx, function(treeNode) {
+// var aTag = treeNode.getElementsByTagName('a')[0];
+// aTag.innerHTML = title;
+// });
+// },
+//
+// setNodeIcon: function(idx, newClassName) {
+// this.performOnTreeNode(idx, function(treeNode) {
+// treeNode.className = treeNode.className.replace(/(class-)[^\s]*/,'$1' + newClassName);
+// treeNode.aSpan.className = 'a ' + treeNode.className.replace('closed','spanClosed');
+// var aTag = treeNode.getElementsByTagName('a')[0];
+// aTag.title = 'Page type: ' + newClassName;
+// treeNode.setIconByClass();
+// });
+// },
+//
+// /**
+// * Set the parent ID of a tree node
+// */
+// setNodeParentID: function (idx, parentID) {
+// var treeNode = this.getTreeNodeByIdx(idx);
+// var parentNode = this.getTreeNodeByIdx(parentID);
+// var currentParentNode = jQuery(treeNode).parents('li')[0];
+// // Only change parent node if its different than the current,
+// // otherwise we affect the sort order unnecessarily due to
+// // appendTreeNode() not looking at existing sorts
+// if(!currentParentNode || parentNode != currentParentNode) parentNode.appendTreeNode(treeNode);
+// },
+//
+// setCurrentByIdx : function(idx) {
+// if(this.selected) {
+// var i,item;
+// for(i=0;item=this.selected[i];i++) {
+// item.removeNodeClass('current');
+// }
+// }
+//
+// __tree = this;
+// __tree.selected = [];
+//
+// this.performOnTreeNode(idx, function(treeNode) {
+// __tree.selected.push(treeNode);
+// treeNode.expose();
+// treeNode.addNodeClass('current');
+// });
+// },
+//
+// changeCurrentTo : function(newNode) {
+// if(this.selected) {
+// var i,item;
+// for(i=0;item=this.selected[i];i++) {
+// item.removeNodeClass('current');
+// }
+// }
+//
+// newNode.addNodeClass('current');
+//
+// this.selected = [newNode];
+// newNode.expose();
+// },
+//
+// firstSelected : function() {
+// if(this.selected) return this.selected[0];
+// }
+// };
+//
+// /**
+// * Extra methods for the tree node when used in the LHS of the CMS
+// */
+// TreeNodeAPI = Class.create();
+// TreeNodeAPI.prototype = {
+// selectTreeNode : function() {
+// var url = jQuery(this).find('a').attr('href');
+// if(url && url != '#') {
+// jQuery('#sitetree').trigger('selectionchanged', {node: this});
+// // don't get page if either event was cancelled,
+// // or the tree is currently in a selectable state.
+// if($('sitetree').notify('SelectionChanged', this) && !jQuery(this.tree).hasClass('multiselect')) {
+// this.getPageFromServer();
+// }
+// } else {
+// jQuery('#Form_EditForm').entwine('ss').removeForm();
+// }
+// },
+//
+// getPageFromServer : function() {
+// var self = this;
+// var xmlhttp = jQuery('#Form_EditForm').entwine('ss').loadForm(
+// jQuery(this).find('a').attr('href'),
+// function(response) {
+// self.removeNodeClass('loading');
+//
+// var pageID = jQuery(this).find(':input[name=ID]').val();
+// jQuery('#sitetree')[0].setCurrentByIdx(pageID);
+// }
+// );
+//
+// if(xmlhttp) this.addNodeClass('loading');
+// },
+// ajaxExpansion : function() {
+// this.addNodeClass('loading');
+// var ul = this.treeNodeHolder(false);
+// ul.innerHTML = 'loading...';
+//
+// // Any attempts to add children to this page should, in fact, cue them up for insertion later
+// ul.cuedNewNodes = [];
+// ul.appendTreeNode = function(node) {
+// this.cuedNewNodes[this.cuedNewNodes.length] = node;
+// }
+//
+// var args = {ajax:1, ID:this.getIdx()};
+//
+// // Add current locale for any subtree selection
+// if ($('LangSelector')) args.locale = $('LangSelector').value;
+//
+// // If the tree is selectable, we have to show all available children without
+// // artificial limitations from the serverside (minNodeCount). This is a measure
+// // to ensure no unexpanded nodes are missed in batch selection
+// if(Element.hasClassName('sitetree', 'multiselect')) args.minNodeCount = 0;
+//
+// url = this.tree.url(args);
+//
+// new Ajax.Request(url, {
+// onSuccess : this.installSubtree.bind(this),
+// onFailure : this.showSubtreeLoadingError
+// });
+// },
+// showSubtreeLoadingError: function(response) {
+// errorMessage('error loading subtree', response);
+// },
+//
+// /**
+// * Context menu
+// */
+// oncontextmenu: function(event) {
+// if(TreeContextMenu) {
+// if(!event) event = window.event;
+// createContextMenu(event, this, TreeContextMenu);
+// Event.stop(event);
+// return false;
+// }
+// },
+// duplicatePage: function() {
+// // Pass the parent ID to the duplicator, which helps ensure that multi-parent pages are duplicated into the node that the user clicked
+// var parentClause = "";
+// if(this.parentTreeNode && this.parentTreeNode.getIdx) {
+// parentClause = "&parentID=" + this.parentTreeNode.getIdx();
+// }
+//
+// new Ajax.Request(jQuery('base').attr('href') + 'admin/duplicate/' + this.getIdx() + '?ajax=1' + parentClause, {
+// method : 'get',
+// onSuccess : Ajax.Evaluator,
+// onFailure : function(response) {
+// errorMessage('Error: ', response);
+// }
+// });
+// },
+// duplicatePageWithChildren: function() {
+// new Ajax.Request(jQuery('base').attr('href') + 'admin/duplicatewithchildren/' + this.getIdx() + '?ajax=1', {
+// method : 'get',
+// onSuccess : Ajax.Evaluator,
+// onFailure : function(response) {
+// errorMessage('Error: ', response);
+// }
+// });
+// }
+// }
+//
+//
+//
+//
+// /**
+// * In the case of Tree & DraggableTree, the root tree and the sub-trees all use the same class.
+// * In this case, however, SiteTree has a much bigger API and so SiteSubTree is smaller.
+// */
+// SiteSubTree = Class.extend('Tree').extend('TreeAPI');
+// SiteSubTree.prototype = {
+// castAsTreeNode: function(li) {
+// behaveAs(li, SiteTreeNode, this.options);
+// }
+// }
+//
+//
+// /**
+// * Our SiteTree class extends the tree object with a richer manipulation API.
+// * The server will send a piece javascript that uses these functions. In this way, the server
+// * has flexibility over its operation, but the Script->HTML interface is kept client-side.
+// */
+// SiteTree = Class.extend('SiteSubTree');
+// SiteTree.prototype = {
+// initialize : function() {
+// this.Tree.initialize();
+//
+// /*
+// if(!this.tree.selected) this.tree.selected = [];
+// var els = this.getElementsByTagName('li');
+// for(var i=0;i -1) {
+// this.tree.selected.push(els[i]);
+// break;
+// }
+// */
+//
+// this.observeMethod('SelectionChanged', this.interruptLoading.bind(this) );
+//
+// jQuery('#Form_EditForm').bind('loadnewpage', this.onLoadNewPage.bind(this));
+// },
+// destroy: function () {
+// if(this.Tree) this.Tree.destroy();
+// this.Tree = null;
+// this.SiteSubTree = null;
+// this.TreeAPI = null;
+// this.selected = null;
+// },
+//
+// /**
+// * Stop the currently loading node from loading.
+// */
+// interruptLoading: function( newLoadingNode ) {
+// if( this.loadingNode ) this.loadingNode.removeNodeClass('loading');
+// this.loadingNode = newLoadingNode;
+// },
+//
+// /**
+// * Assumes to be triggered by a form element with the following input fields:
+// * ID, ParentID, TreeTitle (or Title), ClassName
+// */
+// onLoadNewPage: function(e, eventData) {
+// // finds a certain value in an array generated by jQuery.serializeArray()
+// var findInSerializedArray = function(arr, name) {
+// for(var i=0; i -1) {
+// if(!this.tree.selected) this.tree.selected = [];
+// this.tree.selected.push(this);
+// }
+//
+// if(!this.hints) this.hints = {}
+// },
+//
+// destroy: function () {
+// if(this.TreeNode) this.TreeNode.destroy();
+// this.TreeNode = null;
+// this.TreeNodeAPI = null;
+// },
+//
+// castAsTree: function(childUL) {
+// behaveAs(childUL, SiteSubTree, this.options);
+// if(this.draggableObj) childUL.makeDraggable();
+// },
+//
+// onselect: function() {
+// this.selectTreeNode();
+// return false;
+// },
+//
+//
+//
+// /**
+// * Drag'n'drop handlers - Ajax saving
+// */
+// onParentChanged : function(node, oldParent, newParent) {
+// var self = this;
+//
+// if(newParent.id.match(/^record-new/)) {
+// alert("You must save the page before dragging children into it");
+// return false;
+// }
+//
+// if( node == newParent || node.getIdx() == newParent.getIdx() ) {
+// alert("You cannot add a page to itself");
+// return false;
+// }
+//
+// if(node.innerHTML.toLowerCase().indexOf(' -1) {
+// alert("You can't moved deleted pages");
+// return false;
+// }
+//
+// if( Element.hasClassName( newParent, 'nochildren' ) ) {
+// alert("You can't add children to that node");
+// return false;
+// }
+//
+// jQuery.post(
+// SiteTreeHandlers.parentChanged_url,
+// 'ID=' + node.getIdx() + '&ParentID=' + newParent.getIdx(),
+// function(data, status) {
+// // TODO This should use a more common serialization in a new tree library
+// if(data.modified) {
+// for(var id in data.modified) {
+// self.tree.setNodeTitle(id, data.modified[id]['TreeTitle']);
+// }
+// }
+//
+// // Check if current page still exists, and refresh it.
+// // Otherwise remove the current form
+// var selectedNode = self.tree.firstSelected();
+// if(selectedNode) {
+// var selectedNodeId = self.tree.getIdxOf(selectedNode);
+// if(data.modified[selectedNode.getIdx()]) {
+// // only if the current page was modified
+// selectedNode.selectTreeNode();
+// }
+// }
+// },
+// 'json'
+// );
+//
+// return true;
+// },
+//
+// /**
+// * Called when the tree has been resorted
+// * nodeList is a list of all the nodes in the correct rder
+// * movedNode is the node that actually got moved to trigger this resorting
+// */
+// onOrderChanged : function(nodeList, movedNode) {
+// var self = this;
+//
+// var i, parts = Array();
+// sort = 0;
+//
+// for(i=0;i method; context-menu operations to get called
+// */
+// function createContextMenu(event, owner, menuItems) {
+// if(_CURRENT_CONTEXT_MENU) {
+// document.body.removeChild(_CURRENT_CONTEXT_MENU);
+// _CURRENT_CONTEXT_MENU = null;
+// }
+//
+// var menu = document.createElement("ul");
+// menu.className = 'contextMenu';
+// menu.style.position = 'absolute';
+// menu.style.left = event.clientX + 'px';
+// menu.style.top = event.clientY + 'px';
+//
+// var menuItemName, menuItemTag, menuATag;
+// for(menuItemName in menuItems) {
+// menuItemTag = document.createElement("li");
+//
+// menuATag = document.createElement("a");
+// menuATag.href = "#";
+// menuATag.onclick = menuATag.oncontextmenu = contextmenu_onclick;
+// menuATag.innerHTML = menuItemName;
+// menuATag.handler = menuItems[menuItemName];
+// menuATag.owner = owner;
+//
+// menuItemTag.appendChild(menuATag);
+// menu.appendChild(menuItemTag);
+// }
+//
+// document.body.appendChild(menu);
+//
+// document.body.onclick = contextmenu_close;
+//
+// _CURRENT_CONTEXT_MENU = menu;
+//
+// return menu;
+// }
+//
+// function contextmenu_close() {
+// if(_CURRENT_CONTEXT_MENU) {
+// document.body.removeChild(_CURRENT_CONTEXT_MENU);
+// _CURRENT_CONTEXT_MENU = null;
+// }
+// }
+//
+// function contextmenu_onclick() {
+// this.handler(this.owner);
+// contextmenu_close();
+// return false;
+// }
\ No newline at end of file