//enclose in a scope
(function(){
/**
* To create interactive trees (useful for folders or hierarchies).<br>
* Options are:<br>
* + allow_multiselection: allow to select multiple elements using the shift key<br>
* + allow_rename: double click to rename items in the tree<br>
* + allow_drag: drag elements around<br>
* + height<br>
* Item data should be in the next format:<br>
* {<br>
* id: unique_identifier,<br>
* content: what to show in the HTML (if omited id will be shown)<br>
* children: [] array with another object with the same structure<br>
* className: class<br>
* precontent: HTML inserted before the content<br>
* visible: boolean, to hide it<br>
* dataset: dataset for the element<br>
* onDragData: callback in case the user drags this item somewhere else<br>
* }<br>
* To catch events use tree.root.addEventListener(...)<br>
* item_selected : receive { item: node, data: node.data }<br>
* item_dblclicked<br>
* item_renamed<br>
* item_moved<br>
*
* @class Tree
* @constructor
*/
/*********** LiteTree *****************************/
function Tree( data, options, legacy )
{
if(legacy || (data && data.constructor === String) )
{
var id = data;
data = options;
options = legacy || {};
options.id = id;
console.warn("LiteGUI.Tree legacy parameter, use data as first parameter instead of id.");
}
options = options || {};
var root = document.createElement("div");
this.root = root;
if(options.id)
root.id = options.id;
root.className = "litetree";
this.tree = data;
var that = this;
options = options || {allow_rename: false, allow_drag: true, allow_multiselection: false};
this.options = options;
this.indent_offset = options.indent_offset || 0;
if(options.height)
this.root.style.height = typeof(options.height) == "string" ? options.height : Math.round(options.height) + "px";
//bg click
root.addEventListener("click", function(e){
if(e.srcElement != that.root)
return;
if(that.onBackgroundClicked)
that.onBackgroundClicked(e,that);
});
//bg click right mouse
root.addEventListener("contextmenu", function(e) {
if(e.button != 2) //right button
return false;
if(that.onContextMenu)
that.onContextMenu(e);
e.preventDefault();
return false;
});
var root_item = this.createAndInsert(data, options, null);
if(!root_item)
throw("Error in LiteGUI.Tree, createAndInsert returned null");
root_item.className += " root_item";
//this.root.appendChild(root_item);
this.root_item = root_item;
}
Tree.INDENT = 20;
/**
* update tree with new data (old data will be thrown away)
* @method updateTree
* @param {object} data
*/
Tree.prototype.updateTree = function(data)
{
this.root.innerHTML = "";
var root_item = this.createAndInsert( data, this.options, null);
if(root_item)
{
root_item.className += " root_item";
//this.root.appendChild(root_item);
this.root_item = root_item;
}
else
this.root_item = null;
}
/**
* update tree with new data (old data will be thrown away)
* @method insertItem
* @param {object} data
* @param {string} parent_id
* @param {number} position index in case you want to add it before the last position
* @param {object} options
* @return {DIVElement}
*/
Tree.prototype.insertItem = function( data, parent_id, position, options)
{
if(!parent_id)
{
var root = this.root.childNodes[0];
if(root)
parent_id = root.dataset["item_id"];
}
var element = this.createAndInsert( data, options, parent_id, position );
//update parent collapse button
if(parent_id)
this._updateListBox( this._findElement(parent_id) ); //no options here, this is the parent
return element;
}
Tree.prototype.createAndInsert = function( data, options, parent_id, element_index )
{
//find parent
var parent_element_index = -1;
if(parent_id)
parent_element_index = this._findElementIndex( parent_id );
else if(parent_id === undefined)
parent_element_index = 0; //root
var parent = null;
var child_level = 0;
//find level
if(parent_element_index != -1)
{
parent = this.root.childNodes[ parent_element_index ];
child_level = parseInt( parent.dataset["level"] ) + 1;
}
//create
var element = this.createTreeItem( data, options, child_level );
if(!element) //error creating element
return;
element.parent_id = parent_id;
//check
var existing_item = this.getItem( element.dataset["item_id"] );
if( existing_item )
console.warn("There another item with the same ID in this tree");
//insert
if(parent_element_index == -1)
this.root.appendChild( element );
else
this._insertInside( element, parent_element_index, element_index );
//compute visibility according to parents
if( parent && !this._isNodeChildrenVisible( parent_id ) )
element.classList.add("hidden");
//children
if(data.children)
{
for(var i = 0; i < data.children.length; ++i)
{
this.createAndInsert( data.children[i], options, data.id );
}
}
//update collapse button
this._updateListBox( element, options );
if(options && options.selected)
this.markAsSelected( element, true );
return element;
}
//element to add, position of the parent node, position inside children, the depth level
Tree.prototype._insertInside = function( element, parent_index, offset_index, level )
{
var parent = this.root.childNodes[ parent_index ];
if(!parent)
throw("No parent node found, index: " + parent_index +", nodes: " + this.root.childNodes.length );
var parent_level = parseInt( parent.dataset["level"] );
var child_level = level !== undefined ? level : parent_level + 1;
var indent = element.querySelector(".indentblock");
if(indent)
indent.style.paddingLeft = ((child_level + this.indent_offset) * Tree.INDENT ) + "px"; //inner padding
element.dataset["level"] = child_level;
//under level nodes
for( var j = parent_index+1; j < this.root.childNodes.length; ++j )
{
var new_childNode = this.root.childNodes[j];
if( !new_childNode.classList || !new_childNode.classList.contains("ltreeitem") )
continue;
var current_level = parseInt( new_childNode.dataset["level"] );
if( current_level == child_level && offset_index)
{
offset_index--;
continue;
}
//last position
if( current_level < child_level || (offset_index === 0 && current_level === child_level) )
{
this.root.insertBefore( element, new_childNode );
return;
}
}
//ended
this.root.appendChild( element );
}
Tree.prototype._isNodeChildrenVisible = function( id )
{
var node = this.getItem( id );
if(!node)
return false;
if( node.classList.contains("hidden") )
return false;
//check listbox
var listbox = node.querySelector(".listbox");
if(!listbox)
return true;
if(listbox.getValue() == "closed")
return false;
return true;
}
Tree.prototype._findElement = function( id )
{
if( !id || id.constructor !== String)
throw("findElement param must be string with item id");
for(var i = 0; i < this.root.childNodes.length; ++i)
{
var childNode = this.root.childNodes[i];
if( !childNode.classList || !childNode.classList.contains("ltreeitem") )
continue;
if( childNode.classList.contains("ltreeitem-" + id) )
return childNode;
}
return null;
}
Tree.prototype._findElementIndex = function( id )
{
for(var i = 0; i < this.root.childNodes.length; ++i)
{
var childNode = this.root.childNodes[i];
if( !childNode.classList || !childNode.classList.contains("ltreeitem") )
continue;
if(typeof(id) === "string")
{
if(childNode.dataset["item_id"] === id)
return i;
}
else if( childNode === id )
return i;
}
return -1;
}
Tree.prototype._findElementLastChildIndex = function( start_index )
{
if(start_index == -1)
return -1;
var level = parseInt( this.root.childNodes[ start_index ].dataset["level"] );
for(var i = start_index+1; i < this.root.childNodes.length; ++i)
{
var childNode = this.root.childNodes[i];
if( !childNode.classList || !childNode.classList.contains("ltreeitem") )
continue;
var current_level = parseInt( childNode.dataset["level"] );
if( current_level == level )
return i;
}
return -1;
}
//returns child elements (you can control levels)
Tree.prototype._findChildElements = function( id, only_direct )
{
var parent_index = this._findElementIndex( id );
if(parent_index == -1)
return;
var parent = this.root.childNodes[ parent_index ];
var parent_level = parseInt( parent.dataset["level"] );
var result = [];
for(var i = parent_index + 1; i < this.root.childNodes.length; ++i)
{
var childNode = this.root.childNodes[i];
if( !childNode.classList || !childNode.classList.contains("ltreeitem") )
continue;
var current_level = parseInt( childNode.dataset["level"] );
if(only_direct && current_level > (parent_level + 1) )
continue;
if(current_level <= parent_level)
return result;
result.push( childNode );
}
return result;
}
Tree.prototype.createTreeItem = function( data, options, level )
{
if(data === null || data === undefined)
{
console.error("Tree item cannot be null");
return;
}
options = options || this.options;
var root = document.createElement("li");
root.className = "ltreeitem";
var that = this;
//ids are not used because they could collide, classes instead
if(data.id)
{
var safe_id = data.id.replace(/\s/g,"_");
root.className += " ltreeitem-" + safe_id;
root.dataset["item_id"] = data.id;
}
if(data.dataset)
for(var i in data.dataset)
root.dataset[i] = data.dataset[i];
data.DOM = root; //double link
root.data = data;
if(level !== undefined)
{
root.dataset["level"] = level;
root.classList.add("ltree-level-" + level);
}
var title_element = document.createElement("div");
title_element.className = "ltreeitemtitle";
if(data.className)
title_element.className += " " + data.className;
title_element.innerHTML = "<span class='precontent'></span><span class='indentblock'></span><span class='collapsebox'></span><span class='incontent'></span><span class='postcontent'></span>";
var content = data.content || data.id || "";
title_element.querySelector(".incontent").innerHTML = content;
if(data.precontent)
title_element.querySelector(".precontent").innerHTML = data.precontent;
if(data.dataset)
for(var i in data.dataset)
root.dataset[i] = data.dataset[i];
root.appendChild( title_element );
root.title_element = title_element;
if(data.visible === false)
root.style.display = "none";
//var row = root.querySelector(".ltreeitemtitle .incontent");
var row = root;
row.addEventListener("click", onNodeSelected );
row.addEventListener("dblclick",onNodeDblClicked );
row.addEventListener("contextmenu", function(e) {
var item = this;
e.preventDefault();
e.stopPropagation();
if(e.button != 2) //right button
return;
if(that.onItemContextMenu)
return that.onItemContextMenu(e, { item: item, data: item.data} );
return false;
});
function onNodeSelected(e)
{
e.preventDefault();
e.stopPropagation();
//var title = this.parentNode;
//var item = title.parentNode;
var node = this;
var title = node.title_element;
if(title._editing)
return;
if(e.ctrlKey && that.options.allow_multiselection)
{
//check if selected
if( that.isNodeSelected( node ) )
{
node.classList.remove("selected");
LiteGUI.trigger(that.root, "item_remove_from_selection", { item: node, data: node.data} );
return;
}
//mark as selected
that.markAsSelected( node, true );
LiteGUI.trigger(that.root, "item_add_to_selection", { item: node, data: node.data} );
var r = false;
if(data.callback)
r = data.callback.call(that,node);
if(!r && that.onItemAddToSelection)
that.onItemAddToSelection(node.data, node);
}
if(e.shiftKey && that.options.allow_multiselection)
{
//select from current selection till here
//current
var last_item = that.getSelectedItem();
if(!last_item)
return;
if(last_item === node)
return;
var nodeList = Array.prototype.slice.call( last_item.parentNode.children );
var last_index = nodeList.indexOf( last_item );
var current_index = nodeList.indexOf( node );
var items = current_index > last_index ? nodeList.slice( last_index, current_index ) : nodeList.slice( current_index, last_index );
for( var i = 0; i < items.length; ++i )
{
var item = items[i];
//console.log(item);
//mark as selected
that.markAsSelected( item, true );
LiteGUI.trigger( that.root, "item_add_to_selection", { item: item, data: item.data } );
}
}
else
{
//mark as selected
that.markAsSelected( node );
that._skip_scroll = true; //avoid scrolling while user clicks something
LiteGUI.trigger(that.root, "item_selected", { item: node, data: node.data} );
var r = false;
if(data.callback)
r = data.callback.call(that,node);
if(!r && that.onItemSelected)
that.onItemSelected(node.data, node);
that._skip_scroll = false;
}
}
function onNodeDblClicked(e)
{
var node = this; //this.parentNode;
var title = node.title_element.querySelector(".incontent");
LiteGUI.trigger( that.root, "item_dblclicked", node );
if(!title._editing && that.options.allow_rename)
{
title._editing = true;
title._old_name = title.innerHTML;
var that2 = title;
title.innerHTML = "<input type='text' value='" + title.innerHTML + "' />";
var input = title.querySelector("input");
//loose focus when renaming
$(input).blur(function(e) {
var new_name = e.target.value;
setTimeout(function() { that2.innerHTML = new_name; },1); //bug fix, if I destroy input inside the event, it produce a NotFoundError
//item.node_name = new_name;
delete that2._editing;
LiteGUI.trigger( that.root, "item_renamed", { old_name: that2._old_name, new_name: new_name, item: node, data: node.data } );
delete that2._old_name;
});
//finishes renaming
input.addEventListener("keydown", function(e) {
if(e.keyCode != 13)
return;
$(this).blur();
});
//set on focus
$(input).focus();
e.preventDefault();
}
e.preventDefault();
e.stopPropagation();
}
//dragging element on tree
var draggable_element = title_element;
if(this.options.allow_drag)
{
draggable_element.draggable = true;
//starts dragging this element
draggable_element.addEventListener("dragstart", function(ev) {
//this.removeEventListener("dragover", on_drag_over ); //avoid being drag on top of himself
//ev.dataTransfer.setData("node-id", this.parentNode.id);
ev.dataTransfer.setData("item_id", this.parentNode.dataset["item_id"]);
if(!data.onDragData)
return;
var drag_data = data.onDragData();
if(drag_data)
{
for(var i in drag_data)
ev.dataTransfer.setData(i,drag_data[i]);
}
});
}
var count = 0;
//something being dragged entered
draggable_element.addEventListener("dragenter", function (ev)
{
ev.preventDefault();
if(data.skipdrag)
return false;
if(count == 0)
title_element.classList.add("dragover");
count++;
});
draggable_element.addEventListener("dragleave", function (ev)
{
ev.preventDefault();
//console.log(data.id);
count--;
if(count == 0)
title_element.classList.remove("dragover");
//if(ev.srcElement == this) return;
});
//test if allows to drag stuff on top?
draggable_element.addEventListener("dragover", on_drag_over );
function on_drag_over(ev)
{
ev.preventDefault();
}
draggable_element.addEventListener("drop", function (ev)
{
title_element.classList.remove("dragover");
ev.preventDefault();
if(data.skipdrag)
return false;
var item_id = ev.dataTransfer.getData("item_id");
//var data = ev.dataTransfer.getData("Text");
if(!item_id)
{
LiteGUI.trigger( that.root, "drop_on_item", { item: this, event: ev });
if( that.onDropItem )
that.onDropItem( ev, this.parentNode.data );
return;
}
//try
{
var parent_id = this.parentNode.dataset["item_id"];
if( !that.onMoveItem || (that.onMoveItem && that.onMoveItem( that.getItem( item_id ), that.getItem( parent_id ) ) != false))
{
if( that.moveItem( item_id, parent_id ) )
LiteGUI.trigger( that.root, "item_moved", { item: that.getItem( item_id ), parent_item: that.getItem( parent_id ) } );
}
}
/*
catch (err)
{
console.error("Error: " + err );
}
*/
if( that.onDropItem )
that.onDropItem( ev, this.parentNode.data );
});
return root;
}
/**
* remove from the tree the items that do not have a name that matches the string
* @method filterByName
* @param {string} name
*/
Tree.prototype.filterByName = function(name)
{
for(var i = 0; i < this.root.childNodes.length; ++i)
{
var childNode = this.root.childNodes[i]; //ltreeitem
if( !childNode.classList || !childNode.classList.contains("ltreeitem") )
continue;
var content = childNode.querySelector(".incontent");
if(!content)
continue;
var str = content.innerHTML.toLowerCase();
if(!name || str.indexOf( name.toLowerCase() ) != -1)
{
if( childNode.data && childNode.data.visible !== false )
childNode.classList.remove("filtered");
var indent = childNode.querySelector(".indentblock");
if(indent)
{
if(name)
indent.style.paddingLeft = 0;
else
indent.style.paddingLeft = paddingLeft = ( (parseInt(childNode.dataset["level"]) + this.indent_offset) * Tree.INDENT) + "px";
}
}
else
{
childNode.classList.add("filtered");
}
}
}
/**
* remove from the tree the items that do not have a name that matches the string
* @method filterByName
* @param {string} name
*/
Tree.prototype.filterByRule = function( callback_to_filter, param )
{
if(!callback_to_filter)
throw("filterByRule requires a callback");
for(var i = 0; i < this.root.childNodes.length; ++i)
{
var childNode = this.root.childNodes[i]; //ltreeitem
if( !childNode.classList || !childNode.classList.contains("ltreeitem") )
continue;
var content = childNode.querySelector(".incontent");
if(!content)
continue;
if( callback_to_filter( childNode.data, content, param ) )
{
if( childNode.data && childNode.data.visible !== false )
childNode.classList.remove("filtered");
var indent = childNode.querySelector(".indentblock");
if(indent)
{
if(name)
indent.style.paddingLeft = 0;
else
indent.style.paddingLeft = paddingLeft = ( (parseInt(childNode.dataset["level"]) + this.indent_offset) * LiteGUI.Tree.INDENT) + "px";
}
}
else
{
childNode.classList.add("filtered");
}
}
}
/**
* get the item with that id, returns the HTML element
* @method getItem
* @param {string} id
* @return {Object}
*/
Tree.prototype.getItem = function( id )
{
if(!id)
return null;
if( id.classList ) //if it is already a node
return id;
for(var i = 0; i < this.root.childNodes.length; ++i)
{
var childNode = this.root.childNodes[i];
if( !childNode.classList || !childNode.classList.contains("ltreeitem") )
continue;
if(childNode.dataset["item_id"] === id)
return childNode;
}
return null;
/*
var safe_id = id.replace(/\s/g,"_");
var node = this.root.querySelector(".ltreeitem-"+safe_id);
if(!node)
return null;
if( !node.classList.contains("ltreeitem") )
throw("this node is not a tree item");
return node;
*/
}
/**
* in case an item is collapsed, it expands it to show children
* @method expandItem
* @param {string} id
*/
Tree.prototype.expandItem = function(id)
{
var item = this.getItem(id);
if(!item)
return;
if(!item.listbox)
return;
listbox.setValue(true); //this propagates changes
}
/**
* in case an item is expanded, it collapses it to hide children
* @method collapseItem
* @param {string} id
*/
Tree.prototype.collapseItem = function(id)
{
var item = this.getItem(id);
if(!item)
return;
if(!item.listbox)
return;
listbox.setValue(false); //this propagates changes
}
/**
* Tells you if the item its out of the view due to the scrolling
* @method isInsideArea
* @param {string} id
*/
Tree.prototype.isInsideArea = function( id )
{
var item = id.constructor === String ? this.getItem(id) : id;
if(!item)
return false;
var rects = this.root.getClientRects();
if(!rects.length)
return false;
var r = rects[0];
var h = r.height;
var y = item.offsetTop;
if( this.root.scrollTop < y && y < (this.root.scrollTop + h) )
return true;
return false;
}
/**
* Scrolls to center this item
* @method scrollToItem
* @param {string} id
*/
Tree.prototype.scrollToItem = function(id)
{
var item = id.constructor === String ? this.getItem(id) : id;
if(!item)
return;
var rects = this.root.getClientRects();
if(!rects.length)
return false;
var r = rects[0];
var h = r.height;
var x = ( parseInt( item.dataset["level"] ) + this.indent_offset) * Tree.INDENT + 50;
this.root.scrollTop = item.offsetTop - (h * 0.5)|0;
if( r.width * 0.75 < x )
this.root.scrollLeft = x;
else
this.root.scrollLeft = 0;
}
/**
* mark item as selected
* @method setSelectedItem
* @param {string} id
*/
Tree.prototype.setSelectedItem = function( id, scroll, send_event )
{
if(!id)
{
//clear selection
this.unmarkAllAsSelected();
return;
}
var node = this.getItem(id);
if(!node) //not found
return null;
//already selected
if( node.classList.contains("selected") )
return;
this.markAsSelected(node);
if( scroll && !this._skip_scroll )
this.scrollToItem(node);
if(send_event)
LiteGUI.trigger( node, "click" );
return node;
}
/**
* adds item to selection (multiple selection)
* @method addItemToSelection
* @param {string} id
*/
Tree.prototype.addItemToSelection = function( id )
{
if(!id)
return;
var node = this.getItem(id);
if(!node) //not found
return null;
this.markAsSelected(node, true);
return node;
}
/**
* remove item from selection (multiple selection)
* @method removeItemFromSelection
* @param {string} id
*/
Tree.prototype.removeItemFromSelection = function( id )
{
if(!id)
return;
var node = this.getItem(id);
if(!node) //not found
return null;
node.classList.remove("selected");
}
/**
* returns the first selected item (its HTML element)
* @method getSelectedItem
* @return {HTML}
*/
Tree.prototype.getSelectedItem = function()
{
return this.root.querySelector(".ltreeitem.selected");
}
/**
* returns an array with the selected items (its HTML elements)
* @method getSelectedItems
* @return {HTML}
*/
Tree.prototype.getSelectedItems = function()
{
return this.root.querySelectorAll(".ltreeitem.selected");
}
/**
* returns if an item is selected
* @method isItemSelected
* @param {string} id
* @return {bool}
*/
Tree.prototype.isItemSelected = function(id)
{
var node = this.getItem( id );
if(!node)
return false;
return this.isNodeSelected(node);
}
/**
* returns the children of an item
* @method getChildren
* @param {string} id could be string or node directly
* @param {bool} [only_direct=false] to get only direct children
* @return {Array}
*/
Tree.prototype.getChildren = function(id, only_direct )
{
if( id && id.constructor !== String && id.dataset )
id = id.dataset["item_id"];
return this._findChildElements( id, only_direct );
}
/**
* returns the parent of a item
* @method getParent
* @param {string} id
* @return {HTML}
*/
Tree.prototype.getParent = function(id_or_node)
{
var element = this.getItem( id_or_node );
if(element)
return this.getItem( element.parent_id );
return null;
}
/**
* returns an array with all the ancestors
* @method getAncestors
* @param {string} id
* @return {Array}
*/
Tree.prototype.getAncestors = function( id_or_node, result )
{
result = result || [];
var element = this.getItem( id_or_node );
if(element)
{
result.push( element );
return this.getAncestors( element.parent_id, result );
}
return result;
}
/**
* returns an array with all the ancestors
* @method getAncestors
* @param {string} id
* @return {Array}
*/
Tree.prototype.isAncestor = function( child, node )
{
var element = this.getItem( child );
if(!element)
return false;
var dest = this.getItem( node );
var parent = this.getItem( element.parent_id );
if(!parent)
return false;
if(parent == dest)
return true;
return this.isAncestor( parent, node );
}
/**
* move item with id to be child of parent_id
* @method moveItem
* @param {string} id
* @param {string} parent_id
* @return {bool}
*/
Tree.prototype.moveItem = function( id, parent_id )
{
if(id === parent_id)
return false;
var node = this.getItem( id );
var parent = this.getItem( parent_id );
if( this.isAncestor( parent, node ) )
return false;
var parent_index = this._findElementIndex( parent );
var parent_level = parseInt( parent.dataset["level"] );
var old_parent = this.getParent( node );
if(!old_parent)
{
console.error("node parent not found by id, maybe id has changed");
return false;
}
var old_parent_level = parseInt( old_parent.dataset["level"] );
var level_offset = parent_level - old_parent_level;
if(!parent || !node)
return false;
if(parent == old_parent)
return false;
//replace parent info
node.parent_id = parent_id;
//get all children and subchildren and reinsert them in the new level
var children = this.getChildren( node );
if(children)
{
children.unshift( node ); //add the node at the beginning
//remove all children
for(var i = 0; i < children.length; i++)
children[i].parentNode.removeChild( children[i] );
//update levels
for(var i = 0; i < children.length; i++)
{
var child = children[i];
var new_level = parseInt(child.dataset["level"]) + level_offset;
child.dataset["level"] = new_level;
}
//reinsert
parent_index = this._findElementIndex( parent ); //update parent index
var last_index = this._findElementLastChildIndex( parent_index );
if(last_index == -1)
last_index = 0;
for(var i = 0; i < children.length; i++)
{
var child = children[i];
this._insertInside( child, parent_index, last_index + i - 1, parseInt( child.dataset["level"] ) );
}
}
//update collapse button
this._updateListBox( parent );
if(old_parent)
this._updateListBox( old_parent );
return true;
}
/**
* remove item with given id
* @method removeItem
* @param {string} id
* @return {bool}
*/
Tree.prototype.removeItem = function( id_or_node, remove_children )
{
var node = id_or_node;
if(typeof(id_or_node) == "string")
node = this.getItem( id_or_node );
if(!node)
return false;
//get parent
var parent = this.getParent( node );
//get all descendants
var child_nodes = null;
if(remove_children)
child_nodes = this.getChildren( node );
//remove html element
this.root.removeChild( node );
//remove all children
if( child_nodes )
{
for( var i = 0; i < child_nodes.length; i++ )
this.root.removeChild( child_nodes[i] );
}
//update parent collapse button
if(parent)
this._updateListBox( parent );
return true;
}
/**
* update a given item with new data
* @method updateItem
* @param {string} id
* @param {object} data
*/
Tree.prototype.updateItem = function(id, data)
{
var node = this.getItem(id);
if(!node)
return false;
node.data = data;
if(data.id)
node.id = data.id; //this updateItemId ?
if(data.content)
{
//node.title_element.innerHTML = "<span class='precontent'></span><span class='incontent'>" + + "</span><span class='postcontent'></span>";
var incontent = node.title_element.querySelector(".incontent");
incontent.innerHTML = data.content;
}
return true;
}
/**
* update a given item id and the link with its children
* @method updateItemId
* @param {string} old_id
* @param {string} new_id
*/
Tree.prototype.updateItemId = function(old_id, new_id)
{
var node = this.getItem(old_id);
if(!node)
return false;
var children = this.getChildren( old_id, true );
node.id = new_id;
for(var i = 0; i < children.length; ++i)
{
var child = children[i];
child.parent_id = new_id;
}
return true;
}
/**
* clears all the items
* @method clear
* @param {bool} keep_root if you want to keep the root item
*/
Tree.prototype.clear = function(keep_root)
{
if(!keep_root)
{
this.root.innerHTML = "";
return;
}
var items = this.root.querySelectorAll(".ltreeitem");
for(var i = 1; i < items.length; i++)
{
var item = items[i];
this.root.removeChild( item );
}
}
Tree.prototype.getNodeByIndex = function(index)
{
var items = this.root.querySelectorAll(".ltreeitem");
return items[index];
}
//private ********************************
Tree.prototype.unmarkAllAsSelected = function()
{
this.root.classList.remove("selected");
var selected_array = this.root.querySelectorAll(".ltreeitem.selected");
if(selected_array)
{
for(var i = 0; i < selected_array.length; i++)
selected_array[i].classList.remove("selected");
}
var semiselected = this.root.querySelectorAll(".ltreeitem.semiselected");
for(var i = 0; i < semiselected.length; i++)
semiselected[i].classList.remove("semiselected");
}
Tree.prototype.isNodeSelected = function( node )
{
//already selected
if( node.classList.contains("selected") )
return true;
return false;
}
Tree.prototype.markAsSelected = function( node, add_to_existing_selection )
{
//already selected
if( node.classList.contains("selected") )
return;
//clear old selection
if(!add_to_existing_selection)
this.unmarkAllAsSelected();
//mark as selected (it was node.title_element?)
node.classList.add("selected");
//go up and semiselect
var parent = this.getParent( node );
while(parent)
{
parent.classList.add("semiselected");
parent = this.getParent( parent );
}
/*
var parent = node.parentNode.parentNode; //two elements per level
while(parent && parent.classList.contains("ltreeitem"))
{
parent.title_element.classList.add("semiselected");
parent = parent.parentNode.parentNode;
}
*/
}
//updates the widget to collapse
Tree.prototype._updateListBox = function( node, options )
{
if(!node)
return;
var that = this;
if(!node.listbox)
{
var pre = node.title_element.querySelector(".collapsebox");
var box = LiteGUI.createLitebox(true, function(e) {
that.onClickBox(e, node);
LiteGUI.trigger( that.root, "item_collapse_change", { item: node, data: box.getValue() } );
});
box.stopPropagation = true;
box.setEmpty(true);
pre.appendChild(box);
node.listbox = box;
}
if(options && options.collapsed)
node.listbox.collapse();
var child_elements = this.getChildren( node.dataset["item_id"] );
if(!child_elements)
return; //null
if(child_elements.length)
node.listbox.setEmpty(false);
else
node.listbox.setEmpty(true);
}
Tree.prototype.onClickBox = function(e, node)
{
var children = this.getChildren( node );
if(!children)
return;
//update children visibility
for(var i = 0; i < children.length; ++i)
{
var child = children[i];
var child_parent = this.getParent( child );
var visible = true;
if( child_parent )
visible = this._isNodeChildrenVisible(child_parent);
if(visible)
child.classList.remove("hidden");
else
child.classList.add("hidden");
}
}
LiteGUI.Tree = Tree;
})();