
/* Below line is for jslint. */
/*extern BOT, FCKeditor, YAHOO, document, window, clearTimeout, alert */

/* Start custom namespace. Need to use everywhere eventuallly. */
BOT = {};



/* BEGIN json.js */

/*
Copyright (c) 2005 JSON.org

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The Software shall be used for Good, not Evil.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

/*
    The global object JSON contains two methods.

    JSON.stringify(value) takes a JavaScript value and produces a JSON text.
    The value must not be cyclical.

    JSON.parse(text) takes a JSON text and produces a JavaScript value. It will
    throw a 'JSONError' exception if there is an error.
*/
var JSON = {
    copyright: '(c)2005 JSON.org',
    license: 'http://www.crockford.com/JSON/license.html',
/*
    Stringify a JavaScript value, producing a JSON text.
*/
    stringify: function (v) {
        var a = [];

/*
    Emit a string.
*/
        function e(s) {
            a[a.length] = s;
        }

/*
    Convert a value.
*/
        function g(x) {
            var c, i, l, v;

            switch (typeof x) {
            case 'object':
                if (x) {
                    if (x instanceof Array) {
                        e('[');
                        l = a.length;
                        for (i = 0; i < x.length; i += 1) {
                            v = x[i];
                            if (typeof v != 'undefined' &&
                                    typeof v != 'function') {
                                if (l < a.length) {
                                    e(',');
                                }
                                g(v);
                            }
                        }
                        e(']');
                        return;
                    } else if (typeof x.valueOf == 'function') {
                        e('{');
                        l = a.length;
                        for (i in x) {
                            v = x[i];
                            if (typeof v != 'undefined' &&
                                    typeof v != 'function' &&
                                    (!v || typeof v != 'object' ||
                                        typeof v.valueOf == 'function')) {
                                if (l < a.length) {
                                    e(',');
                                }
                                g(i);
                                e(':');
                                g(v);
                            }
                        }
                        return e('}');
                    }
                }
                e('null');
                return;
            case 'number':
                e(isFinite(x) ? +x : 'null');
                return;
            case 'string':
                l = x.length;
                e('"');
                for (i = 0; i < l; i += 1) {
                    c = x.charAt(i);
                    if (c >= ' ') {
                        if (c == '\\' || c == '"') {
                            e('\\');
                        }
                        e(c);
                    } else {
                        switch (c) {
                        case '\b':
                            e('\\b');
                            break;
                        case '\f':
                            e('\\f');
                            break;
                        case '\n':
                            e('\\n');
                            break;
                        case '\r':
                            e('\\r');
                            break;
                        case '\t':
                            e('\\t');
                            break;
                        default:
                            c = c.charCodeAt();
                            e('\\u00' + Math.floor(c / 16).toString(16) +
                                (c % 16).toString(16));
                        }
                    }
                }
                e('"');
                return;
            case 'boolean':
                e(String(x));
                return;
            default:
                e('null');
                return;
            }
        }
        g(v);
        return a.join('');
    },
/*
    Parse a JSON text, producing a JavaScript value.
*/
    parse: function (text) {
        return (/^(\s+|[,:{}\[\]]|"(\\["\\\/bfnrtu]|[^\x00-\x1f"\\]+)*"|-?\d+(\.\d*)?([eE][+-]?\d+)?|true|false|null)+$/.test(text)) &&
            eval('(' + text + ')');
    }
};
/* END json.js */



/* BEGIN dragdrop support */

/* Below code shamelessly grabbed from:  http://developer.yahoo.com/yui/examples/dragdrop/dd-reorder.html
   Only modified a little bit. To make it easy to figure out changes, I put original version in my todo on date 2007-9-29. */

(function() {

var Dom = YAHOO.util.Dom;
var Event = YAHOO.util.Event;
var DDM = YAHOO.util.DragDropMgr;

//////////////////////////////////////////////////////////////////////////////
// example app
//////////////////////////////////////////////////////////////////////////////
YAHOO.example.DDApp = {
    init: function() {

        function doList(id) {
            new YAHOO.util.DDTarget(id);
            var ul = document.getElementById(id);
            var items = ul.getElementsByTagName('li');
            for (j=0; j<items.length; j=j+1) {
                new YAHOO.example.DDList(items[j].id);
            }
        }
            
        doList('selected_list');
        doList('hidden_list');
    }
};

//////////////////////////////////////////////////////////////////////////////
// custom drag and drop implementation
//////////////////////////////////////////////////////////////////////////////

YAHOO.example.DDList = function(id, sGroup, config) {

    YAHOO.example.DDList.superclass.constructor.call(this, id, sGroup, config);

    this.logger = this.logger || YAHOO;
    var el = this.getDragEl();
    Dom.setStyle(el, "opacity", 0.67); // The proxy is slightly transparent

    this.goingUp = false;
    this.lastY = 0;
};

YAHOO.extend(YAHOO.example.DDList, YAHOO.util.DDProxy, {

    startDrag: function(x, y) {
        this.logger.log(this.id + " startDrag");

        // make the proxy look like the source element
        var dragEl = this.getDragEl();
        var clickEl = this.getEl();
        Dom.setStyle(clickEl, "visibility", "hidden");

        dragEl.innerHTML = clickEl.innerHTML;

        Dom.setStyle(dragEl, "color", Dom.getStyle(clickEl, "color"));
        Dom.setStyle(dragEl, "backgroundColor", Dom.getStyle(clickEl, "backgroundColor"));
        Dom.setStyle(dragEl, "border", "2px solid gray");
    },

    endDrag: function(e) {

        var srcEl = this.getEl();
        var proxy = this.getDragEl();

        // Show the proxy element and animate it to the src element's location
        Dom.setStyle(proxy, "visibility", "");
        var a = new YAHOO.util.Motion( 
            proxy, { 
                points: { 
                    to: Dom.getXY(srcEl)
                }
            }, 
            0.2, 
            YAHOO.util.Easing.easeOut 
        )
        var proxyid = proxy.id;
        var thisid = this.id;

        // Hide the proxy and show the source element when finished with the animation
        a.onComplete.subscribe(function() {
                Dom.setStyle(proxyid, "visibility", "hidden");
                Dom.setStyle(thisid, "visibility", "");
            });
        a.animate();
    },

    onDragDrop: function(e, id) {

        // If there is one drop interaction, the li was dropped either on the list,
        // or it was dropped on the current location of the source element.
        if (DDM.interactionInfo.drop.length === 1) {

            // The position of the cursor at the time of the drop (YAHOO.util.Point)
            var pt = DDM.interactionInfo.point; 

            // The region occupied by the source element at the time of the drop
            var region = DDM.interactionInfo.sourceRegion; 

            // Check to see if we are over the source element's location.  We will
            // append to the bottom of the list once we are sure it was a drop in
            // the negative space (the area of the list without any list items)
            if (!region.intersect(pt)) {
                var destEl = Dom.get(id);
                var destDD = DDM.getDDById(id);
                destEl.appendChild(this.getEl());
                destDD.isEmpty = false;
                DDM.refreshCache();
            }

        }
    },

    onDrag: function(e) {

        // Keep track of the direction of the drag for use during onDragOver
        var y = Event.getPageY(e);

        if (y < this.lastY) {
            this.goingUp = true;
        } else if (y > this.lastY) {
            this.goingUp = false;
        }

        this.lastY = y;
    },

    onDragOver: function(e, id) {
    
        var srcEl = this.getEl();
        var destEl = Dom.get(id);

        // We are only concerned with list items, we ignore the dragover
        // notifications for the list.
        if (destEl.nodeName.toLowerCase() == "li") {
            var orig_p = srcEl.parentNode;
            var p = destEl.parentNode;

            if (this.goingUp) {
                p.insertBefore(srcEl, destEl); // insert above
            } else {
                p.insertBefore(srcEl, destEl.nextSibling); // insert below
            }

            DDM.refreshCache();
        }
    }
});

})();

/* END dragdrop support */




/* BEGIN Debugging functions. */
function showFields(obj) {
    var rval = [];
    var e;
    for (var prop in obj) {
        var v;
        try {
            v = obj[prop];
        } catch (e) {
            continue;
        }
        rval.push([prop, v]);
    }
    alert('showFields: ' + rval);
}
/* END debugging functions. */


BOT.urlEncode = function(unencoded) {
    return encodeURIComponent(unencoded).replace(/\'/g, '%27');
};

BOT.queryString = function(names) {
    var v;
    var o = names;
    names = [];
    var values = [];
    for (var k in o) {
        v = o[k];
        names.push(k);
        values.push(v);
    }

    var rval = [];
    var len = Math.min(names.length, values.length);
    for (var i = 0; i < len; i++) {
        v = values[i];
        if (typeof(v) != 'undefined' && v != null) {
            rval.push(BOT.urlEncode(names[i]) + "=" + BOT.urlEncode(v));
        }
    }
    return rval.join("&");
};


/* appendUrlArgs isn't used as many places as it could be. */
BOT.appendUrlArgs = function(url, args) {
    if (url.indexOf('?') == -1) {
        return url + '?' + args;
    } else {
        return url + '&' + args;
    }
};

function strip(x) {
    /* 2006-5-10: textarea needs empty cr's stripped. */
    while (x.substring(0,1) == ' ' || x.substring(0,1) == '\n') {
        x = x.substring(1);
    }
    while (x.substring(x.length-1,x.length) == ' ' || x.substring(x.length-1,x.length) == '\n') {
        x = x.substring(0,x.length-1);
    }
    return x;
}


function stripInputFields() {
    var inputs = document.getElementsByTagName('input');
    for (var index = 0; index < inputs.length; index++) {
        var input = inputs[index];
        if (input.type != 'file') {  // It is illegal in firefox to strip the string of a file upload input.
            input.value = strip(input.value);
        }
    }

    inputs = document.getElementsByTagName('textarea');  //2006-5-10: how did I forget this?
    for (var index = 0; index < inputs.length; index++) {
        var input = inputs[index];
        input.value = strip(input.value);
    }
}


function showFieldErrorInternal(field, alertMessage) {
    var tab = document.getElementById('tab-First_Page');
    if (tab) {
        tab.onclick();  // Make sure first page is visible. Note we're calling our onclick, not generic click which is avail. in IE but not Mozilla.
    }
    alert(alertMessage);
    field.focus();
}


function showFieldError(form, fieldId, alertMessage) {
    var field = document.getElementById(fieldId);
    showFieldErrorInternal(field, alertMessage);
}


function validateEmail(str) {

		var at="@";
		var dot=".";
		var lat=str.indexOf(at);
		var lstr=str.length;
		var ldot=str.indexOf(dot);

        // Error if no @.
		if (str.indexOf(at)==-1){ return false; }

        // Error if @ at beginning or end.
		if (str.indexOf(at)==-1 || str.indexOf(at)==0 || str.indexOf(at)==(lstr-1)){ return false; }

        // Error if no period or period at beginning or end.
		if (str.indexOf(dot)==-1 || str.indexOf(dot)==0 || str.indexOf(dot)==(lstr-1)){ return false; }

        // Error if there are 2 @.
		if (str.indexOf(at,(lat+1))!=-1){ return false; }

        // Error if character immediately before or after @ is period.
		if (str.substring(lat-1,lat)==dot || str.substring(lat+1,lat+2)==dot){ return false; }

        // Error if no period after @.
		if (str.indexOf(dot,(lat+2))==-1){ return false; }
		
        // Error if there is a space.
		if (str.indexOf(" ")!=-1){ return false; }

        // Error if there is a %.  2006-9-19 added b/c of our conversion to jabber ids.
		if (str.indexOf("%")!=-1){ return false; }

 		return true;
}


/* Check that a required field is present. */
function validateField(fieldId, alertMessage) {
    var field = document.getElementById(fieldId);
    if (field && field.value == "") {  /* 2006-2-27: We could call strip here, but I'll start stripping all input fields before validation. */
        showFieldErrorInternal(field, alertMessage);
        return false;
    } else {
        return true;
    }
}


function validateEmailField(fieldId, alertMessage) {
    var field = document.getElementById(fieldId);
    if (field.value == "" || ! validateEmail(field.value)) {
        showFieldErrorInternal(field, alertMessage);
        return false;
    } else {
        return true;
    }
}


function isValidCreditCardNumber(cardNumber, cardType) {
  var isValid = false;
  var ccCheckRegExp = /[^\d -]/;
  isValid = !ccCheckRegExp.test(cardNumber);

  if (isValid)
  {
    var cardNumbersOnly = cardNumber.replace(/[ -]/g,"");
    var cardNumberLength = cardNumbersOnly.length;
    var lengthIsValid = false;
    var prefixRegExp;

    switch(cardType)
    {
      case "MasterCard":
        lengthIsValid = (cardNumberLength == 16);
        prefixRegExp = /^5[1-5]/;
        break;

      case "Visa":
        lengthIsValid = (cardNumberLength == 16 || cardNumberLength == 13);
        prefixRegExp = /^4/;
        break;

      case "Amex":
        lengthIsValid = (cardNumberLength == 15);
        prefixRegExp = /^3(4|7)/;
        break;

      case "Discover":
        lengthIsValid = (cardNumberLength == 16);
        prefixRegExp = /^60/;
        break;

      default:
        prefixRegExp = /^$/;
        alert("Internal error: Card type not found");
    }

    var prefixIsValid = prefixRegExp.test(cardNumbersOnly);
    isValid = prefixIsValid && lengthIsValid;
  }

  if (isValid)
  {
    var numberProduct;
    var checkSumTotal = 0;

    for (var digitCounter = cardNumberLength - 1; 
      digitCounter >= 0; 
      digitCounter--)
    {
      checkSumTotal += parseInt (cardNumbersOnly.charAt(digitCounter));
      digitCounter--;
      numberProduct = String((cardNumbersOnly.charAt(digitCounter) * 2));
      for (var productDigitCounter = 0;
        productDigitCounter < numberProduct.length; 
        productDigitCounter++)
      {
        checkSumTotal += parseInt(numberProduct.charAt(productDigitCounter));
      }
    }

    isValid = (checkSumTotal % 10 == 0);
  }

  return isValid;
}


function validateCreditCardField(fieldId, paymentTypeId, alertMessage) {
    var field = document.getElementById(fieldId);
    var paymentTypeField = document.getElementById(paymentTypeId);
    if (field.value == "" || ! isValidCreditCardNumber(field.value, paymentTypeField.value)) {
        showFieldErrorInternal(field, alertMessage);
        return false;
    } else {
        return true;
    }
}


function validateEntityForm(form) {
    stripInputFields();
    return validateField('name', 'You must enter a name.');
}


function findSelectedRadioButton(groupname)
{
	var radioButtons = document.forms[0].elements[groupname];
    if (radioButtons.length) {  // if only one radio button then this is not defined
        for( var i = 0; i < radioButtons.length; i++ ) {
            if(radioButtons[i].checked) {
                return radioButtons[i].value;
            }
        }
    } else {
        if(radioButtons.checked) {
            return radioButtons.value;
        }
    }
	return null;
}


/** Return value of selected option in a select list. */
function getValueOfSelectionList(list) {
    var option = list.options[list.selectedIndex];
    return option.value;
}


function focusOnFirstInput() {
    if (document.forms.length == 0) { return; }  // In case there's no forms defined on the page.
    for(var i = 0; i < document.forms[0].length; i++)
    {
      var current = document.forms[0][i];
      if (current.type != "hidden" && current.nodeName != 'FIELDSET')
      {
        if (current.disabled != true)
        {
            try {
                current.focus();
            } catch(er) {
                // IE will throw an exception if object is not currently displayed -- oh well.
                //alert("Caught error: " + er);
            }
            return;
        }
      }
    }
}


function clearList(list) {
    while( list.hasChildNodes() ) {
        list.remove( 0 );
    }
}

function deleteLaterSiblings(parent, child) {
    var sibling = child.nextSibling;
    while (sibling) {
        var toDelete = sibling;
        sibling = sibling.nextSibling;
        parent.removeChild(toDelete);
    }
}

function removeChildren(node) {
    var count = node.childNodes.length;
    while(node.hasChildNodes()){ node.removeChild(node.firstChild); }
    return count;
}

function replaceChildren(node, newChild) {
    removeChildren(node);
    node.appendChild(newChild);
}


function toggleShowHide(link,divId,picId) {
    //alert(link.text);
    //link.text = 'abc'; no that doesn't work
    var div = document.getElementById(divId);
    var pic = document.getElementById(picId);
    switch(div.style.display)
    {
        case "none":
            div.style.display = 'block';
            pic.src = '/custom_images/arrowUp.gif';
            link.innerHTML = 'Hide' + link.text.substring(4);
            break;
        default:
            div.style.display = 'none';
            link.innerHTML = 'Show' + link.text.substring(4);
            pic.src = '/custom_images/arrowDown.gif';
            break;
    }
}

function toggleShowHideOfH2(link, divId, picId) {
    var div = document.getElementById(divId);
    var pic = document.getElementById(picId);
    switch(div.style.display)
    {
        case "none":
            div.style.display = 'block';
            pic.src = '/custom_images/opentriangle.gif';
            //link.innerHTML = 'Hide' + link.text.substring(4);
            break;
        default:
            div.style.display = 'none';
            //link.innerHTML = 'Show' + link.text.substring(4);
            pic.src = '/custom_images/closetriangle.gif';
            break;
    }
    return false;
}

function showDiv(divId) {
    var div = document.getElementById(divId);
    div.style.display = 'block';
}

function hideDiv(divId) {
    var div = document.getElementById(divId);
    div.style.display = 'none';
}

function showSpan(spanId) {
    var span = document.getElementById(spanId);
    span.style.display = '';
}

function hideSpan(spanId) {
    var span = document.getElementById(spanId);
    span.style.display = 'none';
}

function hideULChildren(divId) {
    var div = document.getElementById(divId);
    var uls = div.getElementsByTagName('ul');
    for (var index = 0; index < uls.length; index++) {
        var ul = uls[index];
        ul.style.display = 'none';
    }
}

function selectInBrowser(selectedListElement, valueContainerDiv, selectedDiv) {

    /* Turn off all. */
    var lis = selectedListElement.parentNode.getElementsByTagName('li');
    for (var index = 0; index < lis.length; index++) {
        var li = lis[index];
        BOT.swapElementClass(li, 'on', 'off');
    }

    BOT.swapElementClass(selectedListElement, 'off', 'on');
    hideULChildren(valueContainerDiv);
    showDiv(selectedDiv);
}

/* Useful for showing row which contains a generated form element. */
function showGrandparentDiv(divId) {
    var div = document.getElementById(divId);
    div.parentNode.parentNode.style.display = '';  //using 'inline' worked in ie but not firefox - go figure!
}

function hideGrandparentDiv(divId) {
    var div = document.getElementById(divId);
    div.parentNode.parentNode.style.display = 'none';
}

function enableButton(id) {
    var item = document.getElementById(id);
    item.disabled = false;
}


// How cool.  Instead of customizing fckeditor's config file, we just customize fckeditor right here.
function installRichEditor(nameOfTextarea, height) {
    var textarea = document.forms[0][nameOfTextarea];
    if (textarea.tagName != 'TEXTAREA') {
        return;  // must be something else
    }
    BOT.oFCKeditor = new FCKeditor(nameOfTextarea) ; //name, not id (used to be opposite!)
    BOT.oFCKeditor.Config['LinkUpload'] = false;
    BOT.oFCKeditor.Config['LinkDlgHideAdvanced'] = true;
    BOT.oFCKeditor.Config['LinkBrowser'] = false;
    BOT.oFCKeditor.Height = height;  //"400";
    BOT.oFCKeditor.Width = "450";  // needed b/c default of 100% doesn't work in ie
    //BOT.oFCKeditor.Width = "99%";
    BOT.oFCKeditor.ToolbarSet = 'Basic';
    BOT.oFCKeditor.Config["CustomConfigurationsPath"] = "/custom_fckconfig.js";
    BOT.oFCKeditor.ReplaceTextarea();
}


/* Had to write below function because built-in method Array.indexOf() wasn't working in Mozilla or IE. */
function isItemInArray(arr, item) {
    for (var index = 0; index < arr.length; index++ ) {
        var curr = arr[index];
        if (curr == item) {
            return true;
        }
    }
    return false;
}

function highlightTags(parent /*, tagIdToTagMapping, rowToTagMapping*/) {

    // Make list of selected tags.
    var selectedTags = [];
    var inputs = parent.getElementsByTagName('input');
    for (var index = 0; index < inputs.length; index++) {
        var input = inputs[index];
        if( input.checked ) {
            selectedTags.push(tagIdToTagMapping[input.id]);
        }
    }
    //alert('debug.selected:' + selectedTags);

    function rowIsSelected(x, y) {
        /* Is anything from x in y? */
        for (var x_index = 0; x_index < x.length; x_index++) {
            var x_item = x[x_index];
            for (var y_index = 0; y_index < y.length; y_index++) {
                var y_item = y[y_index];
                if (y_item == x_item) {
                    //alert('debug.rowIsSelected x_item in y:' + x_item + '--' + y);
                    return true;
                }
            }
        }
        return false;
    }

    // Add style class to all rows which have a selected tag.
    for (var index = 0; index < rowToTagMapping.length; index++) {
        var entry = rowToTagMapping[index];
        var rowId = entry[0];
        var tagList = entry[1];
        //alert('debug: ' + tagList + ',' + selectedTags);
        if (rowIsSelected(tagList, selectedTags)) {
            BOT.addElementClass(rowId, 'tagHighlight');
        } else {
            BOT.removeElementClass(rowId, 'tagHighlight');
        }
    }
}



/* json support */
function createSubmitArgs() {
    var out = '';
    for (var index = 0; index < arguments.length; index++) {
        if (out != '') {
            out += '&';
        }
        var name = arguments[index];
        out += name + '=' + encodeURIComponent(document.getElementById(name).value);
    }
    //alert('submitting: ' + out);
    return out;
}


BOT.handleAsyncYUIFailure = function(o) {
    alert("Submission failed: " + o.statusText);
}; 

function myevalJSON(txt) {
    return eval('(' + txt + ')');
}



// START dropdownmenu from link support, originally came from:  http://www.dynamicdrive.com/dynamicindex1/anylinkcss.htm

var disappeardelay=250;  //menu disappear speed onMouseout (in miliseconds)
var enableanchorlink=1; //Enable or disable the anchor link when clicked on? (1=e, 0=d)
var hidemenu_onclick=1; //hide menu when user clicks within menu? (1=yes, 0=no)

/////No further editting needed

var ie5=document.all;
var ns6=document.getElementById&&!document.all;

function getposOffset(what, offsettype){
    var totaloffset=(offsettype=="left")? what.offsetLeft : what.offsetTop;
    var parentEl=what.offsetParent;
    while (parentEl!=null){
        totaloffset=(offsettype=="left")? totaloffset+parentEl.offsetLeft : totaloffset+parentEl.offsetTop;
        parentEl=parentEl.offsetParent;
    }
    return totaloffset;
}

function showhide(obj, e, visible, hidden){
    if (ie5||ns6) {
        dropmenuobj.style.left=dropmenuobj.style.top=-500;
    }
    if (e.type=="click" && obj.visibility==hidden || e.type=="mouseover") {
        obj.visibility=visible;
    }
    else if (e.type=="click") {
        obj.visibility=hidden;
    }
}

function iecompattest(){
    return (document.compatMode && document.compatMode!="BackCompat")? document.documentElement : document.body;
}

function clearbrowseredge(obj, whichedge) {
    var edgeoffset=0;
    if (whichedge=="rightedge") {
        var windowedge=ie5 && !window.opera? iecompattest().scrollLeft+iecompattest().clientWidth-15 : window.pageXOffset+window.innerWidth-15;
        dropmenuobj.contentmeasure=dropmenuobj.offsetWidth;
        if (windowedge-dropmenuobj.x < dropmenuobj.contentmeasure) {
            edgeoffset=dropmenuobj.contentmeasure-obj.offsetWidth;
        }
    }
    else{
        var topedge=ie5 && !window.opera? iecompattest().scrollTop : window.pageYOffset;
        var windowedge=ie5 && !window.opera? iecompattest().scrollTop+iecompattest().clientHeight-15 : window.pageYOffset+window.innerHeight-18;
        dropmenuobj.contentmeasure=dropmenuobj.offsetHeight;
        if (windowedge-dropmenuobj.y < dropmenuobj.contentmeasure) { //move up?
            edgeoffset=dropmenuobj.contentmeasure+obj.offsetHeight;
            if ((dropmenuobj.y-topedge)<dropmenuobj.contentmeasure) { //up no good either?
                edgeoffset=dropmenuobj.y+obj.offsetHeight-topedge;
            }
        }
    }
    return edgeoffset
}

function dropdownmenu(obj, e, dropmenuID){
    if (window.event) { event.cancelBubble=true; }
    else if (e.stopPropagation) { e.stopPropagation(); }
    if (typeof dropmenuobj!="undefined") { //hide previous menu
        dropmenuobj.style.visibility="hidden";
    }
    clearhidemenu();
    if (ie5||ns6) {
        obj.onmouseout=delayhidemenu;
        dropmenuobj=document.getElementById(dropmenuID);
        if (hidemenu_onclick && dropmenuID == 'linkDropdownMenu') {
            dropmenuobj.onclick=function(){dropmenuobj.style.visibility='hidden'};
        }
        dropmenuobj.onmouseover=clearhidemenu;
        dropmenuobj.onmouseout=ie5? function(){ dynamichide(event)} : function(event){ dynamichide(event)};
        showhide(dropmenuobj.style, e, "visible", "hidden");
        dropmenuobj.x=getposOffset(obj, "left");
        dropmenuobj.y=getposOffset(obj, "top");
        dropmenuobj.style.left=dropmenuobj.x-clearbrowseredge(obj, "rightedge")+"px";
        dropmenuobj.style.top=dropmenuobj.y-clearbrowseredge(obj, "bottomedge")+obj.offsetHeight+"px";
    }
    return clickreturnvalue();
}

function clickreturnvalue(){
    if ((ie5||ns6) && !enableanchorlink) { return false; }
    else { return true };
}

function contains_ns6(a, b) {
    while (b.parentNode) {
        if ((b = b.parentNode) == a) {
            return true;
        }
    }
    return false;
}

function dynamichide(e){
    if (ie5&&!dropmenuobj.contains(e.toElement)) {
        delayhidemenu();
    }
    else if (ns6&&e.currentTarget!= e.relatedTarget&& !contains_ns6(e.currentTarget, e.relatedTarget)) {
        delayhidemenu();
    }
}

function delayhidemenu(){
    delayhide=setTimeout("dropmenuobj.style.visibility='hidden'",disappeardelay);
}

function clearhidemenu(){
    if (typeof delayhide!="undefined") {
        clearTimeout(delayhide);
    }
}
// END dropdownmenu support

function toggleShowOutlineAudit(checkbox) {
    var url = window.top.location.href;
    var questionPos = url.indexOf('?');
    if (questionPos >= 0) {
        // Remove current parameter.
        url = url.substring(0, questionPos);
    }
    if (checkbox.checked) {
        // Add query parameter.
        url += '?showOutlineAudit=T';
    }
    window.top.location.href = url;
}

function showFieldset(link, fieldsetId) {
    var fieldset = document.getElementById(fieldsetId);
    BOT.swapElementClass(fieldset, 'collapsed', 'displayed');
}

function hideFieldset(link, fieldsetId) {
    var fieldset = document.getElementById(fieldsetId);
    BOT.swapElementClass(fieldset, 'displayed', 'collapsed');
}

var crvar = '\n';  // Created so that we can avoid escaping issues when rendering from python.

  
function setDefaultTimezone() {
    var timezone = document.getElementById('timezone');
    if (timezone.value == "") {
        // Create choice in format:  (GMT+10:00) ...
        var d = new Date();
        var minDelta = - d.getTimezoneOffset();  // negative b/c js is reversed!
        //minDelta = 210; // TEST TEST TEST
        var hourDelta = Math.floor(Math.abs(minDelta) / 60);
        var lookFor = '(GMT';
        if (parseInt(minDelta) < 0) {
            lookFor += '-';
        } else {
            lookFor += '+';
        }
        if (hourDelta < 10) {
            lookFor += '0';
        }
        lookFor += hourDelta;
        lookFor += ':';
        var mins = Math.abs(minDelta) - Math.abs(hourDelta * 60);
        if (mins < 10) {
            lookFor += '0';
        }
        lookFor += mins;
        lookFor += ')';
        
        // Find first choice that matches.
        for (var i = 0; i < timezone.length; i++) {
            if (timezone.options[i].text.indexOf(lookFor) >= 0) {
                timezone.options[i].selected = true;
                return; // Only select the first one.
            }
        }
    }
}

function insertAsFirstChild(parent, newElement) {
    parent.insertBefore(newElement, parent.firstChild);
}

function searchEntity(form, url) {
    try {
        stripInputFields();
        if (!validateField('search_text', 'You must enter a search term.')) return false;

        var cb = function(out) {
        
            var col = document.getElementById('portal-column-content');
            var div = col.firstChild;
            var searchEntityResultsDiv = document.getElementById('searchEntityResultsDiv');
            if (! searchEntityResultsDiv) {
                searchEntityResultsDiv = document.createElement('div');
                searchEntityResultsDiv.setAttribute('id', 'searchEntityResultsDiv');
                insertAsFirstChild(div, searchEntityResultsDiv);
            }
            searchEntityResultsDiv.innerHTML = out;
        };
        BOT.loadJSONDoc(url + '?search_text=' + encodeURIComponent(form.search_text.value), cb);
    } catch(er) {
        alert("Caught unexpected error: " + er);
    }
    return false;
}

BOT.getTimestamp = function() {
    return new Date().getTime();
}

// Below is workaround for ie since you can't do innerHTML on parts of tables.
BOT.replaceRowContents = function(newContent, targetRow) {
    var hiddenDiv = document.createElement("div");
    hiddenDiv.innerHTML = "<table><tbody><tr>" + newContent + "</tr></tbody></table>";
    var tableElement = hiddenDiv.firstChild;
    var rowElement = tableElement.firstChild.firstChild;
    
    // Now that we have a dom structure, let's have it replace any existing children.
    removeChildren(targetRow);
    while(rowElement.hasChildNodes()){
        var child = rowElement.firstChild;
        targetRow.appendChild(child);
    }
}

// Assume a td is sent, which we will ignore.
BOT.replaceTableCellContents = function(newContent, targetElement) {
    var hiddenDiv = document.createElement("div");
    hiddenDiv.innerHTML = "<table><tbody><tr>" + newContent + "</tr></tbody></table>";
    var tableElement = hiddenDiv.firstChild;
    var rootElementToCopy = tableElement.firstChild.firstChild.firstChild;
    //alert('newContent:' + newContent);
    //alert('tableElement:' + tableElement.innerHTML);
    //alert('going to copy:' + rootElementToCopy.innerHTML);
    
    // Now that we have a dom structure, let's have it replace any existing children.
    removeChildren(targetElement);
    while(rootElementToCopy.hasChildNodes()){
        var child = rootElementToCopy.firstChild;
        targetElement.appendChild(child);
    }
}

BOT.getSelectedId = function() {
    var row = BOT.getFirstElementByTagAndClassName('tr', 'on');
    if (row == null) {
        //Sorry we can't complain if nothing is highlighted because they might be typing in the search box.
        //alert('Please put your mouse over the desired row and then redo your operation.');
        var currentEntityId = document.getElementById('currentEntityId');
        if (currentEntityId) {
            return currentEntityId.value;
        } else {
            throw 'no row selected';
        }
    } else {
        return row.id.split('-')[1];
    }
}

BOT.gotoNextLine = function() {
    var row = BOT.getFirstElementByTagAndClassName('tr', 'on');
    if (row) {
        var target = row.nextSibling;
        if (target) {
            BOT.swapElementClass(row, 'on', 'off');
            BOT.swapElementClass(target, 'off', 'on');
        }
    } else {
        // Nothing selected, let's try moving to first row.
        var row = BOT.getFirstElementByTagAndClassName('tr', 'tableRow');
        if (row) {
            BOT.swapElementClass(row, 'off', 'on');
        }
    }
}

BOT.gotoPrevLine = function() {
    var row = BOT.getFirstElementByTagAndClassName('tr', 'on');
    if (row) {
        var target = row.previousSibling;
        if (target && YAHOO.util.Dom.hasClass(target, 'tableRow')) {
            BOT.swapElementClass(row, 'on', 'off');
            BOT.swapElementClass(target, 'off', 'on');
        }
    } else {
        // Nothing selected, let's try moving to last row.
        var row = BOT.getLastElementByTagAndClassName('tr', 'tableRow');
        if (row) {
            BOT.swapElementClass(row, 'off', 'on');
        }
    }
}

BOT.gotoLink = function(link) {
    if (link) {
        BOT.loadPartialPage(link.href, null, null);
    }
}

BOT.getCharFromEvent = function(event) {
    var number = YAHOO.util.Event.getCharCode(event);  // Returns upper case letters! Fix it below.
    if (!event.shiftKey) {
        if (number >= 65 && number <= 90) { // 65: 'A', 90: 'Z'
            // If not shift, and it's a letter, then make it lower case.
            number = number + 32;
        }
    }
    return String.fromCharCode(number);
}

BOT.entity_list_keypress = function(event) {
    try {
        if (event.ctrlKey || event.altKey || event.metaKey) {
            return;
        }

        if (BOT.isYUIDialogUp()) { return; }
        
        var target = YAHOO.util.Event.getTarget(event);
        if (target.tagName == 'INPUT' || target.tagName == 'TEXTAREA') { return; } // otherwise typing 'n' in search form messes up
        
        var rootUrl = document.getElementById('rootUrl').value;
        var keycode = BOT.getCharFromEvent(event);

        /* NOTE: shortcuts below must be kept in sync with:
                 1. short list of docs in the NewPopupPage.
                 2. complete list at:  http://localhost:8082/help/shortcuts.html
        */
        switch (keycode) {
            case 'd': case 'D': /* duplicate */
                window.location = rootUrl + '/' + BOT.getSelectedId() + '/duplicate';
                break;
            case 'e': case 'E': /* edit */
                BOT.showYUIDialog(rootUrl + '/' + BOT.getSelectedId() + '/ajax_edit', {});
                break;
            case 'h':  /* hoist */
                window.location = rootUrl + '/outline/' + BOT.getSelectedId();
                break;
            case 'H':  /* un-hoist */
                window.location = rootUrl + '/outline';
                break;
            case 'v': case 'V': /* view */
                BOT.showYUIDialog(rootUrl + '/' + BOT.getSelectedId() + '/ajax_view', {});
                break;
            case 'c': /* add child */
                BOT.ajax_add(false, 'child', {id: BOT.getSelectedId()});
                break;
            case 'C': /* add child w/template */
                BOT.ajax_add(true, 'child', {id: BOT.getSelectedId()});
                break;
            case 's': /* add sibling */
                BOT.ajax_add(false, 'sibling', {id: BOT.getSelectedId()});
                break;
            case 'S': /* add sibling w/template */
                BOT.ajax_add(true, 'sibling', {id: BOT.getSelectedId()});
                break;
            case 'n': /* add new */
                BOT.ajax_add(false, 'top', {});
                break;
            case 'N': /* add new w/template */
                BOT.ajax_add(true, 'top', {});
                break;
            case 't': case 'T': /* add tag */
                BOT.showYUIDialog(rootUrl + '/' + BOT.getSelectedId() + '/tagit_popup', {});
                break;
            case 'l': case 'L': /* add link */
                BOT.showYUIDialog(rootUrl + '/' + BOT.getSelectedId() + '/add_link_popup', {});
                break;
            case 'j': /* go to next line */
                BOT.gotoNextLine();
                break;
            case 'k': /* go to previous line */
                BOT.gotoPrevLine();
                break;
            case 'J': /* go to next page */
                BOT.gotoLink(document.getElementById('next_link'));
                break;
            case 'K': /* go to previous page */
                BOT.gotoLink(document.getElementById('prev_link'));
                break;
            default:
                return;
        }
    } catch (er) {
        if (er == 'no row selected') {
            /* gobble exception */
            //alert('skipping known exception');
        } else {
            alert('problem:' + er);
        }
    }
}



BOT.global_keypress_cb = function(event) {
    if (event.ctrlKey || event.altKey || event.metaKey) {
        return;
    }
    
    // Handle escape specially so that it works even if a popup is up.
    var number = YAHOO.util.Event.getCharCode(event);
    if (BOT.isYUIDialogUp()) {
        if (number == 27) {  // This is escape.
            cleanupFn();
            return;
        }
    }

    // Jump out if event happened on a form element - otherwise typing in search form messes up.
    var target = YAHOO.util.Event.getTarget(event);
    if (target.tagName == 'INPUT' || target.tagName == 'TEXTAREA') { return; }
    
    if (BOT.isYUIDialogUp()) {
        BOT.runListenersForDialog(event);
        return;
    }

    // G/g means goto.
    if ( (number == 71) || (number == 103) ) { // 'G'.charCodeAt(0) = 86, 'g' is 118
        BOT.showYUIDialog(document.getElementById('root_url_hidden').value + '/admin/goto_popup', {width: 300});
    }
    
    // If context is viewing entity list then run them.
    var currentEntityContext = document.getElementById('currentEntityContext');
    if (currentEntityContext) {
        currentEntityContext = currentEntityContext.value;
        if (currentEntityContext == 'list') {
            BOT.entity_list_keypress(event);
        } else if (currentEntityContext == 'item') {
            BOT.entity_list_keypress(event);
        } else {
            alert('Did not recognize context: ' + currentEntityContext);
        }
    }
    
}

function closeSearchResults() {
    var searchEntityResultsDiv = document.getElementById('searchEntityResultsDiv');
    var parent = searchEntityResultsDiv.parentNode;
    parent.removeChild(searchEntityResultsDiv);
}

BOT.nocache_regexp = /nocache=\d+/;

BOT.asyncRequest = function(method, url, callback) {
    // resetFormState needed below if you do an ajax POST, and then you GET a url for partial page refresh
    //   -- otherwise yui framework appends previous form's values into GET
    YAHOO.util.Connect.resetFormState();
   
    // To avoid any nasty ie caching, we will append timestamp on every request. So far only needed
    // in graph editing, but let's just do this to all url's just in case.
    // But make sure there's only one nocache argument, we don't want them to accumulate.
    var extraArg = 'nocache=' + BOT.getTimestamp();
    if (BOT.nocache_regexp.test(url)) {
        url = url.replace(BOT.nocache_regexp, extraArg)
    } else {
        if (url.indexOf('?') == -1) {
            url = url + '?' + extraArg;
        } else {
            url = url + '&' + extraArg;
        }
    }
    YAHOO.util.Connect.asyncRequest(method, url, callback);
}

BOT.loadPartialPage = function(url, optionalMoreFun, message) {
    var pos = url.indexOf('?');
    var newUrlSegment = '/partial';
    if (pos == -1) {
        url = url + newUrlSegment;
    } else {
        url = url.substr(0, pos) + newUrlSegment + url.substr(pos);
    }
    var cb = function(out) {
        // Store raw content under temporary hidden div.
        // But still need to have unique ids, else browsers act flaky.
        var tempDiv = document.createElement('div');
        tempDiv.innerHTML = out.responseText;
        var divChildren = YAHOO.util.Dom.getChildren(tempDiv);
    
        // Replace page_of_data with first div.
        var page_of_data = document.getElementById('page_of_data');
        page_of_data.parentNode.replaceChild(divChildren[0], page_of_data);
        divChildren[0].id = 'page_of_data';
        
        // Replace highlight_div with second div.
        var highlight_div = document.getElementById('highlight_div');
        highlight_div.parentNode.replaceChild(divChildren[1], highlight_div);
        divChildren[1].id = 'highlight_div';
       
        // Update or clear message.
        var globalMessage = document.getElementById('globalMessage');
        if (globalMessage) {  // Too difficult to create element, its location is complex.
            if (message) {
                // Update message.
                globalMessage.innerHTML = message;
                BOT.addElementClass(globalMessage, 'portalMessage');
            } else {
                // Clear message.
                removeChildren(globalMessage);
                BOT.removeElementClass(globalMessage, 'portalMessage');
            }
        }
        
        // Run any new javascript.
        BOT.evalScripts(document.getElementById('page_of_data'));
        BOT.evalScripts(document.getElementById('highlight_div'));
        
        if (optionalMoreFun) { optionalMoreFun(); }
    };
    BOT.asyncRequest('GET', url, {success: cb, failure: BOT.handleAsyncYUIFailure});
}

BOT.isYUIDialogUp = function() {
    /* This function relies on how cleanupFn is implemented. */
    return typeof(BOT.myDialog) != 'undefined';
}

BOT.selectRow = function(row) {
    // Turn off all other rows but this one.
    var fun = function(eachRow) {
        BOT.swapElementClass(eachRow, 'on', 'off');
    }
    YAHOO.util.Dom.getElementsByClassName('tableRow', 'tr', row.parentNode, fun)
    
    // Make sure this row is selected.
    BOT.swapElementClass(row, 'off', 'on');
}

BOT.focusOnCurrentRow = function() {
    if (BOT.CurrentRowFocusId) {
        var row = document.getElementById(BOT.CurrentRowFocusId);
        if (row) {
            // This is needed if you mouse over a row, then do a popup, then move mouse away, then escape.
            BOT.swapElementClass(row, 'off', 'on');
        }
    }
}

function cleanupFn() {
    BOT.myDialog.form.blur();  // Else if you typed a little, and you have some autocomplete showing, then it stays up for a little while.
    BOT.myDialog.destroy();  // Don't call delete here, call it in BOT.cleanupDialogCallback.
    BOT.focusOnCurrentRow();
}

/* We had a race condition in IE where multiple event listeners would get called even
   if one called stopEvent -- e.g., v to view entity then v to close popup did a close then open.
   So now we are handling extra event listeners. */
BOT.addKeypressListenerForDialog = function(fun) {
    if (typeof(BOT.currentDialogListeners) == 'undefined') {
        BOT.currentDialogListeners = [];
    }
    BOT.currentDialogListeners.push(fun);
}

BOT.runListenersForDialog = function(event) {
    if (typeof(BOT.currentDialogListeners) != 'undefined') {
        for (var index = 0; index < BOT.currentDialogListeners.length; index++) {
            var fun = BOT.currentDialogListeners[index];
            fun(event);
        }
    }
}

BOT.cleanupDialogCallback = function() {
    delete BOT.myDialog;
    
    // Remove all listeners.
    if (BOT.currentDialogListeners) {
        while (BOT.currentDialogListeners.length > 0) {
            var listener = BOT.currentDialogListeners.pop();
        }
    }
}

/* Return whether login form is present in given text. */
BOT.login_form_regexp = /<form.*id="login"/;

BOT.isLoginFormPresent = function(body) {
    if (BOT.login_form_regexp.test(body)) {
        return true;
    } else {
        return false;
    }
}

/*
  Here is how BOT.showYUIDialog works:
  - call it with a url and optional width in pixels
  - the url should be a page descending from pageutils.BasePopupMixinPage
  - the results of GET'ing the url are .innerHTML'd in, and BOT.evalScripts is called to evaluate all scripts
    - since eval is called, if you declare functions to be used elsewhere they must be global variables, like:
      global_fun = function(param) { ... };  // NOT function global_fun() { ... };
  - the page should have a form on it, but doesn't have to if read-only (YUI will create an empty form for you)
    - the title of the popup comes from the title attribute of the form, else from the display of an h1 (for read-only pages)
  - the form should have an action often ending in /ajax_post which returns json in the form:
       ['', success-message, maybe-url]
       [field-with-error, error-message]  # note that field-with-error might be GLOBAL_ERROR_KEY
  - the form must have required field validate_form then its value is assumed to be a function which is called to validate the form
    - can potentially stop server-side round-trip if return false
    - can be empty if you don't want validation
  - if there's an error then an alert pops up listing it then setting focus to the element with the problem
  - if success then then current page is refreshed with the success-message added to the refreshed url
  
  Notes:
  - call BOT.cancelYUIDialog if you want to close the popup
  - there can only be one popup up at once (it's a javascript global)
  - it's ok to call showYUIDialog with one already showing, then it just gets replaced
*/
BOT.showYUIDialog = function(url, params) {
    // Remember where user is.
    var row = BOT.getFirstElementByTagAndClassName('tr', 'on');
    if (row) {
        BOT.CurrentRowFocusId = row.id;
    } else {
        BOT.CurrentRowFocusId = null;
    }
    
    if (params.width) {
        var width = params.width + "px";
    } else {
        var width = "600px";
    }

    if (document.getElementById('popupDiv') == null) {
        var popupDiv = document.createElement("div");
        popupDiv.innerHTML = '<div id="popupDiv"> <div class="hd"></div> <div id="popupDivBody" class="bd"><br/> Loading...<br/><br/></div> </div>';
        document.body.appendChild(popupDiv);
    }
    if (typeof(BOT.myDialog) == 'undefined') {  // Yes, this is a global. Otherwise you have problems when displayed a second time.
    
        BOT.myDialog = new YAHOO.widget.Dialog("popupDiv", {modal: true,
                                                        fixedcenter: false, // if true then user cannot scroll a big form
                                                        monitorresize: false,
                                                        draggable: true,
                                                        constraintoviewport: true,
                                                        visible: false
                                                        //width: 400 // only ie uses and needs this
                                                       });
 
        BOT.myDialog.destroyEvent.subscribe(BOT.cleanupDialogCallback);
        BOT.myDialog.hideEvent.subscribe(BOT.cleanupDialogCallback);
    }
    BOT.myDialog.render(document.body); // specify document.body so that it's the first form on the page, before our search form - helps with focus and other sloppy document.forms[0] code
    BOT.myDialog.center();
    BOT.myDialog.show();
    
    var cb = function(o){
        if (BOT.isLoginFormPresent(o.responseText)) {
            //alert('Your session has expired. You can login again and then will be redirected to this page.');
            window.location.reload(true);  // param forceGet=true
            return false;
        }
        BOT.myDialog.setBody(o.responseText);  // internally this sets .innerHTML if you pass it text, but if you pass a DOM structure then firefox (but not other browsers) will eval script tags in it
        BOT.evalScripts(BOT.myDialog.body);
        var form = BOT.myDialog.element.getElementsByTagName("form")[0];  // if there was no form then yui creates an empty one
        var title = form.getAttribute("title");
        if (!title) {
            title = BOT.myDialog.element.getElementsByTagName("H1")[0].innerHTML;
        }
        if (!title) {
            title = "TITLE NYI";
        }
        BOT.myDialog.setHeader(title);
        BOT.myDialog.cfg.setProperty("width", width);
        BOT.myDialog.center();

        BOT.myDialog.validate = function() {
            return true;  //return validateEntityForm(form);
        };

        var validate_form = document.getElementById('validate_form');
        if (validate_form) {
            var validateFn = eval(validate_form.value);  // look up function by name
            if (validateFn) {
                BOT.myDialog.validate = validateFn;
            }
        } else {
            var hasFakeForm = BOT.myDialog.form.name.substr(0, 4) == 'frm_';
            if (!hasFakeForm) {  // YUI didn't create a fake form for us, so the user must have created a form.
                alert('ERROR: did not find field validate_form on form');
                return false;
            }
        }
        
        var handleLoginSuccess = function(o) {
            /* In the end, accept either:
                   ['', success-message, maybe-url]
                   [field-with-error, error-message]  # note that field-with-error might be GLOBAL_ERROR_KEY
            */
            var out = myevalJSON(o.responseText);
            if (out[0] == '') {
                // Success, update message and refresh.
                var url, fullRefresh;
                var page_of_data = document.getElementById('page_of_data');
                if (out.length > 2) {
                    fullRefresh = true;
                    url = out[2]; // optional third param is url
                } else if (! page_of_data) {
                    // No page_of_data means that we're not viewing an outline or table, so we must be doing something that doesn't support partial refreshes.
                    fullRefresh = true;
                    url = window.location.href;
                } else {
                    fullRefresh = false;
                    var page_of_data_url = document.getElementById('page_of_data_url');
                    if (page_of_data_url) {
                        url = page_of_data_url.value;
                    } else {
                        url = window.location.href;
                    }
                }
                var message = out[1];
                var newParam = 'message=' + encodeURIComponent(message);
                var pos = url.indexOf('?');
                if (pos == -1) {
                    url = url + '?' + newParam;
                } else {
                    url = url.substr(0, pos+1) + newParam + '&' + url.substr(pos+1);  /* put param at beginning to supersede any previous message */
                }
                
                // Refresh window with message or send user to new url.
                if (fullRefresh) {
                    window.location.href = url;
                } else {
                    BOT.loadPartialPage(url, BOT.cancelYUIDialog, message);  // cancel will also set focus
                }
            } else {
                // Error received.
                alert(out[1]);
                var element = YAHOO.util.Dom.get(out[0]);
                element.focus();
            }
        };

        BOT.myDialog.callback = { success: handleLoginSuccess,
                              upload: handleLoginSuccess, // if a file is uploaded then this is called instead of success - go figure
                              failure: BOT.handleAsyncYUIFailure
                            };
                              
        BOT.myDialog.submit = function () {  // Watch out, we're monkey-patching!
            if (this.validate()) {
                this.beforeSubmitEvent.fire();
                if (typeof(BOT.oFCKeditor) != 'undefined' && typeof(FCKeditorAPI) != 'undefined') { // FCKeditorAPI does not exist on Safari
                    // fckeditor's onsubmit isn't working (race condition?).
                    // So let's do it here.
                    var idOfTextArea = BOT.oFCKeditor.InstanceName;
                    var realEditor = FCKeditorAPI.GetInstance(idOfTextArea);
                    if (realEditor) {
                        // It won't exist in special case: view docs, 'n', esc, 'N', upload file -> bam
                        document.getElementById(idOfTextArea).value = realEditor.GetXHTML();
                    }
                }
                this.doSubmit();
                this.submitEvent.fire();
                //this.hide(); // this is the monkey-patch
                return true;
            } else {
                return false;
            }
        };
        
        // 2007-10-9: hack so that file upload doesn't fail. Temporary?
        var want_to_upload_file = document.getElementById('want_to_upload_file');
        if (want_to_upload_file && (want_to_upload_file.value == 't') && (form.action.indexOf('/document') != -1)) {
            BOT.myDialog.cfg.setProperty('postmethod', 'form');
            var action = form.action;
            action = action.replace('ajax_add/ajax_post', 'add/post');
            action = action.replace('ajax_edit/ajax_post', 'edit/post');
            action = action.replace('ajax_add_child/ajax_post', 'add_child/post');
            action = action.replace('ajax_add_sibling/ajax_post', 'add_sibling/post');
            form.action = action;
            //alert('new action:' + form.action);
           
            var page_of_data_url = document.getElementById('page_of_data_url');
            if (page_of_data_url) {
                form.referer.value = page_of_data_url.value;
            } else {
                form.referer.value = window.location.href;
            }
        }
        
        focusOnFirstInput();
    };
    BOT.asyncRequest('GET', url, {success: cb, failure: BOT.handleAsyncYUIFailure});
    
    return false;
}

BOT.cancelYUIDialog = function() {
    cleanupFn();
}

BOT.showCalendar = function(link, idOfInput) {
   
    // We'll only have one calendar widget on a page (no destroy method on calendars so can't delete them).
    if (BOT.showCalendarWidget) {
        // Move existing one here.
        // The container may no longer be in the DOM, so we can't find it w/getElementById.
        var showCalendarDiv = BOT.showCalendarWidget.oDomContainer;
        link.parentNode.appendChild(showCalendarDiv);
    } else {
        // Create container for calendar widget.
        var showCalendarDiv = document.createElement('div');
        showCalendarDiv.setAttribute('id', 'showCalendarDiv');
        link.parentNode.appendChild(showCalendarDiv);
       
        // Create calendar widget in the container.
        BOT.showCalendarWidget = new YAHOO.widget.Calendar("showCalendarWidget", "showCalendarDiv",
                                    { title: "Choose a date:",
                                      close: true,
                                      MDY_MONTH_POSITION: 2,
                                      MDY_DAY_POSITION: 3,
                                      MDY_YEAR_POSITION: 1,
                                      DATE_FIELD_DELIMITER: "-",
                                      DATE_RANGE_DELIMITER: " to "
                                    } );
    }
    var inputBox = document.getElementById(idOfInput);
    var selected = inputBox.value;
    BOT.showCalendarWidget.selectEvent.unsubscribe();  // In case this is second or subsequent call.
    BOT.showCalendarWidget.cfg.setProperty("selected", selected, false);
    var selectedArrayOfDateArrays = BOT.showCalendarWidget.cfg.getProperty("selected");
    if (selectedArrayOfDateArrays && selectedArrayOfDateArrays[0]) {
        var d = selectedArrayOfDateArrays[0];
        // Only set pagedate if valid date is entered, otherwise you get garbage on screen.
        if (BOT.showCalendarWidget.cfg.checkNumber(d[0]) && BOT.showCalendarWidget.cfg.checkNumber(d[1])) {
            BOT.showCalendarWidget.cfg.setProperty("pagedate", new Date(d[0], d[1]-1, 1), false);  // widget wants month-1, and day is not used
        }
    }
    
    BOT.showCalendarWidget.render();
    BOT.showCalendarWidget.show();
    
    var handleSelect = function(type, args, obj) {
        var dates = args[0]; 
        var date = dates[0];
        var year = date[0], month = date[1], day = date[2];
        
        inputBox.value = year + '-' + month + "-" + day;
        BOT.showCalendarWidget.hide();
    }
    BOT.showCalendarWidget.selectEvent.subscribe(handleSelect, BOT.showCalendarWidget, true);
}

// Below function is a mod from:  http://kratcode.wordpress.com/2006/03/07/javascript-script-execution-in-innerhtml-the-revenge/
/* crs wrote the below. Safari didn't like the above, it would choke with a parse error on large stuff. */
BOT.evalScripts = function(node) {
  var bSaf = (navigator.userAgent.indexOf('Safari') != -1);
  var bOpera = (navigator.userAgent.indexOf('Opera') != -1);
  var bMoz = (navigator.appName == 'Netscape');

  if (!node) return;

  /* IE wants it uppercase */
  var st = node.getElementsByTagName('SCRIPT');
  var strExec;

  for(var i=0;i<st.length; i++)
  {
    if (bSaf) {
      strExec = st[i].innerHTML;
    } else if (bOpera) {
      strExec = st[i].text;
    } else if (bMoz) {
      strExec = st[i].textContent;
    } else {
      strExec = st[i].text;
    }
   
    //alert('About to eval:' + strExec);
    eval(strExec);
  }
};

// Replacements for MochiKit functionality.
BOT.getElement = function(idOrElement) {
    return YAHOO.util.Dom.get(idOrElement);
}
BOT.addElementClass = function (element, className) {
    return YAHOO.util.Dom.addClass(element, className);
}
BOT.removeElementClass = function (element, className) {
    return YAHOO.util.Dom.removeClass(element, className);
}
BOT.swapElementClass = function(element, fromClass, toClass) {
    var obj = BOT.getElement(element);
    BOT.removeElementClass(obj, fromClass);
    BOT.addElementClass(obj, toClass);
}
BOT.getHeight = function(idOrElement) {
    var element = YAHOO.util.Dom.get(idOrElement);  // it's probably a bug in yui that we have to call this
    var region = YAHOO.util.Region.getRegion(element);
    return region.bottom - region.top;
}
BOT.loadJSONDoc = function(url, cb /*, complainOnFailure=true*/) {
    var complainOnFailure = true;
    if (arguments.length > 2) {
        complainOnFailure = arguments[2];
    }
    var handleSuccess = function(o) {
        cb(myevalJSON(o.responseText));
    }
    var callback = { success: handleSuccess };
    if (complainOnFailure) {
        callback.failure = BOT.handleAsyncYUIFailure;
    }
    var request = BOT.asyncRequest('GET', url, callback);
}
BOT.loadJSONDocWithArgs = function(url, argDict, cb) {
    //var args = MochiKit.Base.serializeJSON(argDict);
    //var d = MochiKit.Async.loadJSONDoc(url + '?args=' + MochiKit.Base.urlEncode(args));
    //return d;
    var args = JSON.stringify(argDict);
    BOT.loadJSONDoc(url + '?args=' + BOT.urlEncode(args), cb);
}
BOT.getFirstElementByTagAndClassName = function(tagName, className) {
    return YAHOO.util.Dom.getElementsByClassName(className, tagName)[0];
}
BOT.getLastElementByTagAndClassName = function(tagName, className) {
    var result = YAHOO.util.Dom.getElementsByClassName(className, tagName);
    return result[result.length-1];
}

// Some app-specific functions.
BOT.ajax_add = function(showTemplates, addType, params) {
    /* Based on current context, show a window to create a new entity. addType can be: 'top', 'child', 'sibling' */
    var hoisted_root_id = document.getElementById('hoisted_root_id');
    if (hoisted_root_id) { hoisted_root_id = hoisted_root_id.value; }
    var hasRichTextTemplate = document.getElementById('hasRichTextTemplate').value;
    var rootUrl = document.getElementById('rootUrl').value;
    var url;
    if (addType === 'top') {
        if (hoisted_root_id) {
            url = rootUrl + '/' + hoisted_root_id + '/ajax_add_last_child';
        } else {
            url = rootUrl + '/ajax_add';
        }
    } else if (addType === 'child') {
        url = rootUrl + '/' + params.id + '/ajax_add_child';
    } else if (addType === 'sibling') {
        url = rootUrl + '/' + params.id + '/ajax_add_sibling';
    } else {
        alert('Unrecognized addType:' + addType);
        return;
    }
    if ((hasRichTextTemplate == 't') && showTemplates) {
        url = rootUrl + '/add_popup?addlink=' + encodeURIComponent(url);
        width = 500;
    } else {
        width = 630;  // widest dialog I found was creating tasks, firefox needed wider window than ie
    }
    BOT.showYUIDialog(url, {width: width});
}

// BEGIN SVG.
BOT.svg_ns = 'http://www.w3.org/2000/svg';

BOT.createSVGElement = function(name, attrs /* children... */) {
    var element = document.createElementNS(BOT.svg_ns, name);
    if (attrs) {
        for (var key in attrs) {
            var value = attrs[key];
            element.setAttributeNS(null, key, value);
        }
    }
    if (arguments.length > 2) {
        for(var index=2; index < arguments.length; index++)
        {
            element.appendChild(arguments[index]);
        }
    }
    return element;
}

/* Return false if it looks too old. */
BOT.detectModernBrowser = function() {
    if (YAHOO.env.ua.gecko > 0 && YAHOO.env.ua.gecko < 1.9) {  // Firefox, ff3 = 1.9
        return false;
    }
    if (YAHOO.env.ua.ie > 0 && YAHOO.env.ua.ie < 7) {     // MS IE
        return false;
    }
    return true;
}
// END SVG.


