Source: src/main/javascript/oracle/oj/ojtree/ojtree.js

Oracle® JavaScript Extension Toolkit (JET)
1.1.2

E65298-01

/**
 * Copyright (c) 2014, Oracle and/or its affiliates.
 * All rights reserved.
 */

/* This component is based on original code from:
   jsTree 1.0-rc3   http://jstree.com/
   "Copyright (c) 2010 Ivan Bozhanov (vakata.com)
    Licensed same as jquery - under the terms of either the MIT License or
    the GPL Version 2 License
    http://www.opensource.org/licenses/mit-license.php
    http://www.gnu.org/licenses/gpl.html"
*/


/*---------------------------------------------------------
   ojTree     Displays a Hierarchical Tree
   Depends:   jquery.ui.core.js
              jquery.ui.widget.js
----------------------------------------------------------*/

(function () 
{
  //  "use strict";   // added by build, not required.

  /*
  function debugObj(o)  {
    var s ;
    try { s = JSON.stringify(o) ; }
    catch (e) { s = "ERROR";}
    return s ;
  };
  */

  //  ojTree class names  
  var  /** @const */   OJT_NODE          = "oj-tree-node",            // a tree node (parent or leaf)
       /** @const */   OJT_LEAF          = "oj-tree-leaf",            // leaf node
       /** @const */   OJT_ICON          = "oj-tree-icon",            // <ins> generic icon
       /** @const */   OJT_NICON         = "oj-tree-node-icon",       // <ins> node icon
       /** @const */   OJT_DISC          = "oj-tree-disclosure-icon", // <ins> disclosure icon
       /** @const */   OJT_LAST          = "oj-tree-last",            // last <li> in a <ul>
       /** @const */   OJT_LOADING       = "oj-tree-loading",
       /** @const */   OJT_LAST_SELECTED = "oj-tree-last-selected",
       /** @const */   OJT_TITLE         = "oj-tree-title",           // node text 
       /** @const */   OJT_TYPE          = "oj-tree-type",            // node has a user-defined type
       /** @const */   OJT_INACTIVE      = "oj-tree-inactive" ;


  // Jet class names
  var  /** @const */   OJ_EXPANDED       = "oj-expanded",
       /** @const */   OJ_COLLAPSED      = "oj-collapsed",
       /** @const */   OJ_HOVER          = "oj-hover",
       /** @const */   OJ_SELECTED       = "oj-selected",        // i.e clicked
       /** @const */   OJ_DISABLED       = "oj-disabled",
       /** @const */   OJ_DEFAULT        = "oj-default";


  // ojTree disclosure class names
  var  /** @const */   OJ_DISC           = "oj-tree-icon oj-tree-disclosure-icon oj-component-icon oj-clickable-icon oj-default";

  //  ojTree DnD reorder class names  
  var  /** @const */   OJ_DRAGGABLE      = "oj-draggable",     // node is draggable
       /** @const */   OJ_DRAG           = "oj-drag",          // node that is being dragged
       /** @const */   OJ_ACTIVE_DROP    = "oj-active-drop",   // over node in process of being dropped on
       /** @const */   OJ_VALID_DROP     = "oj-valid-drop",    // over node that could be dropped on
       /** @const */   OJ_INVALID_DROP   = "oj-invalid-drop",  // over node that can't be dropped on
       /** @const */   OJ_DROP           = "oj-drop" ;         // nodes that are valid drop targets


  var  /** @const */   OJT_DROP_OK           = "oj-tree-drop-ok",
       /** @const */   OJT_DROP_INVALID      = "oj-tree-drop-invalid",
       /** @const */   OJT_DROP_MARKER       = "oj-tree-drop-marker",
       /** @const */   OJT_DROP_MARKER_LINE  = "oj-tree-drop-marker-line";

  //  WAI-ARIA  
  var  /** @const */   WA_ROLE              = "role",
       /** @const */   WA_TREE              = "tree",
       /** @const */   WA_TREEITEM          = "treeitem",
       /** @const */   WA_GROUP             = "group",
       /** @const */   WA_SELECTED          = "aria-selected",
       /** @const */   WA_EXPANDED          = "aria-expanded",
       /** @const */   WA_ACTIVEDESCENDANT  = "aria-activedescendant",
       /** @const */   WA_MULTISELECTABLE   = "aria-multiselectable";

  //  Data names
  var  /** @const */   OJT_CHILDREN         = "oj-tree-children";

  //  Data source in use
  var  /** @const */   DS_TREE        =  1,
       /** @const */   DS_COLLECTION  =  2,
       /** @const */   DS_JSON        =  3,
       /** @const */   DS_HTML        =  4,
       /** @const */   DS_NONE        =  0,
       /** @const */   DS_ERROR       = -1;

  // Context Menu item id's
  var  /** @const */  _arMenuCmdMap = { "cut"    : "ojtreecut",
                                        "copy"   : "ojtreecopy",
                                        "paste"  : "ojtreepaste",
                                        "remove" : "ojtreeremove",
                                        "rename" : "ojtreerename"
                                      } ;

  // Context Menu translation keys
  var  /** @const */  _arMenuKeyMap = { "cut"    : "labelCut",
                                        "copy"   : "labelCopy",
                                        "paste"  : "labelPaste",
                                        "remove" : "labelRemove",
                                        "rename" : "labelRename"
                                      } ;

  // Misc translation keys
  var  /** @const */  TRANSKEY_LOADING  = "stateLoading" ;
  var  /** @const */  TRANSKEY_NEWNODE  = "labelNewNode" ;
  var  /** @const */  TRANSKEY_MULTISEL = "labelMultiSelection" ;
  var  /** @const */  TRANSKEY_NODATA   = "labelNoData" ;

  var  /** @const */  USER_UL_ID_PREFIX = "oj-tree-existing-markup-" ;


  var scrollbar_width, e1, e2;
  $(function() {
     if (/msie/.test(navigator.userAgent.toLowerCase())) {
       e1 = $('<textarea cols="10" rows="2"></textarea>').css({ position: 'absolute', top: -1000, left: 0 }).appendTo('body');  //@HTMLUpdateOK
       e2 = $('<textarea cols="10" rows="2" style="overflow: hidden;"></textarea>').css({ position: 'absolute', top: -1000, left: 0 }).appendTo('body'); //@HTMLUpdateOK
       scrollbar_width = e1.width() - e2.width();
       e1.add(e2).remove();
     }
     else {
       e1 = $('<div />').css({ width: 100, height: 100, overflow: 'auto', position: 'absolute', top: -1000, left: 0 })
                        .prependTo('body').append('<div />').find('div').css({ width: '100%', height: 200 });    //@HTMLUpdateOK
       scrollbar_width = 100 - e1.width();
       e1.parent().remove();
     }
  });


  /**
    * Stack of key handler objects for all trees
    * @private
    */
  var  _aKeyHandlerStack = [] ;

  /**
    * Add key combination handler to stack, and add listener 
    * @private
    */
  function  _addKeyFilter(obj)
  {
    _aKeyHandlerStack.push(obj) ;
    $(obj._selector).keydown(_KeyFilterHandler) ;
  };  

  /**
    * Handle keystroke from _KeyFilter and dispatch to the 
    * relevent accessibility key combination handler.
    * @private
    */
  function  _KeyFilterHandler(e)
  {
    var s = "" ;
  
    if (oj.DomUtils.isMetaKeyPressed(e)) {           
      s += "ctrl+" ;
    }
    else if (e.shiftKey) {
      s += "shift+" ;
    }

    var key = e.which ;
    switch(key) {
      case 32:                      // Space
                s += "space"
                break;
      case 37:                      // Left arrow
                s += "left"
                break;
      case 38:                      // Up arrow
                s += "up"
                break;
      case 39:                      // Right arrow
                s += "right"
                break;
      case 40:                      // Down arrow
                s += "down"
                break;
      case 46:                      // Delete
                s += "del"
                break;
      case 33:                      // Page Up
                s += "pgup"
                break;
      case 34:                      // Page Down
                s += "pgdn"
                break;
      case 35:                      // End
                s += "end"
                break;
      case 36:                      // Home
                s += "home"
                break;
      case 56:                      // asterisk
                s = "*"             // remove the shift required to get *
                break;
      case 113:                     // F2
      case 121:                     // F10
/*
      case 112:                     // F1
      case 114:                     // F3
      case 115:                     // F4
      case 116:                     // F5
      case 117:                     // F6
      case 118:                     // F7
      case 119:                     // F8
      case 120:                     // F9
      case 122:                     // F11
      case 123:                     // F12
*/
                s += ("f" + ('1' - (112 - key))) ;
                break;
    }
    if (s.length === 0 || s == "shift+" || s == "ctrl+") {
      return ;
    }

    // If we can find a handler from a tree with focus, dispatch the
    // keystroke event to it.
    var retHandler = null ;
    $.each(_aKeyHandlerStack, function(i, obj) {
        if (obj._this._data.ui.focused) {         // does associated tree have focus
                                                  // yes
          obj._this._data.ui.touchEvent = false ; // ensure touchEvent flag is off
          if (s == "shift+f10") {                 // note the active node if shift+F10
            obj._this._data.menu.activenode = obj._this._data.ui.hovered ;
          }
          else  if (obj._handler[s]) {            // check if matching handler function
              e.preventDefault() ;
              retHandler =  obj._handler[s].call(obj._this, e) ;   // handle the keystroke(s)
          }
          return false ;                          // break out of $.each
        }
    });

    if (retHandler != null) {
      return retHandler ;
    }
  };

  /**
    * Remove key combination handler from stack and remove listener 
    * @private
    */
  function  _removeKeyFilter(selector)
  {
    $.each(_aKeyHandlerStack, function(i) {
       if (_aKeyHandlerStack[i]._selector === selector) {
         $(selector).off("keydown") ;
         _aKeyHandlerStack[i] = null ;
         _aKeyHandlerStack.splice(i, 1) ;
         return false ;
       }
    }) ;
  };


  /**
    *  Creates and adds a style sheet.
    *  @param {Object=}  opts  The css data.
    *  @param {boolean=} bLink  True if opts.url is to be ignored.
    *  @private
    */
  function  _addSheet(opts, bLink)
  {
    var tmp    = false,
        isNew  = true;

    if (opts.str)  {
      if (opts.title)  {
        tmp = $("style[id='" + opts.title + "-stylesheet']")[0];
      }
      if (tmp)  {
        isNew = false;
      }
      else   {
        tmp = document.createElement("style");
        tmp.setAttribute('type',"text/css");
        if (opts.title) {
          tmp.setAttribute("id", opts.title + "-stylesheet");
        }
      }

      if (tmp.styleSheet)  {
        if (isNew)  {
          document.getElementsByTagName("head")[0].appendChild(tmp); 
          tmp.styleSheet.cssText = opts.str; 
        }
        else  {
          tmp.styleSheet.cssText = tmp.styleSheet.cssText + " " + opts.str;
        }
      }
      else   {
        tmp.appendChild(document.createTextNode(opts.str));
        document.getElementsByTagName("head")[0].appendChild(tmp);
      }

      return tmp.sheet || tmp.styleSheet;
    }

/*  Not currently used
    if (opts.url) {
      if (bLink) {
        if (document.createStyleSheet)  {
          try  {
                 tmp = document.createStyleSheet(opts.url);    // IE
          }
          catch (e) { }

        }
        else  {
          tmp        = document.createElement('link');
          tmp.rel    = 'stylesheet';
          tmp.type   = 'text/css';
          tmp.media  = "all";
          tmp.href   = opts.url;

          document.getElementsByTagName("head")[0].appendChild(tmp);
          return tmp.styleSheet;
        }
      }
    }
*/
  };


  /**
    * @private
    */
  var _instance   = -1 ;      // current Tree instance id


/**
  * @ojcomponent oj.ojTree
  * @augments oj.baseComponent
  * @since 0.6
  *
  * @classdesc
  * <h3 id="treeOverview-section">
  *   JET Tree Component
  *   <a class="bookmarkable-link" title="Bookmarkable Link" href="#treeOverview-section"></a>
  * </h3>
  *
  * The ojTree component allows a user to display the hierarchical relationship between the nodes of a tree.<p>
  * The tree contents can be specified in JSON format, or by prepopulating the tree's containing <div> with
  * HTML <ul> list markup.
  * </p></br>
  * 
  * <h4 id="treeJSON-section"> JSON Node Format</h4>
  * </br>
  * Each node object typically has a <code class="prettyprint">title</code> and an
  * <code class="prettyprint">attr</code> property. Any node can be defined as a parent by supplying
  * a <code class="prettyprint">children</code> property, which is an array of more node definitions.
  * (Note that if a node has a <code class="prettyprint">children</code> property defined, but no children
  * are actually specified, then ojTree will perform lazy-loading by requesting child node data only
  * when a node is expanded for the first time - refer to <code class="prettyprint">option</code> property
  * <code class="prettyprint">data</code>.
  * <p>Example: Basic JSON Tree definition
  * <pre class="prettyprint">
  * <code>
  *[
  *   {                                    
  *     "title": "Home",
  *     "attr": {"id": "home"},
  *   },
  *   { 
  *     "title": "News",
  *     "attr": {"id": "news"}
  *   },
  *   { 
  *      "title": "Blogs",
  *      "attr": {"id": "blogs"},
  *      "children": [ {
  *                       "title": "Today",
  *                       "attr": {"id": "today"}
  *                    },
  *                    {
  *                       "title": "Yesterday",
  *                       "attr": {"id": "yesterday"}
  *                    }
  *                  ]
  *   }
  *] 
  *</code></pre>
  *</p></br>
  * Whatever attributes are defined for the <code class="prettyprint">attr</code> property are transferred
  * to the associated DOM <li> element. A <code class="prettyprint">metadata</code> attribute can also be
  * defined for arbitrary user-defined data that is to be associated with a node. (This metadata is
  * maintained within the ojTree instance, and is not represented in the DOM.)  A node's metadata can be retrieved
  * using the jQuery .data() method.
  * </p></br>Example: Expanded use of the <code class="prettyprint">attr</code> property
  * <pre class="prettyprint">
  * <code>
  *[
  *  { 
  *    "title": "Home",
  *    "attr": {
  *               "id": "home",
  *               "myattr1": "Hello",         <-- additional user-defined attributes
  *               "myattr2": "World"          <-- additional user-defined attributes 
  *            },
  *    "metadata": {                          <-- node metadata
  *                  "type": "T123",
  *                  "val": 42,
  *                  "active": true
  *                }
  *  },
  *
  *  . . .
  *]
  *</code></pre>
  *
  * </p></br>Example: Retrieving node attributes and data
  * <pre class="prettyprint">
  * <code>
  *$("#mytree).on("ojtreehover", function (ev, ui){
  *
  *  // ui.item = node
  *  // ui.item.attr("id")         -  retrieve a node attribute
  *  // ui.item.attr("myattr1")    -    ..
  *  // ui.item.data("active")     -  retrieve the "active" meta-data value from previous example
  *
  *});
  *</code></pre>
  *</p></br>
  * For flexibility, attributes can also be applied to the node's <a> element if required, by specifying
  * the node <code class="prettyprint">data</code> property as an object.
  * </p>Example: Using the data property
  * <pre class="prettyprint">
  * <code>
  *{
  *   "attr" : { "id" : "myid" },                    <-- this is set on the <li>
  *   "data" : {
  *              "attr" : {
  *                         "flags"   : "A-B",       <-- this is set on the <a>
  *                         "title" : "This is a tooltip"
  *                       }
  *             }
  *}
  *</code></pre>
  *</p></br>
  * <h4 id="treeHTML-section"> HTML Node Format</h4>
  * </br>
  * A Tree can be populated via standard HTML markup using a <ul> list structure - refer to
  * <code class="prettyprint">option</code> property <code class="prettyprint">"data"</code>.  In
  * the case where the <code class="prettyprint">"data"</code> option has not been defined, ojTree
  * will use any HTML markup defined in the Tree's containing <div>, and on startup the <ul>
  * the markup will be detached from the containing <div>, saved, and used as a template to create a new
  * tree structure in its place.  When the tree is destroyed, the original markup is restored.  Lazy loading of 
  * a node's children (when expanded) is performed if any node indicates that it has children,  
  * but its child <ul> list is left empty.
  * </p></br>Example: Using HTML markup to populate a Tree.
  * <pre class="prettyprint">
  * <code>
  * <div id="mytree">
  *    <ul>
  *       <li id="home">
  *          <a href="#"<Home>/a>
  *       </li>
  *       <li id="news">
  *          <a href="#">News</a>
  *       </li>
  *       <li id="blogs">
  *            <a href="#">Blogs</a>
  *            <ul>
  *              <li id="today">
  *                 <a href="#">Today</a>
  *              </li>
  *              <li id="yesterday">
  *                 <a href="#">Yesterday</a>
  *              </li>
  *            </ul>
  *       </li>
  * </div>
  </code></pre>
  * </p></br>
  *
  *
  *
  * <h3 id="touch-section">
  *   Touch End User Information
  *   <a class="bookmarkable-link" title="Bookmarkable Link" href="#touch-section"></a>
  * </h3>
  *
  * {@ojinclude "name":"touchDoc"}
  * <p>
  *
  * <h3 id="keyboard-section">
  *   Keyboard End User Information
  *   <a class="bookmarkable-link" title="Bookmarkable Link" href="#keyboard-section"></a>
  * </h3>
  *
  * {@ojinclude "name":"keyboardDoc"}
  *
  *
  * <br>
  * <h3 id="rtl-section">
  *   Reading direction
  *   <a class="bookmarkable-link" title="Bookmarkable Link" href="#rtl-section"></a>
  * </h3>
  * 
  * <p>The only supported way to set the reading direction (LTR or RTL) is to set the <code class="prettyprint">"dir"</code> attribute on the 
  * <code class="prettyprint"><html></code> element of the page.  As with any JET component, in the unusual case that the reading direction
  * is changed post-init, the tree must be <code class="prettyprint">refresh()</code>ed, or the page must be reloaded. 
  * 
  * <br>
  *
  * @desc Creates a JET Tree.
  * @param {Object=} options a map of option-value pairs to set on the component
  * 
  * @example <caption>Initialize the Tree with options:</caption>
  * $( ".selector" ).ojTree( {"selectionMode": "single", "data": [JSON objects]} );
  * 
  */
  oj.__registerWidget("oj.ojTree", $['oj']['baseComponent'], 
  {
     widgetEventPrefix : "oj", 
     defaultElement    : "<div>", 

     options: {
                /** Disables the tree if set to <code class="prettyprint">true</code>.
                  * 
                  * @member
                  * @name disabled
                  * @memberof oj.ojTree
                  * @instance
                  * @type {boolean}
                  * @default <code class="prettyprint">false</code>
                  * 
                  * @example <caption>Initialize the tree with the <code class="prettyprint">disabled</code> option specified:</caption>
                  * $( ".selector" ).ojTree( { "disabled": true } );
                  * 
                  * @example <caption>Get or set the <code class="prettyprint">disabled</code> option, after initialization:</caption>
                  * // getter
                  * var disabled = $( ".selector" ).ojTree( "option", "disabled" );
                  * 
                  * // setter
                  * $( ".selector" ).ojTree( "option", "disabled", true );
                  */
                  // disabled option declared in superclass, but we still want the above API doc

                /**
                  * Identifies the JET Menu that the component should launch as a context menu on right-click or
                  * <kbd>Shift-F10</kbd>. If specified, the browser's native context menu will be replaced by the
                  * specified JET Menu.
                  * 
                  * <p>To specify a JET context menu on a DOM element that is not a JET component, see the
                  * <code class="prettyprint">ojContextMenu</code> binding.  
                  * 
                  * <p>To make the page semantically accurate from the outset, applications are encouraged to specify the
                  * context menu via the standard HTML5 syntax shown in the below example.  When the component is
                  * initialized, the context menu thus specified will be set on the component.
                  *
                  * <p>When defining a contextMenu, ojTree will provide built-in behavior for "edit" style functionality
                  *  (e.g. cut/copy/paste) if the following format for menu <li> item's is used (no <a> 
                  *  elements are required):
                  * <ul><li> <li data-oj-command="oj-tree-cut" /></li>
                  *     <li> <li data-oj-command="oj-tree-copy" /></li>
                  *     <li> <li data-oj-command="oj-tree-paste" /></li>
                  *     <li> <li data-oj-command="oj-tree-remove" /></li>
                  *     <li> <li data-oj-command="oj-tree-rename" /></li>
                  * </ul>
                  * The available translated text will be applied to menu items defined this way.
                  *
                  * <p>The JET Menu should be initialized before any component using it as a context menu.
                  * 
                  * @member
                  * @name contextMenu
                  * @memberof oj.ojTree
                  * @instance
                  * @type {string | null}
                  * @default <code class="prettyprint">null</code>
                  * 
                  * @example <caption>Initialize a JET Tree with a context menu:</caption>
                  * // via recommended HTML5 syntax:
                  * <div id="myTree" contextmenu="myMenu" data-bind="ojComponent: { ... }>
                  * 
                  * // via JET initializer (less preferred) :
                  * $( ".selector" ).ojTree({ "contextMenu": "#myContextMenu"  ... } });
                  * 
                  * @example <caption>Get or set the <code class="prettyprint">contextMenu</code> option for
                  *      an ojTree after initialization:</caption>
                  * // getter
                  * var menu = $( ".selector" ).ojTree( "option", "contextMenu" );
                  * 
                  * // setter
                  * $( ".selector" ).ojTree( "option", "contextMenu", "#myContextMenu"} );
                  */

                /**
                  * Specifies whether the user is permitted to reorder the nodes within the same tree using drag and drop.</br></br>
                  * Specify an object with the property "reorder" set to <code class="prettyprint">"enable"</code> to enable
                  * reordering.  Setting the <code class="prettyprint">"reorder"</code> property to <code class="prettyprint">"disable"</code>,
                  * or omitting the <code class="prettyprint">"reorder"</code> property disables reordering support. 
                  * 
                  * @example <caption>Example: Enable drag and drop for tree node reordering</caption>
                  * dnd : (
                  *         "reorder" : "enable"
                  *       }
                  *
                  * @type {Object}
                  * @default <code class="prettyprint">{reorder:'disable'}</code>
                  * @expose
                  * @instance
                  * @memberof oj.ojTree
                  */
                dnd : {reorder:'disable'},             

                /**
                  * Specify <span class="code-caption">true</span> if expanding a node programatically should
                  * also expand its parents (i.e all parent nodes down to this node will be expanded).
                  * @type {boolean}
                  * @default <code class="prettyprint">false</code>
                  * @expose
                  * @instance
                  * @memberof oj.ojTree
                  */
                expandParents : false,

                /**
                  * Specifies whether any nodes should be initially expanded on start-up.   Specify an array
                  * of node id's, or the string <span class="code-caption">"all"</span> if all parent nodes
                  * should be expanded.  The value may optionally be specified as an empty array.
                  * @type {Array | null}
                  * @default <code class="prettyprint">null</code>
                  * @expose
                  * @instance
                  * @memberof oj.ojTree
                  */
                initExpanded  : null,

                initLoaded    : [],                  // suppressed per design review

                /** An array of node elements that are currently selected. If the array is modified
                  * by an application, the selected node status of the tree is modified to match the array
                  * (nodes may be defined as elements, jQuery wrapped nodes, or selectors pointing to the
                  * elements that should be selected).
                  * @type {Array}
                  * @default <code class="prettyprint">Array</code>
                  * @expose 
                  * @instance
                  * @memberof oj.ojTree 
                  */
                selection : [],

                /**
                  * Specifies whether selection is permitted, and whether more than one node
                  * can be selected at a time.  Values are <span class="code-caption">"single"</span>
                  * for single selection, <span class="code-caption">"multiple"</span> to allow multiple
                  * concurrent selections, and <span class="code-caption">"none"</span> to inhibit selection.
                  * @type {string}
                  * @default <code class="prettyprint">"single"</code>
                  * @expose
                  * @instance
                  * @memberof oj.ojTree
                  */
                selectionMode  :  "single" ,

                /**
                  *  Specifies what action is to be taken when a selected node's parent is collapsed.  Specify
                  *  false if nothing is to be done. Specify <span class="code-caption">"selectParent"</span>
                  *  if the node's closed parent is to be selected, or specify <span class="code-caption">"deselect"</span>
                  *  if the node is to be deselected.
                  * @type {boolean | string}
                  * @default <code class="prettyprint">false</code>
                  * @expose
                  * @instance
                  * @memberof oj.ojTree
                  */
                selectedParentCollapse  :  false ,

                /**
                  *  Specifies what action is to be taken when a node is programmatically expanded.  Specify
                  *  <span class="code-caption">true</span> if all of the node's closed parents should be opened
                  *  automatically.  If <span class="code-caption">false</span> is specified, the node is selected but will
                  *  remain invisible if its parents are currently collapsed.
                  * @type {boolean}
                  * @default <code class="prettyprint">true</code>
                  * @expose
                  * @instance
                  * @memberof oj.ojTree
                  */
                selectedParentExpand   :  true ,

                /**
                  * Specifies the action to take when a selected node is deleted.  If set to 
                  * <span class="code-caption">true</span>, its previous sibling (or parent, if no previous siblings)
                  * is selected.  If <span class="code-caption">false</span> is specified, no action is taken.
                  * @type {boolean}
                  * @default <code class="prettyprint">false</code>
                  * @expose
                  * @instance
                  * @memberof oj.ojTree
                  */
                selectPrevOnDelete     :  false ,

                /**
                  * Specifies the data source used to populate the tree. Currently supported data sources are a <code class="prettyprint">JsonTreeDataSource</code>,
                  * or json, or html.</br></br> 
                  * The general format of the <code class="prettyprint">data</code> option is one of the following:
                  *</br></br>
                  *<ul>
                  *   <li>data : oj.JsonTreeDataSource</br></br></li>
                  *   <li>data : null    (or omit) - ojTree will look at the containing <div>
                  *                       and use any existing html <ul> markup found</br></br></li>
                  *   <li>data : "  json string  "</br></br></li>
                  *   <li>data : [ array of json objects ]</br></br></li>
                  *   <li>data : "<ul><li> ...  html markup string  </ul>"</br></br></li>
                  *   <li>data : { "data" :     ... or     "ajax" :     . . .    }        // retrieve json or html</li>
                  * </ul>
                  *</br>
                  * Use of the <code class="prettyprint">"data"</code> property of the <code class="prettyprint">data</code> option,
                  * specifies that the tree is to be populated from JSON or HTML (local or remote).
                  * The <code class="prettyprint">"data"</code> object contains one of two properties:
                  * <ul>
                  *  <li>"data"</li>
                  *  <li>"ajax"</li>
                  * </ul>
                  *  An optional <code class="prettyprint">"dataType"</code> property may also be specified, which can take the
                  *  value <code class="prettyprint">"json"</code> or <code class="prettyprint">"html"</code>, and indicates
                  *  what kind of data is being returned in the <code class="prettyprint">"data"</code> or
                  *  <code class="prettyprint">"ajax"</code> method (default is "json").
                  * </ul>
                  *
                  * When <span class="code-caption">"data"</span> is specified as an object, its <span class="code-caption">"data"</span> property may be specified as a function which
                  * receives two arguments: <span class="code-caption">node</span>, and <span class="code-caption">fn</span>.
                  * </p></br>
                  * Example: Skeleton outline of a <code class="prettyprint">"data"</code> function:
                  *</br>
                  *<pre class="prettyprint">
                  *<code>
                  *data : {
                  *          "data" : function(node, fn) {
                  *                    // node  -  the jQuery wrapped node to be expanded for a lazy load,
                  *                    //          or -1 if it is the initial call to load the tree.
                  *                    // fn    -  a function to call with the JSON to be applied.
                  *
                  *                    fn( new_json_node_data ) ;   // return the JSON
                  *                   }
                  *        }
                  *</code></pre>
                  * </br>
                  * The <code class="prettyprint">"ajax"</code> property of the <code class="prettyprint">"data"</code> option
                  * allows remote JSON to be retrieved. It may be specified as an object (refer to the
                  * jQuery .ajax() settings object). If may also be specified as <code class="prettyprint">false</code> or
                  * omitted, if no AJAX operations are performed.</br></br>
                  * When specified as an object, it should contain the following two properties:
                  * <ul>
                  *  <li>type</li>
                  *  <li>url</li>
                  * </ul>
                  *<pre class="prettyprint">
                  *<code>
                  *"ajax" : {
                  *           "type": "GET",
                  *           "url":   "my_url"      // some url to the content
                  *          }
                  *</code></pre>
                  * <code class="prettyprint">"url"</code> may also be specified as a function which should return
                  * a url string:
                  *</br>
                  *<pre class="prettyprint">
                  *<code>
                  *"ajax" : {
                  *           "type" : "GET",
                  *           "url":   function (node) {
                  *                         ... return a url string ...
                  *                     }
                  *          )
                  *</code></pre>
                  * </br>
                  *  where  <span class="code-caption">node</span> is a parent node (can be used for lazy loading), or -1 to
                  *  indicate the initial tree load.
                  * </br></br>
                  *  Optionally, <span class="code-caption">success</span> and <span class="code-caption">error</span>
                  *  functions may be defined. If the <span class="code-caption">success</span> function returns a
                  *  value, it will be used to populate the tree; this can be useful if there is a need to transform
                  *  the data returned by a server at the client before it is displayed in the tree.
                  * </br></br></br>
                  *
                  * Note: to enable lazy loading of a parent node, specify that it has children but do not define them.
                  * When it is opened, data() or ajax() will be called with the node whose JSON is to be returned.</br></br>
                  *@example <caption>Example 1: Skeleton outline of success and error functions</caption>
                  *<code>
                  *"ajax": {
                  *          "type":"GET",
                  *          "url": myurl    <-- url to full tree JSON
                  *          "success" : function(data, status, obj) {
                  *                        // data   = the JSON data
                  *                        // status = "success"
                  *                        // obj    = the AJAX object.
                  *                        trace("Ajax " + status) ;
                  *                        // return the data, can transform it first if required.
                  *                        // if no return value, the data is used untransformed.
                  *          },
                  *          "error" : function(reason, feedback, obj) {
                  *                        // reason e.g. "parsererror"
                  *                        // feedback.message  e.g. "unexpected string"
                  *                        // obj    = the AJAX object.
                  *                        trace("Ajax error " + reason + " feedback=" + feedback.message) ;
                  *          },
                  * </code>
                  *
                  *@example <caption>Example 2:  Load the complete tree from locally defined JSON.</caption>
                  *<code>
                  *"data" :  [
                  *            { 
                  *             "title": "Home",
                  *             "attr": {"id": "home"},
                  *            },
                  *            { 
                  *              "title": "News",
                  *              "attr": {"id": "news"}
                  *            },
                  *            { 
                  *              "title": "Blogs",
                  *              "attr": {"id": "blogs"},
                  *              "children": [ { 
                  *                             "title": "Today",
                  *                             "attr": {"id": "today"}
                  *                            },
                  *                            { 
                  *                              "title": "Yesterday",
                  *                              "attr": {"id": "yesterday"}
                  *                            }
                  *                          ]
                  *            }
                  *          ]
                  *</code>
                  *
                  *@example <caption>Example 3:  Load the complete tree with remotely served JSON.</caption>
                  *<code>
                  *"data" : {
                  *            "ajax": {
                  *                     "type":"GET",
                  *                     "url": myurl    <-- url to full tree JSON
                  *                    }
                  *           
                  *          }  
                  *</code>
                  *
                  *@example <caption>Example 4:  Load the complete tree with remotely served JSON via a function.</caption>
                  *<code>
                  *"data" : {
                  *           
                  *           "ajax": {
                  *                     "type":"GET",
                  *                     "url": function() {
                  *                               return (a url) ;
                  *                            }
                  *                   }
                  *           
                  *          }  
                  * </code>
                  *
                  *@example <caption>Example 5:  Load a partial tree, and retrieve node data when a parent node is expanded and needs to be populated.</caption>
                  *<code>
                  *"data" : {
                  *           "ajax": {
                  *                     "type":"GET",
                  *                     "url": function(node) {
                  *                             if (node === -1) {                       // -1 indicates initial load
                  *                               return (url for for  partial json) ;   // the tree outline with parent nodes empty.
                  *                             }
                  *                             else {
                  *                               var id = node.attr("id") ;
                  *
                  *                               return (a url based on the node id to retrieve just the node children) ; 
                  *                             }
                  *                           }
                  *                   }
                  *           
                  *          }
                  *</code>
                  *
                  *@example <caption>Example 6:  Transform data received from server before passing to ojTree.</caption>
                  *<code>
                  *"data" : {
                  *           "ajax": {
                  *                     "type":"GET",
                  *                     "url": function(node) {
                  *                              . . .
                  *                            },
                  *                      "success" : function (data)  {
                  *                                    . . .    // transform the received data into node JSON format
                  *
                  *                                    return (transformed data) ;
                  *                                  },
                  *                      "error" : function () {
                  *                                   // ajax call failed.
                  *                                }
                  *                   } 
                  *           
                  *          }
                  *</code>
                  *
                  * @example <caption>Example 7:  Use own mechanism to load a partial tree and retrieve node data when a parent is expanded.</caption>
                  * <code>
                  * // Sample outline of a tree.  Note that the parent nodes "Node2" and "Node3" have
                  * // their "children" property specifed, but no children are actually defined.
                  *
                  *{
                  *  "title" : Node1",
                  *  "attr" : {"id" : "n1"}
                  *},
                  *{
                  *  "title" : Node2",
                  *  "attr" : {"id" : "n2"},
                  *  "children" : []
                  *},
                  *{
                  *  "title" : Node3",
                  *  "attr" : {"id" : "n3"},
                  *  "children" : []
                  *},
                  *
                  *
                  *"data" : {
                  *           "data": function(node, fn) {
                  *                     // node  =  the node whose children are to be retrieved
                  *                     // fn    =  the function to call with the retrieved node json 
                  *
                  *                     if (node === -1) {             // initial tree load
                  *                       fn( acquired node json for the tree) ;
                  *                     }
                  *                     else {                         // node lazy load
                  *                       var id = node.attr("id") ;   // get the node id, will be "n2" 
                  *                                                    // or "n3", in this example.  
                  *                       fn( acquired node json for the expanded node ) ;
                  *                     }
                  *                  }
                  *           
                  *          }
                  *}
                  * </code>
                  * When an <span class="code-caption">option</span> call is made to reset the <span class="code-caption">data</span> property
                  * of a tree, the application does not need to call <span class="code-caption">refresh</span>.
                  * @type {Object | Array | string | null}
                  * @default <code class="prettyprint">null</code>
                  * @expose
                  * @instance
                  * @memberof oj.ojTree
                  */
                data : null,

                /**
                 * The text to display when there are no data in the Tree. If not specified, 
                 * default text is extracted from the resource bundle.  Specify an empty string if
                 * this default behavior is not required.
                 * 
                 * @expose 
                 * @memberof oj.ojTree
                 * @instance
                 * @type {string|null}
                 * @default <code class="prettyprint">"No data"</code>
                 * 
                 * @example <caption>Initialize the tree with text set to 'no data':</caption>
                 * $( ".selector" ).ojTree({ "data":data, "emptyText": "no data" });
                 */
                emptyText: null,

                /**
                  * Specifies whether node icons are to be displayed.  Specify <span class="code-caption">true</span>
                  * to display icons, or <span class="code-caption">false </span> to suppress node icons.
                  * @type {boolean}
                  * @default <code class="prettyprint">true</code>
                  * @expose
                  * @instance
                  * @memberof oj.ojTree
                  */
                icons : true,

                /*
                 *  Specifies whether hierarchy lines between nodes are displayed.
                 *  // not exposed in V1
                 */
                //dots" : false,


                /**
                  * The <span class="code-caption">'types'</span> option allow nodes to be classified and their appearance
                  * and behavior modified.</br></br>
                  * Typical uses are to define a specific icon for a particular node, or to inhibit certain
                  * operations on a particular type of folder (e.g. the root node cannot be deleted or moved).
                  * <p>
                  * A node <span class="code-caption">type</span> has the following properties:
                  * <ul>
                  *   <li><span class="code-caption">"image"</span> -  specifies the location of the icon to be used
                  *         (optional). May also be specified as <span class="code-caption">false</span> to suppress
                  *         the image.</br></br></li>
                  *   <li><span class="code-caption">"position"</span> - position of sprite in the image in the format 
                  *        <span class="code-caption">"left top"</span>, e.g. "-36px -16px".</br>
                  *        Optional - omit if icon is not contained within a multi-sprite image.</br></br></li>
                  *   <li><span class="code-caption">method name</span> - specify a function or a
                  *         boolean. Optional.</br>  Any node operation method (that
                  *         is, takes a node as its first argument) can be redefined (e.g. <span class="code-caption">select</span>,
                  *         <span class="code-caption">expand</span>, <span class="code-caption">collapse</span>, etc). 
                  *         Alternatively, the method can be defined as <span class="code-caption">true</span> or 
                  *         <span class="code-caption">false</span> to permit or inhibit the operation, or a
                  *         function that returns a boolean value. The default value
                  *         if omitted is <span class="code-caption">true</span> (i.e. the operation is permitted).</li>
                  * </ul>
                  * In the following example, three node types have been defined: <span class="code-caption">"myroot"</span>,
                  * <span class="code-caption">"myfolder"</span>, and <span class="code-caption">"myleaf"</span>.
                  * Any node that does not have one of these types defaults its behavior to the default type
                  * (whose properties can also be redefined).  The default <span class="code-caption">"default"</span>
                  * node type has no restrictions on the operations that can be performed on the node. In the following
                  * example, a modification to the default type properties have been made.  Also, for the
                  * <span class="code-caption">"myroot"</span> node type, the standard <span class="code-caption">select</span>,
                  * <span class="code-caption">remove</span> and <span class="code-caption">move</span> operations return false
                  * which inhibts those operations.
                  * been redefined to be no-ops.
                  * @example <caption>Example 1:  Add custom appearance and node behavior.</caption>
                  * <code>
                  *"types": {
                  *            "myroot" :   {
                  *                            "image"  : baseurl + "/img/root.png",
                  *                            "select" : function() { return false; },
                  *                            "remove" : function() { return false; },
                  *                            "move" :   function() { return false; },
                  *                         },
                  *            "myfolder" : {
                  *                            "image" : baseurl + "/img/folder.png"
                  *                         },
                  *            "myleaf" :   {
                  *                           "image" : "baseurl + "/img/leaf.png"
                  *                         },
                  *            "default" : {   <-- optional redefinition of the default behavior
                  *                           "image" : "baseurl + "/img/leaf.png",
                  *                           "remove" : function() { return false; }
                  *                        }
                  *
                  *          }
                  *}
                  *</code>
                  * User-defined types are specified as an attribute of the node.  The default
                  * node type attribute is <span class="code-caption">"type"</span>, but this could be changed if desired using
                  * the <span class="code-caption">"attr"</span> property. Thus, for the node types in example 1 above, the node
                  * type attribute values in the node definitions could be set as in example 2:
                  * @example <caption>Example 2:  Using node types in the tree JSON.</caption>
                  * <code>
                  *[
                  *   {                                    
                  *     "title": "Root",
                  *     "attr": {
                  *               "id": "root",                       
                  *               "type": "myroot"                      <--- node type 
                  *             },
                  *     "children": [
                  *                   {
                  *                     "title": "Home",
                  *                     "attr": {"id": "home",
                  *                              "type": "myleaf"}      <--- node type
                  *                   },
                  *                   { 
                  *                     "title": "News",
                  *                     "attr": {
                  *                               "id": "news",
                  *                               "type": "myleaf"      <--- node type
                  *                             }
                  *                   },
                  *                   { 
                  *                     "title": "Blogs",
                  *                     "attr": {
                  *                               "id": "blogs",
                  *                               "type": "myfolder"    <--- node type
                  *                             },
                  *                     "children": [ {
                  *                                     "title": "Today",
                  *                                     "attr": {
                  *                                               "id": "today",
                  *                                               "type": "myleaf"
                  *                                             }
                  *                                   },
                  *                                   {                 <--- default node type
                  *                                     "title": "Yesterday",
                  *                                     "attr": {"id": "yesterday"}
                  *                                   }
                  *                                 ]
                  *                   }
                  *                 ] 
                  *  }
                  *]
                  *</code>
                  * As described above, the node type attribute used on the corresponding tree
                  * <li> element defaults to <span class="code-caption">"type"</span>, but this can be redefined using the <span class="code-caption">attr</span>
                  * property as in the following example:
                  * @example <caption>Example 2:  Using node types in the tree JSON.</caption>
                  * <code>
                  *"types": {
                  *           "attr" : "mytype",    <--- node type attribute is now "mytype"
                  *           "types": {
                  *                      "myroot" : {
                  *                                   "image" : . . .
                  *                                    . . .
                  *                                 }
                  *          }
                  *</code>
                  * @type {Object | null}
                  * @default <code class="prettyprint">true</code>
                  * @expose
                  * @instance
                  * @memberof oj.ojTree
                  */
                types: null,


                //---------------------------//
                //   Option Event callbacks  //
                //---------------------------//

                /**
                  * Triggered prior to an event.<p>
                  * The following events can be vetoed during <code class="prettyprint">before</code> event
                  * processing by returning </code>false</code> from the <code class="prettyprint">before</code> 
                  * event handler (omitting a return value or returning <code class="prettyprint">true</code> 
                  * permits the event processing to continue): 
                  * <code class="prettyprint">collapse</code>, <code class="prettyprint">expand</code>, 
                  * <code class="prettyprint">hover</code>, <code class="prettyprint">select</code>, 
                  * <code class="prettyprint">remove</code>, <code class="prettyprint">rename</code>.
                  *
                  * @expose 
                  * @event 
                  * @memberof oj.ojTree
                  * @instance
                  * @property {Event} event <code class="prettyprint">jQuery</code> event object
                  * @property {Object} ui Parameters
                  * @property {string} ui.func the event causing this <code class="prettyprint">before</code> event to be triggered.
                  * @property {Object} ui.item the node that is the subject of the event
                  * 
                  * @example <caption>Initialize the Tree with the <code class="prettyprint">before</code> callback specified:</caption>
                  * $( ".selector" ).ojTree({
                  *     "before": function(event, ui)  {
                  *                                       console.log("Before event " + ui.func);
                  *               }
                  * });
                  *
                  *
                  * @example <caption>Bind an event listener to the <code class="prettyprint">ojbefore</code> event:</caption>
                  * $( ".selector" ).on( "ojbefore", function( event, ui ) {
                  *                                       console.log("Before event " + ui.func);
                  *                                  });
                  */
                before : null,

                /**
                  * Triggered when a tree node is collapsed.
                  *
                  * @expose 
                  * @event 
                  * @memberof oj.ojTree
                  * @instance
                  * @property {Event} event <code class="prettyprint">jQuery</code> event object
                  * @property {Object} ui Parameters
                  * @property {Object} ui.item the node that has been collapsed
                  * 
                  * @example <caption>Initialize the Tree with the <code class="prettyprint">collapse</code> callback specified:</caption>
                  * $( ".selector" ).ojTree({
                  *     "collapse": function( event, ui ) {. . .}
                  * });
                  *
                  * @example <caption>Bind an event listener to the <code class="prettyprint">ojcollapse</code> event:</caption>
                  * $( ".selector" ).on( "ojcollapse", function(event, ui) {. . .}
                  *                    );
                  */
                collapse : null,

                /**
                  * Triggered when a tree node has been created and added to the tree.
                  *
                  * @expose 
                  * @event 
                  * @memberof oj.ojTree
                  * @instance
                  * @property {Event} event <code class="prettyprint">jQuery</code> event object
                  * @property {Object} ui Parameters
                  * @property {Object} ui.item the node that has been created
                  * 
                  * @example <caption>Initialize the Tree with the <code class="prettyprint">create</code> callback specified:</caption>
                  * $( ".selector" ).ojTree({
                  *     "create": function( event, ui ) {. . .}
                  * });
                  *
                  * @example <caption>Bind an event listener to the <code class="prettyprint">ojcreate</code> event:</caption>
                  * $( ".selector" ).on( "ojcreate", function(event, ui) {. . .}
                  *                    );
                  */
                create : null,

                /**
                  * Triggered when all nodes of a parent node, or the complete tree, have been collapsed.
                  *
                  * @expose 
                  * @event 
                  * @memberof oj.ojTree
                  * @instance
                  * @property {Event} event <code class="prettyprint">jQuery</code> event object
                  * @property {Object} ui Parameters
                  * @property {Object} ui.item the node(s) that were collapsed.
                  * @property {Object} ui.targ the node that was targeted for collapseAll, or -1 if the complete tree is collapsed.
                  * 
                  * @example <caption>Initialize the Tree with the <code class="prettyprint">collapseAll</code> callback specified:</caption>
                  * $( ".selector" ).ojTree({
                  *     "collapseAll": function( event, ui ) {. . .}
                  * });
                  *
                  * @example <caption>Bind an event listener to the <code class="prettyprint">ojcollapseall</code> event:</caption>
                  * $( ".selector" ).on( "ojcollapseall", function( event, ui ) {. . .} );
                  */
                collapseAll : null,

                /**
                  * Triggered when a tree node has been cut from the tree via the context menu.
                  *
                  * @expose 
                  * @event 
                  * @memberof oj.ojTree
                  * @instance
                  * @property {Event} event <code class="prettyprint">jQuery</code> event object
                  * @property {Object} ui Parameters
                  * @property {Object} ui.item the node that was cut
                  * 
                  * @example <caption>Initialize the Tree with the <code class="prettyprint">cut</code> callback specified:</caption>
                  * $( ".selector" ).ojTree({
                  *     "cut": function( event, ui ) {. . .}
                  * });
                  *
                  * @example <caption>Bind an event listener to the <code class="prettyprint">ojcut</code> event:</caption>
                  * $( ".selector" ).on( "ojcut", function( event, ui ) {. . .} );
                  */
                cut : null,

                /**
                  * Triggered when a tree node is no longer hovered over.
                  *
                  * @expose 
                  * @event 
                  * @memberof oj.ojTree
                  * @instance
                  * @property {Event} event <code class="prettyprint">jQuery</code> event object
                  * @property {Object} ui Parameters
                  * @property {Object} ui.item the node that is no longer hovered over
                  * 
                  * @example <caption>Initialize the Tree with the <code class="prettyprint">dehover</code> callback specified:</caption>
                  * $( ".selector" ).ojTree({
                  *     "dehover": function( event, ui ) {. . .}
                  * });
                  *
                  * @example <caption>Bind an event listener to the <code class="prettyprint">ojdehover</code> event:</caption>
                  * $( ".selector" ).on( "ojdehover", function( event, ui ) {. . .} );
                  */
                dehover : null,

                /**
                  * Triggered when a tree node has been removed.
                  *
                  * @expose 
                  * @event 
                  * @memberof oj.ojTree
                  * @instance
                  * @property {Event} event <code class="prettyprint">jQuery</code> event object
                  * @property {Object} ui Parameters
                  * @property {Object} ui.item the node that has been removed.
                  * @property {Object} ui.parent the parent of the node that was removed.
                  * @property {Object} ui.prev the previous sibling, or if ui.item is the first child of
                  *                    its parent, the parent node.
                  * 
                  * @example <caption>Initialize the Tree with the <code class="prettyprint">remove</code> callback specified:</caption>
                  * $( ".selector" ).ojTree({
                  *     "remove": function( event, ui ) {. . .}
                  * });
                  *
                  * @example <caption>Bind an event listener to the <code class="prettyprint">ojremove</code> event:</caption>
                  * $( ".selector" ).on( "ojremove", function( event, ui ) {. . .} );
                  */
                remove : null,

                /**
                  * Triggered when a tree is destroyed.
                  *
                  * @expose 
                  * @event 
                  * @memberof oj.ojTree
                  * @instance
                  * @property {Event} event <code class="prettyprint">jQuery</code> event object
                  * @property {Object} ui Parameters
                  * 
                  * @example <caption>Initialize the Tree with the <code class="prettyprint">destroy</code> callback specified:</caption>
                  * $( ".selector" ).ojTree({
                  *     "destroy": function( event, ui ) {}
                  * });
                  *
                  * @example <caption>Bind an event listener to the <code class="prettyprint">ojdestroy</code> event:</caption>
                  * $( ".selector" ).on( "ojdestroy", function( event, ui ) {} );
                  */
                destroy : null,

                /**
                  * Triggered when a tree node is expanded.
                  *
                  * @expose 
                  * @event 
                  * @memberof oj.ojTree
                  * @instance
                  * @property {Event} event <code class="prettyprint">jQuery</code> event object
                  * @property {Object} ui Parameters
                  * @property {Object} ui.item the node that has been expanded
                  * 
                  * @example <caption>Initialize the Tree with the <code class="prettyprint">expand</code> callback specified:</caption>
                  * $( ".selector" ).ojTree({
                  *     "expand": function( event, ui ) {. . .}
                  * });
                  *
                  * @example <caption>Bind an event listener to the <code class="prettyprint">ojexpand</code> event:</caption>
                  * $( ".selector" ).on( "ojexpand", function( event, ui ) {. . .} );
                  */
                expand : null,

                /**
                  * Triggered when all nodes of a parent node, or the complete tree, have been expanded.
                  *
                  * @expose 
                  * @event 
                  * @memberof oj.ojTree
                  * @instance
                  * @property {Event} event <code class="prettyprint">jQuery</code> event object
                  * @property {Object} ui Parameters
                  * @property {Object} ui.item the node(s) that were expanded.
                  * @property {Object} ui.targ the node that was targeted for expandAll, or -1 if the complete tree is collapsed.
                  * 
                  * @example <caption>Initialize the Tree with the <code class="prettyprint">expandAll</code> callback specified:</caption>
                  * $( ".selector" ).ojTree({
                  *     "expandAll": function( event, ui ) {. . .}
                  * });
                  *
                  * @example <caption>Bind an event listener to the <code class="prettyprint">ojexpandall</code> event:</caption>
                  * $( ".selector" ).on( "ojexpandall", function( event, ui ) {. . .} );
                  */
                expandAll : null,

                /**
                  * Triggered when a tree node is hovered over.
                  *
                  * @expose 
                  * @event 
                  * @memberof oj.ojTree
                  * @instance
                  * @property {Event} event <code class="prettyprint">jQuery</code> event object
                  * @property {Object} ui Parameters
                  * @property {Object} ui.item the node that is hovered over
                  * 
                  * @example <caption>Initialize the Tree with the <code class="prettyprint">hover</code> callback specified:</caption>
                  * $( ".selector" ).ojTree({
                  *     "hover": function( event, ui ) {. . .}
                  * });
                  *
                  * @example <caption>Bind an event listener to the <code class="prettyprint">ojhover</code> event:</caption>
                  * $( ".selector" ).on( "ojhover", function( event, ui ) {. . .} );
                  */
                hover : null,

                /**
                  * Triggered when a tree has been loaded and the node data has been applied.
                  *
                  * @expose 
                  * @event 
                  * @memberof oj.ojTree
                  * @instance
                  * @property {Event} event <code class="prettyprint">jQuery</code> event object
                  * @property {Object} ui Parameters
                  * 
                  * @example <caption>Initialize the Tree with the <code class="prettyprint">loaded</code> callback specified:</caption>
                  * $( ".selector" ).ojTree({
                  *     "loaded": function( event, ui ) {}
                  * });
                  *
                  * @example <caption>Bind an event listener to the <code class="prettyprint">ojloaded</code> event:</caption>
                  * $( ".selector" ).on( "ojloaded", function( event, ui ) {} );
                  */
                loaded : null,

                /**
                  * Triggered when a tree node has been moved within the tree.
                  *
                  * @expose 
                  * @event 
                  * @memberof oj.ojTree
                  * @instance
                  * @property {Event} event <code class="prettyprint">jQuery</code> event object
                  * @property {Object} ui Parameters
                  * @property {Object} ui.item the node that was moved
                  * @property {string} ui.position the moved node's new position relative to the reference node.
                  *                    Can be "before", "after", or "inside".
                  * @property {Object} ui.reference the reference node that ui.position refers to.
                  * 
                  * @example <caption>Initialize the Tree with the <code class="prettyprint">move</code> callback specified:</caption>
                  * $( ".selector" ).ojTree({
                  *     "move": function(event, ui) {. . .}
                  * });
                  *
                  * @example <caption>Bind an event listener to the <code class="prettyprint">ojmove</code> event:</caption>
                  * $( ".selector" ).on( "ojmove", function(event, ui) {. . .} );
                  */
                move : null,

                /**
                  * Triggered whenever a supported component option changes, whether due to user interaction
                  * or programmatic intervention.  If the new value is the same as the previous value, no
                  * event will be fired.
                  *
                  * Currently there is one supported option, <code class="prettyprint">"selection"</code>, which
                  * reflects the current selection status of the Tree.  Additional options may be supported in
                  * the future, so listeners should verify which option is changing before taking any action.
                  *
                  * @expose
                  * @event
                  * @memberof oj.ojTree
                  * @instance
                  * @property {Event} event <code class="prettyprint">jQuery</code> event object
                  * @property {Object} ui Parameters
                  * @property {string} ui.option the name of the option that is changing
                  * @property {Array}  ui.previousValue the previous value of the option
                  * @property {Array}  ui.value the current value of the option
                  * @property {Object} ui.optionMetadata information about the option that is changing
                  * @property {string} ui.optionMetadata.writeback <code class="prettyprint">"shouldWrite"</code> or
                  *           <code class="prettyprint">"shouldNotWrite"</code>.  For use by the JET writeback mechanism.
                  *
                  */
                 optionChange: null,

                /**
                  * Triggered when a tree node has been pasted into the tree via the context menu.
                  *
                  * @expose 
                  * @event 
                  * @memberof oj.ojTree
                  * @instance
                  * @property {Event} event <code class="prettyprint">jQuery</code> event object
                  * @property {Object} ui Parameters
                  * @property {Object} ui.item the node that was pasted
                  * 
                  * @example <caption>Initialize the Tree with the <code class="prettyprint">paste</code> callback specified:</caption>
                  * $( ".selector" ).ojTree({
                  *     "paste": function( event, ui ) {. . .}
                  * });
                  *
                  * @example <caption>Bind an event listener to the <code class="prettyprint">ojpaste</code> event:</caption>
                  * $( ".selector" ).on( "ojpaste", function( event, ui ) {. . .} );
                  */
                paste : null,

                /**
                  * Triggered when a tree node, or the complete tree, has been refreshed.
                  *
                  * @expose 
                  * @event 
                  * @memberof oj.ojTree
                  * @instance
                  * @property {Event} event <code class="prettyprint">jQuery</code> event object
                  * @property {Object} ui Parameters
                  * @property {Object} ui.item the node that has been refreshed, or -1 if the whole tree has been refreshed.
                  * 
                  * @example <caption>Initialize the Tree with the <code class="prettyprint">refresh</code> callback specified:</caption>
                  * $( ".selector" ).ojTree({
                  *     "refresh": function( event, ui ) {. . .}
                  * });
                  *
                  * @example <caption>Bind an event listener to the <code class="prettyprint">ojrefresh</code> event:</caption>
                  * $( ".selector" ).on( "ojrefresh", function( event, ui ) {. . .} );
                  */
                refresh : null,

                /**
                  * Triggered when a tree node has been renamed.
                  *
                  * @expose 
                  * @event 
                  * @memberof oj.ojTree
                  * @instance
                  * @property {Event} event <code class="prettyprint">jQuery</code> event object
                  * @property {Object} ui Parameters
                  * @property {Object} ui.item the node that has been renamed
                  * @property {string} ui.title the new node text title.
                  * @property {string} ui.prevTitle the node title prior to the rename.
                  * 
                  * @example <caption>Initialize the Tree with the <code class="prettyprint">rename</code> callback specified:</caption>
                  * $( ".selector" ).ojTree({
                  *     "rename": function( event, ui ) {. . .}
                  * });
                  *
                  * @example <caption>Bind an event listener to the <code class="prettyprint">ojrename</code> event:</caption>
                  * $( ".selector" ).on( "ojrename", function( event, ui ) {. . .} );
                  */
                rename : null

     },   // end options


     /*---------------*/
     /* Public API's  */
     /*---------------*/

     /** Collapses an expanded node, so that its children are not visible.  Triggers a "collapse" event.
       *
       * @param {HTMLElement | Object | string} node - Can be a DOM element, a jQuery wrapped node, or a selector
       *                        pointing to the element to be collapsed.
       * @param {boolean=} skipAnim - Set to true to suppress node collapse animation (if a non-zero duration
       *                              is defined or defaulted). Default is false.
       * @expose 
       * @public
       * @instance
       * @memberof oj.ojTree 
       */
     collapse : function(node, skipAnim)
     {
        skipAnim = skipAnim || false ;

        // Get css value $treeAnimationDuration.  This is needed because if the value is zero
        // and a transition is started, browsers do not apparently fire the "transitionend"
        // event and so we don't get the opportunity to emit a collapse event.

        var dur  = skipAnim? 0 : this._animDuration ;

        node = this._getNode(node);

        if (!node.length || node === -1 || !node.hasClass(OJ_EXPANDED) ||
                            this._data.core.locked || node.hasClass(OJ_DISABLED))  {
          return false;
        }

        var rslt = this._emitEvent({"obj" : node, "func" : "collapse"}, "before") ;
        if (typeof rslt == "boolean" && (!rslt)) {
          return ;
        }

//      if (skipAnim || dur)  {
//        node.children("ul").attr("style","display:block !important");
//      }

        node.removeClass(OJ_EXPANDED).addClass(OJ_COLLAPSED).attr(WA_EXPANDED, "false");
        $(node.children()[0]).removeClass(OJ_SELECTED).addClass("oj-default") ;

        if ((!skipAnim) && dur ) {
          this._slide(node, true) ;     // slideUp
        }
        else  {
          this._transitionEnd($(node.children("UL")[0]), node) ;
        }
     },

    /**
      *  @private
      */
     _slide : function(node, bSlideUp)
     {
        var $ul, hNow, hFinal ;

        $ul = $(node.children("ul")) ;

        // For the windows safari platform only we have a problem setting "transitionend" events,
        // the transition end event handler does not get called. Also For the iPad, the transition
        // end handler is called multiple times for some unknown reason.
        // ul.on("transitionend", this._proxyTransitionEndHandler) ;    // doesn't work!

        if (this._isSafari) {
          $ul[0].addEventListener("webkitTransitionEnd", this._proxyTransitionEndHandler);
        }
        else {
//          $ul.on("transitionend", this._proxyTransitionEndHandler) ;
          $ul[0].addEventListener("transitionend", this._proxyTransitionEndHandler);
        }

        if (bSlideUp) {
          hNow   = $ul[0].offsetHeight ;        // collapse
          hFinal = 0 ;
        }
        else
        {                                      // expand
          hNow   = 0 ;
          hFinal = this._getElemHeight($ul[0]) ;
        }

        $ul.css('max-height', hNow + "px") ;       // set the current height
        $ul.css('overflow', 'hidden') ; 

        if (! bSlideUp) {
          $ul[0].style.display = 'block' ;
        }

        setTimeout(function() {
          $ul.addClass("oj-tree-transition");       // enable transitioning on max-height
          $ul.css('max-height', hFinal + "px") ;    // start the transition
        }, 20) ;

     },


     /**
       * Compute the height of the ul element
       * @private
       */
     _getElemHeight : function( el)
     {
       var parent, nextSib, h, disp,
           div = $("<div style='position: absolute'>") ;

       parent  = el.parentNode;
       nextSib = el.nextSibling;
       parent.removeChild(el);

       disp = el.style.display ;                   // note display
       el.style.display = "block";                 // set - it might be 'none'
       div.append(el) ;                            //@HTMLUpdateOK
       this._$container[0].appendChild(div[0]) ;

       h = (el.offsetHeight || el.scrollHeight) ;
     
       // Restore DOM
       el.style.display = disp;                    // reset orig display
       this._$container[0].removeChild(div[0]) ;
       div[0].removeChild(el) ;
       if (nextSib) {
         parent.insertBefore(el, nextSib);         //@HTMLUpdateOK
       }
       else {
          parent.appendChild(el);
       }

//     return h ;
       return h + 10;    // TDO why is this a few pixels too little.  It causes
                         // a small jump at the end of the transition when the
                         // ul style is set to 'block'
     }, 


     /**
       *  @private
       */
     _transitionEndHandler : function (e)
     {
        var $ul  = $(e.target) ;
        var node = $ul.closest("li") ;

        // For the windows safari platform only we have a problem with "transitionend" events,
        // (see _slide() where the event listener is set up.  This code matches the routine in _slide().
        // $ul.off("transitionend", this._proxyTransitionEndHandler) ;    // doesn't work

        if (this._isSafari) {
          $ul[0].removeEventListener("webkitTransitionEnd", this._proxyTransitionEndHandler);
        }
        else {
//          $ul.on("transitionend", this._proxyTransitionEndHandler) ;
          $ul[0].removeEventListener("transitionend", this._proxyTransitionEndHandler);
        }

        $ul.removeClass("oj-tree-transition") ;
        this._transitionEnd($ul, node) ;
     },

    /**
      *  Common code called after a collapse/expand whether there was a transition or not.
      *  @private
      */
     _transitionEnd : function($ul, node)
     {
        if (node.hasClass(OJ_COLLAPSED)) {                  // end collapse
          $ul[0].style.display = "none";
$ul.css('max-height', '') ;   //JRM
          this._emitEvent({ "obj" : node }, "collapse");
          this["after_close"](node);
        }
        else {                                              // end expand
          $ul[0].style.display = "block";
          $ul.css('max-height', '') ;
//        $ul.css('overflow', 'hidden') ;        // from _expand()  
          this._emitEvent({ "obj" : node }, "expand");
          this["after_open"](node);
        }
     },


     /** Collapses a node and all its descendants.  Triggers a "collapseall" event.
       * @param {HTMLElement | Object | string} node - Can be a DOM element, a jQuery wrapped node, or a
       *                       selector pointing to the element whose descendants are to be collapsed. 
       *                       If omitted , or set to -1, all nodes in the tree are collapsed.
       * @param {boolean=} anim - Set to true (or omit) if all nodes are to be collapsed with animation
       *                          (if a non-zero duration is defined or defaulted). Default is true.
       * @expose 
       * @public
       * @instance
       * @memberof oj.ojTree 
     */
     collapseAll : function(node, anim)
     {
        var origTarg = node? node : -1 ;
        var _this = this;

        if (this._data.core.locked) {
          return ;
        }

        node = node ? this._getNode(node) : this._$container;
        if (node && origTarg !== -1) {
          origTarg = node ;
        }
        if (!node || origTarg === -1)  {
          node = this._$container_ul;
        }

        if (node.hasClass(OJ_DISABLED)) {
          return ;
        }

        var subject ;
        if (origTarg !== -1 && this["isExpanded"](node)) {
          subject = node[0] ;
        }

        var objs  =  node.find("li.oj-expanded") ;     // find child nodes that are open
        if (objs.length)  {
          objs.each(function ()  {
                        _this["collapse"](this, !anim);
                    });
        }

        if (subject) {                               // if subject node is also expanded
          this["collapse"](subject, !anim) ;         // include it in the list
          objs.splice(0, 0, subject) ;
        }

        if (objs.length) {
          this._emitEvent({"obj" : objs, "targ" : origTarg }, "collapseAll");
        }
     },


     /** Expands a collapsed parent node, so that its children are visible. Triggers an "expand" event.
       * @param {HTMLElement | Object | string} node - Can be a DOM element, a jQuery wrapped node, 
       *                        or a selector pointing to the element to be expanded.
       * @param {boolean=} skipAnim - Set to true to suppress node expansion animation (if a
       *                        non-zero duration is defined or defaulted). Default is false.
       * @expose 
       * @public
       * @instance
       * @memberof oj.ojTree 
       */
     expand : function(node, skipAnim)
     {
        this._expand(node, false, skipAnim) ;
     },

     /** May be used as a getter of setter.  If no argument is supplied, the method returns an
       * array of nodes currently expanded. (An empty array implies that no nodes are expanded.)  
       * If an array of nodes is supplied as an argument, the specified nodes are expanded.
       * @param {Array=} nodes - Omit to use as a getter, or specify an array of nodes to be
       *                 expanded.  Nodes may be defined as elements, id strings, jQuery wrapped nodes, or
       *                 selectors pointing to the elements to be expanded.
       * @param {boolean=} skipAnim - Set to true to suppress node expansion animation (if a non-zero
       *                        duration is defined or defaulted). Default is false.


       * @return {Object | null} A jQuery wrapped array of nodes if used as a getter, else null
       *                        if used as a setter.
       * @expose 
       * @public
       * @instance
       * @memberof oj.ojTree 
       */
     expanded : function(nodes, skipAnim)
     {
        var exlr,
            exlen,
            _this = this;

        if (nodes && $.type(nodes) === "array")  {    // setter

          if (this._data.core.locked) {
            return null ;
          }

          exlen = nodes.length ;
          $.each(nodes, function (i, val) {
                          _this._expand(val, false, skipAnim); 
          });
          return null ;
        }
        else  {     // getter

          nodes = this._$container_ul.find("li.oj-expanded");
          exlen  = nodes.length ;
          exlr   = [] ;
          for (var n = 0; n < exlen; n++)  {
            exlr.push(nodes[n]);
          };
          return $(exlr) ;
        }
     },


     /** Expands a node and all its descendants.  Triggers an "expandall" event.
       * @param {HTMLElement | Object | string} node - Can be a DOM element, a jQuery wrapped node,
       *                        or a selector pointing to the element whose descendants are to be expanded.
       *                        If omitted , or set to -1, all nodes in the tree are expanded.
       * @param {boolean=} anim - Set to true (or omit) if all nodes are to expanded with animation
       *                        (if a non-zero duration is defined or defaulted). Default is true.
       * @expose 
       * @public
       * @instance
       * @memberof oj.ojTree 
       */
     expandAll : function(node, anim)
     {
        this._expandAll(node, anim) ;
     },

     /** Expands a node if collapsed, or collapses a node if expanded. Triggers an "expand" or "collapse" event.
       * @param {HTMLElement | Object | string} node - Can be a DOM element, a jQuery wrapped node, 
       *                        or a selector pointing to the element to be expanded/collapsed.
       * @param {boolean=} skipAnim - Set to true to suppress node expand/collapse animation (if a
       *                        non-zero duration is defined or defaulted). Default is false.
       * @expose 
       * @public
       * @instance
       * @memberof oj.ojTree 
       */
     toggleExpand : function (node, skipAnim)
     {
        node = this._getNode(node);
        if ((! node) || (! node.length) || (node === -1)) {
          return ;
        }
        if (node.hasClass(OJ_DISABLED) || this._data.core.locked) {
          return ;
        }

        if (node.hasClass(OJ_COLLAPSED))  {
          return this["expand"](node, skipAnim);
        }
        if (node.hasClass(OJ_EXPANDED))  {
          return this["collapse"](node, skipAnim);
        }
     },


     /** Deselects a node. Triggers an "optionChange" event for options property "selection".
       * @param {HTMLElement | Object | string} node - Can be a DOM element, a jQuery wrapped node, 
       *                        or a selector pointing to the element to be deselected.
       * @expose 
       * @public
       * @instance
       * @memberof oj.ojTree 
       */
     deselect : function (node)
     {
        var prevSelections =  this.options["selection"].slice(0) ;  // make new array for optionChange event

        this._deselect(node) ;
        this._fireOptionChangeEvent("selection", prevSelections, null, null) ;
     },


     /** Deselects all selected nodes. If optional argument "context" is specified, only the selected
       * nodes within that context will be selected. Triggers an "optionChange" event for options property
       * "selection".
       * @param {HTMLElement | Object | string=} context - Can be a DOM element, a jQuery wrapped node, 
       *                        or a selector pointing to an element within the tree.
       * @expose 
       * @public
       * @instance
       * @memberof oj.ojTree 
       */
     deselectAll : function(context)
     {
       if (this._data.core.locked) {
         return ;
       }

       var prevSelections =  this.options["selection"].slice(0) ;  // make new array for optionChange event

       this._deselectAll(context) ;
       this._fireOptionChangeEvent("selection", prevSelections, null, null) ;
     },

     /** Selects a node. Triggers an "optionChange event for options property "selection".
       * @param {HTMLElement | Object | string} node - Can be a DOM element, a jQuery wrapped node, 
       *                       or a selector pointing to the element to be selected.
       * @expose 
       * @public
       * @instance
       * @memberof oj.ojTree 
       */
     select : function (node)
     {
        this._select(node, true) ;
     },


     /** Selects a node if deselected, or deselects a node if selected. Triggers an "optionChange" 
       * event for options property "selection".
       * 
       * @param {HTMLElement | Object | string} node - Can be a DOM element, a jQuery wrapped node, 
       *                        or a selector pointing to the element to be expanded/collapsed.
       * @expose 
       * @public
       * @instance
       * @memberof oj.ojTree 
       */
     toggleSelect : function (node)
     {
        node = this._getNode(node) ;
        if (! node.length)  {
           return false;
        }

        if (node.hasClass(OJ_DISABLED) || this._data.core.locked) {
          return false ;
        }

        if (this["isSelected"](node))  {
          this["deselect"](node); 
        }
        else  {
          this._select(node, true);
        }
        return true ;         // selection was toggled
     },

     /** Returns true if the node is collapsed, else false.
       *
       * @param {HTMLElement | Object | string} node - Can be a DOM element, a jQuery wrapped node, 
       *                        or a selector pointing to the element.
       * @return {boolean} true if the node is collapsed, else false.
       * @expose 
       * @public
       * @instance
       * @memberof oj.ojTree 
       */
     isCollapsed : function(node)
     {
       node = this._getNode(node);
       return node && node !== -1 && node.hasClass(OJ_COLLAPSED);
     },

     /** Returns true if the node is expanded, else false.
       *
       * @param {HTMLElement | Object | string} node - Can be a DOM element, a jQuery wrapped node, 
       *                        or a selector pointing to the element.
       * @return {boolean} true if the node is expanded,  else false.
       * @expose 
       * @public
       * @instance
       * @memberof oj.ojTree 
       */
     isExpanded : function(node)
     {
       node = this._getNode(node);
       return node && node !== -1 && node.hasClass(OJ_EXPANDED);
     },

     /** Returns true if the node is a leaf node (that is, it has no children), else false.
       *
       * @param {HTMLElement | Object | string} node - Can be a DOM element, a jQuery wrapped node, 
       *                        or a selector pointing to the element.
       * @return {boolean} true if the node is a leaf node, else false.
       * @expose 
       * @public
       * @instance
       * @memberof oj.ojTree 
       */
     isLeaf : function(node)
     {
       return this._isLeaf(node) ;
     },

     /** Returns true if the node is selected, else false.
       *
       * @param {HTMLElement | Object | string} node - Can be a DOM element, a jQuery wrapped node, 
       *                        or a selector pointing to the element.
       * @return {boolean} true if the node is selected,  else false.
       * @expose 
       * @public
       * @instance
       * @memberof oj.ojTree 
       */
     isSelected : function(node)
     {
       var n = this._getNode(node) ;
       var r = false ;

       if (n && n.length && this._data.ui.selected) {
         r = (this._data.ui.selected.index(n) >= 0);
       }
       return  r ;
     },


     /** Creates a new node and adds it to the tree.  Triggers a "create" event.
       *
       * @param {HTMLElement | Object | string} refnode - specifies the node that the new node will be
       *                       placed in, or next to, depending on the "position" argument. Can be a
       *                       DOM element, a jQuery wrapped node, or a selector pointing to the element.
       *                       If there is no reference node (because the tree is empty), specify null or undefined
       *                       (or -1).
       * @param {string | number} position - specifies the position of the newly created node in relation
       *                       to the "refnode" specified by the first argument.  Can be a string : "before",
       *                       "after", "inside", "first", "last", or a zero-based index to position the
       *                       new node at a specific point among the children of "refnode".
       * @param {Object} data - An object literal containing data to create a new node.  The object properties
       *                       are the same as for defining a JSON node:<br>
       *                       "attr" - an object of attribute name/value pairs (at least an "id" property should
       *                       be defined).<br>
       *                       "title" - a string used for the visible text of the node.<br><br>
       * <code>
       * var new Node = { "title" : "My Title", "attr" : { "id": "myid" } };
       * </code>
       * @return {Object} Returns the jQuery wrapped node created from the 'data' argument.
       * @expose 
       * @public
       * @instance
       * @memberof oj.ojTree 
       */
     create : function(refnode, position, data)
     {
        return this._createNode(refnode, position, data);
     },

     /**  Removes a node. Triggers a "remove" event.
       *
       *  @param {HTMLElement | Object | string} node - Can be a DOM element, a jQuery wrapped node, 
       *                        or a selector pointing to the element.
       *  @return {Object | boolean}   The jQuery wrapped node used as an argument.
       *  @expose 
       *  @public
       *  @instance
       *  @memberof oj.ojTree 
       */ 
     remove : function(node)
     {
       node = this._getNode(node);
       if (!node.length) {
         return false;
       }

       if (node.hasClass(OJ_DISABLED) || this._data.core.locked) {
         return false;
       }

       var rslt = this._emitEvent({"obj" : node, "func" : "remove"}, "before") ;
       if (typeof rslt == "boolean" && (!rslt)) {
         return false ;
       }

       this.__rollback();
       var p    = this._getParent(node),
           prev = $([]),
           t = this,
           sib = this._getPrev(node) ;

       node.each(function () {
           prev = prev.add(t._getPrev(this));
       });

       node = node.detach();

       if (p !== -1 && p.find("> ul > li").length === 0)  {
         p.removeClass("oj-expanded oj-collapsed")
          .addClass(OJT_LEAF)
          .removeAttr(WA_EXPANDED);
       }

       this._cleanNode(p);
       this._emitEvent({ "obj" : node, "prev" : sib, "parent" : p }, "remove");

       return node ;
     },


     /** Returns the title of the specified node
       *
       * @param {HTMLElement | Object | string} node - Can be a DOM element, a jQuery wrapped node, 
       *                       or a selector pointing to the element.
       * @return {string | boolean} The text string title of the node.
       * @expose 
       * @public
       * @instance
       * @memberof oj.ojTree 
       */
     getText : function(node)
     {
       node = this._getNode(node) ;
       if (!node.length) {
          return false;
       }

       node = node.children("a:eq(0)");
       node = node.find("span:eq(0)") ;
       return node[0].textContent;

     },

     /** Renames a node title.
       *
       * @param {HTMLElement | Object | string} node - Can be a DOM element, a jQuery wrapped node, 
       *                        or a selector pointing to the element.
       * @param {string=} text - The new text string.
       * @expose 
       * @public
       * @instance
       * @memberof oj.ojTree 
       */
     rename : function(node, text)
     {
       this._rename_node(node, text) ;
     },

     /** Sets the specifed node as the current node of interest (e.g. a mouse-over).  Triggers a "hover" event.
       *
       * @param {HTMLElement | Object | string} node - Can be a DOM element, a jQuery wrapped node, 
       *                        or a selector pointing to the element.
       * @expose 
       * @public
       * @instance
       * @memberof oj.ojTree 
       */
     hover : function(node)
     {
       if (this._data.menu.activenode) {     //TDO. why is this needed for shift-f10 on a node
         return ;                            // near the bottom. A bogus mousenter for a node
       }                                     // near the middle is received

       node = this._getNode(node);
       if (! node.length)  {
         return;
       }
       if (node.hasClass(OJ_DISABLED) || this._data.core.locked) {
         return ;
       }
       if (node.hasClass(OJ_HOVER))  {
         return ;
       }

       var rslt = this._emitEvent({"obj" : node, "func" : "hover"}, "before") ;
       if (typeof rslt == "boolean" && (!rslt)) {
         return ;
       }

       //if(this.data.ui.hovered && node.get(0) === this.data.ui.hovered.get(0)) { return; }
       if (! node.hasClass(OJ_HOVER))  {
          this["dehover"]();
       }

       this._data.ui.hovered = node.children("a").addClass(OJ_HOVER).parent();
       this._$container_ul.attr(WA_ACTIVEDESCENDANT, this._data.ui.hovered.attr("id")) ;
       this._fix_scroll(node);
       this._emitEvent({ "obj" : node }, "hover");
     },


     /** Removes the "hover" state of the currently hovered (i.e. active) node.  Triggers a "dehover" event.
       *
       * @expose 
       * @public
       * @instance
       * @memberof oj.ojTree 
       */
     dehover : function()
     {
       if (this._data.menu.activenode) {     //TDO. why is this needed for shift-f10 on a node
         return ;                            // near the bottom. A bogus mousenter for a node
       }                                     // near the middle is received

       var obj = this._data.ui.hovered,
           p;

       if (!obj || !obj.length) {
         return;
       }
       if (obj.hasClass(OJ_DISABLED) || this._data.core.locked) {
         return ;
       }

       p = obj.find("a.oj-hover") ;
       if (! p.length) {
         p = this._$container_ul.find("a.oj-hover") ;    // final check in case we get out of sync?
         if (! p.length) {
           return ;                                      // no hover, ignore.
         }
       }
       p = p.removeClass(OJ_HOVER).parent();
       this._$container_ul.removeAttr(WA_ACTIVEDESCENDANT) ;

//       if (this._data.ui.hovered[0] === p[0]) { 
         this._data.ui.hovered = null;
//       }

       if (obj.attr("id") != undefined) {
         this._emitEvent({ "obj" : obj }, "dehover");
       }
     },

     /** Returns the full path to a node, either as an array of ID's or node names, depending on
       * the value of the "idMode" argument.<p>
       * e.g. Given a node with Id 'Node1' at the root level, with a child 'Node2' which has a child 'Node3',
       * then:<p>
       * <span class="code-caption">$tree.ojTree("getPath", "#Node3", true) ;</span>
       * <p>
       * will return:<p>
       * <span class="code-caption">["Node1", "Node2", "Node3"]</span>
       * <br>
       * @param {HTMLElement | Object | string} node - Can be a DOM element, a jQuery wrapped node, 
       *                       or a selector pointing to the element.
       * @param {boolean=} idMode - Set to true (or omit) to return ID's from the node attribute
       *                       "id"), or false to return the names (i.e. text titles).  Default is true.
       *
       * @return {Array | boolean} An array of node ID's or names.
       * @expose 
       * @public
       * @instance
       * @memberof oj.ojTree 
       */
     getPath : function(node, idMode)
     {
        var p     = [],
            _this = this;

        node = this._getNode(node);
        if (node === -1 || !node || !node.length)  {
          return false;
        }

        node.parentsUntil(".oj-tree", "li").each(function () {
               p.push( idMode ? this.id : _this["getText"](this) );
        });

        p.reverse();
        p.push( idMode ? node.attr("id") : this["getText"](node) );

        return p;
      },

     /** Returns the jQuery wrapped top outer <ul> element of the tree.
       *
       * @return {Object} The jQuery wrapped <ul> element of the tree.
       * @expose 
       * @public
       * @instance
       * @memberof oj.ojTree 
       */
     getRoot : function()
     {
        return this._$container.children("ul:eq(0)");    // return the top <ul>
     },

     /**  Refreshes the tree or a node.
       *
       *  @param {HTMLElement | Object | string | number=} node - If -1 is specified (or the argument is omitted),
       *                   the whole tree is refreshed.  Alternatively, a specific node to be refreshed can be
       *                   supplied. Can be a DOM element, a jQuery wrapped node, or a selector pointing to the element.
       *  @expose 
       *  @public
       *  @instance
       *  @memberof oj.ojTree 
       */
     refresh : function(node)
     {
       this._super() ;

        if (this._data.core.locked) {
          return ;
        }

       this._refresh(node? node : -1) ;
     },


     /**  Moves (or copies) a node within a tree, or from one tree to a different tree.
       *
       *  @param {HTMLElement | Object | string | number} node  The node to be moved. Can be a DOM element,
       *            a jQuery wrapped node, or a selector pointing to the element.
       *  @param {HTMLElement | Object | string | number} refnode  The reference node for the move (see "position"
       *            argument below). Can be a DOM element, a jQuery wrapped node, or a selector pointing to the
       *            element. If the receiving tree ie empty and there can be no reference node, null or undefined
       *            (or -1) may be specified.
       *  @param {string | number} position  The position of the moved node relative to the reference node refnode.
       *            Can be "before", "after", "inside", "first", "last", or the zero-based index to position the node at a
       *            specific point among the reference node's current children.
       *  @param {boolean=} iscopy  Omit or specify false for a move operation, or true for a copy.
       *  @expose 
       *  @public
       *  @instance
       *  @memberof oj.ojTree 
       */
     move : function(node, refnode, position, iscopy)
     {
        this._moveNode(node, refnode, position, iscopy);
     },

     /**
       *  Returns the user classified node type applied to the node in the
       *  <span class="code-caption">"types"</span> option. 
       *  @return {string |boolean}  The node's type. If no types have been defined in the tree options,
       *                             false is returned.
       *  @expose 
       *  @public
       *  @instance
       *  @memberof oj.ojTree 
       */
     getType : function(node)
     {
         return this._getType(node) ;
     },

     /**
       *  Sets the "type" attribute of the node using a type defined in the 
       * <span class="code-caption">"types"</span> option.
       *  @param {HTMLElement | Object | string} node - Can be a DOM element, a jQuery wrapped node, 
       *                       or a selector pointing to the element.
       *  @param {string} str - The type. 
       *  @return {boolean}  true if the change was successful, else false. 
       *  @expose 
       *  @public
       *  @instance
       *  @memberof oj.ojTree 
       */
     setType : function(node, str)
     {
         return this._setType(node, str) ;
     },

     /**
       * Returns the subcomponent node element represented by the locator object subId property.</br>
       * 
       * @expose 
       * @public
       * @instance
       * @memberof oj.ojTree 
       * @param {Object} locator An Object containing at minimum a "subId" property whose value is a string.<p>
       * The general format of a subId string is:       <span class="code-caption">"oj-tree-node['node id']['request']"</span> 
       * <table style="border-collapse:collapse;border:1px solid"><tr style="background-color:#eee"><th>Request</th><th>Description</th></tr>
       *  <tr><td><em>"icon"</em></td><td>Returns the <ins> element for the node icon.</td></tr>
       *  <tr><td><em>"disclosure"</em></td><td>Returns the <ins> element for the disclosure (expand/collapse)
       *                                           icon of a parent node.</td></tr>
       *  <tr><td><em>"link"</em></td><td>Returns the <a> element for the node. </td></tr>
       *  <tr><td><em>"title"</em></td><td>Returns the <span> element for the node's title text. </td></tr>
       * </table>
       * @return {Element|null} the subcomponent element located by the subId string passed in locator, or null if not found.<p>
       */
     getNodeBySubId: function(locator)
     {
        if (! locator)  {
          return this.element ? this.element[0] : null;
        }

        return this._processSubId(locator)
     },

     /**
       * Returns the parent node of the node specified.
       * @param {HTMLElement | Object | string} node - Can be a DOM element, a jQuery wrapped node, 
       *                        or a selector pointing to the element.
       * @return {Object | null}   The jQuery wrapped parent node, or null if <span class="code-caption">node</span>
                                   is a top level node.
       * @expose 
       * @public
       * @instance
       * @memberof oj.ojTree 
       */
     getParent : function (node)
     {
        var l = this._getParent(node) ;
        return (l && l.length > 0)? l : null ;
     },

     /**
       * Returns the previous sibling of the node specified.
       * @param {HTMLElement | Object | string} node - Can be a DOM element, a jQuery wrapped node, 
       *                        or a selector pointing to the element.
       * @return {Object | null}   The jQuery wrapped sibling node, or null if there is no previous sibling.
       * @expose 
       * @public
       * @instance
       * @memberof oj.ojTree 
       */
     getPrevSibling : function (node)
     {
        var l = this._getPrev(node, true) ;
        return  (l && l.length > 0)? l : null ;
     },

     /**
       * Returns the next sibling of the node specified.
       * @param {HTMLElement | Object | string} node - Can be a DOM element, a jQuery wrapped node, 
       *                        or a selector pointing to the element.
       * @return {Object | null}   The jQuery wrapped sibling node, or null if there is no next sibling.
       * @expose 
       * @public
       * @instance
       * @memberof oj.ojTree 
       */
     getNextSibling : function (node)
     {
        var l = this._getNext(node, true) ;
        return (l && l.length>  0)? l : null ;
     },

     /**
       * Returns the children of the node specified.
       * @param {HTMLElement | Object | string | number} node - Can be a DOM element, a jQuery wrapped node, 
       *                        or a selector pointing to the element. May also be specified as -1 or omitted to
       *                        indicate the tree, in which case the top level children of the tree are returned.
       * @return {Object | null}   The jQuery wrapped array of child nodes, or null if there are no children.
       * @expose 
       * @public
       * @instance
       * @memberof oj.ojTree 
       */
     getChildren : function (node)
     {
        node = node? node : -1 ;
        var l = this._getChildren(node) ;
        return (l && l.length > 0)? l : null ;
     },


     //-----------------------------------------//
     //     Internal API's and data             //
     //-----------------------------------------//

     /**
       *  Called the first time the widget is called on an element.
       *  @private
       */
     _ComponentCreate: function ()
     {
        this._super() ;

        this._index         = this._newIndex() ;                    // index for this instance
        this._elemId        = this.element.attr("id") ;             // tree element id
        if (this._elemId === undefined) {
          this._elemId = "oj-tree-" + this._getIndex() ;
          this.element.attr("id", this._elemId) ;
        }
        this._elemId        = "#" + this._elemId ;
        this._$container    = this.element ;                        // the containing <div>
        this._$container_ul = null ;                                // the containing <ul>
        this._data          = {} ;                                  // working data
        this._tds           = null ;                                // Tree DataSource
        this._isRtl         = this._GetReadingDirection() === "rtl";
        this._isTouch       = oj.DomUtils.isTouchSupported() ;
        this._initTree() ;                                          // initialize data structures
        this._animDuration  = this._getAnimDuration() ;             // get css $treeAnimationDuration value
        if (this._animDuration) {
          var ai = oj.AgentUtils.getAgentInfo() ;
          this._isSafari = (ai.browser == "safari") ;
          //this._trace(ai.os + " " + ai.browser) ;
          this._proxyTransitionEndHandler = $.proxy(this._transitionEndHandler, this);
        }

        this._start() ;                                             // build/display the tree
     },

     /**
       *  Widget is being destroyed.
       *  @private
       */
     _destroy : function()
     {
        this._clearTree() ;    // Clean out the DOM.  After this, the tree markup has
                               // been removed from the div, and all event handlers
                               // removed.

        // If the tree was constructed from existing user markup found in the div,
        // reinstate that markup to reset the div to its original state pre tree create.
        if (this._data.html.markup_ul) {
          this._$container.append(this._data.html.markup_ul) ;        //@HTMLUpdateOK
          this._data.html.markup_div.remove() ;
          this._data.html.markup_div        = false ;
          this._data.html.useExistingMarkup = false ;
        }

        this._data.ds.type = DS_NONE ;
        this._super() ;
     },


     /**
       *  Handle an option change.
       *  Called by $(selector).ojtree("options", "prop", value)
       *  @private
       */
     _setOption: function (key, newval, flags)
     {
        var _this = this ;
        var prev ;
        var val ;

        if (key === "selection") {
          if (typeof newval == "string") {
            newval = [newval] ;
          }
          else {
            newval = (newval && newval.length)? newval : [] ;
          }
          this._data.core.suppressSelectEvent = true ;    // don't want select/deselect to fire selection change
          this._handleSelectionOptionChange(newval) ;     // apply selection/deselection
          this._data.core.suppressSelectEvent = false ;
          newval = this._getSelected() ;
          // optionChange event will be fired when super is called below.
        }
        else if (key === "selectionMode") {
          if (newval === "none") {
            val = 0 ;
          }
          else if (newval === "single") {
            val = 1;
          }
          else if (newval === "multiple") {
            val = -1 ;
          }
          else {
            val = 0 ;
            newval = "none"
          }
          if (val != _this._data.core.selectMode) {
            _this._data.core.selectMode = val ;
          }
        }
        else if (key === "icons") {
           if ($.type(newval) == "boolean") {
             if (newval != _this._data.themes.icons) {
               _this._data.themes.icons = newval ;
               newval? _this._showIcons() : _this._hideIcons() ;
             }
           }
        }                                // end "core/ui" options
        else if (key === "contextMenu") {
           this._clearMenu() ;
           if (newval) {  
             this._initMenuOpts() ;
             this._initMenu(newval) ;
           }
        }                                // end "contextMenu"
        else if (key === "disabled")  {
           this._handleDisabledChanged(newval) ;
        }                            // end "disabled"
        else if (key === "data") {    //TDO
          this._super(key, newval, flags);
          this._initDSOpts() ;
          this._initDataSource();
          this._initExpandedOpts() ;     // reset the nodes to be initially expanded
          this._loadNodes() ;            // start node loading from the datasource
          return ;                       // super has already been called
        }
        else if (key === "dnd") {
          this._super(key, newval, flags);
          this._initDnDOpts();
          return ;                       // super has already been called
        }
        else if (key == "emptyText") {
          newval = this._escapeHtml(newval) ;   // escape the text for security
        }
        this._super(key, newval, flags);
     },


     /**
       *  Compare two option value(s) for equality.  Returns false if they are different.
       *  @private
       */
     _CompareOptionValues : function (option, value1, value2)
     {
        if (option === "selection") {
          return  this._compareSelectionValues(value1, value2) ;
        }

        return this._superApply(arguments) ;
     },


     /**
       *  Compare selection values, return true if the same, else false.
       *  @private
       */
     _compareSelectionValues : function(value1, value2)
     {
       var  i, j, id, len, len2 ;
       var  bDiff   = false,
            bInList = false ;

       len  = (value1 && value1.length)? value1.length : 0 ;
       len2 = (value2 && value2.length)? value2.length : 0 ;

       if (len !== len2) {
         bDiff = true ;
       }
       else if (len == 0 && len2 === 0) {
         bDiff = false ;
       }
       else {
         for (i = 0; i < len; i++) {
            id = $(value1[i]).attr("id") ;
            bInList = false ;
            for (j = 0; j < len2; j++) {
               if (id == $(value2[j]).attr("id")) {
                 bInList = true ;
                 break ;
               }
            }

            if (! bInList) {        // if not in list, values are different
              bDiff = true ;
              break ;
            }
         }
       }

       return (! bDiff) ;
     },


     /**
       *  Clears out the tree dom
       *  @private
       */
     _clearTree : function()
     {
        var  n = this._getIndex();

        this._$container
            .unbind(".oj-tree")
            .undelegate(".oj-tree")
            .removeData("oj-tree-instance-id")
            .find("[class^='oj-tree']")
            .addBack()
            .attr("class", function ()
                    {
                      return this["className"].replace(/oj-tree[^ ]*|$/ig,'');
                    });

        var cl = this._$container.attr("class") ;      // if jQuery has left us with a
        cl = cl.trim();                                // class of blanks, remove it.
        if (cl.length === 0) {
          this._$container.removeAttr("class") ;
        }

        _removeKeyFilter(this._$container_ul) ;        // remove keyboard listener because
                                                       // _$container_ul will be removed
        $(document)
            .unbind(".oj-tree-" + n)
            .undelegate(".oj-tree-" + n);

        // Remove the constructed tree from the DOM.
        this._$container_ul.remove() ;
        this._$container_ul = null ;
     },

//   /*
//     *  Returns a jQuery wrapped tree node.  obj can be a selector pointing 
//     *  to an element within the tree, a DOM node, or a jQuery wrapped node.  If -1 is used
//     *  (indicating the whole tree), -1 is returned.
//     *  @private
//     */
//                                         Not used in V1
//   _getNodeCore : function(obj) 
//   {
//       var $obj = $(obj, this._$container); 
//
//       if ($obj.is(".oj-tree") || obj == -1)  {
//          return -1;
//       } 
//       $obj = $obj.closest("li", this._$container);
//       return $obj.length ? $obj : false; 
//   },


     /**
       *  Returns a jQuery wrapped tree node.  obj can be a selector pointing 
       *  to an element within the tree, a DOM node, or a jQuery wrapped node.  If obj is -1
       *  (indicating the whole tree), -1 is returned.
       *  @private
       */
     _getNode :  function (obj, allow_multiple)
     {
       if (typeof obj === "undefined" || obj === null) {
         return allow_multiple ? this._data.ui.selected : this._data.ui.lastSelected;
       }

       var $obj = $(obj, this._getContainer()); 

       if ($obj.is(".oj-tree") || obj === -1) {
          return -1;
       } 

       $obj = $obj.closest("li", this._getContainer()); 
       return $obj.length ? $obj : false; 
     },

     /**
       *  Returns the node previous to the supplied obj.  obj can be a selector pointing 
       *  to an element within the tree, a DOM node, or a jQuery wrapped node.  If -1 is used
       *  (indicating the whole tree), -1 is returned.  If arg strict is true, only immediate
       *  siblings are considered, and false will be returned if there is no previous sibling.
       *  If strict is false or omitted and obj has no previous siblings (i.e it is the first
       *  child of its parent), the parent is returned.
       *  @private
       */
     _getPrev  : function (obj, strict)
     {
        obj = this._getNode(obj);
        if (obj === -1) {
          return this._$container.find("> ul > li:last-child");
        }
        if (!obj.length) {
          return false;
        }
        if (strict) {
         return (obj.prevAll("li").length > 0) ? obj.prevAll("li:eq(0)") : false;
        }

        if (obj.prev("li").length) {
            obj = obj.prev("li").eq(0);
            while (obj.hasClass(OJ_EXPANDED)) {
               obj = obj.children("ul:eq(0)").children("li:last");
            }
           return obj;
        }
        else {
           var o = obj.parentsUntil(".oj-tree","li:eq(0)");
           return o.length ? o : false;
        }
     },

     /**  Returns the node after the supplied obj.  obj can be a selector pointing 
       *  to an element within the tree, a DOM node, or a jQuery wrapped node.  If -1 is used
       *  (indicating the whole tree), -1 is returned.  If arg strict is true, only immediate
       *  siblings are considered, else if the obj has no more siblings (i.e is the last
       *  child of its parent), the parent's next sibling is returned.
       *  @private
       */
     _getNext  : function (obj, strict)
     {
        obj = this._getNode(obj);
        if (obj === -1)  {
          return this._$container.find("> ul > li:first-child");
        }
        if (!obj.length) {
           return false;
         }
        if (strict)  {
           return (obj.nextAll("li").size() > 0) ? obj.nextAll("li:eq(0)") : false;
        }
        
        if (obj.hasClass(OJ_EXPANDED))  {
          return obj.find("li:eq(0)");
        }
        else if(obj.nextAll("li").size() > 0)  {
          return obj.nextAll("li:eq(0)");
        }
        else  {
           return obj.parentsUntil(".oj-tree","li").next("li").eq(0);
        }
     },


     /**
       *  Returns the parent node of the supplied obj.  obj can be a selector pointing 
       *  to an element within the tree, a DOM node, or a jQuery wrapped node.
       *  child of its parent), the parent is returned.
       *  @private
       */
     _getParent  : function (obj)
     {
        obj = this._getNode(obj);
        if (obj == -1 || !obj.length)  {
          return false;
        }
        var o = obj.parentsUntil(".oj-tree", "li:eq(0)");
        return o.length ? o : -1;
     },

     /**
       *  Returns the child nodes of the supplied obj, or false if no children or failure.
       *  obj can be a selector pointing to an element within the tree, a DOM node, or a
       *  jQuery wrapped node.  obj can be -1 to indicate the whole tree.
       *  @private
       */
     _getChildren   : function (obj)
     {
        obj = this._getNode(obj);
        if (obj === -1) {
          return this._$container.children("ul:eq(0)").children("li");
        }
        if (!obj.length) {
          return false;
        }
        return obj.children("ul:eq(0)").children("li");
     },

     /**
       *  @private
       */
     _isLeaf : function(node)
     {
       node = this._getNode(node) ;
       return node && node !== -1 && node.hasClass(OJT_LEAF);
     },

     /**
          Returns the jQuery wrapped <li> element for a node spcification
       *  @private
       */
     _getNodeElem : function(node)
     {
        var el  = this._$container_ul.find(node) ;
        var ret =  false ;

        if (el && el.length && el.length > 0) {
          if ($(el).is("li")) {
            ret = el ;
          }
        }

        return ret ;
     },

     /**
       *  Returns the widget instance for the supplied node as the member
       *  ot in the supplied object o.
       *  @instance
       *  @private
       */
     _reference : function(node, o)
     {
        var div  = node.parents("div").eq(0) ;
        var ctor = oj.Components.getWidgetConstructor(div) ;

       if (ctor && o) {
         ctor("getCI", o) ;
       }
       return this ;
     },

//   /**
//     *  Get the data block for the tree component containing the specified
//     *  node.  Returned as member db of the supplied object.
//     *  @instance
//     *  @private
//     */
// Not used in V1
//   _referenceData : function(node, o)
//   {
//      var div   = node.parents("div").eq(0) ;
//      var ctor  = oj.Components.getWidgetConstructor(div) ;
//
//      if (ctor && o) {
//        ctor("getDB", o) ;
//      }
//   },


     /**
       *  Internal use only - return component instance as member ot
       *  in supplied object.
       *  @instance
       *  @private
       */
     "getCI" : function(o)
     {
       o.ot = this ;
     },

//   /**
//     *  Internal use only - return the data block for a tree's this.
//     *  @instance
//     *  @private
//     */
//  Not used in V1
//   "getDB" : function(o)
//   {
//      o.db = this._data ;
//   },
//

     /**
       *  Add default values to options, unless already defined in options.
       *  @private
       */
     _applyDefaults :  function(to, from)
     {
        if (to != undefined && from != undefined)  {
          $.each(from, function(prop, val) {
                                             if (to[prop] == undefined) {
                                               to[prop] = val ;
                                             }  
                                            });
        }
     },

     /**
          Handle change of the disabled option
       *  @private
       */
     _handleDisabledChanged  : function(newval)
     {
        var curState ;

        if (typeof newval === "undefined")  {
          return ;
        }

        curState  = this._$container_ul.hasClass(OJ_DISABLED) ;
        if (! curState) {
          curState = false ;      // in case undefined
        }


        if (curState != newval) {
          if (newval) {
            this._$container_ul.addClass(OJ_DISABLED) ;
//          this._$container_ul.prop("disabled", "disabled");
            this._$container_ul.prop("disabled", true);
          }
          else {
            this._$container_ul.removeClass(OJ_DISABLED) ;
//          this._$container_ul.removeAttr("disabled");
            this._$container_ul.prop("disabled", false);
          }
//        this._lock(newval) ;
          this._treeDisable(newval) ;
        }
     },

     /**
       *  @private
       */
     _treeDisable : function (lstate) {

        lstate = lstate || false ;
        if (lstate) {
          this._data.core.locked = true;
          this._data.ui.opacity  = this._$container.children("ul").css("opacity") ;
          this._$container_ul.addClass(OJ_DISABLED).css("opacity","0.9");
        }
        else {
          this._data.core.locked = false;
          this._$container_ul.removeClass(OJ_DISABLED).css("opacity", this._data.ui.opacity);
        }
//      this._emitEvent({});
     },


     /**
       *  Handles a change to the selection option property. Nodes currently selected that are
       *  not in the new array are deselected.  Nodes in the new array become (or stay) selected.
       *  @private
       */
     _handleSelectionOptionChange : function(newSels)
     {
        var sels   = newSels.slice(0) ;                       // working copy
        var cur    = [] ;                                     // xfer valid selection args to this
        var newvals ;
        var _this  = this ;
        var $elem, id, inList, len, bRemoved = false;

        // Convert the new array to jQuery wrapped node elements (thereby changing any
        // refs like "#myid"). If any node reference is invalid, remove it from the
        // selections array.
        newvals = $.grep(sels, function(node, i) {
                                  $elem = _this._getNodeElem(node) ;
                                  if ($elem) {                       // if node elem exists
                                    cur.push($elem) ;                // note it.
                                    sels[i] = $elem[0] ;             // ensure we store an <li> 
                                  }                                  // not a string like "#news"
                                  else {
                                   return false ;                    // remove from array
                                  }
                                  return true ;                      // retain in array
                 });

        //  Deselect any currently selected nodes that are not in the new array.
        var aSelected = this._getSelected() ;
        $.each(aSelected, function(i, node) {
                            len    = cur.length ; 
                            id     =  $(node).attr("id") ;
                            inList = false ;
                            for (i = 0; i < len; i++) {
                               if (id == cur[i].attr("id")) {
                                 inList = true ;
                                 break ;
                               }
                            }
                            if (! inList) {
                              _this._deselect(node) ;
                            }
                          }) ;

        this._setSelected(cur, null) ;      //  Select all nodes in the new selection array
     },


     /**
       *  Creates a "move" object containing details of the impending move, and
       *  stores it in this._data.core.prepared_move
       *
       *  The object contains :
       *     .o   the node being moved
       *     .r   the reference node
       *     .p   the destination position relative to the reference node
       *     .np  the new parent
       *     .oc  the original node if there was a copy
       *     .cy  boolean indicating if the move was a copy
       *     .op  the former parent
       *     .or  the node that was previously in the position of the moved node
       *     .ot  the original tree instance this
       *     .rt  the reference tree instance
       *     .cr  same as .np but if a root node is created this is -1
       *
       *   @private
       */
     _prepare_move :  function (o, r, pos, cb, is_cb)
     {
       var p  = {},         // new prepared_move object
           _t = this;

       r  = ((r === - 1) || (!r)) ? -1 : this._getNode(r);

       // Get tree component's involved
       this._reference(o, p) ;               // for node being moved
       p.rt = this;                          // for reference node

       p.o  = p.ot._getNode(o);
       p.r  = r ;
       p.p  = (typeof pos === "undefined" || pos === false) ? "last" : pos; // TODO: move to a setting

       if (!is_cb && this._data.core.prepared_move.o               &&
                     this._data.core.prepared_move.o[0] === p.o[0] &&
                     this._data.core.prepared_move.r[0] === p.r[0] &&
                     this._data.core.prepared_move.p    === p.p)  {
//        this._emitEvent(this._data.core.prepared_move, "prepare_move, true);
          if (cb)  {
            cb.call(this, this._data.core.prepared_move);
          }
          return;
       }

       if (p.r === -1 || !p.r)  {
         p.cr = -1;

         switch(p.p)
         {
            case "first":
            case "before":
            case "inside":
                           p.cp = 0; 
                           break;
            case "after":
            case "last":
                           p.cp = p.rt._$container.find(" > ul > li").length; 
                           break;
            default:
                           p.cp = p.p;
                           break;
         }
       }
       else {
         if (!/^(before|after)$/.test(p.p) && !this._is_loaded(p.r)) {
           return this._load_node(p.r, function () {
                                           this._prepare_move(o, r, pos, cb, true);
                                 });
         }
          switch(p.p)
          {
             case "before":  p.cp = p.r.index();
                             p.cr = p.rt._getParent(p.r);
                             break;
             case "after":   p.cp = p.r.index() + 1;
                             p.cr = p.rt._getParent(p.r);
                             break;
             case "inside":
             case "first":
                             p.cp = 0;
                             p.cr = p.r;
                             break;
             case "last":
                             p.cp = p.r.find(" > ul > li").length; 
                             p.cr = p.r;
                             break;
             default: 
                             p.cp = p.p;
                             p.cr = p.r;
                             break;
          }
       }

       p.np  = p.cr == -1 ? p.rt._getContainer() : p.cr;
       p.op  = p.ot._getParent(p.o);
       p.cop = p.o.index();

       if (p.op === -1)  {
         p.op = p.ot ? p.ot._$container : this._$container;
       }
       if (!/^(before|after)$/.test(p.p) && p.op && p.np && p.op[0] === p.np[0] && p.o.index() < p.cp)
       {
         p.cp++;
       }

       //if(p.p === "before" && p.op && p.np && p.op[0] === p.np[0] && p.o.index() < p.cp) { p.cp--; }
       p.or = p.np.find(" > ul > li:nth-child(" + (p.cp + 1) + ")");
       this._data.core.prepared_move = p;
//     this._emitEvent(this._data.core.prepared_move, "prepare_move", true);
       if (cb) {
         cb.call(this, this._data.core.prepared_move, "prepare_move");
       }
     },


     /**
       *  Checks the prepared_move object, and returns true if the move is
       *  acceptable, else false.
       */
     "check_move" : function ()
     {
       var obj  = this._data.core.prepared_move,
           ret  = true,
           r ;

       r = obj.r === -1 ? this._$container : obj.r;

       if ((!obj) || (!obj.o) || obj.or[0] === obj.o[0]) {
         return false;
       }
       if (!obj.r) {        // tdo, why is r is false when we use jquery simulate drag
         return false ;
       }

       if (!obj.cy) {
         if (obj.op && obj.np && obj.op[0] === obj.np[0] && obj.cp - 1 === obj.o.index())  { 
           return false;
         }

         // Get all li ancestors plus the reference node. If the dragged node 
         // is found, end loop and set return to false to indicate move is invalid.   
         obj.o.each(function ()
                    { 
                      if (r.parentsUntil(".oj-tree", "li").addBack().index(this) !== -1)  {
                        ret = false;
                        return false;
                      }
                    });
       }

       return ret;
     },

     /**
       *  Changes the text title of a node
       *  @private
       */
     _rename_node : function(node, text)
     {
       var  t ;

       node = this._getNode(node);
       this.__rollback();
       t  = this["getText"](node) ;

       if (node && node.length) {
          var rslt = this._emitEvent({"obj"  : node,
                                      "func" : "rename",
                                      "title" : text,
                                      "prevTitle": t}, "before") ;
          if (typeof rslt == "boolean" && (!rslt)) {
            return ;
          }
       }

       if (node && node.length && this._set_text.apply(this, Array.prototype.slice.call(arguments)))  {
         this._emitEvent({ "obj" : node, "title" : text, "prevTitle" : t}, "rename");
       }
     },

     /**
       *   Moves a node within the tree
       *   @private
       */ 
     _moveNode : function (obj, ref, position, is_copy, is_prepared, skip_check)
     {
        if (! is_prepared) {
          obj = this._getNode(obj) ;
        }

        if ((obj.hasClass && obj.hasClass(OJ_DISABLED)) || this._data.core.locked) {
          return ;
        }

        if (!is_prepared)  { 
          return this._prepare_move(obj, ref, position,
                                    function (p) {
                                       // Before the move/copy occurs, if the
                                       // node is being moved (not copied) to
                                       // a different tree, deselect it. 
                                       if ((p.ot !== p.rt) && (!p.cy)) {
                                         p.ot["deselect"](p.o) ;
                                       }
                                       this._moveNode(p, false, false,
                                                      is_copy, true, skip_check);
                                    });
        }

        if (is_copy) { 
          this._data.core.prepared_move.cy = true;
        }
        if (!skip_check && !this["check_move"]())  {
          return false;
        }

        this.__rollback();
        var o = false;

        if (is_copy)  {
           o = obj.o.clone(true);
           o.find("*[id]").addBack().each(function () {
                                             if (this.id) {
                                               this.id = "copy_" + this.id;
                                             }
                                         });
        }
        else   {
          o = obj.o;
        }

        if (obj.or.length)  {
          obj.or.before(o);                             //@HTMLUpdateOK
        }
        else   { 
           if (!obj.np.children("ul").length)  {
             $("<ul />").appendTo(obj.np);              //@HTMLUpdateOK
           }
           obj.np.children("ul:eq(0)").append(o);       //@HTMLUpdateOK
        }

        try { 
               obj.ot._cleanNode(obj.op);
               obj.rt._cleanNode(obj.np);
               if (!obj.op.find("> ul > li").length)  {
                obj.op.removeClass("oj-expanded oj-collapsed")
                      .removeAttr(WA_EXPANDED)
                      .addClass(OJT_LEAF)
                      .children("ul")
                      .remove();
               }
        } catch (e) { }

        if (is_copy)  { 
           this._data.core.prepared_move.cy = true;
           this._data.core.prepared_move.oc = o; 
        }

        var d = $.extend(true, {}, this._data.core.prepared_move) ;
        d["obj"] = obj.o ;  
        this._emitEvent(d, "move");
        return this._data.core.prepared_move;
     },


      /**
        *  Returns the "move" object created by a previous _prepare_move()
        *
        *  The object contains :
        *     .o   the node being moved
        *     .r   the reference node
        *     .p   the destination position relative to the reference node
        *     .np  the new parent
        *     .oc  the original node if there was a copy
        *     .cy  boolean indicating if the move was a copy
        *     .op  the former parent
        *     .or  the node that was previously in the position of the moved node
        *     .ot  the original tree instance
        *     .rt  the reference tree instance
        *     .cr  same as .np but if a root node is created this is -1
        *     .cp  index of ref node
        *     .cr  parent of ref node (-1, or node)
        *     .cop index of node 
        *
        *  @private
        */
      _getMove  : function ()
      {
         return this._data.crrm.prepared_move;
      },


      /**
        *  Returns the "type" attribute of the node.  If not found, returns "default"
        *  @private
        */
      _getType : function (node)
      {
        var n = null ;
        var t = this.options["types"];             // are types defined?

        if (t) {
          n = this._getNode(node);
        }
        return (!n || !n.length) ? false : n.attr(this.options["types"]["attr"]) || "default";
      },

      /**
        *  Sets the "type" attribute of the node.
        *  @return {boolean}  true if the change was successful, else false. 
        *  @private
        */
      _setType : function (node, str)
      {
         var  s   = this.options["types"], 
              tattr,
              ret = false ;

         node = this._getNode(node);

         if (s && node && (node != -1) && node.length && str) {
           tattr = s["attr"] ;
           if (tattr && s[str]) {
             node.attr(tattr, str);
             node.addClass(OJT_TYPE) ;
//           this._emitEvent({ "obj" : node, "type" : str}, "settype", true); 
             ret = true ;
           }
         }

         return ret;
      },


      /**
        *  @private
        */
      _check : function (rule, obj, opts)
      {
         obj = this._getNode(obj);

         var v     = false,
             ty    = this._getType(obj),
             d     = 0,
             _this = this,
             s     = this._getOptions()["types"],
             data  = false;

             if (obj === -1) { 
               if (!!s[rule])  {
                 v = s[rule];
                }
               else  {
                 return;
               }
             }
             else   {
               if (ty === false)  {
                 return;
               }

               data = this._data.types.defaults["useData"] ? obj.data("oj-tree") : false;
               if (data && data["types"] && typeof data["types"][rule] !== "undefined")  {
                 v = data["types"][rule];
               }
               else if(!!s["types"][ty] && typeof s["types"][ty][rule] !== "undefined") {
                   v = s["types"][ty][rule];
               }
               else if(!!s["types"]["default"] && typeof s["types"]["default"][rule] !== "undefined")  {
                   v = s["types"]["default"][rule]; }
               }

               if ($.isFunction(v))  {
                   v = v.call(this, obj);
               }

               var md = this._data.types.defaults["maxDepth"] ;

               if (rule === "maxDepth" && obj !== -1 && opts !== false && this._data.types.defaults["maxDepth"] !== -2 && v !== 0)  {
                 // also include the node itself - otherwise if root node it is not checked
                 obj.children("a:eq(0)").parentsUntil(".oj-tree","li").each(function (i)
                        {
                           // check if current depth already exceeds global tree depth
                           if ((md !== -1) && (md - (i + 1) <= 0))  {
                             v = 0;
                             return false;
                           }

                           d = (i === 0) ? v : _this._check(rule, this, false);

                           // Check if current node max depth is already matched or exceeded
                           if (d !== -1 && d - (i + 1) <= 0)  {
                             v = 0; return false;
                           }

                           // otherwise - set the max depth to the current value minus current depth
                           if (d >= 0 && (d - (i + 1) < v || v < 0) )  {
                             v = d - (i + 1);
                           }

                           // If the global tree depth exists and it minus the nodes calculated
                           // so far is less than `v` or `v` is unlimited
                           if ((md >= 0) && (md - (i + 1) < v || v < 0) )  {
                             v = md - (i + 1);
                           }
                        });
               }

               return v;
     },


      /**
        * Applies all necessary classes to the node and its descendants.
        * obj can be a node, or -1 to clean the whole tree.
        * @private
        */
      _cleanNode  : function (obj)
      {
         obj = obj && obj != -1 ? $(obj) : this._$container_ul;
         obj = obj.is("li") ? obj.find("li").addBack() : obj.find("li");

         obj.removeClass(OJT_LAST)
            .addClass(OJT_NODE)
            .addClass(OJ_DRAGGABLE)
            .filter("li:last-child")
            .addClass(OJT_LAST)
            .end()
            .filter(":has(li)")
            .not(".oj-expanded")
            .removeClass(OJT_LEAF)
            .addClass(OJ_COLLAPSED)
            .attr(WA_EXPANDED, "false");

         obj.not(".oj-expanded, .oj-collapsed")     // clean leaf nodes
            .addClass(OJT_LEAF)
            .children("ul")
            .remove();

         var typeAttr = (this.options["types"])? this.options["types"]["attr"] : false ;

         var nodes = obj.find("li"),         // clean the node and disclosure icon <ins> 's
             disc,
             t ;
         $.each(obj, function (i, val) {
                 t = $(this) ;
                 disc = t.find("> ins") ;
                 if (disc.length > 1) {
                   disc = $(disc[0]) ;
                 }
                 if (t.hasClass(OJT_LEAF)) {
                   disc.removeClass(OJ_DISC);
                   disc.addClass(OJT_ICON);
                 }
                 else {
                   disc.addClass(OJ_DISC);
                   disc.removeClass(OJT_NICON);
                 }

                 if (typeAttr && t.attr(typeAttr)) {      // if user has declared a node type
                   t.addClass(OJT_TYPE) ;                 // give the node our type class.
                 }
         }) ;

//       this._emitEvent({ "obj" : obj }, "cleanNode", true);
      },


      /**
        *  Creates a new node
        *  @private
        */
      _createNode :  function (obj, position, js, callback, is_loaded)
      {
         obj = obj || -1 ;  

         obj = this._getNode(obj);
         if (obj !== -1 && !obj.length) {
           return false;
         }

         position = (typeof position === "undefined") ? "last" : position;

         var d = $("<li />"),
             s = this.options,
             tmp;

         if (!is_loaded && !this._is_loaded(obj)) {
           this._load_node(obj, function () 
                               {
                                 this._createNode(obj, position, js, callback, true);
                               });
           return false;
         }
           
         this.__rollback();
           
         if (typeof js === "string") {
            js = { "data" : js };
         }
         if (!js) {
            js = {};
         }
         if (js["attr"])  {
           d.attr(js["attr"]);
         }
         if (js["metadata"]) {
           d.data(js["metadata"]);
         }
         if (js["state"]) {
           d.addClass((js["state"] === "expanded")? OJ_EXPANDED : OJ_COLLAPSED);
         }
         if (!js["data"]) {
           js["data"] = (js["title"] !== undefined)? js["title"] :
                                                     this._getString(TRANSKEY_NEWNODE);
         }
           
         if (!$.isArray(js["data"]))  {
           tmp = js["data"];
           js["data"] = [];
           js["data"].push(tmp);
         }

         var _this = this ;
         var sp ;
         $.each(js["data"], function (i, m) {
             tmp = $("<a tabindex='-1' />");
             if ($.isFunction(m)) {
                m = m.call(this, js);
             }
             sp = $("<span class='" + OJT_TITLE + "'>") ;
             if (typeof m == "string")  {
               sp[0].textContent = m ;           // per security post-impl
               tmp.attr("href", "#");
             }
             else  {
               if (! m["attr"])  {
                  m["attr"] = {};
               }
               if (! m["attr"]["href"])  {
                  m["attr"]["href"] = '#';
               }
               sp[0].textContent = m ;           // per security post-impl
               if (m["language"]) {
                 tmp.addClass(m["language"]);
               }
             }
             tmp.append(sp) ;                                                                            //@HTMLUpdateOK
             tmp.prepend("<ins class='oj-tree-icon oj-tree-node-icon oj-component-icon'> </ins>");  //@HTMLUpdateOK

             if (!m["icon"] && js["icon"]) {
               m["icon"] = js["icon"];
             }
             if (m["icon"]) { 
               if (m["icon"].indexOf("/") === -1)  {
                  tmp.children("ins").addClass(m["icon"]);
               }
               else  {
                  tmp.children("ins").css("background","url('" + m["icon"] + "') center center no-repeat");
               }
             }
             d.append(tmp);                                                                             //@HTMLUpdateOK
         });

         d.prepend("<ins class='" + OJ_DISC + "'> </ins>");                                        //@HTMLUpdateOK
         if (obj === -1) {
           obj = this._$container;
           if (position === "before")  {
             position = "first";
           }
           if (position === "after") {
             position = "last";
           }
         }

         switch(position)
         {
           case "before":  obj.before(d);                                      //@HTMLUpdateOK
                           tmp = this._getParent(obj);
                           break;
           case "after" :  obj.after(d);                                       //@HTMLUpdateOK
                           tmp = this._getParent(obj);
                           break;
           case "inside":
           case "first" :  if (!obj.children("ul").length) {
                            obj.append("<ul />");                              //@HTMLUpdateOK
                           }
                           obj.children("ul").prepend(d);                      //@HTMLUpdateOK
                           tmp = obj;
                           break;
           case "last":    if (!obj.children("ul").length) {
                             obj.append("<ul />");                             //@HTMLUpdateOK
                           }
                           obj.children("ul").append(d);                       //@HTMLUpdateOK
                           tmp = obj;
                           break;
           default:        if (!obj.children("ul").length) {
                             obj.append("<ul />");                             //@HTMLUpdateOK
                           }
                           if (!position) {
                             position = 0;
                           }
                           tmp = obj.children("ul").children("li").eq(position);
                           if (tmp.length)  {
                             tmp.before(d);                                    //@HTMLUpdateOK
                           }
                           else  {
                             obj.children("ul").append(d);                     //@HTMLUpdateOK
                           }
                           tmp = obj;
                           break;
         }

         if (tmp === -1 || tmp.get(0) === this._$container.get(0)) {
           tmp = -1;
         }
         this._cleanNode(tmp);
         this._emitEvent({ "obj" : d, "parent" : tmp }, "create");

         if (callback) {
           callback.call(this, d);
         }

         return d;
      },


      /**
        *  Expands a collapsed node
        *  @private
        */
      _expand : function (obj, callback, skipAnim)
      {
         obj = this._getNode(obj);
         if ((! obj) || (! obj.length)) {
           return false;
         }

         skipAnim = skipAnim || false ;

         if (obj.hasClass(OJ_DISABLED) || this._data.core.locked) {
           return ;
         }

         if (! obj.hasClass(OJ_COLLAPSED))  {
           if (callback)  {
             callback.call();
           }
           return false;
         }

         // Issue a before event, and cancel the expand if the user vetoes it.

         var rslt = this._emitEvent({"obj" : obj, "func" : "expand"}, "before") ;
         if (typeof rslt == "boolean" && (!rslt)) {
           return ;
         }

        // Get css value $treeAnimationDuration. This is needed because if the value is
        // zero and a transition is started, the transitionend event does not fire and
        // we don't get the opportunity to emit an expand event.

         var dur  = skipAnim? 0 : this._animDuration,
             t    = this;

         if (! this._is_loaded(obj))  {
           obj.children("a").addClass(OJT_LOADING);
           this._load_node(obj, function () {
                                   t._expand(obj, callback, skipAnim);
                                }, callback) ;
         }
         else  {
           if (this.options["expandParents"])  {
             obj.parentsUntil(".oj-tree", ".oj-collapsed").each(function ()
                                   {
                                      t._expand(this, false, true);
                                   });
           }

//           if (skipAnim || dur)  {
//             obj.children("ul").css("display","none");
//           }
           obj.removeClass(OJ_COLLAPSED).addClass(OJ_EXPANDED)
                                        .attr(WA_EXPANDED, "true")
                                        .children("a")
                                        .removeClass(OJT_LOADING);
           $(obj.children()[0]).removeClass(OJ_SELECTED).addClass("oj-default") ;

           if ((!skipAnim) && dur)  {
            this._slide(obj, false) ;       // slideDown
           }
           else  {
             this._transitionEnd($(obj.children("UL")[0]), obj) ;
           }

           if (callback)  {
             callback.call();
           }
         }
    },


    /**
      *  Expands all collapsed nodes
      *  @private
      */
    _expandAll  : function (obj, animate, original_obj)
    {
      var origTarg = obj? obj : -1 ;

      obj = obj ? this._getNode(obj) : -1;
      if (!obj || obj === -1) {
        obj = this._$container_ul;
      }
      else {
        origTarg = obj ;
      }

      if (original_obj)  { 
        obj = obj.find("li.oj-collapsed");
      }
      else   {
        original_obj = obj;
        if (obj.is(".oj-collapsed"))
        {
          obj = obj.find("li.oj-collapsed").addBack();
        }
        else  {
          obj = obj.find("li.oj-collapsed");
        }
      }

      var _this = this;
      obj.each(function ()
           { 
             var __this = this; 

             if (!_this._is_loaded(this))   {
               _this["expand"](this, function()
                                     {
                                       _this._expandAll(__this, animate, original_obj);
                                     }, !animate);
             }
             else  {
               _this._expand(this, false, !animate);
             }
           });

      // so that callback is fired AFTER all nodes are open
      if (original_obj.find('li.oj-collapsed').length === 0)  {
        this._emitEvent({ "obj" : obj, "targ" : origTarg }, "expandAll");
      }
   },


    /**
      *  Selects a node. If "e" is not defined, the selection originated from an
      *  app API call and not via mouse/keyboard/touch action.
      *  @return  {boolean} true if selection was actually performed, else false.
      *  @private
      */
    _select : function (node, check, e)
    {
       var selectMode  = this._data.core.selectMode ;    // 0 = no select, 1 to n, or -1 = multi-select

       if (selectMode == 0) {
         return false ;
       }

       node = this._getNode(node);

       if ((node == -1) || (!node) || (!node.length))  {
         return false;
       }

       if (node.hasClass(OJ_DISABLED) || this._data.core.locked) {
         return false ;
       }

       if ((! e)|| (e.type != "touchend")) {       // if via api, ensure touchEvent flag is off
         this._data.ui.touchEvent = false ;
       }

       var isSelected  = this["isSelected"](node) ;

       if (! isSelected) {
         var rslt = this._emitEvent({"obj" : node, "func": "select"}, "before") ;
         if (typeof rslt == "boolean" && (!rslt)) {
           return false ;
         }
       }

       // make copy for potential optionChange event
       var prevSelections =  this.options["selection"].slice(0) ;

       //  Establish whether the select should happen, and deselect where necessary.

       var s              = this.options ;
       var selMultMod     = this._data.ui.defaults["selectMultipleModifier"] ;   // to remove - not published
       var selRangeMod    = this._data.ui.defaults["selectRangeModifier"] ;      // not yet published
       var disSelChildren = this._data.ui.defaults["disableSelectingChildren"] ; // not yet published 
       var isMultiple     = (selMultMod == "on" || (selMultMod !== false && e && oj.DomUtils.isMetaKeyPressed(e))),           
           isRange        = ((selRangeMod !== false) && (e &&  (e[selRangeMod + "Key"]))  &&
                            this._data.ui.lastSelected && this._data.ui.lastSelected[0] !== node[0] &&
                            (this._data.ui.lastSelected.parent()[0] === node.parent()[0])),
           proceed        = true,
           t              = this;

       if (check) {
          if (disSelChildren && isMultiple && 
              ((node.parentsUntil(".oj-tree","li").children("a.oj-selected").length) ||
               (node.children("ul").find("a.oj-selected:eq(0)").length)))  {
             return false;
          }
          proceed = false;
          switch(!0)  {
             case (isRange):
                 this._data.ui.lastSelected.addClass("oj-tree-last-selected");
                 node = node[ node.index() < this._data.ui.lastSelected.index() ? "nextUntil" : "prevUntil" ](".oj-tree-last-selected").addBack();

                 if (selectMode == -1 || node.length < selectMode)  {
                   this._data.ui.lastSelected.removeClass("oj-tree-last-selected");
                   this._data.ui.selected.each(function ()
                                           {
                                              if (this !== t._data.ui.lastSelected[0])  {
                                                t._deselect(this);
                                              }
                                         });
                   isSelected = false;
                   proceed    = true;
                 }
                 else {
                   proceed = false;
                 }
                 break;

             case  (this._data.ui.touchEvent && selectMode == -1) :  // touch and multi-select mode
                 this._data.ui.touchEvent = false ;                  // prevent race, when we call toggleselect()
                 this["toggleSelect"](node) ;                        // call into public API, want event
                 proceed = false;
                 break ;

             case (isSelected && !isMultiple): 
                 if (! e) {                      // if via api, nothing to do - node is
                   break ;                       // already selected.
                 }
                 this._deselectAll(); 
                 if (!this._data.ui.spacebar) {  // only kbd toggles selected status
                   isSelected = false;
                 }
                 proceed = true;
                 break;

             case (! isSelected && !isMultiple): 
                 if (e) {                                          // if mouse click
                   if (this._data.ui.selected && (this._data.ui.selected.length == 1)) {
                     this._deselect(this._data.ui.selected) ;      // clear the selection 
                   }
                   else {
                     this._deselectAll(this._data.ui.selected) ;   // clear all selections
                   }
                 }
                 else   //if (this._data.core.selectMode == -1 || this._data.core.selectMode > 0)  {
                   if (selectMode === 1)  {
                     this._deselect(this._data.ui.selected) ;      // clear the selection
                 }
                 else if (selectMode > 1)  {                  // TDO check if the selected count will exceed the
                   this._deselectAll();                       // the selectMode count. Do we veto this select?
                 }
                 proceed = true;
                 break;

             case (isSelected && isMultiple): 
                 this["deselect"](node);                      // call into public API, want event
                 break;

             case (! isSelected && isMultiple): 
                 if ((selectMode == -1) ||
                     (this._data.ui.selected.length + 1 <= selectMode))  { 
                   proceed = true;
                 }
                 break;
          }
       }

       // Proceed with the selection

       var bRet = false ;
       if (proceed && !isSelected)  {
          if (!isRange)  {
            this._data.ui.lastSelected = node;
          }
          node.children("a").addClass(OJ_SELECTED);
          node.attr(WA_SELECTED, "true");
          bRet = true ;                      // selection performed

          if (s["selectedParentExpand"])  {
             node.parents(".oj-collapsed").each(function () {
                                                   t._expand(this, false, true);
                                            });
          }

          this._data.ui.selected = this._data.ui.selected.add(node);
          this._fix_scroll(node.eq(0));

          if (! this._data.core.suppressSelectEvent) {
            this._fireOptionChangeEvent("selection", prevSelections, null, e) ; 
          }
       }

       return bRet ;
     },


     /**
       *  Deselects a node.
       *  @private
       */
     _deselect : function (node)
     {
       node = this._getNode(node);
       if (!node.length)  {
         return false;
       }
       if (node.hasClass(OJ_DISABLED) || this._data.core.locked) {
         return ;
       }

       if (this["isSelected"](node))  {
         node.children("a").removeClass(OJ_SELECTED);
         node.removeAttr(WA_SELECTED);

         this._data.ui.selected = this._data.ui.selected.not(node);

         if (this._data.ui.lastSelected && this._data.ui.lastSelected.length) {
           if (this._data.ui.lastSelected.get(0) === node.get(0))  {
             this._data.ui.lastSelected = this._data.ui.selected.eq(0);
           }
         }
       }

     },


      /**
        *  Deselect all nodes.  Nodes can be deselected all together with a "deselectAll" event,
        *  single optionChangeor separately with "deselect" events.
        *  @private
        */
     _deselectAll : function(context)
     {
        if (this._data.core.locked) {
          return ;
        }

        var origTarg = context? context : -1 ;
        var ret = context ? $(context).find("a.oj-selected").parent() :
                            this._$container.find("a.oj-selected").parent();

        ret.not(".oj-disabled") ;
        if (ret.length === 0) {
          return ;                // nothing selected
        }

        var _this = this ;
        $.each(ret, function() {
            _this._deselect(this) ; 
        }) ;

     },


     /** Selects all nodes specified in array.
       * @param {Array}  nodes - an array of nodes to be selected.  Nodes may be defined as elements,
       *                         jQuery wrapped nodes, or selectors pointing to the elements to be selected.
       * @private
       */
     _setSelected : function(nodes, e)
     {
        if (this._data.core.locked) {
          return null ;
        }

        if (nodes && nodes.length > 0)  {
          var _this = this;
          $.each(nodes, function (i, val) {
            if (val)  {
              _this._select(val, true, e);
            }
          });
        }
     },

     /*
      *  Handle tapping or clicking of a node
      *  @private
      */
     _handleNodeTapClick : function(event)
     {
        event.preventDefault();
        event.currentTarget.blur();
        if (! $(event.currentTarget).hasClass(OJT_LOADING))  {
          this._setFocus();
          if (this._select(event.currentTarget, true, event)) {
            this._$container_ul.focus() ;
          }
        }

        this._data.ui.touchEvent = false ;
     },

     /**
       *  Handle hover over a disclosure icon-font
       *  @private
       */
     _disclosureHover : function(elem, bHover)
     {
        elem = $(elem) ;
        if (elem.hasClass(OJ_DISABLED) || this._data.core.locked) {
          return ;
        }

        var par     = elem.parent() ;
        var bOpen   = par.hasClass(OJ_EXPANDED) ;
        var bClosed = par.hasClass(OJ_COLLAPSED) ;
        if (bOpen || bClosed) {                    // only care about disclosure
          if (bHover) {                            // <ins>'s of a disclosable
            elem.addClass(OJ_HOVER) ;              // parent node.
            elem.removeClass("oj-default") ;
            elem.removeClass(OJ_SELECTED) ;
          }
          else {
            elem.removeClass(OJ_HOVER) ;
            elem.addClass("oj-default") ;
          }
       }
     },

     /**
       *  Placeholder for a more specific refresh action. It is replaced
       *  by _refresh_json() for json_data, or _refresh_ui() for html_data.
       *
       *  Refreshes the tree - notes all open nodes, reloads them in the tree
       *  and then reopens the noted open nodes.
       *  @private
       */
     _refresh : function(node) {
        this._refresh_core(node) ;
     },


     /**
       *  Base _refresh action
       *  @private
       */
     _refresh_core : function(node)
     {
       var origTarg = node? node : -1 ;
       var _this    = this;
       
       this._save_opened();
       if (!node) {
          node = -1;
       }
       node = this._getNode(node);
       if (!node)  {
         node = -1;
       }
       else {
         origTarg = node
       }

       if (node !== -1)  {
         node.children("UL").remove();
       }
       else  {
         this._$container_ul.empty();
         this._processExistingMarkup() ;    // if existing markup, reprocess in case it changed
       }
       this._load_node(node, function ()
                           {
                             _this._emitEvent({ "obj" : origTarg}, "refresh");
                             _this._reload_nodes();
                            }
                      );
     },


     /**
       *  Ui _refresh action 
       *  @private
       */
     _refresh_ui : function (obj)
     {
        this["saveSelected"]();
        this._refresh_core(obj) ;
     },


    /**
      *  Emits an "after_open" event for internal use only.
      *  @private
      */
    "after_open"  : function (obj)
    { 
       this._emitEvent({ "obj" : obj }, "after_open", true);
    },


    /**
      *  Emits an internal "after_close" event for internal use only.
      *  @private
      */
    "after_close" : function (obj)
    { 
       this._emitEvent({ "obj" : obj }, "after_close", true);
    },


    /**
      *  Expand any nodes that have been specified to be expanded.
      *  Emits an internal "reopen" event.
      *  @private
      */
    _reopen : function ()
    {
       var _this = this;

       if (this._data.core.toExpand.length) {
         $.each(this._data.core.toExpand, function (i, val)
                                          {
                                             _this._expand(val, false, true); 
                                          });
       }
       this._emitEvent({}, "reopen", true);     // this event will also cause selections to be tried.
     },


     /**
       *  Returns an array of currently selected nodes.
       *  @private
       */
     _getSelected : function (context)
     { 
         var sel, ar, i, len ;

         sel = context ? $(context).find("a.oj-selected").parent() : this._data.ui.selected ; 
         ar  =[] ;
         len = sel.length ;
         for (i = 0; i < len; i++) {
            ar.push(sel[i]) ;
         }
         return ar ;
     },


     /**
       *  Changes the text title of the node's <a>
       *  @private
       */
     _set_text  : function (obj, val)
     {
        obj = this._getNode(obj);
        if (!obj.length)  {
          return false;
        }

        obj = obj.children("a:eq(0)");
        obj = obj.find("span:eq(0)") ;
        this._emitEvent({ "obj" : obj, "name" : val }, "set_text", true);
        return (obj[0].textContent = val);
     },

     /**
          Load the tree
       *  @private
       */ 
     _loadNodes : function()
     {              
        if (this._data.ds.type !== DS_NONE && this._data.ds.type !== DS_ERROR) {
          // Construct the tree
          this._load_node(-1,
                          function () {                         // success func
                                         this._loaded();        // emit ojloaded event
                                         this._reload_nodes();  // handle initExpanded, etc
                                      });
        }
        else {
           this._applyEmptyText() ;     //  Use emptyText option if defined.
           this._loaded() ;
          }
      },


     /**
       *  @private
       */ 
     _load_node : function(obj, success_callback, error_callback)     // Dummy function overriden by data methods
     {
        this._emitEvent({ "obj" : obj }, "load_node", true);
     },


     /**
       *  Returns whether a node is current loaded. This is a dummy function
       *  overriden by data methods such as _is_loaded_J(), _is_loaded_html()
       *  or _is_loaded_DS().
       *  @private
       */
     _is_loaded  : function (obj)                 // Dummy function overriden by data methods
     {
       return true;
     },


     /**
       *  TreeDataSource's _load_node
       *  @private
       */
     _load_node_DS : function (obj, s_call, e_call)
     {
        var _this = this;
        this._load_node_tree(obj, function ()  {
                                      _this._emitEvent({ "obj" : _this._getNode(obj) }, "load_node", true);
                                      s_call.call(this);
                                  }, e_call);
     },


     /**
       *   JsonTreeDataSource's _is_loaded()
       *   @return {boolean}
       *   @private
       */
     _is_loaded_DS : function (obj)
     { 
//     var s = this.options["data"]["json"];

       obj = this._getNode(obj); 

//     return (obj === -1 || !obj ) || (!s["ajax"] && !this._data.ds.progressiveRender && !$.isFunction(s["data"]))
       return (obj === -1 || !obj ) || obj.is(".oj-expanded, .oj-tree-leaf")
                                    || obj.children("ul").children("li").length > 0;
     },


     /**
       *   JsonTreeDataSource's _refreshDS
       *  @private
       */
     _refresh_DS  : function (obj)
     {
        obj = this._getNode(obj);

//      var s = this.options["data"]["json"];
//      if (obj && obj !== -1 && this._data.ds.progressiveUnload && ($.isFunction(s["data"]) || !!s["ajax"]))  {
        if (obj && obj !== -1)  {
          obj.removeData(OJT_CHILDREN);
        }
        return this._refresh_ui(obj);
     },


     /**
       *  JSON_data's _load_node()
       *  @private
       */
     _load_node_J : function (obj, s_call, e_call)
     {
        var _this = this;
        this._load_node_json(obj,                             // node or -1 for tree
                             function ()                      // success func
                              {
                                _this._emitEvent({ "obj" : _this._getNode(obj) }, "load_node", true);
                                s_call.call(this);
                              },
                              e_call);                        // error func
     },

     /**
       *   JSON _data's _is_loaded()
       *   @private
       */
     _is_loaded_J : function (obj)
     { 
       var s = this.options["data"];

       obj = this._getNode(obj); 
       return obj == -1 || !obj || (!s["ajax"] && !this._data.ds.progressiveRender && !$.isFunction(s["data"]))
                        || obj.is(".oj-expanded, .oj-tree-leaf")
                        || obj.children("ul").children("li").length > 0;
     },


     /**
       *   html_data's _load_node
       *   @private
       */
     _load_node_H : function (obj, s_call, e_call)
     {
       var _this = this;
       this._load_node_html(obj, function ()                   // success func
                                   {
                                     _this._emitEvent({ "obj" : _this._getNode(obj)}, "load_node", true) ;
                                     s_call.call(this);
                                   },
                                   e_call);                   // error func
     },

     /**
       *  html_data's _is_loaded()
       *  @private
       */
     _is_loaded_H : function (obj)
     { 
       var s    = this.options["data"],
           data = null,
           ajax = null ;

      if (s) {
        data  = s["data"] || null ;
        ajax  = s["ajax"] || null ;
      }

       obj = this._getNode(obj); 

       return obj == -1 || !obj || (!ajax && !$.isFunction(data)) ||
                           obj.is(".oj-expanded, .oj-tree-leaf") || obj.children("ul").children("li").size() > 0;
     },

       
     //  Rselect nodes as of last savedSelected()
     "reselect" : function ()
     {
        var _this = this,
            s      = this._data.ui.to_select;

        s = $.map($.makeArray(s), function (n) {
                           return "#" + n.toString().replace(/^#/,"")
                                                    .replace(/\\\//g,"/").replace(/\//g,"\\\/").replace(/\\\./g,".").replace(/\./g,"\\.").replace(/\:/g,"\\:"); });
        // this.deselect_all(); WHY deselect, breaks plugin state notifier?
        $.each(s, function (i, val)
                   {
                      if (val && val !== "#")  {
                        _this["select"](val);
                      }
                   });

        this._data.ui.selected = this._data.ui.selected.filter(function ()
                                           {
                                             return this["parentNode"];
                                           });
        this._emitEvent(null, "reselect", true);
     },

     // Save selection state - see also reselect()
     "saveSelected" : function ()
     {
        var _this = this;

        this._data.ui.to_select = [];
        this._data.ui.selected.each(function ()
                  {
                    if (this.id) {
                      _this._data.ui.to_select.push("#" + this.id.toString().replace(/^#/,"").replace(/\\\//g,"/").replace(/\//g,"\\\/").replace(/\\\./g,".").replace(/\./g,"\\.").replace(/\:/g,"\\:")); 
                     }
                  });
        this._emitEvent(this._data.ui.to_select, "savedselected", true);
     },


     /*
      *      Not documented
      */
     "rollback" : function (rb)
     {
        if (rb)  {
          if (!$.isArray(rb))  {
            rb = [ rb ];
          }
//        $.each(rb, function (i, val)
//                   {
//                     instances[val.i]["set_rollback"](val.h, val.d);     //TDO
//                   });
        }
    },


    "get_rollback" : function ()
    { 
       this._emitEvent(null, "get_rollback", true);
       return {
                i : this._getIndex(),
                h : this._$container.children("ul").clone(true),
                d : this["data"]                    // TDO ???
              }; 
    },
           
/*  Currently not used
    "set_rollback" : function (html, data)
    {
       if (this._$container && this._$container_ul)   {
         this._$container_ul.empty().append(html);
       }

       this["data"] = data;                       // TDO ???
       this._emitEvent(null, "set_rollback", true);
    },
*/

    /**
      *  Load json for a particular node (or the whole tree)
      *  @private
      */
    _load_node_tree : function (obj, s_call, e_call)
    {
       var parentKey = (obj && obj != -1)? obj[0].id : (obj? obj : -1) ;
       var rslt = this._JsonDSToJson(parentKey, obj) ;

       if ((! rslt.success) || (! rslt.js))  {
         return ;       // TDO
       }

       var  bTree = ((! obj) || (obj === -1)) ;
       var  s     = this.options["data"] ;
       var  d ;

       if ((!!s["data"] && !s["ajax"]) || (!!s["data"] && !!s["ajax"] && bTree))  {
         if (bTree)  {
           d = this._parseJson(rslt.js, obj) ;
           if (d)  {
             this._$container_ul.empty().append(d.children());      //@HTMLUpdateOK
             this._cleanNode();
           }
           else  { 
              if (this._data.ds.correctState) {
                  this._$container_ul.empty();
               }
           }
         }
         if (s_call) {
            s_call.call(this);
         }
       }
       else if  ((!s["data"] && !!s["ajax"]) || (!!s["data"] && !!s["ajax"] && (! bTree))) {
          d = this._parseJson(rslt.js, obj);
          if (d)  {
            if (bTree)  {
              var $u =  this._$container_ul;
              $u.empty().append(d.children());                     //@HTMLUpdateOK
              $u.attr(WA_ROLE, WA_TREE).attr("tabindex", "0").css("outline", "none") ;
              if (this._data.core.selectMode === -1)  {
                $u.attr("aria-multiselectable", true) ;
              }
            }
            else  {
              obj.append(d).children("a.oj-tree-loading").removeClass(OJT_LOADING);   //@HTMLUpdateOK
              obj.removeData("oj-tree-is-loading");
            }
   
            this._cleanNode(obj);
            if (s_call)  {
              s_call.call(this);
            }
          }
          else  {
            if (bTree) {
              if (this._data.ds.correctState)  { 
                this._$container_ul.empty(); 
                if (s_call) {
                  s_call.call(this);
                }
              }
            }
            else  {
               obj.children("a.oj-tree-loading").removeClass(OJT_LOADING);
               obj.removeData("oj-tree-is-loading");
               if (s["correct_state"]) { 
                 this._correct_state(obj);
                 if (s_call)  {
                   s_call.call(this);
                 } 
               }
            }
          }
       }

     },


    /**
      * Process a JsonTreeDataSource to a Json array ready for parsing.
      * This is temporary - it assumes that all nodes are available in the Json;
      * that is, there is no lazy-loading.   TDO
      * @private
      * @return {Object} contains "success" (boolean) and "js" (the json nodes)
      */
     _JsonDSToJson : function (parentKey, node)
     {
        var arJson = [],
            ds     = this._tds,
            cc,
            range  = {},
            rslt   = {success : false, js : null} ;

        if (parentKey == -1) {
          parentKey = null ;
          range["start"] = 0 ;
        }
        else {
          // TDO
        }

        cc = ds.getChildCount(parentKey) ;   
        if (cc > 0) {
          range["count"] = cc ;
          ds.fetchChildren(parentKey, range,              // get the JsonNodeSet 
              {
                "success" : $.proxy(function(jns) {
                     var c = jns.getCount(),
                         attr,
                         n ;

                     for (var i = 0; i < c; i++) {
                        node = {} ;
                        attr = jns.getData(i) ;           // get the attribute list to be applied 
                        if (attr) {
                          node["attr"] = attr ;
                        }
                       node["title"] = jns.m_nodes[i]["title"] ;         // hack, wait for chadwick
                       if (attr.metadata) {
                         node["metadata"] = jns.m_nodes[i]["metadata"] ; // hack, wait for chadwick
                       }

                       var key  = node["attr"]["id"] ;

                       n = ds.getChildCount(key) ;
                       if (n > 0) {
                          var r = this._JsonDSToJson(key, node) ;
                          node["children"] = r.js ;
                       }

                       arJson.push(node) ;
                     }
  
                     rslt.success = true ;
                     rslt.js      = arJson ;
                   }, this),

                "error" :  function(status) {
                              rslt.success = false ;
                           }
              }) ;
        }
        return rslt ;
     },


     /**
       *  @private
       */
     _refresh_json  : function (obj)
     {
        obj = this._getNode(obj);

        if (this._data.core.locked) {
          return ;
        }

        var bTree = (!obj || obj !== -1 || !obj.length) ;
        if (!bTree && obj.hasClass(OJ_DISABLED)) {
          return ;
        }

        var s = this.options["data"]["json"];

        if (! bTree && this._data.ds.progressiveUnload && ($.isFunction(s["data"]) || !!s["ajax"]))  {
          obj.removeData(OJT_CHILDREN);
        }
        return this._refresh_ui(obj);
     },


     /**
       *  Load json for a particular node (or -1 for the whole tree). s_call and e_call are
       *  optional success and error callback's.
       *  @private
       */
     _load_node_json : function (obj, s_call, e_call)
     {
       var s = this._getOptions()["data"],        // work on a copy of the options to avoid
           d,                                     // endless loop in calling success/error funcs
           error_func   = function () {},
           success_func = function () {},
           data         = (s && s["data"]) || null,
           ajax         = (s && s["ajax"]) || null;

       if (s && !data && !ajax) {
         data = s ;                               // we just have data, no data() ajax() methods defined
       }

       obj = this._getNode(obj);

       if (obj && obj !== -1 && (this._data.ds.progressiveRender || this._data.ds.progressiveUnload)
                             && !obj.is(".oj-expanded, .oj-tree-leaf")
                             && obj.children("ul").children("li").length === 0 && obj.data(OJT_CHILDREN)) {
          d = this._parseJson(obj.data(OJT_CHILDREN), obj);
          if (d)  {
            obj.append(d);                                             //@HTMLUpdateOK
            if (! this._data.ds.progressiveUnload) {
              obj.removeData(OJT_CHILDREN);
            }
          }

          this._cleanNode(obj);
          if (s_call)  {
            s_call.call(this);
          }
          return;
       }

       if (obj && obj !== -1) {
         if (obj.data("oj-tree-is-loading"))  {
            return;
         }
         else  {
            obj.data("oj-tree-is-loading",true);
         }
       }

       switch (!0)
       {
          case (!data && !ajax) : throw "ojTree - neither data nor ajax settings supplied.";

          // function option added here for easier model integration (also supporting async - see callback)

          case ($.isFunction(data)):
                       data.call(this, obj, $.proxy(function (d) {
                           d = this._parseJson(d, obj);
                           if (!d) { 
                              if (obj === -1 || !obj)  {
                                if (this._data.ds.correctState)  {
                                  this._$container_ul.empty();
                                }
                              }
                              else  {
                                 obj.children("a.oj-tree-loading").removeClass(OJT_LOADING);
                                 obj.removeData("oj-tree-is-loading");

                                 if (this._data.ds.correctState) {
                                   this._correct_state(obj);
                                 }
                              }
                              if (e_call) {
                                e_call.call(this);
                              }
                           }
                           else  {
                              if (obj === -1 || !obj)  {
                                 this._$container_ul.empty().append(d.children());    //@HTMLUpdateOK
                              }
                              else  {
                                 obj.append(d).children("a.oj-tree-loading").removeClass(OJT_LOADING);   //@HTMLUpdateOK
                                 obj.removeData("oj-tree-is-loading");
                              }
                              this._cleanNode(obj);
                              if (s_call)  {
                                s_call.call(this);
                              }
                           }
                       }, this));
                       break;

          case (!!data && !ajax) || (!!data && !!ajax && (!obj || obj === -1)):

                       if (!obj || obj == -1)  {
                         d = this._parseJson(data, obj) ;
                         if (d)  {
                           this._$container_ul.empty().append(d.children());    //@HTMLUpdateOK
                           this._cleanNode();
                         }
                         else  { 
                            if (this._data.ds.correctState) {
                              this._$container_ul.empty();
                             }
                         }
                       }
                       if (s_call) {
                          s_call.call(this);
                       }
                       break;

          case (!data && !!ajax) || (!!data && !!ajax && obj && obj !== -1):

                       error_func = function (x, status, e)
                       {
                         var ef = this._getOptions()["data"]["ajax"]["error"];  // reget the options       
                         if (ef) {                                              // without our updated ajax
                           ef.call(this, status, e, x);                         // changes to avoid forever loop
                         }
                         if (obj != -1 && obj.length)  {
                            obj.children("a.oj-tree-loading").removeClass(OJT_LOADING);
                            obj.removeData("oj-tree-is-loading");
                            if (status === "success" && this._data.ds.correctState)  {
                              this._correct_state(obj);
                            }
                         }
                         else   {
                           if ((status == "error") || (status === "success" && this._data.ds.correctState))  {
                             this._$container_ul.empty();             // remove tree loading icon/node
                           }
                         }
                         if (e_call)  {
                           e_call.call(this);
                         }
                       };

                       success_func = function (d, status, x)
                       {
                         var sf = this._getOptions()["data"]["ajax"]["success"];  // reget the options
                         if (sf) {                                                // without our updated ajax
                           d = sf.call(this, d, status, x) || d;                  // changes to avoid forever loop
                         }

                         // Check if all whitespace since JSON.parse() will barf on this. 
                         if (typeof d == "string") {
                           var tempd = d.replace(/^[\s\n]+$/,"") ;
                           try {
                                 tempd = $.parseJSON(tempd) ;
                               } catch(err) {tempd = null;}     // have nothing useful to display

                           if (! tempd) {
                             return error_func.call(this, x, "Bad JSON", "");
                           }
                         }

                         d = this._parseJson(d, obj);        // parse json and get a $(ul)
                         if (d)  {
                            if (obj === -1 || !obj)  {
                               var $u =  this._$container_ul;
                               $u.empty().append(d.children());                                         //@HTMLUpdateOK
                               $u.attr(WA_ROLE, WA_TREE).attr("tabindex", "0").css("outline", "none") ;
                               if (this._data.core.selectMode === -1)  {
                                 $u.attr("aria-multiselectable", true) ;
                               }
                             }
                             else   {
                                obj.append(d).children("a.oj-tree-loading").removeClass(OJT_LOADING);   //@HTMLUpdateOK
                                obj.removeData("oj-tree-is-loading");
                             }

                             this._cleanNode(obj);
                             if (s_call)  {
                               s_call.call(this);
                             }
                         }
                         else  {                             // parse returned nothing
                           if (obj === -1 || !obj) {
                              if (this._data.ds.correctState)  { 
                                 this._$container_ul.empty(); 
                                 if (s_call) {
                                   s_call.call(this);
                                 }
                              }
                           }
                           else  {
                              obj.children("a.oj-tree-loading").removeClass(OJT_LOADING);
                              obj.removeData("oj-tree-is-loading");
                              if (this._data.ds.correctState) { 
                                this._correct_state(obj);
                                if (s_call)  {
                                  s_call.call(this);
                                } 
                              }
                           }
                         }
                       };

                       //  Prepare for an ajax op. (note: we are updating a copy of the options)
                       s["ajax"]["context"] = this;
                       s["ajax"]["error"]   = error_func;
                       s["ajax"]["success"] = success_func;

                       if (! s["dataType"])  {
                         s["ajax"]["dataType"] = "json";
                       }
                       if ($.isFunction(s["ajax"]["url"]))  {
                         s["ajax"]["url"] = s["ajax"]["url"].call(this, obj);
                       }
                       if ($.isFunction(s["ajax"]["data"]))  {
                         s["ajax"]["data"] = s["ajax"]["data"].call(this, obj);
                       }
                       $.ajax(s["ajax"]);
                       break;                 // end case

       }   // end switch

     },


     /**
       *  Parses a JSON representation of a tree or tree node.  Returns a jQuery
       *  wrapped <ul> structure.
       *  @private
       */
     _parseJson : function (js, obj, isRecurse)
     {
        var d = false, 
            tmp, i, j, ul1, ul2, title;

        if (!js) {
          return d;
        }

        if (this._data.ds.progressiveUnload && obj && obj !== -1)  { 
          obj.data(OJT_CHILDREN, d);
        }

        if (typeof js == "string")  {
          try {
                js = $.parseJSON(js) ;
              } catch(err) {js = [];}     // have nothing useful to display
        }

        if ($.isArray(js))  {             // array of ojects ?
          d = $('<ul>');
          if (! js.length)  {
            return false;
          }

          for (i = 0, j = js.length; i < j; i++)  {       // recurse until we have an individual node obj 
             tmp = this._parseJson(js[i], obj, true);
             if (tmp.length)  {
               d = d.append(tmp);                        //@HTMLUpdateOK
             }
          }

          d = d.children();
        }
        else  {
           //  We have an individual node object

           if (typeof js == "string")  {
             js = { "data" : js };
           }

//JRM      if(!js.data && js.data !== "") { return d; }

           title = (typeof js["title"] == "string")?  js["title"] : " " ;

           d = $("<li role='" + WA_TREEITEM + "' />");                  // start a node

           if (js["attr"])  {
             if (this._data.types.defType && (! js["attr"]["type"])) {  // if no type and "default" type
               js["attr"]["type"] = "oj-tree-deftype" ;                 // defined, add default type
               d.addClass(OJT_TYPE) ;
             }
             d.attr(js["attr"]);       // apply attr's to the <li>
           }
           if (js["metadata"])  {      // and any user defined arbitrary data
             d.data(js["metadata"]);
           }

           // js.state     // not published - per Design Review

           if (js["state"] || (js["children"] && js["children"].length === 0)) {    // length zero means lazy load
             d.addClass((js["state"] === "expanded")? OJ_EXPANDED : OJ_COLLAPSED);
           }

//         if (!$.isArray(js["data"])) {
//           tmp = js["data"];
//           js["data"] = [];
//           js["data"].push(tmp);
//         }
           
           if (! js["data"]) {
             js["data"] = {"dummy": 0} ;
           }

           var bIns = false,
               sp ;

           tmp = $("<a tabindex='-1' />");
           $.each(js["data"], function (i, m)   {
//                    tmp = $("<a role=presentation tabindex=-1 />");
                      if ($.isFunction(m)) {
                        m = m.call(this, js);
                      }
                      if (typeof m == "string")  {
//                      tmp.attr('href','#')[ ht? "html" : "text" ](title);
                      }
                      else  {
//                      if (!m["attr"]) {
//                        m["attr"] = {};
//                      }

//                      for (var x in m) {
//                         if (x !== "attr") {
//                           m["attr"][x] = m[x] ;
//                         }
//                      }

//                        if (!m["attr"]["href"]) {
//                          m["attr"]["href"] = '#';
//                        }

//                       tmp.attr(m["attr"]);                // apply attr's to the <a>
                       if (i == "attr") {
                         tmp.attr(m) ;
                       }
                       else if (i == "style") {
                         tmp.css(m) ;
                       }
                        if (i == "language") {
                          tmp.addClass(m);
                        }
                      }

                      if (! bIns) {
                        sp = $("<span class='" + OJT_TITLE + "'>") ;
                        sp[0].textContent = title ;       // per securty post-impl

                        tmp.prepend("<ins class='oj-tree-icon " + OJT_NICON + " oj-component-icon" + "'> </ins>", sp);    // node icon //@HTMLUpdateOK
                        bIns = true ;
                      }
                      if (!m["icon"] && js["icon"])  {
                        m["icon"] = js["icon"];
                      }
                      if (m["icon"])  { 
                        if (m["icon"].indexOf("/") === -1)  {
                          tmp.children("ins").addClass(m["icon"]);
                        }
                        else  {
                          tmp.children("ins").css("background","url('" + m["icon"] + "') center center no-repeat");
                        }
                      }
           });

           d.append(tmp);            // append the <a> and its children to the <li>        //@HTMLUpdateOK

           d.prepend("<ins class='" + OJ_DISC + "'> </ins>");      // potential disclosure icon     //@HTMLUpdateOK

           if (js["children"])  { 
             if (this._data.ds.progressiveRender && js["state"] !== "expanded")  {
               d.addClass(OJ_COLLAPSED).attr(WA_EXPANDED, "false")
                                       .data(OJT_CHILDREN, js["children"]);
             }
             else  {
               if (this._data.ds.progressiveUnload)  {
                 d.data(OJT_CHILDREN, js["children"]);
               }
               if ($.isArray(js["children"]) && js["children"].length) {
                 tmp = this._parseJson(js["children"], obj, true);
                 if (tmp.length)  {
                   ul2 = $("<ul role='" + WA_GROUP + "' />");
                   ul2.append(tmp);                                     //@HTMLUpdateOK
                   d.append(ul2);                                       //@HTMLUpdateOK
                 }
               }
             }
           }
        }  // end else

        if (! isRecurse)  {
          ul1 = $("<ul />");
          ul1.append(d);
          d = ul1;
        }

        return d;
     },

     /*
      *   Returns a Json representation of a node
      *   @private
      */
//    ---  Currently not used in V1  ---

//     _getJson : function (obj, li_attr, a_attr, is_callback)
//     {
//       var result = [], 
//           s      = this.options, 
//           _this  = this,
//           tmp1,
//           tmp2,
//           li,
//           a,
//           t,
//           lang;
//
//       obj = this._getNode(obj);
//
//       if (!obj || obj === -1)  {
//         obj = this._$container.find("> ul > li");
//       }
//       li_attr = $.isArray(li_attr) ? li_attr : [ "id", "class" ];
//
//       if (!is_callback && s["types"])  {
//         li_attr.push(s["types"]["attr"]);
//       }
//       a_attr = $.isArray(a_attr) ? a_attr : [ ];
//
//       obj.each(function () {
//                  li = $(this);
//                  tmp1 = { data : [] };
//
//                  if (li_attr.length)  {
//                    tmp1.attr = { };
//                  }
//                  $.each(li_attr, function (i, v)  { 
//                                    tmp2 = li.attr(v); 
//                                    if (tmp2 && tmp2.length && tmp2.replace(/oj-tree[^ ]*/ig,'').length)  {
//                                      tmp1.attr[v] = (" " + tmp2).replace(/ oj-tree[^ ]*/ig,'')
//                                                                 .replace(/\s+$/ig," ")
//                                                                 .replace(/^ /,"").replace(/ $/,""); 
//                                    }
//                  });
//
//                  if (li.hasClass(OJT_OPEN))  {
//                    tmp1.state = "open";
//                  }
//                  if (li.hasClass(OJT_CLOSED))  {
//                    tmp1.state = "closed";
//                  }
//                  if (li.data())  {
//                    tmp1.metadata = li.data();
//                  }
//                  a = li.children("a");
//                  a.each(function ()  {
//                          t = $(this);
//
//                          if ( a_attr.length || $.inArray("languages", s["plugins"]) !== -1 || 
////                             t.children("ins").get(0).style.backgroundImage.length || 
//                               t.children("ins").get(0)["style"]["backgroundImage"].length || 
////                            (t.children("ins").get(0).className && t.children("ins").get(0).className.replace(/oj-tree[^ ]*|$/ig,'').length))
//                              (t.children("ins").get(0)["className"] && t.children("ins").get(0)["className"].replace(/oj-tree[^ ]*|$/ig,'').length))
//                          { 
//                            lang = false;
//                            if ($.inArray("languages", s["plugins"]) !== -1 && $.isArray(s["languages"]) && s["languages"].length)  {
////                            $.each(s.languages, function (l, lv)   {
//                              $.each(s["languages"], function (l, lv)   {
//
//                                                     if (t.hasClass(lv))  {
//                                                       lang = lv;
//                                                       return false;
//                                                     }
//                              });
//                            }
//
//                            tmp2 = { "attr" : { }, "title" : _this.getText(t, lang) }; 
//                            $.each(a_attr, function (k, z) {
//                                tmp2.attr[z] = (" " + (t.attr(z) || "")).replace(/ oj-tree[^ ]*/ig,'')
//                                                                        .replace(/\s+$/ig," ")
//                                                                        .replace(/^ /,"")
//                                                                        .replace(/ $/,"");
//                            });
//                            if ($.inArray("languages", s["plugins"]) !== -1 && $.isArray(s["languages"]) && s["languages"].length) {
//                               $.each(s["languages"], function (k, z)  {
//                                  if (t.hasClass(z))  {
//                                    tmp2.language = z;
//                                    return true;
//                                  }
//                               });
//                            }
//
//                            if (t.children("ins").get(0)["className"].replace(/oj-tree[^ ]*|$/ig,'').replace(/^\s+$/ig,"").length)
//                            {
//                              tmp2.icon = t.children("ins").get(0)["className"].replace(/oj-tree[^ ]*|$/ig,'').replace(/\s+$/ig," ").replace(/^ /,"").replace(/ $/,"");
//                            }
//                            if (t.children("ins").get(0)["style"]["backgroundImage"].length)
//                            {
//                              tmp2.icon = t.children("ins").get(0)["style"]["backgroundImage"].replace("url(","").replace(")","");
//                            }
//                          }
//                          else   {
//                             tmp2 = _this["getText"](t);
//                          }
//
//                          if (a.length > 1)  {
//                            tmp1.data.push(tmp2);
//                          }
//                          else   {
//                            tmp1.data = tmp2;
//                         }
//                  });
//
//                  li = li.find("> ul > li");
//                  if (li.length)  {
//                     tmp1.children = _this._getJson(li, li_attr, a_attr, true);
//                  }
//                  result.push(tmp1);
//       });
//
//       return result;
//     },

     /**
       *  Corrects nodes by setting closed nodes to leaf nodes if no children found. 
       *  @private
       */
     _correct_state   : function (obj)
     {
         obj = this._getNode(obj);
         if (!obj || obj === -1) {
           return false;
         }
         obj.removeClass("oj-collapsed oj-expanded")
            .removeAttr(WA_EXPANDED)
            .addClass(OJT_LEAF)
            .children("ul")
            .remove();

         obj.find("ins:first-child").removeClass(OJ_DISC).addClass(OJT_ICON) ;
         this._emitEvent({ "obj" : obj }, "correct_state", true);
     },


     /*
      *  @private
      */
/*                                           // not used in V1
     _core_notify : function (n, data)
     {
        if (data.opened)  {
          this._expand(n, false, true);
        }
     },
*/

     /**
       *  Dummy function to fire after the first load (so that there is a loaded event)
       *  @private
       */
     _loaded  : function ()
     { 
        this._emitEvent(null, "loaded"); 
      },


     /**
       *   Process the user tree <ul> list placed in the tree div, or loaded via ajax.
       *   @private
       */
     _load_node_html : function (obj, s_call, e_call)
     {
       var s = this._getOptions()["data"],        // work on a copy of the options to avoid
           d,                                     // endless loop in calling success/error funcs
           error_func   = function () {},
           success_func = function () {},
           data         = (s && s["data"]) || null,
           ajax         = (s && s["ajax"]) || null;

       obj = this._getNode(obj);
       if (obj && obj !== -1)  {
         if (obj.data("oj-tree-is-loading")) {
           return;
         }
         else  {
           obj.data("oj-tree-is-loading", true);
         }
       }

       switch(!0)
       {
          case ((!data && !ajax) && (s && (typeof s === "string"))) :
                    this._loadHtmlString(s, obj, s_call, e_call) ;
                    break ;

          case $.isFunction(data):
                    data.call(this, obj, $.proxy(function (d)
                              {
                                 this._loadHtmlString(d, obj, s_call, e_call) ;
                              }, this));
                    break;

          case (!data && !ajax):
                    if (!obj || obj == -1 )  {
                      this._$container_ul
                              .empty()
                              .append(this._data.html.cloneMarkup)      //@HTMLUpdateOK
                              .find("li, a")
                              .filter(function () {
                                               return (!this.firstChild || !this.firstChild.tagName ||
                                                       this.firstChild.tagName !== "INS");
                                                   })
                              .prepend("<ins class='oj-tree-icon' > </ins>").end()          //@HTMLUpdateOK
                              .filter("a").children("ins:first-child")
                              .not(".oj-tree-node-icon")
                              .addClass(OJT_NICON)
                              .addClass(OJT_ICON);

                      this._$container_ul.find("li").children("ins:first-child")
                                                    .addClass(OJ_DISC);

                      //  No point in checking for empty parent <ul> because we don't have a
                      //  "data" option in the first place, so no lazy loading posssible.

                      //  Add the <a> text <span> for hover/click styling
                      this._insertHtmlTextSpan(this._$container_ul) ;

                      if (this._data.types.defType) {             // if "default" type defined
                        this._addDefType(this._$container_ul) ;   // apply to nodes with no asooc type
                      }

                      this._cleanNode();
                      this._$container_ul.find("ul").attr(WA_ROLE, WA_GROUP) ;
                      this._$container_ul.find("li").attr(WA_ROLE, WA_TREEITEM) ;
                      this._$container_ul.find("a").attr("tabindex", -1) ;
                    }
                    if (s_call)  {
                      s_call.call(this);
                    }
                    break;

          case (!!data && !ajax) || (!!data && !!ajax && (!obj || obj === -1)):
                    if (!obj || obj == -1) {
                           d = $(data);
                           if (!d.is("ul")) {
                             d = $("<ul />").append(d);            //@HTMLUpdateOK
                           }
                           this._$container_ul
                               .empty()
                               .append(d                           //@HTMLUpdateOK
                               .children())
                               .find("li, a")
                               .filter(function ()
                                           {
                                              return (!this.firstChild || !this.firstChild.tagName ||
                                                      this.firstChild.tagName !== "INS");
                                           })
                               .prepend("<ins class='oj-tree-icon'> </ins>").end()   //@HTMLUpdateOK
                               .filter("a")
                               .children("ins:first-child")
                               .not(".oj-tree-node-icon")
                               .addClass(OJT_NICON)
                               .addClass(OJT_ICON);

                           this._$container_ul.find("li.oj-tree-leaf ins:first-child").removeClass(OJ_DISC)
                                                                                      .removeClass(OJT_NICON)
                                                                                      .addClass(OJT_ICON);

                           //  Add the <a> text <span> for hover/click styling
                           this._insertHtmlTextSpan(this._$container_ul) ;

                           if (this._data.types.defType) {             // if "default" type defined
                             this._addDefType(this._$container_ul) ;   // apply to nodes with no asooc type
                           }

                           this._cleanNode();
                           this._$container_ul.find("ul").attr(WA_ROLE, WA_GROUP) ;
                           this._$container_ul.find("li").attr(WA_ROLE, WA_TREEITEM) ;
                           this._$container_ul.find("a").attr("tabindex", "-1") ;
                    }
                    if (s_call)  {
                         s_call.call(this);
                    }
                    break;

          case (!data && !!ajax) || (!!data && !!ajax && obj && obj !== -1):
                    obj = this._getNode(obj);
                    error_func = function (x, t, e)
                         {
                           var ef = this._getOptions()["data"]["ajax"]["error"];  // reget the options
                           if (ef) {                                              // without our updated ajax
                             ef.call(this, x, t, e);                              // changes to avoid forever loop
                           }

                           if (obj != -1 && obj.length)  {
                             obj.children("a.oj-tree-loading").removeClass(OJT_LOADING);
                             obj.removeData("oj-tree-is-loading");
                             if (t === "success" && this._data.ds.correctState) {
                                this._correct_state(obj);
                             }
                           }
                           else  {
                             if (t === "success" && this._data.ds.correctState) {
                               this._$container_ul.empty();
                             }
                           }
                           if (e_call)  {
                             e_call.call(this);
                           }
                        };
                    success_func = function (d, t, x)
                        {
                          var parent, lazy ;
                          var sf = this._getOptions()["data"]["ajax"]["success"];  // reget the options
                          if (sf) {                                                // without our updated ajax
                            d = sf.call(this, d, t, x) || d;                       // changes to avoid forever loop
                          }

                          if (d === "" || (d && d.toString && d.toString().replace(/^[\s\n]+$/,"") === "")) {
                            return error_func.call(this, x, t, "");
                          }

                          if (d)  {
                            d = $(d);
                            if (!d.is("ul")) {
                              d = $("<ul />").append(d);             //@HTMLUpdateOK
                            }
                            if (obj == -1 || !obj) {
                              this._$container_ul.empty()
                                                 .append(d.children())               //@HTMLUpdateOK
                                                 .find("li, a")
                                                 .filter(function ()
                                                    {
                                                      return !this.firstChild || !this.firstChild.tagName ||
                                                                                 this.firstChild.tagName !== "INS";
                                                    })
                                                 .prepend("<ins class='oj-tree-icon'> </ins>")    //@HTMLUpdateOK
                                                 .end()
                                                 .filter("a")
                                                 .children("ins:first-child")
                                                 .not(".oj-tree-node-icon")
                                                 .addClass(OJT_NICON)
                                                 .addClass(OJT_ICON);

                              this._$container_ul.find("li.oj-tree-leaf ins:first-child").removeClass(OJ_DISC)
                                                                                         .removeClass(OJT_NICON)
                                                                                         .addClass(OJT_ICON);
                              parent = this._$container_ul ;
                            }
                            else  {
                               obj.children("a.oj-tree-loading")
                                  .removeClass(OJT_LOADING);

                               this._removeEmptyUL(obj) ;

                               obj.append(d)                           //@HTMLUpdateOK
                                  .children("ul")
                                  .find("li, a")
                                  .filter(function () {
                                             return (!this.firstChild || !this.firstChild.tagName ||
                                                                         this.firstChild.tagName !== "INS");
                                          })
                                  .prepend("<ins class='oj-tree-icon'> </ins>")     //@HTMLUpdateOK
                                  .end()
                                  .filter("a")
                                  .children("ins:first-child")
                                  .not(".oj-tree-node-icon")
                                  .addClass(OJT_NICON)
                                  .addClass(OJT_ICON);
                               obj.removeData("oj-tree-is-loading");

                               obj.find("li.oj-tree-leaf ins:first-child").removeClass(OJ_DISC)
                                                                          .removeClass(OJT_NICON)
                                                                          .addClass(OJT_ICON);
                               parent = obj ;
                            }

                            //  Look for parents with empty children <ul> list (lazy loading),
                            //  and add the closed class to make it a parent.
                            this._handleHtmlParentNoChildren(parent) ;

                            //  Add the <a> text <span> for hover/click styling
                            this._insertHtmlTextSpan(parent) ;

                            //  Escape all Ajax loaded node text for security
                            var nodes = parent.children("UL") ;
                            if (nodes.length >= 1) {
                              nodes = nodes.first().find("span.oj-tree-title") ;
                              var _this = this ;
                              $.each(nodes, function() {
                                             this.textContent = _this._escapeHtml(this.textContent) ;
                                             });
                            }

                            // If "default" type defined, apply to nodes with no assoc type
                            if (this._data.types.defType && parent)  {
                              this._addDefType(this._$container_ul) ;
                            }

                            this._cleanNode(obj);
                            if (s_call)  {
                              s_call.call(this);
                            }
                          }
                          else  {
                            if (obj && obj !== -1)  {
                               obj.children("a.oj-tree-loading").removeClass(OJT_LOADING);
                               obj.removeData("oj-tree-is-loading");
                               if (this._data.ds.correctState)  { 
                                  this._correct_state(obj);
                                  if (s_call) {
                                    s_call.call(this);
                                  } 
                               }
                              }
                              else  {
                                if (this._data.ds.correctState)  { 
                                   this._$container_ul.empty();
                                   if (s_call) {
                                     s_call.call(this);
                                   } 
                                }
                              }
                              this._$container_ul.find("ul").attr(WA_ROLE, WA_GROUP) ;
                              this._$container_ul.find("li").attr(WA_ROLE, WA_TREEITEM) ;
                          }
                        };

                        //  Prepare for an ajax op. (note: we are updating a copy of the options)
                        s["ajax"]["context"] = this;
                        s["ajax"]["error"]   = error_func;
                        s["ajax"]["success"] = success_func;

                        if (!s["ajax"]["dataType"]) {
                          s["ajax"]["dataType"] = "html";
                        }
                        if ($.isFunction(s["ajax"]["url"])) {
                          s["ajax"]["url"] = s["ajax"]["url"].call(this, obj);
                        }
                        if ($.isFunction(s["ajax"]["data"])) {
                          s["ajax"]["data"] = s["ajax"]["data"].call(this, obj);
                        }
                        $.ajax(s["ajax"]);
                        break;
       }
     },


     /**
       *   Look for parents with empty children <ul> list (lazy loading), and add the closed
       *   class to make it a parent.
       *   @private
       */
     _handleHtmlParentNoChildren : function (parent)
     {
        var lazy = parent
                     .find(parent.is("ul")?  "li ul" : "ul")
                     .filter(function () {
                                return (!this.firstChild ||
                                        this.childNodes.length == 0 ||
                                        (this.childNodes.length == 1 && this.firstChild.nodeType ==3));
                                       }) ;
        $.each(lazy, function() {
                                  $(this).closest("li")
                                         .addClass(OJ_COLLAPSED) ;
                                }) ;
     },


     /**
       *   Remove an empty <ul> in a node (lazy loading).
       *   @private
       */
     _removeEmptyUL : function(parent)
     {
        var l = parent.find("ul")
                     .filter(function () {
                                return (!this.firstChild ||
                                        this.childNodes.length == 0 ||
                                        (this.childNodes.length == 1 && this.firstChild.nodeType ==3));
                                       }) ;
       if (l.length > 0) {
         l.remove() ;
       }
     },


     /**
       *   Load an HTML <ul><li>...</ul> markup string
       *   @private
       */
     _loadHtmlString : function (s, obj, s_call, e_call)
     {
        var parent ;

        if (s && s !== "" && s.toString && s.toString().replace(/^[\s\n]+$/,"") !== "") {
          s = $(s);
          if (! s.is("ul")) {
            s = $("<ul />").append(s);                     //@HTMLUpdateOK
          }

          if (obj == -1 || !obj) {
            this._$container_ul.empty()
                               .append(s.children())       //@HTMLUpdateOK
                               .find("li, a")
                               .filter(function () {
                                         return (!this.firstChild || !this.firstChild.tagName ||
                                                 this.firstChild.tagName !== "INS");
                                       })
                               .prepend("<ins class='oj-tree-icon oj-tree-disclosure-icon'> </ins>")  //@HTMLUpdateOK
                               .end().filter("a")
                               .children("ins:first-child")
                               .not(".oj-tree-node-icon")
                               .addClass(OJT_NICON)
                               .addClass(OJT_ICON);

            this._$container_ul.find("li.oj-tree-leaf ins:first-child").removeClass(OJ_DISC)
                                                                       .removeClass(OJT_NICON)
                                                                       .addClass(OJT_ICON);

            parent = this._$container_ul ;

            //  No point in checking for empty parent <ul> because we don't have a
            //  "data" option in the first place, so no lazy loading posssible.

            //  If the "default" node type has been defined, add the def type to any nodes
            //  that have not been given an explicit type
            this._addDefType(this._$container_ul) ;
          }
          else  {
            obj.children("a.oj-tree-loading").removeClass(OJT_LOADING);
            obj.append(s)                                                    //@HTMLUpdateOK
               .children("ul")
               .find("li, a")
               .filter(function ()
                          {
                             return !this.firstChild || !this.firstChild.tagName ||
                                                         this.firstChild.tagName !== "INS";
                          })
               .prepend("<ins class='oj-tree-icon'> </ins>")         //@HTMLUpdateOK
               .end().filter("a")
               .children("ins:first-child")
               .not(".oj-tree-node-icon")
               .addClass("oj-tree-node-icon")
               .addClass("oj-tree-icon");
            obj.removeData("oj-tree-is-loading");

            parent = obj ;

            //  If the "default" node type has been defined, add the def type to
            //  any nodes that have not been given an explicit type
            this._addDefType(this.obj) ;
          }

          //  Add the <a> text <span> for hover/click styling
          if (parent) {
            this._insertHtmlTextSpan(parent) ;
          }

          //  Escape all node text for security
          var nodes = parent.children("UL") ;
          if (nodes.length >= 1) {
            nodes = nodes.first().find("span.oj-tree-title") ;
            var _this = this ;
            $.each(nodes, function() {
                           this.textContent = _this._escapeHtml(this.textContent) ;
                           });
          }

          this._cleanNode(obj);
          if (s_call)  {
            s_call.call(this);
          }
        }
        else  {
          if (obj && obj !== -1)  {
            obj.children("a.oj-tree-loading").removeClass(OJT_LOADING);
            obj.removeData("oj-tree-is-loading");
            if (this._data.ds.correctState) { 
              this._correct_state(obj);
              if (s_call) {
                s_call.call(this);
              } 
            }
          }
          else  {
            if (this._data.ds.correctState)
            { 
              this._$container_ul.empty();
              if (s_call) {
                s_call.call(this);
              } 
            }
          }
        }
     },

     /**
       *  Insert the <a> text <span> for hover/click styling
       *  @private
       */
     _insertHtmlTextSpan : function(elem)
     {
        $.each(elem.find("li a"), function (i, val) {
             var ih = val.innerHTML ;
             ih = ih.replace("ins>", "ins><span class='" + OJT_TITLE + "'>") ;
             ih += "</span>" ;
             val.innerHTML = ih ;
        });
     },

     /**
       *  Add the "default" type attr to all nodes that do not have an explicit type defined.
       *  @private
       */
     _addDefType : function(obj)
     {
        var s, typeAttr ;
        if (this._data.types.defType) {
          s = this.options["types"];
          typeAttr = s? s["attr"] : this._data.types.defaults["attr"];

          $.each(obj.find("li"), function (i, val) {
                  val = $(val)
                  if (! val.attr(typeAttr)) {
                    val.attr(typeAttr, "oj-tree-deftype").addClass(OJT_TYPE) ;
                  }
          });
        }
     },


     /**
       *
       *   @private
       */
     _dnd_prepare : function ()
     {
       var  vars = this._data.dnd.vars ;

       if (! vars.r || ! vars.r.length) {
         return;
       }
       this._data.dnd.off = vars.r.offset();   // get pos of dragged node relative to doc

       if (this._isRtl) {
         // arrow icon is the right of the marker line
         this._data.dnd.off.right  = this._data.dnd.off.left + vars.r.width() - vars.r.find(">a").width() -14 ;
       }
       this._data.dnd.targ_ml_width = vars.r.find("a").width();
//   this._data.dnd.targ_ml_width = this._data.dnd.targ_ml_width > 100? 100 : this._data.dnd.targ_ml_width ;
//   this._data.dnd.targ_ml_width = this._data.dnd.targ_ml_width < 25? 25   : this._data.dnd.targ_ml_width ;

       if (this._data.dnd.foreign)  {

         var a = this.options["dnd"]["drag_check"].call(this, { "o" : vars.o,
                                                                "r" : vars.r });
         this._data.dnd["after"]   = a["after"];
         this._data.dnd["before"]  = a["before"];
         this._data.dnd["inside"]  = a["inside"];
         this._data.dnd.prepared   = true;
         return this._dnd_show();
       }

       this._prepare_move(vars.o, vars.r, "before");
       this._data.dnd["before"] = this["check_move"]();
       this._prepare_move(vars.o, vars.r, "after");
       this._data.dnd["after"]  = this["check_move"]();

       if (this._is_loaded(vars.r)) {
         this._prepare_move(vars.o, vars.r, "inside");
         this._data.dnd["inside"] = this["check_move"]();
       }
       else  {
         this._data.dnd["inside"] = false;
       }

       this._data.dnd.prepared = true;
       return this._dnd_show();
     },


     /**
       *  Positions and shows (or hides) the drag insert position marker and line, and
       *  updates the drag and drop targets with the can/can't drop classes.
       *  @private
       */
     _dnd_show : function ()
     {
        var  dnd      = this._data.dnd,
             isParent = false,
             mLeft,                // pos of marker rel to document
             mlLeft,               // position of marker line rel to doc
             lineTop ;             // top pos of marker line rel to doc

        if (! dnd.prepared)  {
          return;
        }

        var o     =  ["before","inside","after"],
            r     =  false,
            pos,
            ctl   =  dnd.ctl,
            vars  =  dnd.vars ;

        if (dnd.w < this._data.core.li_height/3)  {
           o = ["before","inside","after"];
        }
        else if (dnd.w <= this._data.core.li_height * 2/3)  {
          o = dnd.w < this._data.core.li_height/2 ? ["inside","before","after"] :
                                                    ["inside","after" ,"before"] ;
        }
        else {
          o = ["after","inside","before"];
        }
 
        $.each(o, $.proxy(function (i, val)
           { 
             if (this._data.dnd[val])  {
               ctl.helper.children("ins")
                         .removeClass(OJT_DROP_INVALID)
                         .addClass(OJT_DROP_OK);
               this._getContainer().removeClass("oj-invalid-drop") ;      // set cursor on tree div
               r = val;
               return false;
             }
        }, this));

        if (r === false)  {                           // not a valid drop position
          ctl.helper.children("ins")
                    .removeClass(OJT_DROP_OK)
                    .addClass(OJT_DROP_INVALID);
          vars.r.addClass(OJ_INVALID_DROP) ;
        }
        else {
          vars.r.removeClass(OJ_INVALID_DROP).addClass(OJ_VALID_DROP) ; 
        }
        
        pos       = this._isRtl ? (this._data.dnd.off.right - 18) : (this._data.dnd.off.left + 5);

        isParent  = ! this["isLeaf"](vars.r);
        mLeft     = this._isRtl? (pos + this._data.dnd.targ_ml_width + 18) : pos ;  // pos of marker rel to doc
        mlLeft    = this._isRtl? (mLeft - this._data.dnd.ml_width) : (pos + 8)      // pos of marker line rel to doc
        
        switch(r)
        {
            case "before":
                lineTop = this._data.dnd.off.top - 3 ;

                vars.m.css({ "left" : mLeft + "px",
                             "top"  : (lineTop -7) + "px" 
                           }).show();
                if (vars.ml) {
                    vars.ml.css({ "left" : mlLeft + "px",
                                  "top"  : lineTop + "px" 
                                }).show();
                }
                break;

            case "after":
                lineTop = this._data.dnd.off.top + this._data.core.li_height - 3 ;
                vars.m.css({ "left" : mLeft + "px",
                             "top"  : (lineTop - 2) + "px" 
                           }).show();
                if (vars.ml) {
                   vars.ml.css({ "left" : mlLeft + "px",
                                 "top" : (lineTop + 6) + "px" 
                               }).show();
                }
                break;

            case "inside":
                vars.m.css({ "left" : mLeft + (this._isRtl? (isParent? -4 : 0) : 4) + "px",
                             "top" : (this._data.dnd.off.top + this._data.core.li_height/2 - 8) + "px" 
                            }).show();
                if (vars.ml)  {
                  vars.ml.hide();
                }
                break;

            default:
                vars.m.hide();
                if (vars.ml) {
                  vars.ml.hide();
                }
                break;
        }

        vars.last_pos = r;
        return  r;
     },


     /**
       *   A node's %lt;a> link has been moused over, or the mouse is moving
       *   within it.
       *   @private
       */
     _dnd_enter : function (obj)
     {
        var dnd  = this._data.dnd,
            vars = dnd.vars ;

        if (dnd.mto)  { 
          clearTimeout(dnd.mto);
          dnd.mto = false;
        }

        dnd.prepared = false;
        vars.r = this._getNode(obj);

        var s             = this.options["dnd"];
        var checkTimeout  = s["check_timeout"] ;

        if (checkTimeout)  { 

           // do the calculations after a minimal timeout (users tend to drag quickly to the desired location)

           if (dnd.to1)  {
             clearTimeout(dnd.to1);
           }
           dnd.to1 = setTimeout($.proxy(this._dnd_prepare, this), checkTimeout); 
        }
        else { 
            this._dnd_prepare(); 
        }

        var openTimeout  = s["open_timeout"] ;
        if (openTimeout)  { 
          if (dnd.to2)  {
            clearTimeout(dnd.to2);
          }
          if (vars.r && vars.r.length && vars.r.hasClass(OJ_COLLAPSED))  { 
            // if the node is closed - open it, then recalculate
            dnd.to2 = setTimeout($.proxy(this._dnd_open, this), openTimeout);
          }
        }
        else   {
          if (vars.r && vars.r.length && vars.r.hasClass(OJ_COLLAPSED))  { 
             this._dnd_open();
          }
        }
  
     },


     /**
       *  The mouse has left a node while dragging
       *   @private
       */
     _dnd_leave : function (e)
     {
        var dnd  = this._data.dnd,
            vars = this._data.dnd.vars ;

        dnd["after"]   = false;
        dnd["before"]  = false;
        dnd["inside"]  = false;

        this._data.dnd.ctl.helper.children("ins")
                                 .removeClass(OJT_DROP_OK)
                                 .addClass(OJT_DROP_INVALID);
        this._getContainer().addClass("oj-invalid-drop") ;      // set cursor on tree div

        if (vars.o) {
          vars.o.removeClass(OJ_VALID_DROP).removeClass(OJ_INVALID_DROP) ;
        }
        if (vars.r) {
          vars.r.removeClass(OJ_VALID_DROP).removeClass(OJ_INVALID_DROP) ;
        }

        vars.m.hide();

        if (vars.ml) {
          vars.ml.hide();
        }
        if (vars.r && vars.r[0] === e.target.parentNode)
        {
          if (dnd.to1) {
            clearTimeout(dnd.to1);
            dnd.to1 = false;
          }
          if (dnd.to2)  {
            clearTimeout(dnd.to2);
            dnd.to2 = false;
          }
        }

     },


     /**
       *  Expand the node hovered over while dragging so the user can drop inside a folder.
       *  @private
       */
     _dnd_open : function ()
     {
        var  vars = this._data.dnd.vars ;

        this._data.dnd.to2 = false;
        this["expand"](vars.r, $.proxy(this._dnd_prepare, this), true);
     },


     /**
       *  Drop is complete - move the node. (_drag_stop will be called from the
       *                                     document mouseup handler later)
       *  @private
       */
     _dnd_finish : function (e)
      {
         var dnd  = this._data.dnd,
             vars = this._data.dnd.vars ;

         if (dnd.foreign)   {
           if (dnd["after"] || dnd["before"] || dnd["inside"])  {
              this.options["dnd"]["drag_finish"].call(this, { "o" : vars.o,
                                                              "r" : vars.r,
                                                              "p" : vars.last_pos
                                                             });
           }
         }
         else  {
           this._dnd_prepare();
           this._moveNode(vars.o, vars.r, vars.last_pos, e[this.options["dnd"]["copy_modifier"] + "Key"]);
         }

         if (vars.o) {                          // remove classes from dragged node
           vars.o.removeClass(OJ_DRAG)
                 .removeClass(OJ_INVALID_DROP)
                 .removeClass(OJ_VALID_DROP) ;  
         }

         vars.o = false;
         vars.r = false;

         vars.m.hide();                 // hide the insert marker
         if (vars.ml) {
           vars.ml.hide();              // hide the insert marker line
         }
     },


     /**
       *  Mouse has gone down on a node's <a> link. Prepare for drag and drop.
       *  @private
       */
     _start_drag : function (obj, e)
     {
        var  dnd  = this._data.dnd,
             vars = this._data.dnd.vars ;

        vars.o = this._getNode(obj);

        if (vars.o.hasClass(OJ_DISABLED) || this._data.core.locked) {
          return ;
        }

        if (this._data.ui && this["isSelected"](vars.o))  {
          vars.o = this._getNode(null, true);
        }

        var dt  = vars.o.length > 1 ? this._getString(TRANSKEY_MULTISEL) : this["getText"](vars.o),
            cnt = this._getContainer();

        dt = dt.replace(/</ig,"<").replace(/>/ig,">");

        vars.o.addClass("oj-drag") ;       // add class to the dragged node

        this._drag_start(e, { jstree : true,
                              obj    : vars.o
//                          }, "<ins class='oj-tree-icon'></ins>" + dt);        // we no longer use an icon
                            }, "<span>" + dt + "</span>");

        if (this._data.themes) {
          if (vars.m) {
             vars.m.addClass("oj-tree-" + this._data.themes.theme);
          }
          dnd.ctl.helper.addClass("oj-tree-dnd-helper oj-tree-" + this._data.themes.theme); 
        }

        dnd.cof    = cnt.offset();
        dnd.cw     = parseInt(cnt.width(),10);
        dnd.ch     = parseInt(cnt.height(),10);
        dnd.active = true;
     },


     /**
       *  Start drag/drop process
       *  @private
       */
     _drag_start : function (e, data, html)
     {  
         var ctl = this._data.dnd.ctl ;

         if (ctl.is_drag)  {
           this._drag_stop() ;
         }
         try {
               e.currentTarget.unselectable = "on";
               e.currentTarget.onselectstart = function()  { return false; };
               if (e.currentTarget.style)  {
                 e.currentTarget.style.MozUserSelect = "none";
               }
         } catch(err) { }

         ctl.init_x    = e.pageX;
         ctl.init_y    = e.pageY;
         ctl.user_data = data;
         ctl.is_down   = true;
         ctl.helper    = $("<div class='oj-tree-title oj-drag' style='top:-2000px' />").html(html); //.fadeTo(10,0.25);   //@HTMLUpdateOK

         // Listen for mouse drag and mouse up events
         $(document).bind("mousemove", this._drag.bind(this));
         $(document).bind("mouseup",   this._drag_stop.bind(this));

         return false;
     },


     /**
       *  Mouse is down and we are dragging 
       *  @private
       */
     _drag :  function (e)
     { 
        var ctl  = this._data.dnd.ctl,
            vars = this._data.dnd.vars ;

        if (! ctl.is_down) {
          return;
        }
        if (! ctl.is_drag)  {
          if (Math.abs(e.pageX - ctl.init_x) > 5 || Math.abs(e.pageY - ctl.init_y) > 5)  { 
            ctl.helper.appendTo("body");                                                     //@HTMLUpdateOK
            ctl.is_drag = true;
            $(document).triggerHandler("drag_start.ojtreeu", [{
                                                                 "event" : e,
                                                                 "data"  : ctl.user_data
                                                              }]);
          }
          else  {
            return;
          }
        }


        // maybe use a scrolling parent element instead of document?
        if (e.type === "mousemove")
        {   // thought of adding scroll in order to move the helper, but mouse position is n/a
           var d = $(document),
               t = d.scrollTop(),
               l = d.scrollLeft();

           if (e.pageY - t < 20)  { 
             if (vars.sti && vars.dir1 === "down") {
                clearInterval(vars.sti);
                vars.sti = undefined;
             }
             if (! vars.sti)  {
               vars.dir1 = "up";
               vars.sti = setInterval(function ()
                            {
                              $(document).scrollTop( $(document).scrollTop() - ctl.scroll_spd );
                            }, 150);
             }
           }
           else  { 
              if (vars.sti && vars.dir1 === "up")   {
                clearInterval(vars.sti);
                vars.sti = undefined;
              }
           }

           if ($(window).height() - (e.pageY - t) < 20)   {
             if (vars.sti && vars.dir1 === "up")  {
                clearInterval(vars.sti);
                vars.sti = undefined;
             }
             if (! vars.sti)  {
               vars.dir1 = "down";
               vars.sti = setInterval(function ()
                         {
                           $(document).scrollTop( Number($(document).scrollTop()) + ctl.scroll_spd );
                           // above Number() is for the closure compiler!!
                         }, 150);
             }
           }
           else   { 
              if (vars.sti && vars.dir1 === "down")  {
                clearInterval(vars.sti);
                vars.sti = undefined;
              }
           }

           if (e.pageX - l < 20)   {
             if (vars.sli && vars.dir2 === "right")  {
                clearInterval(vars.sli);
                vars.sli = undefined;
             }
             if (! vars.sli)  {
                vars.dir2 = "left";
                vars.sli = setInterval(function ()
                       {
                         $(document).scrollLeft( $(document).scrollLeft() - ctl.scroll_spd );
                       }, 150);
             }
           }
           else   { 
             if (vars.sli && vars.dir2 === "left")   {
               clearInterval(vars.sli);
               vars.sli = undefined;
             }
           }

           if ($(window).width() - (e.pageX - l) < 20)   {
             if (vars.sli && vars.dir2 === "left")  {
                clearInterval(vars.sli);
                vars.sli = undefined;
             }
             if (! vars.sli)   {
               vars.dir2 = "right";
               vars.sli = setInterval(function ()
                        {
//                        Number() is for the closure compiler!!
                          $(document).scrollLeft(Number($(document).scrollLeft()) + ctl.scroll_spd);
                        }, 150);
             }
           }
           else  { 
             if (vars.sli && vars.dir2 === "right")  {
               clearInterval(vars.sli);
               vars.sli = undefined;
             }
           }
        }

        //  Position the dragged text relative to the mouse
        var horizDisp = ctl.helper_horiz ;

        if (this._isRtl && vars.o) {
          var $title = vars.o.find("span.oj-tree-title:first") ;
          if ($title && $title.length > 0) {
            horizDisp = - horizDisp - $title[0].offsetWidth ;
          }
        }
        ctl.helper.css({ left : (e.pageX + horizDisp) + "px",
                         top  : (e.pageY + ctl.helper_top) + "px" 
                       });

        $(document).triggerHandler("drag.ojtreeu", [{
                                                      "event" : e,
                                                      "data"  : ctl.user_data
                                                    }]);
     },


     /**
       *  @private
       */
     _drag_stop  : function (e)
     {
        var vars = this._data.dnd.vars,
            ctl  = this._data.dnd.ctl ;

        if (vars.sli)  {
          clearInterval(vars.sli);
        }
        if (vars.sti)   {
          clearInterval(vars.sti);
        }

        if (vars.o) {                     // remove classes from dragged node
          vars.o.removeClass(OJ_DRAG)
                .removeClass(OJ_INVALID_DROP)
                .removeClass(OJ_VALID_DROP) ;  
        }

        $(document).unbind("mousemove", this._drag.bind(this));
        $(document).unbind("mouseup",   this._drag_stop.bind(this));
        $(document).triggerHandler("drag_stop.ojtreeu", [{
                                                           "event" : e? e: {},
                                                           "data"  : ctl.user_data
                                                         }]);
        ctl.helper.remove();
        ctl.init_x    = 0;
        ctl.init_y    = 0;
        ctl.user_data = {};
        ctl.is_down   = false;
        ctl.is_drag   = false;

        this._getContainer().removeClass("oj-invalid-drop") ;      // set cursor on tree div
    },


     /**
       *  @private
       */
     _save_opened : function ()
     {
        var _this = this;
        this._data.core.toExpand = [];
        this._$container_ul.find("li.oj-expanded").each(function () { 
            if (this.id)  { 
              _this._data.core.toExpand.push("#" + this.id.toString().replace(/^#/,"").replace(/\\\//g,"/").replace(/\//g,"\\\/").replace(/\\\./g,".").replace(/\./g,"\\.").replace(/\:/g,"\\:"));
            }
        });

        // this._emitEvent(_this._data.core.toExpand, "save_opened", true);
     },


     /**
       *  Reload tree nodes
       *  If this is not a callback request, then if initExpanded is defined,
       *  any specified (or all) collapsed nodes are  expanded (loading if neccesaary
       *  because of lazy-loading).
       *  @param {boolean=} bIsCallback  true if this method has been called from within this method.
       *  @private
       */
      _reload_nodes : function (bIsCallback)
      {
         var _this     = this,
             bDone     = true,
             current   = [],
             remaining = [],
             ea, n, id, bExpandAll ;
     
         if (! bIsCallback)  { 
            this._data.core.reopen     = false; 
            this._data.core.refreshing = true; 
         }

         bExpandAll = this._isOptExpandAll() ;     // is option initExpanded "all" ?
         if (bExpandAll) {
           this._data.core.toExpand  = [];
           ea = this._$container_ul.find("li.oj-collapsed").each(function() {
                  id = '#' + $(this).attr("id") ;
                  if (! _this["isExpanded"](id)) {
                    _this._data.core.toExpand.push(id) ;
                  }
                }) ;
         }
     
         this._data.core.toExpand = 
                  $.map(
                         $.makeArray(this._data.core.toExpand),
                             function (n) {
                                return "#" + n.toString().replace(/^#/, "")
                                                         .replace(/\\\//g, "/")
                                                         .replace(/\//g, "\\\/")
                                                         .replace(/\\\./g, ".")
                                                         .replace(/\./g, "\\.")
                                                         .replace(/\:/g, "\\:");
                             }
                       );

         this._data.core.toLoad = 
                    $.map(
                           $.makeArray(this._data.core.toLoad),
                               function (n)  {
                                  return "#" + n.toString().replace(/^#/,"")
                                                           .replace(/\\\//g,"/")
                                                           .replace(/\//g,"\\\/")
                                                           .replace(/\\\./g,".")
                                                           .replace(/\./g,"\\.")
                                                           .replace(/\:/g,"\\:");
                         }
                    );
     
         if (this._data.core.toExpand.length)  {
           this._data.core.toLoad  = this._data.core.toLoad.concat(this._data.core.toExpand);

           // Remove duplicates
           this._data.core.toLoad = this._data.core.toLoad.reduce(function(a, b) {
                       if (a.indexOf(b) < 0 ) a.push(b);
                         return a;
                       }, []);
         }
     

         // Analyse combined expand/load list and create current and remaining arrays.

         if (this._data.core.toLoad.length) {
            $.each(this._data.core.toLoad, function (i, val) {
                if (val == "#") {
                  return true;
                }
                n = _this._$container.find(val) ; 
                if (n.length) {                      // is node in the tree
                  if (! _this["isExpanded"](val)) {  // and not expanded
                    current.push(val);               // yes.
                  }
                }
                else {
                  remaining.push(val);               // no.
                }
            });
     
            if (current.length) {
               this._data.core.toLoad  =  remaining;
               $.each(current, function (i, val) { 
                   if (! _this._is_loaded(val))  {        // if load not loaded,
                      _this._load_node(val,               // load it.
                                       function ()  {_this._reload_nodes(true);},    // success func
                                       function ()  {_this._reload_nodes(true);}     // error func
                                      );
                      bDone = false;        // note we're not complete yet since we just loaded a node
                   }
               });
            }
         }


         //  Expand the nodes referred to.

         if (this._data.core.toExpand.length)  {
            $.each(this._data.core.toExpand, function (i, val) {
                if (! _this["isExpanded"](val)) {
                  _this._expand(val, false, true);      // no animation for initExpand
                }
            });
         }

         if (bDone)  { 
            // TODO: find a more elegant approach to syncronizing returning requests

            if (this._data.core.reopen)  {
              clearTimeout(this._data.core.reopen);
            }
            this._data.core.reopen = setTimeout(function () {
                                                               _this._emitEvent({}, "reload_nodes", true);
                                                            }, 50);
            this._data.core.refreshing = false;
            this._reopen();                      // check expanded and selections
         }
    },


     /**
       *   Not exposed in V1
       *   @private
       */
    "setTheme" : function (theme_name, theme_url)
    {
       if (! theme_name)  {
         return false;
       }

       if (! theme_url)  {
          theme_url = this._data.themes._themes + theme_name + '/style.css';
       }

       if ($.inArray(theme_url, this._data.themes.themes_loaded) == -1)  {
         _addSheet({ url : theme_url });
         this._data.themes.themes_loaded.push(theme_url);
       }

       if (this._data.themes.theme != theme_name)  {
         this._$container.removeClass('oj-tree-' + this._data.themes.theme);
         this._data.themes.theme = theme_name;
       }

       this._$container.addClass('oj-tree-' + theme_name);

       if (! this._data.themes.dots) {
         this._hideDots();
       }
       else  {
          this._showDots();
       }

       if (! this._data.themes.icons)  {
         this._hideIcons();
       }
       else  {
         this._showIcons();
       }
 
//     this._emitEvent(null, "set_theme", true);
    },


    /*
     *   Shows the hierarchy lines.  Not used in V1
     *   @private
     */
/*
    "show_dots"  : function ()
    {
      this._showDots() ;
    },
*/
    /*
     *   Hides the hierarchy lines.  Not used in V1
     *   @private
     */
/*
    "hide_dots"   : function ()
    {
      this._hideDots() ;
    },
*/
    /*
     *   Toggles the current hierarchy line state.  Not used in V1
     *   @private
     */
/*
    "toggle_dots" : function ()
    {
       if (this._data.themes.dots) {
         this._hideDots();
       }
       else  {
         this._showDots"]();
       }
    },
*/
    /**
      *   Returns true if node icons are currently displayed.  Users can find this state from options.
      *   @private
      */
    "isIcons"  : function ()
    {
       return this._data.themes.icons ;
    },


    /**
      *   Displays node icons. Users can set this state from options.
      *   @private
      */
    _showIcons  : function ()
    {
       this._data.themes.icons = true;
       this._$container.children("ul").removeClass("oj-tree-no-icons");
    },

    /**
     *   Hides node icons. Users can set this state from options.
     *   @private
     */
    _hideIcons  : function ()
    {
       this._data.themes.icons = false;
       this._$container.children("ul").addClass("oj-tree-no-icons");
    },

    /**
     *   Toggles the display state of node icons. User can set this state from options.
     *   @private
     */
    "toggleIcons" : function ()
    {
/*                                      Not used in V1
       if (this.data.themes.icons) {
         this._hideIcons();
       }
       else  {
         this._showIcons();
       }
*/
    },

    /**
      *   Enables keyboard support
      *   @private
      */
    _enableKeys : function ()
    {
       this._data.keys.enabled = true;
    },

//  /**
//    *   Disables keyboard support
//    *   @private
//    */
//         Not used in V1
//  _disableKeys : function ()
//  {
//     this._data.keys.enabled = false;
//  },
//

    /**
      *   Initializes the widget, examining options and setting up
      *   internal data structures.
      *   @private
      */
     _initTree : function()
     {
        this._initData() ;
        this._initCoreOpts() ;
        this._initUIOpts() ;
//      this._initCrrmOpts() ;      // not in V1
//      this._initThemeOpts()       // not in V1
        this._initDSOpts(true) ;
        this._initTypeOpts()
        this._initDnDOpts() ;
        this._initMenuOpts() ;

        this._initCore() ;
        this._initUI() ;
        this._initThemes() ;
        this._initDataSource() ;
//      this._initCrrm() ;          // not in V1
        this._initTypes() ;
        this._initDnD() ;
        this._initKeys() ;
        this._initMenu() ;
     },


     /**
       *  Emit events
       *  @param {Object} data an object containing details about the event.
       *  @param {string} evname the raw event name (e.g. "hover", or "expandAll".
       *                          When the event is fired, it is "ojhover" and "ojexpandall",
       *                          but also the options "hover" and "expandAll" are called.
       *  @param {boolean=} bInternal true if an internal event, or omit or set false for
       *                              a public event.
       *  @private
       */
     _emitEvent : function (data, evname, bInternal) 
     { 
        if ((! evname) || $.type(evname) !== "string") {
          return ;
        } 
        if ((this._data.core.locked === true) && (evname !== "unlock")
                                              && (evname !== "isLocked")
                                              && (evname !== "lock")) {
          return ;
        }

        // console.log("Event " +  evname + (evname === "before"? " (" + data["func"] + ")" : "") +
        //             " public=" + (bInternal? false : true));

        var func,
            args     = Array.prototype.slice.call(arguments),
            rlbk     = false,
            inst     = this._$container,
            isBefore = (evname === "before"),
            isPublic = bInternal? false : true ;

        if (! isPublic)  {                       // internal event?
           evname = "_tree" + evname ;
        }


        // Build the "ui" argument
        var eventdata = {} ;
        eventdata["item"] = (data? data["obj"] : undefined) ;
        eventdata["inst"] = inst ;

        if (isBefore) {
          func               = data["func"] ;      // target method
          eventdata["func"]  = func ;
          eventdata["args"]  = args ;
          if (func === "rename") {
            eventdata["title"]     = data["title"] ;
            eventdata["prevTitle"] = data["prevTitle"] ;
          }
        }
        else  if (isPublic) {
          if (evname == "move") {
            eventdata["position"]  = data.p ;      // position relative to the reference node
            eventdata["reference"] = data.r ;      // the reference node
            eventdata["data"]      = data ;        // (req'd internally)
          }
          else if (evname == "rename") {
            eventdata["title"]     = data["title"] ;      // the new node title to be
            eventdata["prevTitle"] = data["prevTitle"] ;  // the current title
          }
          else if (evname == "remove") {            // node was deleted via context menu
            eventdata["parent"]  = data["parent"] ; // parent node
            eventdata["prev"]    = data["prev"] ;   // the "previous" node
          }
          else if (evname == "delete") {            // node was deleted via context menu
            eventdata["prev"]   = data["prev"] ;    // (req'd internally) - the "previous" node
            eventdata["parent"] = data["parent"] ;  // parent node
          }
          else if (evname === "expandAll" || evname === "collapseAll" || evname === "deselectAll") {         
            eventdata["targ"]   = data["targ"] ;    // the target of the op (node or -1)
          }
          else if (evname === "loaded") {
            eventdata["item"] = -1 ;
          }
        }

        if (isPublic) {
          var bContinue = this._trigger(evname, new $.Event("oj" + evname), eventdata) ;

          if (isBefore) {
            //  console.log("Event " + evname + ") " + eventdata["func"] + " returned " + rslt);
            if (typeof bContinue != "undefined") {
              bContinue = bContinue? true : false ;         // returns true/false/undefined
            }
            return bContinue ;
          }
        }
        else {
          this._$container.trigger(evname, eventdata) ;    // internal event
        }
        
     },


     /**
       *  Fire optionChange event
       *  @param {string}  key             the option key whose property value has been changed.
       *  @param {Object}  prevVal         the previous value prior to the change 
       *  @param {Object | null}  newVal   the new value after the change
       *  @param {Event | null} origEvent  false if option change is not due to user interaction.
       *  @private
       */
     _fireOptionChangeEvent : function(key, prevVal, newVal, origEvent)
     {
        if (key === "selection")  {
          if (newVal == null) {
            newVal = this._getSelected() ;
          }  

          // Only fire the event if the new and prev vals are different.

          if (! this._compareSelectionValues(prevVal, newVal))  {
            this.option(key, newVal, {'_context' : { originalEvent: origEvent,
                                                     internalSet: true
                                                   },
                                      'changed': true     // don't need comparison check
                                     });                    
          }    // end if "selection"
        }
     },

     /**
       *  TDO _not currently used in V1
       *  @private
       */
     __rollback : function ()
     { 
         var
         rlbk = this["get_rollback"]();
         return rlbk;
     },

     /**
       *  Tree initialization is complete.  Build and render the tree.
       *  @private
       */
     _start : function()
     {
       if (this._isRtl) {
         this._$container.addClass("oj-tree-rtl").css("direction", "rtl");
       }

       //  Create the outer <ul> with a temporary <li> saying "Loading..."

       this._$container.html("<ul role='tree' tabindex='0' class='oj-tree-list' style='outline:none'" +       //@HTMLUpdateOK
                    ((this._data.core.selectMode === -1)? " aria-multiselectable='true'" : "") +
                    "><li class='oj-tree-last oj-tree-leaf'><ins class='oj-tree-icon'> </ins><a class='oj-tree-loading' href='#'><ins class='oj-tree-icon'> </ins>" +
                     this._getString(TRANSKEY_LOADING) + "</a></li></ul>");

       this._$container_ul = this._$container.children("ul:eq(0)");
       this._$container.data("oj-tree-instance-id", this._getIndex());

       this._data.core.li_height = this._$container_ul.find("li.oj-collapsed, li.oj-tree-leaf")
                                                      .eq(0)
                                                      .height() || 18;

       if (this._isTouch) {             // for tap expand/collapse
         this._$container
              .delegate(".oj-tree-list ins.oj-tree-disclosure-icon", "touchend.ojtree", $.proxy(function (event)  {
                     event.preventDefault() ;        // don't want browser click generated after 300ms
                     var trgt = $(event.target);
                     this["toggleExpand"](trgt);
                }, this));
       }

       this._$container
              .delegate(".oj-tree-list ins.oj-tree-disclosure-icon", "click.ojtree", $.proxy(function (event)  {
                     var trgt = $(event.target);
                     // if(trgt.is("ins") && event.pageY - trgt.offset().top < this.data.core.li_height) { this.toggle_node(trgt); }
                     this["toggleExpand"](trgt);
                }, this))

              .delegate(".oj-tree-list ins.oj-tree-disclosure-icon", "mousedown", $.proxy(function (event)  {
                     this._data.ui.disclosureClick = true ;  // if no prev hover/selection, this will
                                                             // prevent hover going to 1st node when
                                                             // a disclosure has been clicked as the first action
                                                             // on the tree (see focus event below)  
                     var trgt = $(event.target);
                     trgt.removeClass("oj-default").removeClass(OJ_HOVER).addClass(OJ_SELECTED) ;
                }, this))

              .delegate(".oj-tree-list ins.oj-tree-disclosure-icon", "mouseup", $.proxy(function (event)  {
                     var trgt = $(event.target);
                     trgt.removeClass("oj-selected").addClass("oj-default") ;
                }, this))

              .bind("mousedown.ojtree", $.proxy(function ()  { 
                     this._setFocus(); // This used to be setTimeout(set_focus,0) - why?
                }, this))
              .bind("dblclick.ojtree", function (event)  { 
                     var sel;
                     if (document.selection && document.selection.empty) {
                       document.selection.empty();
                     }
                     else  {
                       if (window.getSelection)  {
                         sel = window.getSelection();
                         try { 
                               sel.removeAllRanges();
                               sel["collapse"](document.getElementsByTagName("body")[0], 0);
                             } catch (err) { }
                       }
                     }
                });

       this._$container_ul
               .focus($.proxy(function(e) {
                        if (this._data.ui.disclosureClick) {         // if very first action is
                          this._data.ui.disclosureClick = false ;    // mousedown on a disclosure icon
                          return ;                                   // don't set hover.
                        }

                        this._data.ui.focused = true ;
                        var n ;

                        if (this._data.ui.lastHovered) {
                          this._data.ui.hovered =  this._data.ui.lastHovered ;
                          n = this._data.ui.hovered ;
                        }
                        else if (this._data.ui.lastSelected && (this._data.ui.lastSelected.length > 0)) {
                          this._data.ui.hovered =  this._data.ui.lastSelected ;
                          n = this._data.ui.hovered ;
                        }
                        else {
                          n = this._$container_ul.find("li:first");
                        }
                        if (n) {
                          this["hover"](n);
                          this._data.ui.lastHovered = null ;

                          // want color change for selected inactive to selected
                          this._$container_ul.find("a.oj-selected").removeClass(OJT_INACTIVE) ;
                        }
                      }, this)
               )
               .blur($.proxy(function(e) {
                        this._data.ui.focused     = false ;
                        this._data.ui.lastHovered = this._data.ui.hovered ;
                        if (this._data.ui.lastHovered) {
                          this["dehover"](this._data.ui.hovered) ;
                        }
                        // want color change for selected active to selected inactive
                        this._$container_ul.find("a.oj-selected").addClass(OJT_INACTIVE) ;
                     }, this)
               );

/*     Not used in V1
       if (this._data.core.load_open)   {
         this._$container
            .bind("_treeload_node", $.proxy(function (e, ui)  { 
                 var o = this._getNode(ui["item"]),
                     t = this;
                 if (o === -1)  {
                   o = this._$container_ul;
                 }
                 if (!o.length)  {
                   return;
                 }
                 o.find("li.oj-expanded:not(:has(ul))").each(function ()
                     {
                        this._load_node(this, $.noop, $.noop);
                     });
            }, this));
       }
*/
       this._emitEvent({}, "init", true);  // Tree is now init'd (but nodes have not yet been added)

       this._loadNodes() ;                 // start node loading from the datasource

       //  Tree div is now constructed, note if context menu has been set.
       if (this._data.menu.usermenu) {
         this._applyMenu() ;
       }

       // Add and enable this tree's keystroke handler
       _addKeyFilter( {
                        _handler : this._keyHandler,
                        _selector: this._$container_ul,
                        _this    : this
                      }) ;
       this._enableKeys();
     },


     /**
       *  Initialize the Core section
       *  @private
       */ 
     _initCore : function()
     {
       this._data.core.locked   = false;

       this._$container.addClass("oj-tree oj-tree-" + this._getIndex()) ;     //TDO

       this._$container.css("outline", "none");
       this._$container.css("MozUserSelect", "none");
       this._$container.css("WebkitTouchCallout", "none");
       this._$container.css("WebkitUserSelect", "none");
       this._$container.css("WebkitTapHighlightColor", "rgba(0,0,0,0)");
     },


    /**
      *  Initialize the UI section
      *  @private
      */
     _initUI : function() 
     {
         this._data.ui.selected      = $(); 
         this._data.ui.lastSelected  = false; 
         this._data.ui.hovered       = null;
         var a = this.options["selection"] ;

         if (a && ($.type(a) === "array") && a.length > 0) {
           this._data.ui.to_select   = a ;
           this.options["selection"] = [] ;  // will be updated when the selections are performed
         }

        // Bind to events

        if (this._isTouch) {             // for tap select/deselect
          this._$container
             .delegate(".oj-tree-list a", "touchend.ojtree", $.proxy(function (event)
               {
                 // This becomes the hovered node
//               if (! $(event.currentTarget).hasClass(OJT_LOADING))  {
//                 this["hover"](event.target) ;
//               }

                 this._data.ui.touchEvent = true ;
                 this._handleNodeTapClick(event) ;   // select the node, or toggle if multi-select

                 //  Since this is a tap, no node is hovered
                 if (! $(event.currentTarget).hasClass(OJT_LOADING))  {
                   this["dehover"]();
                 }
               }, this));
        }

        //-----------------------------------------------------------------//
        //  Handle events (particularly post operation events like move()  //
        //  remove(), collapse(), etc.                                     //
        //-----------------------------------------------------------------//

        this._$container
             .delegate(".oj-tree-list a", "click.ojtree", $.proxy(function (event)
                {
                  this._data.ui.touchEvent = false ;
                  this._handleNodeTapClick(event) ;
                }, this))
             .delegate(".oj-tree-list a", "mouseenter.ojtree", $.proxy(function (event)
                {
                  if (! $(event.currentTarget).hasClass(OJT_LOADING))  {
                     this["hover"](event.target);
                  }
                }, this))
             .delegate(".oj-tree-list a", "mouseleave.ojtree", $.proxy(function (event)
               {
                  if (! $(event.currentTarget).hasClass(OJT_LOADING))  {
                    this["dehover"](event.target);
                  }
               }, this))
             .delegate(".oj-tree-list .oj-tree-disclosure-icon", "mouseenter.ojtree", $.proxy(function (event)
                {
                  if (! $(event.currentTarget).hasClass(OJT_LOADING))  {
                     this._disclosureHover(event.target, true);
                  }
                }, this)) 
             .delegate(".oj-tree-list .oj-tree-disclosure-icon", "mouseleave.ojtree", $.proxy(function (event)
                {
                  if (! $(event.currentTarget).hasClass(OJT_LOADING))  {
                     this._disclosureHover(event.target, false);
                  }
                }, this))
             .bind("_treereopen", $.proxy(function ()
                { 
                  this["reselect"]();
                }, this))
             .bind("_treeget_rollback", $.proxy(function ()
                { 
                  this["dehover"]();
                  this["saveSelected"]();
                }, this))
/*   Not used in V1
             .bind("_treeset_rollback", $.proxy(function ()
               { 
                  this["reselect"]();
               }, this))
*/
             .bind("ojcollapse", $.proxy(function (event, ui)
              { 
                var obj   = this._getNode(ui["item"]),
                    clk   = (obj && obj.length) ? obj.children("ul").find("a.oj-selected") : $(),
                    _this = this;
                if (this.options["selectedParentCollapse"] === false || (! clk.length))  {
                  return;
                }
                clk.each(function ()  { 
                               this["deselect"](this);
                               if (this.options["selectedParentCollapse"] === "selectParent")  {
                                  this["select"](obj);
                               }
                         });
                 }, this))
             .bind("ojremove", $.proxy(function (event, ui)         // node delete 
                { 
                   var s     = this.options["selectPrevOnDelete"],
                       obj   = this._getNode(ui["item"]),
                       clk   = (obj && obj.length) ? obj.find("a.oj-selected") : [],
                       _this = this,
                       n ;

                   clk.each(function ()
                              {
                                 _this["deselect"](this);

                                 // Remove lastHovered if it refers to the node removed
                                 if (_this._data.ui.lastHovered) {
                                   n = _this._getNode(this) ;
                                   if (n && (_this._data.ui.lastHovered.attr("id") == n.attr("id"))) {
                                     _this._data.ui.lastHovered = null ;
                                   }
                                 }
                              });
                   if (s && clk.length)  { 
                     if (ui["prev"]) {
                       ui["prev"].each(function ()
                          { 
                            if (this["parentNode"]) {
                              _this["select"](this);
                              return false;   // if return false is removed all prev nodes will be selected 
                            }
                          });
                     }
                   }
                 }, this))
             .bind("ojmove", $.proxy(function (event, ui)
                { 
                   var data = ui["data"],
                       copy = data.cy,
                       p ;

                   if (copy && data.oc)  { 
                     data.oc.find("a.oj-selected").removeClass(OJ_SELECTED);
                     data.oc.removeAttr(WA_SELECTED);
                   }

                   // If the node being moved (not copied) is from a different
                   // tree, and the node was noted as lastHovered or lastSelected
                   // in the tree when it lost focus, clear that tree's lastHovered/
                   // lastSelected.

                   if ((data.ot !== data.rt) && (!copy)) {     // different trees and a move op?
                     p = data.ot._data.ui ;
                     if (p.lastHovered) {
                       if (data.o.attr("id") == p.lastHovered.attr("id")) {
                         p.lastHovered = null ;
                       }
                       if (p.lastSelected) {
                         if (data.o.attr("id") == p.lastSelected.attr("id")) {
                           p.lastSelected = null ;
                         }
                       }
                     }    
                   }
                 }, this));

     },

     /**
       *  Initialize the data source
       *  @private
       */
     _initDataSource : function()
     {
        this._initTreeData() ;
        this._initJsonData() ;
        this._initHtmlData() ;
     },

     /**
       *  Initialize the tree data source
       *  @private
       */
     _initTreeData : function()
     {
        if (this._data.ds.type === DS_TREE) {
          this._tds        = (this.options["data"] || null) ;     // the tree data source
          this._load_node  = this._load_node_DS ;
          this._is_loaded  = this._is_loaded_DS ;
          this._refresh    = this._refresh_DS ;
       }
     },

     /**
       *  Initialize the json_data section if requested
       *  @private
       */
     _initJsonData : function()
     {
        if (this._data.ds.type === DS_JSON) {
          if (this._data.ds.progressiveUnload)  {
            this._$container.bind("_treeafter_close", function (e, ui)  {
                                                         ui["item"].children("ul").remove();
                                                      });
          }

          this._load_node  = this._load_node_J ;
          this._is_loaded  = this._is_loaded_J ;
          this._refresh    = this._refresh_json ;
        }
     },


     /**
       *  Initialize html_data support if requested.
       *  @private
       */
     _initHtmlData : function()
     {
       if (this._data.ds.type !== DS_HTML) {
         return ;
       }

       this._processExistingMarkup() ;         // check for existing markup in the div

       this._load_node = this._load_node_H ;
       this._is_loaded = this._is_loaded_H ;
       this._refresh   = this._refresh_ui ;
     },


     /**
      *  If there is existing html markup in the div, clone it and move it to another div.
      *  @private
      */
     _processExistingMarkup : function ()
     {
       if (! this._data.html.useExistingMarkup) {
         return ;
       }

       if (! this._data.html.markup_ul) {  // first time find the user <ul>
         this._data.html.markup_ul   = this._$container.find(" > ul");          // user's <ul>

         // Move the user <ul> in the tree div to another div so that it is still in the DOM,
         // and can be located by the app, changed, and refresh() called if desired, and so it
         // will not interfere with the <ul> we maintain for the tree in the tree div. If the
         // app wants to change the markup, it must find its <ul> first and then find node id's
         // from it if it needs to, because the <ul> has been cloned and the id's will
         // therefore be duplicated.
         // TDO - we need to have a better way to do this.
         this._data.html.markup_div  = $("<div id='" +
                                       USER_UL_ID_PREFIX + this._getIndex() +
                                       "' style='display:none'>").append(this._data.html.markup_ul) ;   //@HTMLUpdateOK
         this._$container.after(this._data.html.markup_div) ;                                           //@HTMLUpdateOK
       }

       this._data.html.markup      = this._data.html.markup_ul.find(" > li");   // user's <li>'s
       this._data.html.cloneMarkup = this._data.html.markup.clone(true);        // our clone of the <li>'s

       // this used to use html() and clean the whitespace, but this way any attached data was lost
       // remove white space from LI node - otherwise nodes appear a bit to the right
       this._data.html.cloneMarkup.find("li").addBack()
                                             .contents()
                                             .filter(function()
                                                {
                                                  return this.nodeType == 3;
                                                 })
                                             .remove();
     },


     /**
       *  Initialize the Themes section   Not used in V1
       *  @private
       */
     _initThemes : function()
     {
        // autodetect themes path
        if (this._data.themes._themes === false)  {
           $("script").each(function ()
            { 
              if( this.src.toString().match(/jquery\.oj-tree[^\/]*?\.js(\?.*)?$/))  { 
                 this._data.themes._themes = this.src.toString().replace(/jquery\.oj-tree[^\/]*?\.js(\?.*)?$/, "") + 'themes/'; 
                 return false; 
              }
            });
        }
        if (this._data.themes._themes === false)    {
          this._data.themes._themes = "themes/";
        }

        this._$container
             .bind("_treeinit", $.proxy(function ()
                {
                   var s = this.options ;

                   this._data.themes.dots  = s["dots"]; 
                   this._data.themes.icons = s["icons"]; 
                   this["setTheme"](this._data.themes.theme, this._data.themes.url);

                }, this))

             .bind("ojloaded", $.proxy(function ()
                {
                   // bound here too, as simple HTML tree's won't honor dots & icons otherwise
                   if (! this._data.themes.dots)  {
                     this._hideDots();
                   }
                   else  {
                     this._showDots();
                   }
                   if (!this._data.themes.icons)  {
                     this._hideIcons();
                   }
                   else  {
                     this._showIcons();
                   }
                }, this));

     },


     /**
       *  Initialize the icon type functionality
       *  @private
       */
     _initTypes : function()
     {
        var s = this.options["types"];
        if (! s) {
          return ;
        }

        this._$container
               .bind("_treeinit", $.proxy(function ()
                  { 
                    var types     = $.extend(true, {}, s["types"]),
                        attr      = s["attr"] || this._data.types.defaults["attr"],
                        icons_css = "", 
                        _this     = this;

                    $.each(types, function (i, tp)
                       {
                         $.each(tp, function (k, v)
                            { 
                              if (!/^(maxDepth|maxChildren|icon|validChildren)$/.test(k))  {
                                _this._data.types.attachTo.push(k);
                              }
                            });

                         // For ojTree we allow image and position props to not
                         // have to be in an icon object like jsTree.
                         var ot = (typeof tp["icon"]) ;
                         if (ot === "undefined") {
                           ot = (typeof tp["image"]) ;
                           if ((ot === "boolean") && (!tp["image"])) {
                             tp["image"] = "ojt$none" ;
                           }
                           else if ((! tp["image"]) && (! tp["position"])) {
                             return true;
                           }

                           tp["icon"] = {} ;
                           if (tp["image"])  {
                             tp["icon"]["image"] = tp["image"]; 
                             delete tp["image"] ;
                           } 

                           if (tp["position"] !== undefined)  {
                             tp["icon"]["position"] = tp["position"] ;
                             delete tp["position"] ;
                           }
                         }    

                         if ( tp["icon"]["image"] || tp["icon"]["position"])  {
                           if (i == "default")  {
                             _this._data.types.defType = true ;     // note that the "default" type has been defined       icons_css += '.oj-tree-' + _this._getIndex() + ' a > .oj-tree-icon { ';
                             icons_css += '.oj-tree-' + _this._getIndex() + ' .oj-tree-list li.oj-tree-type a > .oj-tree-node-icon { ';
                             icons_css += _this._addTypeCss(tp, icons_css) ;
                             icons_css += '} ';
                             icons_css += '.oj-tree-' + _this._getIndex() + ' .oj-tree-list li[' + attr + '="oj-tree-deftype"].oj-tree-type > a ins.oj-tree-node-icon { ';
                           }
                           else if (tp["icon"]["image"]) {
                             icons_css += '.oj-tree-' + _this._getIndex() + ' .oj-tree-list li[' + attr + '="' + i + '"].oj-tree-type > a > ins.oj-tree-node-icon { ';
                           }

                           icons_css += _this._addTypeCss(tp, icons_css) ;
                           icons_css += '} ';
                         }
                    });

                    if (icons_css !== "")  {
                      _addSheet({ str : icons_css, title : "oj-tree-types" });
                    }
                   }, this))
               .bind("ojbefore", $.proxy(function (e, data)
                  { 
                     var s,
                         ty, 
                         func = data["func"],
                         item = data["item"],
                         o = this._data.types.defaults["useData"] ? this._getNode(item) : false, 
                         d = (o && o !== -1 && o.length)? o.data("oj-tree") : false;

                     if (d && d["types"] && d[func] === false)
                     {
                       e.stopImmediatePropagation();
                       return false;
                     }

                     if ($.inArray(func, this._data.types.attachTo) !== -1)  {
                       if (!data["item"] || (!data["item"]["tagName"] && !data["item"]["jquery"]))  {
                         return;
                       }
                       s   = this.options["types"]["types"];
                       ty  = this._getType(item);             // get "type" attr name for node

                       if (( (s[ty] && typeof s[ty][func] !== "undefined") || 
                             (s["default"] && typeof s["default"][func] !== "undefined") 
                            ) && this._check(func, item) === false)  {
                         e.stopImmediatePropagation();
                         return false;
                       }
                     }

                  }, this));
     },

     /**
       *  Add css for the type.
       *  @private
       */
     _addTypeCss : function(tp)
     {
         var css = "" ;

         if (tp["icon"]["image"] !== "ojt$none")  {
           css += ' background-image:url(' + tp["icon"]["image"] + '); ';
         }
         else  {
           css += " background-image:none; ";
         }

         if (tp["icon"]["position"])  {
           css += ' background-position:' + tp["icon"]["position"] + '; ';
         }
         else  {
           css += ' background-position:0 0; ';
         }
         return css ;
     },

     /**
       *  Initialize Drag and Drop functionality.  TDO - this will change to HTML5 drag/drop post V1.0
       *  @private
       */
     _initDnD : function()
     {
        if (! this._data.dnd.reorder) {          // only handling a temporary version
          return ;                               // of Dnd for reorder within the tree.
        }
  
        var vars = this._data.dnd.vars ;

        vars.m =                                 // dnd insertion point marker
         $("<div class='oj-tree-drop-marker'><span class='oj-tree-drop-ptr oj-component-icon'> </span></div>")
            .hide()
            .bind("mouseleave mouseenter", $.proxy(function (e) { 
                var vars = this._data.dnd.vars ;
                vars.m.hide();
                vars.ml.hide();
                e.preventDefault(); 
                e.stopImmediatePropagation(); 
                return false; 
            }, this))
            .appendTo("body");                   //@HTMLUpdateOK

        vars.ml = $("<div />")                   // dnd insert point marker line
            .addClass(OJT_DROP_MARKER_LINE)
            .hide()
            .bind("mouseup", function (e) { 

               var vars = this._data.dnd.vars ;

               if (vars.r && vars.r.length) { 
                  vars.r.children("a").trigger(e); 
                  e.preventDefault(); 
                  e.stopImmediatePropagation(); 
                  return false; 
               } 
            })
            .bind("mouseleave", $.proxy(function (e)  { 

                var vars = this._data.dnd.vars ;
                var rt = $(e.relatedTarget);

                if (rt.is(".oj-tree") || rt.closest(".oj-tree").length === 0) {
                    if (vars.r && vars.r.length) { 
                        vars.r.children("a").trigger(e); 
                        vars.m.hide();
                        vars.ml.hide();
                        e.preventDefault(); 
                        e.stopImmediatePropagation(); 
                        return false; 
                    }
                }
            }, this))
            .appendTo("body");               //@HTMLUpdateOK

        this._data.dnd.ml_width = vars.ml.width() ;    // marker line width from style

        $(document).bind("drag_start.ojtreeu", $.proxy(function (e, data)
            {
             var vars = this._data.dnd.vars ;

             if (data.data.jstree)  {
                vars.m.show();
                if (vars.ml)  {
                   vars.ml.show();
                 }
             }
           }, this));

        $(document).bind("drag_stop.ojtreeu", $.proxy(function (e, data)
           {
              var vars = this._data.dnd.vars ;

              if (data.data.jstree)  {
                vars.m.hide();
                if (vars.ml) {
                  vars.ml.hide();
                }
              }
           }, this));

        this._getContainer()
            .bind("mouseenter.ojtree", $.proxy(function (e)
                {
                  var  ctl  = this._data.dnd.ctl,
                       vars = this._data.dnd.vars ;

                  if (ctl.is_drag && ctl.user_data.jstree)  {
                    if (this.options["themes"]) {
                      vars.m.addClass("oj-tree-" + this._data.themes.theme); 
                      if (vars.ml) {
                        vars.ml.addClass("oj-tree-" + this._data.themes.theme);
                      }
                      ctl.helper.addClass("oj-tree-dnd-helper oj-tree-" + this._data.themes.theme);
                    }
                      //if($(e.currentTarget).find("> ul > li").length === 0) {
                    if (e.currentTarget === e.target && ctl.user_data.obj &&
                        $(ctl.user_data.obj).length && $(ctl.user_data.obj).parents(".oj-tree:eq(0)")[0] !== e.target)
                    {
                      // node should not be from the same tree
                      var tr = this._reference(e.target),   //TDO
                          dc;

                      if (tr.data.dnd.foreign) {
                        dc = tr.options["dnd"]["drag_check"].call(this, { "o" : vars.o, "r" : tr._getContainer(), "is_root" : true });
                         if (dc === true || dc["inside"] === true || dc["before"] === true || dc["after"] === true)  {
                           ctl.helper.children("ins").removeClass(OJT_DROP_INVALID)
                                                     .addClass(OJT_DROP_OK);
                           this._getContainer().removeClass("oj-invalid-drop") ;      // set cursor on tree div
                           if (vars.o) {
                             vars.o.removeClass(OJ_INVALID_DROP)
                                   .addClass(OJ_VALID_DROP) ;
                           }
                         }
                      }
                      else {
                         tr._prepare_move(vars.o, tr._getContainer(), "last");
                         if (tr["check_move"]()) {
                           ctl.helper.children("ins").removeClass(OJT_DROP_INVALID)
                                                     .addClass(OJT_DROP_OK);
                           this._getContainer().removeClass("oj-invalid-drop") ;      // set cursor on tree div
                           if (vars.o) {
                             vars.o.removeClass(OJ_INVALID_DROP)
                                   .addClass(OJ_VALID_DROP) ;
                           }
                         }
                      }
                    }
                  }
                 }, this))
            .bind("mouseup.ojtree", $.proxy(function (e)
               {
                  var vars = this._data.dnd.vars,
                      ctl = this._data.dnd.ctl ;       // jsTree helper class

                  if (ctl.is_drag && ctl.user_data.jstree && e.currentTarget === e.target  &&
                                     ctl.user_data.obj    && $(ctl.user_data.obj).length   &&
                                     $(ctl.user_data.obj).parents(".oj-tree:eq(0)")[0] !== e.target)
                  {  // node should not be from the same tree
                      var tr = this._reference(e["currentTarget"]),     //TDO
                          dc;
                      if (tr["data"]["dnd"]["foreign"])  {
                        dc = tr._getOptions()["dnd"]["drag_check"].call(this, { "o" : vars.o, "r" : tr._getContainer(), "is_root" : true });
                        if (dc === true || dc["inside"] === true || dc["before"] === true || dc["after"] === true)
                        {
                           tr._getOptions()["dnd"]["drag_finish"].call(this, { "o" : vars.o, "r" : tr._getContainer(), "is_root" : true });
                        }
                      }
                      else  {
                         tr._moveNode(vars.o, tr._getContainer(), "last", e[tr.options["dnd"]["copy_modifier"] + "Key"]);
                      }
                  }
                 }, this))
            .bind("mouseleave.ojtree", $.proxy(function (e)
               {
                  var ctl = this._data.dnd.ctl ;       // dnd helper object

                  if (e.relatedTarget && $(e.relatedTarget).hasClass(OJT_DROP_MARKER_LINE))  {
                    return false; 
                  }
                  if (ctl.is_drag && ctl.user_data.jstree)  {
                    if (this._data.dnd.i1) {
                       clearInterval(this._data.dnd.i1);
                    }
                    if (this._data.dnd.i2) {
                      clearInterval(this._data.dnd.i2);
                    }
                    if (this._data.dnd.to1)  {
                      clearTimeout(this._data.dnd.to1);
                    }
                    if (this._data.dnd.to2) {
                      clearTimeout(this._data.dnd.to2);
                    }
                    if (ctl.helper.children("ins").hasClass(OJT_DROP_OK))  {
                      ctl.helper.children("ins").removeClass(OJT_DROP_OK)
                                                .addClass(OJT_DROP_INVALID);
                      this._getContainer().addClass("oj-invalid-drop") ;      // set cursor on tree div

                      if (this._data.dnd.vars.o) {
                        this._data.dnd.vars.o.removeClass(OJ_VALID_DROP)
                                             .removeClass(OJ_INVALID_DROP) ;
                      }
                    }
                  }
                }, this))
            .bind("mousemove.ojtree", $.proxy(function (e)
               {
                  var ctl = this._data.dnd.ctl ;       // get the dnd helper object

                  if (ctl.is_drag && ctl.user_data.jstree) {
                    var cnt = this._getContainer()[0];

                    // Horizontal scroll
                    if (e.pageX + 24 > this._data.dnd.cof.left + this._data.dnd.cw) {
                      if (this._data.dnd.i1) {
                        clearInterval(this._data.dnd.i1);
                      }
                      this._data.dnd.i1 = setInterval($.proxy(function () {
                                                   this.scrollLeft += ctl.scroll_spd;
                                                 }, cnt), 100);
                    }
                    else if (e.pageX - 24 < this._data.dnd.cof.left)  {
                      if (this._data.dnd.i1) {
                        clearInterval(this._data.dnd.i1);
                      }
                      this._data.dnd.i1 = setInterval($.proxy(function () {
                                                this.scrollLeft -= ctl.scroll_spd;
                                              }, cnt), 100);
                    }
                    else   {
                      if (this._data.dnd.i1)  {
                        clearInterval(this._data.dnd.i1);
                      }
                    }

                    // Vertical scroll
                    if (e.pageY + 24 > this._data.dnd.cof.top + this._data.dnd.ch)  {
                      if (this._data.dnd.i2)  {
                        clearInterval(this._data.dnd.i2);
                      }
                      this._data.dnd.i2 = setInterval($.proxy(function () {
                                                this.scrollTop += ctl.scroll_spd;
                                             }, cnt), 100);
                    }
                    else if (e.pageY - 24 < this._data.dnd.cof.top) {
                        if (this._data.dnd.i2)  {
                          clearInterval(this._data.dnd.i2);
                        }
                        this._data.dnd.i2 = setInterval($.proxy(function () {
                                                 this.scrollTop -= ctl.scroll_spd;
                                               }, cnt), 100);
                    }
                    else   {
                       if (this._data.dnd.i2) {
                         clearInterval(this._data.dnd.i2);
                       }
                    }

                  }
                }, this))
            .bind("scroll.ojtree", $.proxy(function (e)
               { 
                  var ctl  = this._data.dnd.ctl,        // get the dnd helper object
                      vars = this._data.dnd.vars ; 

                  if (ctl.is_drag && ctl.user_data.jstree && vars.m && vars.ml)  {
                     vars.m.hide();
                     vars.ml.hide();
                  }
                }, this))
            .delegate(".oj-tree-list a", "mousedown.ojtree", $.proxy(function (e)
                { 
                  if (e.which === 1) {
                    this._start_drag(e.currentTarget, e);
                    return false;
                  }
                }, this))
            .delegate(".oj-tree-list a", "mouseenter.ojtree", $.proxy(function (e)
                { 
                  var ctl = this._data.dnd.ctl ;        // get the dnd helper object

                  if (ctl.is_drag && ctl.user_data.jstree)  {
                    this._dnd_enter(e.currentTarget);
                  }
                }, this))
            .delegate(".oj-tree-list a", "mousemove.ojtree", $.proxy(function (e)
                { 
                  var  ctl  = this._data.dnd.ctl,       // get the dnd helper object
                       vars = this._data.dnd.vars ;

                  if (ctl.is_drag && ctl.user_data.jstree)  {
                     if (! vars.r || ! vars.r.length || vars.r.children("a")[0] !== e.currentTarget) {
                         this._dnd_enter(e.currentTarget);
                     }
                     if (typeof this._data.dnd.off.top === "undefined") {
                       this._data.dnd.off = $(e.target).offset();
                     }
                     if (typeof this._data.dnd.off.top === "undefined")  {
                       this._data.dnd.off = $(e.target).offset();
                     }

                     this._data.dnd.w = (e.pageY - (this._data.dnd.off.top || 0)) % this._data.core.li_height ;

                     if (this._data.dnd.w < 0)   {
                       this._data.dnd.w += this._data.core.li_height;
                     }
                     this._dnd_show();
                  }
                }, this))
            .delegate(".oj-tree-list a", "mouseleave.ojtree", $.proxy(function (e)
                { 
                  var ctl  = this._data.dnd.ctl,        // get the dnd helper object
                      vars = this._data.dnd.vars ;

                  if (ctl.is_drag && ctl.user_data.jstree)  {
                     if (e.relatedTarget && $(e.relatedTarget).hasClass(OJT_DROP_MARKER_LINE))  {
                       return false; 
                     }
                     if (vars.m) {
                        vars.m.hide();
                     }
                     if (vars.ml) {
                       vars.ml.hide();
                     }
                     /*
                     var ec = $(e.currentTarget).closest("li"), 
                         er = $(e.relatedTarget).closest("li");
                     if(er[0] !== ec.prev()[0] && er[0] !== ec.next()[0]) {
                         if(m) { m.hide(); }
                         if(ml) { ml.hide(); }
                     }
                     */

                     this._data.dnd.mto = setTimeout( (function (t) {
                                                         return function () {
                                                                   t._dnd_leave(e);
                                                                };
                                                       })(this), 0);
                  }
                }, this))
            .delegate(".oj-tree-list a", "mouseup.ojtree", $.proxy(function (e)
                { 
                  var ctl = this._data.dnd.ctl ;       // get the dnd helper object

                  if (ctl.is_drag && ctl.user_data.jstree)  {
                    this._dnd_finish(e);
                  }
                }, this));

            $(document)
                .bind("drag_stop.ojtreeu", $.proxy(function ()
                   {
                     var dnd  = this._data.dnd,
                         vars = this._data.dnd.vars ;

                     if (dnd.to1) {
                       clearTimeout(dnd.to1);
                     }
                     if (dnd.to2) {
                       clearTimeout(dnd.to2);
                     }
                     if (dnd.i1)  {
                       clearInterval(dnd.i1);
                     }
                     if (dnd.i2)  {
                       clearInterval(dnd.i2);
                     }

                     dnd["after"]   = false;
                     dnd["before"]  = false;
                     dnd["inside"]  = false;
                     dnd.off        = false;
                     dnd.prepared   = false;
                     dnd.w          = false;
                     dnd.to1        = false;
                     dnd.to2        = false;
                     dnd.i1         = false;
                     dnd.i2         = false;
                     dnd.active     = false;
                     dnd.foreign    = false;

                     if (vars.m) {
                       vars.m.css({ "top" : "-2000px" });
                     }
                     if (vars.ml) {
                       vars.ml.css({ "top" : "-2000px" });
                     }
                   }, this))
                .bind("drag_start.ojtreeu", $.proxy(function (e, data)
                   {
                      if (data.data.jstree) { 
                        var et = $(data.event.target);
                        if (et.closest(".jstree").hasClass("oj-tree-" + this._getIndex())) {
                          this._dnd_enter(et);
                        }
                      }
                   }, this));

                /*
                .bind("keydown.jstree-" + this.get_index() + " keyup.jstree-" + this.get_index(), $.proxy(function(e) {
                        if($.vakata.dnd.is_drag && $.vakata.dnd.user_data.jstree && !this.data.dnd.foreign) {
                            var h = $.vakata.dnd.helper.children("ins");
                            if(e[this._get_settings().dnd.copy_modifier + "Key"] && h.hasClass("jstree-ok")) {
                                h.parent().html(h.parent().html().replace(/ \(Copy\)$/, "") + " (Copy)");
                            } 
                            else {
                                h.parent().html(h.parent().html().replace(/ \(Copy\)$/, ""));
                            }
                        }
                    }, this)); */



            var s = this.options["dnd"];
            if (s["drag_target"])
            {
               $(document)
                   .delegate(s["drag_target"], "mousedown.ojtree-" + this._getIndex(), $.proxy(function (e) {

                       var ctl  = this._data.dnd.ctl,
                           dnd  = this._data.dnd,
                           vars = this._data.dnd.vars ;

                       vars.o = e.target;

                       this._drag_start(e, { jstree : true,
                                             obj    : e.target
                                           }, "<ins class='oj-tree-icon'></ins>" + $(e.target).text());
                       if (this._data.themes) { 
                         if (vars.m) {
                            vars.m.addClass("oj-tree-" + this._data.themes.theme);
                         }
                         if (vars.ml) {
                           vars.ml.addClass("oj-tree-" + this._data.themes.theme);
                         }
                         ctl.helper.addClass("oj-tree-dnd-helper oj-tree-" + this._data.themes.theme); 
                       }

                       ctl.helper.children("ins").removeClass(OJT_DROP_OK)
                                                 .addClass(OJT_DROP_INVALID);
                       this._getContainer().addClass("oj-invalid-drop") ;      // set cursor on tree div

                       if (vars.o) {
                         vars.o.removeClass(OJ_VALID_DROP)
                               .addClass(OJ_INVALID_DROP) ;
                       }

                       var cnt     = this._getContainer();
                       dnd.cof     = cnt.offset();
                       dnd.cw      = parseInt(cnt.width(),10);
                       dnd.ch      = parseInt(cnt.height(),10);
                       dnd.foreign = true;
                       e.preventDefault();
                   }, this));
            }

            if (s["drop_target"])
            {
              $(document)
             .delegate(s["drop_target"], "mouseenter.ojtree-" + this._getIndex(), $.proxy(function (e)
                         {
                           var dnd  = this._data.dnd,
                               vars = this._data.dnd.vars ;

                           if (dnd.active && this.options["dnd"]["drop_check"].call(this, { "o" : vars.o, "r" : $(e.target), "e" : e }))  {
                             dnd.ctl.helper.children("ins")
                                           .removeClass(OJT_DROP_INVALID)
                                           .addClass(OJT_DROP_OK);
                             this._getContainer().removeClass("oj-invalid-drop") ;    // set cursor on tree div

                             if (vars.o) {
                               vars.o.removeClass(OJ_INVALID_DROP)
                                     .addClass(OJ_VALID_DROP) ;
                             }
                            }
                        }, this))
             .delegate(s["drop_target"], "mouseleave.ojtree-" + this._getIndex(), $.proxy(function (e)
                {
                   if (this._data.dnd.active)  {
                     this._data.dnd.ctl.helper.children("ins")
                                              .removeClass(OJT_DROP_OK)
                                              .addClass(OJT_DROP_INVALID);
                     this._getContainer().addClass("oj-invalid-drop") ;      // set cursor on tree div
                    }
                }, this))
             .delegate(s["drop_target"], "mouseup.ojtree-" + this._getIndex(), $.proxy(function (e)
                       {
                         var vars = this._data.dnd.vars ;

                         if (this._data.dnd.active && this._data.dnd.ctl.helper.children("ins").hasClass(OJT_DROP_OK))  {
                           this.options["dnd"]["drop_finish"].call(this, { "o" : vars.o, "r" : $(e.target), "e" : e });
                         }
                        }, this));
            }

          // TDO

     },


     /**
       *  Process Drag and Drop options.
       *  @private
       */
     _initDnDOpts : function()
     {
        this._data.dnd.reorder = false ;

        var opts = this.options["dnd"],
            ot   = (typeof opts),
            bFixup = true ;

        if (ot === "object") {
          opts = opts["reorder"] ;
          if (typeof opts === "string") {
            if (opts === "enable") {
              this._data.dnd.reorder = true ;
              bFixup = false ;
            }
            else if (opts == "disable") {
              bFixup = false ;
            }
          }
        }

        if (this._data.dnd.reorder) {
          //  Add our default requirements until these are made public and can be
          //  defined in options.
          this.options["dnd"] = {} ;
          this._applyDefaults(this.options["dnd"], this._data.dnd.defaults) ;
          this.options["dnd"]["reorder"] = "enable" ;
        }
        else {
          if (bFixup) {
            this.options["dnd"] = {"reorder" : "disable"} ;
          }
        }
     },


     /*
      *  Initialize the context menu cut/copy/paste/remove/rename support.
      *  @private
      */
/*                                Not currently used in V1
     _initCrrm :  function()
     {
        this._$container
                .bind("ojmove",
                     $.proxy(function (e, data)
                               {
                                 //if (this.options["crrm"]["move"]["openOnMove"])
                                 if (this._data.crrm.defaults["move"]["openOnMove"]) {
                                   var t = this;
//
//                                 data.rslt.np.parentsUntil(".oj-tree").addBack()
//                                                                      .filter(".oj-tree-closed")
//                                                                      .each(function ()
//                                                                        {
//                                                                         t["expand"](this, false, true);
//                                                                        });
//
                                 }
                               }, this));

     },
*/

     /**
       *  Initialize keyboard support
       *  @private
       */
     _initKeys  :  function()
     {
     },


     /**
       *  Initialize the context menu.  This is called on startup, or on option
       *  "contextMenu" change.
       *  @param {Object=} newVal   true if called because of an option change.
       *  @private
       */
     _initMenu : function(newVal)
     {
       var  menu, t, html, $html ;

// Temp code for M6 to clear bug 18712968.  Once the new methods
// are in place via Max and Jim, this whole mechanism will change. 

if ((! newVal) && (! this.options["contextMenu"])) {
 menu = this._$container.attr("contextmenu") ;   // check for attribute
 if (menu) {
   this.options["contextMenu"] = "#" + menu ;
 }
}

       if ((! newVal) && (! this.options["contextMenu"])) {
         return ;
       }

       menu =  newVal || this.options["contextMenu"] ;
       t = $.type(menu) ;
       if (t == "function") {
         try {
            menu = menu() ;             // call user's method to get the context menu
         }
         catch (e) {
            menu = null;
         }
         t = $.type(menu) ;
       }

       if (t !== "string") {
         return ;      // unknown
       }

       var $m = $(menu) ;                    // get the user's <ul> list   
       if ($m) {
         $m.css("display", "none") ;         // ensure it's not visible
         var  dm       = this._data.menu ;
         dm.$container = $m ;
         dm.usermenu   = true ;              // have a context menu
       }
       
       if (this._data.menu.usermenu) {       // if we have a context menu
         if (newVal) {                       // and it is being changed
           this._applyMenu() ;               // complete menu creation/attachnment
         }
       }

       //  If not a new val from options, Menu will be noted at the end of initialization in _start()
     },


     /**
       *   Check menu selected to see if it one of our predefined remove/cut/copy/paste id's
       *   @private
       */
     _handleContextMenuSelect: function(ev, ui)
     {
        //  Make sure its a menu ojselect and not from ojTree
        if (ui["inst"]) {
          return ;
        }

        // There may be multiple trees sharing (and thereby listening to) the same 
        // context menu.  Check that this tree is the same as the tree on which the
        // menu was invoked.
        if (this._data.menu.treeDivId != this._elemId.substr(1)) {
          return ;
        }

        var  id = ui? ui.item.attr("id") : undefined ;

        if (id === "ojtreecopy") {
          this._crrm_copy(this._data.menu.node);
        }
        else if (id === "ojtreecut") {
          this._crrm_cut(this._data.menu.node);
        }
        else if (id === "ojtreepaste") {
          this._crrm_paste(this._data.menu.node);
        }
        else if (id === "ojtreeremove") {
          if (this["isSelected"](this._data.menu.node)) {
            this._crrm_remove();
          }
          else {
           this._crrm_remove(this._data.menu.node);
          }
        }
        else if (id === "ojtreerename") {
          this._crrm_rename(this._data.menu.node);
        }
        else if (id === "ojtreecreate") {
          this._crrm_create(this._data.menu.node);
        }
     },

    /**
      *  Notification that the user has invoked the context menu via the default
      *  gestures: right-click, pressHold, and Shift-F10.
      *  we Ensure that focus returns to the tree if the menu is dismissed in
      *  some way via the keyboard.
      *  @param {Object} menu       The JET Menu to open as a context menu
      *  @param {Event}  event      Triggering event
      *  @param {string} eventType  Triggering event type (e.g. 'keyboard', 'mouse', 'touch')
      *  @private	
      */
    _NotifyContextMenuGesture: function(menu, event, eventType)
     {
        //this._trace("_NotifyContextMenuGesture() eventType=" + eventType + " event.type=" + event.type) ;
        var keyboard = (eventType === 'keyboard') ;

        if ((event.type != "contextmenu") && (!keyboard) && (eventType != 'touch')) {
          return ;
        }

        //  Get the tree node acted on
        this._data.menu.node  = keyboard? this._data.ui.hovered : $(event.target);
        var textElem          = this._data.menu.node.find('.oj-tree-title')[0] ;  // the node text <span>

        this._data.menu.activenode = null ;    // clear keyboard active node

        if (! this._data.menu.node) {          // is there an active (hovered) node ?
          event.preventDefault() ;
          return ;
        }

        var $nodeElem = keyboard? this._data.menu.node : this._data.menu.node.closest("li") ;
        //console.log("_NotifyContextMenuGesture node=" + $nodeElem.attr("id"));

        this._data.menu.treeDivId = this._data.menu.node.closest("div").attr("id") ;
        
        var openOptions = {"launcher" : this._$container_ul};
        if (keyboard) {
          // Set position relative to the node in the SHIFT+F10 case.
          // Set here to avoid conflicting with user override in before open event

          openOptions["position"] = { "of" : textElem } ;
        }

        // Set submenu "Paste" disable state, depending on whether there's been a
        // previous "cut" or "copy"
        if (this._data.menu.usermenu && this._data.menu.$elemPaste) {
          var state         = !! this._data.menu.$elemPaste.hasClass(OJ_DISABLED) ;
          var disabledState = (!this._data.crrm.ct_nodes && !this._data.crrm.cp_nodes) ;

          if (state != disabledState) {
            if (disabledState) {
              this._data.menu.$elemPaste.addClass(OJ_DISABLED) ;
            }
            else {
              this._data.menu.$elemPaste.removeClass(OJ_DISABLED) ;
            }
            this._data.menu.$container.ojMenu("refresh") ;
          }
        }

        this._OpenContextMenu(event, eventType, openOptions);
     },

     /**
       *  Process Core options
       *  @private
       */
     _initCoreOpts : function()
     {
        var val = this.options["selectionMode"] ;

        val = (val == undefined)? "single" : val ;
        if (val === "none") {
          val = 0 ;
        }
        else if (val === "single") {
          val = 1;
        }
        else if (val === "multiple") {
          val = -1 ;
        }
        this._data.core.selectMode = val ;

        this._data.themes.icons = this.options["icons"] ;

        this._initExpandedOpts();             // (updates this._data.coreToExpand)
        this._data.core.toLoad   = this.options["initLoaded"];
     },


     /**
       *  Process UI type options
       *  @private
       */
     _initUIOpts : function()
     {
     },

     /**
       *  Process data source options
       *  @private
       */
     _initDSOpts : function(bInit)
     {
        var s = this.options["data"],
            dt,
            ot ;

        this._data.ds.type = DS_NONE ;
        this._data.html.useExistingMarkup  = false ;
        this._data.html.cloneMarkup        = false ;
        // _data.html.markup_ul, _data.html.markup and  _data.html.markup_div
        // are left intact for destroy.

        if (s) {
          ot = $.type(s) ;
          if (ot === "string") {
           if (this._isHtml(s)) {
             this._data.ds.type = DS_HTML ;          // we have an non-Tree DS html source
           }
           else {
             this._data.ds.type = DS_JSON ;          // we have a non-tree DS json source
             this._initJsonOpts() ;
           }
          }
          else if (ot === "array") {
             //  we have an array of local json objects
             this._data.ds.type = DS_JSON ;          // we have a non-tree DS json source
          }
          else if (ot === "object") { 
             try
             {                                           // don't crash if no ojdatacollection-common
               if (s instanceof oj.JsonTreeDataSource) {
                 this._data.ds.type = DS_TREE ;          // we have a tree DS source
               }
             }
             catch(e)
             {
                this._data.ds.type = DS_ERROR
             } ;
             if (this._data.ds.type !== DS_TREE) {
               try
               {                                           // don't crash if no ojtree-model
                  if (s instanceof oj.CollectionTreeDataSource) {
                    this._data.ds.type = DS_COLLECTION ;   // we have a tree DS source
                  }
               }
               catch(e)
               {
                  this._data.ds.type = DS_ERROR
               } ;
             }
             if ((this._data.ds.type === DS_TREE)   ||
                 (this._data.ds.type === DS_COLLECTION))  {
               this._initTreeDSOpts() ;
             }
             else {
               if (s["data"] || s["ajax"])  {
                 dt = s["dataType"] ;
                 if (dt) {
                   if (dt === "json") {
                     this._data.ds.type = DS_JSON ;    // we have a non-tree DS json source
                     this._initJsonOpts() ;
                   }
                   else if (dt === "html") {
                     this._data.ds.type = DS_HTML ;    // we have a non-tree DS html source
                     this._initHtmlOpts() ;
                   }
                 }
                 else {
                   s["dataType"]      = "json" ;
                   this._data.ds.type = DS_JSON ;      // we have a non-tree DS json source
                   this._initJsonOpts() ;
                 }
               }
             }
          }
        }

        //  If no data defined, see if there is initial user markup in the div
        if (bInit && this._data.ds.type == DS_NONE) {
          if (this._$container.find("ul").length > 0) {
            this._data.ds.type                = DS_HTML ; // note we have an non-Tree DS html source
            this._data.html.useExistingMarkup = true ;
          }
        }
     },

     /**
       *  Process Tree DataSource options
       *  @private
       */
     _initTreeDSOpts : function()
     {
     },

     /**
       *  Process json_data options
       *  @private
       */
     _initJsonOpts : function()
     {
     },


     /**
       *  Process html_data options
       *  @private
       */
     _initHtmlOpts : function()
     {
     },

     /**
       *  Process cut/copy/paste/rename/remove options
       *  @private
       */
//   _initCrrmOpts : function()
//   {
//       if (this.options["crrm"] == undefined)  {
//         //  Will use our defaults until these are made public and can be
//         //  defined in options.
//         this._applyDefaults(this.options["crrm"], this._data.crrm.defaults) ;
//       }
//   },

     /**
       *  Process context menu options
       *  @private
       */
     _initMenuOpts : function()
     {
     },

//   /**
//     *  Process theme options
//     *  @private
//     */
//   _initThemeOpts : function()
//   {
//     //  Themes support not yet published
//   },


     /**
       *  Process node type options
       *  @private
       */
     _initTypeOpts  : function()
     {
       var opts ;
       var o = this.options["types"] ;

//     For V1, there are no default options that we publish
//     if (typeof o === "object") {
//       //  Add our default requirements until these are made public and can be
//       //  defined in options.
//       this._applyDefaults(this["options"]["types"], this._data.types.defaults) ;
//     }
       if (typeof o === "object") {
         this._applyDefaults(o, {"attr" : this._data.types.defaults["attr"]}) ;
       }
     },

     /**
       *  Set/reset the initExpanded opts
       *  @private
       */
     _initExpandedOpts : function()
     {
        this._data.core.toExpand = this._varCopy(this.options, "initExpanded");  
        if (this._data.core.toExpand == null) {
          this._data.core.toExpand = [] ;
        }
     },

     /**
       *  Set up tree instance data
       *  @private
       */
     _initData : function()
     {
        //  Establish working values, and define default values

        var data = this._data ;
         
        //  Core
        data.core  =  {
                        initLoaded     : [],
                        selectMode     : 1,          //  0 = no selection, 1, 2 ..., or -1 for unlimited
                        load_open      : false,
                        li_height      : 0,
                        notify_plugins : false,       // TDO
                        toExpand       : false,
                        toLoad         : false,
                        prepared_move  : {},         // for the _moveNode function
                        suppressSelectEvent : false, // used for batching up optionChange events
                        strings        : {}          // translated/escaped strings
                      };

        // UI
        data.ui =  {
                      selected          : $(),       // selected node jquery list
                      lastSelected      : false,
                      hovered           : null,      // currently hovered
                      lastHovered       : null,      // last hovered before blur
                      disclosureClick   : false,     // used for 1st time disclosure click
                      to_select         : null,      // initial selection in options:selection
                      opacity           : 1,         // used by disable/_lock()
                      spacebar          : false,     // true if select caused by keybd (toggles)
                      focused           : false,     // tree has focus
                      animDurDiv        : null,      // div used to get animation duration
                      touchEvent        : false      // true if touch event
                   };

        data.ui.defaults = {                          // default values not yet published or suppressed
                                                      // design committee 
                             "selectMultipleModifier"   :  "ctrl" ,      // on, or ctrl, shift, alt
                             "selectRangeModifier"      :  "shift" ,
                             "disableSelectingChildren" :  false
                           } ;


        //  Creating/renaming/removing/moving via context menu

        data.crrm = {} ;
        data.crrm.cp_nodes = false;     // nodes that have been copy'd
        data.crrm.ct_nodes = false;     // node(s) that have been cut
        data.crrm.defaults = {
                               "inputWidthLimit" : 200,
                               "move"            : {
                                                     "alwaysCopy"       : false, // false, true or "multitree"
                                                     "openOnMove"       : true,
                                                     "defaultPosition"  : "last",
                                                     "checkMove"        : function (m) {
                                                                             return true;
                                                                          }
                                                   }
                             };
        data.crrm.prepared_move = {} ;   // for the move node function

        // Data Source

        data.ds = {} ;
        data.ds.progressiveRender = false ;       // options not currently exposed
        data.ds.progressiveUnload = false ;
        data.ds.correctState      = true ;

        data.ds.type              = DS_NONE ;     // type of data source (DS_TREE, DS_JSON, DS_HTML)

        //  json data source

        data.json = {} ;
        data.json.defaults = {
                               "data"  : false,   // `data` can be a function:
                                                  //  accepts two arguments - node being loaded
                                                  //  and a callback to pass the result to,
                                                  //  & ajax won't be supported
                               "ajax"  : false
                             } ;

        // html data source

       data.html = {} ;
       data.html.defaults = {
                              "data"   : false,   // `data` can be a function:
                              "ajax"   : false
                            };

       data.html.useExistingMarkup  = false ;  // true == use existing div markup
       data.html.markup_ul          = false ;  // user's <ul>
       data.html.markup_div         = false ;  // the added div used to store user's <ul>
       data.html.markup             = false ;  // the user's markup ( <li>'s )
       data.html.cloneMarkup        = false ;  // our clone of user's orig markup <li's>


        //  Themes

        data.themes         = {} ;
        data.themes.icons   = true ;
        data.themes.dots    = false ;
        data.themes.theme   = "default" ;
        data.themes.url     = false ;

        data.themes.themes_loaded = [];
        data.themes._themes = false;   // path to themes folder - if false it will be autodetected

        //  Types

        data.types           = {} ;
        data.types.attachTo  = [];
        data.types.defType   = false ;                    // true if "default" type defined
        data.types.defaults  = {                                 //  Options not published in V1
                                 "maxChildren"         : -1,     // defines max number of root nodes
                                                                 // (-1 = unlimited, -2 = disable maxChildren checking)
                                 "maxDepth"            : -1,     //  maximum depth of the tree
                                 "validChildren"       : "all",  // defines valid node types for the root nodes
                                 "useData"             : false,  // whether to use $.data     TDO
                                 "attr"                : "type", // attr name in <li> where type is stored
                                 "types" : {                     // a list of types
                                             "default" : {       // the default type
                                                           "maxChildren"   : -1,
                                                           "maxDepth"      : -1,
                                                           "validChildren" : "all"
                                       
                                                           // Bound functions - you can bind any other function here
                                                           // (using boolean or function)
                                                           //"select_node" : true
                                                         }
                                           }
                               } ;

        //  Context menu

        data.menu              = {} ;
        data.menu.usermenu     = false ;   // user has supplied an ojMenu id if true
        data.menu.$container   = false ;   // the menu <ul>
        data.menu.$elemPaste   = false ;   // the menu "Paste" element
        data.menu.node         = false ;   // the tree node the menu was activated on
        data.menu.activenode   = false ;   // active node for shift-F10


        //  Keyboard support

        data.keys         = {} ;
        data.keys.enabled = true ;
        data.keys.bound   = [];

        // Drag and Drop

        data.dnd            = {} ;
        data.dnd.reorder    = false ;

        data.dnd.active     = false ;
        data.dnd["after"]   = false ;
        data.dnd["inside"]  = false ;
        data.dnd["before"]  = false ;
        data.dnd.off        = false ;
        data.dnd.prepared   = false ;
        data.dnd.w          = 0,
        data.dnd.to1        = false ;
        data.dnd.to2        = false ;
        data.dnd.cof        = false ;
        data.dnd.cw         = false ;
        data.dnd.ch         = false ;
        data.dnd.i1         = false ;
        data.dnd.i2         = false ;
        data.dnd.mto        = false ;
        data.dnd.ml_width   = 100 ;    // marker line width - updated in _initDnd() from css
        data.dnd.targ_ml_width = 100 ; // marker line width of target node
  
        data.dnd.defaults = {
                              "copy_modifier"  : "ctrl",
                              "check_timeout"  : 100,
                              "open_timeout"   : 500,
                              "drop_target"    : ".oj-tree-drop",
                              "drop_check"     : function (data) { return true; },
                              "drop_finish"    : $.noop,
                              "drag_target"    : ".oj-tree-draggable",
                              "drag_finish"    : $.noop,
                              "drag_check"     : function (data)
                                                 {
                                                    return {
                                                             "after"  : false,
                                                             "before" : false,
                                                             "inside" : true
                                                           };
                                                 }
                            } ;

        data.dnd.vars          = {}          // drag/drop block of vars 
        data.dnd.vars.o        = false ;     // node being dragged
        data.dnd.vars.r        = false ;     // reference node 
        data.dnd.vars.m        = false ;     // marker pointer div
        data.dnd.vars.ml       = false ;     // marker line div
        data.dnd.vars.sli      = undefined ;
        data.dnd.vars.sti      = undefined ;
        data.dnd.vars.dir1     = false ;
        data.dnd.vars.dir2     = false ;
        data.dnd.vars.last_pos = false ;

        data.dnd.ctl              = {} ;     //  dnd helper object (port note: this was the old ojtreeu helper)
        data.dnd.ctl.is_down      = false,
        data.dnd.ctl.is_drag      = false,
        data.dnd.ctl.helper       = false,
        data.dnd.ctl.scroll_spd   = 10,
        data.dnd.ctl.init_x       = 0,
        data.dnd.ctl.init_y       = 0,
        data.dnd.ctl.threshold    = 5,
        data.dnd.ctl.helper_horiz = 15,      // horiz disp for dragged text div from mouse
        data.dnd.ctl.helper_top   = 5,       // top disp for dragged text div from mouse 
        data.dnd.ctl.user_data    = {}
     },

     /**
       *  @private
       */
     _fix_scroll : function (obj)
     {
        var c = this._$container[0],
            t;
       
       if (c.scrollHeight > c.offsetHeight)
       {
          obj = this._getNode(obj);
          if (!obj || obj === -1 || !obj.length || !obj.is(":visible"))  {
             return;
          }

          t = obj.offset().top - this._$container.offset().top;

          if (t < 0)  { 
            c.scrollTop = c.scrollTop + t - 1; 
          }

         if (t + this._data.core.li_height + (c.scrollWidth > c.offsetWidth ? scrollbar_width : 0) > c.offsetHeight)  { 
           c.scrollTop = c.scrollTop + (t - c.offsetHeight + this._data.core.li_height + 1 + (c.scrollWidth > c.offsetWidth ? scrollbar_width : 0)); 
          }
       }
     },

     /**
       *  @private
       */
     _setFocus   : function ()            // undocumented per Design Review
     { 
/*                                        // bug 18857563
       if (this._isFocused()) {
          return;
       }
       var f = this._focused();
       if (f) {
         this._unsetFocus();
       }

       this._$container.addClass("oj-tree-focused"); 
       if (this._$container_ul) {
         this._$container_ul.focus() ;
       }
       this._data.ui.focused = true ;
       this._emitEvent(null, "setfocus", true);
*/
     },

//                                         // currently unused
//   /**
//      *  Return true if this tree has focus.
//      *  @private
//      */
//    _isFocused  : function ()            // undocumented per Design Review
//    { 
//       return this._data.ui.focused ;
//    },

     /**
       *  @private
       */
     _unsetFocus : function ()            // undocumented per Design Review
     {
/*    See setFocus()
        if (this._isFocused()) {
          this._$container.removeClass("oj-tree-focused"); 
          this._data.ui.focused = false ;
          if (this._$container_ul) {
            this._$container_ul.blur() ;
          }
        }
        this._emitEvent(null, "unsetfocus", true);
*/
     },

//                                          // currently unused
//   /**
//     *  Return the "this of the tree that currently has focus, or null.
//     *  @private
//     */
//   _focused : function ()
//   {
//      var ret = null ;
//      $.each(_aInstances, function(i, _this) {
//          if (_this._data.ui.focused) {
//            ret = _this ;
//            return false ;
//          }
//      });
//
//      return ret ;
//   },

     /**
       *  Returns the next instance index to use.
       *  @private
       */
     _newIndex: function()
     {
       return ++_instance
     },

     /**
       *  Returns the current instance index.
       *  @private
       */
     _getIndex : function()
     {
        return this._index ;
     },

     /**
       *  Returns a copy of the options.
       *  @private
       */
     _getOptions : function()
     {
        return ($.extend(true, {}, this.options)) ;
     },

     /**
       *  Returns the tree containing div
       *  @private
       */ 
     _getContainer : function()
     {
        return this._$container ;
     },

     /**
       *   Handle keystroke and combination keystrokes.
       *   The return value is passed to the jQuery keydown handler.
       *   @private
       */
     _keyHandler : {

         "up" : function ()
         { 
            var o = this._data.ui.hovered || this._data.ui.lastSelected || -1;
            this["hover"](this._getPrev(o));
            return false; 
         },

         "ctrl+up" : function ()
         { 
            var o = this._data.ui.hovered || this._data.ui.lastSelected || -1;
            this["hover"](this._getPrev(o));
            return false; 
         },

         "shift+up" : function (t)
         { 
            var o = this._data.ui.hovered || this._data.ui.lastSelected || -1;
            this["select"](this._getPrev(o), (this._data.ui.selectMode !== -1));
            return false; 
         },

         "down" : function ()
         { 
            var o = this._data.ui.hovered || this._data.ui.lastSelected || -1;
            this["hover"](this._getNext(o));
            return false;
         },

         "ctrl+down" : function ()
         { 
            var o = this._data.ui.hovered || this._data.ui.lastSelected || -1;
            this["hover"](this._getNext(o));
            return false;
         },

         "shift+down" : function ()
         { 
           var o = this._data.ui.hovered || this._data.ui.lastSelected || -1;
           this["select"](this._getNext(o), (this._data.ui.selectMode !== -1));
           return false;
         },

         "left" : function ()
         { 
            var o = this._data.ui.hovered || this._data.ui.lastSelected;
            if (o) {
              if (o.hasClass(OJ_EXPANDED))  {
                this["collapse"](o);
              }
              else  {
                this["hover"](this._getPrev(o));
              }
            }
            return false;
         },

         "ctrl+left" : function ()
         { 
           var o = this._data.ui.hovered || this._data.ui.lastSelected;
           if (o)  {
             if (o.hasClass(OJ_EXPANDED))  {
                this["collapse"](o);
             }
             else  {
               this["hover"](this._getPrev(o));
             }
           }
           return false;
         },

         "shift+left" : function ()
         { 
           var o = this._data.ui.hovered || this._data.ui.lastSelected;
            if (o) {
              if (o.hasClass(OJ_EXPANDED)) {
                this["collapse"](o);
              }
              else  {
                this["hover"](this._getPrev(o));
              }
            }
            return false;
         },

         "right" : function ()
         { 
            var o = this._data.ui.hovered || this._data.ui.lastSelected;
            if (o && o.length) {
               if (o.hasClass(OJ_COLLAPSED))  {
                  this["expand"](o);
               }
               else  {
                  this["hover"](this._getNext(o));
               }
            }
            return false;
         },

         "ctrl+right" : function ()
         { 
            var o = this._data.ui.hovered || this._data.ui.lastSelected;
            if (o && o.length) {
               if (o.hasClass(OJ_COLLAPSED)) {
                 this["expand"](o);
               }
               else   {
                 this["hover"](this._getNext(o));
               }
            }
            return false;
         },

         "shift+right" : function ()
         { 
            var o = this._data.ui.hovered || this._data.ui.lastSelected;
            if (o && o.length) {
              if (o.hasClass(OJ_COLLAPSED))  {
                this["expand"](o);
              }
              else  {
                this["hover"](this._getNext(o));
              }
            }
            return false;
         },

         "space" : function ()              // toggle hovered node select status
         { 
            if (this._data.ui.hovered)  {
              this._data.ui.spacebar = true ;      // note keyboard - so click will toggle
              this._data.ui.hovered.children("a:eq(0)").click();
              this._data.ui.spacebar = false ;
            } 
            return false; 
         },

         "home" : function ()               // move hover to top node
         { 
           this["hover"](this._$container_ul.find("li:first"));
           return false; 
         },

         "end" : function ()                // move hover to last visible node
         { 
           var a = this._$container_ul.find("li.oj-tree-last:visible");
           if (a && a.length) {
             this["hover"](a[a.length-1]) ;
           }
           return false; 
         },

         "*" : function ()                  // expand all nodes
         { 
           var l = this._$container_ul.find("a") ;
           this._expandAll(-1, false) ;
           return false; 
         },

         "ctrl+space" : function (event)    // toggle the select state
         { 
           if (this._data.ui.hovered)  { 
             var ev = $.Event("click") ;
             ev.ctrlKey = true;
             this._data.ui.hovered.children("a:eq(0)").trigger(ev);
           } 
           return false; 
         },

         "shift+space" : function (event)   // select to the hovered node
         { 
           if (this._data.ui.hovered)  {
             var ev = $.Event("click") ;
             ev.shiftKey = true;
             this._data.ui.hovered.children("a:eq(0)").trigger(ev);
           } 
           return false; 
         },

         "shift+home" : function (event)   // extend selection to top node
         { 
           var prevSelections ;
           var hover  = this._data.ui.hovered;

           if (hover)  {
             var bContinue  = true,
                 _this      = this,
                 nodes      = this._$container_ul.find("li:visible");

             // Batch up the optionChange/selection events so that the final result is
             // fired in the optionChange event (rather than individual events).
             this._data.core.suppressSelectEvent = true  ;
             prevSelections =  this.options["selection"].slice(0) ;  // make new array for optionChange event

             hover = hover[0] ;
             $.each(nodes, function(i, node) {         // select from top
                  if (node == hover) {                 // until we find our
                    bContinue = false ;                // starting node
                  }
                  _this._select(node, true) ; 
                  return bContinue ;
             });

             this._data.core.suppressSelectEvent = false ;
             this._fireOptionChangeEvent("selection", prevSelections, null, event) ; 
           } 

           return false; 
         },

         "shift+pgdn" : function (event)      // extend selection to last node
         { 
           var prevSelections ;
           var hover  =  this._data.ui.lastSelected || this._data.ui.hovered ;

           if (hover)  {
             var bFound = false,
                 _this  = this,
                 l      = this._$container_ul.find("li:visible");


             // Will batch up the optionChange/selection events so that the final result is fired in
             // the optionChange event (rather than individual events).
             this._data.core.suppressSelectEvent = true  ;
             prevSelections =  this.options["selection"].slice(0) ;  // make new array for optionChange event

             hover = hover[0] ;
             $.each(l, function(i, node) {
                if (! bFound) {                        // select to bottom from 
                  bFound = (node == hover) ;           // last selected node
                }
                if (bFound && (! _this["isSelected"](node))) {
                  _this._select(node, true) ; 
                }
                return true ;
             });

             this._data.core.suppressSelectEvent = false ;
             this._fireOptionChangeEvent("selection", prevSelections, null, event) ; 
           } 

           return false; 
         },

         "f2" : function ()
         {
//         this["rename"](this._data.ui.hovered || this._data.ui.lastSelected);
           this._crrm_rename(this._data.ui.hovered || this._data.ui.lastSelected) ;
           return false ;
         },

         "del" : function ()
         {
           this["remove"](this._data.ui.hovered || this._getNode(null));
           return false ;
         }
     },

     /**
       *   Attach the user menu <ul> list to the tree div, and set up listeners on it.
       *   @private
       */
     _applyMenu : function()
     {
       if (! this._data.menu.usermenu) {
         return ;
       }

       // Add our listeners so that we can handle build-in cut/copy/paste, etc
       var $menuContainer = this._data.menu.$container ;
       var _this          = this ;

       $menuContainer.on("ojselect",     $.proxy(this._handleContextMenuSelect,     this));

       // If there are any ojTree built in menu item ids, construct the menu items
       var listItems = $menuContainer.find("[data-oj-command]");
       var bChanged  = false ;

       listItems.each(function()  {
              var cmd;
              if ($(this).children('a').length === 0)  {
                cmd = $(this).attr('data-oj-command').split("oj-tree-");                // get cut/paste, etc
                $(this).replaceWith(_this._buildContextMenuItem(cmd[cmd.length-1]));    //@HTMLUpdateOK
                if ($(this).hasClass("oj-menu-divider")) {
                  $(this).removeClass("oj-menu-divider")
                         .addClass("oj-menu-item") ;
                }
                bChanged = true ;
              }
          });

       if (bChanged) {
         $menuContainer.ojMenu('refresh');
       }

       //  Note "paste" element for disabling if no prev cut/copy
       this._data.menu.$elemPaste = $menuContainer.find("#ojtreepaste") ;
       
     },

     /**
       *  Clear out any contextMenu data.
       *  @private
       */
     _clearMenu : function() {

       var um = this._data.menu ;

       if (um.usermenu)  {
         um.usermenu   = false ;
         um.$container.off("ojselect") ;
         um.$container = null ;
       }
     },

     /**
       * Builds a menu for a command, takes care of submenus where appropriate
       * @param {string} cmd
       * @private    
       */            
     _buildContextMenuItem : function(cmd)
     {
        return $(this._buildContextMenuListItem(cmd)) ;
     },

     /**
       * Builds a context menu list item from a command
       * @param {string} cmd the string to look up command value for as well as translation
       * @return {string} an HTML string containing a list item and a  
       * @private    
       */
     _buildContextMenuListItem: function(cmd)
     {
        var id = _arMenuCmdMap[cmd] ;
        return '<li id=' + id + '>' + this._buildContextMenuLabel(cmd) + '</li>';
     },

     /**
       * Builds a context menu anchor element with translated text
       * @param {string} cmd the command string whose label translated label should be used.
       * @return {string} an HTML anchor element string containing the translated label  
       * @private    
       */
     _buildContextMenuLabel: function(cmd)
     {
         var key = _arMenuKeyMap[cmd] ;
         return '<a href="#">' + this._getString(key) + '</a>';
     },        


     /**
       *  Menu "cut" functionality
       *  @private
       */
     _crrm_cut : function (obj)
     {
        obj = this._getNode(obj, true);
        if (!obj || !obj.length)  {
          return false;
        }

        this._data.crrm.cp_nodes = false;
        this._data.crrm.ct_nodes = obj;

        this._emitEvent({ "obj" : obj }, "cut");
     },

     /**
       *  Menu "copy" functionality
       *  @private
       */
     _crrm_copy : function (obj)
     {
        obj = this._getNode(obj, true);
        if (!obj || !obj.length)  {
          return false;
        }
        this._data.crrm.ct_nodes = false;
        this._data.crrm.cp_nodes = obj;
        this._emitEvent({ "obj" : obj }, "copy");
     },

     /**
       *  Menu "paste" functionality
       *  @private
       */
     _crrm_paste : function (obj)
     { 
        obj = this._getNode(obj);
        if (!obj || !obj.length) {
          return false;
        }
        var nodes = this._data.crrm.ct_nodes ? this._data.crrm.ct_nodes : this._data.crrm.cp_nodes;

        if (! this._data.crrm.ct_nodes && !this._data.crrm.cp_nodes)  {
           return false;
         }
         if (this._data.crrm.ct_nodes)  {
//         this.move_node(this._data.crrm.ct_nodes, obj);
           this._crrm_move_node(this._data.crrm.ct_nodes, obj);
           this._data.crrm.ct_nodes = false;
         }
         if (this._data.crrm.ct_nodes)
         {
//         this.move_node(this._data.crrm.ct_nodes, obj);
           this._crrm_move_node(this._data.crrm.ct_nodes, obj);
           this._data.crrm.ct_nodes = false;
         }
         if (this._data.crrm.cp_nodes)  {
//         this.move_node(this._data.crrm.cp_nodes, obj, false, true);
           this._crrm_move_node(this._data.crrm.cp_nodes, obj, false, true);
         }
         this._emitEvent({ "obj" : obj, "nodes" : nodes}, "paste");
     },

     /**
       *  Menu move node support for paste
       * @private
       */
     _crrm_move_node : function (obj, ref, position, is_copy, is_prepared, skip_check)
     {
        //var s = this.options["crrm"]["move"];
        var s = this._data.crrm.defaults["move"];
        if (! is_prepared)  { 
          if (typeof position === "undefined")  {
            position = s["defaultPosition"];
          }
          if (position === "inside" && !s["defaultPosition"].match(/^(before|after)$/))  {
            position = s["defaultPosition"];
          }
            return this._moveNode(obj, ref, position, is_copy, false, skip_check);
        }

        // if the move is already prepared
        if (s["alwaysCopy"] === true || (s["alwaysCopy"] === "multitree" && obj.rt._getIndex() !== obj.ot._getIndex() ))  { 
          is_copy = true;
        }
        this._moveNode(obj, ref, position, is_copy, true, skip_check);
     },

     /**
       *  Menu "remove" (i.e. delete) functionality
       *  @private
       */
     _crrm_remove : function (obj)
     {
        obj = this._getNode(obj, true);

//      var p   = this._getParent(obj),
//          prev = this._getPrev(obj);

         this.__rollback();
         obj = this["remove"](obj);
//         if (obj !== false) {
//           this._emitEvent({ "obj" : obj, "prev" : prev, "parent" : p }, "remove");
//         }
     },

     /**
       *  Menu rename a node functionality
       *  @private
       */
     _crrm_rename  :  function (obj)
     {
        var f = this._emitEvent;

        obj = this._getNode(obj);
        this.__rollback();
        this._crrm_showInput(obj, function (obj, new_name, old_name)
                                  { 
                                    //  Emit rename event
//                                    f.call(this, {
//                                                   "obj"       : obj,
//                                                   "title"     : new_name,
//                                                   "prevTitle" : old_name 
//                                                 },  "rename");
                                  });
     },

     /**
       *  Turn node into an input field to allow the user to rename the node.
       *  @private
       */
     _crrm_showInput : function (obj, callback)
     {
        obj = this._getNode(obj);

        var rtl = this._isRtl,
//          w   = this.options["crrm"]["inputWidthLimit"],       // applyDefaults() not done for V1
            w   = this._data.crrm.defaults["inputWidthLimit"],
            w1  = obj.children("ins").width(),
            w2  = obj.find("> a:visible > ins").width() * obj.find("> a:visible > ins").length,
            t   = this["getText"](obj),
            _this = this,

            h1  = $("<div />", { css : { "position"   : "absolute",
                                         "top"        : "-200px",
                                         "left"       : (rtl ? "0px" : "-1000px"),
                                         "visibility" : "hidden"
                                       }
                               }).appendTo("body"),               //@HTMLUpdateOK

            h2  = obj.css("position", "relative").append(         //@HTMLUpdateOK
                $("<input />", { 
                    "value" : t,
                    "class" : "oj-tree-rename-input",
                    // "size" : t.length,
                    "css" : {
                              "padding"  : "0",
                              "border"   : "1px solid silver",
                              "position" : "absolute",
                              "left"     : (rtl ? "auto" : (w1 + w2 + 4) + "px"),
                              "right"    : (rtl ? (w1 + w2 + 4) + "px" : "auto"),
                              "top"      : "0px",
                              "height"   : (this._data.core.li_height - 2) + "px",
                              "lineHeight" : (this._data.core.li_height - 2) + "px",
                              "width"    : "150px" // will be set a bit further down
                             },
                    "blur" : $.proxy(function ()
                      {
                        var i = obj.children(".oj-tree-rename-input"),
                            v = i.val();
                        if (v === "")  {
                          v = t;
                        }
                        h1.remove();
                        i.remove();                    // rollback purposes
                        this._set_text(obj, t);        // rollback purposes
                        this._rename_node(obj, v);
                        callback.call(this, obj, v, t);
                        obj.css("position","");
                    }, this),

                    "keyup" : function (event)
                      {
                        var key = event.keyCode || event.which;
// ignore first enter, it appears to be left
// over from hitting enter on the menu rename item????!!!
if (! _this._done) {
  _this._done=true ;
  return false;
}
                        if(key == 27)  {
_this._done = false ;
                           this.value = t;
                           this.blur();
                           return;
                        }
                        else if(key == 13)  {
_this._done = false ;
                           this.blur();
                           return;
                        }
                        else  {
                           h2.width(Math.min(h1.text("pW" + this.value).width(),w));
                        }
                    },

                    "keypress" : function(event)
                    {
                      var key = event.keyCode || event.which;
                      if (key == 13)  {
                        return false;
                      }
                    }
                })
            ).children(".oj-tree-rename-input"); 
            this._set_text(obj, "");
            h1.css({
                    fontFamily      : h2.css('fontFamily')      || '',
                    fontSize        : h2.css('fontSize')        || '',
                    fontWeight      : h2.css('fontWeight')      || '',
                    fontStyle       : h2.css('fontStyle')       || '',
                    fontStretch     : h2.css('fontStretch')     || '',
                    fontVariant     : h2.css('fontVariant')     || '',
                    letterSpacing   : h2.css('letterSpacing')   || '',
                    wordSpacing     : h2.css('wordSpacing')     || ''
            });
            h2.width(Math.min(h1.text("pW" + h2[0].value).width(),w))[0]["select"]();
     },

     /**
       *  @private
       */
     _crrm_create : function (obj, position, js, callback, skip_rename)
     {
        var t, _this = this;
        obj = this._getNode(obj);
        if (!obj) {
           obj = -1;
        }
        this.__rollback();
        t = this._createNode(obj, position, js, function (t)
             {
                var p = this._getParent(t),
                    pos = $(t).index();

                if (callback)  {
                   callback.call(this, t);
                }
                if (p.length && p.hasClass(OJ_COLLAPSED))  {
                   this["expand"](p, false, true);
                }
                if (!skip_rename)  { 
                  this._crrm_showInput(t, function (obj, new_name, old_name)
                         { 
                            _this._emitEvent({ "obj"      : obj,
                                               "name"     : new_name,
                                               "parent"   : p,
                                               "position" : pos
                                              });
                         });
                }
                else  {
                   _this._emitEvent({ "obj"      : t, 
                                      "name"     : this["getText"](t),
                                      "parent"   : p,
                                      "position" : pos 
                                    });
                }
             });

        return t;
     },

//                // currently unused
//   /**
//     *  @private
//     */
//   _crrm_check_move : function ()
//   {
//      if (! this.__call_old())  {
//        return false;
//      }
//      //var s = this.options["crrm"]["move"];
//      var s = this._data.defaults.crrm["move"];
//      if (! s["checkMove"].call(this, this._getMove()))  {
//        return false;
//      }
//      return true;
//   },

     /**
       *  @private
       */
     _isHtml : function(s)
     {
        if (!s || s.length < 3) {
          return false ;
        }

        s = s.trim() ;
        return (s.charAt(0) === "<") ;
     },


     /**
       *  Use emptyText option if defined.
       *  @private
       */
     _applyEmptyText : function()
     {
        var txt = this.options.emptyText ;                   // has app set new text

        if (typeof txt !== "string") {
          txt = this._getString(TRANSKEY_NODATA);            // no, get default value.
        }
        if (txt && txt.length > 0)  {

          var $u =  this._$container_ul;
          var $d  = $("<li class='oj-tree-empty'></li>") ;   // no <a>, not interactive 
          $d[0].textContent = txt ;
          $u.empty().append($d);                             //@HTMLUpdateOK
        }
      },

     /**
       *  Return the HTMLElement based on the locator subid property.
       *  @private
       */
     _processSubId : function(locator)
     {
        // Parent node
        // <li role="treeitem" id="blogs" class="oj-tree-node oj-tree-expanded|oj-tree-collapsed">
        //    <ins class="oj-tree-icon oj-tree-disclosure-icon oj-..."> </ins>  <-- disclosure icon
        //    <a tabindex="-1" href="#">
        //       <ins class="oj-tree-icon"> </ins> 
        //       <span class="oj-tree-title">Blogs</span> 
        //       <ul role="group"> 
        //          <li . . . child nodes . . .
        //       </ul>
        //    </a>
        // </li>
        //
        //  Leaf node
        // <li role="treeitem" id="home" myattr1="Hello" class="oj-tree-node oj-tree-leaf">
        //    <ins class="oj-tree-icon"> </ins>                   <-- unused disclosure icon
        //    <a tabindex="-1" href="#">
        //       <ins class="oj-tree-icon oj-tree-node-icon oj-component-icon"> </ins>
        //       <span class="oj-tree-title">Home</span>
        //    </a>
        // </li>

        var subId, node, a, sNode, c, sKey = null, i, ret ;

        if (locator) {
          subId = locator["subId"] ;
        }
        if (! subId) {
          return null ;
        }

        a  = subId.split("[") ;
        if (a.length === 3) {
          a[0] = a[0].trim() ;
          if (a[0] === "oj-tree-node") {
            a[1] = a[1].trim() ;
            c = a[1].charAt(0) ;                  // should be ' or "
            i = a[1].indexOf(c, 1) ;
            if (i >= 0) {
              sNode = a[1].substring(1, i).trim() ;

              a[2] = a[2].trim() ;
              c = a[2].charAt(0) ;                // should be ' or "
              i = a[2].indexOf(c, 1) ;
              if (i >= 0) {
                sKey = a[2].substring(1, i).trim() ;
                try {
                      node = this._getNode(sNode) ;
                    }
                catch (e)
                    {
                      node = null ;
                    }
                if (sKey && node && node !== -1) {
                  switch(sKey)
                  {
                    case "icon" :       if (this._data.themes.icons) {
                                          ret = node.find(" > a > ins:eq(0)") ;
                                        }
                                        break;
                    case "link" :       ret = node.find(" > a:eq(0)") ;
                                        break;
                    case "disclosure" : if (! this._isLeaf(node)) {
                                          ret = node.find(" > ins:eq(0)") ;
                                        }
                                        break;
                    case "title" :      ret = node.find(" > a > span") ;
                                        break;
                  }
                }
              }
            }
          }
        }

        return ret? (ret.length? ret[0] : null) : null ;

     },


     /**
       *  Hide the node connecting lines
       *  @private
       */
     _hideDots   : function ()
     {
       this._data.themes.dots = false;
       this._$container.children("ul").addClass("oj-tree-no-dots");
     },

     /**
       *  Show the node connecting lines
       *  @private
       */
     _showDots   : function ()
     {
       this._data.themes.dots = true;
       this._$container.children("ul").removeClass("oj-tree-no-dots");
     },

     /**
       *  Check the options for initExpanded : ["all"] ;
       *  @private
       */
     _isOptExpandAll : function()
     {
        var s = this.options["initExpanded"] ;
        return  ((s && s == "all") || ($.isArray(s) && (s.length && s[0] == "all"))) ;
     },

     /**
       *   Get translated string and escape it.
       *   @private
       */
     _getString: function(key)
     {
       var oStrings = this._data.core.strings,
           s;

       s = oStrings[key] ;                          // have we already escaped this string?
       if (! s) {                                   // no
         s = this.getTranslatedString(key) ;
         s = this._escapeHtml(s) ;
         oStrings[key] = s ;
       }
       return s;
     },

     /**
       *  Return sanitized html fragment/text for security
       *  @private
       */
     _escapeHtml: function(text) {
     
       var div = document.createElement('div') ;
       $(div).text(text) ;                         // escape the text
       return  div.textContent;
     },

     /**
       *  Get the duration in ms from the css $treeAnimationDuration value. 
       *  (This is currently neccessary because if the duration is zero, the
       *  transition end callback is not called, and the expand event is 
       *  never fired.
       *  @private
       */
     _getAnimDuration : function()
     {
        var ret = 0, val, c, s, $div, span ;

        if (window.getComputedStyle) {
          $div = this._data.ui.animDurDiv ;
          if (! $div) {
            $div  = $("<div id='ojtree-comp-animduration'><span class='oj-tree-transition'>dummy</span></div>") ; 
            this._data.ui.animDurDiv = $div ;
            $("body").append($div) ;   // must be <body>, doesn't work if _$container used and not attached to the DOM //@HTMLUpdateOK
          }
          span = $div[0].childNodes[0] ;
          val = window.getComputedStyle(span);
          if (val["transitionDuration"]) {
            val = val["transitionDuration"] ;
          }
          else if (val["-webkit-transition-duration"]) {   // need this for windows safari
            val = val["-webkit-transition-duration"] ;
          }
          else {
            val = 0 ;
          }

          $div.detach() ;

          if (typeof val == "string" && val.length > 1) {
            c = val.charAt(val.length -1).toLowerCase()
            if (c == "s") {
              val = val.substring(0, val.length-1) ;
              val = parseFloat(val) ;
              if (! isNaN(val)) {
                ret = val * 1000 ;
              }
            }
            else {
               if (val.length > 2) {
                 s = val.substring(val.length - 2).toLowerCase() ;
                 if (s == "ms") {
                   s = val.substring(0, val.length - 2) ;
                   val = parseFloat(val) ;
                   if (! isNaN(val)) {
                     ret = val ;
                   }
                 }
              }
            }
          }
        }
        return ret ;        // return milliseconds
     },


     /**
       *  Return a copy of a member of an object.
       *  @private
       */
     _varCopy :  function(obj, s)
     {
        var o = {} ;
        o[s] = obj[s] ;
        var o2 = $.extend(true, {}, o) ;

        return o2[s] ;
     }


//   /**
//     * For easier debugging on tablet page via vpn.  Emits a _treetrace event
//     * containing the supplied string.
//     * @private
//     */
//
//,  _trace : function(s)
//   {
//     this._emitEvent({ "obj" : {"msg" : s }}, "trace", true);
//   }
//

    // API doc for inherited methods with no JS in this file:

    /**
     * Removes the Tree from the DOM.  If the tree was constructed from original user <ul> markup
     * defined in the Tree's containing <div>, this markup is reinstated.
     * <p>This method does not accept any arguments.
     * 
     * @method
     * @name oj.ojTree#destroy
     * @memberof oj.ojTree
     * @instance
     * 
     * @example <caption>Invoke the <code class="prettyprint">destroy</code> method:</caption>
     * $( ".selector" ).ojTree( "destroy" );
     */

     // Fragments:

    /**
      * <table class="keyboard-table">
      *   <thead>
      *     <tr>
      *       <th>Target</th>
      *       <th>Gesture</th>
      *       <th>Action</th>
      *     </tr>
      *   </thead>
      *   <tbody>
      *     <tr>
      *       <td>Node Disclosure Icon</td>
      *       <td><kbd>Tap</kbd></td>
      *       <td>Toggle the node's expanded state.</td>
      *     </tr>
      *     <tr>
      *       <td>Node Text</td>
      *       <td><kbd>Tap</kbd></td>
      *       <td>Select the node. Toggle the select state if multi-select mode.</td>
      *     </tr>
      *     <tr>
      *       <td>Node Text</td>
      *       <td><kbd>Press and Hold</kbd></td>
      *       <td>Open the context menu.</td>
      *     </tr>
      *   </tbody>
      * </table>
      *
      * @ojfragment touchDoc - Used in touch gesture section of classdesc, and standalone gesture doc
      * @memberof oj.ojTree
      */

     /**
       * 
       * <table class="keyboard-table">
       *   <thead>
       *     <tr>
       *       <th>Key</th>
       *       <th>Use</th>
       *     </tr>
       *   </thead>
       *   <tbody>
       *     <tr>
       *       <td><kbd>Up/down arrow</kbd></td>
       *       <td>Moves between visible nodes.</td>
       *     </tr>
       *     <tr>
       *       <td><kbd>Left-arrow</kbd></td>
       *       <td>On an expanded node, collapses the node.<br>On a collapsed or leaf node, moves focus to the node's parent.</td>
       *     </tr>
       *     <tr>
       *       <td><kbd>Right-arrow</kbd></td>
       *       <td>On a collapsed node, expands the node.<br>On an expanded node, moves to the first first child of the node.<br>On an end node, does nothing.</td>
       *     </tr>
       *     <tr>
       *       <td><kbd>Space bar</kbd></td>
       *       <td>Toggles the selected status of the node.</td>
       *     </tr>
       *     <tr>
       *       <td><kbd>Home</kbd></td>
       *       <td>Moves to the top node of the tree.</td>
       *     </tr>
       *     <tr>
       *       <td><kbd>End</kbd></td>
       *       <td>Moves to the last visible node of the tree.</td>
       *     </tr>
       *     <tr>
       *       <td><kbd>Shift-Up arrow</kbd></td>
       *       <td>Extends selection up one node (assuming multiple selection has been defined).</td>
       *     </tr>
       *     <tr>
       *       <td><kbd>Shift-Down arrow</kbd></td>
       *       <td>Extends selection down one node (assuming multiple selection has been defined).</td>
       *     </tr>
       *     <tr>
       *       <td><kbd>Shift-Home</kbd></td>
       *       <td>Extends selection up to the top-most node.</td>
       *     </tr>
       *     <tr>
       *       <td><kbd>Shift+pgDn</kbd></td>
       *       <td>Extends selection to the last node.</td>
       *     </tr>
       *     <tr>
       *       <td><kbd>Ctrl+Space bar</kbd></td>
       *       <td>Toggles the selection state of the current node (assuming multiple selection has been defined).</td>
       *     </tr>
       *     <tr>
       *       <td><kbd>Shift+Space bar</kbd></td>
       *       <td>Extends selection to the current node (assuming multiple selection has been defined).</td>
       *     </tr>
       *     <tr>
       *       <td><kbd>Shift+F10</kbd></td>
       *       <td>Invoke Context Menu (if defined) on current node.</td>
       *     </tr>
       *     <tr>
       *       <td><kbd>* (asterisk)</kbd></td>
       *       <td>Expands all nodes.</td>
       *     </tr>
       * </tbody></table>
       *
       * @ojfragment keyboardDoc - Used in keyboard section of classdesc, and standalone gesture doc
       * @memberof oj.ojTree
       */

  }) ;    // end    $.registerWidget("oj.ojTree", ...

})();