mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-09-08 10:41:25 +02:00
The main benefit of this is so that authors who make use of .editorconfig don't end up with whitespace changes in their PRs. Spaces vs. tabs has been left alone, although that could do with a tidy-up in SS4 after the switch to PSR-1/2. The command used was this: for match in '*.ss' '*.css' '*.scss' '*.html' '*.yml' '*.php' '*.js' '*.csv' '*.inc' '*.php5'; do find . -path ./thirdparty -not -prune -o -path ./admin/thirdparty -not -prune -o -type f -name "$match" -exec sed -E -i '' 's/[[:space:]]+$//' {} \+ find . -path ./thirdparty -not -prune -o -path ./admin/thirdparty -not -prune -o -type f -name "$match" | xargs perl -pi -e 's/ +$//' done
926 lines
26 KiB
JavaScript
926 lines
26 KiB
JavaScript
/*
|
|
* Content-separated javascript tree widget
|
|
*
|
|
* Usage:
|
|
* behaveAs(someUL, Tree)
|
|
* OR behaveAs(someUL, DraggableTree)
|
|
*
|
|
* Extended by Steven J. DeRose, deroses@mail.nih.gov, sderose@acm.org.
|
|
*
|
|
* INPUT REQUIREMENTS:
|
|
* Put class="tree" on topmost UL(s).
|
|
* Can put class="closed" on LIs to have them collapsed on startup.
|
|
*
|
|
* The structure we build is:
|
|
* li class="children last closed" <=== original li from source
|
|
* children: there's a UL child
|
|
* last: no following LI
|
|
* closed: is collapsed (may be in src)
|
|
* span class="a children spanClosed" <=== contains children before UL
|
|
* children: there's a UL (now a sib)
|
|
* spanClosed: is collapsed
|
|
* span class="b" <=== +/- click is caught here
|
|
* span class="c" <=== for spacing and lines
|
|
* a href="..." <=== original pre-UL stuff (e.g., a)
|
|
* ul...
|
|
*
|
|
*/
|
|
|
|
Tree = Class.create();
|
|
Tree.prototype = {
|
|
/*
|
|
* Initialise a tree node, converting all its LIs appropriately.
|
|
* This means go through all li children, and move the content of each
|
|
* (before any UL child) down into 3 intermediate spans, classes a/b/c.
|
|
*/
|
|
initialize: function(options) {
|
|
this.isDraggable = false;
|
|
var i,li;
|
|
|
|
this.options = options ? options : {};
|
|
if(!this.options.tree) this.options.tree = this;
|
|
|
|
this.tree = this.options.tree;
|
|
|
|
// Set up observer
|
|
if(this == this.tree) Observable.applyTo(this);
|
|
|
|
// Find all LIs to process
|
|
// Don't let it re-do a node it's already done.
|
|
for(i=0;i<this.childNodes.length;i++) {
|
|
if(this.childNodes[i].tagName && this.childNodes[i].tagName.toLowerCase() == 'li' &&
|
|
!(this.childNodes[i].childNodes[0] &&
|
|
this.childNodes[i].childNodes[0].attributes &&
|
|
this.childNodes[i].childNodes[0].attributes["class"] &&
|
|
this.childNodes[i].childNodes[0].attributes["class"] == "a")) {
|
|
li = this.childNodes[i];
|
|
|
|
this.castAsTreeNode(li);
|
|
|
|
// If we've added a DIV to this node, then increment i;
|
|
while(this.childNodes[i].tagName.toLowerCase() != "li") i++;
|
|
}
|
|
}
|
|
|
|
// Not sure what following line is really doing for us....
|
|
this.className = this.className.replace(/ ?unformatted ?/, ' ');
|
|
|
|
if(li) {
|
|
li.addNodeClass('last');
|
|
//li.addNodeClass('closed');
|
|
|
|
if(this.parentNode.tagName.toLowerCase() == "li") {
|
|
this.treeNode = this.parentNode;
|
|
}
|
|
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
},
|
|
destroy: function() {
|
|
this.tree = null;
|
|
this.treeNode = null;
|
|
if(this.options) this.options.tree = null;
|
|
this.options = null;
|
|
},
|
|
|
|
/**
|
|
* Convert the given <li> tag into a suitable tree node
|
|
*/
|
|
castAsTreeNode: function(li) {
|
|
behaveAs(li, TreeNode, this.options);
|
|
},
|
|
|
|
getIdxOf : function(el) {
|
|
if(!el.treeNode) el.treeNode = el;
|
|
// Special case for TreeMultiselectField
|
|
if(el.treeNode.id.match(/^selector-([^-]+)-([0-9]+)$/)) return RegExp.$2;
|
|
// Other case for LHS tree of CMS
|
|
if(el.treeNode.id.match(/([^-]+)-(.+)$/)) return RegExp.$1;
|
|
else return el.treeNode.id;
|
|
},
|
|
|
|
childTreeNodes: function() {
|
|
var i,item, children = [];
|
|
for(i=0;item=this.childNodes[i];i++) {
|
|
if(item.tagName && item.tagName.toLowerCase() == 'li') children.push(item);
|
|
}
|
|
return children;
|
|
},
|
|
hasChildren: function() {
|
|
return this.childTreeNodes().length > 0;
|
|
},
|
|
|
|
/**
|
|
* Turn a normal tree into a draggable one.
|
|
*/
|
|
makeDraggable: function() {
|
|
this.isDraggable = true;
|
|
var i,item,x;
|
|
|
|
var trees = this.getElementsByTagName('ul');
|
|
for(x in DraggableTree.prototype) this[x] = DraggableTree.prototype[x];
|
|
DraggableTree.prototype.setUpDragability.apply(this);
|
|
|
|
var nodes = this.getElementsByTagName('li');
|
|
for(i=0;item=nodes[i];i++) {
|
|
for(x in DraggableTreeNode.prototype) item[x] = DraggableTreeNode.prototype[x];
|
|
}
|
|
for(i=0;item=trees[i];i++) {
|
|
for(x in DraggableTree.prototype) item[x] = DraggableTree.prototype[x];
|
|
}
|
|
for(i=0;item=nodes[i];i++) {
|
|
DraggableTreeNode.prototype.setUpDragability.apply(item);
|
|
}
|
|
for(i=0;item=trees[i];i++) {
|
|
DraggableTree.prototype.setUpDragability.apply(item);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Add the given child node to this tree node.
|
|
* If 'before' is specified, then it will be inserted before that.
|
|
*/
|
|
appendTreeNode : function(child, before) {
|
|
if(!child) return;
|
|
|
|
// Remove from the old parent node - this will ensure that the classes of the old tree
|
|
// item are updated accordingly
|
|
if(child && child.parentTreeNode) {
|
|
var oldParent = child.parentTreeNode;
|
|
oldParent.removeTreeNode(child);
|
|
}
|
|
var lastNode, i, holder = this;
|
|
if(lastNode = this.lastTreeNode()) lastNode.removeNodeClass('last');
|
|
|
|
// Do the actual moving
|
|
if(before) {
|
|
child.removeNodeClass('last');
|
|
|
|
if(holder != before.parentNode) {
|
|
throw("TreeNode.appendTreeNode: 'before' not contained within the holder");
|
|
holder.appendChild(child);
|
|
} else {
|
|
holder.insertBefore(child, before);
|
|
}
|
|
} else {
|
|
holder.appendChild(child);
|
|
}
|
|
|
|
if(this.parentNode && this.parentNode.fixDragHelperDivs) this.parentNode.fixDragHelperDivs();
|
|
if(oldParent && oldParent.fixDragHelperDivs) oldParent.fixDragHelperDivs();
|
|
|
|
// Update the helper classes
|
|
if(this.parentNode && this.parentNode.tagName.toLowerCase() == 'li') {
|
|
if(this.parentNode.className.indexOf('closed') == -1) this.parentNode.addNodeClass('children');
|
|
this.lastTreeNode().addNodeClass('last');
|
|
}
|
|
|
|
// Update the helper variables
|
|
if(this.parentNode.tagName.toLowerCase() == 'li') child.parentTreeNode = this.parentNode;
|
|
else child.parentTreeNode = null;
|
|
|
|
if(this.isDraggable) {
|
|
for(x in DraggableTreeNode.prototype) child[x] = DraggableTreeNode.prototype[x];
|
|
DraggableTreeNode.prototype.setUpDragability.apply(child);
|
|
}
|
|
},
|
|
|
|
lastTreeNode : function() {
|
|
var i, holder = this;
|
|
for(i=holder.childNodes.length-1;i>=0;i--) {
|
|
if(holder.childNodes[i].tagName && holder.childNodes[i].tagName.toLowerCase() == 'li') return holder.childNodes[i];
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Remove the given child node from this tree node.
|
|
*/
|
|
removeTreeNode : function(child) {
|
|
// Remove the child
|
|
var holder = this;
|
|
try { holder.removeChild(child); } catch(er) { }
|
|
|
|
// Look for remaining children
|
|
var i, hasChildren = false;
|
|
for(i=0;i<holder.childNodes.length;i++) {
|
|
if(holder.childNodes[i].tagName && holder.childNodes[i].tagName.toLowerCase() == "li") {
|
|
hasChildren = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Update the helper classes accordingly
|
|
if(!hasChildren) this.removeNodeClass('children');
|
|
else this.lastTreeNode().addNodeClass('last');
|
|
|
|
// Update the helper variables
|
|
if(child.parentTreeNode == this.parentNode) {
|
|
child.parentTreeNode = null;
|
|
}
|
|
},
|
|
|
|
open: function() {
|
|
|
|
},
|
|
|
|
expose: function() {
|
|
|
|
},
|
|
|
|
addNodeClass : function(className) {
|
|
if( this.parentNode.tagName.toLowerCase() == 'li' )
|
|
this.parentNode.addNodeClass(className);
|
|
},
|
|
removeNodeClass : function(className) {
|
|
if( this.parentNode.tagName.toLowerCase() == 'li' )
|
|
this.parentNode.removeNodeClass(className);
|
|
}
|
|
}
|
|
|
|
TreeNode = Class.create();
|
|
TreeNode.prototype = {
|
|
initialize: function(options) {
|
|
var spanA, spanB, spanC;
|
|
var startingPoint, stoppingPoint, childUL;
|
|
var j;
|
|
|
|
// Basic hook-ups
|
|
var li = this;
|
|
this.options = options ? options : {};
|
|
|
|
this.tree = this.options.tree;
|
|
|
|
if(!this.ajaxExpansion && this.options.ajaxExpansion)
|
|
this.ajaxExpansion = this.options.ajaxExpansion;
|
|
if(this.options.getIdx)
|
|
this.getIdx = this.options.getIdx;
|
|
|
|
// Get this.recordID from the last "-" separated chunk of the id HTML attribute
|
|
// eg: <li id="treenode-6"> would give a recordID of 6
|
|
if(this.id && this.id.match(/([^-]+)-(.+)$/))
|
|
this.recordID = RegExp.$1;
|
|
|
|
// Create our extra spans
|
|
spanA = document.createElement('span');
|
|
spanB = document.createElement('span');
|
|
spanC = document.createElement('span');
|
|
spanA.appendChild(spanB);
|
|
spanB.appendChild(spanC);
|
|
spanA.className = 'a ' + li.className.replace('closed','spanClosed');
|
|
spanB.className = 'b';
|
|
spanB.onclick = TreeNode_bSpan_onclick;
|
|
spanC.className = 'c';
|
|
|
|
this.castAsSpanA(spanA);
|
|
|
|
// Add +/- icon to select node that has children
|
|
if (li.hasChildren() && li.className.indexOf('current') > -1) {
|
|
li.className = li.className + ' children';
|
|
spanA.className = spanA.className + ' children';
|
|
}
|
|
|
|
// Find the UL within the LI, if it exists
|
|
stoppingPoint = li.childNodes.length;
|
|
startingPoint = 0;
|
|
childUL = null;
|
|
for(j=0;j<li.childNodes.length;j++) {
|
|
// Find last div before first ul (unnecessary in our usage)
|
|
/*
|
|
if(li.childNodes[j].tagName && li.childNodes[j].tagName.toLowerCase() == 'div') {
|
|
startingPoint = j + 1;
|
|
continue;
|
|
}
|
|
*/
|
|
|
|
if(li.childNodes[j].tagName && li.childNodes[j].tagName.toLowerCase() == 'ul') {
|
|
childUL = li.childNodes[j];
|
|
stoppingPoint = j;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Move all the nodes up until that point into spanC
|
|
for(j=startingPoint;j<stoppingPoint;j++) {
|
|
/* Use [startingPoint] every time, because the appentChild
|
|
removes the node, so it then points to the next one. */
|
|
spanC.appendChild(li.childNodes[startingPoint]);
|
|
}
|
|
|
|
// Insert the outermost extra span into the tree
|
|
if(li.childNodes.length > startingPoint) li.insertBefore(spanA, li.childNodes[startingPoint]);
|
|
else li.appendChild(spanA);
|
|
|
|
// Create appropriate node references;
|
|
if(li.parentNode && li.parentNode.parentNode && li.parentNode.parentNode.tagName.toLowerCase() == 'li') {
|
|
li.parentTreeNode = li.parentNode.parentNode;
|
|
}
|
|
li.aSpan = spanA;
|
|
li.bSpan = spanB;
|
|
li.cSpan = spanC;
|
|
li.treeNode = spanA.treeNode = spanB.treeNode = spanC.treeNode = li;
|
|
var aTag = spanC.getElementsByTagName('a')[0];
|
|
if(aTag) {
|
|
aTag.treeNode = li;
|
|
li.aTag = aTag;
|
|
|
|
} else {
|
|
throw("Tree creation: A tree needs <a> tags inside the <li>s to work properly.");
|
|
}
|
|
|
|
|
|
aTag.onclick = TreeNode_aTag_onclick.bindAsEventListener(aTag);
|
|
|
|
|
|
// Process the children
|
|
if(childUL != null) {
|
|
if(this.castAsTree(childUL)) { /* ***** RECURSE ***** */
|
|
if(this.className.indexOf('closed') == -1) {
|
|
this.addNodeClass('children');
|
|
}
|
|
}
|
|
} else {
|
|
this.removeNodeClass('closed');
|
|
}
|
|
|
|
this.setIconByClass();
|
|
},
|
|
|
|
destroy: function() {
|
|
// Debug.show(this);
|
|
|
|
this.tree = null;
|
|
this.treeNode = null;
|
|
this.parentTreeNode = null;
|
|
|
|
if(this.options) this.options.tree = null;
|
|
this.options = null;
|
|
|
|
if(this.aTag) {
|
|
this.aTag.treeNode = null;
|
|
this.aTag.onclick = null;
|
|
}
|
|
if(this.aSpan) {
|
|
this.aSpan.treeNode = null;
|
|
this.aSpan.onmouseover = null;
|
|
this.aSpan.onmouseout = null;
|
|
}
|
|
if(this.bSpan) {
|
|
this.bSpan.treeNode = null;
|
|
this.bSpan.onclick = null;
|
|
}
|
|
if(this.cSpan) this.cSpan.treeNode = null;
|
|
|
|
this.aSpan = null;
|
|
this.bSpan = null;
|
|
this.cSpan = null;
|
|
this.aTag = null;
|
|
},
|
|
|
|
/**
|
|
* Cast the given span as the <span class="a"> item for this tree.
|
|
*/
|
|
castAsSpanA: function(spanA) {
|
|
var x;
|
|
for(x in TreeNode_SpanA) spanA[x] = TreeNode_SpanA[x];
|
|
},
|
|
/**
|
|
* Cast the child <ul> as a tree
|
|
*/
|
|
castAsTree: function(childUL) {
|
|
return behaveAs(childUL, Tree, this.options);
|
|
},
|
|
|
|
/**
|
|
* Triggered from clicks on spans of class b, the +/- buttons.
|
|
* Closed is represented by adding class close to the LI, and
|
|
* class spanClose to spanA.
|
|
* Pass 'force' as "open" or "close" to force it to that state,
|
|
* otherwise it toggles.
|
|
*/
|
|
toggle : function(force) {
|
|
if(this.treeNode.wasDragged || this.treeNode.anchorWasClicked) {
|
|
this.treeNode.wasDragged = false;
|
|
this.treeNode.anchorWasClicked = false;
|
|
return;
|
|
}
|
|
|
|
/* Note: It appears the 'force' parameter is no longer used. Here is old code that used it:
|
|
if( force == "open"){
|
|
treeOpen( topSpan, el )
|
|
}
|
|
else if( force == "close" ){
|
|
treeClose( topSpan, el )
|
|
}
|
|
*/
|
|
|
|
if(this.hasChildren() || this.className.match(/(^| )unexpanded($| )/)) {
|
|
if(this.className.match(/(^| )closed($| )/) || this.className.match(/(^| )unexpanded($| )/)) this.open();
|
|
else this.close();
|
|
}
|
|
},
|
|
|
|
open : function () {
|
|
// Normal tree node
|
|
if(Element.hasClassName(this, 'unexpanded') && !this.hasChildren()) {
|
|
if(this.ajaxExpansion) this.ajaxExpansion();
|
|
}
|
|
|
|
if(!this.className.match(/(^| )closed($| )/)) return;
|
|
|
|
this.removeNodeClass('closed');
|
|
this.removeNodeClass('unexpanded');
|
|
},
|
|
close : function () {
|
|
this.addNodeClass('closed');
|
|
},
|
|
expose : function() {
|
|
if(this.parentTreeNode) {
|
|
this.parentTreeNode.open();
|
|
this.parentTreeNode.expose();
|
|
}
|
|
},
|
|
setIconByClass: function() {
|
|
if(typeof _TREE_ICONS == 'undefined') return;
|
|
var classes = this.className.split(/\s+/);
|
|
var obj = this;
|
|
|
|
classes.each(function(className) {
|
|
var className = className.replace(/class-/, '');
|
|
if(_TREE_ICONS[className]) {
|
|
obj.fileIcon = _TREE_ICONS[className].fileIcon;
|
|
obj.openFolderIcon = _TREE_ICONS[className].openFolderIcon;
|
|
obj.closedFolderIcon = _TREE_ICONS[className].closedFolderIcon;
|
|
throw $break;
|
|
|
|
} else if(className == "Page") {
|
|
obj.fileIcon = null;
|
|
obj.openFolderIcon = null;
|
|
obj.closedFolderIcon = null;
|
|
}
|
|
});
|
|
|
|
this.updateIcon();
|
|
},
|
|
updateIcon: function() {
|
|
var icon;
|
|
if(this.closedFolderIcon && this.className.indexOf('closed') != -1) {
|
|
icon = this.closedFolderIcon;
|
|
|
|
} else if(this.openFolderIcon && this.className.indexOf('children') != -1) {
|
|
icon = this.openFolderIcon;
|
|
|
|
} else if(this.fileIcon) {
|
|
icon = this.fileIcon;
|
|
}
|
|
if(icon) this.aTag.style.background = "url(" +icon + ") no-repeat";
|
|
else this.aTag.style.backgroundImage = "";
|
|
},
|
|
|
|
/**
|
|
* Add the given child node to this tree node.
|
|
* If 'before' is specified, then it will be inserted before that.
|
|
*/
|
|
appendTreeNode : function(child, before) {
|
|
this.treeNodeHolder().appendTreeNode(child, before);
|
|
},
|
|
|
|
treeNodeHolder : function(performCast) {
|
|
if(performCast == null) performCast = true;
|
|
|
|
var uls = this.getElementsByTagName('ul');
|
|
if(uls.length > 0) return uls[0];
|
|
else {
|
|
var ul = document.createElement('ul');
|
|
this.appendChild(ul);
|
|
if(performCast) this.castAsTree(ul);
|
|
return ul;
|
|
}
|
|
},
|
|
hasChildren: function() {
|
|
var uls = this.getElementsByTagName('ul');
|
|
if(uls.length > 0) {
|
|
var i,item;
|
|
for(i=0;item=uls[0].childNodes[i];i++) {
|
|
if(item.tagName && item.tagName.toLowerCase() == 'li') return true;
|
|
}
|
|
}
|
|
return false;
|
|
},
|
|
|
|
/**
|
|
* Remove the given child node from this tree node.
|
|
*/
|
|
removeTreeNode : function(child) {
|
|
// Remove the child
|
|
var holder = this.treeNodeHolder();
|
|
try { holder.removeChild(child); } catch(er) { }
|
|
|
|
// Look for remaining children
|
|
var i, hasChildren = false;
|
|
for(i=0;i<holder.childNodes.length;i++) {
|
|
if(holder.childNodes[i].tagName && holder.childNodes[i].tagName.toLowerCase() == "li") {
|
|
hasChildren = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Update the helper classes accordingly
|
|
if(!hasChildren) this.removeNodeClass('children');
|
|
else this.lastTreeNode().addNodeClass('last');
|
|
|
|
// Update the helper variables
|
|
child.parentTreeNode = null;
|
|
},
|
|
lastTreeNode : function() {
|
|
return this.treeNodeHolder().lastTreeNode();
|
|
},
|
|
firstTreeNode : function() {
|
|
var i, holder = this.treeNodeHolder();
|
|
for(i=0;i<holder.childNodes.length;i++) {
|
|
if(holder.childNodes[i].tagName && holder.childNodes[i].tagName.toLowerCase() == 'li') return holder.childNodes[i];
|
|
}
|
|
},
|
|
addNodeClass : function(className) {
|
|
if(Element && Element.addClassName) {
|
|
Element.addClassName(this, className);
|
|
if(className == 'closed') Element.removeClassName(this, 'children');
|
|
this.aSpan.className = 'a ' + this.className.replace('closed','spanClosed');
|
|
|
|
if(className == 'children' || className == 'closed') this.updateIcon();
|
|
}
|
|
},
|
|
removeNodeClass : function(className) {
|
|
if(Element && Element.removeClassName) {
|
|
Element.removeClassName(this, className);
|
|
if(className == 'closed' && this.hasChildren()) Element.addClassName(this, 'children');
|
|
this.aSpan.className = 'a ' + this.className.replace('closed','spanClosed');
|
|
|
|
if(className == 'children' || className == 'closed') this.updateIcon();
|
|
}
|
|
},
|
|
|
|
getIdx : function() {
|
|
if(this.id.match(/([^-]+)-(.+)$/)) return RegExp.$2;
|
|
else return this.id;
|
|
},
|
|
getTitle: function() {
|
|
return this.aTag.innerHTML;
|
|
},
|
|
|
|
installSubtree : function(response) {
|
|
var ul = this.treeNodeHolder(false);
|
|
ul.innerHTML = response.responseText;
|
|
ul.appendTreeNode = null;
|
|
this.castAsTree(ul);
|
|
/*
|
|
var i,lis = ul.childTreeNodes();
|
|
for(i=0;i<lis.length;i++) {
|
|
this.tree.castAsTreeNode(lis[i]);
|
|
}
|
|
*/
|
|
|
|
// Cued new nodes are nodes added while we were waiting for the expansion to finish
|
|
if(ul.cuedNewNodes) {
|
|
var i;
|
|
for(i=0;i<ul.cuedNewNodes.length;i++) {
|
|
ul.appendTreeNode(ul.cuedNewNodes[i]);
|
|
}
|
|
ul.cuedNewNodes = null;
|
|
}
|
|
|
|
this.removeNodeClass('closed');
|
|
this.addNodeClass('children');
|
|
this.removeNodeClass('loading');
|
|
this.removeNodeClass('unexpanded');
|
|
}
|
|
}
|
|
|
|
/* Close or Open all the trees, at beginning or on request. sjd. */
|
|
function treeCloseAll() {
|
|
var candidates = document.getElementsByTagName('li');
|
|
for (var i=0;i<candidates.length;i++) {
|
|
var aSpan = candidates[i].childNodes[0];
|
|
if(aSpan.childNodes[0] && aSpan.childNodes[0].className == "b") {
|
|
if (!aSpan.className.match(/spanClosed/) && candidates[i].id != 'record-0' ) {
|
|
aSpan.childNodes[0].onclick();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function treeOpenAll() {
|
|
var candidates = document.getElementsByTagName('li');
|
|
for (var i=0;i<candidates.length;i++) {
|
|
var aSpan = candidates[i].childNodes[0];
|
|
if(aSpan.childNodes[0] && aSpan.childNodes[0].className == "b") {
|
|
if (aSpan.className.match(/spanClosed/)) {
|
|
aSpan.childNodes[0].onclick();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
TreeNode_aTag_onclick = function(event) {
|
|
Event.stop(event);
|
|
jQuery(this.treeNode.tree).trigger('nodeclicked', {node: this.treeNode});
|
|
if(!this.treeNode.tree || this.treeNode.tree.notify('NodeClicked', this.treeNode)) {
|
|
if(this.treeNode.options.onselect) {
|
|
return this.treeNode.options.onselect.apply(this.treeNode, [event]);
|
|
} else if(this.treeNode.onselect) {
|
|
return this.treeNode.onselect();
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
TreeNode_bSpan_onclick = function() {
|
|
this.treeNode.toggle();
|
|
};
|
|
|
|
TreeNode_SpanA = {
|
|
onmouseover : function(event) {
|
|
this.parentNode.addNodeClass('over');
|
|
},
|
|
onmouseout : function(event) {
|
|
this.parentNode.removeNodeClass('over');
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------------------------//
|
|
|
|
|
|
DraggableTree = Class.extend('Tree');
|
|
DraggableTree.prototype = {
|
|
initialize: function(options) {
|
|
this.Tree.initialize(options);
|
|
this.setUpDragability();
|
|
},
|
|
setUpDragability: function() {
|
|
this.isDraggable = true;
|
|
|
|
this.allDragHelpers = [];
|
|
if(this.parentNode.tagName.toLowerCase() == "li") {
|
|
this.treeNode = this.parentNode;
|
|
if(this.treeNode.hasChildren()) {
|
|
this.treeNode.createDragHelper();
|
|
}
|
|
}
|
|
},
|
|
/**
|
|
* Turn a draggable tree into a normal one.
|
|
*/
|
|
stopBeingDraggable: function() {
|
|
// this.parentNode.destroy();
|
|
this.isDraggable = false;
|
|
|
|
var i,item,nodes = this.getElementsByTagName('li');
|
|
for(i=0;item=nodes[i];i++) {
|
|
item.destroyDraggable();
|
|
}
|
|
for(i=0;item=this.allDragHelpers[i];i++) {
|
|
Droppables.remove(item);
|
|
if(item.parentNode){
|
|
item.parentNode.removeChild(item);
|
|
}
|
|
}
|
|
this.allDragHelpers = [];
|
|
},
|
|
|
|
/**
|
|
* Convert the given <li> tag into a suitable tree node
|
|
*/
|
|
castAsTreeNode: function(li) {
|
|
behaveAs(li, DraggableTreeNode, this.options);
|
|
}
|
|
}
|
|
|
|
|
|
DraggableTreeNode = Class.extend('TreeNode');
|
|
DraggableTreeNode.prototype = {
|
|
initialize: function(options) {
|
|
this.TreeNode.initialize(options);
|
|
this.setUpDragability();
|
|
},
|
|
setUpDragability: function() {
|
|
// Set up drag and drop
|
|
this.draggableObj = new Draggable(this, TreeNodeDragger);
|
|
|
|
//if(!this.dropperOptions || this.dropperOptions.accept != 'none')
|
|
Droppables.add(this.aTag, this.dropperOptions ? Object.extend(this.dropperOptions, TreeNodeDropper) : TreeNodeDropper);
|
|
|
|
// Add before DIVs to be Droppable items
|
|
if(this.parentTreeNode && this.parentTreeNode.createDragHelper){
|
|
this.parentTreeNode.createDragHelper(this);
|
|
}
|
|
|
|
if(this.hasChildren() && this.parentNode.tagName.toLowerCase() == "li") {
|
|
this.treeNode = this.parentNode;
|
|
// this.treeNode.createDragHelper();
|
|
}
|
|
|
|
// Fix up the <a> click action
|
|
this.aTag._onclick_before_draggable = this.aTag.onclick;
|
|
this.aTag.baseClick = this.aTag.onclick;
|
|
|
|
if(this.options.onParentChanged) this.onParentChanged = this.options.onParentChanged;
|
|
if(this.options.onOrderChanged) this.onOrderChanged = this.options.onOrderChanged;
|
|
},
|
|
|
|
/**
|
|
* Remove all the draggy stuff
|
|
*/
|
|
destroyDraggable: function() {
|
|
Droppables.remove(this.aTag);
|
|
this.aTag.onclick = this.aTag._onclick_before_draggable;
|
|
|
|
if(this.draggableObj) {
|
|
this.draggableObj.destroy();
|
|
this.draggableObj = null;
|
|
}
|
|
},
|
|
/*
|
|
this was commented out because SiteTreeNode takes care of it instead
|
|
castAsTree: function(childUL) {
|
|
// Behaving as DraggableTree directly doesn't load in expansion behaviours
|
|
behaveAs(childUL, Tree, this.options);
|
|
childUL.makeDraggable();
|
|
},
|
|
*/
|
|
|
|
/**
|
|
* Rebuild the "Drag Helper DIVs" that sit around each tree node within this node
|
|
*/
|
|
fixDragHelperDivs : function() {
|
|
var i, holder = this.treeNodeHolder();
|
|
|
|
// This variable toggles between div & li
|
|
var lastDiv, expecting = "div";
|
|
for(i=0;i<holder.childNodes.length;i++) {
|
|
if(holder.childNodes[i].tagName) {
|
|
if(holder.childNodes[i].tagName.toLowerCase() == "div") lastDiv = holder.childNodes[i];
|
|
|
|
// alert(i + ': ' + expecting + ', ' + holder.childNodes[i].tagName);
|
|
if(expecting != holder.childNodes[i].tagName.toLowerCase()) {
|
|
if(expecting == "div") {
|
|
this.createDragHelper(holder.childNodes[i]);
|
|
} else {
|
|
holder.removeChild(holder.childNodes[i]);
|
|
}
|
|
i--;
|
|
|
|
} else {
|
|
// Toggle expecting
|
|
expecting = (expecting == "div") ? "li" : "div";
|
|
}
|
|
}
|
|
}
|
|
// If we were left looking for an li, remove the last div
|
|
// if(expecting == "li") holder.removeChild(lastDiv);
|
|
|
|
// If we were left looking for a div, add one at the end
|
|
if(expecting == "div") this.createDragHelper();
|
|
},
|
|
|
|
/**
|
|
* Create a drag helper within this item.
|
|
* It will be inserted to the end, or before the 'before' element if that is given.
|
|
*/
|
|
createDragHelper : function(before) {
|
|
// Create the node
|
|
var droppable = document.createElement('div');
|
|
droppable.className = "droppable";
|
|
droppable.treeNode = this;
|
|
|
|
this.dragHelper = droppable;
|
|
this.tree.allDragHelpers[this.tree.allDragHelpers.length] = this.dragHelper;
|
|
|
|
// Insert into the DOM
|
|
var holder = this.treeNodeHolder();
|
|
if(before) holder.insertBefore(droppable, before);
|
|
else holder.appendChild(droppable);
|
|
|
|
// Make droppable
|
|
var customOptions = holder.parentNode.dropperOptions ? Object.extend(holder.parentNode.dropperOptions, TreeNodeSeparatorDropper) : TreeNodeSeparatorDropper;
|
|
if(!customOptions.accept != 'none') {
|
|
if(Droppables) Droppables.add(droppable, customOptions);
|
|
}
|
|
}
|
|
}
|
|
|
|
TreeNodeDragger = {
|
|
onStartDrag : function(dragger) {
|
|
dragger.oldParent = dragger.parentTreeNode;
|
|
},
|
|
revert: true
|
|
}
|
|
|
|
TreeNodeDropper = {
|
|
onDrop : function(dragger, dropper, event) {
|
|
var result = true;
|
|
|
|
// Handle event handlers
|
|
if(dragger.onParentChanged && dragger.parentTreeNode != dropper.treeNode)
|
|
result = dragger.onParentChanged(dragger, dragger.parentTreeNode, dropper.treeNode);
|
|
|
|
// Get the future order of the children after the drop completes
|
|
var i = 0, item = null, items = [];
|
|
items[items.length] = dragger.treeNode;
|
|
for(i=0;item=dropper.treeNode.treeNodeHolder().childNodes[i];i++) {
|
|
if(item != dragger.treeNode) items[items.length] = item;
|
|
}
|
|
|
|
if(result && dragger.onOrderChanged)
|
|
result = dragger.onOrderChanged(items, items[0]);
|
|
|
|
if(result) {
|
|
dropper.treeNode.appendTreeNode(dragger.treeNode, dropper.treeNode.firstTreeNode());
|
|
}
|
|
|
|
dragger.wasDragged = true;
|
|
|
|
},
|
|
hoverclass : 'dragOver',
|
|
checkDroppableIsntContained : true
|
|
}
|
|
|
|
TreeNodeSeparatorDropper = {
|
|
onDrop : function(dragger, dropper, event) {
|
|
var result = true;
|
|
|
|
// Handle parent-change handlers
|
|
if(dragger.onParentChanged && dragger.parentTreeNode != dropper.treeNode)
|
|
result = dragger.onParentChanged(dragger, dragger.parentTreeNode, dropper.treeNode);
|
|
|
|
// Get the future order of the children after the drop completes
|
|
var i = 0, item = null, items = [];
|
|
for(i=0;item=dropper.treeNode.treeNodeHolder().childNodes[i];i++) {
|
|
if(item == dropper) items[items.length] = dragger.treeNode;
|
|
if(item != dragger.treeNode) items[items.length] = item;
|
|
}
|
|
|
|
// Handle order change
|
|
if(result && dragger.onOrderChanged)
|
|
result = dragger.onOrderChanged(items, dragger.treeNode);
|
|
|
|
if(result) {
|
|
dropper.treeNode.appendTreeNode(
|
|
dragger.treeNode, dropper);
|
|
}
|
|
|
|
dragger.wasDragged = true;
|
|
},
|
|
hoverclass : 'dragOver',
|
|
greedy : true,
|
|
checkDroppableIsntContained : true
|
|
}
|
|
|
|
//---------------------------------------------------------------------------------------------///
|
|
|
|
/**
|
|
* Mix-in for the tree to enable mulitselect support
|
|
* Usage:
|
|
* - tree.behaveAs(MultiselectTree)
|
|
* - tree.stopBehavingAs(MultiselectTree)
|
|
*/
|
|
MultiselectTree = Class.create();
|
|
MultiselectTree.prototype = {
|
|
initialize: function() {
|
|
Element.addClassName(this, 'multiselect');
|
|
this.MultiselectTree_observer = this.observeMethod('NodeClicked', this.multiselect_onClick.bind(this));
|
|
this.selectedNodes = { }
|
|
},
|
|
destroyDraggable: function() {
|
|
this.stopObserving(this.MultiselectTree_observer);
|
|
},
|
|
|
|
multiselect_onClick : function(selectedNode) {
|
|
if(selectedNode.selected) {
|
|
this.deselectNode(selectedNode);
|
|
} else {
|
|
this.selectNode(selectedNode);
|
|
}
|
|
|
|
// Trigger the onselect event
|
|
return true;
|
|
},
|
|
|
|
selectNode: function(selectedNode) {
|
|
var idx = this.getIdxOf(selectedNode);
|
|
selectedNode.addNodeClass('selected');
|
|
selectedNode.selected = true;
|
|
this.selectedNodes[idx] = selectedNode.aTag.innerHTML;
|
|
},
|
|
|
|
deselectNode : function(selectedNode) {
|
|
var idx = this.getIdxOf(selectedNode);
|
|
selectedNode.removeNodeClass('selected');
|
|
selectedNode.selected = false;
|
|
delete this.selectedNodes[idx];
|
|
}
|
|
|
|
}
|