/*
================================================================================
GroupTree
================================================================================
*/
/**
* GroupTree constructor.
*
* @param kwArgs.id [String] DOM node id
* @param kwArgs.icns [String] URL to magnifying glass icon
* @param kwArgs.minIcon [String] URL to min.gif
* @param kwArgs.plusIcon [String] URL to plus.gif
*/
bobj.crv.newGroupTree = function(kwArgs) {
var UPDATE = MochiKit.Base.update;
kwArgs = UPDATE ( {
id : bobj.uniqueId (),
visualStyle : {
className : null,
backgroundColor : null,
borderWidth : null,
borderStyle : null,
borderColor : null,
fontFamily : null,
fontWeight : null,
textDecoration : null,
color : null,
width : null,
height : null,
fontStyle : null,
fontSize : null
},
icns : bobj.crvUri ('images/magnify' + (bobj.crv.config.isRTL ? '_rtl' : '')+ '.gif'),
minIcon : bobj.crvUri ('images/min.gif'),
plusIcon : bobj.crvUri ('images/plus.gif')
}, kwArgs);
var o = newTreeWidget (kwArgs.id + '_tree', '100%', '100%', kwArgs.icns, null, null, 'groupTree',
bobj.crv.GroupTree._expand, bobj.crv.GroupTree._collapse, null, kwArgs.minIcon, kwArgs.plusIcon);
o.vsbar = new bobj.crv.VerticalScrollBar();
o._children = [];
o._modalChildren = [];
o._lastNodeIdInitialized = -1;
o._lastNodeInitialized = null;
o._curSigs = [];
bobj.fillIn (o, kwArgs);
o.widgetType = 'GroupTree';
o.initOld = o.init;
UPDATE (o, bobj.crv.GroupTree);
return o;
};
bobj.crv.GroupTree = {
/**
* Disposes group tree
*/
dispose : function() {
/* removes all the signals */
while (this._curSigs.length > 0) {
bobj.crv.SignalDisposer.dispose (this._curSigs.pop ());
}
/* disposes all the children*/
while (this._children.length > 0) {
var child = this._children.pop ();
child.dispose ();
bobj.deleteWidget (child);
delete child;
}
this._lastNodeIdInitialized = -1;
this._lastNodeInitialized = null;
this.sub = [];
bobj.removeAllChildElements(this.treeLyr);
},
beginHTML : function () {
return '' +
'
'+
'
';
},
setSelected : function(isSelected) {
if(isSelected)
this.getCtrNode().setAttribute("role", "navigation");
else
this.getCtrNode().removeAttribute("role");
},
focusFirstChild : function () {
var isFocused = false;
if (this.focusNode) {
this.setFocus(this.focusNode.id);
return true;
}
else {
return false;
}
},
endHTML : function () {
return '
'+this.vsbar.getHTML()+'
';
},
getModalChildren : function () {
return this._modalChildren;
},
/**
* Adds a child widget as a group tree node.
*
* @param widget
* [Widget] Child tree node widget
*/
addChild : function(widget) {
var Base = MochiKit.Base;
var Signal = MochiKit.Signal;
var connect = Signal.connect;
widget.expandPath = this._children.length + '';
this._children.push (widget);
this.add (widget);
widget.updatePropertyAndConnectSignals (this.enableDrilldown, this.enableNavigation);
this._curSigs.push (connect (widget, 'grpDrilldown', Base.partial (Signal.signal, this, 'grpDrilldown')));
this._curSigs.push (connect (widget, 'grpNodeRetrieveChildren', Base.partial (Signal.signal, this, 'grpNodeRetrieveChildren')));
},
/**
* Since GroupTree nodes are delay loaded, we would have to store the timeout ids to cancel them in case user drill down to another views
*/
delayedBatchAdd : function(children) {
this._modalChildren = [];
if (!children || children.length == 0)
return;
this._modalChildren = children;
var childrenHTML = "";
// Add non group node (i.e. main report and sub report node)
var rootNode = null;
var groupRootNode = null;
while (this.containSingleReportNode(children))
{
var childWidget = bobj.crv.createWidget (children[0], true); // true = skip children
if (groupRootNode != null)
{
groupRootNode.addChild(childWidget);
}
else
{
this.focusNode = childWidget;
rootNode = childWidget;
}
groupRootNode = childWidget;
children = children[0].children;
}
var numChildrenToRender = 0;
if (children) {
numChildrenToRender = children.length > 100 ? 100 : children.length;
if (groupRootNode != null)
groupRootNode.actualNumChildren = children.length;
else if (rootNode != null)
rootNode.actualNumChildren = children.length;
}
if(numChildrenToRender > 0) {
for(var i = 0 ; i < numChildrenToRender ; i++) {
var childWidget = bobj.crv.createWidget (children[i]);
if (groupRootNode != null)
{
groupRootNode.addChild(childWidget);
}
else
{
this.addChild(childWidget);
if(this.initialized())
childrenHTML += child.getHTML(0);
}
}
}
if (rootNode != null)
{
this.addChild(rootNode);
childrenHTML += rootNode.getHTML(0, true);
}
if(this.initialized()) {
this.appendChildrenHTML (childrenHTML);
this.initChildren ();
}
},
getCtrNode : function () {
return this._ctrNode;
},
appendChildrenHTML : function (childrenHTML) {
append (this.treeLyr, childrenHTML);
},
appendToTreeCtr : function (node) {
this._ctrNode.appendChild (node);
},
init : function() {
this.initOld ();
bobj.setVisualStyle (this.layer, this.visualStyle);
this.css.verticalAlign = "top";
this.initChildren ();
this._ctrNode = getLayer (this.id + '_ctr');
this.vsbar.init();
this.vsbar.setScrollableElement (this._ctrNode);
this._groupTreeListener = new bobj.crv.GroupTreeListener(this);
this.selectPath(this.selectedPath);
},
update : function(update) {
if (update.cons == "bobj.crv.newGroupTree") {
var args = update.args;
var path = args.lastExpandedPath;
var previousFocusPath = this.focusNode ? this.focusNode.expandPath : "";
/* if path specified, then update specific path, otherwise recreate grouptree */
// if user has expands a node after session timeout -> the whole gt must be rerendered
if (path.length > 0 && this._children.length > 0) {
this.updateNode (path, update);
} else {
this.refreshChildNodes (update);
if(previousFocusPath != "") {
// Accessibility requirement: when a node is expanded, set the focus back to that node after refreshing the tree
var resetFocusNode = this.findNodeByGroupPath(previousFocusPath);
if(resetFocusNode != null && (resetFocusNode.domElem.clientWidth != 0 || resetFocusNode.domElem.offsetTop != 0)) {
this.setFocus(resetFocusNode.id);
}
}
}
this.selectPath(args.selectedPath);
}
},
selectPath : function(path) {
if (path) {
var node = this.findNodeByGroupPath(path);
if (node && node.selectOld) {
node.selectOld();
}
}
},
delayedAddChild : function(widget) {
this.addChild (widget);
append (this.treeLyr, widget.getHTML (this.initialIndent));
},
initChildren : function () {
while(this._lastNodeIdInitialized < this.getChildrenCount() - 1)
this.initNextChild ();
},
initNextChild : function () {
var nextNode = null;
var nextNodeId = -1;
var children = this._children;
if(this._lastNodeIdInitialized == -1) {
var treeSpanLayer = getLayer("treeCont_" + this.id);
nextNode = treeSpanLayer.firstChild;
nextNodeId = 0;
}
else {
nextNode = this._lastNodeInitialized;
do {
if (nextNode.nextSibling != null)
nextNode = nextNode.nextSibling;
else
nextNode = nextNode.firstChild;
} while(!(nextNode.id && nextNode.id.indexOf("TWe_") > -1))
nextNodeId = this._lastNodeIdInitialized + 1;
}
if(nextNode != null) {
this.getChildren(nextNodeId).init(nextNode);
this._lastNodeInitialized = nextNode;
this._lastNodeIdInitialized = nextNodeId;
}
},
getBestFitHeight : function () {
return bobj.getHiddenElementDimensions (this.layer).h;
/**
* Since container of tree could be invisible, getHiddenElementDimensions has to be called
* instead of element.getHeight()
*/
},
/**
* refreshes group tree by removing all nodes and adding new ones
*/
refreshChildNodes : function(update) {
this.dispose ();
this.delayedBatchAdd (update.children);
MochiKit.Signal.signal(this, "refreshed");
},
/**
* @param path
* path to the node that should be updated eg) 0-1-2
* @param newTree
* the new tree sent in update
*
* Updates children of node specified by path
*/
updateNode : function(path, newTree) {
if (path && path.length > 0) {
var pathArray = path.split ('-');
var node = this;
var newNode = newTree;
/* Navigating to the node that requires update */
var i = 0;
var len = pathArray.length;
for ( ; i < len; i++) {
if (node && newNode) {
var childIndex = parseInt (pathArray[i]);
var newNodeTmp = newNode.children[childIndex];
var nodeTmp = node._children[childIndex];
if (newNodeTmp && nodeTmp) {
newNode = newNodeTmp;
node = nodeTmp;
}
} else {
break;
}
}
/* if we found the node that requires update, then update its children */
if (node && newNode && newNode.args.groupPath == node.groupPath && node._children.length == 0) {
for ( var nodeNum in newNode.children) {
var newChildnode = bobj.crv.createWidget (newNode.children[nodeNum]);
node.addChild (newChildnode);
}
node.updatePropertyAndConnectSignals (this.enableDrilldown, this.enableNavigation);
node.expand ();
for ( ; i < len; i++) {
var childIndex = parseInt (pathArray[i]);
var node = node._children[childIndex];
if (node)
node.expand ();
}
}
}
},
containSingleReportNode : function (children) {
if (!children || children.length != 1)
return false;
var object = children[0];
if (object.widgetType)
return (object.type != "group");
else if (object.args)
return (object.args.type != "group");
else
return false;
},
getChildrenCount : function () {
var count = 0;
var children = this._children;
while (this.containSingleReportNode(children))
{
count++;
children = children[0]._children;
}
if (children)
count += children.length;
return count;
},
getModalChildrenCount : function () {
var count = 0;
var children = this._modalChildren;
while (this.containSingleReportNode(children))
{
count++;
children = children[0].children;
}
if (children)
count += children.length;
return count;
},
getChildren : function (index) {
var children = this._children;
while (this.containSingleReportNode(children) && index > 0)
{
index--;
children = children[0]._children;
}
return children[index];
},
getModalChild : function (index) {
var children = this._modalChildren;
while (this.containSingleReportNode(children) && index > 0)
{
index--;
children = children[0].children;
}
if (index < children.length)
return children[index];
else
return null;
},
delayedAddChildToRealGroupRoot : function (node) {
this.getRealGroupRoot().delayedAddChild(node);
},
getRealGroupRoot : function () {
var children = this._children;
var root = null;
while (this.containSingleReportNode(children))
{
root = children[0];
children = children[0]._children;
}
return root;
},
addRealGroupChildrenHTML : function (html) {
var treeSpanLayer = getLayer("treeCont_" + this.id);
var children = this._children;
var realGroupChildrenLayer = treeSpanLayer;
while(this.containSingleReportNode(children))
{
children = children[0]._children;
realGroupChildrenLayer = realGroupChildrenLayer.firstChild.nextSibling;
}
append(realGroupChildrenLayer, html);
},
/**
* Private. Callback function when a (complete) group tree node is collapsed.
*/
_collapse : function(expandPath) {
MochiKit.Signal.signal (this, 'grpNodeCollapse', expandPath);
this._doLayout (this.getWidth(), this.getHeight());
},
/**
* Private. Callback function when a (complete) group tree node is expanded.
*/
_expand : function(expandPath) {
MochiKit.Signal.signal (this, 'grpNodeExpand', expandPath);
this._doLayout (this.getWidth(), this.getHeight());
},
_doLayout : function (w, h) {
bobj.setOuterSize (this.layer, w, h);
bobj.setOuterSize (this._ctrNode, w, h);
if (bobj.isNumber(h)) {
this.vsbar.adjustForResize (h);
if (this.vsbar.isDisplayed()) {
var newWidth = Math.max (0, w - this.vsbar.SCROLLBAR_SIZE)
bobj.setOuterSize (this._ctrNode, newWidth, null);
}
}
},
resize : function(width, height) {
this._doLayout(width, height);
MochiKit.Signal.signal(this, "resized");
},
findNodeByGroupPath : function(groupPath) {
var pathArray = groupPath.split ('-');
var node = this;
for ( var i = 0, len = pathArray.length; i < len; i++) {
if (node) {
var childIndex = parseInt (pathArray[i]);
node = node._children[childIndex];
} else {
break;
}
}
return node;
}
};