/*
* Copyright (C) 2005 - 2011 Jaspersoft Corporation. All rights reserved.
* http://www.jaspersoft.com.
*
* Unless you have purchased a commercial license agreement from Jaspersoft,
* the following license terms apply:
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
/**
* Original Author of this file: Martin Mouritzen. (martin@nano.dk)
*
*
* (Lack of) Documentation:
*
*
* If a finishedLoading method exists, it will be called when the tree is loaded.
* (good to display a div, etc.).
*
*
* You have to set the variable rootNode (as a TreeNode).
*
* You have to set a container element, this is the element in which the tree will be.
*
*
* TODO:
* Save cookies better (only 1 cookie for each tree). Else the page will totally cookieclutter.
*
*
* //////////////////////////////////////
*
* Changes made to the original version
* - tree and treenode related methods and fields made instance methods and fields (were global)
* - now you can have many trees on the same page independently
* - trees and nodes maps intruduced to find trees and nodes by id
* - nodes get autogenerated ids, unique over all nodes on the page
*
* temporarly changes
* - tree visual state gets reset each time tree is rendered
* - drag & drop does not work (was for IE only)
*/
/**
* Main namespace for all tree related things
*/
var dynamicTree = {
/**
* tree map, use to find/store trees, ex: trees['myId']
* Tree constructor registers the tree automatically in here
*/
trees: {},
/**
* node index map, helps to find any node by id
* TreeNode constructor registers the node automatically in here
*/
nodes: {},
/**
* Current edited node
*/
treeNodeEdited: null,
/**
* If true all changes of node title will be ignored
*/
editaborted: false,
/**
* The identifier of active tree.
*/
activeTreeId: null,
/**
* The Tree Object.
*
* @param id {String} - tree id.
* @param options {JSON Object} - Set of configuration options for tree :
*
* - root {Tree Node} - Root Node
* - bShowRoot {Boolean} - true if you want to show tree root node, false otherwise
* - handleNodeOnDblclick {Boolean} - false if you don't want to handle node on dblclick
* - multiSelectEnabled {Boolean} - true if you want select more then one node at the same time
* - showAllNodesOnStartup {Boolean} - Shows/Hides subnodes on startup
* - treeClassName {String} - a css class to override dynamicTree.Tree.DEFAULT_TREE_CLASS_NAME
* - dragPattern {String} - cssStyle pattern for which dragging is enabled
* - dropClasses {Array} - array of class names which can be dropped onto a tree node
* - dragClasses {String} - string with class names which will be used for modifying ghost of the tree node while dragging
* - selectOnMousedown (Boolean)
- should selection occure on mousedown (otherwise its on mouseup)
* - regionID (String)
- where is this tree located (e.g. AVAILABLE_FIELDS)
*
*/
Tree: function(id, options) {
this.id = id;
if (options) {
this.setRootNode(options.root);
this.bShowRoot = !!options.bShowRoot;
this.handleNodeOnDblclick = options.handleNodeOnDblclick !== undefined ? options.handleNodeOnDblclick : true;
this.multiSelectEnabled = !!options.multiSelectEnabled;
this.showAllNodesOnStartup = !!options.showAllNodesOnStartup;
this.treeClassName = options.treeClassName ? options.treeClassName : "";
this.dragPattern = options.dragPattern;
this.dragClasses = options.dragClasses;
this.dropClasses = options.dropClasses;
this.selectOnMousedown = options.selectOnMousedown;
this.regionID = options.regionID;
this.scroll = options.scroll;
this.templateDomId = (options.templateDomId) ? options.templateDomId : this.DEFAULT_TREE_TEMPLATE_ID;
} else {
this.templateDomId = this.DEFAULT_TREE_TEMPLATE_ID;
this.handleNodeOnDblclick = true;
}
this.statearray = new Array();
this.sortNodes = true;
this.sorters = [this.sortByOrder, this.sortByName];
this.selectedNodes = [];
this.TREE_NN_ITEMS_SELECTED = "#{count} items selected";
// self-indexing
dynamicTree.trees[this.id] = this;
this._createFromTemplate();
this.refreshStyle();
this._registerEvents();
this._registerCustomScroll();
},
/**
* Global node id counter.
*/
getNextId: function() {
var nextId = 1; // private static var
return function() {
return nextId++;
}
}(),
/**
* Returns the lasts tree which was active by mouse down event on tree container.
*/
getActiveTree: function() {
return dynamicTree.trees[dynamicTree.activeTreeId];
},
/**
* Returns tree node for the specified node identifier.
*
* @param nodeID the identifier of the node.
*/
getTreeNode: function(nodeID) {
return dynamicTree.nodes[nodeID];
},
/**
* Returns the value from the cookie starting from the specified offset. The value should be ended with ';'
* character.
*
* @param offset the offset.
*/
getCookieVal: function(offset) {
var endstr = document.cookie.indexOf (";",offset);
if (endstr == -1) {
endstr = document.cookie.length;
}
return unescape(document.cookie.substring(offset,endstr));
},
/**
* Returns the value from the cookie by the specified name.
*
* @param name the name.
*/
getCookie: function(name) {
var arg = name + "=";
var alen = arg.length;
var clen = document.cookie.length;
var i = 0;
while (i < clen) {
var j = i + alen;
if (document.cookie.substring(i, j) == arg) {
return dynamicTree.getCookieVal(j);
}
i = document.cookie.indexOf(" ", i) + 1;
if (i == 0) {
break;
}
}
return null;
},
/**
* Stores the value in the cookie using specified name.
*
* @param name the name.
* @param value the value.
*/
setCookie: function (name, value) {
var argv = dynamicTree.setCookie.arguments;
var argc = dynamicTree.setCookie.arguments.length;
var expires = (argc > 2) ? argv[2] : null;
var path = (argc > 3) ? argv[3] : null;
var domain = (argc > 4) ? argv[4] : null;
var secure = (argc > 5) ? argv[5] : false;
document.cookie = name + "=" + escape (value) + ((expires == null) ? "" : ("; expires=" + expires.toGMTString())) + ((path == null) ? "" : ("; path=" + path)) + ((domain == null) ? "" : ("; domain=" + domain)) + ((secure == true) ? "; secure" : "");
},
_templateHash: {}
};
/**
* Name(s) of CSS class(es) for the default tree.
*/
dynamicTree.Tree.addVar('DEFAULT_TREE_TEMPLATE_ID', "list_responsive_collapsible");
/**
* The name of the CSS class which is used to hide the root.
*/
dynamicTree.Tree.addVar('HIDE_ROOT_CLASS_NAME', "hideRoot");
/**
* Returns the identifier of the tree.
*/
dynamicTree.Tree.addMethod('getId', function() { return this.id; });
/**
* Returns the tree DOM element.
*/
dynamicTree.Tree.addMethod('_getElement', function() {
if (!this._element) {
this._element = $(this.id);
}
return this._element;
});
/**
* Returns the tree DOM element.
*/
dynamicTree.Tree.addMethod('_createFromTemplate', function() {
if (this._getElement()) {
this._getElement().insert({
after: this._getTemplateElement(this._getElement())
});
this._getElement().remove();
this._element = null;
this._getElement().update();
}
//disableSelectionWithoutCursorStyle(this._getElement());
});
/**
*
*/
dynamicTree.Tree.addMethod('_registerCustomScroll', function() {
if (!this.scroll && this._getElement()) {
var scrollBar = this._getElement().up(layoutModule.SWIPE_SCROLL_PATTERN);
if(scrollBar) {
var scroll = layoutModule.scrolls.get(scrollBar.identify());
scroll && (this.scroll = scroll);
// if (this.scroll /*&& this.scroll.tree != this*/) {
// this.scroll.tree = this;
//
// var onScrollStart = this.scroll.options.onScrollStart;
//
// this.scroll.options.onScrollStart = function(e) {
// onScrollStart && onScrollStart();
// this.tree.revertSelection(e);
// }
// }
}
}
//disableSelectionWithoutCursorStyle(this._getElement());
});
/**
* Sets root node.
*
* @param rootNode the root node to be set.
*/
dynamicTree.Tree.addMethod('setRootNode', function(rootNode) {
this.rootNode = rootNode;
if (this.rootNode) {
this.rootNode.treeId = this.id;
}
});
/**
* Returns the root node.
*/
dynamicTree.Tree.addMethod('getRootNode', function() {
return this.rootNode;
});
/**
* Refresh the style of the tree.
*/
dynamicTree.Tree.addMethod('refreshStyle', function() {
var element = this._getElement();
if(element.templateClassName) { element.className = element.templateClassName; }
this.treeClassName && element.addClassName(this.treeClassName);
if (this.bShowRoot) {
element.removeClassName(this.HIDE_ROOT_CLASS_NAME);
} else {
element.addClassName(this.HIDE_ROOT_CLASS_NAME);
}
});
/**
* Resets the list of selected nodes.
*/
dynamicTree.Tree.addMethod('resetSelected', function() {
this._prevSelectedNodes = this.selectedNodes.clone();
this.selectedNodes = [];
});
dynamicTree.Tree.addMethod('revertSelection', function(evt) {
console.log("Revert");
var nodes = this._prevSelectedNodes.clone();
nodes = nodes.concat(this.selectedNodes);
this.selectedNodes = this._prevSelectedNodes.clone();
for (var i = 0; i < nodes.length; i++) {
nodes[i].refreshStyle();
}
});
/**
* Returns the first selected node from the list of selected nodes. If no nodes were selected then null returned.
*/
dynamicTree.Tree.addMethod('getSelectedNode', function() {
return (this.selectedNodes.length == 0) ? null : this.selectedNodes[0];
});
/**
* Adds the specified node to the list of selected nodes.
*
* @param node the node.
*/
dynamicTree.Tree.addMethod('addNodeToSelected', function(node) {
this.selectedNodes.push(node);
this._prevSelectedNodes = this.selectedNodes.clone();
});
/**
* Removes the specified node from the list of selected nodes.
*
* @param the node.
*/
dynamicTree.Tree.addMethod('removeNodeFromSelected', function(node) {
for (var i = 0; i < this.selectedNodes.length; i++) {
if (this.selectedNodes[i] == node) {
this._prevSelectedNodes = this.selectedNodes.clone();
this.selectedNodes.splice(i, 1);
return;
}
}
});
/**
* Returns true
if specified node is selected, false
otherwise.
*
* @param node the node.
*/
dynamicTree.Tree.addMethod('isNodeSelected', function(node) {
var len = this.selectedNodes && this.selectedNodes.length;
if (len) {
for (var i = 0; i < len; i++) {
if (this.selectedNodes[i] == node) {
return true;
}
}
}
return false;
});
/**
* Resorts the sub-tree for the specified parent node.
*
* @param parentNode the parent node.
*/
dynamicTree.Tree.addMethod('resortSubtree', function(parentNode) {
if (parentNode) {
parentNode.resortChilds();
for (var i = 0; i < parentNode.childs.length; i++) {
this.resortSubtree(parentNode.childs[i]);
}
}
});
/**
* Resorts all the tree.
*/
dynamicTree.Tree.addMethod('resortTree', function() {
if (this.sortNodes) {
this.resortSubtree(this.rootNode);
}
});
/**
* Reads states from the cookies.
*/
dynamicTree.Tree.addMethod('readStates', function() {
var states = dynamicTree.getCookie('tree' + this.id);
if (states != null) {
var array = states.split(';');
for(var i=0;inode2, 0 otherwise
*/
dynamicTree.Tree.addMethod('comparer', function (node1, node2) {
var i, k;
if (this.sorters && this.sorters.length) {
for (i = 0; i < this.sorters.length; i++) {
k = this.sorters[i](node1, node2);
if (k != 0) {
return k;
}
}
}
return 0;
});
/**
* Renders tree into a container.
*/
dynamicTree.Tree.addMethod('renderTree', function() {
this.readStates();
this.stopWaiting();
this.refreshStyle();
/* Setting event handlers */
var treeContainer = this._getElement();
if (this.rootNode) {
this.writeStates(this.rootNode.id, dynamicTree.TreeNode.State.OPEN);
this.rootNode.showNode();
this.rootNode.render(treeContainer);
this.refreshScroll();
}
//TODO Removed for now - lack of available event causes issues
//var selected = this.getSelectedNode() || (this.bShowRoot ? this.rootNode : this.rootNode.getFirstChild()) ;
//selected && selected.select();
});
dynamicTree.Tree.addMethod('refreshScroll', function(delay) {
if (delay) {
setTimeout(function(){
this.scroll && this.scroll.refresh();
}.bind(this), delay);
} else {
this.scroll && this.scroll.refresh();
}
});
/**
*
*/
dynamicTree.Tree.addMethod('binarySearchOfNode', function(nodes, node) {
var low = 0;
var high = nodes.length - 1;
while (low <= high) {
var mid = Math.round((low + high) / 2);
var midNode = nodes[mid];
if (this.comparer(midNode, node) < 0) {
low = mid + 1;
} else if (this.comparer(midNode, node) > 0) {
high = mid - 1;
} else {
return mid; // key found
}
}
return -(low + 1); // key not found.
});
/**
* Deselects all the nodes.
*/
dynamicTree.Tree.addMethod('_deselectAllNodes', function(event) {
if (this.selectedNodes.length > 0) {
var nodes = this.selectedNodes.clone();
this.resetSelected();
for (var i = 0; i < nodes.length; i++) {
nodes[i].refreshStyle();
}
if (event) {
var tree = dynamicTree.trees[this.id];
tree.fireUnSelectAllEvent(event);
}
}
});
dynamicTree.Tree.addMethod('_selectOrEditNode', function(evt, node, ctrlHeld, shiftHeld, isContextMenuBtn) {
var isContextMenu = node.isSelected() && isContextMenuBtn;
var isDeselect = this.multiSelectEnabled && node.isSelected() && ctrlHeld && !isContextMenu;
var isEdit = node.isSelected() && dynamicTree.treeNodeEdited !== node && !this.multiSelectEnabled && !ctrlHeld && !isContextMenu;
var isDoEndEdit = dynamicTree.treeNodeEdited != null && (isDeselectAll || isDeselect);
var isRangeSelect = this.multiSelectEnabled && !node.isSelected() && shiftHeld && isNotNullORUndefined(this._lastSelectedNode)
&& this._lastSelectedNode.parent === node.parent;
var isRangeReduce = this.multiSelectEnabled && node.isSelected() && shiftHeld && isNotNullORUndefined(this._lastSelectedNode)
&& this._lastSelectedNode.parent === node.parent;
var isDeselectAll = (!this.multiSelectEnabled && !node.isSelected()) || (this.multiSelectEnabled && !ctrlHeld && !node.isSelected());
var isSelect = !node.isSelected() || (isDeselectAll && this.selectedNodes.length > 1);
if(!shiftHeld || !this._lastSelectedNode) {
this._lastSelectedNode = node;
}
if (isDeselect) {
node.deselect(evt);
}
if (isDeselectAll) {
this._deselectAllNodes(evt);
}
if (isEdit) {
node.edit(evt);
return;
}
if (isDoEndEdit) {
dynamicTree.treeNodeEdited.doEndEdit(evt);
}
if(isRangeSelect || isRangeReduce) {
var parent = node.parent;
var start = parent.childs.indexOf(this._lastSelectedNode);
var end = parent.childs.indexOf(node);
var min = Math.min(start, end), max = Math.max(start, end);
}
if (isRangeSelect) {
if (min > -1) {
for (var i = min; i <= max; i++) {
parent.childs[i].select(evt);
}
} else {
parent.childs[max].select(evt);
}
return;
}
if (isRangeReduce) {
for (var i = 0; i < min; i++) {
parent.childs[i].deselect(evt);
}
for (var i = max+1; i < parent.childs.length; i++) {
parent.childs[i].deselect(evt);
}
return;
}
if (isSelect) {
node.select(evt);
}
});
dynamicTree.Tree.addMethod('_deselectOthers', function (evt, node, ctrlHeld, shiftHeld, isContextMenuBtn) {
var isDeselectOther = (this.multiSelectEnabled && this.selectedNodes.length > 1 && node.isSelected() &&
!(ctrlHeld || shiftHeld || isContextMenuBtn));
isDeselectOther && this.selectedNodes.findAll(function(n) { return n != node; }.bind(this)).invoke('deselect', evt);
});
/**
* find the next node in the tree (ignoring hierachy) and select it
* @param {Object} node - current node
*/
dynamicTree.Tree.addMethod('_selectNextNode', function (node, event) {
//recurse up the parent chain until we get a parent with a next sibling
function getNextUncle(node) {
node = node.parent;
return (!node && null) || node.nextSibling || getNextUncle(node);
}
var nextNode = (node.isOpen() && node.getFirstChild()) || node.nextSibling || getNextUncle(node);
nextNode && (node.deselect() && nextNode.select(event));
});
/**
* find the previous node in the tree (ignoring hierachy) and select it
* @param {Object} node - current node
*/
dynamicTree.Tree.addMethod('_selectPreviousNode', function (node, event) {
function getLastVisibleDescendant(node) {
return (!(node.isOpen() && node.hasChilds()) && node) || getLastVisibleDescendant(node.getLastChild());
}
var prevNode = (node.prevSibling && getLastVisibleDescendant(node.prevSibling)) || node.parent;
prevNode && (node.deselect() && prevNode.select(event));
});
/**
* if open go to first child node, otherwise open node
* @param {Object} node
*/
dynamicTree.Tree.addMethod('_selectInwards', function (node, event) {
var inNode = node.isOpen() && node.getFirstChild();
inNode ? (node.deselect() && inNode.select(event)) : node.handleNode(event);
});
/**
* if closed or leaf go to parent, otherwise close node
* @param {Object} node
*/
dynamicTree.Tree.addMethod('_selectOutwards', function (node, event) {
if (!node.isHiddenRootNode()) {
var outNode = node.isOpen() ? null : node.parent;
outNode ? (node.deselect() && outNode.select(event)) : node.handleNode(event);
}
});
/**
* Sorter by order value assigned to nodes.
* Order has to be a number. Node that has some order is considered to be
* LESS than node that does not have any order (order=null)
* @param {Object} node1 first node
* @param {Object} node2 second node
* @returns negative number if node1node2, 0 otherwise
*/
dynamicTree.Tree.addMethod('sortByOrder', function (node1, node2) {
var order1 = node1.orderNumber;
var order2 = node2.orderNumber;
if (order1 == null && order2 == null) {
return 0;
}
if (order1 == null) {
return 1;
}
if (order2 == null) {
return -1;
}
return order1 - order2;
});
/**
* Sorter alphabetically by node names
* @param {Object} node1 first node
* @param {Object} node2 second node
* @returns negative number if node1node2, 0 otherwise
*/
dynamicTree.Tree.addMethod('sortByName', function (node1, node2) {
var n1 = node1.name.toLowerCase();
var n2 = node2.name.toLowerCase();
return n1 > n2 ? 1 : (n1 < n2 ? -1 : 0);
});
/**
* DOM identifier of the tree wait template.
*/
dynamicTree.Tree.addVar('TREE_WAIT_TEMPLATE_DOM_ID', "list_responsive_collapsible:loading");
/**
* Makes visual effect of wait for tree loading.
*/
dynamicTree.Tree.addMethod('wait', function() {
$(this.id).update($(this.TREE_WAIT_TEMPLATE_DOM_ID).cloneNode(true));
});
/**
* Removes visual effect of wait for tree loading.
*/
dynamicTree.Tree.addMethod('stopWaiting', function() {
$(this.id).update("");
});
dynamicTree.Tree.addMethod('_getTemplateElement', function(currentElement) {
var id = this.templateDomId;
/**
* @see comment in _getHeaderTemplateElement in tree.treenode.js for an explanation of the commented below
*/
// if (!dynamicTree._templateHash[id]) {
// dynamicTree._templateHash[id] = $(id);
// }
// var clone = dynamicTree._templateHash[id].cloneNode(true);
var clone = $(id).cloneNode(true);
clone.writeAttribute("id", this.getId());
clone.templateId = id;
clone.templateClassName = clone.className;
cloneCustomAttributes(currentElement, clone);
return clone;
});