//##############################################################################
// File:    treeview.js
// Author:  Simon A. Platten
// Notes:   Javascripts Treeview object
//##############################################################################
// History:
//  30/11/09  SP, Created
//##############################################################################

//##############################################################################
// Class constructor, creates new treenode object, do not use this class it is
// used and managed internally by the TREEVIEW class
//
// Parameters:  strKey, unique node name to identify this node in the tree
//              strText, any text to display for this node
//              objPNode, where this node fits in the tree, null for root
//              strFun, custom call back to call when node clicked
//              objTree, the tree this node is associated with
//              blnExpand, true to expand, false to collapse
//              blnSortChildren, true to sort, false not to sort
//              blnForceImg, true to force expand image, false to use auto. 
//##############################################################################
var aryAllTrees = null;

function TREENODE( strKey, strText, objPNode, 
                   strFun, objTree, blnExpand, 
                   blnSortChildren, blnForceImg )
{
  try{
    var strPKey;
    
    if ( blnExpand == undefined )
      blnExpand = false;
            
    if ( objPNode == null )
      {
  // We are adding this element to the root      
      if ( objTree == null )
        throw( "Invalid tree object" );
      strPKey = objTree.strKey;
      }        
    else
      {
      strPKey = objPNode.strKey;      
  // Modify the parent node, adding this node to its list of child nodes
      objPNode.objChildNodes[strKey] = this;
      objPNode.objChildNodes.length++;      
  // Make the expand / collaspe image visible
      var elimg = GetElementByID( strPKey + "img" );
  // The image may not be visible yet      
      if ( elimg  )        
        elimg.style.display = "block";
      }
  // Setup the node members                
    this.strKey         = strKey;
    this.strText        = strText;
    this.objPNode       = objPNode;
    this.strFun         = strFun;
    this.objChildNodes  = new Array();
    this.objChildNodes.length = 0;
    this.blnState       = blnExpand;
    this.objTree        = objTree;
    this.strDATA        = "";
    this.objData        = null;
  // Save the key                                            
    this.intIndex       = objTree.aryKeys.length;     
    objTree.aryKeys[objTree.aryKeys.length] = strKey;                                            
    
    var OnClick = 
      "onclick=\"new function() {\n" +
      "  var objTmpTree = " + objTree.strInstance + ";\n" +
      "  if ( objTmpTree == null || objTmpTree == undefined )\n" +
      "    return;\n" +      
      "  var objNode = objTmpTree.FindNode( '" + this.strKey + "' );\n" +
      "  if ( objNode == null )\n" +
      "    return;\n" +      
      "  objNode.ToggleView();\n" +
      "}\" ";

    var strImg, strExpand;
    
    if ( blnExpand )
      {
      strImg = "collapse.gif";
      strExpand = "block";
      }
    else
      {
      strImg = "expand.gif";
      strExpand = "none";
      }
    if ( blnForceImg )
      strShowImg = "block";
    else
      strShowImg = "none";      
    this.strHTML        = 
        "<table border=\"0\" " +
               "cellpadding=\"0\" " +
               "cellspacing=\"0\" >" +
          "<tr>" +                   
            "<td align=\"center\" " +
                "valign=\"top\" " +
                "width=\"13px\">" +
              "<img id=\"" + this.strKey + "img\" " +
                   "src=\"/osr/images/" + strImg + "\" " +
                   "width=\"13\" " +
                   "height=\"13\" " +
                   "alt=\"Expand\" " +
                   "style=\"display:" + strShowImg + "; padding: 3px;\" " +
                   OnClick +
                   "/>" +
            "</td>" +
            "<td align=\"left\" " +
                "valign=\"middle\" >" +
              "<div " + OnClick +
                    "id=\"" + this.strKey + "node\" " +
                    "class=\"treenode\" " + 
                    "onmouseover=\"TreeNodeHighLight( this );\" " +
                    "onmouseout=\"TreNodeNormal( this ); \" >" + 
                this.strText +
              "</div>" +
              "<span id=\"" + this.strKey + "\" " +
                   "style=\"display:" + strExpand + ";\">" + 
              "</span>" +
            "</td>" +
          "</tr>" +
        "</table>" +
        "<div id=\"" + this.strKey + "data\" " +
              "style=\"display:" + strExpand + ";\"></div>";
  // All nodes in the root are rendered immediately        
    if ( objPNode == null )
      {
  // Get the parent node      
      var el = GetElementByID( strPKey );
    
      if ( !el )
        throw ( "Cannot add node, parent is not accessible in DOM: " + strPKey );            
      el.innerHTML += this.strHTML;
      }
    else
      {
  // Make sure children are sorted
      if ( blnSortChildren )      
        objPNode.SortChildren( objTree.blnOrder );                                            
  // This node has a parent if it is expanded then attach this node to the DOM
  // immediately.
      if ( objPNode.blnState == true )
        {
        var el = GetElementByID( strPKey );
      
        if ( !el )
          throw ( "Cannot add node, parent is not accessible in DOM: " + strPKey );
  // Rebuild HTML incase order of child nodes has changed          
        var HTML = "";        
        for( var strKey in objPNode.objChildNodes )
          HTML += objPNode.objChildNodes[strKey].strHTML;                
        el.innerHTML = HTML;
        }
      }
  } catch( e ) {
    alert( "ERROR in TREENODE(): " + e.message );
  }      
}
//##############################################################################
// Sort all children of this node
// Pass 0 to sort in ascending order, 1 to sort in descending order
//##############################################################################
TREENODE.prototype.SortChildren = function( blnOrder )
{
  try{
    if ( this.objChildNodes.length == 0 )
      return;
// Make an array of keys    
    var aryKeys = new Array();
    for( var x in this.objChildNodes )
      aryKeys[aryKeys.length] = x;
// Sort the keys
    aryKeys.sort();    
    if ( blnOrder )
      aryKeys.reverse();
// Build resulting associative array
    var objRes = {};          
    for( var i=0; i<aryKeys.length; i++ )
      objRes[aryKeys[i]] = this.objChildNodes[aryKeys[i]];
// Replace the original array with the sorted array      
    this.objChildNodes = new Array();
    this.objChildNodes.length = 0;
    
    for( var x in objRes )
      this.objChildNodes[x] = objRes[x];
    this.objChildNodes.length = aryKeys.length;
// Make sure the tree array of keys is in the correct order
    this.objTree.aryKeys = new Array();
    for( var x in this.objTree.objNodes )
      {
// Re-index the nodes
      this.objTree.objNodes[x].intIndex = this.objTree.aryKeys.length;             
      this.objTree.aryKeys[this.objTree.aryKeys.length] = x;
      }                                       
  } catch( e ) {
    alert( "ERROR in TREENODE.SortChildren(): " + e.message );
  }
}
//##############################################################################
// De-Selects node putting restoring it to normal
//##############################################################################
TREENODE.prototype.DeSelect = function()
{
  try{
    var elnode  = GetElementByID( this.strKey + "node" );
    
    if ( elnode )
      {
      elnode.className = "treenode";
      elnode.style.border = "none";
      }
    this.objTree.objSelectedNode = null;    
  } catch( e ) {
    alert( "ERROR in TREENODE.DeSelect(): " + e.message );
  }      
}
//##############################################################################
// Selects node putting a dotted border around it
//##############################################################################
TREENODE.prototype.Select = function()
{
  try{
  // Go through all other trees and make sure that no node is left selected
  // so you cannot have two trees with a selection
    for( var t=0; t<aryAllTrees.length; t++ )
      {
      var objTree = aryAllTrees[t];
      var el = GetElementByID( objTree.strKey );
  // If the tree is no longer present or there is no selection then skip it      
      if ( !el || objTree.objSelectedNode == null)
        continue; 
      objTree.objSelectedNode.DeSelect();
      }
  // Make sure the specific node is selected      
    var elnode  = GetElementByID( this.strKey + "node" );
    
    if ( elnode )
      elnode.style.border = "1px dotted #333333";
    this.objTree.objSelectedNode = this;
  } catch( e ) {
    alert( "ERROR in TREENODE.Select(): " + e.message );
  }      
}
//##############################################################################
// Hides or Shows child nodes associated with this node
//##############################################################################
TREENODE.prototype.ToggleView = function()
{
  try{
    var el      = GetElementByID( this.strKey );
    var elimg   = GetElementByID( this.strKey + "img" );            
  // Make sure the expand / collaspe image is correct
    if ( elimg )
      {
      if ( String(elimg.src).match( "/osr/images/collapse.gif" ) )
        elimg.src = "/osr/images/expand.gif";      
      else
        elimg.src = "/osr/images/collapse.gif";
      }
    if ( el )
      {
      if ( el.style.display == "block" )
        {
        if ( this.objChildNodes.length )
          {
          var eldata = GetElementByID( this.strKey + "data" );
          
          if ( eldata )
            {
            eldata.style.display = "none";
            el.style.display = "none";
            }
          this.blnState = false;
          }
        }
      else
        {
  // Do we need to add the child HTML to the node?      
  // Build up list of all HTML immediate child nodes        
        var HTML = "";        
        for( var strKey in this.objChildNodes )
          HTML += this.objChildNodes[strKey].strHTML;      
        el.innerHTML = HTML;
  // Do these children have children, if yes render expand / collaspe image
        for( var strKey in this.objChildNodes )        
          {
          if ( this.objChildNodes[strKey].objChildNodes.length || 
               this.objChildNodes[strKey].strDATA )
            {
            var elcimg = GetElementByID( strKey + "img" );
            
            if ( elcimg )
              elcimg.style.display = "block";
            } 
          }      
        if ( this.strDATA.length )
          {
          var eldata = GetElementByID( this.strKey + "data" );
          
          if ( eldata )
            {
            if ( eldata.innerHTML.length == 0 )
              eldata.innerHTML = this.strDATA;
            eldata.style.display = "block";
            }
          }        
        el.style.display = "block";
        this.blnState = true;
        }
      }
    if ( this.objTree.objSelectedNode )
      this.objTree.objSelectedNode.DeSelect();      
    if ( this.blnState == true )
      {
      this.Select();                  

      if ( this.strFun )
        this.strFun( this );
      } 
  } catch( e ) {
    alert( "ERROR in TREENODE.ToggleView(): " + e.message );
  }
}
//##############################################################################
// Highlights node text when mouse cursor is over it
//##############################################################################
function TreeNodeHighLight( el )
{
  try{
    el.className = "treehighlight";
  } catch( e ) {
    alert( "ERROR in TREENODE.TreeNodeHighLight(): " + e.message );  
  }
}
//##############################################################################
// Restores node text to normal when mouse cursor moves off it
//##############################################################################
function TreNodeNormal( el )
{
  try{
    el.className = "treenode";
  } catch( e ) {
    alert( "ERROR in TREENODE.TreNodeNormal(): " + e.message );  
  }
}
//##############################################################################
// Method:      AddData
// Parameters:  strHTML, the HTML to associate with a node
//##############################################################################
TREENODE.prototype.AddData = function( strHTML )
{ 
  try{
    this.strDATA = strHTML;
  // If a node already exists for this item in the DOM, then update it    
    var eldata = GetElementByID( this.strKey + "data" );
        
    if ( !eldata )
      return;
    eldata.innerHTML = this.strDATA;
  } catch( e ) {
    alert( "ERROR in TREENODE.AddData(): " + e.message );  
  }
}
//##############################################################################
// Method:      Remove
// Parameters:  None
// Notes:       Removes this node from the tree, all children are removed,
//              Parent nodes are also cleaned up.
//##############################################################################
TREENODE.prototype.Remove = function()
{ 
  try{
  // Remove the entry from the collection of all nodes
    var el = GetElementByID( this.strKey );

    if ( el )
      el.innerHTML = "";
    var eldata = GetElementByID( this.strKey + "data" );
        
    if ( eldata )
      eldata.innerHTML = "";
  // Check the parent of this node, and remove it from the children list         
    if ( this.objPNode && this.objPNode.objChildNodes.length > 0 )
      {
      delete this.objPNode.objChildNodes[this.strKey];
      this.objPNode.objChildNodes.length--;
      }
    delete this.objTree.objNodes[this.strKey];

// Make sure the keys array is re-indexed
    this.objTree.aryKeys = new Array();
    for( var x in this.objTree.objNodes )
      {
// Re-index the nodes
      this.objTree.objNodes[x].intIndex = this.objTree.aryKeys.length;             
      this.objTree.aryKeys[this.objTree.aryKeys.length] = x;
      }                                 
// If the parent only had one child then remove it
    if ( this.objPNode )    
      {
      for( var objParent=this.objPNode; objParent!=null;
            objParent=objParent.objPNode )
        {
        if ( objParent.objChildNodes.length == 0 )
          objParent.Remove();
        else
          {
  // Toggling the view twice, updates the children and restores the state of
  // the parent
          objParent.ToggleView();          
          objParent.ToggleView();
          break;
          }
        }
      }            
  } catch( e ) {
    alert( "ERROR in TREENODE.Remove(): " + e.message );  
  }
}
TREENODE.prototype.strKey;
TREENODE.prototype.strText;
TREENODE.prototype.strFun;
TREENODE.prototype.objPNode;
TREENODE.prototype.objChildNodes;
TREENODE.prototype.objTree;
TREENODE.prototype.blnState;
TREENODE.prototype.intIndex;
TREENODE.prototype.strHTML;
TREENODE.prototype.strDATA;
TREENODE.prototype.objData; // This is for storing information with a node,
                            // can be anything you like, it isn't used by the
                            // tree
//##############################################################################
// Class constructor, creates new treeview object
//
// Parameters: strID, the ID of the DIV element that will contain the tree
//             strInstance, the name of the tree object in the script
//##############################################################################
function TREEVIEW( strID, strInstance )
{
  try{
    var el = GetElementByID( strID );
  
    if ( !el )
      throw ( "Cannot find element: " + strID + " in DOM" );
    this.strKey          = "tv_root_" + strID;
    el.innerHTML         = "<div id=\"" + this.strKey + "\"></div>";
    this.RemoveAll();
    this.strInstance     = strInstance;
    this.blnOrder        = false;   // false = Ascending, true = Descending        
// Add this tree to the array of all trees
    if ( aryAllTrees == null )
      aryAllTrees = new Array();
    aryAllTrees[aryAllTrees.length] = this;    
  } catch( e ) {
    alert( "ERROR in TREEVIEW: " + e.message );
  }    
}
//##############################################################################
// Method:      RemoveAll
// Parameters:  None 
// Returns:     None
// Initialises the tree with nothing in it.
//##############################################################################
TREEVIEW.prototype.RemoveAll = function()
{
  try{
    this.objSelectedNode = null;
    this.objNodes        = new Array();
    this.aryKeys         = new Array();
    var el = GetElementByID( this.strKey );
    
    if ( el )
      el.innerHTML = "";       
  } catch( e ) {
    alert( "ERROR in TREEVIEW.RemoveAll: " + e.message );
  }    
} 
//##############################################################################
// Method:      AddNode
// Parameters:  strKey,   unique node identifier
//              strText, the text to display for the node
//              objPNode, the parent node associated with this node, ptr to
//                        TREENODE object or null if this node is part of the 
//                        root.
//              strFun,  string function with parameters to call when this node 
//                        is clicked can be null.
//              blnExpand, true to expand, false to collapse
//              blnSortChildren, true to sort, false not to sort
//              blnForceImg, true to force expand image, false to use auto. 
// Returns:     new Node
//##############################################################################
TREEVIEW.prototype.AddNode = function( strKey, strText, 
                                       objPNode, strFun, 
                                       blnExpand, blnSortChildren, 
                                       blnForceImg )
{
  try{
  // Does this node already exist ?
    if ( this.objNodes[strKey] == undefined )
      {
  // No, create it now        
      this.objNodes[strKey] = new TREENODE( strKey, strText, objPNode,
                                            strFun, this, blnExpand,
                                            blnSortChildren, blnForceImg );
      }      
    return this.objNodes[strKey];
  } catch( e ) {
    alert( "ERROR in TREEVIEW.AddNode(): " + e.message );
  }      
  return null;
}

//##############################################################################
// Method:      Sort
// Parameters:  None
// Returns:     none
//##############################################################################
TREEVIEW.prototype.Sort = function()
{
  try{
    for( var x in this.objNodes )
      this.objNodes[x].SortChildren( this.blnOrder );
  } catch( e ) {
    alert( "ERROR in TREEVIEW.Sort(): " + e.message );
  }      
}
//##############################################################################
// Method:      FindNode
// Parameters:  strKey,   unique node identifier
// Returns:     new Node or null if not found
//##############################################################################
TREEVIEW.prototype.FindNode = function( strKey )
{
  try{
    var RC;  
    if ( this.objNodes[strKey] == undefined )
      RC = null;
    else
      RC = this.objNodes[strKey];
    return RC;
  } catch( e ) {
    alert( "ERROR in TREEVIEW.FindNode(): " + e.message );
  }      
  return null;    
}
//##############################################################################
// Method:      SearchAllNodes
// Parameters:  strKey, partial text of the key to find
// Returns:     first matching node
//##############################################################################
TREEVIEW.prototype.SearchAllNodes = function( strKey )
{
  try{
    for( var x in this.objNodes )
      {
      if ( x.indexOf( strKey ) != -1 )
        return this.objNodes[x];
      }  
    return null;
  } catch( e ) {
    alert( "ERROR in TREEVIEW.SearchAllNodes(): " + e.message );
  }      
  return null;    
}
TREEVIEW.prototype.objSelectedNode;
TREEVIEW.prototype.objNodes;
TREEVIEW.prototype.aryKeys;
TREEVIEW.prototype.strKey;
TREEVIEW.prototype.strInstance;
TREEVIEW.prototype.blnOrder;

