silverstripe-cms/javascript/LeftAndMain_left.js
Ingo Schommer 7ffbefb6b1 BUGFIX Changed sitetree default selection in LeftAndMain.Tree.js to fire on window.load instead of document.ready() through entwine. We need to ensure behaviour.js bindings are available before
BUGFIX Automatically selecting root node in CMS trees (necessary because now we actually have forms on the root node, and its a valid click target) (from r99605)

git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/cms/branches/2.4@99693 467b73ca-7a2a-4603-9d3b-597d59a354a9
2011-02-02 17:53:01 +13:00

557 lines
15 KiB
JavaScript
Executable File

/**
* 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_AddPageOptionsForm',
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;
tree.setCurrentByIdx(id);
}, 200);
});
}(jQuery));
/**
* Overload this with a real context menu if necessary
*/
var TreeContextMenu = null;
/**
* 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;
new Ajax.Request(url, {
onSuccess: function(response){
self.innerHTML = response.responseText;
self.castAsTreeNode(self.firstChild);
if (options && options.onSuccess) options.onSuccess(response);
},
onFailure: function(response){
errorMessage('error loading tree', response);
if (options.onError) options.onError(response);
}
});
},
/**
* 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;
});
},
addNodeClassByIdx : function(idx, className) {
this.performOnTreeNode(idx, function(treeNode) {
treeNode.addNodeClass(className);
});
},
setNodeIcon: function(idx, oldClassName, newClassName) {
this.performOnTreeNode(idx, function(treeNode) {
treeNode.className = treeNode.className.replace(oldClassName,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);
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() {
if(this.getElementsByTagName('a')[0].href) {
_AJAX_LOADING = true;
if($('sitetree').notify('SelectionChanged', this)) {
autoSave(true, this.getPageFromServer.bind(this));
}
}
},
getPageFromServer : function() {
$('Form_EditForm').getPageFromServer(this.id.replace('record-',''), this);
_AJAX_LOADING = false;
},
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.installSubtreeAndRefresh.bind(this),
onFailure : this.showSubtreeLoadingError
});
},
installSubtreeAndRefresh : function(response){
this.installSubtree(response);
if(this.tree.className.indexOf('multiselect') != -1) {
batchActionGlobals.refreshSelected(this);
}
},
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(baseHref() + 'admin/duplicate/' + this.getIdx() + '?ajax=1' + parentClause, {
method : 'get',
onSuccess : Ajax.Evaluator,
onFailure : function(response) {
errorMessage('Error: ', response);
}
});
},
duplicatePageWithChildren: function() {
new Ajax.Request(baseHref() + '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<els.length;i++) if(els[i].className.indexOf('current') > -1) {
this.tree.selected.push(els[i]);
break;
}
*/
this.observeMethod('SelectionChanged', this.interruptLoading.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;
}
}
SiteTreeNode = Class.extend('TreeNode').extend('TreeNodeAPI');
SiteTreeNode.prototype = {
initialize: function(options) {
this.TreeNode.initialize(options);
if(this.className && this.className.match(/ *([^ ]+)( +|$)/)) {
if((typeof siteTreeHints != 'undefined') && siteTreeHints[ RegExp.$1 ]) {
this.hints = siteTreeHints[ RegExp.$1 ];
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) {
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;
}
var currentlyOpenPageID = 0;
if($('Form_EditForm').elements.ID) currentlyOpenPageID = $('Form_EditForm').elements.ID.value;
statusMessage(ss.i18n._t('CMSMAIN.SAVING'), '', true);
new Ajax.Request(SiteTreeHandlers.parentChanged_url, {
method : 'post',
postBody : 'ID=' + node.getIdx() + '&ParentID=' + newParent.getIdx() + '&CurrentlyOpenPageID=' + currentlyOpenPageID,
onSuccess : Ajax.Evaluator,
onFailure : function(response) {
errorMessage('error saving parent', response);
}
});
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) {
statusMessage(ss.i18n._t('CMSMAIN.SAVING'), '', true);
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 )
if( $('Form_EditForm_ID') && ( 'record-' + $('Form_EditForm_ID').value == nodeList[i].id ) )
if( $('Form_EditForm_Sort') )
$('Form_EditForm_Sort').value = ++sort;
}
}
if(movedNode.getIdx && movedNode.getIdx()) {
parts[parts.length] = 'MovedNodeID=' + movedNode.getIdx();
}
var currentlyOpenPageID = 0;
if($('Form_EditForm').elements.ID) currentlyOpenPageID = $('Form_EditForm').elements.ID.value;
if(parts) {
new Ajax.Request(SiteTreeHandlers.orderChanged_url, {
method : 'post',
postBody : parts.join('&') + '&CurrentlyOpenPageID=' + currentlyOpenPageID,
/*onSuccess : function(response) {
// statusMessage(response.responseText, 'good');
},*/
onSuccess: Ajax.Evaluator,
onFailure : function(response) {
errorMessage('error saving order', response);
}
});
}
return true;
}
}
/**
* Action panel at the top of the left hand tree
*/
treeactions = {
toggleSelection : function(li) {
li = (li.tagName.toLowerCase() == 'button') ? li.parentNode : li;
// Deselect
if(li.selected) this.closeSelection(li, true);
else this.openSelection(li, true);
fixHeight_left();
return li.selected;
},
closeSelection : function(li, nested) {
li = (li.tagName.toLowerCase() == 'button') ? li.parentNode : li;
Element.hide(_HANDLER_FORMS[li.id]);
removeClass(li, 'selected');
li.selected = false;
$(_HANDLER_FORMS[li.id]).notify('Close');
if(li.onclose) li.onclose();
if(typeof nested == 'undefined') fixHeight_left();
},
openSelection: function(li, nested) {
li = (li.tagName.toLowerCase() == 'button') ? li.parentNode : li;
addClass(li, 'selected');
Element.show(_HANDLER_FORMS[li.id]);
li.selected = true;
// Deselect siblings
var i, set = li.parentNode.getElementsByTagName('li');
for(i=0;i<set.length;i++) if(li != set[i] && set[i].selected) {
this.closeSelection(set[i], true);
}
if(typeof nested == 'undefined') fixHeight_left();
}
}
function fixHeight_left() {
if($('treepanes')) {
fitToParent('treepanes');
var st = $('treepanes');
if(st.resize) st.resize();
}
else fitToParent('sitetree_holder');
}
// 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();
}
}
}