
// 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));
            }
        }
    }
};

// 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);
            }
        }
    });
    
});

var Tablet = Tablet || {};
Tablet.Ajaxificator = function() {
    this.init.apply(this, arguments);
};
Tablet.Ajaxificator.prototype = {
    init: function(link) {
        this.link = $(link);
        var ajaxOptions = Object.extend({
            url:this.link.href
        }, (arguments[1] && arguments[1].ajaxOptions) ? arguments[1].ajaxOptions : {} );
        arguments[1].ajaxOptions = ajaxOptions;
        this.options = Object.extend({
            handleEvent:'click',
            auth: false,
            handle401:false,
            beforeStart: function() {},
            ajaxOptions: {},
            yieldWithAltKey:false
        }, arguments[1] || {});
        this.initializeEvents();
    },
    initializeEvents: function() {
        Event.observe(this.link, this.options.handleEvent, this.clickHandler.bind(this));
    },
    unloadEvents: function() {
        Event.stopObserving(this.link, this.options.handleEvent);
    },
    clickHandler: function(event) {
        if (this.options.yieldWithAltKey && Tablet.altKey(event)) {
            return true; // yield to do whatever the link wanted to do
        }
        this.options.beforeStart.apply(this, []);
        if (!this.options.auth) {
            this.ajaxHandler();
        } else {
            this.authHandler();
        }
        event.stop();
    },
    deniedHandler: function(resp) {
        // check for message back from server
        var message = '';
        if (resp.headerJSON && resp.headerJSON.message) {
            message = resp.headerJSON.message;
        }
        // I don't think this is right, we want to re-do the original request, not the success handler
        // var onAuthorized;
        // if (this.options.ajaxOptions && typeof this.options.ajaxOptions.onSuccess === 'function') {
        //     onAuthorized = this.options.ajaxOptions.onSuccess.bind(this);
        // }
        var onAuthorized = this.ajaxHandler.bind(this);
        Tablet.EnterSite.pane.page('loginorregister', {
            onShow:function() {
                var action = {action:onAuthorized, args:[this]};
                Tablet.EnterSite.setManeuver(null, action); // sending null will create one automatically
            }.bind(this),
            message:message
        });
    },
    ajaxHandler: function() {
        var respFunctions = {};
        if (this.options.handle401) {
            respFunctions = {
                on401:this.deniedHandler.bind(this),
                on403:this.deniedHandler.bind(this)
            };
        }
        var options = Object.extend(respFunctions, this.options.ajaxOptions);
        if (options && typeof options.onSuccess === 'function') {
            options.onSuccess = options.onSuccess.bind(this);
        }
        new Ajax.Request(
            this.options.ajaxOptions.url,
            options
        );
    },
    authHandler: function() {
        // man there's a lot of binding going on here
        Tablet.EnterSite.isRecognized({
            onSuccess:this.ajaxHandler.bind(this),
            onDenied:function(resp) {
                var message = '';
                // check for message back from server

                Tablet.EnterSite.pane.page('loginForm', {
                    onShow:function() {
                        var action = {action:this.ajaxHandler.bind(this), args:[this]};
                        Tablet.EnterSite.setManeuver(null, action); // sending null will create one automatically
                    }.bind(this)
                });
            }.bind(this)
        });
    }
};

var Tablet = Tablet || {};
Tablet.UI = {
    Enhanced:(function() {
        return !(false /*@cc_on || @_jscript_version < 5.7 @*/);
    })(),
    fadeIn: function(els, options) {
        if (typeof els.length === 'undefined') {
            els = $A([els]);
        }
        els.collect(function(el) { return $(el); });
        options = Object.extend({
            activeClass:'',
            duration:0.4,
            afterFinish:null,
            from:0.0,
            to:1.0
        }, options);

        if (options.activeClass) {
            els.invoke('setStyle', {display:'none'});
            els.invoke('addClassName', options.activeClass);
        }

        var el;
        for (var i=els.length-1;i>=0;i--) {
            el = els[i];
            new Effect.Appear(el, {
                duration:options.duration,
                afterFinish:options.afterFinish,
                from:options.from,
                to:options.to
            });
        }
    },
    fadeOut: function(els, options) {
        if (typeof els.length === 'undefined') {
            els = $A([els]);
        }
        els.collect(function(el) { return $(el); });
        options = Object.extend({
            activeClass:'',
            startDisplay:'block',
            duration:0.4,
            afterFinish:null,
            from:1.0,
            to:0.0
        }, options);

        if (options.activeClass) {
            els.invoke('setStyle', {display:options.startDisplay});
            els.invoke('removeClassName', options.activeClass);
        }

        var el;
        for (var i=els.length-1;i>=0;i--) {
            el = els[i];
            new Effect.Fade(el, {
                duration:options.duration,
                afterFinish:options.afterFinish,
                from:options.from,
                to:options.to
            });
        }
    },
    slideWrap: function(el, options) {
        options = Object.extend({
            paddingToWrap:true
        }, options);
        var wrapped = $A(el.getElementsByClassName('slidewrap')).any(function(child) {
            return child.parentNode === el;
        });
        if (!wrapped) {
            var elContent = el.innerHTML;
            el.update();
            var div = TH.newElement(el, 'div');
            div.update(elContent);
            div.addClassName('slidewrap');
            // el.update(div);
            if (options && options.paddingToWrap) {
                var paddingRight = el.getStyle('padding-right');
                var paddingLeft = el.getStyle('padding-left');
                div.setStyle({
                    paddingTop:el.getStyle('padding-top'),
                    paddingBottom:el.getStyle('padding-bottom'),
                    paddingRight:paddingRight,
                    paddingLeft:paddingLeft,
                    borderBottomWidth:el.getStyle('borderBottomWidth'),
                    borderBottomColor:el.getStyle('borderBottomColor'),
                    borderBottomStyle:el.getStyle('borderBottomStyle'),
                    position:'relative'
                });
                var newWidth = (el.getStyle('width')) ? el.getStyle('width').replace('px','') : '' ;
                if ((paddingLeft || paddingRight) && newWidth) {
                    newWidth = (parseInt(paddingLeft.replace('px', '')) + parseInt(paddingRight.replace('px', '')) +
                        parseInt(newWidth.replace('px', '')));
                }
                el.setStyle({paddingTop:'0px', paddingBottom:'0px', paddingLeft:'0px',
                    paddingRight:'0px', borderBottomWidth:'0px',
                    width:(newWidth !== '') ? newWidth+'px' : ''});
            }
        }

        return el;
    },
    slideDown: function(el, options) {
        if (el.hasClassName('sliding')) {
            // already doing a sliding effect, ignore this for now
            return;
        }
        options = Object.extend({
            duration:0.4,
            wrap:true
        }, options);
        if (options.wrap) {
            el = Tablet.UI.slideWrap(el, options);
        }
        if (options.activeClass) {
            el.setStyle({display:'none'});
            el.addClassName(options.activeClass);
        }
        options.afterFinish = function(e) {
            e.element.removeClassName('sliding');
        }
        el.addClassName('sliding');
        Effect.SlideDown(el, options);
        return el;
    },
    slideUp: function(el, options) {
        if (el.hasClassName('sliding')) {
            // already doing a sliding effect, ignore this for now
            return;
        }
        options = Object.extend({
            duration:0.4,
            wrap:true
        }, options);
        if (options.wrap) {
            el = Tablet.UI.slideWrap(el, options);
        }
        var finishClasses = $A(['sliding']);
        if (options.activeClass) {
            finishClasses.push(options.activeClass);
        }
        options.afterFinish = function(e) {
            finishClasses.each(function(cls) {
                e.element.removeClassName(cls);
            });
        }
        el.addClassName('sliding');
        Effect.SlideUp(el, options);
        return el;
    },
    scrollTo: function(el, options) {
        options = Object.extend({
            duration:0.2,
            offset:-20,
            onlyifhidden:true
        }, options);
        // check to make sure element 
        if (!options.onlyifhidden || !Tablet.UI.isElementVisible(el)) {
            Effect.ScrollTo(el, options);
        }
    },
    isElementVisible: function(el) {
        // currently just checks if the element is above the viewport
        // should handle the current cases though
        var eloffsets = Element.viewportOffset(el);
        return (eloffsets.top < 0) ? false : true;
    },
    // getContainerHeight calculates the height of an element, without padding,
    // or what the element's height should be set to to keep the same space after removing child elements
    getContainerHeight: function(el) {
        var hidden, position, left, visibility;
        if (el.getStyle('display') === 'none') {
            hidden = true;
            position = el.getStyle('position');
            left = el.getStyle('top');
            visibility = el.getStyle('visibility');
            // make element visible but off the page to get the accurate height
            el.setStyle({display:'block', position:'absolute', left:'-9999px', visibility:'hidden'});
        }
        var height = parseInt(el.offsetHeight) -
            parseInt(el.getStyle('padding-top').replace('px', '')) -
            parseInt(el.getStyle('padding-bottom').replace('px', '')) -
            (
                (!isNaN(parseInt(el.getStyle('border-top-width').replace('px', ''))) ? parseInt(el.getStyle('border-top-width').replace('px', '')) : 0)
                +
                (!isNaN(parseInt(el.getStyle('border-bottom-width').replace('px', ''))) ? parseInt(el.getStyle('border-bottom-width').replace('px', '')) : 0)
            )
        ;
        // reset styles if necessary
        if (hidden) {
            el.setStyle({'display':'none', 'position':position, 'left':left, 'visibility':visibility});
        }
        return height;
    },
    // getChlidHeight calculates the space an element takes up inside an element
    // use hidden to get the height of a element with display:none
    getChildHeight:function(el) {
        var hidden, position, left = '', visibility = '';
        if (el.getStyle('display') === 'none') {
            hidden = true;
            position = el.getStyle('position');
            left = el.getStyle('top');
            visibility = (el.style.visibility) ? el.style.visibility : ''; // getStyle('visibility') returns inherited values, which we don't want
            // make element visible but off the page to get the accurate height
            el.setStyle({display:'block', position:'absolute', left:'-9999px', visibility:'hidden'});
        }
        var height = parseInt(el.offsetHeight) +
            (el.getStyle('margin-top') ? parseInt(el.getStyle('margin-top').replace('px', '')) : 0 )
            +
            (el.getStyle('margin-bottom') ? parseInt(el.getStyle('margin-bottom').replace('px', '')) : 0)
        ;
        // reset styles if necessary
        if (hidden) {
            el.setStyle({'display':'none', 'position':position, 'left':left, visibility:visibility});
        }
        return height;
    },
    fadeOutAndShortify: function(container, els, options) {
        if (typeof els.length === 'undefined') {
            els = [els];
        }
        options = Object.extend({
            duration:0.4,
            fadeOptions:{},
            containerHeight:undefined,
            clearHeightAfterFinish:false
        }, options);
        var containerHeight = (typeof options.containerHeight !== 'undefined' ? options.containerHeight : this.getContainerHeight(container));
        container.setStyle({height:containerHeight+'px'});
        var morphheight = 0;
        for (var i=0;i<els.length;i++) {
            morphheight += this.getChildHeight(els[i]);
            Tablet.UI.fadeOut(els[i], options.fadeOptions);
        };
        new Effect.Morph(container, {style:{
            height:(this.getContainerHeight(container)-morphheight)+'px'},
            duration:options.duration,
            afterFinish:function(e) {
                setTimeout(function() {
                    if (options.clearHeightAfterFinish) {
                        e.element.setStyle({height:''});
                    }
                }, 1200);
            }
        });
    },
    fadeInAndTallify: function(container, els, options) {
        if (typeof els.length === 'undefined') {
            els = [els];
        }
        options = Object.extend({
            duration:0.4,
            fadeOptions:null,
            containerHeight:undefined,
            clearHeightAfterFinish:false
        }, options);
        var containerHeight = (typeof options.containerHeight !== 'undefined' ? options.containerHeight : this.getContainerHeight(container));
        container.setStyle({height:containerHeight+'px'});
        var morphheight = 0;
        for (var i=0;i<els.length;i++) {
            morphheight += this.getChildHeight(els[i]);
        };
        new Effect.Morph(container, {style:{
            height:(containerHeight+morphheight)+'px'},
            duration:options.duration,
            afterFinish:function(e) {
                setTimeout(function() {
                    if (options.clearHeightAfterFinish) {
                        e.element.setStyle({height:''});
                    }
                }, 1200);
            }
        });
        setTimeout(function() {
            for (var i=0;i<els.length;i++) {
                Tablet.UI.fadeIn(els[i], options.fadeOptions);
            };
        }, 200);
    },
    shortify: function(els, options) {
        if (typeof els.length === 'undefined') {
            els = [els];
        }
        options = Object.extend({
            duration:0.4
        }, options);
        var height, margintop;
        for (var i=0;i<els.length;i++) {
            var height = this.getContainerHeight(els[i]);
            // fixed bug in IE where doing the parseInt on .replace wiped out els for some reason
            var margintop = (els[i].getStyle('margin-top')) ? parseInt(els[i].getStyle('margin-top').replace('px', '')) : '0';
            els[i].setStyle({
                height:height+'px',
                marginTop:margintop+'px',
                overflow:'hidden'
            });
            var resetEl = function(height, margintop) {
                return function(e) {
                    e.element.setStyle({
                        display:'none',
                        height:height+'px',
                        marginTop:margintop+'px',
                        overflow:''
                    });
                };
            };
            new Effect.Morph(els[i], {
                style:{
                    'height':'0px',
                    'margin-top':'0px'
                },
                duration:options.duration,
                afterFinish:resetEl(height, margintop)
            });
        }
    },
    tallify: function(els, options) {
        if (typeof els.length === 'undefined') {
            els = [els];
        }
        options = Object.extend({
            duration:0.4,
            afterFinish:null
        }, options);
        var height, margintop;
        for (var i=0;i<els.length;i++) {
            height = this.getContainerHeight(els[i]);
            margintop = parseInt(els[i].getStyle('margin-top').replace('px', ''));
            els[i].setStyle({
                height:'0px',
                marginTop:'0px',
                display:'block',
                overflow:'hidden'
            });
            new Effect.Morph(els[i], {
                style:{
                    'height':height+'px',
                    'margin-top':margintop+'px'
                },
                duration:options.duration,
                afterFinish:function(e) {
                    $(e.element).setStyle({
                        overflow:''
                    });
                    if (typeof options.afterFinish === 'function') {
                        options.afterFinish(e)
                    }
                }
            });
        }
    },
    switchByFade: function(container, hide, show, options) {
        options = Object.extend({
            newClass:'',
            addClass:'',
            removeClass:''
        }, options);
        var containerheight = this.getContainerHeight(container);
        container.setStyle({height:containerheight+'px',
            overflow:'hidden'});
        var oldheight = this.getChildHeight(hide);
        var newheight = this.getChildHeight(show);
        new Effect.Fade(hide, {
            duration:0.2,
            afterFinish:function(e) {
                show.setStyle({display:'none'});
                if (options.newClass) {
                    container.className = options.newClass;
                }
                else if (options.addClass && options.removeClass) {
                    container.removeClassName(options.removeClass);
                    container.addClassName(options.addClass);
                }
                new Effect.Morph(container, {
                    duration:0.2,
                    style:{height:containerheight+(newheight-oldheight)+'px',
                        overflow:''},
                    afterFinish:function(e) {
                        e.element.setStyle({overflow:''});
                    }
                });
                new Effect.Appear(show, {duration:0.2});
            }
        });
    }
};
var Tablet = Tablet || {};

Tablet.InlineForm = function(link, options) {
    link = link || null;
    if (!link) return;

    options = Object.extend({
        container:null,
        onShowForm:null,
        onInitForm:null,
        onSuccess:null,
        hideOnSuccess:true,
        onContent:null,
        duration:0.4,
        insertAfter:null,
        formClass:'inlineForm',
        initialStyle:{},
        handleSubmit:true,
        initControls:true,
        errorContainer:null,
        focusElement:true,
        errorMsgClass:'errorMsg'
    }, options);
    var onInitForm = options.onInitForm;
    var onShowForm = options.onShowForm;
    var onSubmitSuccess = options.onSuccess;
    var hideOnSuccess = options.hideOnSuccess;
    var onNewContent = options.onContent;
    var effectDuration = options.duration;
    var formClass = options.formClass;
    var errorContainer = options.errorContainer;
    // check for form that has already be retrieved
    // if not found, do a request for the form

    var container = options.container || link.up('li') || link.up('div');

    var insertAfter = options.insertAfter ; // link.up('p.act')
    if (!container) return;
    var saveBlock, frm, errorMsgs;

    if (typeof options.saveBlock !== 'undefined') {
        saveBlock = $(options.saveBlock);
    }

    var initForm = function(frm) {
        if (frm) {
            errorMsgs = frm.select('p.'+options.errorMsgClass);

            if (options.initControls) {
                frm.select('div.selectbox').each(function(el) {
                    new Tablet.SelectBox(el);
                });
            }

            frm.select('.hoverelement').each(function(el) {
                new Tablet.HoverElement(el);
            });

            // Tablet.handleHints(frm);

            if (options.handleSubmit) {
                frm.select('[type=submit]', '[type=image]').each(function(btn) {
                    btn.observe('click', submitForm);
                });
            }

            $A(saveBlock.getElementsByClassName('cancel')).each(function(el) {
                if (el.tagName === 'A') {
                    el.observe('click', function(e) {
                        closeForm();
                        Event.stop(e);
                    });
                }
            });
        }
    };

    var showForm = function(resp) {
        if (saveBlock) {
            if (!saveBlock.visible()) {
                if (Tablet.UI.Enhanced) {
                    Effect.SlideDown(saveBlock, {duration:effectDuration, afterFinish:function() {
                        if (options.focusElement && frm.elements.length > 0) {frm.focusFirstElement();}
                    }});
                }
                else {
                    saveBlock.show();
                }

                if (typeof onShowForm === 'function') {
                    onShowForm(container);
                }
            }
        }
        else {
            if (container) {
                // detect any other forms and close them

                // do this after the p.act
                if (insertAfter) {
                    insertAfter.insert({after:resp.responseText});
                }
                else {
                    container.insert(resp.responseText);
                }

                // huh. this will pose problems with multiple forms there.
                saveBlock = container.down('div.' + formClass);

                if (!saveBlock) return;
                if (Tablet.UI.Enhanced) {
                    saveBlock = Tablet.UI.slideWrap(saveBlock,{paddingToWrap:true});
                }

                if (options.initialStyle) {
                    // saveBlock.setStyle(options.initialStyle);
                }

                if (typeof frm === 'undefined') {
                    frm = $(Tablet.getElementByTagName(saveBlock, 'form'));
                }

                if (Tablet.UI.Enhanced) {
                    Tablet.UI.slideDown(saveBlock,  {
                        duration:effectDuration,
                        afterFinish:function() {
                            // frm.focusFirstElement();
                        }
                    });
                }
                else {
                    saveBlock.show();
                }

                initForm(frm);

                if (typeof onInitForm === 'function') {
                    onInitForm(container);
                }

                if (typeof onShowForm === 'function') {
                    onShowForm(container);
                }
            }
        }
    };

    var closeForm = function() {
        if (Tablet.UI.Enhanced) {
            new Effect.SlideUp(saveBlock, {
                duration:effectDuration,
                afterFinish:function(e) {
                    frm.removeClassName('error');
                    clearErrors();
                }
            });
        }
        else {
            saveBlock.hide();
        }
    };

    var clearErrors = function() {
        $A(frm.getElementsByClassName(options.errorMsgClass)).invoke('hide');
        $A(frm.getElementsByClassName(options.errorMsgClass)).invoke('update', '');
    };

    var submitForm = function(e) {
        frm.removeClassName('error');
        clearErrors();

        var submitBtn = this;

        if (submitBtn.tagName.toLowerCase() === 'input') {
            submitBtn.value = submitBtn.name;
        }
        var frmParams = frm.serialize({hash:false});
        if (submitBtn.tagName.toLowerCase() === 'button') {
            frmParams += '&' + submitBtn.name + '=' + submitBtn.value;
        }
        else if (submitBtn.tagName.toLowerCase() === 'input') {
            submitBtn.value = '';
        }
        saveBlock.addClassName('processing');
        new Ajax.Request(frm.action, {
            method:'post',
            parameters:frmParams,
            onSuccess:function(transport) {
                var resp;
                if (transport.responseJSON) {
                    resp = transport.responseJSON;
                }
                else if (transport.headerJSON) {
                    resp = transport.headerJSON;
                }
                if (resp) {
                    saveBlock.removeClassName('processing');
                    if (resp.success) {
                        saveBlock.addClassName('done');
                        // fade up, on complete remove element or done class name
                        setTimeout(function() {
                            if (hideOnSuccess) {
                                if (Tablet.UI.Enhanced) {
                                    new Effect.SlideUp(saveBlock, {
                                        duration:effectDuration-0.2,
                                        afterFinish:function(e) {
                                            saveBlock.removeClassName('done');
                                            e.element.hide();
                                            frm.select('input[type=text]').each(function(el) {
                                                if (el.hasAttribute('hint')) {
                                                    el.value = el.getAttribute('hint');
                                                }
                                                else {
                                                    el.value = '';
                                                }
                                            });
                                            frm.select('div.selectbox input[type=hidden]').each(function(el) {
                                                el.value='';
                                            });

                                            // can we check resp.content and insert it after container?
                                            if (resp.content) {
                                                if (typeof onNewContent === 'function') {
                                                    onNewContent(resp.content);
                                                }
                                            }
                                        }
                                    });
                                }
                                else {
                                    saveBlock.hide();
                                }
                            }

                            if (typeof onSubmitSuccess === 'function') {
                                onSubmitSuccess(resp);
                            }

                        }, 500);

                    }
                    else if (resp.errorMsg) {
                        // show errors in some way
                        frm.addClassName('error');
                        var errorMsg = submitBtn.next('p.'+options.errorMsgClass);
                        if (errorMsg) {
                            errorMsg.innerHTML = resp.errorMsg;
                            Tablet.UI.fadeIn(errorMsg, {duration:0.4, activeClass:'active'});
                        }
                    }
                    else if (resp.errors) {
                        var npt, errorP;
                        frm.addClassName('error');
                        for (error in resp.errors) {
                            npt = container.down('[name='+error+']');
                            errorP = npt.next('p.'+options.errorMsgClass) || TH.newElement(
                                errorContainer || npt.up('div.field') || npt.up(), 'p', {'class':options.errorMsgClass});
                            errorP.update(resp.errors[error]);
                            if (npt.offsetTop) {
                                errorP.setStyle({top:(npt.offsetTop+2)+'px'});
                            }
                            Tablet.UI.fadeIn(errorP, {duration:0.4, activeClass:'active'});
                        }
                    }
                }
            }
        }, this);
        Event.stop(e);
    };

    var onDenied = function(resp) {
        Tablet.EnterSite.pane.page('loginForm', {
            onShow:function() {
                action = {action:function() {
                    Tablet.EnterSite.pane.hide();
                    retrieveForm();
                }};
                Tablet.EnterSite.setManeuver(maneuver, action);
                Tablet.EnterSite.setLoginDestination();
            }
        });
    };

    if (typeof options.frm !== 'undefined') {
        frm = $(options.frm);
        initForm(frm);
    }

    var maneuver = parseInt((new Date).getTime() * Math.random());

    var retrieveForm = function() {
        if (saveBlock) {
            showForm();
        }
        else {
            new Ajax.Request(link.href,{
                method:'get',
                onSuccess:showForm,
                on401:onDenied,
                on403:onDenied
            });
        }
    };

    var onLinkClick = function(e) {
        retrieveForm();
        Event.stop(e);
    };

    link.observe('click', onLinkClick);
    link.observe('tablet:init', onLinkClick);
    link.observe('tablet:fire', onLinkClick);
    link.observe('tablet:close', closeForm);

    return {
        link:link
    };
};
/* *****
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.googleLoaded = false;
Tablet.documentLoaded = false;

Tablet.loadGoogle = function() {
    if (!Tablet.GOOGLE_API) {
        throw('No path to google API specified.');
    }
    var script = document.createElement('script');
    script.setAttribute('src', Tablet.GOOGLE_API);
    script.setAttribute('type', 'text/javascript');
    document.body.appendChild(script);
    //document.body.innerHTML += '<script src="'+Tablet.GOOGLE_API+'" type="text/javascript"></script>';
    var script2 = document.createElement('script');
    script2.setAttribute('type', 'text/javascript');
    script2.appendChild(document.createTextNode('Tablet.loadGoogleMaps();'));//'google.load("maps","2",{callback:Tablet.loadGoogles});'));
    document.body.appendChild(script2);
    //document.body.innerHTML += '<script type="text/javascript">google.load("maps","2",{callback:Tablet.loadGoogles});</script>';
};

Event.onGoogleLoad = function(method) {
    Tablet.googleLoads = Tablet.googleLoads || [];
    Tablet.googleLoads.push(method);
    if (Tablet.googleLoaded) {
        Tablet.loadGoogles();
    }
    else if (Tablet.documentLoaded && !Tablet.googleLoading) { // and !Tablet.googleLoaded
        Tablet.initGoogle();
    }
}
Tablet.loadGoogles = function() {
    Tablet.googleLoading = false;
    for (var i=0;i<Tablet.googleLoads.length;i++) {
        Tablet.googleLoads[i]();
    }
    Tablet.googleLoads = false;
    Tablet.googleLoaded = true;
};
Tablet.loadGoogleMaps = function() {
    if (window['google']) {
        google.load("maps","2",{callback:Tablet.loadGoogles});
        return;
    }
    window.setTimeout(Tablet.loadGoogleMaps, 100);
};
Tablet.googleLoads = [];
/*Event.onTabletLoaded(function() {
    Tablet.thread(Tablet.loadGoogle);
});*/
Tablet.initGoogle = function() {
    Tablet.googleLoading = true;
    new OffloadedJavascript(Tablet.GOOGLE_API, '', {
        callback: Tablet.loadGoogleMaps
    });
};
Event.onReady(function() {
    // only do this if we have scripts to run
    if (Tablet.googleLoads.length > 0) {
        Tablet.initGoogle();
    }
    Tablet.documentLoaded = true;
});
// this is mostly to set behaviors
Tablet.Map = Class.create({
    initialize:function(id, options) {
        if (typeof google.maps === 'undefined') { return null; }
        this.options = Object.extend({
            mapType:'',
            controls:['smallmap', 'maptype'],
            maxZoom:14,
            preMarkerClick:null,
            onMapClick:null
        }, options);

        function createUUID() {
            // http://www.ietf.org/rfc/rfc4122.txt
            var s = [];
            var hexDigits = "0123456789ABCDEF";
            for (var i = 0; i < 32; i++) {
                s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
            }
            s[12] = "4";  // bits 12-15 of the time_hi_and_version field to 0010
            s[16] = hexDigits.substr((s[16] & 0x3) | 0x8, 1);  // bits 6-7 of the clock_seq_hi_and_reserved to 01

            var uuid = s.join("");
            return uuid;
        }
        this.GUID = createUUID();
        this.points = [];
        this.markers = [];
        this.bounds = null;
        this.maxZoom = this.options.maxZoom;
        this.checkZoomInterval = null;
        this.zoomChecks = 0;
        this.icons = new Hash();
        this.map = null;
        this.container = $(id);
        this.preMarkerClick = this.options.preMarkerClick;
        var gMap = new google.maps.Map2(this.container);

        gMap.setCenter(new google.maps.LatLng(0, 0), 1);

        var mapType;
        if (typeof this.options.mapType !== 'undefined') {
            if (this.options.mapType == 'normal') {
                mapType = G_NORMAL_MAP;
            }
            else {
                mapType = G_PHYSICAL_MAP;
                gMap.removeMapType(G_HYBRID_MAP);
                gMap.addMapType(G_PHYSICAL_MAP);
            }
            gMap.setMapType(mapType);
        }

        for (var i=0;i<this.options.controls.length;i++) {
            switch (this.options.controls[i]) {
                case 'smallzoom':
                    gMap.addControl(new google.maps.SmallZoomControl());
                    break;
                case 'smallmap':
                    gMap.addControl(new google.maps.SmallMapControl());
                    break;
                case 'maptype':
                    // we want the terrain option present whenever there's a map type selector
                    gMap.addMapType(G_PHYSICAL_MAP);
                    gMap.addControl(new google.maps.HierarchicalMapTypeControl(true));
                    break;
                case 'largemap':
                    gMap.addControl(new google.maps.LargeMapControl3D());
                    break;
            }
        }

        if (typeof this.options.onMapClick === 'function') {
            google.maps.Event.addListener(gMap, 'click', this.options.onMapClick);
        }

        this.map = gMap;

        this.currentOverlay = null;

        this.mapIcons = {
            'blank': {
                'image':'/img/global/markers/blank.png',
                'width':21,
                'height':24,
                'shadow': {
                    'image':'/img/global/markers/THmapIconShadow.png',
                    'width':37,
                    'height':24
                }
            },
            'number': {
                'image':'/img/global/markers/%s.png',
                'width':21,
                'height':24,
                'shadow': {
                    'image':'/img/global/markers/THmapIconShadow.png',
                    'width':37,
                    'height':24
                }
            },
            'small': {
                'image':'/img/global/markers/pin.png',
                'width':9,
                'height':16,
                'shadow': {
                    'image':'/img/global/markers/pin-shadow.png',
                    'width':37,
                    'height':24
                }
            },
            'region': {
                'image':'/img/global/markers/find-region.png',
                'width':21,
                'height':24,
                'shadow': {
                    'image':'/img/global/markers/pin-shadow.png',
                    'width':37,
                    'height':24
                }
            },
            'city': {
                'image':'/img/global/markers/find-city.png',
                'width':21,
                'height':24,
                'shadow': {
                    'image':'/img/global/markers/pin-shadow.png',
                    'width':37,
                    'height':24
                }
            },
            'hotel': {
                'image':'/img/global/markers/find-hotel.png',
                'width':21,
                'height':24,
                'shadow': {
                    'image':'/img/global/markers/pin-shadow.png',
                    'width':37,
                    'height':24
                }
            }
        };
    },
    getIcon:function(iconName, number) {
        iconName = iconName || '';
        var gIcon, image;
        var icon, shadow;

        if (iconName == 'number') {
            icon = this.mapIcons['number'];
            imgSrc = icon['image'].replace('%s', number);
            shadow = this.mapIcons['number']['shadow'];
        }
        else if (typeof this.mapIcons[iconName] !== 'undefined') {
            icon = this.mapIcons[iconName];
            imgSrc = icon['image'];
            shadow = icon['shadow'];
        }
        else {
            icon = this.mapIcons['blank'];
            imgSrc = icon['image'];
            shadow = icon['shadow'];
        }

        if (this.icons.get(imgSrc)) {
            gIcon = this.icons.get(imgSrc);
        }
        else {
            var gIcon = new google.maps.Icon();
            gIcon.image = imgSrc;
            gIcon.iconSize = new GSize(icon['width'], icon['height']);
            gIcon.shadow = shadow['image'];
            gIcon.shadowSize = new GSize(shadow['width'], shadow['height']);
            gIcon.iconAnchor = new google.maps.Point(6, 20);
            gIcon.infoWindowAnchor = new google.maps.Point(5, 1);

            this.icons.set(imgSrc, gIcon);
        }
        return gIcon;
    },
    clearMarkers:function() {
        var length = this.markers.length;
        for(var i=length-1;i>=0;i--) {
            if (typeof this.markers[i].importance !== 'undefined' &&
                    !this.markers[i].importance) { // for now, it is safe to assume we want to keep it
                this.map.removeOverlay(this.markers[i]);
                this.markers.splice(i, 1);
                this.points.splice(i, 1);
            }
        }
        this.bounds = null;
    },
    removeOverlay:function(marker) {
        this.map.removeOverlay(marker);
    },
    getPointZindex:function(marker, top) {
        var zindex, importance = 0;
        if (marker.importance) {
            importance = marker.importance*1000000;
        }
        zindex = GOverlay.getZIndex(marker.getLatLng().lat()) + importance;
        return zindex;
    },
    addPoint:function(lat, lng, options) {
        var options = options || {};
        var index, observer;
        var icon;

        // some code for backwards compatibility
        if (typeof options !== 'object') {
            index = arguments[2];
            if (!isNaN(index)) {
                icon = 'number';
            }
            observer = arguments[3];
        }
        else {
            options = Object.extend({
                index:null,
                observer:null,
                onMarkerClick:null,
                icon:null,
                bubble:null,
                tip:null,
                hideOnOverlay:true,
                showInfoEvent:'click',
                hideInfoEvent:null,
                onOverlayInit:null,
                overlayClass:'hotelwindow'
            }, options);
            index = options.index; // index is passed when a numbered marker should be used
            observer = options.observer;
            onMarkerClick = options.onMarkerClick;
            icon = options.icon; // icon is used to denote a type of marker, like 'small'
            if (index) {
                icon = 'number';
            }
        }

        var bubble = options.bubble || ''; // bubble is some html that show show on click
        var hasinfowindow = (typeof observer == 'function' || bubble !== '') ? true : false;
        icon = this.getIcon(icon, index);
        var point =  new google.maps.LatLng(lat, lng);
        var marker = new google.maps.Marker(point, {
            icon:icon,
            clickable:hasinfowindow,
            zIndexProcess:this.getPointZindex
        });
        marker.importance = options.importance || 0;
        marker.offset = offset = (options.offset || new GPoint(0, 0));

        if (bubble) {
            marker.html = bubble;
            // don't like the smell of this setting of properties on the marker for it's own map and overlay
            // feels like it might break soon
            marker.tmap = this;
            if (typeof Tablet.Maps.Overlay === 'undefined') {
                Tablet.Maps.initOverlays();
            }
            marker.overlay = new Tablet.Maps.Overlay(marker, {
                enableHover:(options.showInfoEvent === 'mouseover'),
                onInit:options.onOverlayInit,
                overlayClass:options.overlayClass
            });
            this.map.addOverlay(marker.overlay);
            marker.hideOnOverlay = options.hideOnOverlay;
        }

        this.map.addOverlay(marker);

        this.points.push(point);
        this.markers.push(marker);
        if (!this.bounds) {
            this.bounds = new google.maps.LatLngBounds();
        }
        this.bounds.extend(point);
        if (hasinfowindow) {
            observer = (typeof observer !== 'function' && bubble) ? this.onMarkerClick.bind(this) : observer;
            google.maps.Event.addListener(marker, options.showInfoEvent || 'click', function() { observer(this); });
            if (options.showInfoEvent !== 'mouseover' && options.tip) {
                if (typeof Tablet.Maps.Overlay === 'undefined') {
                    Tablet.Maps.initOverlays();
                }
                var tipoverlay = new Tablet.Maps.TipOverlay(marker, {
                    html:options.tip});
                this.map.addOverlay(tipoverlay);
                google.maps.Event.addListener(marker, 'mouseover', function() {
                    tipoverlay.show();
                });
                google.maps.Event.addListener(marker, 'mouseout', function() {
                    tipoverlay.hide();
                });
                marker.tipoverlay = tipoverlay;
            }
            marker.showInfoEvent = options.showInfoEvent;
        }
        return marker;
    },
    onMarkerOut:function() {

    },
    getOffsetPt:function(a,b,t,z) {
        var p=(t) ? t.getProjection() : this.map.getCurrentMapType().getProjection();
        var z=(z) ? z : this.map.getZoom();
        if (a.lat()) a=p.fromLatLngToPixel(a,z);
        var d=new GPoint(a.x+b.x,a.y+b.y);
        return p.fromPixelToLatLng(d,z);
    },
    showOverlay:function(marker) {
        if (!marker.openInfoWindowHtml) return;

        if (this.currentMarker) {
            this.closeOverlay();
        }

        if (marker.overlay) {
            if (marker.showInfoEvent === 'click') {
                var newPoint;
                if (marker.offset) {
                    newPoint = this.getOffsetPt(marker.getPoint(), marker.offset);
                }
                else {
                    newPoint = marker.getPoint();
                }
                this.map.checkResize();
                // this.map.setCenter(this.getMapCenter());
                this.map.panTo(newPoint);
            }
            marker.overlay.show();
            if (marker.hideOnOverlay) {
                marker.hide();
            }
        } else {
            marker.openInfoWindowHtml(marker.html);
        }

        this.currentMarker = marker;
    },
    closeOverlay:function() {
        if (this.currentMarker) {
            this.currentMarker.overlay.hide();
            if (this.currentMarker.hideOnOverlay) {
                this.currentMarker.show();
            }
            if (typeof this.currentMarker.showInfoEvent !== 'undefined' && this.currentMarker.showInfoEvent === 'click') {
                this.map.panTo(this.currentMarker.getPoint());
            }
        }
        return false;
    },
    onMarkerClick:function() {
        if (typeof this.preMarkerClick === 'function') {
            this.preMarkerClick();
        }
        if (typeof arguments[0].overlay !== 'undefined') {
            if (!arguments[0].isHidden()) {
                this.showOverlay(arguments[0]);
            }
        }
        else {
            arguments[0].openInfoWindowHtml(arguments[0].html);
        }
    },
    getMapCenter:function(zoom) {
        var zoom = zoom || 10;
        if (this.bounds) {
            // probably only want to do this if there are multiple points
            // and again, should change based on the zoom setting
            // var nePoint = this.bounds.getNorthEast();
            // this.bounds.extend(new google.maps.LatLng((nePoint.lat() + 4),
            //     nePoint.lng()));
            return this.bounds.getCenter();
        }
        else {
            return null;
        }
    },
    center:function() {
        this.map.checkResize();
        this.map.setCenter(this.getMapCenter());
    },
    centerAndZoom:function(options) {
        var zoom, vbounds;
        if (typeof options !== 'object' && parseInt(options) !== NaN) {
            zoom = parseInt(options);
            options = {};
        }
        else {
            options = Object.extend({
                zoom:null,
                zoomOffset:null,
                bounds:null
            }, options);
            zoom = options.zoom;
            zoomOffset = options.zoomOffset;
            vbounds = options.bounds;
        }
        this.map.checkResize();
        zoom = zoom || null;

        if (!vbounds) {
            // need to reset this because we added the buffer points last time
            this.bounds = new google.maps.LatLngBounds();

            for (var i=0;i<this.points.length;i++) {
                this.bounds.extend(this.points[i]);
            }
            vbounds = this.bounds;
        }

        // extending the bounds a touch to accomodate the height of the marker icons
        var proj = this.map.getCurrentMapType().getProjection();
        var boundsZoom = this.map.getBoundsZoomLevel(vbounds);
        var nePx = proj.fromLatLngToPixel(vbounds.getNorthEast(), boundsZoom);
        nePx = new GPoint(nePx.x-21, nePx.y-24);
        var ne = proj.fromPixelToLatLng(nePx,boundsZoom);
        vbounds.extend(ne);
        var swPx = proj.fromLatLngToPixel(vbounds.getSouthWest(), boundsZoom);
        swPx = new GPoint(swPx.x+21, swPx.y+24);
        var sw = proj.fromPixelToLatLng(swPx,boundsZoom);
        vbounds.extend(sw);

        if (vbounds && !zoom) {
            zoom = this.map.getBoundsZoomLevel(vbounds);
        }

        if ((zoom && zoom > this.maxZoom) || !zoom) {
            zoom = this.maxZoom;
        }

        if (options.zoomOffset) {
            zoom = zoom + options.zoomOffset;
        }

        this.map.setZoom(zoom);
        this.map.setCenter(vbounds.getCenter(), zoom);
    },
    isOverZoomed:function(minCount) {
        var zoomChecks = 0;
        var minCount = minCount || 3;
        this.zoomChecks++;

        var ps = this.map.getContainer().getElementsByTagName('p');
        var imgs = this.map.getContainer().getElementsByTagName('img');

        if (ps.length > minCount) {
            var pscount = ps.length;
            for (var i=pscount-1;i>=0;i--) {
                // at last check, this phrase was the same on all language sites
                // if we don't check the contents, the p's in a marker bubble can trigger this if shown before
                if (ps[i] && ps[i].innerHTML.indexOf('Try zooming out for a broader look.') > -1) {
                    this.map.zoomOut();
                    break;
                }
            }
        }
        // inserted to prevent the interval running the whole time someone is on the page.
        // zooming out 10 times should be enough
        if (this.zoomChecks > 75) { // || ps.length <= 0
            clearInterval(this.checkZoomInterval);
        }
    },
    checkMapZoom:function(minCount) {
        var minCount = minCount || 3;
        var tmap = this;
        this.checkZoomInterval = setInterval(function() {tmap.isOverZoomed(minCount);}, 50);
    }
});
Tablet.Maps = Tablet.Maps || {};


Tablet.Maps.initOverlays = function() {
    /* Base Overlay used for hotels on maps */
    Tablet.Maps.Overlay = function(marker, options) {
        this._marker = marker;
        if (this._marker.html) {
            this.html = this._marker.html;
        }
        this.options = Object.extend({
            enableHover:false,
            overlayClass:'hotelwindow'
        }, options);

        this._map = null;
        this._div = null;
    };
    Tablet.Maps.Overlay.prototype = new GOverlay();
    Tablet.Maps.Overlay.prototype.initialize = function(map) {
        this._map = map;
    };
    // the actual initialization done only when the overlay is show-n. otherwise,
    // that's a lot of code to run when the map loads
    Tablet.Maps.Overlay.prototype.initoverlay = function() {
        var div = $(document.createElement("div"));
        div.className = this.options.overlayClass;
        div.update(this.html);

        this._map.getPane(G_MAP_FLOAT_PANE).appendChild(div);

        div.style.visibility = 'hidden';
        div.style.top = (this._map.fromLatLngToDivPixel(this._marker.getPoint()).y) + 'px';
        div.style.left = (this._map.fromLatLngToDivPixel(this._marker.getPoint()).x) + 'px';
        div.setStyle({
            marginTop:'-'+(div.offsetHeight+12)+'px',
            marginLeft:'-'+(div.offsetWidth/2-4)+'px'
        });

        var removeOverlay = function(e) {
            this.hide();
        }

        if (this.options.enableHover) {
            // except, we don't want to hide it if we're in the overlay
            // google.maps.Event.addListener(this.marker, 'mouseout', removeOverlay.bind(this));
            div.observe('mouseleave', removeOverlay.bind(this));
        }

        var closeLink = (div.getElementsByClassName('close')) ? div.getElementsByClassName('close')[0] : null;
        if (closeLink && this._marker.tmap.closeOverlay) {
            closeLink.onclick = this._marker.tmap.closeOverlay.bind(this._marker.tmap);
        }

        this._div = div;
    };
    Tablet.Maps.Overlay.prototype.redraw = function(force) {
        if (!force) { return; }
        if (this._div) {
            this._div.style.top = (this._map.fromLatLngToDivPixel(this._marker.getPoint()).y) + 'px';
            this._div.style.left = (this._map.fromLatLngToDivPixel(this._marker.getPoint()).x) + 'px';
        }
    };
    Tablet.Maps.Overlay.prototype.show = function(){
        if (!this._div) {
            this.initoverlay();
        }
        this._div.style.visibility = '';
    };
    Tablet.Maps.Overlay.prototype.hide = function(){
        if (this._div) {this._div.style.visibility = 'hidden';}
    };
    Tablet.Maps.Overlay.prototype.remove = function(){
        if (this._div) {this._div.parentNode.removeChild(this._div);}
    };


    /* A maps overlay that behaves like a tooltip */
    Tablet.Maps.TipOverlay = function(marker, options) {
        this._marker = marker;
        this.options = Object.extend({
            html:''
        }, options);
    };
    Tablet.Maps.TipOverlay.prototype = new GOverlay();
    Tablet.Maps.TipOverlay.prototype.initialize = function(map) {
        this._map = map;
    };
    Tablet.Maps.TipOverlay.prototype.updateHtml = function(html) {
        if (this._div) {
            this._div.update(html);
        }
        else {
            this.options.html = html;
        }
    };
    // the actual initialization done only when the overlay is show-n. otherwise,
    // that's a lot of code to run when the map loads
    Tablet.Maps.TipOverlay.prototype.initoverlay = function() {
        var div = $(document.createElement("div"));
        div.update(this.options.html);
        div.className = 'markertip';
        div.style.position = 'absolute';
        div.style.visibility = 'hidden';
        this._map.getPane(G_MAP_FLOAT_PANE).appendChild(div);
        this._div = div;
    };
    Tablet.Maps.TipOverlay.prototype.remove = function(){
        if (this._div) {this._div.parentNode.removeChild(this._div);}
    }

    Tablet.Maps.TipOverlay.prototype.redraw = function(force){
        if (!force) {return;}
    }
    Tablet.Maps.TipOverlay.prototype.show = function(){
        if (!this._div) {
            this.initoverlay();
        }
        var divPos = this._map.fromLatLngToDivPixel(this._marker.getPoint());
        var cntPos = this._map.fromLatLngToContainerPixel(this._marker.getPoint());

        /*  when the map is moved, a div that contains the markers gets positive and
            negative left / top values that need to be accomodated when setting
            the left and top values for the bubble that should appear */
        var xoffset = divPos.x - cntPos.x;
        var yoffset = divPos.y - cntPos.y;

        var offsetleft = 5;
        var offsettop = 2;
        var left = cntPos.x + xoffset;
        var top = cntPos.y + yoffset; // - iconAnchor.y - this._div.clientHeight;

        // show tooltip to right and blow marker
        // unless right edge is off map
        // offset needs to be subtracted here, because, it does
        if ((left + this._div.offsetWidth - xoffset) > this._map.getSize().width) {
            left = left - this._div.offsetWidth - offsetleft;
        }
        else {
            left += offsetleft;
        }
        // if (x + this._div.offsetWidth)
        // or bottom edge is off map
        if ((top + this._div.offsetHeight - yoffset) > this._map.getSize().height) {
            var iconheight = this._marker.getIcon().iconSize.height;
            top = top - this._div.offsetHeight - (iconheight/2+8) - offsettop;
        }
        else {
            // top += offsettop;
        }

        this._div.style.left = left + 'px';
        this._div.style.top = top + 'px';
        // console.log("tip left: %s", this._div.style.left);
        this._div.style.visibility = 'visible';
    }

    Tablet.Maps.TipOverlay.prototype.hide = function(){
        if (this._div) {this._div.style.visibility = 'hidden';}
    }
}
/* requires on lib/prorotype.js, js/tablet/map.js, js/tablet/ui.js */
Tablet = Tablet || {};
Tablet.Guides = Tablet.Guides || {};

Tablet.Guides.Map = function() {
    this.init.apply(this, arguments);
};
Tablet.Guides.Map.prototype = {
    container:null,
    gmap:undefined,
    markers:null,
    init:function() {
        this.container = $(arguments[0]);
        this.options = Object.extend({
            parentContainer:null,
            points:[],
            showMapLink:null,
            hideMapLink:null,
            showOnLoad:false,
            gmapOptions:{},
            onMapToggle:null
        }, arguments[1] || {});

        if (!this.options.parentContainer) {
            this.parentContainer = $(this.container.parentNode);
        }
        if (this.options.showMapLink) {
            this.options.showMapLink.observe('click', this.onShowMapClick.bind(this));
        }
        if (this.options.hideMapLink) {
            this.options.hideMapLink.observe('click', this.onHideMapClick.bind(this));
        }
        if (this.options.showOnLoad) {
            this.onShowMapClick();
        }
    },
    initMap:function() {
        this.container.addClassName('loading');
        this.gmap = new Tablet.Map('gmap', this.options.mapOptions);
        this.initMapPoints();
        this.container.removeClassName('loading');
    },
    initMapPoints:function() {
        if (typeof this.options.points !== 'undefned') {
            this.markers = $A();
            var centerOptions = {};
            var pointzoom = null;
            var clickobserver;
            $A(this.options.points).each(function(point) {
                if (typeof point.zoom !== 'undefined') {
                    pointzoom = point.zoom;
                }
                this.markers.push(this.gmap.addPoint(
                    point.lat,
                    point.lng,
                    {
                        index:point.index || null,
                        tip:point.tip || '',
                        observer:point.observer || null
                    }
                ));
            }, this);
            if (pointzoom) {
                centerOptions.zoom = pointzoom;
            }
            this.container.setStyle({position:'absolute', display:'block', top:'-9999px', left:'-9999px'});
            this.gmap.centerAndZoom(centerOptions);
            this.container.setStyle({position:'', display:'', top:'', left:''});
        }
    },
    showMap:function() {
        Tablet.UI.fadeInAndTallify(this.parentContainer, this.container, {
            clearHeightAfterFinish:true,
            fadeOptions:{
                activeClass:'active'
            }
        });
        if (typeof this.gmap === 'undefined') {
            Event.onGoogleLoad(this.initMap.bind(this));
        }
        this.toggleMapLinks();
    },
    hideMap:function() {
        Tablet.UI.fadeOutAndShortify(this.parentContainer, this.container, {
            clearHeightAfterFinish:true,
            fadeOptions:{activeClass:'active'}
        });
        this.toggleMapLinks();
    },
    onShowMapClick:function(e) {
        if (e) { Event.stop(e); }
        this.showMap();
    },
    onHideMapClick:function(e) {
        if (e) { Event.stop(e); }
        this.hideMap();
    },
    toggleMapLinks:function() {
        if (this.options.hideMapLink.hasClassName('active')) {
            this.options.hideMapLink.removeClassName('active');
            this.options.showMapLink.removeClassName('inactive');
        }
        else {
            this.options.showMapLink.addClassName('inactive');
            this.options.hideMapLink.addClassName('active');
        }
        if (typeof this.options.onMapToggle === 'function') {
            this.options.onMapToggle();
        }
    }
};
Tablet = Tablet || {};
Tablet.Guides = Tablet.Guides || {};

Tablet.Guides.Guide = (function() {
    var mapHash = 'map';
    return {
        init:function() {
            mapitlink = Tablet.getElementByClassName(document, 'mapit', 'a');
            hidemaplink = Tablet.getElementByClassName(document, 'hidemap', 'a');

            if (mapitlink && hidemaplink && $('map')) {
                var places = $$('ul.places li');
                points = $A();
                var latel, lngel, zoomel, name, clickobserver;
                places.each(function(el, index) {
                    latel = el.down('p.latitude');
                    lngel = el.down('p.longitude');
                    zoomel = el.down('p.zoomlevel');
                    name = el.down('h3 a');
                    placeurl = name.href;
                    clickobserver = function(url) {
                        return function() {
                            window.location.href = url;
                        }
                    }(placeurl);
                    if (latel && lngel) {
                        points.push({
                            lat:latel.innerHTML,
                            lng:lngel.innerHTML,
                            index:index+1,
                            // zoom:(zoomel) ? zoomel.innerHTML : null, // we want the map to zoom this
                            tip:'<p>'+name.innerHTML+'</p>',
                            observer:clickobserver
                        });
                    }
                });
                new Tablet.Guides.Map($('map'), {
                    points:points,
                    showMapLink:mapitlink,
                    hideMapLink:hidemaplink,
                    showOnLoad:(window.location.hash.replace('#', '').toLowerCase() === mapHash),
                    onMapToggle:function() {
                        $('results').toggleClassName('mapped');
                    }
                });
            }
        }
    };
})();
document.observe('dom:loaded', function(e) {
    Tablet.Guides.Guide.init();
});