// 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'});
<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>
.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; }