
// we want to make sure that the hover state images don't need to load on the actual hover
var preload = [

];
if (!window.CRS) {
    Tablet.preloadImages(preload);
}
var Tablet = Tablet || {};
Tablet.SiteNav = {
    setPageNav:function(el) {
        if (typeof el['pagenav'] === 'undefined') {
            el.pagenav = Tablet.getElementByClassName(el, 'page-nav', 'ul') || false;
            if (el.pagenav && (Prototype.Browser.IE)) {
                var listwidth = 0;
                var lis = $A(el.pagenav.getElementsByTagName('li'));
                if (lis) {
                    if (Tablet.UI.Enhanced) {
                        // absolutely positioned elements don't expand in width for their child elements
                        el.pagenav.setStyle({left:'-9000px', top:'-9000px', display:'block', position:'static'});
                        lis.each(function(li) {
                            if (li.offsetWidth > listwidth) {
                                listwidth = li.offsetWidth;
                            }
                        });
                        el.pagenav.setStyle({left:'', top:'', display:'', width:'', position:''});
                    }
                    else {
                        listwidth = 220;
                    }
                }
                if (listwidth > 0) {
                    el.pagenav.setStyle({width:listwidth+'px'});
                }
            }
        }
    },
    onMouseEnter:function(e) {
        Tablet.SiteNav.setPageNav(this);
        this.addClassName('hover');
        if (this.pagenav) {
            this.up('ul').addClassName('hover');
            if (Tablet.UI.Enhanced) {
                Tablet.UI.fadeIn(this.pagenav, {duration:0.2, activeClass:'active'});
            }
            else {
                this.pagenav.addClassName('active');
            }
        }
    },
    onMouseLeave:function(e) {
        Tablet.SiteNav.setPageNav(this);
        this.removeClassName('hover');
        if (this.pagenav) {
            this.up('ul').removeClassName('hover');
            if (Tablet.UI.Enhanced) {
                Tablet.UI.fadeOut(this.pagenav, {duration:0.1, activeClass:'active'});
            }
            else {
                this.pagenav.removeClassName('active');
            }
        }
    }
};
document.observe('dom:loaded', function(e) {
    if (!$('mainnav')) { return; }
    $('mainnav').select('ul.section-nav > li').each(function(el) {
        el.addClassName('enhanced');
        el.observe('mouseenter', Tablet.SiteNav.onMouseEnter);
        el.observe('mouseleave', Tablet.SiteNav.onMouseLeave);
        if (el.hasClassName('active')) { //  || el.getElementsByTagName('UL').length > 0
            var link = el.down('a');
            if (link) {
                var marker = $(document.createElement('span'));
                marker.addClassName('marker');
                el.appendChild(marker);
            }
        }
    });
    
});

/* *****
NOTICE: this file gets combined for the live site. any changes here should also be made to the
combined file by running bin/minify.py. The combined file should be committed to Subversion
along with updates to this file.
***** */

var Tablet = Tablet || {};

Tablet.Pane = Class.create();
Tablet.Pane.prototype = {
    enabled:true,
    ready:false,
    container:null,
    overlay:null,
    contentUrl:null,
    pages:[],
    pageHeight:0,
    currentPage:null,
    bod:null,
    pageReset:false,
    refreshContent:false,
    defaultTop:35,  // in pixels, so we can push it off of the top offset,
    onContent:null,
    flashOs:[],
    selectEls:undefined,
    messagePs:undefined,
    hideFlash:false,
    hideSelects: false,
    showDuration:0.4,
    hideDuration:0.4,
    initialize: function(content, options) {
        this.paneId = Tablet.buster();
        var content = content || '';
        var url = '';

        var contentEl = $(content);
        if (!contentEl) {
            var url = content;
        }

        var options = Object.extend({
            containerId:'',
            onContentFailure:null
        }, options);
        this.options = options;
        // take different actions if pages is a url, single id, or array
        this.bod = document.getElementsByTagName('body')[0];

        // handling multiple boxes on one page...
        // or maybe putting it in the same as another page...
        this.overlay = $('overlay'+this.paneId) || TH.newElement(this.bod, 'div',
            {'id':'overlay'+this.paneId, 'class':'paneoverlay', 'style':'display:none'});
        this.container = TH.newElement(this.bod, 'div', {'id':options.containerId, 'class':'thbox',
            'style':'top:-9999px; position:absolute;display:block;'});
        this.container.className = 'thbox';

        this.pages = $A();

        this.pageReset = options.pageReset || this.pageReset;
        this.refreshContent = (typeof options.refreshContent !== 'undefined') ? options.refreshContent : this.refreshContent;
        this.defaultTop = options.defaultTop || this.defaultTop;
        this.onContent = options.onContent || null;
        this.onHide = options.onHide || null;
        this.setPageHeight = (options.setPageHeight != null) ? options.setPageHeight:true;

        if (contentEl) {
            contentEl = contentEl.remove();
            this.container.appendChild(contentEl);
            this.initContent();
        }

        else if (url !== '') {
            this.contentUrl = url;
        }
        else {
            this.disableBox();
        }

        this.hideFlash = Prototype.Browser.IE || Prototype.Browser.WebKit || Prototype.Browser.Opera;
        this.hideSelects = Prototype.Browser.IE && navigator.userAgent.match(/MSIE\s*6/);
    },
    getFlashObjects: function() {
        if (this.flashOs.length <= 0 ) {
            var objects = document.getElementsByTagName('object');
            var isFlash;
            for (var i=0;i<objects.length;i++) {
                isFlash = false;
                var type = objects[i].getAttributeNode('type');
                var codebase = objects[i].getAttributeNode('codebase');
                if (type) {
                    isFlash = type.value.indexOf('flash');
                }
                else if (codebase) {
                    isFlash = codebase.value.indexOf('swflas.cab');
                }
                if (isFlash) {
                    this.flashOs.push(objects[i]);
                }
            }
        }
        return this.flashOs;
    },
    getSelectElements: function() {
        if (typeof this.selectEls === 'undefined') {
            var box = this.container;
            this.selectEls = $$('select').select(function(el) {
                return !el.descendantOf(box);
            });
        }
        return this.selectEls;
    },
    getMessages: function() {
        if (typeof this.messagePs === 'undefined') {
            this.messagePs = this.container.select('p.message');
        }
        return this.messagePs;
    },
    hideFlashObjects: function() {
        var objects = this.getFlashObjects();
        for(var i=0;i<objects.length;i++) {
            if (Prototype.Browser.IE) {
                objects[i].style.visibility = 'hidden';
            }
            else {
                objects[i].style.marginLeft = '5000px';
            }
        }
    },
    hideSelectElements: function() {
        this.getSelectElements().invoke('setStyle', {visibility:'hidden'});
    },
    showFlashObjects: function() {
        var objects = this.getFlashObjects();
        for(var i=0;i<objects.length;i++) {
            if (Prototype.Browser.IE) {
                objects[i].style.visibility = 'visible';
            }
            else {
                objects[i].style.marginLeft = '';
            }
        }
    },
    showSelectElements: function() {
        this.getSelectElements().invoke('setStyle', {visibility:''});
    },
    onContentFailure: function(resp) {
        if (typeof this.options.onContentFailure === 'function') {
            this.options.onContentFailure(resp);
        }
        else {
            this.disableBox();
        }
    },
    disableBox: function() {
        this.enabled = false;
    },
    getContent: function(afterInit, id, options) {
        var afterInit = afterInit || function() {};
        var args = args || [];

        if (this.contentUrl) {
            // get content passed in here

            var successHandler = function (transport) {
                this.initContent(transport);
                afterInit.bind(this, id, options)();
            };
            var errorHandler = function(transport) {
                this.onContentFailure(transport);
            };
            new Ajax.Request(this.contentUrl, {
                    method: 'get',
                    onSuccess: successHandler.bind(this),
                    onFailure: errorHandler.bind(this)
                }
            );
        }
    },
    initContent: function(transport) {
        this.pages = [];
        // this.container.setStyle({'display':'block'});
        if (transport) {
            this.container.update(transport.responseText);
        }

        var initialHeight = this.container.getHeight();
        // set some initial styles on all the pages
        var boxHeight = 0, pg = null;
        var pgs = this.container.select('div.TBPage');
        // if there are no specific pages, just get immediate children of this.container
        if (pgs.size() <= 0) {
            pgs = this.container.childElements();
        }
        var pgHeight;
        pgs.each(function(pg) {
            pgHeight = 0; pgPadding = 0;
            pgHeight = this.getPageHeight(pg);
            if (pgHeight > boxHeight) {
                boxHeight = pgHeight;
            }
            pg.setStyle({display:'none', height: (this.setPageHeight) ? pgHeight+'px':''});
            this.pages.push(pg);
        }, this);
        this.pageHeight = boxHeight;

        this.attachLinks();

        if (!this.container.down('div.THBoxHeader')) {
            var header = new Element('div', {'class':'THBoxHeader'});
            header.className = 'THBoxHeader';
            Element.insert(this.container, {'top':header});
        }

        // this.container.setStyle({display:'none'});

        if (this.onContent) { this.onContent.bind(this)(); };

        this.ready = true;
        this.enabled = true;

        if (this.pages.size() > 0) {
            this.currentPage = 0;
        }
    },
    attachLinks: function(page) {
        var container = page || this.container;
        // hook up any p.close links
        var closers = container.select('a.thaction[rel=close]');
        for(var i=0;i<closers.length;i++) {
            Event.observe(closers[i], 'click', this.hide.bindAsEventListener(this));
        }

        // hook up other page related links
        container.select('a.thaction[rel|=page]').each(function(link) {
            Event.observe(link, 'click', this.pageClick.bindAsEventListener(this));
        }, this);
    },
    setContent: function(transport) {
        this.initContent(transport);
    },
    setPageContent: function(pg, content) {
        var pg = pg || 0;
        var pgId = '', pgIndex = -1;
        var content = content || '';
        if (typeof pg === 'string') {
            pgId = pg;
            pg = this.pages.find(function(p) {
                return p.id == pgId;
            });
            if (!pg) {
                pg = TH.newElement(this.container, 'div', {id:pgId});
                this.pages.push(pg);
            }
            if (pg) {
                pgIndex = this.pages.indexOf(pg);
                var oldPg = pg;
                var pgStyle = oldPg.getAttribute('style');
                // insert completely new element with content
                this.container.insert({bottom:content});
                oldPg.remove();
                var pg = this.container.down('#' + pgId);
                pg.setAttribute('style', pgStyle);
                this.pages[pgIndex] = pg;
            }

        }

        this.attachLinks();
        this.enabled = true;
    },
    getPageHeight: function(pg) {
        var pgHeight = 0;
        var pgPadding = 0;
        if (pg.getStyle('paddingTop')) {
            pgPadding += parseInt(pg.getStyle('paddingTop').replace('px', ''));
        }
        if (pg.getStyle('paddingBottom')) {
            pgPadding += parseInt(pg.getStyle('paddingBottom').replace('px', ''));
        }
        return (pg.offsetHeight - pgPadding);
    },
    pageClick: function(e) {
        var rel = e.element().getAttribute('rel');
        if (rel.startsWith('page-')) {
            var pg = rel.replace('page-', '');
            this.page(pg);
        }
        Event.stop(e);
    },
    page: function(id, options) {
        if (!this.ready && this.contentUrl) {
            this.getContent(this.page, id, options);
            return;
        }

        var options = options || {};
        options.onShow = options.onShow || null;
        options.effect = options.effect || 'slide';
        options.duration = options.duration || 1;
        options.values = options.values || {};
        options.adjustHeight = (typeof options.adjustHeight !== 'undefined') ? options.adjustHeight : true;
        options.message = options.message || '';

        var newPage = this.pages.indexOf($(id));
        if (newPage < 0)   newPage=0;

        var oldPage = this.currentPage;
        this.currentPage = newPage;

        var inView = this.container.positionedOffset()[1] > 0;

        if (options.message.length > 0) {
            var msg = this.pages[newPage].down('p.message');
            if (msg) {
                msg.update(options.message);
                msg.setStyle({display:'block'});
                if (this.setPageHeight) {
                    if (this.pages[newPage].getStyle('display') === 'none') {
                        this.pages[newPage].setStyle({display:'block', height:''});
                    }
                    var pgHeight = this.getPageHeight(this.pages[newPage]);
                    this.pages[newPage].setStyle({height:pgHeight+'px', display:'none'});
                }
            }
        }
        if (!inView) {
            this.show();
            this.onPageShow(options);
        } else {
            // slide to new page if we need to

            if (this.currentPage == oldPage) return;

            var curTop = this.pages[oldPage].offsetTop;
            var curPagePaddingTop = this.pages[oldPage].getStyle('paddingTop').replace('px', '');
            var curPagePaddingBottom = this.pages[oldPage].getStyle('paddingBottom').replace('px', '');
            var curHeight = this.pages[oldPage].getHeight();
            var curContainerHeight = curHeight + (parseInt(curPagePaddingBottom) || 0) + (parseInt(curPagePaddingTop) || 0);
            var curWidth = this.pages[oldPage].offsetWidth;
            
            this.pages[this.currentPage].setStyle({left:'-9000px', display:'block', position:'absolute', height:''});
            var newPagePaddingTop = this.pages[this.currentPage].getStyle('paddingTop').replace('px', '');
            var newPagePaddingBottom = this.pages[this.currentPage].getStyle('paddingBottom').replace('px', '');
            var newHeight = this.pages[this.currentPage].getHeight();
            var newContainerHeight = newHeight + (parseInt(newPagePaddingBottom) || 0) + (parseInt(newPagePaddingTop) || 0);
            this.pages[this.currentPage].setStyle({left:'', display:'none', position:''});
            
            var container = this.container;
            this.pages[oldPage].setStyle({position:'absolute',top:curTop+'px'});
            var shim = TH.newElement(this.container, 'div', {
                'id':'shim',
                'style':'position:absolute;height:' + curContainerHeight + 'px;width:'+ curWidth +'px;top:' + curTop + 'px;background:#FFF;'
            });
            container.setStyle({height:curTop+curContainerHeight+'px'});

            if (options.adjustHeight) {
                var morphLength = 0.6;
                new Effect.Morph(shim, {
                    style:{'height':(newContainerHeight)+'px'},
                    duration:morphLength
                });
                new Effect.Morph(container, {
                    style:{'height':(curTop+newContainerHeight)+'px'},
                    duration:morphLength
                });
            }

            if (options.effect == 'slide') {

                // if current page is right of current, slide both left
                if (this.currentPage > oldPage) {
                    new Effect.Move(this.pages[oldPage], { x: -curWidth, mode: 'relative', afterFinish:function(e) {
                            e.element.setStyle({position:'',top:''});
                            e.element.hide();
                        }
                    });
                    // the 15 fixes something that happens in the slide that pushes the header to the left and skews everything up
                    this.pages[this.currentPage].setStyle({left:(curWidth-15) + 'px', display:'block'});
                    new Effect.Move(this.pages[this.currentPage], { x:0, mode:'absolute', afterFinish:function(e) {
                            shim.remove();
                            container.setStyle({height:''});
                            this.onPageShow(options);
                        }.bind(this)
                    });
                }
                // if current page is left of current, slide both right
                else if (this.currentPage < oldPage) {
                    new Effect.Move(this.pages[oldPage], { x: curWidth-5, mode: 'relative', afterFinish:function(e) {
                            e.element.setStyle({position:'',top:''});
                            e.element.hide();
                        }
                    });
                    this.pages[this.currentPage].setStyle({left:'-' + curWidth + 'px', display:'block'});
                    new Effect.Move(this.pages[this.currentPage], { x:0, mode:'absolute', afterFinish:function(e) {
                            shim.remove();
                            container.setStyle({height:''});
                            this.onPageShow(options);
                        }.bind(this)
                    });
                }
            }
            else if (options.effect == 'replace') {
                Effect.Fade(this.pages[oldPage], { from: 1, to: 0.0, duration:options.duration });
                this.pages[this.currentPage].setStyle({height:newHeight + 'px'});
                Effect.Appear(this.pages[this.currentPage], {from:0, to:1, duration: options.duration, afterFinish:function(e) {
                        shim.remove();
                        container.setStyle({height:''});
                        this.onPageShow(options);
                    }.bind(this)
                });
            }
        }
    },
    onPageShow: function(options) {
        var defaultNpt;
        if (this.pageReset) {
            // should do this when hiding a form only
            // this.clearForm();
            this.setDefaultFocus();
        }

        // if we got any values, set the ones we find
        for (npt in options.values) {
            this.container.select('input[name='+npt+']').each(function(el) {
                el.value = options.values[npt] || '';
            });
        }
        if (typeof options.onShow == 'function') {
            options.onShow();
        }
    },
    setFormAction: function(action) {
        var frm = this.container.down('form');
        if (frm) {
            frm.setAttribute('action', action);
        }
    },
    clearForm: function() {
        // clear inputs
        this.container.select('div.field input[type=text]').invoke('clear');
        this.container.select('div.field textarea').invoke('clear');
        this.container.select('div.field select').invoke('clear');
        this.container.select('div.field p.error').invoke('remove');
        this.container.select('.generalerror').invoke('remove');
        this.container.select('div.field').invoke('removeClassName', 'error');
    },
    setDefaultFocus: function() {
        var currentPage = this.pages[this.currentPage];
        var defaultNpt = currentPage.down('input[type="text"]');
        if (defaultNpt) {
            try {
                defaultNpt.focus();
            }
            catch(ex) {
                // meh, you're using a stupid browser. suck it.
            }
        }
    },
    show: function() {
        var currentPage = this.pages[this.currentPage];
        if (!this.ready && this.contentUrl) {
            this.getContent(this.show);
            return;
        }
        if (!this.enabled || !this.pages[this.currentPage]) return;

        if (this.container.getStyle('marginLeft') == '0px' || !this.container.getStyle('marginLeft')) {
            this.container.setStyle({'marginLeft':'-'+(this.container.offsetWidth/2)+'px', 'left':'50%'});
        }
        this.pages.invoke('hide');

        // show it at the top of the viewport, not the page, with absolute position

        var topOffset = document.viewport.getScrollOffsets()['top'];
        var boxTop = topOffset + this.defaultTop;

        var pane = this;
        this.container.setStyle({top:boxTop+'px', display:'none'});
        new Effect.Appear(this.container, { duration:this.showDuration,
            afterFinish:function(e) {
                pane.setDefaultFocus();
            }
        });

        // I know, this is weird. but it gets around behavior that was jumping the
        // view to the top of the page when this function was run. ticket 4826.
        setTimeout(function() { currentPage.setStyle({display:'block'}); }, 100);

        if (Prototype.Browser.IE) {
            var bod = document.getElementsByTagName('body')[0];
            var bodyHeight = bod.getHeight();
            var viewportHeight = document.viewport.getHeight();
            this.overlay.setStyle({height:((bodyHeight > viewportHeight) ? bodyHeight : viewportHeight) + 'px', position:'absolute'});
        }

        Effect.Appear(this.overlay, { from: 0.0, to: 0.5, duration:(this.showDuration) });
        if (this.hideFlash) setTimeout(this.hideFlashObjects.bind(this), 400);
        if (this.hideSelects) {
            window.setTimeout(this.hideSelectElements.bind(this), 400);
        }
    },
    resetPages : function() {
        this.pages.each(function(pg) {
            pg.setStyle({position:'', left:'', display:'', top:''});
        });
    },
    hideMessages : function() {
        var messages = this.getMessages();
        messages.each(function(el) {
            el.setStyle({display:'none'});
            el.update('');
        });
    },
    hide: function(e) {
        try {
            new Effect.Fade(this.container, {duration:this.hideDuration,
                afterFinish:function(e) {
                    e.element.setStyle({top:'-9999px'});
                    e.element.show(); // it needs to be display:block to get the height of the content pages
                }});
            Effect.Fade(this.overlay, { from: 0.5, to: 0.0, duration:(this.hideDuration) });

            if (this.hideFlash) setTimeout(this.showFlashObjects.bind(this), 400);
            if (this.hideSelects) {
                window.setTimeout(this.showSelectElements.bind(this), 400);
            }
            setTimeout(this.resetPages.bind(this), 700);
            setTimeout(this.hideMessages.bind(this), 800);
        }
        catch (ex) {
            Tablet.debug(ex);
        }
        if (e) {
            Event.stop(e);
        }
        if (this.pageReset) {
            this.clearForm();
        }

        if (typeof this.onHide == 'function') {
            this.onHide();
        }
    }
};

// is there a global callback handler that finds the right form and executes something?

Tablet.FormActions = (function() {
    var inplaceForms = $H();
    var maneuvers = $H();

    return {
        registerForm:function(token, form) {
            inplaceForms.set(token, form);
        },
        callback:function(token, maneuver, resp) {
            var iform = inplaceForms.get(token);
            if (iform) {
                iform.callback(maneuver, resp);
            }
        },
        tests:{
            required: {
                test: function(v, h) {
                    return  !((v == null) || (v.length == 0) || (h && v.strip() === h.strip()));
                }
            }, // || /^\s+$/.test(v));
            emailaddress : {
                test: function(v) { return (/\w{1,}[@][\w\-]{1,}([.]([\w\-]{1,})){1,3}$/).test(v); }
            },
            password: {
                test: function(v) { return true; }
            }
        }
    };
})();
// a form that should submit to iframe, execute callback when done
// callback might change, based on what initiated the form, mostly in the case of the login form
// there is a default maneuver that should happen on every login, and possibly others
// a maneuver being something that happens on successful submission of the attached form
Tablet.InplaceForm = function(frm, options) {
    this.init.apply(this, arguments);
};
Tablet.InplaceForm.prototype = {
    frm:null,
    token:null,
    maneuvers:null,
    allKey:'all',
    successActions:null,
    failureActions:null,
    init:function(frm, options) {
        this.options = Object.extend({
            rules:{},
            phrases:{}
        }, options);

        this.frm = $(frm);
        if (!this.frm) return null;
        this.token = this.frm.down('input[name=token]');
        if (!this.token) return null;

        this.successActions = $H();
        this.failureActions = $H();

        if (Tablet.FormActions) {
            Tablet.FormActions.registerForm(this.token.value, this);
        }

        // seems most likely to be a problem of this stuff not working
        // so the form's target is just the window
        // can we determine the server side stuff on the server?
        // create this form there?
        // why didn't we do that in the first place?
        if (typeof Tablet.INPLACE_LOGIN !== 'undefined' ? Tablet.INPLACE_LOGIN : true) {
            // create iframe that will accept the post
            var trgt = new Element('iframe', {
                'class':'postTarget',
                'style':'display:none',
                'name':this.token.value
            });
            this.frm.appendChild(trgt);
            // set target of form to that iframe
            this.frm.setAttribute('target', this.token.value);
        }

        this.frm.observe('submit', function(e) {
            if (this.validate()) {
                return true;
            }
            else {
                Event.stop(e);
            }
        }.bind(this));
    },
    callback:function(maneuver, resp) {
        var maneuver = maneuver || '';
        if (resp.errors) {
            this.showErrors(resp.errors);
            var maneuverActions = this.failureActions.select(function(action) {
                return action.key === maneuver;
            });
            maneuverActions.each(function(action) {
                action.value[0].apply(null, (action.value[1] && action.value[1].length >0) ? action.value[1]:[resp]);
            });
            var allActions = this.failureActions.select(function(action) {
                return action.key == this.allKey;
            }, this);
            allActions.each(function(action) {
                action.value[0].apply(null, (action.value[1] && action.value[1].length > 0) ? action.value[1]:[resp]);
            });
        }
        else if (resp.success) {
            // execute maneuvers first, it might take page away
            var maneuverActions = this.successActions.select(function(action) {
                return action.key === maneuver;
            });
            maneuverActions.each(function(action) {
                action.value[0].apply(null, (action.value[1] && action.value[1].length > 0) ? action.value[1] : [resp.success]);
            });
            var allActions = this.successActions.select(function(action) {
                return action.key === this.allKey;
            }, this);
            allActions.each(function(action) {
                // use args if they were passed, otherwise the response data
                action.value[0].apply(null, (action.value[1] && action.value[1].length > 0) ? action.value[1] : [resp.success]);
            });
        }
    },
    attachFailure: function(maneuver, action, args) {
        maneuver = maneuver || 'all';
        this.failureActions.set(maneuver, [action, args]);
    },
    attachSuccess: function(maneuver, action, args) {
        maneuver = maneuver || 'all';
        this.successActions.set(maneuver, [action, args]);
    },
    maneuver: function() {
        var npt = this.frm.down('input[name=maneuver]');
        if (!npt) {
            npt = new Element('input', {
                name:'maneuver'
            });
            this.frm.down('div.submit').appendChild(npt);
        }
        return npt;
    },
    setManeuver: function(maneuver, successActions, failureActions) {
        var npt = this.maneuver();
        npt.value = maneuver;
        if (successActions && successActions['action']) {
            this.attachSuccess(maneuver, successActions['action'], successActions['args']);
        }
    },
    validate: function() {
        var errors = {}, hasError = false;

        var fields = this.frm.elements;
        var field, fieldRule, components;

        for (rule in this.options.rules) {
            field = this.frm.elements[rule];
            fieldRules = this.options.rules[rule];
            for (var i=0;i<fieldRules.length;i++) {
                fieldRule = fieldRules[i];
                components = /(\w*)(\[(\w*).(\w*)=?(.*)?\])?/.exec(fieldRule);
                if (components && components[1] && this.options.phrases[components[1]]) { // if we don't have an error phrase for the field, let the server get it.
                    fieldRule = components[1];
                    // we don't have a dependent form value
                    if (typeof components[2] !== 'undefined' && components[3] && components[4]) {
                        depField = components[3];
                        depAttr = components[4];
                        // components[5] would have a value, but that's hard and we don't need it now
                        // for now, if the attr is true, we care
                        if (fields[depField] && fields[depField] && !fields[depField][depAttr]) {
                            break;
                        }
                    }
                    // testing field against test for rule
                    if (!Tablet.FormActions.tests[fieldRule].test(field.value, field.getAttribute('hint'))) {
                        hasError = true;
                        if (!errors[field.name]) {
                            errors[field.name] = this.options.phrases[fieldRule];
                            // break for loop for fieldRules
                            break;
                        }
                    }
                }
            }
        }

        if (hasError) {
            this.showErrors(errors);
        }
        else {
            this.clearErrors();
        }

        return ! hasError;
    },
    showErrors: function(errors) {
        var errors = errors || null;
        var npt = null, errorP;

        if (!errors) return;
        var npt, field, errorP;

        var hasError, ps;
        this.frm.select('div.field').each(function(field) {
            npts = field.descendants().findAll(
                function(el) {
                    return ['select', 'input'].indexOf(el.tagName.toLowerCase()) >= 0;
                }
            );
            hasError = false;
            ps = [];
            npts.each(function(npt) {
                errorP = field.down('p.'+npt.name+'error') || TH.newElement(field, 'p', {'class':'error '+npt.name+'error'});
                if (errors[npt.name]) {
                    hasError = true;
                    errorP.setStyle({top:(npt.positionedOffset()[1]+2)+'px'});
                    errorP.update(errors[npt.name]);
                    errorP.removeClassName('ok');
                    ps.push(errorP);
                } else {
                    errorP.update('');
                    errorP.addClassName('ok');
                }
            });
            if (hasError) {
                // 
                Tablet.preloadImages(['/img/guides/TGRightArrow.gif', '/img/guides/TGRightArrow.gif'])
                ps.invoke('setStyle', {display:'none'});
                field.addClassName('error');
                Tablet.UI.fadeIn(ps);
            }
            else {
                ps.invoke('setStyle', {display:'none'});
                field.removeClassName('error');
                Tablet.UI.fadeOut(ps);
            }
        });
        
        npt = null;
        field = null;
        errorP = null;
    },
    clearErrors: function() {
        this.frm.select('div.error').each(function(el) {
            el.removeClassName('error');
            el.select('p.error').invoke('update', '');
            el.select('p.error').invoke('addClassName', 'ok');
        });
    }
};
var Tablet = Tablet || {};
Tablet.HoverElement = function(id, options) {
    var el = $(id);
    options = Object.extend({
        hover:'Hover',
        active:'Active',
        hasActive:false,
        cssHover:false,
        hasImage:true
    }, options);
    var img, imgParent;
    
    var primeImage = function(src, klass) {
        klass = klass || '';
        var newi = new Element('img', {'src':src, 'class':klass});
        if (klass) {
            imgParent.insertBefore(newi, img.nextSibling);
            // imgParent.appendChild(newi);
            newi.className=klass;
        }
    };
    
    // we can put the 'hoverelement' class on an element containing an image that should change
    if (options.hasImage) {
        if (el.hasAttribute('src')) {
            img = el;
        }
        else {
            el.addClassName('hoverparent');
            img = el.down('img');
        }
    }
    
    if (!el) {
        return;
    }
    
    if (img) {
        imgParent = img.parentNode;
        
        var normalSrc = img.src;
        var ext = 'gif'; // gif default extension
        var m = normalSrc.match(/.*[\/\\][^\/\\]+\.(\w+)$/);
        if (m) {
            ext = m[1];
        }
        if (normalSrc.toLowerCase().indexOf(options.active.toLowerCase() + '.' + ext) > 0) {
            normalSrc = normalSrc.replace(new RegExp(options.active, "i"), '');
        }

        var hoverSrc = normalSrc.replace('.'+ext, options.hover+'.'+ext);
        var activeSrc = normalSrc.replace('.'+ext, options.active+'.'+ext);

        if (options.hasActive && !el.hasClassName(options.active.toLowerCase())) {
            primeImage(activeSrc, (options.cssHover) ? 'active' : '');
        }
        primeImage(hoverSrc, (options.cssHover) ? 'hover' : '');
        if (!options.cssHover && !options.hasActive && el.hasClassName(options.active.toLowerCase())) {
            img.src = hoverSrc;
        }
        
        if (!options.cssHover && options.hasActive && el.hasClassName(options.active.toLowerCase())) {
            img.src = activeSrc;
        }
    } 
    Event.observe(el, 'mouseover', function() {
        el.addClassName('hover');
        if (img && !options.cssHover && (!options.hasActive || (options.hasActive && !el.hasClassName(options.active.toLowerCase())))) {
            img.src = hoverSrc;
        }
    });
    Event.observe(el, 'mouseout', function(event) {
        var target = (Prototype.Browser.IE) ? event.toElement:event.relatedTarget;
        if (!target || (!$(target).descendantOf(el) && el != target)) {
            el.removeClassName('hover');
            if (img && !options.cssHover && !el.hasClassName(options.active.toLowerCase())) {
                img.src = normalSrc;
            }
        }
    });
    
    // ugg, not liking this much anymore
    var activate = function() {
        el.addClassName(options.active.toLowerCase());
        if (img && !options.cssHover) {
            img.src = (options.hasActive) ? activeSrc : hoverSrc;
        }
    };
    var deactivate = function() {
        el.removeClassName(options.active.toLowerCase());
        if (img && !options.cssHover) {
            img.src = normalSrc;
        }
    };
    
    el.activate = function() {
        activate();
    };
    el.deactivate = function() {
        deactivate();
    };
    
    return el;
};

var Tablet = Tablet || {};
Tablet.TextInput = function(id, options) {
    var el, nameHint, autoComplete;
    options = Object.extend({
        choices:[],
        url:'',
        completer:'namecomplete',
        onItemSelect:null,
        onFocus: function() {},
        callback:null,
        select:false,
        completed:'',
        onInputBlur:null,
        onInputSet:null,
        onInputClear:null,
        onCompletedChange:null,
        updateElement:null,
        dependents:null,
        autocompleter:null
    }, options);

    var choices = options.choices;
    var url = options.url;
    var completer = options.completer;
    var onItemSelect = options.onItemSelect;
    var onFocus = options.onFocus;
    var callbackEx = options.callback;
    var selectOnFocus = options.select;
    var completed = (options.completed !== '') ? $(options.completed) : null;
    var onInputBlur = options.onInputBlur;
    var onInputClear = options.onInputClear;
    var updateElement = options.updateElement;
    var onCompletedChange = options.onCompletedChange;

    var val = null;

    el = $(id);
    if (!el) return null;
    
    nameHint = (el.getAttribute('hint')) ? el.getAttribute('hint') : '';
    if (el.value === '' && nameHint) {
        el.value = nameHint;
    }
    else if (el.value !== nameHint) {
        el.addClassName('set');
    };

    Event.observe(el, 'focus', function(e) {
        if (selectOnFocus && this.value !== nameHint && this.value !== '') {
            this.select();
        } else {
            if (selectOnFocus) {
                if (this.setSelectionRange) {
                    var len = this.value.length;
                    this.setSelectionRange(0,len);
                } else if (this.createTextRange) { // for IE
                    var range = this.createTextRange();
                    range.collapse(true);
                }
            } else if (this.value == nameHint && nameHint !== '') {
                this.value = '';
            }
        }
        onFocus(this);
    });

    Event.observe(el, 'keyup', function(e) {
        if (this.value === nameHint || this.value === '') { // or what the value searched for filled in by default?
            // this.clear();
            this.removeClassName('set');
            if (typeof onInputClear === 'function') {
                onInputClear.bind(this)();
            }
        }
        else {
            if (!this.hasClassName('set')) {
                this.addClassName('set');
            }
            if (this.value !== '' && typeof options.onInputSet === 'function') {
                options.onInputSet.bind(this)();
            }
        }
    });

    if (completed && completed.hasAttribute('value')) {
        Event.observe(el, 'keyup', function(e) {
            if (val !== this.value && (e.keyCode !== Event.KEY_RETURN)) {
                completed.value = '';
                val = this.value;
                if (typeof onCompletedChange === 'function') {
                    onCompletedChange(this);
                }
            }
        });
    }

    Event.observe(el, 'blur', function(e) {
        if (this.value === '') {
            this.removeClassName('set');
            this.value = nameHint;
        }
        if (typeof onInputBlur === 'function') {
            onInputBlur(this);
        }
    });
    el['clear'] = el['clear'].wrap(function(proceed) {
        this.removeClassName('set');
        if (nameHint) {
            this.value = nameHint;
        }
        else {
            proceed();
        }
        this.value = nameHint;
    });
    el['reset'] = function() {
        this.clear();
    };
    if (choices.length > 0) {
        new Autocompleter.Local(
            el,
            completer,
            choices,
            {
                choices: choices.length,
                fullSearch: true,
                afterUpdateElement : function (input, li) {
                    input.addClassName('set');
                    if (completed && completed.hasAttribute('value')) {
                        completed.value = li.id;
                    }
                    if (typeof onItemSelect === 'function') {
                        onItemSelect(input, li);
                    }
                },
                onShow: (typeof showAutocomplete !== 'undefined') ? showAutocomplete : null,
                updateElement: updateElement || null
            }
        );
    }

    if (url !== '') {
        new Ajax.Autocompleter(
            el,
            completer,
            url,
            {
                minChars:3,
                method:'get',
                callback : function(npt) {
                    return 'q=' + npt.value + ((typeof callbackEx === 'function') ? callbackEx() : '');
                },
                afterUpdateElement : function (input, li) {
                    input.addClassName('set');
                    if (completed && (typeof completed.value !== 'undefined')) {
                        completed.value = li.id;
                    }
                    if (typeof onItemSelect === 'function') {
                        onItemSelect(input, li);
                    }

                    if (options.dependents) {
                        var newValue = Element.collectTextNodesIgnoreClass(li, 'informal');
                        var dependent;
                        for (var i=0;i<options.dependents.length;i++) {
                            dependent = options.dependents[i];
                            if (li.hasAttribute(dependent['attribute']) && li.getAttribute(dependent['attribute'])) {
                                dependent['setter'](li, dependent['input'], li.getAttribute(dependent['attribute']));
                            }
                            else {
                                dependent['input'].value = '';
                            }
                        }
                    }
                },
                onShow: (typeof showAutocomplete !== 'undefined') ? showAutocomplete : null,
                updateElement: updateElement || null
            }
        );
    }
    return el;
};
Tablet.Search = Tablet.Search || {};
Tablet.Search.TextInput = Tablet.TextInput;

/* Tablet "Monkey patched" stuff on Autocompleter so it works better with Tablet.TextInput */
var Autocompleter = Autocompleter || false;
if (Autocompleter && Autocompleter.Base) {
    Autocompleter.Base.prototype.getToken = function() {
        var bounds = this.getTokenBounds();
        var val = this.element.value.substring(bounds[0], bounds[1]).strip();
        if (this.element.hasAttribute('hint') && val == this.element.getAttribute('hint')) {
            val = '';
        }
        return val;
    };
}

/* *****
NOTICE: this file gets combined for the live site. any changes here should also be made to the
combined file by running bin/minify.py. The combined file should be committed to Subversion
along with updates to this file.
***** */

var TH = TH || {};
var Tablet = Tablet || {};

// this is mostly to set behaviors
Tablet.SelectBox = Class.create({
    options:{},
    index:-1,
    _listCount:null,
    subSelect:null,
    parentSelect:null,
    onItemSelect:null,
    active:true,
    subLists:null,
    field:null,
    list:null,
    label:null,
    anchor:null,
    npts:[],
    initialize:function(id, options) {
        this.options = Object.extend({
            npts:[],
            nptId:null,
            active:this.active,
            hideInactive:false,
            onItemSelect:null,
            stopAfterSelect: true,
            overwriteClickHandlers: true,
            requireLabelMatch:true,
            updateSubselect:true,
            listSourceUrl:'',
            colonize:undefined
        }, options || {});

        this.container = $(id);

        if (!this.container) {
            return null;
        }
        this.field = this.container.up('div.field');
        this.list = this.container.down('.selectlist');
        this.label = this.container.down('p');

        // the key handling isn't ready yet
        Event.observe(this.container, 'keydown', this.onKeyPress.bindAsEventListener(this));

        this.npt = $(this.options.nptId) || this.container.down('input[type=hidden]');
        if (this.options.npts) {
            this.setNpts(this.options.npts);
        }

        this.active = this.options.active;

        this.attachItemClicks();

        // set container click action
        this.anchor = Tablet.getElementByTagName(this.label, 'a');
        // needs to be after the this.anchor stuff because it checks for the presences of that.
        this.setInitialLabel();
        Event.observe(this.label, 'click',
            this.onSelectClick.bindAsEventListener(this));

        if (this.anchor) {
            // Event.observe(this.anchor, 'click', function(e) {}); //this.onSelectClick.bindAsEventListener(this)
            // keyup works better than focus, we only want to do this when it was a tab
            Event.observe(this.anchor, 'keyup', this.onSelectFocus.bindAsEventListener(this));
            Event.observe(this.anchor, 'keydown', this.onSelectBlur.bindAsEventListener(this));
        }

        // if there is any text in here, but only if there isn't an inner anchor
        if (this.label && !this.anchor && !this.label.hasAttribute('hint') && this.label.innerHTML !== '') {
            this.label.setAttribute('hint', this.label.innerHTML);
        }

        if (!this.active) {
            this.deactivate();
        }

        this.columns = this.list.select('ul.col');
        this.columnHeight = 0;

        // this is set up for Tablet.MultiSelect to be able to clear out the SelectBox
        this.container.setValue = this.setValue.bind(this);

        if (typeof this.options.colonize !== 'undefined' && this.options.colonize) {
            this.columnizeList();
        }

    },
    setOptions:function(options) {
        this.options = Object.extend(this.options, options || {});
        if ((options || {})['npts']) {
            this.setNpts(this.options['npts']);
        }
    },
    setNpts:function(npts) {
        // probably good to reset this every time
        this.npts = [];
        for (var i=0;i<npts.length;i++) {
            if ($(npts[i])) {
                this.npts.push($(npts[i]));
            }
        }
    },
    activate:function() {
        this.active = true;
        if (this.container) {
            this.container.removeClassName('inactive');
        }
        if (this.options.hideInactive && this.field) {
            this.field.removeClassName('inactive');
        }
    },
    deactivate:function() {
        this.active = false;
        if (this.container) {
            this.container.addClassName('inactive');
        }
        if (this.options.hideInactive && this.field) {
            this.field.addClassName('inactive');
        }
    },
    setInitialLabel:function() {
        // check input for value, set label if found
        if (this.npt && this.npt.value !== '') {
            var selectedItem = this.list.down('*[id="' + this.npt.value + '"]');
            if (selectedItem) {
                this.setLabel(selectedItem);
            }
            else if (!this.requireLabelMatch) {
                this.setLabel(this.npt.value);
            }
        } else {
            this.setLabel('');
        }
    },
    currentItem:function() {
        var item = null;
        if (this.npt && this.npt.value !== '') {
            item = $A(this.list.getElementsByTagName('a')).find(function(link) {
                return link.id == this.npt.value;
            }, this);
        }
        return item;
    },
    setSubSelect:function(el) {
        this.subSelect = el;
        el.parentSelect = this;
        this.updateSubSelect(false);
    },
    getSubLists:function(reset) {
        reset = (typeof reset !== 'undefined') ? reset : false;
        if (this.subLists === null || reset) {
            // this filter doesn't work. we only want the root sublist
            // will any real sublists always have an id?
            this.subLists = $A(this.container.getElementsByTagName('ul')).filter(function(el) {
                return (el.id !== '');
            });
        }
        return this.subLists;
    },
    getCurrentSubList:function() {
        var current = this.subLists.find(function(list) {
            return list.visible();
        });
        return current;
    },
    onNewList:function(resp) {
        // need to assume that this is the selectbox object
        if (resp && resp.responseText) {
            this.subSelect.appendList(resp.responseText);
            this.subSelect.getSubLists(true);
            this.subSelect.attachItemClicks();
            this.subSelect.setInitialLabel();
            // this.updateSubSelect();
            this.subSelect.activate();
        }
        else {
            this.subSelect.deactivate();
        }
        this.subSelect.container.removeClassName('loading');
    },
    appendList:function(html) {
        var listContainer;
        if (typeof this.options.colonize !== 'undefined' &&
                this.options.colonize.inside) {
            listContainer = this.options.colonize.inside;
        }
        else {
            listContainer = this.list;
        }
        listContainer.insert(html);
        if (typeof this.options.colonize !== 'undefined') {
            // get last list because that's the one that was just appended
            this.columnizeList(listContainer.lastChild);
        }
    },
    updateSubSelect:function(reset) {
        var subLists = this.subSelect.getSubLists();
        var subList = null;

        if (this.npt && this.npt.value !== '') {
            subList = subLists.find(function(list) {
                return list.id.endsWith(this.npt.value);
            }, this);
        }

        if (subList) {
            this.subSelect.activate();
        }
        else {
            if (this.npt.value !== '' && typeof this.options.getNewList === 'function') {
                this.options.getNewList.bind(this)();
            }
            else {
                this.subSelect.deactivate();
            }
        }

        // another potential long executing loop
        setTimeout(function() {
            var count = subLists.length;
            var el;
            for (var i=count-1;i>=0;i--) {
                $(subLists[i]).setStyle({display:'none'});
            }

            // put in here because otherwise the code in the timeout overwrites with none
            if (subList) {
                subList.setStyle({display:'block'});
            }
        }, 500);

        if (reset && this.options.updateSubselect) {
            this.subSelect.setValue('', false);
        }
    },
    attachItemClicks:function(container) {
        var closeps = $A(this.list.getElementsByClassName('close'));
        closeps.each(function(el) {
            var link = el.down('a');
            if (link) {
                Event.stopObserving(link, 'click');
                Event.observe(link, 'click', function(e) {
                    Event.stop(e);
                    this.toggleList();
                }.bind(this));
            }
        }, this);
        var items = $A(this.list.getElementsByTagName('li'));

        // this can be quite a few links to iterate, do in Timeout to not block execution
        setTimeout(function() {
            for (var i=items.length-1;i>=0;i--) {
                // if (this.options.overwriteClickHandlers) {
                //     Event.stopObserving(items[i], 'click');
                // }
                if (!$(items[i]).hasClassName('col')) {
                    var link = Tablet.getElementByTagName(items[i], 'a');
                    if (!link) {
                        link = $(items[i]);
                    }
                    if (link) {
                        Event.observe(link, 'click', this.onItemClick.bindAsEventListener(this));
                    }
                }
            }
        }.bind(this), 100);
    },
    listCount:function() {
        // TODO: should accomodate current sublist if necessary
        if (this._listCount === null) {
            this._listCount = this.list.getElementsByTagName('li').length;
        }
        return this._listCount;
    },
    getItem:function(index) {
        // TODO : should accomodate sublists
        return this.list.getElementsByTagName('li')[index];
    },
    getValue:function() {
        return this.npt.value;
    },
    setValue:function(item, setNpts, resetSubSelect) {
        setNpts = (typeof setNpts !== 'undefined') ? setNpts : true;
        resetSubSelect = (typeof resetSubSelect !== 'undefined') ? resetSubSelect : true;
        if (this.npt === null) return;
        var val;
        if (typeof item == 'string' || typeof item == 'number') {
            val = item;
        } else {
            val = item.id;
        }
        this.npt.value = val;
        this.setInitialLabel();

        // need to prevent this when sub-selects are being set
        // except that, when we set this to 'all', we want to set it to the value of a parent, if present
        // is it reasonable to always do that, or should a hook / callback be added for this specific case?
        if (setNpts) {
            this.npts.each(function(npt) {
                npt.value = val;
            });
        }
        if (this.subSelect) {
            this.updateSubSelect(resetSubSelect);
        }
    },
    hasValue:function(val) {
        val = val || '';
        var items = this.list.select('*[id=' + val + ']');
        return (items && items.length > 0) ? items[0] : false;
    },
    getLabel:function(item) {
        var el = this.container;
        return el.down('p').innerHTML;
    },
    setLabel:function(item) {
        var hintAttr = 'hint';
        var optionalAttr = 'optional';
        var labelAttr = 'floobydoo';
        var text;
        if (typeof item == 'string') {
            text = item;
        } else {
            if (item.hasAttribute(labelAttr)) {
                text = item.getAttribute(labelAttr);
            }
            else {
                text = item.innerHTML;
            }
        }
        var el = this.container;
        var currentText;
        if (this.anchor) {
            currentText = el.down('p > a') || TH.newElement(TH.newElement(el, 'p'), 'a');
        }
        else {
            currentText = el.down('p') || TH.newElement(el, 'p');
        }
        if (text !== '') {
            if (currentText) {
                currentText.update(text);
                el.addClassName('set');
            }
        } else if (currentText) {
            if (currentText.hasAttribute(hintAttr)) {
                currentText.update(currentText.getAttribute(hintAttr) + ((currentText.hasAttribute(optionalAttr)) ? ' <span class="optional">('+currentText.getAttribute(optionalAttr)+')</span>':''));
            }
            else {
                currentText.update(text);
            }
            el.removeClassName('set');
        }
    },
    onSelectClick:function(e) {
        Event.stop(e);
        if (this.active) {
            this.toggleList();
            // Event.stop(e);
        }
    },
    onSelectFocus:function(e) {
        /* in Firefox, if the list scrolls, tabbing off the anchor will hit the anchor again unless the form inputs have a tabindex. */
        var keyCode = e.keyCode || e.charCode;
        if (keyCode === Event.KEY_TAB && this.active) {
            this.showList();
        }
    },
    onSelectBlur:function(e) {
        var keyCode = e.keyCode || e.charCode;
        if (keyCode === Event.KEY_TAB) {
            this.hideList();
        }
    },
    onKeyPress:function(e) {
        if (!this.active) return;
        switch(e.keyCode) {
            case Event.KEY_TAB:
                return;
            case Event.KEY_RETURN:
                // console.log(e.element());
                // if it's this.container
                // this.toggleList();
                // select the first item in the list
                // else if one of the items
                // select that item and hide
                this.onItemClick(e, this.getItem(this.index));
                Event.stop(e);
            case Event.KEY_ESC:
                Event.stop(e);
                // close the thing?
                return;
            case Event.KEY_LEFT:
            case Event.KEY_RIGHT:
                return;
            case Event.KEY_UP:
                this.markPrevious();
                Event.stop(e);
                return;
            case Event.KEY_DOWN:
                this.markNext();
                Event.stop(e);
                return;
        }
    },
    markPrevious:function(e) {
        if (this.index > 0) {
            this.index--;
        }
        else {
            this.index = this.listCount() - 1;
        }
        this.resetSelected();
    },
    markNext:function(e) {
        if (this.index < (this.listCount() - 1)) {
            this.index++;
        }
        else {
            this.index = 0;
        }
        this.resetSelected();
    },
    resetSelected:function(e) {
        for (var i=0;i<this.listCount();i++) {
            this.index === i ? Element.addClassName(this.getItem(i), "selected") : Element.removeClassName(this.getItem(i), "selected");
        }
    },
    onItemClick:function(e) {
        this.toggleList();
        this.setValue(arguments[1] || e.element());
        if (this.options.onItemSelect) {
            return this.options.onItemSelect.bind(this)(e); // by returning, we're leaving it up to the onItemSelect to handle the actual click
        }
        if (this.options.stopAfterSelect) {
            Event.stop(e);
        }
    },
    updateList:function(html) {
        this.container.down('div.selectlist ul').update(html);
        this.attachItemClicks();
        this.setInitialLabel();
    },
    columnizeList:function(targetUl) {
        if (this.options.colonize.inside) {
            if (typeof targetUl === 'undefined') {
                // if we didn't get one, select the first UL
                targetUl = Tablet.getElementByTagName(this.options.colonize.inside, 'UL');
            }
            if (!targetUl) return;
            targetUl = $(targetUl);
            var items = [], item;
            $A(targetUl.getElementsByTagName('LI')).each(function(item) {
                items.push($(item).remove());
            });

            var numberOfCols = this.options.colonize.cols;
            var itemCount = items.size() + 1;
            var minPerCol = 5;

            var perCol = Math.ceil(itemCount / numberOfCols);
            perCol = (perCol < minPerCol) ? minPerCol : perCol;

            var hasCols = (numberOfCols > 1);

            var currentCount = 0;
            var subUl;

            if (hasCols) {
                targetUl.addClassName('cols');
            }
            else {
                subUl = targetUl;
            }

            for (var i=0;i<items.length;i++) {
                item = items[i];
                if (currentCount == 0 && hasCols) {
                    subUl = $ul();
                    targetUl.appendChild($li({'class':'col'}, subUl));
                }
                subUl.appendChild(item);
                currentCount ++;

                if (currentCount >= perCol) {
                    currentCount = 0;
                }
            }
        }
    },
    showList:function() {
        var el = this.list;
        if (el.getStyle('display') === 'none') {
            el.setStyle({display:'block'});
            this.container.addClassName('open');
            el.scrollTop = 0;
        }
    },
    hideList:function() {
        var el = this.list;
        if (el.getStyle('display') === 'block') {
            el.setStyle({display:'none'});
            this.container.removeClassName('open');
        }
    },
    toggleList:function() {
        var el = this.list;
        var newDisplay = '';
        if (el) {
            if (el.getStyle('display') === 'block') {
                newDisplay = 'none';
                this.container.removeClassName('open');
                // but how dop I unset this when the popup just goes away
                // from clicking elsewhere
            } else {
                newDisplay = 'block';
                this.container.addClassName('open');
            }

            el.setStyle({display:newDisplay});
            el.scrollTop = 0;

            if (newDisplay === 'block') {
                var colHeight = 0;
                if (this.columns.size() > 0 && this.columnHeight <= 0) {
                    this.columns.each(function(col, index) {
                        if (col.getHeight() > colHeight) {
                            colHeight = col.getHeight();
                            if (Prototype.Browser.IE && index === 1) {
                                col.addClassName('first-child');
                            }
                        }
                    });
                    this.columns.each(function(col) {
                        col.setStyle({height:colHeight+'px'});
                    });
                }
                this.columnHeight = colHeight;
            }
        }
    }
});

TH.loadSelections = function (targetId, items, itemsOrder, cols) {
    var targetUl = $(targetId);

    if (targetUl.tagName != 'UL') {
      newUl = $ul();
      targetUl.appendChild(newUl);
      targetUl = newUl;
    }

    targetUl.descendants().invoke('remove');

    items = $H(items);

    // itemsOrder can be a separate array of ids with the objects in correct sorted order
    var sortedItems = (itemsOrder) ? $A(itemsOrder) : items.keys();
    // reject items whose keys aren't in the original list of keys
    sortedItems = sortedItems.reject(function(key) {return ! items.keys().member(key);});

    var numberOfCols = cols;

    var itemCount = sortedItems.size() + 1;
    var minPerCol = 5;

    var perCol = Math.ceil(itemCount / numberOfCols);
    perCol = (perCol < minPerCol) ? minPerCol : perCol;

    var hasCols = (numberOfCols > 1);

    var currentCount = 0;
    var subUl;

    if (hasCols) {
      targetUl.addClassName('cols');
    } else {
      subUl = targetUl;
    }

    // create var for "Doesn't Matter" link. append to end of list if it exists.
    var noMatter;

    sortedItems.each(function(key) {
        var item = items[key];

        var item = items.get(key);

      if (currentCount == 0 && hasCols) {
        subUl = $ul();
        targetUl.appendChild($li(subUl));
      }

      var action = $a({'id' : item.id, 'href' : "#"+item.id}, item.name.unescapeHTML());
      var listItem = $li('', action);

      if (item.id == "-1") {
          listItem.addClassName("nomatter");
          noMatter = listItem;
      }
      else {
          subUl.appendChild(listItem);
          currentCount ++;
      }

      if (currentCount >= perCol) {
        currentCount = 0;
      }
    }); // Object.keys(items).each(function(key)

    if (typeof noMatter !== "undefined") {
        var noMatter = subUl.appendChild(noMatter);
        if (subUl.descendants().indexOf(noMatter) <= 0) {
            noMatter.addClassName('first');
        }
    }
};


TH.toggle = function(id, relativeTo) {
    var el = $(id);
    var newDisplay = '';
    if (el) {
        var relativeTo = relativeTo || null;
        if (relativeTo) {
            var topPos = relativeTo.offsetHeight;
            var leftPos = relativeTo.offsetLeft;
            el.setStyle({top:topPos+'px', left:leftPos+'px'});
        }

        if (el.getStyle('display') === 'block') {
            newDisplay = 'none';
        } else {
            newDisplay = 'block';
        }

        el.setStyle({display:newDisplay});
        el.scrollTop = 0;
    }
};
TH.showPopup = function(id) {
    var el = $(id);
    if (el) {
        el.setStyle({display:'block'});
    }
};
TH.hidePopup = function(id) {
    var el = $(id);
    if (el) {
        el.setStyle({display:'none'});
    }
};
TH.togglePopup = function(id, relativeTo) {
    TH.hidePopups(id);
    TH.toggle(id, relativeTo);
};
TH.setValue = function(obj, targetDisplay, inputId) {
    var target = $(targetDisplay);
    if (target) {
        TH.setLabelValue(targetDisplay, obj.innerHTML);
        target.addClassName('set');
    }

    var npt = $(inputId);
    if (npt) {
        npt.value = obj.id;
    }

    return false;
};
TH.setLabelValue = function(containId, text) {
    var hintAttr = 'hint';
    var el = $(containId);
    if (el) {
        currentText = el.down('p') || TH.newElement(el, 'p');
        if (text != '') {
            if (currentText) {
                currentText.update(text);
            }
        } else {
            if (currentText.hasAttribute(hintAttr)) {
                currentText.update(currentText.getAttribute(hintAttr));
            }
        }
    }
};

Event.addBehavior({
  "div.brief a#morelink:click" : function(e) {
    $('moretext').toggle();
    return false;
  }
});
var emailBox;
// after content of layer is set, set behavior of form post
// function updateEmailForm() {
//     friendEmailForm = document.getElementById('friendEmailForm');
//     if (friendEmailForm) {
//         friendEmailForm.observe('submit', function(e) { processEmailForm(this); Event.stop(e); });
//     }
// }
// process form by ajax instead of normal behavior
function processEmailForm(emailForm) {
    new Ajax.Request(
        emailForm.getAttribute('action'),{
            method: 'post', 
            onSuccess: processEmailResponse.bindAsEventListener(this), 
            parameters:Form.serialize(emailForm, true)
        }
    );
  
  return false;
}
// call processInfo to reset content of email layer
function processEmailResponse(resp) {
    emailBox.setPageContent('emailpage', resp.transport.responseText);
}

document.observe('dom:loaded', function(e) {

    var shareLinks = $$('a.emailpage');
    if (shareLinks.size() > 0) {
        emailBox = new Tablet.Pane('', {
            pageReset: true
        });
        
        shareLinks.each(function(link) {
            link.observe('click', function(e) {

                new Ajax.Request(link.href, {
                    method:'get',
                    onSuccess:function(transport) {
                        emailBox.setContent(transport);
                        emailBox.show();
                    }
                });
            
                Event.stop(e);
            });
        });
    }
  
});
