FEATURE Replacing custom tree.js with jstree thirdparty library

This commit is contained in:
Ingo Schommer 2011-02-22 18:08:04 +13:00
parent fe126e1a23
commit 2d85a4596b
2 changed files with 683 additions and 644 deletions

View File

@ -247,8 +247,8 @@ class LeftAndMain extends Controller {
Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/scriptaculous/dragdrop.js'); Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/scriptaculous/dragdrop.js');
Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/scriptaculous/controls.js'); Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/scriptaculous/controls.js');
Requirements::javascript(THIRDPARTY_DIR . '/tree/tree.js'); Requirements::javascript(THIRDPARTY_DIR . '/jstree/jquery.jstree.js');
Requirements::css(THIRDPARTY_DIR . '/tree/tree.css'); Requirements::css(THIRDPARTY_DIR . '/jstree/themes/default/style.css');
Requirements::javascript(CMS_DIR . '/javascript/LeftAndMain.js'); Requirements::javascript(CMS_DIR . '/javascript/LeftAndMain.js');
Requirements::javascript(CMS_DIR . '/javascript/LeftAndMain.Tree.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/dragdrop.js',
'sapphire/thirdparty/scriptaculous/controls.js', 'sapphire/thirdparty/scriptaculous/controls.js',
'cms/javascript/LeftAndMain.js', 'cms/javascript/LeftAndMain.js',
'sapphire/javascript/tree/tree.js', 'sapphire/thirdparty/jstree/jquery.jstree.js',
'sapphire/javascript/TreeSelectorField.js', 'sapphire/javascript/TreeDropdownField.js',
'cms/javascript/ThumbnailStripField.js', 'cms/javascript/ThumbnailStripField.js',
) )
); );
@ -523,7 +523,7 @@ class LeftAndMain extends Controller {
// getChildrenAsUL is a flexible and complex way of traversing the tree // getChildrenAsUL is a flexible and complex way of traversing the tree
$titleEval = ' $titleEval = '
"<li id=\"record-$child->ID\" class=\"" . $child->CMSTreeClasses($extraArg) . "\">" . "<li id=\"record-$child->ID\" data-id=\"$child->ID\" class=\"" . $child->CMSTreeClasses($extraArg) . "\">" .
"<a href=\"" . Controller::join_links(substr($extraArg->Link(),0,-1), "show", $child->ID) . "\" class=\"" . $child->CMSTreeClasses($extraArg) . "\" title=\"' "<a href=\"" . Controller::join_links(substr($extraArg->Link(),0,-1), "show", $child->ID) . "\" class=\"" . $child->CMSTreeClasses($extraArg) . "\" title=\"'
. _t('LeftAndMain.PAGETYPE','Page type: ') . _t('LeftAndMain.PAGETYPE','Page type: ')
. '".$child->class."\" >" . ($child->TreeTitle) . . '".$child->class."\" >" . ($child->TreeTitle) .

View File

@ -2,650 +2,689 @@
* File: LeftAndMain.Tree.js * 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($) { (function($) {
$(window).bind('load', function(e) { $(document).ready(function() {
// behaviour.js load handlers need to be fired before this event, so we artificially delay it $('#sitetree_ul').jstree({
setTimeout(function() { 'core': {
// make sure current ID of loaded form is actually selected in tree 'initially_open': ['record-0']
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' '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',
* Perform the given code on the each tree node with the given index. 'data': function(node) {
* There could be more than one :-) return { ID : $(node).data("id") ? $(node).data("id") : 0 , ajax: 1};
* @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) { 'ui': {
var treeNode = this.getTreeNodeByIdx(idx); "select_limit" : 1,
if(!treeNode) return; 'initially_select': [$('#sitetree_ul').find('.current').attr('id')]
},
if(treeNode.className.indexOf('manyparents') == -1) { 'plugins': ['themes', 'html_data', 'ui']
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); $('#sitetree_ul').bind('select_node.jstree', function(e, data) {
}, var node = data.rslt.obj;
var url = $(node).find('a:first').attr('href');
setNodeTitle : function(idx, title) { if(url && url != '#') {
this.performOnTreeNode(idx, function(treeNode) { var xmlhttp = $('#Form_EditForm').entwine('ss').loadForm(
var aTag = treeNode.getElementsByTagName('a')[0]; url,
aTag.innerHTML = title; 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]; }(jQuery));
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);
}
}
/** // if(typeof SiteTreeHandlers == 'undefined') SiteTreeHandlers = {};
* Our SiteTree class extends the tree object with a richer manipulation API. // SiteTreeHandlers.parentChanged_url = 'admin/ajaxupdateparent';
* The server will send a piece javascript that uses these functions. In this way, the server // SiteTreeHandlers.orderChanged_url = 'admin/ajaxupdatesort';
* has flexibility over its operation, but the Script->HTML interface is kept client-side. // SiteTreeHandlers.showRecord_url = 'admin/show/';
*/ // SiteTreeHandlers.controller_url = 'admin';
SiteTree = Class.extend('SiteSubTree'); //
SiteTree.prototype = { // var _HANDLER_FORMS = {
initialize : function() { // addpage : 'Form_AddForm',
this.Tree.initialize(); // batchactions : 'batchactionsforms',
// search : 'search_options'
/* // };
if(!this.tree.selected) this.tree.selected = []; //
var els = this.getElementsByTagName('li'); // (function($) {
for(var i=0;i<els.length;i++) if(els[i].className.indexOf('current') > -1) { // $(window).bind('load', function(e) {
this.tree.selected.push(els[i]); // // behaviour.js load handlers need to be fired before this event, so we artificially delay it
break; // 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;
this.observeMethod('SelectionChanged', this.interruptLoading.bind(this) ); // if(tree) tree.setCurrentByIdx(id);
// }, 200);
jQuery('#Form_EditForm').bind('loadnewpage', this.onLoadNewPage.bind(this)); // });
}, // }(jQuery));
destroy: function () { //
if(this.Tree) this.Tree.destroy(); // /**
this.Tree = null; // * Overload this with a real context menu if necessary
this.SiteSubTree = null; // */
this.TreeAPI = null; // var TreeContextMenu = null;
this.selected = null; //
}, // /**
// * Class: TreeAPI
/** // *
* Stop the currently loading node from loading. // * Extra methods for the tree when used in the LHS of the CMS
*/ // */
interruptLoading: function( newLoadingNode ) { // TreeAPI = Class.create();
if( this.loadingNode ) this.loadingNode.removeNodeClass('loading'); // TreeAPI.prototype = {
this.loadingNode = newLoadingNode; //
}, // setCustomURL: function(url, arguments) {
// this.customURL = url;
/** // this.customArguments = $H(arguments);
* Assumes to be triggered by a form element with the following input fields: // },
* ID, ParentID, TreeTitle (or Title), ClassName //
*/ // clearCustomURL: function() {
onLoadNewPage: function(e, eventData) { // this.customURL = this.customArguments = null;
// finds a certain value in an array generated by jQuery.serializeArray() // },
var findInSerializedArray = function(arr, name) { //
for(var i=0; i<arr.length; i++) { // url: function(args) {
if(arr[i].name == name) return arr[i].value; // var args = $H(args).merge(this.customArguments);
}; //
return false; // var url = this.customURL ? this.customURL : SiteTreeHandlers.loadTree_url;
}; // url = url + (url.match(/\?/) ? '&' : '?') + args.toQueryString();
//
var id = jQuery(e.target.ID).val(); // return url;
// },
// check if a form with a valid ID exists //
if(id) { // reload: function(options) {
var parentID = jQuery(e.target.ParentID).val(); // this.innerHTML = 'Loading...';
//
// set title (either from TreeTitle or from Title fields) // var args = {ajax:1, ID:0};
// Treetitle has special HTML formatting to denote the status changes. // if ($('LangSelector')) args.locale = $('LangSelector').value;
var title = jQuery((e.target.TreeTitle) ? e.target.TreeTitle : e.target.Title).val(); //
if(title) this.setNodeTitle(id, title); // var url = this.url(args);
//
// update icon (only if it has changed) // var self = this;
var className = jQuery(e.target.ClassName).val(); // jQuery.get(
if(className) this.setNodeIcon(id, className); // url,
// function(data, status){
// check if node exists, might have been created instead // self.innerHTML = data;
if(!this.getTreeNodeByIdx(id)) { // self.castAsTreeNode(self.firstChild);
var newNode = $('sitetree').createTreeNode(id, title, className); // if (options && options.onSuccess) options.onSuccess(data);
var parentNode = $('sitetree').getTreeNodeByIdx(parentID); // },
if(parentNode) parentNode.appendTreeNode(newNode); // 'html'
//newNode.selectTreeNode(); // );
} // },
//
// set correct parent (only if it has changed) // /**
if(parentID) this.setNodeParentID(id, jQuery(e.target.ParentID).val()); // * Perform the given code on the each tree node with the given index.
// * There could be more than one :-)
// set current tree element // * @param idx The index of the tree node
this.setCurrentByIdx(id); // * @param code A method to be executed, that will be passed the tree node
} else { // */
if(typeof eventData.origData != 'undefined') { // performOnTreeNode: function(idx, code) {
var node = this.getTreeNodeByIdx(eventData.origData.ID); // var treeNode = this.getTreeNodeByIdx(idx);
if(node && node.parentTreeNode) node.parentTreeNode.removeTreeNode(node); // if(!treeNode) return;
} //
} // if(treeNode.className.indexOf('manyparents') == -1) {
// code(treeNode);
} //
} // } else {
// var i,item,allNodes = this.getElementsByTagName('li');
SiteTreeNode = Class.extend('TreeNode').extend('TreeNodeAPI'); // for(i=0;item=allNodes[i];i++) {
SiteTreeNode.prototype = { // if(treeNode.id == item.id) code(item);
initialize: function(options) { // }
this.TreeNode.initialize(options); // }
if(this.className && this.className.match(/class\-([^\s]*)/)) { // },
var klass = RegExp.$1; //
if(typeof siteTreeHints != 'undefined' && siteTreeHints[klass]) { // getTreeNodeByIdx : function(idx) {
this.hints = siteTreeHints[klass]; // if(!idx) idx = "0";
this.dropperOptions = { // var node = document.getElementById('record-' + idx);
accept : (this.hints.allowedChildren && (this.className.indexOf('nochildren') == -1)) // if(idx == "0" && !node) node = document.getElementById('record-root');
? this.hints.allowedChildren : 'none' // return node;
}; // },
} // getIdxOf : function(treeNode) {
} // if(treeNode && treeNode.id && treeNode.id.match(/record-([0-9a-zA-Z\-]+)$/))
// return RegExp.$1;
if(this.className.indexOf('current') > -1) { // },
if(!this.tree.selected) this.tree.selected = []; // createTreeNode : function(idx, title, pageType) {
this.tree.selected.push(this); // var i;
} // var node = document.createElement('li');
// node.id = 'record-' + idx;
if(!this.hints) this.hints = {} // node.className = pageType;
}, //
// var aTag = document.createElement('a');
destroy: function () { // aTag.href = SiteTreeHandlers.showRecord_url + idx;
if(this.TreeNode) this.TreeNode.destroy(); // aTag.title = 'Page type: ' + pageType;
this.TreeNode = null; // aTag.innerHTML = title;
this.TreeNodeAPI = null; // node.appendChild(aTag);
}, //
// SiteTreeNode.create(node, this.options);
castAsTree: function(childUL) { //
behaveAs(childUL, SiteSubTree, this.options); // return node;
if(this.draggableObj) childUL.makeDraggable(); // },
}, //
// setNodeIdx : function(idx, newIdx) {
onselect: function() { // this.performOnTreeNode(idx, function(treeNode) {
this.selectTreeNode(); // treeNode.id = 'record-' + newIdx;
return false; // var aTag = treeNode.getElementsByTagName('a')[0];
}, // aTag.href = aTag.href.replace(idx, newIdx);
// });
//
// var treeNode = this.getTreeNodeByIdx(idx);
/** // },
* Drag'n'drop handlers - Ajax saving //
*/ // setNodeTitle : function(idx, title) {
onParentChanged : function(node, oldParent, newParent) { // this.performOnTreeNode(idx, function(treeNode) {
var self = this; // var aTag = treeNode.getElementsByTagName('a')[0];
// aTag.innerHTML = title;
if(newParent.id.match(/^record-new/)) { // });
alert("You must save the page before dragging children into it"); // },
return false; //
} // setNodeIcon: function(idx, newClassName) {
// this.performOnTreeNode(idx, function(treeNode) {
if( node == newParent || node.getIdx() == newParent.getIdx() ) { // treeNode.className = treeNode.className.replace(/(class-)[^\s]*/,'$1' + newClassName);
alert("You cannot add a page to itself"); // treeNode.aSpan.className = 'a ' + treeNode.className.replace('closed','spanClosed');
return false; // var aTag = treeNode.getElementsByTagName('a')[0];
} // aTag.title = 'Page type: ' + newClassName;
// treeNode.setIconByClass();
if(node.innerHTML.toLowerCase().indexOf('<del') > -1) { // });
alert("You can't moved deleted pages"); // },
return false; //
} // /**
// * Set the parent ID of a tree node
if( Element.hasClassName( newParent, 'nochildren' ) ) { // */
alert("You can't add children to that node"); // setNodeParentID: function (idx, parentID) {
return false; // var treeNode = this.getTreeNodeByIdx(idx);
} // var parentNode = this.getTreeNodeByIdx(parentID);
// var currentParentNode = jQuery(treeNode).parents('li')[0];
jQuery.post( // // Only change parent node if its different than the current,
SiteTreeHandlers.parentChanged_url, // // otherwise we affect the sort order unnecessarily due to
'ID=' + node.getIdx() + '&ParentID=' + newParent.getIdx(), // // appendTreeNode() not looking at existing sorts
function(data, status) { // if(!currentParentNode || parentNode != currentParentNode) parentNode.appendTreeNode(treeNode);
// TODO This should use a more common serialization in a new tree library // },
if(data.modified) { //
for(var id in data.modified) { // setCurrentByIdx : function(idx) {
self.tree.setNodeTitle(id, data.modified[id]['TreeTitle']); // if(this.selected) {
} // var i,item;
} // for(i=0;item=this.selected[i];i++) {
// item.removeNodeClass('current');
// Check if current page still exists, and refresh it. // }
// Otherwise remove the current form // }
var selectedNode = self.tree.firstSelected(); //
if(selectedNode) { // __tree = this;
var selectedNodeId = self.tree.getIdxOf(selectedNode); // __tree.selected = [];
if(data.modified[selectedNode.getIdx()]) { //
// only if the current page was modified // this.performOnTreeNode(idx, function(treeNode) {
selectedNode.selectTreeNode(); // __tree.selected.push(treeNode);
} // treeNode.expose();
} // treeNode.addNodeClass('current');
}, // });
'json' // },
); //
// changeCurrentTo : function(newNode) {
return true; // if(this.selected) {
}, // var i,item;
// for(i=0;item=this.selected[i];i++) {
/** // item.removeNodeClass('current');
* 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 //
*/ // newNode.addNodeClass('current');
onOrderChanged : function(nodeList, movedNode) { //
var self = this; // this.selected = [newNode];
// newNode.expose();
var i, parts = Array(); // },
sort = 0; //
// firstSelected : function() {
for(i=0;i<nodeList.length;i++) { // if(this.selected) return this.selected[0];
if(nodeList[i].getIdx && nodeList[i].getIdx()) { // }
parts[parts.length] = 'ID[]=' + nodeList[i].getIdx(); // };
//
// Ensure that the order of new records is preserved when they are moved THEN saved // /**
if( // * Extra methods for the tree node when used in the LHS of the CMS
nodeList[i].id.indexOf("record-new") == 0 // */
&& $('Form_EditForm_ID') // TreeNodeAPI = Class.create();
&& ('record-' + $('Form_EditForm_ID').value == nodeList[i].id) // TreeNodeAPI.prototype = {
&& $('Form_EditForm_Sort') // selectTreeNode : function() {
) { // var url = jQuery(this).find('a').attr('href');
$('Form_EditForm_Sort').value = ++sort // 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')) {
if(movedNode.getIdx && movedNode.getIdx()) { // this.getPageFromServer();
parts[parts.length] = 'MovedNodeID=' + movedNode.getIdx(); // }
} // } else {
// jQuery('#Form_EditForm').entwine('ss').removeForm();
if(parts) { // }
jQuery.post( // },
SiteTreeHandlers.orderChanged_url, //
parts.join('&'), // getPageFromServer : function() {
function(data, status) { // var self = this;
// TODO This should use a more common serialization in a new tree library // var xmlhttp = jQuery('#Form_EditForm').entwine('ss').loadForm(
if(data.modified) { // jQuery(this).find('a').attr('href'),
for(var id in data.modified) { // function(response) {
self.tree.setNodeTitle(id, data.modified[id]['TreeTitle']); // self.removeNodeClass('loading');
} //
} // var pageID = jQuery(this).find(':input[name=ID]').val();
// jQuery('#sitetree')[0].setCurrentByIdx(pageID);
// Check if current page still exists, and refresh it. // }
// Otherwise remove the current form // );
var selectedNode = self.tree.firstSelected(); //
if(selectedNode) { // if(xmlhttp) this.addNodeClass('loading');
var selectedNodeId = self.tree.getIdxOf(selectedNode); // },
if(data.modified[selectedNode.getIdx()]) { // ajaxExpansion : function() {
// only if the current page was modified // this.addNodeClass('loading');
selectedNode.selectTreeNode(); // 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
'json' // ul.cuedNewNodes = [];
); // ul.appendTreeNode = function(node) {
} // this.cuedNewNodes[this.cuedNewNodes.length] = node;
// }
return true; //
} // var args = {ajax:1, ID:this.getIdx()};
} //
// // Add current locale for any subtree selection
// Build the site tree // if ($('LangSelector')) args.locale = $('LangSelector').value;
SiteTree.applyTo('#sitetree'); //
// // If the tree is selectable, we have to show all available children without
/** // // artificial limitations from the serverside (minNodeCount). This is a measure
* Reorganise action checkbox // // to ensure no unexpanded nodes are missed in batch selection
*/ // if(Element.hasClassName('sitetree', 'multiselect')) args.minNodeCount = 0;
ReorganiseAction = Class.create(); //
ReorganiseAction.applyTo('#sortitems'); // url = this.tree.url(args);
ReorganiseAction.prototype = { //
initialize: function () { // new Ajax.Request(url, {
}, // onSuccess : this.installSubtree.bind(this),
// onFailure : this.showSubtreeLoadingError
onclick : function() { // });
if ($('sitetree').isDraggable == false) { // },
$('sitetree').makeDraggable(); // showSubtreeLoadingError: function(response) {
} else { // errorMessage('error loading subtree', response);
$('sitetree').stopBeingDraggable(); // },
} //
} // /**
} // * Context menu
// */
var _CURRENT_CONTEXT_MENU = null; // oncontextmenu: function(event) {
// if(TreeContextMenu) {
/** // if(!event) event = window.event;
* Create a new context menu // createContextMenu(event, this, TreeContextMenu);
* @param event The event object // Event.stop(event);
* @param owner The DOM element that this context-menu was requested from // return false;
* @param menuItems A map of title -> method; context-menu operations to get called // }
*/ // },
function createContextMenu(event, owner, menuItems) { // duplicatePage: function() {
if(_CURRENT_CONTEXT_MENU) { // // Pass the parent ID to the duplicator, which helps ensure that multi-parent pages are duplicated into the node that the user clicked
document.body.removeChild(_CURRENT_CONTEXT_MENU); // var parentClause = "";
_CURRENT_CONTEXT_MENU = null; // if(this.parentTreeNode && this.parentTreeNode.getIdx) {
} // parentClause = "&parentID=" + this.parentTreeNode.getIdx();
// }
var menu = document.createElement("ul"); //
menu.className = 'contextMenu'; // new Ajax.Request(jQuery('base').attr('href') + 'admin/duplicate/' + this.getIdx() + '?ajax=1' + parentClause, {
menu.style.position = 'absolute'; // method : 'get',
menu.style.left = event.clientX + 'px'; // onSuccess : Ajax.Evaluator,
menu.style.top = event.clientY + 'px'; // onFailure : function(response) {
// errorMessage('Error: ', response);
var menuItemName, menuItemTag, menuATag; // }
for(menuItemName in menuItems) { // });
menuItemTag = document.createElement("li"); // },
// duplicatePageWithChildren: function() {
menuATag = document.createElement("a"); // new Ajax.Request(jQuery('base').attr('href') + 'admin/duplicatewithchildren/' + this.getIdx() + '?ajax=1', {
menuATag.href = "#"; // method : 'get',
menuATag.onclick = menuATag.oncontextmenu = contextmenu_onclick; // onSuccess : Ajax.Evaluator,
menuATag.innerHTML = menuItemName; // onFailure : function(response) {
menuATag.handler = menuItems[menuItemName]; // errorMessage('Error: ', response);
menuATag.owner = owner; // }
// });
menuItemTag.appendChild(menuATag); // }
menu.appendChild(menuItemTag); // }
} //
//
document.body.appendChild(menu); //
//
document.body.onclick = contextmenu_close; // /**
// * In the case of Tree & DraggableTree, the root tree and the sub-trees all use the same class.
_CURRENT_CONTEXT_MENU = menu; // * In this case, however, SiteTree has a much bigger API and so SiteSubTree is smaller.
// */
return menu; // SiteSubTree = Class.extend('Tree').extend('TreeAPI');
} // SiteSubTree.prototype = {
// castAsTreeNode: function(li) {
function contextmenu_close() { // behaveAs(li, SiteTreeNode, this.options);
if(_CURRENT_CONTEXT_MENU) { // }
document.body.removeChild(_CURRENT_CONTEXT_MENU); // }
_CURRENT_CONTEXT_MENU = null; //
} //
} // /**
// * Our SiteTree class extends the tree object with a richer manipulation API.
function contextmenu_onclick() { // * The server will send a piece javascript that uses these functions. In this way, the server
this.handler(this.owner); // * has flexibility over its operation, but the Script->HTML interface is kept client-side.
contextmenu_close(); // */
return false; // 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<els.length;i++) if(els[i].className.indexOf('current') > -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<arr.length; i++) {
// if(arr[i].name == name) return arr[i].value;
// };
// return false;
// };
//
// var id = jQuery(e.target.ID).val();
//
// // check if a form with a valid ID exists
// if(id) {
// var parentID = jQuery(e.target.ParentID).val();
//
// // set title (either from TreeTitle or from Title fields)
// // Treetitle has special HTML formatting to denote the status changes.
// var title = jQuery((e.target.TreeTitle) ? e.target.TreeTitle : e.target.Title).val();
// if(title) this.setNodeTitle(id, title);
//
// // update icon (only if it has changed)
// var className = jQuery(e.target.ClassName).val();
// if(className) this.setNodeIcon(id, className);
//
// // check if node exists, might have been created instead
// if(!this.getTreeNodeByIdx(id)) {
// var newNode = $('sitetree').createTreeNode(id, title, className);
// var parentNode = $('sitetree').getTreeNodeByIdx(parentID);
// if(parentNode) parentNode.appendTreeNode(newNode);
// //newNode.selectTreeNode();
// }
//
// // set correct parent (only if it has changed)
// if(parentID) this.setNodeParentID(id, jQuery(e.target.ParentID).val());
//
// // set current tree element
// this.setCurrentByIdx(id);
// } else {
// if(typeof eventData.origData != 'undefined') {
// var node = this.getTreeNodeByIdx(eventData.origData.ID);
// if(node && node.parentTreeNode) node.parentTreeNode.removeTreeNode(node);
// }
// }
//
// }
// }
//
// SiteTreeNode = Class.extend('TreeNode').extend('TreeNodeAPI');
// SiteTreeNode.prototype = {
// initialize: function(options) {
// this.TreeNode.initialize(options);
// if(this.className && this.className.match(/class\-([^\s]*)/)) {
// var klass = RegExp.$1;
// if(typeof siteTreeHints != 'undefined' && siteTreeHints[klass]) {
// this.hints = siteTreeHints[klass];
// this.dropperOptions = {
// accept : (this.hints.allowedChildren && (this.className.indexOf('nochildren') == -1))
// ? this.hints.allowedChildren : 'none'
// };
// }
// }
//
// if(this.className.indexOf('current') > -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('<del') > -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<nodeList.length;i++) {
// if(nodeList[i].getIdx && nodeList[i].getIdx()) {
// parts[parts.length] = 'ID[]=' + nodeList[i].getIdx();
//
// // Ensure that the order of new records is preserved when they are moved THEN saved
// if(
// nodeList[i].id.indexOf("record-new") == 0
// && $('Form_EditForm_ID')
// && ('record-' + $('Form_EditForm_ID').value == nodeList[i].id)
// && $('Form_EditForm_Sort')
// ) {
// $('Form_EditForm_Sort').value = ++sort
// }
// }
// }
//
// if(movedNode.getIdx && movedNode.getIdx()) {
// parts[parts.length] = 'MovedNodeID=' + movedNode.getIdx();
// }
//
// if(parts) {
// jQuery.post(
// SiteTreeHandlers.orderChanged_url,
// parts.join('&'),
// 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;
// }
// }
//
// // Build the site tree
// SiteTree.applyTo('#sitetree');
//
// /**
// * Reorganise action checkbox
// */
// ReorganiseAction = Class.create();
// ReorganiseAction.applyTo('#sortitems');
// ReorganiseAction.prototype = {
// initialize: function () {
// },
//
// onclick : function() {
// if ($('sitetree').isDraggable == false) {
// $('sitetree').makeDraggable();
// } else {
// $('sitetree').stopBeingDraggable();
// }
// }
// }
//
// var _CURRENT_CONTEXT_MENU = null;
//
// /**
// * Create a new context menu
// * @param event The event object
// * @param owner The DOM element that this context-menu was requested from
// * @param menuItems A map of title -> 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;
// }