// todo: add class to inactive nodes
// todo: bug deselecting sibling node
// todo: add events
if(!MultiSelectTree){
var MultiSelectTree = new Class({
Implements : [Events, Options],
options : {
allowOrphans : true, //remove any orphaned nodes without complaining
relationshipAttribute : 'ancestry',
openGlyph : '▼',
closedGlyph : '▶',
selectChildren : true,
selectNode : false,
openToSelected : true,
partialSelect : true,
openDepth : 1
},
initialize : function(element, options){
this.element = document.id(element);
this.setOptions(options);
this.options.openDepth++;
var tree = {};
this.element.getChildren('option').each(function(item, key){
var descention = item.getAttribute(this.options.relationshipAttribute);
descention = descention=='' ? item.value : descention+'>'+item.value;
tree = this.buildTree(tree, descention, item.value);
}.bind(this));
var el = this.buildList(tree);
el.addClass('multiselecttree');
el.inject(this.element, 'after');
this.element.hide();
el.show();
this.root = el;
},
getOption : function(id){
var result;
this.element.getChildren('option').each(function(element){
if(element.get('value') == id) result = element;
});
return result;
},
buildTree : function(tree, id, value){
var node = tree;
id.split('>').each(function(key, index){
var cleanKey = this.getOption(key.trim()).get('html');
if(!node[cleanKey]) node[cleanKey] = {'*':value};
else node[cleanKey]['*'] = key;
node = node[cleanKey];
}.bind(this));
return tree;
},
partiallySelect : function(node, select){
var parentUl = node.getParent('ul');
var parentLi = parentUl.getParent('li');
if(!parentLi) return;
var parentLabel = parentLi.getElement('> a > .multiselecttree_label');
if(!select){
var selectedChildren = parentUl.getElements('.multiselecttree_label:not(.selected_node)');
if(selectedChildren.length == 0){
parentLabel.addClass('children_selected');
parentLabel.removeClass('partially_selected_node');
}else{
parentLabel.addClass('partially_selected_node');
}
}else{
parentLabel.removeClass('children_selected');
var selectedChildren = parentLi.getElements('.selected_node');
if(selectedChildren.length < 1) parentLabel.removeClass('partially_selected_node');
}
this.partiallySelect(parentLabel, select);
},
buildList : function(root, depth){
if(!depth) depth = 0;
var element = new Element('ul');
if(depth >= this.options.openDepth){
element.hide();
element.closed = true;
}
var count = 0;
Object.each(root, function(node, key){
var listElement = new Element('li');
var link = new Element('a');
if(node['*']){
link.setAttribute('identifier', node['*']);
delete node['*'];
}
if(this.options.linkClass) link.addClass(this.options.linkClass);
var label = new Element('span', {
html : key,
class : 'multiselecttree_label'
});
var spinner = new Element('span', {
html : this.options.closedGlyph
});
if(!this.closedGlyphValue) this.closedGlyphValue = spinner.get('html');
if(Object.keys(node).length > 0) spinner.inject(link);
label.inject(link);
if(typeOf(node) == 'object'){
var sublist = this.buildList(node, depth + 1);
sublist.inject(link);
sublist.toggle = function(){
if(sublist.closed){
sublist.reveal();
sublist.closed = false;
sublist.fireEvent('open');
}else{
sublist.dissolve();
sublist.closed = true;
sublist.fireEvent('close');
}
}
spinner.addEvent('click', function(event){
sublist.toggle();
if(spinner.get('html') == this.closedGlyphValue) spinner.set('html', this.options.openGlyph);
else spinner.set('html', this.options.closedGlyph);
}.bind(this));
var option = this.getOption(link.getAttribute('identifier'));
if(option.getAttribute('selected') != null) label.addClass('selected_node');
var getFirstLeafNode = function(node){
var nodeList = node.getElements('a');
if(nodeList.length > 0){
var sublistList = nodeList[0].getElements('ul');
if(sublistList.length > 0 && sublistList[0].getChildren().length > 0) return getFirstLeafNode(sublistList[0]);
else return nodeList[0];
}
return false;
}
if(option.getAttribute('disabled') == null) label.addEvent('click', function(event){
var selected = option.getAttribute('selected');
if(this.options.selectChildren && !this.options.selectNode){
var node = getFirstLeafNode(sublist);
if(node){
var o = this.getOption(node.getAttribute('identifier'));
selected = o.getAttribute('selected');
}
}
if(selected){
if(this.options.selectChildren){
var links = sublist.getElements('a');
links.each(function(item, key){
var option = this.getOption(item.getAttribute('identifier'));
item.getElements('span.multiselecttree_label').removeClass('selected_node');
if(this.options.selectNode || item.getElements('li').length == 0 ){
option.removeAttribute('selected');
}
}.bind(this));
}
label.removeClass('selected_node');
if(this.options.selectNode || link.getElements('li').length == 0 ){
option.removeAttribute('selected');
}
}else{
if(this.options.selectChildren){
var links = sublist.getElements('a');
links.each(function(item, key){
var option = this.getOption(item.getAttribute('identifier'));
item.getElements('span.multiselecttree_label').addClass('selected_node');
if(this.options.selectNode || item.getElements('li').length == 0 ){
option.setAttribute('selected', true);
}
}.bind(this));
}
label.addClass('selected_node');
if(this.options.selectNode || link.getElements('li').length == 0 ){
option.setAttribute('selected', true);
}
}
if(this.options.partialSelect){
this.partiallySelect(sublist, selected);
}
}.bind(this));
link.inject(listElement);
listElement.inject(element);
count++;
if(sublist.getStyle('display') != 'none'){
spinner.set('html', this.options.openGlyph);
}
}
}.bind(this));
if(this.options.openToSelected){
var nodes = element.getElements('span.selected_node')
if(nodes.length > 0) nodes.getParents('ul').show();
}
return element;
}
});
}
new MultiSelectTree('treeselect', {relationshipAttribute : 'heredity'});
.multiselecttree {
line-height: 18px; }
.multiselecttree .selected_node {
color: black; }
.multiselecttree .multiselecttree_label {
padding-left: 1px;
cursor: pointer; }
.multiselecttree .multiselecttree_label:before {
margin-right: 3px;
display: inline-block;
height: 13px;
width: 13px;
background-color: #CCCCCC;
content: '\A0';
background-color: #ececec;
background-image: -webkit-gradient(linear, left top, left bottom, from(#ececec), to(#a8a8a8));
background-image: -webkit-linear-gradient(top, #ececec, #a8a8a8);
background-image: -moz-linear-gradient(top, #ececec, #a8a8a8);
background-image: -o-linear-gradient(top, #ececec, #a8a8a8);
background-image: -ms-linear-gradient(top, #ececec, #a8a8a8);
background-image: linear-gradient(top, #ececec, #a8a8a8);
box-shadow: inset 0px 0px 3px #686868;
-moz-box-shadow: inset 0px 0px 3px #686868;
-webkit-box-shadow: inset 0px 0px 3px #686868;
-webkit-border-radius: 2px;
-moz-border-radius: 2px;
border-radius: 2px;
border: 1px solid #626262; }
.multiselecttree .selected_node:before {
content: '\2713'; }
.multiselecttree .children_selected:before {
color : black;
content: '\2713'; }
.multiselecttree .partially_selected_node:before {
font-size:38px;
line-height:6px;
color : black;
content: '-'; }
.multiselecttree ul {
margin-left: 20px; }
.multiselecttree_label {
color: grey;
margin-left: 3px; }
<select multiple name="selector" class="treeselect" id="treeselect">
<option value="1" heredity="">An Item</option>
<option value="2" heredity="1" disabled>A Test</option>
<option value="3" heredity="1>2">Another Test</option>
<option value="4" heredity="1>2">A Test3</option>
<option value="5" heredity="1">A Test4</option>
<option value="6" heredity="1">A Test5</option>
<option value="7" heredity="1>5">A Test2</option>
<option value="8" heredity="">fsfdsfg</option>
<option value="9" heredity="">werttg</option>
</select>