MINOR Moved jsparty/tree to sapphire/javascript/tree
git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/branches/2.4@93565 467b73ca-7a2a-4603-9d3b-597d59a354a9
24
javascript/tree/LICENSE
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
* Copyright (c) 2008, Silverstripe Ltd.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
* * Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name of the <organization> nor the
|
||||||
|
* names of its contributors may be used to endorse or promote products
|
||||||
|
* derived from this software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY Silverstripe Ltd. ``AS IS'' AND ANY
|
||||||
|
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL Silverstripe Ltd. BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
104
javascript/tree/README.md
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
# JavaScript Tree Control
|
||||||
|
|
||||||
|
## Maintainers
|
||||||
|
|
||||||
|
* Sam Minnee (sam at silverstripe dot com)
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
* Build trees using semantic HTML and unobtrusive JavaScript.
|
||||||
|
* Style the tree to suit your application you with CSS.
|
||||||
|
* Demo: http://www.silverstripe.org/assets/tree/demo.html
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
The first thing to do is include the appropriate JavaScript and CSS files:
|
||||||
|
|
||||||
|
<code html>
|
||||||
|
<link rel="stylesheet" type="text/css" media="all" href="tree.css" />
|
||||||
|
<script type="text/javascript" src="tree.js"></script>
|
||||||
|
</code>
|
||||||
|
|
||||||
|
Then, create the HTML for you tree. This is basically a nested set of bullet pointed links. The "tree" class at the top is what the script will look for. Note that you can make a tree node closed to begin with by adding `class="closed"`.
|
||||||
|
|
||||||
|
Here's the HTML code that I inserted to create the demo tree above.
|
||||||
|
|
||||||
|
<code html>
|
||||||
|
<ul class="tree">
|
||||||
|
<li><a href="#">item 1</a>
|
||||||
|
<ul>
|
||||||
|
<li><a href="#">item 1.1</a></li>
|
||||||
|
<li class="closed"><a href="#">item 1.2</a>
|
||||||
|
<ul>
|
||||||
|
<li><a href="#">item 1.2.1</a></li>
|
||||||
|
<li><a href="#">item 1.2.2</a></li>
|
||||||
|
<li><a href="#">item 1.2.3</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li><a href="#">item 1.3</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li><a href="#">item 2</a>
|
||||||
|
<ul>
|
||||||
|
<li><a href="#">item 2.1</a></li>
|
||||||
|
<li><a href="#">item 2.2</a></li>
|
||||||
|
<li><a href="#">item 2.3</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</code>
|
||||||
|
|
||||||
|
Your tree is now complete!
|
||||||
|
|
||||||
|
## How it works
|
||||||
|
|
||||||
|
Obviously, this isn't a complete detail of everything that's going on, but it gives you an insight into the overall process.
|
||||||
|
|
||||||
|
### Starting the script
|
||||||
|
|
||||||
|
In simple situations, creating an auto-loading script is a simple matter of setting window.onload to a function. But what if there's more than one script? To this end, we created an appendLoader() function that will execute multiple loader functions, including a previously defined loader function
|
||||||
|
|
||||||
|
### Finding the tree content
|
||||||
|
|
||||||
|
Rather than write a piece of script to define where your tree is, we've tried to make the script as automatic as possible - it finds all ULs with a class name containing "tree".
|
||||||
|
|
||||||
|
### Augmenting the HTML
|
||||||
|
|
||||||
|
Unfortunately, an LI containing an A isn't sufficient for doing all of the necessary tree styling. Rather than force people to put non-semantic HTML into their file, the script generates extra `<span>` tags.
|
||||||
|
|
||||||
|
So, the following HTML:
|
||||||
|
|
||||||
|
<code html>
|
||||||
|
<li>
|
||||||
|
<a href="#">My item</a>
|
||||||
|
</li>
|
||||||
|
</code>
|
||||||
|
|
||||||
|
Is turned into the more ungainly, and yet more easily styled:
|
||||||
|
|
||||||
|
<code html>
|
||||||
|
<li>
|
||||||
|
<span class="a"><span class="b"><span class="c">
|
||||||
|
<a href="#">My item</a>
|
||||||
|
</span></span></span>
|
||||||
|
</li>
|
||||||
|
</code>
|
||||||
|
|
||||||
|
Additionally, some helper classes are applied to the `<li>` and `<span class="a">` elements:
|
||||||
|
|
||||||
|
* `"last"` is applied to the last node of any subtree.
|
||||||
|
* `"children"` is applied to any node that has children.
|
||||||
|
|
||||||
|
### Styling it up
|
||||||
|
|
||||||
|
Why the heck do we need 5 styling elements? Basically, because there are 5 background-images to apply:
|
||||||
|
|
||||||
|
* li: A repeating vertical line is shown. Nested <li> tags give us the multiple vertical lines that we need.
|
||||||
|
* span.a: We overlay the vertical line with 'L' and 'T' elements as needed.
|
||||||
|
* span.b: We overlay '+' or '-' signs on nodes with children.
|
||||||
|
* span.c: This is needed to fix up the vertical line.
|
||||||
|
* a: Finally, we apply the page icon.
|
||||||
|
|
||||||
|
### Opening / closing nodes
|
||||||
|
|
||||||
|
Having come this far, the "dynamic" aspect of the tree control is very trivial. We set a "closed" class on the `<li>` and `<span class="a">` elements, and our CSS takes care of hiding the children, changing the - to a + and changing the folder icon.
|
BIN
javascript/tree/images/i-bottom.gif
Normal file
After Width: | Height: | Size: 125 B |
BIN
javascript/tree/images/i-repeater.gif
Normal file
After Width: | Height: | Size: 91 B |
BIN
javascript/tree/images/insertBetween.gif
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
javascript/tree/images/l.gif
Normal file
After Width: | Height: | Size: 131 B |
BIN
javascript/tree/images/minus.gif
Normal file
After Width: | Height: | Size: 146 B |
BIN
javascript/tree/images/page-closedfolder.gif
Normal file
After Width: | Height: | Size: 210 B |
BIN
javascript/tree/images/page-closedfolder.png
Normal file
After Width: | Height: | Size: 210 B |
BIN
javascript/tree/images/page-file.gif
Normal file
After Width: | Height: | Size: 211 B |
BIN
javascript/tree/images/page-file.png
Normal file
After Width: | Height: | Size: 211 B |
BIN
javascript/tree/images/page-openfolder.gif
Normal file
After Width: | Height: | Size: 219 B |
BIN
javascript/tree/images/page-openfolder.png
Normal file
After Width: | Height: | Size: 219 B |
BIN
javascript/tree/images/plus.gif
Normal file
After Width: | Height: | Size: 149 B |
BIN
javascript/tree/images/t.gif
Normal file
After Width: | Height: | Size: 94 B |
160
javascript/tree/tree.css
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
/*
|
||||||
|
* Default CSS for tree-view
|
||||||
|
*/
|
||||||
|
ul.tree{
|
||||||
|
width: auto;
|
||||||
|
padding-left: 0;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.tree img {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.tree, ul.tree ul {
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.tree ul {
|
||||||
|
margin-left: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.tree li.closed ul {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.tree li {
|
||||||
|
list-style: none;
|
||||||
|
background: url(images/i-repeater.gif) repeat-y 1 0;
|
||||||
|
display: block;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
ul.tree li.last {
|
||||||
|
list-style: none;
|
||||||
|
background-image: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Span-A: I/L/I glpyhs */
|
||||||
|
ul.tree span.a {
|
||||||
|
background: url(images/t.gif) no-repeat 0 50%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
ul.tree .a.last {
|
||||||
|
background: url(images/l.gif) no-repeat 0 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Span-B: Plus/Minus icon */
|
||||||
|
ul.tree span.b {
|
||||||
|
}
|
||||||
|
ul.tree span.a.children span.b {
|
||||||
|
display: inline-block;
|
||||||
|
background: url(images/minus.gif) no-repeat 0 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
ul.tree li.closed span.a span.b, ul.tree span.a.unexpanded span.b {
|
||||||
|
display: inline-block;
|
||||||
|
background: url(images/plus.gif) no-repeat 0 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Span-C: Spacing and extending tree line below the icon */
|
||||||
|
ul.tree span.c {
|
||||||
|
margin-left: 16px;
|
||||||
|
}
|
||||||
|
ul.tree span.a.children span.c, ul.tree span.a.spanClosed span.c {
|
||||||
|
background: url(images/i-bottom.gif) no-repeat 0 50%;
|
||||||
|
}
|
||||||
|
ul.tree span.a.spanClosed span.c, ul.tree span.a.unexpanded span.c {
|
||||||
|
background-image: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Anchor tag: Page icon */
|
||||||
|
ul.tree span.c {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
ul.tree a {
|
||||||
|
display: inline-block; /* IE needs this */
|
||||||
|
white-space: pre;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 3px 0 1px 19px;
|
||||||
|
line-height: 16px;
|
||||||
|
background: url(images/page-file.png) no-repeat 0 50%;
|
||||||
|
background-position: 0 50% !important;
|
||||||
|
text-decoration: none;
|
||||||
|
outline: none;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
ul.tree a * {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
ul.tree a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.tree span.a.children a {
|
||||||
|
background-image: url(images/page-openfolder.png);
|
||||||
|
}
|
||||||
|
ul.tree span.a.spanClosed a, ul.tree span.a.unexpanded a {
|
||||||
|
background-image: url(images/page-closedfolder.png);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Unformatted tree */
|
||||||
|
ul.tree.unformatted li {
|
||||||
|
background-image: none;
|
||||||
|
padding-left: 16px;
|
||||||
|
}
|
||||||
|
ul.tree.unformatted li li {
|
||||||
|
background-image: none;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Hover / Link tags
|
||||||
|
*/
|
||||||
|
ul.tree a:hover{
|
||||||
|
text-decoration : none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Divs, by default store vertically aligned data
|
||||||
|
*/
|
||||||
|
/* As inside DIVs should be treated normally */
|
||||||
|
ul.tree div a {
|
||||||
|
padding: 0;
|
||||||
|
background-image: none;
|
||||||
|
min-height: 0;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.tree li a:link,
|
||||||
|
ul.tree li a:hover,
|
||||||
|
ul.tree li a:visited {
|
||||||
|
color: #111;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Drag and drop styling
|
||||||
|
*/
|
||||||
|
ul.tree div.droppable {
|
||||||
|
float: none;
|
||||||
|
margin: -7px 0px -7px 16px;
|
||||||
|
height: 10px;
|
||||||
|
font-size: 1px;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
html > body ul.tree div.droppable {
|
||||||
|
margin: -5px 0px -5px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.tree div.droppable.dragOver {
|
||||||
|
background: url(images/insertBetween.gif) no-repeat 50% 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.tree a.dragOver, ul.tree li.dragOver a, ul.tree li.dragOver li.dragOver a {
|
||||||
|
border: 3px solid #0074C6;
|
||||||
|
margin: -3px;
|
||||||
|
}
|
||||||
|
ul.tree li.dragOver li a {
|
||||||
|
border-style: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
957
javascript/tree/tree.js
Normal file
@ -0,0 +1,957 @@
|
|||||||
|
/*
|
||||||
|
* 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...
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
Function.prototype.create = function(item, arg1, arg2, arg3, arg4, arg5, arg6) {
|
||||||
|
return behaveAs(item, this, arg1, arg2, arg3, arg4, arg5, arg6);
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
// Remove from the old parent node - this will ensure that the classes of the old tree
|
||||||
|
// item are updated accordingly
|
||||||
|
if(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) {
|
||||||
|
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');
|
||||||
|
|
||||||
|
if (typeof(DropFileItem) !== 'undefined') {
|
||||||
|
// add new li elements as DropFileItem targets
|
||||||
|
list = ul.getElementsByTagName("li");
|
||||||
|
for ( var i=0; i<list.length; i++ ) {
|
||||||
|
behaveAs(list[i], DropFileItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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);
|
||||||
|
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;
|
||||||
|
|
||||||
|
this.aTag.onclick = this.aTagOnClick.bindAsEventListener(this.aTag);
|
||||||
|
|
||||||
|
if(this.options.onParentChanged) this.onParentChanged = this.options.onParentChanged;
|
||||||
|
if(this.options.onOrderChanged) this.onOrderChanged = this.options.onOrderChanged;
|
||||||
|
},
|
||||||
|
|
||||||
|
aTagOnClick: function(event) {
|
||||||
|
// This will be bound to the <a> tag, not the <li>.
|
||||||
|
if(this.treeNode.wasDragged) {
|
||||||
|
Event.stop(event);
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
this.treeNode.anchorWasClicked = true;
|
||||||
|
this.treeNode.wasDragged = false;
|
||||||
|
return this.baseClick(event);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 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];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Find the first child of el that is of type 'tag'
|
||||||
|
*/
|
||||||
|
function findChildWithTag(el, tag) {
|
||||||
|
for(var i=0;i<el.childNodes.length;i++) {
|
||||||
|
if(el.childNodes[i].tagName != null && el.childNodes[i].tagName.toLowerCase() == tag) return el.childNodes[i];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|