/* $Id: bigmedium_cp.js 3233 2008-08-21 12:47:26Z josh $
   Copyright 2005-2008 Josh Clark and Global Moxie, LLC. All rights reserved.
   http://globalmoxie.com/
*/

function BMMarker_BM() {};
/***************************************************************/
    var BM = new Object();

/*   BM Object
****************************************************************/

// these defaults, while sane, should get overwritten by page instance
BM.moxiebin = '/cgi-bin/moxiebin';
BM.imagedir = '/bmadmin/img';
BM.bmadmin = '/bmadmin';

BM.pathDiv = '/';
BM.isIE = !window.opera && navigator.userAgent.indexOf('MSIE') != -1;
BM.isSafari = navigator.userAgent.indexOf('AppleWebKit') != -1;
BM.alertWidth = 600;

window.onbeforeunload = function (){
   if (BM.confirmLeave) return BM.Lang._('BM:You have unsaved changes.');
};
Event.observe(window,'unload',function(){BM.Behavior._cache=false;});



/***************************************************************
    BM.onDOM
    Runs events when DOM is loaded. Based on work by Dean Edwards:
    http://dean.edwards.name/weblog/2006/06/again/?full#comment5338
****************************************************************/

BM.onDOM = {
    Events : [],
    finished : false,
    kickstart : function() {
        /* for Mozilla/Opera9 */
        if (document.addEventListener) {
          document.addEventListener("DOMContentLoaded", BM.onDOM.doEvents, false);
        }
    
        /* for Internet Explorer */
        /*@cc_on @*/
        /*@if (@_win32)
            document.write("<script id=__ie_onload defer src=\"//:\"><\/script>");
            var script = document.getElementById("__ie_onload");
            script.onreadystatechange = function() {
                if (this.readyState == "complete") {
                    BM.onDOM.doEvents(); // call the onload handler
                }
            };
            // Clear reference to prevent leaks in IE
            script = null;
        /*@end @*/

        /* for Safari and Konqueror */
        if (/KHTML|WebKit/i.test(navigator.userAgent)) { // sniff
            BM.onDOM.safariRepeat = setInterval(function() {
                if (/loaded|complete/.test(document.readyState)) {
                    BM.onDOM.doEvents(); // call the onload handler
                }
            }, 10);
        }

        /* a backstop for other browsers */
        Event.observe(window, 'load', BM.onDOM.doEvents); //all events

    },
    addEvent : function (listener) {
        BM.onDOM.Events.push(listener);
    },
    doEvents : function() {
        if (BM.onDOM.finished) return;
        BM.onDOM.finished = true;
        if (BM.onDOM.safariRepeat) clearInterval(BM.onDOM.safariRepeat);
        BM.onDOM.Events.each(function(f) { f() } );
        BM.onDOM.Events = [];
    }
};
BM.addLoadEvent = BM.onDOM.addEvent; //alternate name
BM.onDOM.kickstart();



BM.addLoadEvent( function() {
    //preload progress image, have to wait til after we have real imagedir
    BM.Ajax.progressImage = new Image;
    BM.Ajax.progressImage.src = BM.imagedir + '/bmcp_progress.gif';
    BM.Ajax.progressHTML = '<img src="' + BM.Ajax.progressImage.src
          + '" alt="" width="10" height="10" />';

    if(!$('BM_CURRENT_SITE')) return;
    BM.siteID = encodeURIComponent($F('BM_CURRENT_SITE'))

    BM.Behavior.init();
} );


/*   Basic BM utilities
****************************************************************/

BM.alert = function(html,option) {
    if (typeof option != 'object') option = {};
    option.width = BM.alertWidth;
    var modal = new BM.Modal(option);
    modal.displayContent(html);
    return modal;
};

BM.random = function() {return Math.floor(Math.random() * 99999999999) + 1;};

BM.escapeHTML = function(text) { // escapeHTML doesn't catch quotes
    return text.escapeHTML().replace(RegExp('"', 'g'), '&quot;');
};

BM.px = function(unit) {
    var unit = unit.toString();
    if (unit.indexOf('%') < 0) unit = Math.round(unit) + 'px';
    return unit;
};

BM.url = function(option) {
    if (typeof option != 'object') return '';
    if (!option.script) return '';
    if (!option.site) option.site = BM.siteID || 'x';
    if (!option.rm) option.rm = '';
    var path = [option.rm, option.site];
    if (option.path) {
        path.each( function(v) { v = encodeURIComponent(v) } );
        path = path.concat(option.path);
    }
    return BM.moxiebin + '/' + option.script + BM.pathDiv + path.join('/')
      + '?' + BM.random();
};

BM.iframeDoc = function(iframe) {
    if (! (iframe = $(iframe) ) ) return;
    var iDoc = iframe.contentWindow
      || iframe.contentDocument
      || window.frames[iframe.id]
      || iframe;
    if (iDoc.document) iDoc=iDoc.document;
    return iDoc;
};

BM.getWindowSize = function() {
    var screenHeight, screenWidth;
    if (window.innerHeight) {
        screenHeight = window.innerHeight;
        screenWidth = window.innerWidth;
    }
    else if (  document.body.parentElement
            && document.body.parentElement.clientHeight )
    { //ie6; measure the html element's content height
        screenHeight = document.body.parentElement.clientHeight;
        screenWidth = document.body.parentElement.clientWidth;
    }
    else if (document.body && document.body.clientHeight) {
        screenHeight = document.body.clientHeight;
        screenWidth = document.body.clientWidth
    }
    return {height: screenHeight, width: screenWidth};
};

BM.getPageSize = function() {
    var screenSize = BM.getWindowSize();
    var pageWidth, pageHeight;
    if (window.innerHeight && window.scrollMaxY) {
        pageWidth = document.body.scrollWidth;
        pageHeight = window.innerHeight + window.scrollMaxY;
    } else if (document.body.scrollHeight > document.body.offsetHeight){
        pageWidth = document.body.scrollWidth;
        pageHeight = document.body.scrollHeight;
    } else {
        pageWidth = document.body.offsetWidth;
        pageHeight = document.body.offsetHeight;
    }
    // for small pages w/dimensions less then size of viewport
    if (pageHeight < screenSize.height) pageHeight = screenSize.height;
    if (pageWidth < screenSize.width) pageWidth = screenSize.width;

    return {height:pageHeight, width:pageWidth};
};


/* the remarkable jsonT script from stefan goessner
   http://goessner.net/articles/jsont/ */
BM.jsont = function (self, rules) {
   var T = {
      output: false,
      init: function() {
         for (var rule in rules)
            if (rule.substr(0,4) != "self")
               rules["self."+rule] = rules[rule];
         return this;
      },
      apply: function(expr) {
         var trf = function(s){ return s.replace(/{([A-Za-z0-9_\$\.\[\]\'@\(\)]+)}/g,
                                  function($0,$1){return T.processArg($1, expr);})},
             x = expr.replace(/\[[0-9]+\]/g, "[*]"), res;
         if (x in rules) {
            if (typeof(rules[x]) == "string")
               res = trf(rules[x]);
            else if (typeof(rules[x]) == "function")
               res = trf(rules[x](eval(expr)).toString());
         }
         else
            res = T.eval(expr);
         return res;
      },
      processArg: function(arg, parentExpr) {
         var expand = function(a,e){return (e=a.replace(/^\$/,e)).substr(0,4)!="self" ? ("self."+e) : e; },
             res = "";
         T.output = true;
         if (arg.charAt(0) == "@")
            res = eval(arg.replace(/@([A-za-z0-9_]+)\(([A-Za-z0-9_\$\.\[\]\']+)\)/,
                                   function($0,$1,$2){return "rules['self."+$1+"']("+expand($2,parentExpr)+")";}));
         else if (arg != "$")
            res = T.apply(expand(arg, parentExpr));
         else
            res = T.eval(parentExpr);
         T.output = false;
         return res;
      },
      eval: function(expr) {
         var v = eval(expr), res = "";
         if (typeof(v) != "undefined") {
            if (v instanceof Array) {
               for (var i=0; i<v.length; i++)
                  if (typeof(v[i]) != "undefined")
                     res += T.apply(expr+"["+i+"]");
            }
            else if (typeof(v) == "object") {
               for (var m in v)
                  if (typeof(v[m]) != "undefined")
                     res += T.apply(expr+"."+m);
            }
            else if (T.output)
               res += v;
         }
         return res;
      }
   };
   return T.init().apply("self");
};

BM.postRequest = function(href) {
    if (!href) return;
    var form = Builder.node('form', {action:href, method:'post'});
    document.body.appendChild(form);
    form.submit();
};

BM.hiddenTabs = function(parent) {
    var tabFinder = new Selector('div.tabOpt');
    var hidTabs = tabFinder.findElements(parent).reject( Element.visible );
    return hidTabs;
};


function BMMarker_Ajax() {};
/***************************************************************/
    BM.Ajax = new Object;

/*  Enable and respond to asynchronous Ajax requests
****************************************************************/

BM.Ajax.isLocal = function(url) {
    var currentHost = document.location.protocol + '//' + document.location.host;
    if (url.indexOf(currentHost) != 0) return false;
    return true;
};

BM.Ajax.makeLocal = function(url) {
    var host = document.location.protocol + '//' + document.location.host;
    if ( ( url.indexOf(BM.bmadmin) == 0 || url.indexOf(BM.moxiebin) == 0 )
        && url.indexOf(host) != 0 )
    {
        url = url.replace(/^https?:\/\/[^\/]+/,host);
    }
    return url;
}

BM.Ajax.request = function(url,option) {
    if (! BM.Ajax.isLocal(url)) return false;
    return new Ajax.Request(url, option);
};

BM.Ajax.requestWithStatus = function(url,onsuccess) {
    var id = 'bmAjax' + BM.random();
    var modal =
      BM.alert('<div id="' + id + '"></div>');
    var statusObj = $(id);
    if (!onsuccess) onsuccess = function( httpReq, statusObj, modal ) {
        var ajaxJSON = BM.Ajax.parseJSON(httpReq);
        BM.Ajax.showMessage(ajaxJSON, statusObj);
        if (ajaxJSON.html) setTimeout(function(){modal.remove()}, 1000);
    }
    if ( !url.match(/\?/) ) url += '?' + BM.random();
    BM.Ajax.request(
        url,
        { 'method' : 'get',
          'onLoading' : function() { BM.Ajax.inProgress(statusObj) },
          'onSuccess' : function(httpReq) { onsuccess(httpReq, statusObj, modal) },
          'onFailure' : function(httpReq) { BM.Ajax.error(httpReq, statusObj) }
        }
    );
    return false;
};

BM.Ajax.updater = function(element, url,option) {
    if (! BM.Ajax.isLocal(url)) return false;
    return new Ajax.Updater(element, url, option);
};

BM.Ajax.statusObj = function(field) {
    if ( !(field = $(field))) return;
    return $(field.id + 'AjaxStat');
};

BM.Ajax.showMessage = function(ajaxJSON, statusObj, errPreamble) {
    if ( !(statusObj = $(statusObj))) return;
    if (ajaxJSON.error) {
        BM.Ajax.error(ajaxJSON.error,statusObj, errPreamble);
        return false;
    }
    statusObj.innerHTML = ajaxJSON.html || ajaxJSON;
    return true;
}

BM.Ajax.parseJSON = function(response) {
    var json = typeof response == 'object' ? response.responseText : response;
    var ajaxJSON = '';
    try {
        eval(json);
    }
    catch (e) {
        ajaxJSON = json;
    }
    return ajaxJSON;
};

BM.Ajax.inProgress = function(statusObj) {
    if ( !(statusObj = $(statusObj))) return;
    statusObj.innerHTML = BM.Ajax.progressHTML + ' '
      + BM.Lang._('Ajax:checking');
}

BM.Ajax.clearStatus = function(statusObj) {
    if ( !(statusObj = $(statusObj))) return;
    statusObj.innerHTML = '';
}

BM.Ajax.error = function(msg, statusObj, preamble) {
    if ( !msg || !(statusObj = $(statusObj))) return;
    if (typeof msg == 'object') { //httprequest
        var txt = ': ' + msg.status ;

        if (msg.statusText && msg.statusText != 'OK') { //safari 'OK' weirdness
            txt += ' (' + msg.statusText + ')';
        }
        msg = txt;
    }
    else {
        msg = ': ' + msg;
    }
    if (!preamble) preamble = BM.Lang._('Ajax:error');
    statusObj.innerHTML='<span class="bm_ajaxNotOK">' + preamble + msg
      + '</span>';
}

BM.Ajax.loginRequired = function(ajaxJSON, modal) {
    //the modal argument is just used for recursive checks,
    //no need to use it except for from within this routine itself
    if ( !ajaxJSON.login ) return false;
    if ($('ajaxLoginForm')) return true;
    if (!modal || !$('BM_AJAX_LOGIN_STATUS')) { // don't have a window, open it
        modal = BM.alert(ajaxJSON.login);
    }
    else { //already have one, pour the content there.
        modal.setContent( ajaxJSON.login );
    }
    BM.Behavior.apply();
    var form = $('ajaxLoginForm');
    var onsuccess = function(httpReq) {
        var ajaxJSON = BM.Ajax.parseJSON(httpReq);
        if (BM.Ajax.loginRequired(ajaxJSON, modal)) return;
        if (ajaxJSON.valid == 'OK') {
            var msg = $('BM_AJAX_LOGIN_STATUS');
            if (msg) {
                msg.innerHTML = BM.Lang._('Ajax:Login successful');
                setTimeout(function(){modal.remove()}, 1000);
            }
        }
        else {
            var html = ajaxJSON.error || ajaxJSON.html || ajaxJSON;
            modal.setContent(html);
        }
    };
    form.onsubmit = function() {
        BM.Ajax.submitForm(this,'BM_AJAX_LOGIN_STATUS',{onsuccess:onsuccess});
        Element.remove(this);
        return false;
    }
    setTimeout("BM.Field.focus('login_username')", 500);
    return true;
}

BM.Ajax.submitForm = function(form, statusObj, options) {
    if ( !(statusObj = $(statusObj))) return;
    if ( typeof options != 'object' ) options = {};
    var onsuccess =
      options.onsuccess || function(httpReq, update) {
        update.innerHTML = httpReq.responseText;
      };
    var onloading =
      options.onloading || function(){ BM.Ajax.inProgress(statusObj) };
    var onfailure =
      options.onfailure || function(httpReq){ BM.Ajax.error(httpReq, statusObj) };
    var param = Form.serialize(form);
    BM.Ajax.request(
        form.action,
        { 'method' : 'post',
          'postBody' : param,
          'parameters' : param,
          'onLoading' : function() { onloading(statusObj) },
          'onSuccess' : function(httpReq) { onsuccess(httpReq, statusObj) },
          'onFailure' : function(httpReq) { onfailure(httpReq, statusObj) }
        }
    );
    return false;
}

BM.Ajax.confirmURL = function(url, statusObj) {
    if (!url || !(statusObj = $(statusObj))) return;
    var onsuccess = function() {
        statusObj.innerHTML = '<span class="bm_ajaxOK">'
          + BM.Lang._('Ajax:url confirmed') + '</span>';
    }
    var onfailure = function(httpReq) {
         BM.Ajax.error(httpReq, statusObj, BM.Lang._('Ajax:url unconfirmed'));
    }
    var request = BM.Ajax.request(
        url,
        { 'method': 'get', // iewin doesn't seem to like head requests
          'onSuccess': onsuccess,
          'onFailure': onfailure
          // took out onloading -- seemed to cause trouble with iewin
        }
    );
    if (!request) BM.Ajax.clearStatus(statusObj);
    return true;
}


function BMMarker_Behavior() {};
/***************************************************************/
    BM.Behavior = new Object;

/*  Sets event handlers based on element selectors, thanks
    to the helping hands of Justin Palmer's marvelous EventSelectors:
    http://encytemedia.com/event-selectors/
****************************************************************/

BM.Behavior = {
    rules : {
        'body:click' : function(e,evt) {
          var cn = e.className || '';
          if ( cn.indexOf('bmcp_icon_button')!= -1
            || e.up().hasClassName('bmcp_icon_button') )
          {
            var a = Event.findElement(evt,'A');
            BM.FormWidget.updateIconButtons(a);
            Event.stop(evt); return false;
          }
          else if(e.tagName=='A' && cn.indexOf('preview')!=-1) {
            BM.PopUp.pop(e,evt);
          }
          else if(e.tagName=='A' && cn.match(/popup|pconfirm/)) {
            BM.PopUp.pop(e,evt);
          }
        }
    },
    exemptForm : //custom forms with custom submit behaviors
      $A(['bmcp_iframe_submit', 'bmcp_ajax_submit', 'bmcpMenuStatus',
          'bmcp_confirmdirs'
      ]),
    formRules : {
        'form:submit' : function(e,evt) {
            var form = Event.findElement(evt,'FORM');
            if ( !BM.Valid.validateForm(form) ) {
                Event.stop(evt); return false;
            }
            if ( form.onvalidate && !form.onvalidate() ) {
                Event.stop(evt); return false;
            }
            BM.TempForm.removeAll();
            BM.confirmLeave = false;
        },
        'form.bmcp_iframe_submit:submit' : function(e,evt) {
            var form = Event.findElement(evt,'FORM');
            if (!BM.Valid.validateForm(form)) {Event.stop(evt); return false;}
            var tempform = BM.tempElements[form.id];
            if (!tempform) return;
            if (!tempform.iframeSubmit()) {Event.stop(evt); return false;}
        },
        'form.bmcp_ajax_submit:submit' : function(e,evt) {
            var form = Event.findElement(evt,'FORM');
            if (!BM.Valid.validateForm(form)) {Event.stop(evt); return false;}
            var tempform = BM.tempElements[form.id];
            if (tempform) tempform.ajaxSubmit();
            Event.stop(evt); return false;
        },
        'form:click' : function(e,evt) { //single listener for clicks
            var t = e.tagName;
            //do manual check instead of hasClassName for speed
            if (t == 'INPUT') {
                var c = e.className || "";
                if (c.indexOf('bm_radio_tab') != -1) BM.RadioTab.select(e);
                else if (c.indexOf('optionalToggle') !=  -1) {
                   var target = $(e.id + '_tog');
                   if (!target) return;
                   var effect = e.checked ? 'reveal' : 'hide';
                   BM.FX[effect](target, {effect:'blind'});
                }
                else if ( c.indexOf('bmautoImage') != -1 ) {
                    var base = e.id.replace(/__AUTO$/, '');
                    Element.toggle(base + '__MTOG');
                    Element.toggle(base + '__ATOG');
                }
            } else if (t == 'A' && e.hasClassName('bmcp_list_button') ) {
                BM.FormWidget.updateImageButtons(e);
                Event.stop(evt); return false;
            }
        }
    },
    init : function() {
        Object.extend(this.rules, {
         _each: function(iterator) {
           for (key in this) {
             if(key == '_each') continue;
             var value = this[key];
             var pair = [key, value];
             pair.key = key;
             pair.value = value;
             iterator(pair);
           }
         }
        });
        if (BM.isIE) {
          // ie is weird for selects; fires blur when it shouldn't;
          // the focus/blur styles also look odd when iframes present
          this._inputCanFocus['select-multiple'] = null;
          this._inputCanFocus['select-one'] = null;
          this._canHaveEvents['select-multiple'] = null;
          this._canHaveEvents['select-one'] = null;
        }
        this.apply(this.rules);
    },
    register : function(rule) {
        Object.extend(this.rules, rule);
    },
    apply : function() {
        var observer = null;
        this.unloadCache();
        this._addFormRules();
        this.rules._each(function(rule) {
          var selectors = $A(rule.key.split(','));
          selectors.each(function(selector) {
            var pair = selector.split(':');
            var event = pair[1];
            $$(pair[0]).each(function(element) {
              if(pair[1] == '' || pair.length == 1) rule.value(element);
              else this.addObserver(element,event,rule.value);
            }.bind(this));
          }.bind(this));
        }.bind(this));
    },
    unloadCache: function() {
        if (!this._cache) return;
        for (var i = 0; i < this._cache.length; i++) {
          Event.stopObserving.apply(this, this._cache[i]);
          this._cache[i][0] = null;
        }
        this._cache = [];
    },
    _cache: [],
    _addFormRules : function() {
        var bod = document;
        //form submit rules
        var onfocus = this._inputOnFocus.bind(this);
        var onblur = this._inputOnBlur.bind(this);
        $A(bod.getElementsByTagName('form')).each(function(f){
          if( !(f=$(f)) ) return;
          var cn=this.exemptForm.find(function(c){return f.hasClassName(c)});
          if (!cn) { //regular form
            this.addObserver(f,'submit',this.formRules['form:submit']);
          }
          else {
            var rule = this.formRules['form.'+cn+':submit'];
            if (rule) this.addObserver(f,'submit',rule);
          }
          this.addObserver(f,'click',this.formRules['form:click']);
          $A(f.elements).each(function(i){
            if (!this._canHaveEvents[i.type]) return;
            this.addObserver(i,'focus',onfocus);
            this.addObserver(i,'blur',onblur);
          }.bind(this) );
        }.bind(this));
    },
    addObserver : function(element,eventName,func) {
        var observer = function(event) {
          var e = Event.element(event);
          if(e.nodeType == 3) e = e.parentNode; //Safari bug, fixed in Webkit
          func($(e), event);
        };
        this._cache.push([element, eventName, observer]);
        if (eventName == 'keypress') { // Avoid munging of keypress
            Event._observeAndCache(element, 'keypress', observer, false);
        }
        else {
            Event.observe(element, eventName, observer);
        }
    },

    _canHaveEvents : {
      text:1,textarea:1,file:1,password:1,'select-multiple':1,'select-one':1
    },
    _inputCanFocus : {
      text:1,textarea:1,file:1,password:1,'select-multiple':1,'select-one':1
    },
    _inputOnFocus : function(e) {
        BM.FX.addClass($(e.name + 'Label'),'labelHighlight');
        if (BM.Behavior._inputCanFocus[e.type]) BM.FX.addClass(e,'inFocus');
    },
    _inputOnBlur : function (e) {
        BM.Field.validate(e);
        BM.FX.removeClass($(e.name + 'Label'),'labelHighlight');
        BM.FX.removeClass(e,'inFocus');
    }
};


function BMMarker_Confirm() {};
/***************************************************************/
    BM.Confirm = Class.create();

/*  Presents a modal dialog to confirm an action
****************************************************************/

BM.Confirm.prototype = {
    initialize: function(attr) {
        if (typeof attr != 'object') attr = {};
        if (typeof attr.action != 'function') return;
        this.action = attr.action;
        this.button = attr.button || BM.Lang._('Ajax:Yes, Do It');
        this.postMessage = attr.postMessage;
        this.showProg = attr.showProg;

        //set up text and buttons in a new modal
        var msg_div = Builder.node('div',{style:'margin-bottom:1em'});
        msg_div.innerHTML = attr.message || BM.Lang._('Confirm:Are you sure?');
        var cancel = Builder.node( 'button',
          {className: 'bm_MultipleButtons' }, BM.Lang._('Ajax:Cancel')
        );
        var okay = Builder.node(
          'button', {className: 'bm_MultipleButtons' }, this.button
        );
        var container = Builder.node(
          'div', {style:'text-align:center'}, [msg_div, cancel, okay]
        );
        if (attr.modal) {
            this.modal = attr.modal;
            this.modal.setContent(container);
        }
        else {
            this.modal = new BM.Modal({width:BM.alertWidth});
            this.modal.displayContent(container);
        }

        //always use onclick for modal closeLink (often need to replace it)
        var doRemove = this.remove.bind(this);
        this.modal.closeLink.onclick = doRemove;

        //observer needs to wait til it's in the dom.
        var doAction = this.doAction.bind(this);
        setTimeout(function(){
            okay.focus();
            Event.observe(okay,'click',doAction);
            Event.observe(cancel,'click',doRemove);
          },150);
    },
    remove : function() {
        this.action = null;
        this.modal.remove();
        return false;
    },
    doAction : function(evt) {
        this.action();
        if (this.showProg) {
            BM.Ajax.inProgress(this.modal.contentDiv);        
        }
        else if (this.postMessage) {
            this.modal.setContent(this.postMessage);
            setTimeout(this.remove.bind(this),1000);
        }
        else {
            this.remove();
        }
        Event.stop(evt); return false;
    }
};


function BMMarker_Field() {};
/***************************************************************/
    BM.Field = new Object;

/*  Form field handling
****************************************************************/

BM.Field.focus = function(field) {
    Field.activate(field);
}

BM.Field.validate = function(field, option) {
    if ( !(field = $(field)) || !BM.Valid.fieldInfo[field.name] ) return 1;
    if (typeof option != 'object') option = {};
    var type = BM.Valid.fieldInfo[field.name].type;
    var message;
    if (BM.Valid.fieldInfo[field.name].required && field.value == '') {
        message = BM.Valid.fieldInfo[field.name].name + ": "
          + BM.Lang._('Field:Please provide a value');
    }
    else if (type && BM.Valid.types[type] && field.value != '') {
        var regexp = BM.Valid.types[type]['regexp'];
        if (regexp) {
            var checkMatch = new String(field.value);
            if (checkMatch.match(regexp)) { //contains illegal value
                message = BM.Valid.errorMsg(field.name);
            }
        }
        else {
            var do_function = BM.Valid.types[type]['function'];
            if (do_function && do_function(field, option) == false) {
                message = BM.Valid.errorMsg(field.name);
            }
        }
    }
    if (message) {
        return BM.Field.showStatus(field,message,option);
    }
    BM.FX.hide(field.name + 'Status');
    return 1;
}

BM.Field.ajaxValidate = function(field, script, runmode, dataID, siteID) {
    if ( !(field = $(field)) || !script || !runmode ) return;
    var statusObj = BM.Ajax.statusObj(field);
    if (!statusObj) return;

    if (dataID == null) dataID = '';
    if (siteID == null) siteID = '';
    var url = BM.moxiebin + '/' + script + BM.pathDiv + runmode + '/' + BM.siteID;
    var params = 'v=' + encodeURIComponent(field.value) + '&id='
      + encodeURIComponent(dataID) + '&site_id=' + encodeURIComponent(siteID);
    var onsuccess = function(httpReq) {
        var ajaxJSON = BM.Ajax.parseJSON(httpReq);
        if (BM.Ajax.loginRequired(ajaxJSON)) {
            BM.Ajax.clearStatus(statusObj);
        }
        else {
            BM.Ajax.showMessage(ajaxJSON, statusObj);
        }
    };
    BM.Ajax.request(
        url,
        { 'onLoading' : function() {BM.Ajax.inProgress(statusObj)},
          'parameters' : params,
          'method' : 'post',
          'onSuccess' : onsuccess,
          'onFailure' : function() {  // look the other way
              BM.Ajax.clearStatus(statusObj)
          }
        }
    );
}

BM.Field.showStatus = function(field, message, option) {
    if ( !(field=$(field)) || !message ) return;
    if (typeof option != 'object') option = {};
    var statusObj = $(field.name + 'Status');
    if (!statusObj && !option.noalert) {
        alert(message);
    }
    else {
        try { //ie throws 'unknown runtime error' on tempforms status fields
            if (statusObj.innerHtml != message) statusObj.innerHTML = message;
            BM.FX.reveal(statusObj);
        }
        catch (e) {
            BM.FX.hide(statusObj);
            alert(message); //ok even w/option.noalert because it's a tempform
        }
    }
    return message;
}


function BMMarker_FX() {};
/***************************************************************/
    BM.FX = new Object;

/*  Visual display and effect functions, most of which are
    wrappers to the miraculous scriptaculous:
    http://script.aculo.us
****************************************************************/

BM.FX.addClass = function(obj, className) {
    Element.addClassName(obj,className);
}

BM.FX.removeClass = function(obj, className) {
    Element.removeClassName(obj,className);
}

BM.FX.revealEffects = {
    'fade' : 'Appear',
    'slide' : 'SlideDown',
    'blind' : 'BlindDown'
}

BM.FX.hideEffects = {
    'fade' : 'Fade',
    'slide' : 'SlideUp',
    'blind' : 'BlindUp',
    'switch' : 'SwitchOff',
    'puff' : 'Puff',
    'squish' : 'Squish'
}

BM.FX.reveal = function(obj, option) {
    if ( !( obj = $(obj) ) || Element.visible(obj) ) return;
    BM.FX.doEffect(obj, option, BM.FX.revealEffects);
}

BM.FX.hide = function(obj, option) {
    if ( !( obj = $(obj) ) || !Element.visible(obj) ) return;
    BM.FX.doEffect(obj, option, BM.FX.hideEffects);
}

BM.FX.doEffect = function(obj, option, effectKey) {
    if (!(obj = $(obj))) return;
    if (typeof option != 'object') option = {};
    if (!option.effect || !effectKey[option.effect]) option.effect = 'fade';
    var funcName = effectKey[option.effect];
    delete option.effect;
    if (option.duration == null) option.duration = 0.4;
    new Effect[funcName](obj,option);
}

BM.FX.tintError = function(obj) {
    BM.FX.addClass(obj,'bmcp_error');
}

BM.FX.tintMsg = function(obj) {
    BM.FX.removeClass(obj,'bmcp_error');
}

BM.FX.trigger = function (trigger, target, option) {
    if (  !(trigger = $(trigger)) || !(target = $(target)) ) return;
    BM.FX.hide(trigger, {duration:0});
    BM.FX.reveal(target, option);
}

BM.FX.toggle = function (trigger, target, option) {
    if (  !(trigger = $(trigger)) || !(target = $(target)) ) return;
    if (Element.hasClassName(trigger, 'bmTriggered')) {
        BM.FX.removeClass(trigger,'bmTriggered');
        BM.FX.hide(target, option);
    }
    else {
        BM.FX.addClass(trigger,'bmTriggered');
        BM.FX.reveal(target, option);
    }
}

BM.FX.flash = function(obj, option) {
    if (!(obj = $(obj))) return;
    if (typeof option != 'object') option = {};
    if (option.duration == null) option.duration = 1.5;
    new Effect.Highlight(obj,option);
}

BM.FX.slideUp = function(obj,option) {
    if (!(obj = $(obj))) return;
    if (typeof option != 'object') option = {};
    if (option.duration == null) option.duration = 0.4;
    new Effect.SlideUp(obj,option);
}

BM.FX.slideDown = function(obj,option) {
    if (!(obj = $(obj))) return;
    if (typeof option != 'object') option = {};
    if (option.duration == null) option.duration = 0.4;
    new Effect.SlideDown(obj,option);
}


function BMMarker_Lang() {};
/***************************************************************/
    BM.Lang = new Object;

/*  Handles language localization
****************************************************************/

BM.Lang.lexicon = {};

BM.Lang._ = function(key) {
    if (!key || !BM.Lang.lexicon[key]) return key;
    return BM.Lang.lexicon[key];
}

BM.Lang.register = function(thisLexicon) {
    if (typeof thisLexicon != 'object') return;
    Object.extend(BM.Lang.lexicon, thisLexicon);
}
/* default english language localizations are in
   bm-lang-en-us.js */
   

function BMMarker_Menu() {};
/***************************************************************/
    BM.Menu = new Object;

/*  Simple initialization of menus (just transparency for now)
****************************************************************/

/* this function gets overridden by bm-iemenu.js for IE 6 and below */
BM.Menu.init = function(container) {
    if (!container) container = document.body;
    if ( !(container = $(container)) ) return;
    var subnav = Element.getElementsBySelector(container,".subnav");
    subnav.each(function (list) { Element.setOpacity(list, 0.93) } );
};

function BMMarker_Modal() {};
/***************************************************************/
    BM.Modal = Class.create();

/*  Handles creation/toggle of modal dialog.
    Inspired by the fine lighbox.js project of Lokesh Dhakar:
    http://www.huddletogether.com/projects/lightbox/
****************************************************************/

BM.Modal.prototype = {
    initialize: function(attr) {
        if (typeof attr != 'object') attr = {};
        this.onremove = attr.onremove;

        //zindices to handle multiple modals at once
        var coverZ = 10000;
        while ( $('BM_MODALCOVER' + coverZ) ) {
            coverZ += 4;
        }
        var frameZ = coverZ + 1;
        var tintZ = coverZ + 2;
        var contentZ = coverZ + 3;

        /* assemble the transparent cover and stretch across the screen by
           manually figuring the height; not perfect because resizing the
           screen can make the edges show. works without that with fixed
           position (firefox/safari), but then we have to have the content
           box have fixed position too, or we start to have some usability
           issues, including disappearing cursors. But fixed means that
           you cannot scroll to see the window if it extends beyond
           window height. so for now at least...
        */

        this.cover = Builder.node('div',
            {id: 'BM_MODALCOVER' + coverZ,
             className: 'bmcp_modalCover',
             style:'display:none;z-index:'+coverZ
            }
        );
        this.cover.appendChild(
            this.tintDiv = Builder.node('div',
              { className : 'bmcp_modalTint',
                style: 'z-index:' + tintZ
              } )
        );
        Element.setOpacity(this.tintDiv, 0.8);

        var height = BM.px(BM.getPageSize().height);
        this.cover.style.height = height;
        this.tintDiv.style.height = height;
        if ( BM.isIE && !$('BM_MODALFRAME') ) {
            //add an iframe to block select elements
            this.iframeCover = Builder.node('iframe',
                { id: 'BM_MODALFRAME',
                  className: 'bmcp_modalFrame',
                  style: 'z-index:' + frameZ + ';height:' + height
                }
            );
            Element.setOpacity(this.iframeCover, 0);
            this.cover.appendChild(this.iframeCover);
        }

        /*add the contentBox and nested content div (including a close link)
          contentBox is visibility:hidden instead of display:none so that we
          can get the height in displayContent method */
        var contentStyle = '';
        if (attr.width) {
            contentStyle = 'width:' + attr.width + 'px;'
              + 'left:' + (Math.floor(attr.width/2) * -1) + 'px;'
              + 'margin-left:50%;'
        }
        this.contentBox = Builder.node('div',
          { className : 'bmcp_modalContentBox',
            style: contentStyle + 'visibility:hidden;z-index:'+contentZ },
          [ ( this.closeLink = Builder.node( 'a',
                {className:'bmcp_ModalTrigger', href: '#' },
                BM.Lang._('Modal:Close') )
            ),
            ( this.contentDiv = Builder.node('div',{className:'bm_ModalMessage'}) )
          ]
        );

        //inject the elements and flip on the modal cover
        document.body.appendChild(this.cover);
        document.body.appendChild(this.contentBox);
        this.cover.style.display = 'block';
        var doRemove = this.remove.bind(this);

        //always use onclick for modal closeLink (often need to replace it)
        this.closeLink.onclick = function() { doRemove(); return false; };
    },

    setContent : function(html) {
        if (typeof html == 'object') {
            this.contentDiv.appendChild(html);
        }
        else {
            this.contentDiv.innerHTML = html;
        }
    },

    displayContent : function(html) {
        this.setContent(html);
        var scrollY = 0;
        if (document.body && typeof document.body.scrollTop != "undefined") {
            scrollY += document.body.scrollTop;
            if (document.body.parentNode && typeof document.body.parentNode.scrollTop != "undefined") {
                scrollY += document.body.parentNode.scrollTop;
            }
        }
        else if (typeof window.pageYOffset != "undefined") {
            scrollY += window.pageYOffset;
        }
        var screenSize = BM.getWindowSize();
        this.contentBox.style.top = Math.round( 10 + scrollY) + 'px';
        this.contentBox.style.display = 'none';
        this.contentBox.style.visibility = 'visible';
        BM.FX.reveal(this.contentBox);

        //ie doesn't like focus.bind; use anonymous function instead
        var closeLink = this.closeLink;
        setTimeout(function(){closeLink.focus();},100);
   },

    remove : function() {
        var cBox = this.contentBox, cover = this.cover;
        BM.FX.hide(cBox, { afterFinish: function(){
            Element.remove(cBox); Element.remove(cover);
          } }
        );
        if (this.onremove) { this.onremove(); }
    }
};


function BMMarker_PopUp() {};
/***************************************************************/
    BM.PopUp = new Object;

/*  Handles popup links
****************************************************************/

BM.PopUp.style = {
    'preview' : [
      'preview',
      'scrollbars=yes,resizable=yes,status=yes,toolbar=yes,location=yes,menubar=yes,directories=yes'
    ],
    'pconfirm' : [
      'pconfirm',
      'width=350,height=200,location=yes,resizable=yes,scrollbars=yes,status=yes'
    ],
    'popup' : [
      'popup',
      'width=550,height=350,location=yes,resizable=yes,scrollbars=yes,status=yes,directories=yes'
    ]
};

BM.PopUp.pop = function(link,evt) {
    var targ = link.target || link.className || 'popup';
    var styles = $H(BM.PopUp.style).keys();
    targ = styles.detect( function(s) { if (s == targ) return true; } )
      || 'popup';
    var attr = BM.PopUp.style[targ];
    var newWin;
    try { //ie6 can choke if window already exists
        newWin = window.open(link.href, targ, attr);
    }
    catch (err) {
        link.target = '_blank';
        return;
    }
    newWin.focus();
    Event.stop(evt);
};



function BMMarker_RadioTab() {};
/***************************************************************/
    BM.RadioTab = new Object;

/*  Initialization and handling of tabbed display of radio
    buttons, with subsidiary fields
****************************************************************/

BM.RadioTab = {
    init : function() {
        $A(Element.getElementsBySelector(document,'.bm_radio_tab')).each(function(radio) {
            if (radio.checked) BM.RadioTab.select(radio);
        });
    },
    select : function(radio) {
        if ( !(radio = $(radio)) ) return;
        var radioId = radio.id;
        $A(radio.up('div.bm_tab_set').getElementsByTagName('label')).each(
          function(tab){
            if(tab.htmlFor == radioId) $(tab).addClassName('bm_radio_tab_select');
            else $(tab).removeClassName('bm_radio_tab_select');
          });
            var wantTog = radioId + 'TogField';
        var group = radio.up('.bm_radio_tab_group');
        Element.getElementsBySelector(group,'.tabOpt').each(function(panel) {
            if (!panel.id) return;
            if(panel.id == wantTog) {
                panel.show()
                var richText = panel.id.match(/^RichTextTab\-(.*)TogField$/)
                if ( richText && !$('RichText' + richText[1] + '___Frame') ) {
                    //rich text field, but editor hasn't been constructed yet.
                    BM.RichText.insertFrame('RichText' + richText[1]);
                }
                else if (richText) {
                    var fck = FCKeditorAPI.GetInstance('RichText' + richText[1]);
                    if (fck) fck.Focus();
                }
/*                else if (richText && !BM.isIE) {
                   // firefox 1.0 needs nudge; is this still required?
                    var fck = FCKeditorAPI.GetInstance('RichText' + richText[1]);
                    if ( fck && fck.EditorDocument ) {
                        fck.SwitchEditMode();
                        fck.SwitchEditMode();
                    }
                } */
            }
            else {
                panel.hide();
            }
        });
    }
};


function BMMarker_RichText() {};
/***************************************************************/
    BM.RichText = new Object;

/*  Handle some juggling with richtext fields.
****************************************************************/

//custom fckeditor callback

FCKeditor_OnComplete = function (fck) {
    fck.Events.AttachEvent( 'OnBlur',  BM.RichText.blur);
    fck.Events.AttachEvent( 'OnFocus',  BM.RichText.focus);
}

BM.RichText.focus = function(fck) {
    var doc = fck.EditorDocument;
    if (doc && doc.body) BM.FX.addClass(doc.body,'inFocus');
}
BM.RichText.blur = function(fck) {
    var doc = fck.EditorDocument;
    if (doc && doc.body) BM.FX.removeClass(doc.body,'inFocus');
}

BM.RichText.insertFrame = function(fieldID) {
    var urlField = $(fieldID + '___BM_RTURL');
    if (!urlField) return;
    var heightField = $(fieldID + '___BM_RTHEIGHT');
    var height = heightField && heightField.value ? heightField.value : 400;
    var widthField = $(fieldID + '___BM_RTWIDTH');
    var width = widthField && widthField.value ? widthField.value : '100%';

    var frame = Builder.node(
        'iframe',
        { id: fieldID + '___Frame',
          src:urlField.value,
          height:height,
          width:width,
          frameborder:'no',
          scrolling:'no'
        }
    );
    var parent = urlField.parentNode;
    parent.insertBefore(frame, urlField);
}

BM.RichText.prepForSubmit = function(fieldID){
    if (typeof FCKeditorAPI == 'undefined' || !FCKeditorAPI) return;
    var fckEditor = FCKeditorAPI.GetInstance(fieldID);
    if (fckEditor) fckEditor.UpdateLinkedField();
}

BM.RichText.buildFields = function(field) {
    var filters = BM.RichText.filters;
    if (!filters) filters = ['RawHTML'];

//TOFIX: ie cant cope with rich text in modal dialog.
if (BM.isIE && filters[0] == 'RichText') filters.shift();
    var thisFilter, text, match;
    if (field.value && (match = field.value.match(/^([^:]+):([\s\S]*)/)) ) {
        thisFilter = match[1];
if (BM.isIE && thisFilter == 'RichText') thisFilter = 'RawHTML';
        text = match[2] || '';
    }
    else {
        thisFilter = filters[0];
        text = field.value || '';
    }
    var width = BM.px(BM.RichText.width);
    var height = BM.px(BM.RichText.height);
    var tabs = [];
    var style = 'width:' + width + ';height:' + height + ';';
    filters.each( function(filterName) {
        var fieldType = (filterName == 'RichText' ? 'wysiwyg' : 'raw_text');
        var thisTab = {
            text: BM.Lang._('RichText:' + filterName),
            id: filterName + 'Tab-' + field.id,
            value : filterName
        };
        var fieldID = filterName + field.id;
        var thisField = {
            label: '&nbsp;',
            hide_status: 1,
            url: BM.bmadmin + '/wysiwyg/editor/fckeditor.html?InstanceName='
              + fieldID + '&amp;Toolbar=bm-brief',
            height: height,
            width: width,
            style: style
        };
        if ( filterName == thisFilter ) {
            thisTab.checked = true;
            thisField.value = BM.escapeHTML(text);
        }
        thisTab.fields=[ [fieldType, fieldID, thisField] ];
        tabs.push(thisTab);
    });
    var html = BM.TempForm.processTabbedFields(field.id, tabs, {width:'68%'});
    return html;
};


function BMMarker_Scrub() {};
/***************************************************************/
    BM.Scrub = new Object;

/*  Cleans and tidies user-submitted values
****************************************************************/

BM.Scrub.url = function(url, option) {
    if (!url) return url;
    if (typeof option != 'object') option = {};
    url = url.replace(/^\s+/, '');
    url = url.replace(/\s+$/, '');
    if (option.http) {
        var IPv4address  = '([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+)';
        var toplabel     = '([a-zA-Z][-a-zA-Z0-9]*[a-zA-Z0-9]|[a-zA-Z])';
        var domainlabel  = '(([-a-zA-Z0-9][-a-zA-Z0-9]*)?[-a-zA-Z0-9])';
        var hostName     = '((' + domainlabel + '\\.)*' + toplabel + '\\.?)';
        var host         = '(' + IPv4address + '|' + hostName + ')';
        var hostProtocol = new RegExp('^https?:\\/\\/' + host);
        if (!url.match(hostProtocol)) return false;
    }
    return url;
}

BM.Scrub.url_dir = function(url, option) {
    var url = BM.Scrub.url(url, option);
    if (!url) return false;
    url = url.replace(/^(.+?)[\\\/]+$/, '$1'); //trailing slashes
    return url;
}

BM.Scrub.dir = function(dir) {
    if (dir == null) dir = '';
    dir = dir.replace(/^\s+/, '');
    dir = dir.replace(/\s+$/, '');
    
    // eliminate non-leading double slashes (UNC can start with \\)
    dir = dir.replace(/([^\\\/])([\\\/]){2,}/g,'$1$2');
    dir = dir.replace(/^(.+?)[\\\/]+$/, '$1'); // trailing slash
    if (dir.match(/\.\./)
      || !dir.match(/^[^\|;<>\`\*\(\)\[\]\{\}\$\n\r]+$/)
      || !dir.match(/^(([a-zA-Z]:[\\\/])|\/|\\\\)/) ) {
        return false;
    }
    return dir;
}


function BMMarker_Slider() {};
/***************************************************************/
    BM.Slider = new Object;

/*  Initialize slider objects
****************************************************************/

BM.Slider.init = function(id) {
    var field = $(id), knob = $(id + '__knob'), track = $(id + '__track');
    if (!field || !knob || !track || !track.offsetWidth ) { // keep polling
        setTimeout(function(){ BM.Slider.init(id) }, 100);
        return;
    }
    var slider = new Control.Slider(knob, track,
      { range:$R(0,1000),
        onChange:function(v){ field.value = Math.floor(v) },
        onSlide:function(v){ field.value = Math.floor(v) }
      }
    );
    slider.setValue( field.value );
    Event.observe(field,
      'change',
      function(){
        field.value = field.value.replace(/\D.*/, '');
        if (!field.value) field.value = '0';
        slider.setValue(field.value);
      },
      true);
}

function BMMarker_Sort() {};
/***************************************************************/
    BM.Sort = new Object;

/*  Functions for manipulating and handling sortable lists
****************************************************************/

BM.Sort = {
  init : function(container) {
    if (! (container = $(container))) return;
    var lists = container.getElementsByTagName('UL');
    //with nested lists, kids must be initialized first; step thru backwards
    for (var i=lists.length -1; i>=0; i--) {
        Sortable.create(
            lists[i],
            {   overlap:'vertical',
                constraint: 'vertical',
                handle: 'bmcp_handle',
                onUpdate:function(){ BM.confirmLeave=1; }
            }
        );
    }
  },
  serialize : function(container) {
    if (! (container = $(container))) return;
    var lists = container.getElementsByTagName('ul');
    var serialize = [];
    $A(container.getElementsByTagName('ul')).each(function(list){
        serialize.push( Sortable.serialize(list) );
    });
    return serialize.join('&');
  },
  closeDetails : function(handle) {
    var li = $(handle).up('li');
    if (!li) return false;
    var match = li.id.match(/_(\d+)$/);
    if (!match) return false;
    var details = $('section_' + match[1] + '_details');
    if (details.style.display == '') {
        var anchors = li.getElementsByTagName('a');
        BM.FX.toggle(anchors[0],details,{effect:'slide'});
    }
  }
};

function BMMarker_FormWidget() {};
/****************************************************************/
    BM.FormWidget = new Object;

/*  handle miscellaneous form behaviors
****************************************************************/

BM.FormWidget.updateImageButtons = function(link) {
    if (!link.id || typeof link.rel == 'undefined') return;
    var match = link.id.match(/^(.+)___/);
    if (!match) return;
    var baseID = match[1];
    BM.FormWidget.setImageButtons(baseID,link.rel,'');
}

BM.FormWidget.setImageButtons = function(baseID,value,fallback) {
    var field = $(baseID);
    if (!field) { // poll til the field is ready
      setTimeout(
        function(){BM.FormWidget.setImageButtons(baseID,value,fallback)},100
      );
      return;
    }
    if (typeof value == 'undefined') value = fallback;
    field.value=value;
    var buttons = Element.getElementsBySelector(baseID + '___LIST','.bmcp_list_button');
    buttons.each(function(button){
        var opacity = button.rel == value ? 1 : 0.4;
        Element.setOpacity(button,opacity);
    });
}

BM.FormWidget.positionChange = function(id_or_obj) {
    if (!( select = $(id_or_obj)) ) { // poll til the field is ready
        setTimeout( function(){BM.FormWidget.positionChange(id_or_obj)}, 100);
        return;
    }
    var baseID = select.id;
    $(baseID + '___PARNUM').style.visibility =
      select.options[select.selectedIndex].value == 'block' ? 'visible' : 'hidden';
}

BM.FormWidget.updateIconButtons = function(link) {
    if (!link.id || !link.rel) return;
    var match = link.id.match(/^(.+)___/);
    if (!match) return;
    var baseID = match[1];
    $(baseID).value=link.rel;
    var buttons = Element.getElementsBySelector(baseID + '___LIST', '.bmcp_icon_button');
    buttons.each(function(button){
        if ( button.id == link.id ) {
            BM.FX.addClass(button, 'bmcp_list_button_on');
        }
        else {
            BM.FX.removeClass(button, 'bmcp_list_button_on');
        }
    });
}


function BMMarker_TempForm() {};
/****************************************************************/
    BM.TempForm = Class.create();

/*  Adds a form and input elements into the DOM
****************************************************************/

BM.tempElements = {};
BM.TempForm.tabs = [];
BM.TempForm.inputMap = {
    'alignment' : 'image_buttons',
    'big_small_text' : 'image_buttons',
    'boolean' : 'checkbox',
    'body_position' : 'body_position',
    'document' : 'document',
    'image_file' : 'image_file',
    'hidden' : 'hidden',
    'link_info' : 'link_info',
    'library' : 'library',
    'priority_slider' : 'priority_slider',
    'raw_text' : 'textarea',
    'rich_text_brief' : 'rich_text',
    'rich_text' : 'rich_text',
    'rich_text_inline' : 'rich_text_inline',
    'simple_text' : 'text',
    'url_safe' : 'text',
    'url' : 'url',
    'value_list' : 'select',
    'wysiwyg' : 'wysiwyg'
};
BM.TempForm.noWrapper = {
    submit : true,
    cancel : true,
    hidden : true
};
BM.TempForm.rules = {
    'wrapStart' : {
        'self' : '<div {container_id} class="bm_FieldEntry {hidden} ">'
          + '{@status($)}{@label($)}{description}',
        'container_id' : function(c) { return c ? 'id="' + c + '"' : '' },
        'hidden' : function(h) {return h ? 'style="display:none;"' : '' },
        'status' : function(self) {
            return self.hide_status ? '' : '<div id="' + self.id
              + 'Status" class="fieldStatus" style="display:none"></div>';
          },
        'label' : function(self) { return BM.TempForm.buildLabel(self) },
        'description' : function(d) {
            return d ? '<div class="fieldDescription">' + d + '</div>' : '';
          }
    },
    'hidden' : {
      'self' : '<input id="{$.id}" name="{$.id}" type="hidden" value="{$.value}" />'
    },
    'checkbox' : {
      'self' : '<label class="checkradio"><input id="{id}" name="{id}" type="checkbox" '
        + 'value="1" class="checkbox"{@checked($)} /> {@label($)}</label>',
      'checked' : function(self) {
        return self.value ? ' checked="checked"' : '';
      },
      'label' : function(self) {
        self.label_as_head = 1;
        return self.option_label ? BM.Lang._(self.option_label) : self.id;
      }
    },
    'text' : {
      'self' : '<input id="{$.id}" name="{$.id}" type="text" '
        + 'value="{$.value}" class="{$.css_class}" />'
    },
    'document' : {
      'self' : '{@fileInfo($)}<input id="{id}" name="{id}" type="file" '
        + 'class="inline" />{@cancel($)}',
      'fileInfo'  : function(self) {
        if (!self.value) return '';
        var url = ( self.directory_url || BM.htmlDir + '/bm' + BM.dot + 'doc' )
          + '/' + self.value;
        var match = self.value.match(/\.([a-zA-Z0-9]+)$/);
        var docClass = match ? ' docIconSm_' + match[1] : '';
        return '<div class="fieldsRight">'
          + '<span class="docIconSm' + docClass +'">'
          + self.value + '</span> &nbsp;&nbsp; '
          + '<a href="' + url + '" class="preview bmcpSupportText">'
          + '[&nbsp;' + BM.Lang._('TempForm:Show me') + '&nbsp;]</a>'
          + '<p><a href="#" id="' + self.id + '__TRIG" class="fileReplace toggler">'
          +  BM.Lang._('TempForm:Replace this file') + '</a></p> <div id="' + self.id
          + '__REPLACE" ' + 'style="display:none; margin: 1em 0"><div>';
      },
      'cancel' : function(self) {
        return !self.value ? '' :
         ' &nbsp;&nbsp; <a href="#" id="' + self.id + '__CANCEL" '
         + 'class="cancelFileReplace bmcpSupportText canceler">'
         + BM.Lang._('Ajax:Cancel') + '</a><br />'
         + '<p class="bmcpSupportText">'
         + BM.Lang._('TempForm:Other pages will be updated') + '</p>'
         + '</div></div></div>';
      }
    },
    'image_file' : {
      'self' : '<div class="fieldsRight" style="bmcpSupportText">{@controls($)}'
        + '{@inputs($)}{@preview($)}</div>',
      'controls'  : function(self) {
        var id = self.id;
        var autosize = '';
        if (BM.canThumbnail) {
            autosize = '<label class="inline"><input type="checkbox" name="'
              + id + '__AUTO" id="' + id + '__AUTO" value="1" '
              + 'class="checkbox bmautoImage" checked="checked" /> '
              + BM.Lang._('Mini:image-autosize') + '</label> &nbsp;&nbsp; ';
        }
        if (!self.value || !self.value.length) return '<p>'+autosize+'</p>';
        return '<a href="#" id="' + id + '__TRIG" '
          + 'class="fileReplace toggler">'
          +  BM.Lang._('TempForm:Replace image') + '</a> <div id="' + id
          + '__REPLACE" ' + 'style="display:none; margin: 1em 0"><div>'
          + '<p class="bmcpSupportText">'
          + BM.Lang._('TempForm:Other images will be updated') + '<br />'
          + autosize + '<a href="#" id="' + id + '__CANCEL" '
          + 'class="cancelFileReplace bmcpSupportText canceler">'
          + BM.Lang._('Ajax:Cancel') + '</a></p>'
      },
      'inputs' : function(self) {
        var fields = [ ['orig','orig'] ].concat(BM.imageFormats);
        var fieldHTML = '';
        var inputs = [];
        if (BM.canThumbnail) { // field for resizing
            fieldHTML = '<div id="' + self.id + '__ATOG"><label '
              + 'class="inline"><input name="' + self.id + '__MSTR" id="'
              + self.id + '__MSTR" class="inline" type="file" /> '
              + BM.Lang._('Mini:image-orig') + '</label></div>'
              + '<div id="' + self.id + '__MTOG" style="display:none"><div>';
        }
        var values=self.value || [];
        var urlLab = BM.Lang._('TempForm:image-url');
        var fileLab = BM.Lang._('TempForm:image-file');
        var fvalue = {};
        for (var i=0; i<values.length; i+=2) {fvalue[values[i]] = values[i+1]}
        fields.each( function(f) {  //individual url and file uploads
            var label, key;
            var fieldID = self.id + f[1];
            key = 'Mini:image-' + f[1];
            label = BM.Lang._(key);
            if (label == key) label = f[1];
            else label += ' (' + f[1] + ')';

            var uvalue, udisp, fdisp, fstyle, ustyle;
            if (fvalue[f[1]] && fvalue[f[1]].indexOf('url:') == 0) {
                uvalue = fvalue[f[1]].replace(/^url:/,'');
                udisp = 'inline';
                ustyle = 'linkSelected';
                fdisp = 'none'; fstyle = '';
            }
            else {
                uvalue = 'http://'; udisp = 'none'; ustyle = '';
                fdisp='inline'; fstyle = 'linkSelected';
            }
            var input = '<a href="#" class="bmimageUFtog ' + fstyle
              + '" id="' + fieldID + '__FTRG">' + fileLab + '</a> <a href="#" '
              + 'class="bmimageUFtog ' + ustyle + '" id="' + fieldID
              + '__UTRG">' + urlLab + '</a> <span id="'+ fieldID + '__FTOG" '
              + 'style="display:' + fdisp + '"><input name="' + fieldID
              + '" id="' + fieldID + '" class="inline" type="file" /></span>'
              + '<span id="'+ fieldID + '__UTOG" style="display:' + udisp + '">'
              + '<input name="' + fieldID + '__URL" id="' + fieldID
              + '__URL" class="inline" type="text" value="' +uvalue
              + '" /></span> ' + label;
            inputs.push(input);
        });
        fieldHTML += inputs.join('<br /><br />');
        if (BM.canThumbnail) fieldHTML += '</div></div>';
        return fieldHTML;
      },
      'preview' : function(self) {
        if (!self.value || !self.value.length) return '';
        var format = new Object;
        for( var i = 0; i<self.value.length; i+=2) { // map to array by width
            var size = self.value[i];
            var match = size.match(/^(\d+)x/);
            if (match) size = match[1];
            format[size] = [ self.value[i], self.value[i+1] ];
        }
        var dir = BM.htmlDir + '/bm' + BM.dot + 'pix/';
        var linkHTML = '';
        format = $H(format);
        var formatByWidth = [];
        format.keys().each(function(key) {
            var width = key.match(/^(\d+)/);
            width = width ? width[1] : 0;
            formatByWidth.push( [ width, format[key] ] );
        });

        formatByWidth.sort( function(a,b){return a[0]-b[0]} ).each(function(size) {
            var key = 'Mini:image-' + size[1][0];
            var label = BM.Lang._(key);
            if ( label == key ) label = size[1][0];
            var url = !size[1][1].indexOf('url:')
                    ? size[1][1].replace(/^url:/, '')
                    : dir + size[1][1];
            linkHTML += '<a href="' + url + '" class="preview">[&nbsp;'
              + label + '&nbsp;]</a> ';
        } );
        var img = '';
        var purl = format['60'] || format['100'];
        if (purl) {
            purl = purl[1];
            if (purl.indexOf('url:') == 0) purl = purl.replace(/^url:/,'');
            else purl = dir + purl;
            img = '<img src="'+purl+'" align="left" style="margin-right:5px" '
              + 'alt="" />';
        }
        return '</div></div><p class="bmcpSupportText">' + img
         + BM.Lang._('TempForm:Current images:') + '<br />' + linkHTML + '</p>';
      }
    },
    'link_info' : {
        'self' : '{@tabs($)}',
        'tabs' : function(self) {
            var suffix = 'PAGE_' + self.id;
            var match = self.id.match( /^BM_MINI_link_info(.+?\-(\d+))$/ );
            var url = '', text = '', bmPageId = '';
            if (match && BM.Mini.obj.url && BM.Mini.obj.url[ match[2] ]) {
                //got an existing url object; get its value to populate
                url = BM.Mini.obj.url[ match[2] ].url;
                text= BM.Mini.obj.url[ match[2] ].text;
                var bmURL = url.match(/^bm:\/\/(\d+)\/(\d+)/);
                if (bmURL) {
                    bmPageId = bmURL[2];
                    BM.Browser.data = {};
                    BM.Browser.data[bmPageId] = {
                        title : text, // TODO: need to populate this server-side!
                        pub_status: 'published',
                        id: bmPageId
                    }
                    url = 'http://', text = ''; //display blanks in external
                 }
            }
            var tabs = [
              { text : BM.Lang._('Mini:Internal'),
                value : 'internal',
                id : self.id + '__int',
                fields : [
                  ['library', 'library', {
                      suffix: suffix,
                      sort: BM.Lang._('Browser:Sort'),
                      alpha: BM.Lang._('Browser:Alphabetically'),
                      recent: BM.Lang._('Browser:Recent')
                    }
                  ],
                  ['hidden', 'BM_LIB_ID_PAGE_' + self.id, {value:bmPageId} ]
                ],
                checked : bmPageId
              },
              { text : BM.Lang._('Mini:External'),
                value: 'external',
                id : self.id + '__ext',
                fields : [
                    [ 'simple_text',
                      self.id + '__text',
                      {label: BM.Lang._('TempForm:Link Text'), value: text, required: 1 }
                    ],
                    [ 'url', self.id + '__url',
                      {label:BM.Lang._('TempForm:Link URL'), required: 1, value: url || 'http://' }
                    ],
                ],
                checked : !bmPageId
              }
            ];
            setTimeout( function(){
                if (bmPageId) BM.Browser.select(suffix,'page',bmPageId);
                new BM.Browser({suffix:'PAGE_' + self.id, objType:'page'});
              }, 500 );
            return BM.TempForm.processTabbedFields(self.id, tabs,
             {containerClass : 'bmcp_mini_tabbed'});
        }
    },
    'textarea' : {
      'self' : '<textarea rows="5" cols="30" id="{$.id}" name="{$.id}" '
        + 'class="{$.css_class}" style="{$.style}">{$.value}</textarea>'
    },
    'submit' : {
        'self' : '<input id="{id}" name="{id}" type="submit" '
          + 'value="{$.value}" class="{css_class}" />',
        'id' : function(id){ return id ? id : 'id' + BM.random() },
        'css_class' : function(c) { return c ? c : 'bm_MultipleButtons' }
    },
    'cancel' : {
        'self' : '<input id="{$.id}" name="{$.id}" type="button" '
          + 'value="{$.value}" class="{css_class}" />',
        'css_class' : function(c) { return c ? c : 'bm_MultipleButtons' }
    },
    'priority_slider' : {
        'self' : '<div class="sliderGroup"><div id="{id}__track" '
          + 'class="slider"><div id="{id}__knob" class="sliderKnob"> </div>'
          + '</div><div class="priorityLegend bmcpSupportText">'
          + '<span class="priorityLow">{@low($)}</span>'
          + '<span class="priorityHigh">{@high($)}</span>'
          + '<input id="{id}" name="{id}" type="text" size="5" value="{value}" />'
          + '</div></div>{@init($.id)}',
        'init' : function(id) { // safari won't process a script tag, so this...
            BM.Slider.init(id);
            return '';
         },
         'low' : function(){ return BM.Lang._('Slider:Low priority') },
         'high' : function(){ return BM.Lang._('Slider:High priority') }
    },
    'rich_text' : {
        'self' : '{@process($)}',
        'process' : function(self) { return BM.RichText.buildFields(self); }
    },
    'rich_text_inline' : {
        'self' : '{@process($)}',
        'process' : function(self) {
            var value = self.value;
            if ( typeof(value) != "undefined" && value != '') {
                 //escape * and _ before replacing <em> and <strong>
                value = value.toString(); //otherwise, number can throw err
                value = value.replace(/([*_])/g, '<$1>');
                value = value.replace(/<\/?em>/g, '_');
                value = value.replace(/<\/?strong>/g, '**');
                value = value.replace(/<([*_])>/g, '$1');
            }
            return '<input id="' + self.id + '" name="' + self.id + '" '
            + 'type="text" ' + 'value="' + value + '" class="'
            + self.css_class + '" />'
        }
    },
    'url' : {
        'self' : '{@process($)}',
        'process' : function(self) {
            var value = self.value ? BM.escapeHTML(self.value) : 'http://';
            return '<input id="' + self.id + '" name="' + self.id + '" '
            + 'type="text" ' + 'value="' + value + '" class="'
            + self.css_class + '" />'
        }
    },
    'wysiwyg' : {
        'self' : '<input type="hidden" name="{$.id}" id="{$.id}" '
          + 'value="{$.value}" />'
          + '<input type="hidden" name="{$.id}___Config" id="{$.id}___Config" '
          + 'value="" />'
          + '<input type="hidden" name="{$.id}___BM_RTURL" id="{$.id}___BM_RTURL" '
          + 'value="{$.url}" />'
          + '<input type="hidden" name="{$.id}___BM_RTHEIGHT" id="{$.id}___BM_RTHEIGHT" '
          + 'value="{$.height}" />'
          + '<input type="hidden" name="{$.id}___BM_RTWIDTH" id="{$.id}___BM_RTWIDTH" '
          + 'value="{$.width}" />'
    },
    'library' : {
        'self': '<div class="bmcpLib"><div class="bmcpLibPreview" '
        + 'id="BM_BROWSE_PREV_{suffix}"> </div><div class="bmcpLibLegend '
        + 'barform">{@filter($)}: <input id="BM_MFILTER" name="BM_MFILTER" '
        + 'class="inline" /><input id="BM_MTYPE" name="BM_MTYPE" '
        + 'value="{suffix}" type="hidden" /> &nbsp; '
        + '<select id="BM_MSORT" name="BM_MSORT" class="inline">'
        + '<option value="recent">{recent}</option>'
        + '<option value="alpha">{alpha}</option></select> &nbsp; '
        + '<button id="BM_MFIND" class="bm_MultipleButtons">{@find($)}</button>'
        + '</div> <div class="bmcpLibBrowser" id="BM_BROWSE_{suffix}"> </div>'
        + '<div id="BM_BROWSE_NAV_{suffix}" class="bmcpLibStat bmcpSupportText"> '
        + '</div></div>',
       'filter' : function(){ return BM.Lang._('Mini:Filter... Name') },
       'find' : function(){ return BM.Lang._('Mini:Find') }
    },
    'body_position' : {
        'self' : '<select class="inline" name="{$.id}" id="{$.id}" '
          + 'onchange="BM.FormWidget.positionChange(this)">'
          + '{@options($)}</select> '
          + '<span id="{$.id}___PAR" style="visibility:hidden">'
          + '<input name="{$.id}___PARNUM" id="{$.id}___PARNUM" class="inline" '
          + 'value="{@number($.value)}" type="text" size="5" /></span>'
          + '{@init($.id)}',
        'init' : function(id) { // safari won't process a script tag, so this...
            BM.FormWidget.positionChange(id);
            return '';
          },
        'options' : function(self) {
            var value = self.value || '';
            var match = value.match(/(\S+):\d+$/);
            var selected = match ? match[1] : value;
            return BM.TempForm.buildSelectOptions(self,selected);
        },
        'number' : function(value) {
            if (!value) value = '';
            var match = value.match(/\S+:(\d+)$/);
            return match ? match[1] : '1';
        }
    },
    'image_buttons' : {
          'self' : '<ul class="bmcp_image_list" id="{id}___LIST">{@images($)}</ul>'
          + '<input name="{id}" id="{id}" value="" type="hidden" />'
          + '{@init($)}',
        'init' : function(self) { // safari won't process a script tag, so this...
            BM.FormWidget.setImageButtons(self.id, self.value, "");
            return '';
          },
        'images' : function(self) {
            var html = [];
            self.options.each( function(value) {
                var id = self.id + '___' + value;
                var title = BM.Lang._('FormWidget:'+value);
                html.push( '<li><a href="#" '
                  + 'id="' + id + '" class="bmcp_list_button bmcp_list_button_'
                  + value + '" ' + 'title="' + title + '" + rel="' + value
                  + '">' + title + '</a></li>'
                );
            } );
            return html.join('');
        }
    },
    'select' : {
        'self' : '<select name="{$.id}" id="{$.id}"{@multiple($)}>'
          + '{@options($)}</select> ',
        'multiple' : function(self) {
            if (!self.multiple) return '';
            return ' multiple="multiple" size="' + self.multiple + '"';
        },
        'options' : function(self) {
            var value = self.value || '';
            return BM.TempForm.buildSelectOptions(self,value);
        }
    }
};

BM.TempForm.buildSelectOptions = function(obj, value) {
    var html = [];
    if (!obj.options) return '<option value=""></option>';
    obj.options.each( function(opt) {
        var label = obj.labels[opt] || opt;
        var selected = opt == value ? ' selected="selected"' : '';
        html.push('<option value="' + opt + '"' + selected + '>' + label + '</option>');
    } );
    return html.join('');
};

BM.TempForm.buildLabel = function(self) {
    if (!self.label) return '';
    var attr = {
        'id': self.id + 'Label',
        'pointer' : ' for="' + self.id + '"',
        'className' : '',
        'required' : '',
        'title' : ''
    };
    var tag = 'label';
    if (self.label_as_head) {
        tag = 'h4';
        attr.className = ' class="bm_labelHead"';
        attr.pointer = '';
    }
    if (self.required) {
        attr.required = '<span class="required">&loz;&nbsp;</span>';
        attr.title = ' title="' + BM.Lang._('TempForm: Required Field')
          + '"';
    }
    return '<' + tag + ' id="' + attr.id + '"' + attr.pointer
      + attr.className + attr.title + '>'
      + attr.required + self.label + '</' + tag + '>';
};


BM.TempForm.processFields = function(schema) {
    var fieldHTML = [];
    schema.each( function(fieldRef) {
        if (typeof fieldRef != 'object') return '';
        var field = fieldRef[2];
        if (typeof field != 'object') return '';
        field.id = fieldRef[1];
        var ruleType = BM.TempForm.inputMap[fieldRef[0]];
        fieldHTML.push( BM.TempForm.fieldHTML(field,ruleType) );
        BM.Valid.addValidation(field);
    });
    return fieldHTML.join('');
};

BM.TempForm.fieldHTML = function(field, ruleType) {
    if (!ruleType || !BM.TempForm.rules[ruleType]) ruleType = 'text';
    var fieldHTML = BM.jsont(field, BM.TempForm.rules[ruleType]);
    if (!BM.TempForm.noWrapper[ruleType]) {
        fieldHTML = BM.jsont(field, BM.TempForm.rules.wrapStart) + fieldHTML + '</div>';
    }
    return fieldHTML;
};

BM.TempForm.processTabbedFields = function(id, tabs, options) {
    if (typeof options != 'object') options = {};
    var containerClass = options.containerClass || '';
    var width = options.width ? BM.px(options.width) : '100%';
    var tabHTML = ['<div class="bm_radio_tab_group">'
      + '<div class="bm_tab_set" style="width:' + width + ';">'];
    var fieldHTML = [];
    var clickTabs = [];
    tabs.each(function(tab){ // { name: tabname, fields: [fields], checked:true }
        var checked, display;
        if (tab.checked) {
            checked = 'checked="checked" ';
            display = 'block';
            clickTabs.push(tab.id);
        }
        else {
            checked = '';
            display = 'none';
        }
        var preamble = tab.preamble || '';
        tabHTML.push('<label class="bm_radio_tab" for="' + tab.id + '">'
          + '<input type="radio" id="' + tab.id + '" name="' + id
          + '" class="bm_radio_tab" value="' + tab.value + '" '
          + checked + '/>&nbsp;'
          + tab.text + '</label>');
        fieldHTML.push('<div id="' + tab.id + 'TogField" class="bm_FieldEntry '
          + 'tabOpt ' + containerClass + '" style="display:' + display + '">'
          + preamble + BM.TempForm.processFields(tab.fields) + '</div>');
    });
    BM.TempForm.tabs = BM.TempForm.tabs.concat(clickTabs);
    return tabHTML.join('') + '</div>' + fieldHTML.join('') + '</div>';
};


BM.TempForm.prototype = {

    initialize : function(attr, options) {
        if (typeof attr != 'object') attr = {};
        if (typeof options != 'object') options = {};
        if (!attr.method) attr.method = 'post';
        if (!attr.id) attr.id = 'BMtempForm';
        attr.enctype = "multipart/form-data";
        attr.acceptCharset = 'utf-8';
        if (options.onremove) this.onremove = options.onremove;

        // don't allow multiple instances of same form
        if (BM.tempElements[attr.id]) BM.tempElements[attr.id].remove();
        BM.tempElements[attr.id] = this;
        this.form = Builder.node( 'form', attr );

        // custom options
        if (options.heading) {
            this.form.appendChild(
              Builder.node('h3', {className:'bmcp_mini_heading'}, options.heading)
            );
        }
        if (options.remote == 'iframe') {
            BM.FX.addClass(this.form, 'bmcp_iframe_submit');
            this.form.appendChild( Builder.node(
              'input', {type:'hidden', name: 'BM_IFRAMEAJAX', value: '1'}
            ));
            this.addIframe();
        }
        else if (options.remote == 'ajax') {
            BM.FX.addClass(this.form, 'bmcp_ajax_submit');
        }
        this.form.appendChild( this.fieldDiv = Builder.node('div') );
        this.fields = [];
        this.oncomplete = options.oncomplete;
        this.toggle = options.toggle;
        this.trigger = options.trigger;
        this.status = options.status;
        this.clickOnLoad = [];
        if (!this.toggle && !this.trigger) {
            this.modal = new BM.Modal(options.modal);
            this.modalContent = Builder.node('div', {style:'display:block;'});
            var internalDiv = Builder.node('div');
            internalDiv.appendChild(this.form);
            this.modalContent.appendChild(internalDiv);
            if (!this.status) {
                var ajaxID = 'BM_TEMPAJAX_' + attr.id;
                this.modalStatus = '<div id="' + ajaxID + '"></div>';
                this.status = ajaxID;
            }
        }
    },

  /*------------------------------
    TempForm:
    CREATE/APPEND FIELDS
    ------------------------------*/

    addFields : function(schema) {
        this.fields.push( BM.TempForm.processFields(schema) );
    },

    addTabbedFields : function(id, tabs, options) {
        this.fields.push( BM.TempForm.processTabbedFields(id, tabs, options) );
    },

    addSubmitCancel : function() {
        this.cancelID = 'BM_CANCEL_' + BM.random();
        var html = '<div style="text-align:center;margin:0 auto;clear:left">'
          + BM.TempForm.fieldHTML( { value:BM.Lang._('Ajax:Save') }, 'submit')
          + BM.TempForm.fieldHTML(
            { id: this.cancelID, value:BM.Lang._('Ajax:Cancel') }, 'cancel'
          )
          + '</div>';
        this.fields.push(html);
    },

  /*------------------------------
    TempForm:
    INSERT FORM INTO DOM AND INITIALIZE BEHAVIORS
    ------------------------------*/

    addAsChildOf : function(parent) {
        if ( !(parent = $(parent))) return;
        this.injectAllFields();
        parent.appendChild(this.form);
        this.initializeFields();
    },

    displayModal : function() {
        if (this.modalStatus) this.modal.contentDiv.innerHTML = this.modalStatus;
        this.injectAllFields();
        this.modal.displayContent(this.modalContent);
        this.initializeFields();
        //always use onclick for modal closeLink (often need to replace it)
        this.modal.closeLink.onclick = this.remove.bind(this);
    },

    injectAllFields : function() {
        this.fieldDiv.innerHTML = this.fields.join('');
    },

    initializeFields : function() {
        setTimeout(function(){BM.Behavior.apply();},100);
        if (this.cancelID) {
            var doRemove = this.remove.bind(this);
            var cButton = this.cancelID;
            setTimeout(function(){
                Event.observe(cButton,'click',function(evt){
                    doRemove(); Event.stop(evt); return false;
                  })
              }, 150);
        }
        this.activateTabs();
        var firstText = Form.findFirstElement(this.form);
        if (firstText && firstText.visible()) {
            var hidTabs = BM.hiddenTabs(this.form);
            var hidden;
            if (hidTabs && hidTabs.length) {
                hidden =
                  hidTabs.any(function(tab){return Element.childOf(firstText,tab) });
             }
            if (!hidden && firstText.focus) setTimeout(function(){firstText.focus()},200);
        }
    },

    activateTabs : function() {
        if (!BM.TempForm.tabs.length) return;
        var tabs = $A(BM.TempForm.tabs);
        BM.TempForm.tabs = [];
        // Firefox needs a moment after displaying page
        setTimeout( function(){ tabs.each(BM.RadioTab.select) }, 150 );
    },

  /*------------------------------
    TempForm:
    FORM SUBMISSION
    ------------------------------*/

    ajaxSubmit : function() {
        BM.Ajax.submitForm(this.form, this.status,
          {onsuccess:this.processResponse.bind(this)}
        );
        this.close({deactivateTrigger: true});
        return false;
    },

    iframeSubmit : function() {
        if (!this.iframe || !this.form) return false;
        this.form.target = this.iframe.id;
        BM.Ajax.inProgress(this.status);

        //FF needs a little stutter (50ms) on the close or it won't submit a file
        //Safari needs more than that for binary files (200ms)
        var closeform = this.close.bind(this, {deactivateTrigger: true} );
        setTimeout(closeform,300);
        return true;

     /* the processing of the response happens in BM.TempForm.readIframe
        static method, the onload event for the iframe, but most of the action
        happens below in processResponse() */
    },

    processResponse : function(response) {
        if (!response) return;
        var ajaxJSON = BM.Ajax.parseJSON(response);
        var status = this.status;
        var handler = this.oncomplete || function() {
            return BM.Ajax.showMessage(ajaxJSON, status);
        };
        if (BM.Ajax.loginRequired(ajaxJSON)) {
            BM.Ajax.clearStatus(this.status);
            this.remove();
        }
        else if ( handler(ajaxJSON, this) ) { //true: done with form
            setTimeout(this.remove.bind(this),800);
        }
        else { //false: no dice, show the form
            this.show();
        }
    },

  /*------------------------------
    TempForm:
    ADD/RETRIEVE IFRAME
    ------------------------------*/

    addIframe : function() {
        var id = this.form.id + '__IFRAME' + BM.random();
        var html = '<iframe id="' + id + '" name="' + id + '" '
          + 'style="height:0px;width:0px;border-width:0px;border-style:none;" '
          + 'onload="BM.TempForm.readIframe(\'' + this.form.id + '\')"></iframe>';
        new Insertion.Bottom(document.body,html);
        this.iframe = $(id);
    },

    getIframeDoc : function() {
        return BM.iframeDoc(this.iframe);
    },

  /*------------------------------
    TempForm:
    FORM OPEN/CLOSE: trigger/toggle
    ------------------------------*/

    close : function(option) {
        if (typeof option != 'object') option = {};
        option.effect = 'slide';
        var closer =
            this.modal   ? this.closeModal.bind(this,option)
          : this.toggle  ? this.closeToggle.bind(this,option)
          : this.trigger ? this.unTrigger.bind(this,option)
          : null;
        if (closer) return closer();
        return false;
    },

    show : function() {
        var doShow = this.modal   ? this.openModal.bind(this)
                   : this.toggle  ? this.openToggle.bind(this)
                   : this.trigger ? this.fireTrigger.bind(this)
                   : null;
        if (doShow) return doShow();
        return false;
    },

    closeModal : function(option) {
        if (!this.modalContent || this.modalContent.style.display == 'none') return false;
        BM.FX.hide(this.modalContent, {effect:'slide'});
        return true;
    },

    openModal : function() {
        BM.FX.reveal(this.modalContent, {effect:'slide'});
        return true;
    },

    closeToggle : function(option) {
        if (!this.toggle || !this.toggle.target) return false;
        if (!(this.toggle.target = $(this.toggle.target))) return false;
        if (this.toggle.target.style.display == 'none') return false;
        BM.FX.toggle( this.toggle.trigger, this.toggle.target, option );
        return true;
    },

    openToggle : function() {
        if (!this.toggle || !this.toggle.target) return;
        if (!(this.toggle.target = $(this.toggle.target))) return;
        if (this.toggle.target.style.display != 'none') return;
        BM.FX.toggle( this.toggle.trigger, this.toggle.target, {effect:'slide'} );
        return true;
    },

    unTrigger : function(option) {
        if (!this.trigger || !this.trigger.target) return false;
        if (!option.deactivateTrigger) BM.FX.reveal( this.trigger.trigger );
        if (!(this.trigger.target = $(this.trigger.target))) return false;
        if (this.trigger.target.style.display == 'none') return false;
        BM.FX.hide( this.trigger.target, option );
        return true;
    },

    fireTrigger : function() {
        if (!this.trigger || !this.trigger.target) return false;
        BM.FX.reveal( this.trigger.target, {effect:'slide'} );
        BM.FX.hide( this.trigger.trigger );
        return true;
    },

  /*------------------------------
    TempForm:
    REMOVE/DELETE FORM
    ------------------------------*/

    remove : function(option) {
        if (typeof option != 'object') option = {};
        if (option.duration == null) option.duration = 0.4;
        var immediate = option.immediate;
        if (immediate) {
            option.duration = 0;
            delete option.immediate;
        }
        if ( ( this.onremove && this.onremove() ) || !this.onremove ) {
            var form=this.form, ifr=this.iframe, mCont=this.modalContainer,
                mod=this.modal;
            var remover = function() {
                var id = form.id;
                if (form) Element.remove(form);
                if (ifr) document.body.removeChild(ifr);
                if (mCont) Element.remove(mCont);
                if (mod) mod.remove();
                delete BM.tempElements[id];
            }
            if ( option.duration && this.close(option) ) {
                //wait for the form to slide shut before removing
                var wait = (option.duration * 1000) + 100;
                setTimeout(remover, wait)
            }
            else if (option.duration) { //already closed, remove immediately
                remover();
            }
            else {                      //no duration, close/remove right away
                this.close(option);
                remover();
            }
        }
        return false;
    }
};


BM.TempForm.readIframe = function(id) {
    var tempform = BM.tempElements[id];
    if (!tempform) return;
    var idoc = tempform.getIframeDoc();
    if (!idoc || !idoc.body) return;
    //in order for results to read correctly from iframe body,
    //iframe requests are returned escaped -- unescape here;
    var response = idoc.body.innerHTML.unescapeHTML();

    //innerhtml and unescapehtml can add unwanted line breaks in some
    //browsers, breaking the ajax parsing.
    response = response.replace(/[\n\r]+/g, ' ');
    tempform.processResponse( response );
};

BM.TempForm.removeAll = function() {
    for ( var id in BM.tempElements ) {
        //sometimes the id lingers but the form no longer exists, not sure
        //why...
        if (BM.tempElements[id] && BM.tempElements[id].remove) {
            BM.tempElements[id].remove( {immediate:true} );
        }
    }
}


function BMMarker_Tritoggle() {};
/***************************************************************/
/*  Manage tritoggle prompt buttons
****************************************************************/

BM.Tritoggle = {
    initButton : function(field,states) {
        if ( !( field = $(field) ) ) return;
        var button;
        if ( !( button = $(field.id + '___CTRL') ) ) return;
        button.bmStates = $H(states);
        button.bmField = field;
        button.onmouseup = function(){
            BM.Tritoggle.cycleButton(button);return false;
        };
    },
    cycleButton : function(button) {
        if ( !( button = $(button) ) || !button.bmStates) return;
        var state = BM.Tritoggle.getState(button,button.bmField.value);
        var newState = state=='Default' ? 'On'
                     : state=='On'      ? 'Off'
                     :                    'Default';
        BM.Tritoggle.setValue(button, button.bmStates[newState]);
    },
    getState : function(button,value) {
        if (typeof value == 'undefined') return 'Default';
        return button.bmStates.keys().detect(function(state){
            return button.bmStates[state]==value;
        }) || 'Default';
    },
    setValue : function(button,newValue) {
        if ( !( button = $(button) ) ) return;
        if ( button.id.indexOf('___CTRL') < 0 ) { //caught field, not button
            button = $(button.id + '___CTRL');
            if (!button) return;
        }
        if ( !button.bmStates) return;
        var state = BM.Tritoggle.getState(button,newValue);
        button.bmField.value = button.bmStates[state];
        BM.FX.addClass(button,'bmcp_toggle'+state);
        button.bmStates.keys().each(function(check){
            if (check!=state) BM.FX.removeClass(button,'bmcp_toggle'+check);
        });
    }
};

function BMMarker_StatusBar() {};
/***************************************************************/
    BM.StatusBar = Class.create();

/*  Creates a status bar (do this via StatusDriver)
****************************************************************/

BM.StatusBar.prototype = {
  initialize: function(container, options) {
    if (typeof options != 'object') options = {};
    this.onComplete = options.onComplete;
    this.container = $(container);
    this.parent=window.parent;
    this.construct = Builder.node('div',
      {style:'border:1px solid #999;padding:5px;margin:1em;'},
      [ this.box = Builder.node('div',
          {style : 'border:1px solid #000;overflow:hidden;position:relative;'
                + 'height:20px;'},
        [ this.status = Builder.node('div',
            {style: 'background-color:#366;height:20px;width:100%;'
                  + 'position:absolute;left:-99990em;' } )
        ] ),
        this.caption = Builder.node('div',
          {style:'font-size:x-small;margin-top:1em;'} )
      ]
    );
    this.container.innerHTML='';
    this.container.appendChild(this.construct);
  },

  updateBar : function(json) {
    if (typeof json == 'undefined' || !json) json = 'empty';

    if (typeof json.percent != 'undefined' || json.percent != null) {
        this.caption.innerHTML = BM.Ajax.progressHTML
          + ' ' + Math.round(json.percent*100) + '%: ' + (json.message || '');
        this.positionStatus(json.percent);
    }
    else if (json.error) {
        this.container.innerHTML = json.error;
    }
    else if (json.is_complete) {
        this.positionStatus(1);
        this.caption.innerHTML = '100%: ' + BM.Lang._('StatusBar:Done');
        setTimeout(this.stop.bind(this), 1000);
    }
    else {
        this.caption.innerHTML = 'Unexpected response:<br /><br />' + json;
    }
  },

  positionStatus : function(percent) {
    var total_width = this.box.offsetWidth - 2;
    var remaining = 1 - percent;

    if (remaining >= 1 || total_width < 0) {
        this.status.style.left = '-9999999em';
    }
    else {
        if (remaining < 0) remaining = 0;
        this.status.style.left =
          '-' + ( Math.floor(total_width * remaining) ).toString() + 'px';
    }
  },

  stop: function() {
    if (this.onComplete) this.onComplete();
  },

  remove: function() {
    var construct = this.construct;
    var remove = function(){Element.remove(construct)};
    BM.FX.hide(this.container, { effect:'slide', updateComplete:remove } );
  }

};


/***************************************************************/
    BM.StatusDriver = Class.create();

/*  Creates an iframe remote to drive a status bar
****************************************************************/

BM.StatusDriver.prototype = {
    initialize: function(container, url, options) {
        if (typeof options != 'object') options = {};
        var transport = options.iframe || 'bmStatusDriver';
        if ( !$(transport) ) {
            var tempframe = Builder.node('iframe',
              { id:    transport,
                name:  transport,
                style: 'width:0px;height:0px;border:none'
              }
            );
            transport = document.body.appendChild(tempframe);

            var onload = this.checkIframeResult.bind(this);
            if (BM.isSafari) transport.onload = onload;
            else Event.observe(transport, 'load', onload, true);
        }
        this.transport = transport;
        this.onfinish = options.onfinish;
        this.onlogin = options.onlogin;
        this.container = $(container);
        BM.Ajax.inProgress(this.container);
        var uparam = 'BMCont=' + this.container.id + '&' + BM.random();
        var qfind = url.match(/\?(.*)/);
        url += ( qfind ? '&' : '?' ) + uparam;
        BM.iframeDoc(this.transport).location.replace( BM.Ajax.makeLocal(url) );
    },
    checkIframeResult : function () {
        var ifr = window[this.transport.id]; //only way to get vars
        if (!ifr || ifr.finishedErr) return;
        var idoc = BM.iframeDoc(ifr);
        if (!idoc || !idoc.body || ifr.finishedBuild) {
            if (this.onfinish) this.onfinish();
            return;
        }

        //incomplete response; strip out any leading JS to get error
        var response = idoc.body.innerHTML.replace(/[\s\S]+<\/script>\s*/i,'');
        response = response.unescapeHTML().replace(/[\n\r]+/g, ' ');
        var ajaxJSON = BM.Ajax.parseJSON(response);
        if (BM.Ajax.loginRequired(ajaxJSON)) {
            if (this.onlogin) this.onlogin();
            else if (this.onfinish) this.onfinish();
        }
        else {
            this.container.innerHTML =
              ajaxJSON.error || ajaxJSON || 'Lost connection with server.';
        }
    }
};


function BMMarker_Valid() {};
/***************************************************************/
    BM.Valid = new Object;

/*  Handles form/field validation
****************************************************************/

BM.Valid.fieldInfo = {}; // provided in the page instance

BM.Valid.types = {
    'email': {
        'function': function(field, option) {
            return BM.Valid.email(field, option);
        }
    },
    'simple_text': {
        'regexp': /[\n\r]/   //no line breaks
    },
    'username': {
        'function': function(fieldObj, option) {
            return BM.Valid.username(fieldObj, option);
        }
    },
    'license' : {
        'function' : function (fieldObj,option) {
            var v = fieldObj.value =
              fieldObj.value.toString().replace(/\s/g,'').toUpperCase();
            if (!v) { return false; }
            var bm = v.match(/^BM-\d+-\S{4}[1-9Zz]{6}$/);
            if ( v.match(/^BM-\d+-\S{4}[1-9Zz]{6}$/) ) { return true; }
            var map = {};
            var dv = 0;
            $w('A B C D E F G H J K L M N P Q R T U V W Y 3 4 6 7 8 9').each(
              function(d){ map[d] = dv++; }
            );
            var chars = v.toArray().reverse();
            var check = chars.shift();
            chars = chars.findAll(function(d){ return map[d] != null });
            if (chars.length != 19) { return false; }
            var totalVal = 0;
            var flip = 1;
            chars.each(function(c){
                var posVal = map[c];
                if ( !(flip = !flip) ) { posVal *=2 }
                while(posVal){
                    totalVal = totalVal + (posVal % 10);
                    posVal = Math.floor(posVal/10);
                }
            });
            return ( check == ( 10 - totalVal % 10 ) % 10 );
        }
    },
    'bmadmin_url' : {
        'function': function(fieldObj, option){
            Object.extend(option, {suffix: 'txt'});
            var mbin = $('bigmedurl');
            if (mbin) {
                var match = mbin.value.match(/^(https?:\/\/[^\/]+)/);
                if (!match || fieldObj.value.indexOf(match[1]) != 0 ) {
                    return false;
                }
            }
            return BM.Valid.ajax_urlDir(fieldObj, option);
        }
    },
    'moxiebin_url' : {
        'function': function(fieldObj, option){
            Object.extend(option, {suffix: 'cgi'});
            return BM.Valid.ajax_urlDir(fieldObj, option);
        }
    },
    'dir_path' : {
        'function': function(fieldObj, option){
            return BM.Valid.dirPath(fieldObj, option)
        }
    },
    'dir_url' : {
        'function': function(fieldObj, option){
            return BM.Valid.urlDir(fieldObj, option)
        }
    },
    'moxiedata_path' : {
        'function': function(fieldObj, option){
            Object.extend(option, {type: 'moxiedata'});
            return BM.Valid.dirPath(fieldObj, option)
        }
    },
    'v1moxiedata_path' : {
        'function': function(fieldObj, option){
            Object.extend(option, {type: 'v1moxiedata'});
            return BM.Valid.dirPath(fieldObj, option)
        }
    },
    'bmadmin_path' : {
        'function': function(fieldObj, option){
            Object.extend(option, {type: 'bmadmin'});
            return BM.Valid.dirPath(fieldObj, option)
        }
    },
    'html_path' : {
        'function': function(fieldObj, option){
            Object.extend(option, {type: 'html'});
            return BM.Valid.dirPath(fieldObj, option)
        }
    },
    'kilobytes' : {
        'function' : function(fieldObj,option) {
            fieldObj.value = fieldObj.value.replace(/[^\-\d.]/g, '');
            return fieldObj.value != '' && fieldObj.value >= 0;
        }
    },
    'number_zeroplus_integer' : {
        'regexp' : /\D/
    },
    'number_integer_positive' : {
        'function' : function(fieldObj, option) {
            fieldObj.value = fieldObj.value.replace(/\D/g, '');
            return fieldObj.value > 0;
        }
    },
    'password_confirm' : {
        'function': function(fieldObj, option) {
            return BM.Valid.passwordConf(fieldObj, option);
        }
    },
    'rich_text' : {
        'function': function(fieldObj, option) {
            return BM.Valid.rich_text(fieldObj, option);
        }
    },
    'rich_text_brief' : {
        'function': function(fieldObj, option) {
            return BM.Valid.rich_text(fieldObj, option);
        }
    },
    'url_safe' : {
        'regexp': /[^A-Za-z0-9\-\_]/
    }
};

BM.Valid.addValidation = function(fieldRef) {
    if (typeof fieldRef != 'object') return;
    if (fieldRef.validate_as) {
        if (!fieldRef.error) fieldRef.error = 'Invalid value';
        BM.Valid.fieldInfo[fieldRef.id] = {
            name: fieldRef.label,
            type: fieldRef.validate_as,
            error: fieldRef.error,
            required: fieldRef.required
        };
    }
};

BM.Valid.validateForm = function(form) {
    if (!BM.Valid.fieldInfo) return true;
    if (! (form = $(form))) return true;
    var all_msg = '';
    var hidTabs = BM.hiddenTabs(form);
    var inputs = Form.getElements(form);
    inputs.each(function(field){
        if (hidTabs && hidTabs.length) {
            var hidden =
              hidTabs.any(function(tab){return Element.childOf(field,tab) });
            if (hidden) return;
         }
        var result = BM.Field.validate(field, {noalert:1, submission:1});
        if (result != 1) all_msg +='<li>' + result + '</li>\n';
    });
    if (BM.Mini && BM.Mini.relation && !form.action.match(/ajax/)) {
        var result = BM.Mini.hasRequired();
        if (result != 1) all_msg += result;
    }
    if (all_msg) {
        BM.alert(
            BM.Lang._('Valid:Validation error') + "\n<ul>" + all_msg + '</ul>'
        );
        return false;
    }
    return true;
}

BM.Valid.errorMsg = function(fieldName) {
    var type = BM.Valid.fieldInfo[fieldName].type;
    var msg = BM.Lang._('Valid:'+type);
    if (msg == 'Valid:'+type) msg = BM.Lang._('Valid:Invalid value for this field.');
    return BM.Valid.fieldInfo[fieldName].name + ": " + msg;
}

BM.Valid.urlDir = function(field, option) {
    if ( !(field = $(field)) ) return;
    if (typeof option != 'object') option = {};
    option.http = 1;
    var url = BM.Scrub.url_dir(field.value, option);
    if (!url) return false;
    field.value = url;
    return true;
}

BM.Valid.ajax_urlDir = function (field, option) {
    if ( !(field = $(field)) ) return;
    if (typeof option != 'object') option = {};
    option.http = 1;
    var url = BM.Scrub.url_dir(field.value, option);
    if (!url) return false;
    field.value = url;
    if (!option.submission) {
        if (!option.suffix) option.suffix = 'txt';
        BM.Ajax.confirmURL(
          url + '/ping.' + option.suffix, BM.Ajax.statusObj(field)
        );
    }
    return true;
}

BM.Valid.dirPath = function(field, option) {
    if ( !(field = $(field)) ) return;
    if (typeof option != 'object') option = {};
    var dir = BM.Scrub.dir(field.value);
    if (!dir) return false;
    field.value = dir;
    if (option.submission) return true;

    if (option.type != 'html') {
        BM.Config.confirmDir( dir, option.type, BM.Ajax.statusObj(field) );
    }
    else {
        BM.Config.confirmHtmlDir(dir, field.id);
    }
    return true;
}

BM.Valid.email = function(field) {
    /* based on an email-checking script by Sandeep V. Tamhankar
       (stamhankar@hotmail.com)                                 */

    if ( !(field = $(field)) ) return;
    var email = field.value;
    var checkTLD=1; // check top-level domains or not?
    var knownDomsPat=/^(com|net|org|edu|int|mil|gov|arpa|biz|aero|name|coop|info|pro|museum)$/;

    var emailPat=/^(.+)@(.+)$/;
    // don't allow  ( ) < > @ , ; : \ " . [ ] */ or space
    var specialChars="\\(\\)><@,;:\\\\\\\"\\.\\[\\]";
    var validChars="\[^\\s" + specialChars + "\]";

    // if user is a quoted string, no limitations on chars in user name
    var quotedUser="(\"[^\"]*\")";

    var ipDomainPat=/^\[(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\]$/;
    var atom=validChars + '+';
    var word="(" + atom + "|" + quotedUser + ")";
    var userPat=new RegExp("^" + word + "(\\." + word + ")*$");
    var domainPat=new RegExp("^" + atom + "(\\." + atom +")*$");

    //start testing address
    var matchArray=email.match(emailPat);
    if (matchArray==null) {
        return false;
    }
    var user=matchArray[1];
    var domain=matchArray[2];
    for (i=0; i<user.length; i++) {
        if (user.charCodeAt(i)>127) {
            return false;
        }
    }
    for (i=0; i<domain.length; i++) {
        if (domain.charCodeAt(i)>127) {
            return false;
       }
    }
    if (user.match(userPat)==null) {
        return false;
    }
    var IPArray=domain.match(ipDomainPat);
    if (IPArray!=null) {
        for (var i=1;i<=4;i++) {
            if (IPArray[i]>255) {
                return false;
            }
        }
        return true;
    }
    var atomPat=new RegExp("^" + atom + "$");
    var domArr=domain.split(".");
    var len=domArr.length;
    for (i=0;i<len;i++) {
        if (domArr[i].search(atomPat)==-1) {
            return false;
        }
    }
    if (checkTLD && domArr[domArr.length-1].length!=2 &&
    domArr[domArr.length-1].search(knownDomsPat)==-1) {
        return false;
    }
    if (len<2) {
        return false;
    }
    return true;
}

BM.Valid.passwordConf = function(field) {
    if ( !(field = $(field)) ) return;
    var orig_pass = $('newpassword');
    if (orig_pass && orig_pass.value && field.value != orig_pass.value) {
        return false;
    }
    return true;
}

BM.Valid.rich_text = function(field,option) {
    // just checks for value if required
    if ( !(field = $(field)) ) return;
    var filter = field.value;
    if (filter == 'RichText') BM.RichText.prepForSubmit(filter + field.name);
    if(!field.checked) return true;
    if (typeof option != 'object') option = {};
    if (!option.submission || !BM.Valid.fieldInfo[field.name].required) return true;
    var fieldText = $(filter + field.name);
    if (!fieldText || !fieldText.value) return false;
    else return true;
}

BM.Valid.username = function(field,option) {
    if ( !(field = $(field)) ) return;
    if (typeof option != 'object') option = {};
    if (field.value.match(/\n\r/)) return false;
    if (!option.submission && $('user_id') ) { //when editing an account
        BM.Field.ajaxValidate(
          field, 'bm-account.cgi', 'ajax-username-avail', $F('user_id')
        );
    }
    return true;
}
