// functions for hiding the URL bar on the iPhone
/*
if (navigator.userAgent.indexOf('iPhone') != -1) {
	addEventListener("load", function() {
		setTimeout(hideURLbar, 0);
	}, false);
}

function hideURLbar() {
	window.scrollTo(0, 1);
}
*/
// If we have IE8, set a flag
Prototype.Browser.IE8 = Prototype.Browser.IE && navigator.userAgent.match(/MSIE 8/);

// functions for scrolling the photo list on the profile pages

// a photo is 80x70 with 5px margin on left and right and 3px border on bottom (total space: 85x73)
var _photoWidth = 90;
var _calendarDayWidth = 163;

// stupid helper to determine if a string is pure whitespace or not
String.prototype.isBlank = function() {
  return this.match(/^\s*$/);
}

// scroll the photo list to the next item, grabbing if necessary
function nextPhoto() {
  _currentPhoto++;
  photoHandler('bottom');
}

function scrollPhotoNext() {
  new Effect.Move('photo-list', { 
    x: -_photoWidth, 
    y: 0, 
    transition: Effect.Transitions.sinoidal, 
    duration: 0.25,
    queue: { position: 'end', scope: 'global' }
  });
}

// scroll the photo list to the previous item
function previousPhoto() {
  _currentPhoto--;
  photoHandler('top');
}

function scrollPhotoPrevious() {
  new Effect.Move('photo-list', {
    x: _photoWidth, 
    y: 0, 
    transition: Effect.Transitions.sinoidal, 
    duration: 0.25,
    queue: { position: 'end', scope: 'global' }
  });
}

// this is the main function that handles getting the next/previous photo in the list
// and putting it in the right place.
// 
// direction: 'top' for top, 'bottom' for bottom (previous/next)
function photoHandler(direction) {
  if(_currentPhoto < _firstLoaded || _currentPhoto > _lastLoaded) {
    // need to get the photo in the html
    var reqpos = ((direction == 'top') ? _currentPhoto - 1 : _currentPhoto + 1); // position we're requesting
    new Ajax.Request(_photoUrl, {parameters: 'position=' + reqpos + '&where=' + direction, asynchronous:true, evalScripts:true });
    if(_currentPhoto < _firstLoaded) _firstLoaded = _currentPhoto;
    if(_currentPhoto > _lastLoaded) _lastLoaded = _currentPhoto;
  } /*else {*/
    if(direction == 'top') scrollPhotoPrevious();
    else scrollPhotoNext();
/*  }*/
}

// set an element el to visible if visible is true
function setVisible(el, visible) {
  if(visible)
    $(el).show();
  else
    $(el).hide();
}

// Calendar scrolling functions, very very similar to above photo functions
function previousCalendarDay() {
  var _day = --_currentDay;
  calendarDayHandler('top', _day);
}

function nextCalendarDay() {
  var _day = ++_currentDay;
  calendarDayHandler('bottom', _day);
}

function calendarDayHandler(direction, offset) {
  var new_x;
  var months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];

  if(!offset) offset = _currentDay;
  
  // scroll
  if(direction == 'top') {
    new_x = _calendarDayWidth;
  } else {
    new_x = -_calendarDayWidth;
  }
  
  var needDate;
  if(offset < _firstDayLoaded || offset > _lastDayLoaded) {
    needDate = true;
  } else {
    needDate = false;
  }
  
  // update the edge dates
  var _jsBaseDate = _calendarBaseDate.replace(/-/g, '/');
  var baseDate = Date.parse(_jsBaseDate);
  baseDate += 86400000 * offset;
  var leftDate = baseDate - 86400000;
  var rightDate = baseDate + (86400000 * 5);
  
  $('previous-number').innerHTML = new Date(leftDate).getDate();
  $('next-number').innerHTML = new Date(rightDate).getDate();
  
  // and the month names
  var lastMonth = new Date(baseDate).getMonth() - 1;
  while(lastMonth < 0) lastMonth += 12;
  var nextMonth = lastMonth + 2;
  $('last-month').innerHTML = months[lastMonth % 12].substr(0, 3);
  $('next-month').innerHTML = months[nextMonth % 12].substr(0, 3);
  $('month-name').innerHTML = months[new Date(baseDate).getMonth()];
  
  // animate
  new Effect.Move('calendar-window', {
    x: new_x,
    y: 0,
    transition: Effect.Transitions.sinoidal,
    duration: 0.25,
    queue: {position: 'end', scope: 'global'},
    afterFinish: function(evt) {
      new Ajax.Request(_calendarDayUrl, {parameters: 'position=' + offset + '&where=' + direction + '&base_date=' + _calendarBaseDate + '&need_data=' + (needDate ? '1' : '0'), asynchronous:true, evalScripts:true, onComplete:function() {
        fixCalendarHeight();
      }});
    }
  });
  
  if(offset < _firstDayLoaded) _firstDayLoaded = offset;
  if(offset > _lastDayLoaded) _lastDayLoaded = offset;
}

function fixCalendarHeight() {
  var days = $$('div.calendardayouter');
  var max_height = days.max(function(obj, i) { return obj.getHeight(); });
  $('calendar-day-wrapper').setStyle({height: String(max_height) + 'px'});
}

Element.addMethods({
  fixSize: function(element) {
    var element = $(element);

    var _width = element.getWidth() + 'px';
    var _height = element.getHeight() + 'px';
    element.style.width = _width;
    element.style.height = _height;

    return true;
  }
})

function openIbox(url, params) {
  if(!params) var params = {};
  var title = '';
  
  if(showIbox(url,title,params)) {
		showBG();
		window.onscroll = maintPos;
		window.onresize = maintPos;
		
		if(!params.width || !params.height) resizeIbox();
	}
}

// Show the #message-response div with a message
function flashNotice(msg) {
  $('message-response-message').update(msg);
  new Effect.Appear($('message-response'), {duration: 0.5});
  $('message-response').show();
}

function hideNotice() {
  new Effect.Fade($('message-response'), {duration: 0.25});
  // $('message-response').hide();
}

// plan lightbox helpers

function createPlan(is_notice) {
  // show the create plan lightbox, loaded from somewhere else
  var new_plan_url = '/plan/edit/new?ibox=true';
  if(is_notice)
    new_plan_url += "&in_notice=true";
    
	openIbox(new_plan_url, {height:528, width:615});
}

function editPlan(date, options) {
  var the_options = $H({inPlanView:false}).merge(options);
  
  var edit_plan_url = '/plan/edit?ibox=true&date=DATE';
  edit_plan_url = edit_plan_url.replace(/DATE/, date);
  
  if(the_options.inPlanView) edit_plan_url += "&in_plan_view=true";
  
  openIbox(edit_plan_url, {height:528,width:615});
}

var needs_page_refresh = false;
function setNeedsPageRefresh() {
  needs_page_refresh = true;
}

// Our autocompleter thing
Autocompleter.AjaxCacher = Class.create();
Autocompleter.AjaxCacher.prototype = Object.extend(new Autocompleter.Base(), {
  lastEdit: '',
  cache: {},
  previous_url: {},
  doingAjax: false,
  
  initialize: function(element, update, array, url, options) {
    if(!options.onShow) options.onShow = function(element, update){
      if(!update.style.position || update.style.position=='absolute') {
        // IE8 needs to have it shown to properly position it
        if(Prototype.Browser.IE8) $(update).show();
        update.style.position = 'absolute';
        Position.clone(element, update, {
          setHeight: false,
          offsetTop: element.offsetHeight
        });
        update.style.width = (update.getWidth() - 2) + "px";
        if(Prototype.Browser.IE8) $(update).hide();
      }
      Effect.Appear(update,{duration:0.15});
    };
    
    this.baseInitialize(element, update, options);
    this.options.array = array;
    this.options.url = url;
    
    // bind our focus event to show the enter tooltip
    if(this.options.typeMessage) {
      Event.observe(this.element, 'focus', this.onFocus.bindAsEventListener(this));
      $(this.element).value = this.options.typeMessage;
      $(this.element).addClassName('input_placeholder');
    }
    
    // to clear out the input field on escape
    if(this.options.selectOnBlur) {
      Event.observe(this.element, 'blur', this.selectCurrentEntry.bindAsEventListener(this));
    } else if(this.options.clearInputField) {
      Event.observe(this.element, 'blur', this.clearInputField.bindAsEventListener(this));
    }
  },
  
  onFocus: function(event) {
    if(this.options.clearInputField) {
      this.element.value = '';
    } else if($F(this.element).blank()) {
      this.update.update('<ul><li class="type_something">' + this.options.typeMessage + '</li></ul>');
      this.show();
    }
    
    $(this.element).removeClassName('input_placeholder');
  },
  
  clearInputField: function(event) {
    $(this.element).value = this.options.typeMessage || '';
    if(this.options.typeMessage) $(this.element).addClassName('input_placeholder');
  },
  
  startIndicator: function() {
    if(this.options.loadingMessage) {
      // console.log('loading: ', this.options.loadingMessage);
      this.changed = true;
      this.update.update('<ul><li class="loading">' + this.options.loadingMessage + '</li></ul>');
      this.show();
    }
  }, 
  
  stopIndicator: function() {
  },
  
  selectCurrentEntry: function() {
    if($F(this.element).isBlank() || $F(this.element) == this.options.typeMessage) {
      $(this.element).value = this.options.typeMessage || '';
      if(this.options.typeMessage) $(this.element).addClassName('input_placeholder');
      
      return;
    }
    
    this.selectEntry();
    $(this.element).value = this.options.typeMessage || '';
    if(this.options.typeMessage) $(this.element).addClassName('input_placeholder');
  },
  
  // Remove the/a/an prefixes from strings
  cleanStringPrefix: function(str) {
    return str.replace(/^(the|a|an)\s+/i, '');
  },
  
  // get a list of values from the server
  // this should return everything that matches (or a very large number)
  // it expects a json response of an array of:
  //  {
  //    'id': N,
  //    'type': N,
  //    'name': N
  //  }
  cacheValuesFromServer: function(str, cache) {
    // console.log('cacheValuesFromServer: ', str, ' url=', this.options.url);
    var my = this;
    var the_url;
    if(typeof this.options.url == 'function') 
      the_url = this.options.url();
    else
      the_url = this.options.url;
    
    this.doingAjax = true;
    new Ajax.Request(the_url, {
      asynchronous: true,
      evalScripts: false,
      parameters: {value: str},
      method: 'get',
      onComplete: function(e) {
        my.doingAjax = false;
        // console.log('cacheValuesFromServer result: ', e);
        // console.log(' json: ', e.responseText.evalJSON());
        // console.log(' >> cache ', cache);
        cache['_' + str] = e.responseText.evalJSON();
        // console.log(' >> cache ', cache);
        my.changed = false;
        my.getUpdatedChoices();
        my.stopIndicator();
      }
    });
    this.startIndicator();
  },
  
  // this is a cache of partialChars prefixes
  // basically, instead of ajaxing the list every time, only do it once and
  // fulfill requests from that (stored in this.cache)
  getCachedValues: function(str, cache) {
    if(typeof this.options.url == 'function') {
      var new_url = this.options.url();
      // if the url would change, we need to clear the cache.  not perfect, but deal.
      if(new_url != this.previous_url) cache = {};
      this.previous_url = new_url;
    }
    
    // console.log('getCachedValues ', str, ', ', this.options.partialChars);
    if(str.length >= this.options.partialChars) {
      // strip the, a, an words from the beginning of str
      var the_str = this.cleanStringPrefix(str);
      var prefix = the_str.substr(0, this.options.partialChars).toLowerCase();
      var key = '_' + prefix;
      // console.log('str=[' + str + ']', 'the_str=[' + the_str + ']', 'prefix=[' + prefix + ']', 'key=[' + key + ']');
      
      // console.log('>> cache', cache);
      // console.log('prefix: ', prefix, 'key: ', key, ' val:', cache[key]);
      if(cache[key]) {
        // return from the cache
        return cache[key];
      } else {
        // get new values from server
        this.cacheValuesFromServer(prefix, cache);
        return [];
      }
    } else {
      return [];
    }
  },
  
  getUpdatedChoices: function() {
    this.updateChoices(this.options.selector(this));
    if(this.options.useAsEnteredMessage) this.setIndex(1);
    // this.render();
  },
  
  setIndex: function(idx) {
    if(!this.doingAjax) {
      if(idx < this.entryCount) this.index = idx;
      else this.index = 0;
      this.render();
    }
    // this.active = true;
  },
  
  onKeyPress: function(event) {
    if(this.active) {
      switch(event.keyCode) {
        case Event.KEY_TAB:
        case Event.KEY_RETURN:
          this.selectEntry();
          if(event.keyCode != Event.KEY_TAB) Event.stop(event);
          this.hide();
          this.active = false;
          return;
        case Event.KEY_ESC:
          this.hide();
          this.active = false;
          if(this.options.clearInputField) this.clearInputField();
          Event.stop(event);
          return;
        case Event.KEY_LEFT:
        case Event.KEY_RIGHT:
          return;
        case Event.KEY_UP:
          this.markPrevious();
          this.render();
          Event.stop(event);
          return;
        case Event.KEY_DOWN:
          this.markNext();
          this.render();
          Event.stop(event);
          return;
      }
    } else {
      if(event.keyCode==Event.KEY_RETURN || 
        (navigator.appVersion.indexOf('AppleWebKit') > 0 && event.keyCode == 0)) {
          if(this.options.disableEnterKey) Event.stop(event);
          return;
      } else if(event.keyCode == Event.KEY_TAB) {
        return;
      }
        
    }
    this.lastEdit = this.element.value;

    this.changed = true;
    this.hasFocus = true;

    if(this.observer) clearTimeout(this.observer);
      this.observer = 
        setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
  },
  
  // Override the parent markPrevious/markNext to not scroll the view
  markPrevious: function() {
    if(this.index > 0) this.index--;
      else this.index = this.entryCount-1;
  },
  markNext: function() {
    if(this.index < this.entryCount-1) this.index++;
      else this.index = 0;
  },
  
  ZZupdateElement: function(selectedElement) {
    if(this.options.updateElement) {
      this.options.updateElement(selectedElement);
      return;
    }
    var value = '';
    if (this.options.select) {
      var nodes = document.getElementsByClassName(this.options.select, selectedElement) || [];
      if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
    } else
      value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
    
    var lastTokenPos = this.findLastToken();
    if (lastTokenPos != -1) {
      var newValue = this.element.value.substr(0, lastTokenPos + 1);
      var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/);
      if (whitespace)
        newValue += whitespace[0];
      this.element.value = newValue + value;
    } else {
      tagArray = this.element.value.trim().split(' ')
      if (!this.getCurrentTag()) return;  // We had a problem getting the current tag, just return?
      tagArray[this.currentTag.index] = value;
      this.element.value = tagArray.join(' ');
    }
    this.element.focus();
    
    if (this.options.afterUpdateElement)
      this.options.afterUpdateElement(this.element, selectedElement);
  },
  
  ZZfixIEOverlapping: function() {
    if(!$(this.update).up('#ibox_wrapper') || false) {
      Position.clone(this.update, this.iefix, {setTop:(!$(this.update).style.height)});
      $(this.iefix).style.zIndex = 10000;
      $(this.update).style.zIndex = 20000;
      Element.show(this.iefix);
    }
  },

  setOptions: function(options) {
    this.options = Object.extend({
      choices: 9,
      partialSearch: true,
      partialChars: 2,
      minChars: 1,
      ignoreCase: true,
      fullSearch: false,
      url: null,
      cache: {},
      loadingMessage: null, // "Loading..."
      typeMessage: null, // "Enter the name of a venue"
      useAsEnteredMessage: null, // 'Use "#{text}"', is a prototype string template
      clearInputField: true, // clear input field upon selection/blur, this also moves typeMessage *into* the field when not focused, instead of below when focused.
      selectOnBlur: false, // select current entry on blur
      disableEnterKey: false, // Disable ENTER key on this field always.
      
      // this is a simple formatter example, that just highlights the entry in item.name
      // and returns it in an <li>
      formatter: function(instance, item) {
        // console.log("In formatter: instance=", instance, ', item=', item);
        var ret = '';
        var entry = instance.cleanStringPrefix(instance.getToken().strip());
        var entry = instance.getToken().strip();
        var replacement = null;
        
        if(instance.options.ignoreCase) {
          replacement = new RegExp('(' + entry.gsub(/\(/, '\\(').gsub(/\)/, '\\)') + ')', 'ig');
        } else {
          replacement = new RegExp('(' + entry.gsub(/\(/, '\\(').gsub(/\)/, '\\)') + ')', 'g');
        }
        
        ret = "<li id=\"" + item.id + "\">" + item.name.replace(replacement, '<strong>$1</strong>') + "</li>";
        return ret;
      },
      selector: function(instance) {
        var ret       = []; // Beginning matches
        var partial   = []; // Inside matches
        var entry     = instance.getToken();
        
        // clean entry
        entry = entry.strip();//.split(' ').pop();
        //entry = instance.cleanStringPrefix(entry);
        
        var count     = 0;
        
        var possibilities = instance.getCachedValues(entry, instance.options.cache);

        var matchtest = instance.options.ignoreCase ?
          new RegExp('\\s' + entry.gsub(/\(/, '\\(').gsub(/\)/, '\\)'), 'i') :
          new RegExp('\\s' + entry.gsub(/\(/, '\\(').gsub(/\)/, '\\)'), '');

        if(instance.options.useAsEnteredMessage) {
          // custom
          var tmpl = new Template('<li id="text_#{text}">' + instance.options.useAsEnteredMessage + '</li>');
          // always keep the prefix when using as entered
          ret.push(tmpl.evaluate({text: instance.getToken().strip()}));
        }

        for (var i = 0; i < possibilities.length &&  
          ret.length < instance.options.choices ; i++) { 

          var elem = possibilities[i];
          var name = elem.name;
          var id = elem.id;
          var type = elem.type;
          
          var foundPos = instance.options.ignoreCase ? 
            name.toLowerCase().indexOf(entry.toLowerCase()) : 
            name.indexOf(entry);

          if(foundPos == 0 && name.length >= instance.options.partialChars) {
            // match at beginning
            ret.push(instance.options.formatter(instance, elem));
          } else if(name.length >= instance.options.partialChars && instance.options.partialSearch && foundPos != -1) {
            // inner match, make sure it's at the start of a word
            // console.log('name=', name, ' matchtest=', matchtest, ' test=', matchtest.test(name), matchtest.test(name));
            if(!instance.options.fullSearch && matchtest.test(name)) {
              partial.push(instance.options.formatter(instance, elem));
            }
          }
        }
        
        // console.log('ret=', ret, ' partial=', partial);
        
        if (partial.length)
          ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
          
        return "<ul>" + ret.join('') + "</ul>";
      }    
    }, options || {});
  }
});

// format the hash just like nameWithNeighborhood, just with the photo ONLY
function nameWithPhoto(instance, item) {
  // console.log("In formatter: instance=", instance, ', item=', item);
  var ret = '';
  var entry = instance.cleanStringPrefix(instance.getToken().strip());
  var entry = instance.getToken().strip();
  var replacement = null;
  
  if(instance.options.ignoreCase) {
    replacement = new RegExp('(' + entry.gsub(/\(/, '\\(').gsub(/\)/, '\\)') + ')', 'ig');
  } else {
    replacement = new RegExp('(' + entry.gsub(/\(/, '\\(').gsub(/\)/, '\\)') + ')', 'g');
  }
  
  var highlighted_name = item.name.replace(replacement, '<strong class="highlight">$1</strong>');
  
  ret = '<li id="' + item.id + '">';

  ret += '<img src="' + item.photo + '" alt="" width="20" height="20" />';
  
  ret += highlighted_name;
  
  ret += '</li>'

  return ret;
}

// formats the hash into something visible that fits in a <li></li>
// +entry+ is the value submitted
function nameWithNeighborhood(instance, item) {
  // console.log("In formatter: instance=", instance, ', item=', item);
  var ret = '';
  var entry = instance.cleanStringPrefix(instance.getToken().strip());
  var entry = instance.getToken().strip();
  var replacement = null;
  
  if(instance.options.ignoreCase) {
    replacement = new RegExp('(' + entry.gsub(/\(/, '\\(').gsub(/\)/, '\\)') + ')', 'ig');
  } else {
    replacement = new RegExp('(' + entry.gsub(/\(/, '\\(').gsub(/\)/, '\\)') + ')', 'g');
  }
  
  var highlighted_name = item.name.replace(replacement, '<strong class="highlight">$1</strong>');
  
  ret = '<li id="' + item.id + '">';

  ret += '<img src="' + item.photo + '" alt="" width="32" height="32" />';
  
  ret += highlighted_name;
  ret += '<div class="informal location">';
  if(item.neighborhood) ret += item.neighborhood;
  if(item.neighborhood && item.city) ret += ', ';
  if(item.city) ret += item.city
  ret += '</div>';
  
  ret += '</li>'

  return ret;
}

function nameWithBusinessType(instance, item) {
  var ret = '';
  var entry = instance.cleanStringPrefix(instance.getToken().strip());
  var entry = instance.getToken().strip();
  var replacement = null;
  
  if(instance.options.ignoreCase) {
    replacement = new RegExp('(' + entry.gsub(/\(/, '\\(').gsub(/\)/, '\\)') + ')', 'ig');
  } else {
    replacement = new RegExp('(' + entry.gsub(/\(/, '\\(').gsub(/\)/, '\\)') + ')', 'g');
  }
  
  var highlighted_name = item.name.replace(replacement, '<strong class="highlight">$1</strong>');
  
  ret = '<li id="' + item.id + '" data-hash="' + Object.toJSON($H(item)).gsub(/"/, '&quot;') + '">';

  ret += highlighted_name;
  ret += '<div class="informal location">';
  if(item.business_type) ret += item.business_type;
  ret += '</div>';
  
  ret += '</li>'

  return ret;
}

function setDefaultValue(el, text) {
  if($(el) && $F(el) == '') $(el).value = text;
}

function clearDefaultValue(el, text) {
  if($(el) && $F(el) == text) $(el).value = '';
}

// Check all checkboxes that are contained in some container
function checkAll(container, el) {
  container = $(container);
  el = $(el);
  if(!container) container = el.up('FORM');

  var new_state = el.checked;
  
  $(container).getElementsBySelector('input[type=checkbox]').each(function(e){
    if(!$(e).disabled) $(e).checked = new_state;
  });
  
  if(el) (function() { el.checked = new_state; }).defer();
}

// Dropdown things
HeaderMenu = (function() {
  var show_timer;
  var hide_timer;
  var header_dropdown_shown = false;
  
  var effect_options = {
    duration: 0.05,
    queue: 'end'
  };
  
  // public methods
  return {
    show: function() {
      if(!header_dropdown_shown) {
        Effect.SlideDown('loginbox', {duration:0.05, queue:'end'});
        header_dropdown_shown = true;
        clearTimeout(show_timer);
        show_timer = null;
      }
    },
    hide: function() {
      if(header_dropdown_shown) {
        Effect.SlideUp('loginbox', effect_options);
        header_dropdown_shown = false;
        clearTimeout(hide_timer);
        hide_timer = null;
      }
    },
    toggle: function() {
      if(header_dropdown_shown) {
        this.hide();
      } else {
        this.show();
      }
    },
    
    // event handlers
    mouseOver: function(e) {
      if(!header_dropdown_shown) {
        // handle setting timer to show
        if(!show_timer) show_timer = setTimeout(this.show, 150);
      } else {
        clearTimeout(hide_timer);
        hide_timer = null;
      }
    },
    mouseOut: function(e) {
      if(!header_dropdown_shown) {
        clearTimeout(show_timer);
        show_timer = null;
      } else {
        if(!hide_timer) hide_timer = setTimeout(this.hide, 250);
      }
    }
  }
})();

// This will disable el with msg if fn returns true and call el.form.submit(),
// otherwise it will return false
function disableOrSubmitWithValidation(el, fn, msg) {
  if(fn()) {
    el.disabled = true;
    el.value = msg;
    el.form.submit();
  } else {
    return false;
  }
}

// Disable a submit button that is an ajax form submit.
// Basically the rails helper, just without the reset.
function disableFormSubmit(el, msg) {
  if (window.hiddenCommit) { 
    window.hiddenCommit.setAttribute('value', el.value); 
  } else { 
    hiddenCommit = el.cloneNode(false);
    hiddenCommit.setAttribute('type', 'hidden');
    el.form.appendChild(hiddenCommit); 
  }
  
  el.setAttribute('originalValue', el.value);
  // defering this allows Safari to submit without needing the onsubmit/submit action called explicitly,
  // while preventing Firefox from submitting twice.
  (function() {
    el.disabled = true;
    el.value = msg;
  }).defer();
  
  // Safari needs, Fx not
  //result = (el.form.onsubmit ? (el.form.onsubmit() ? el.form.submit() : false) : el.form.submit());
  var result = null;
  return result;
}

// Disables a submit button with a message.
// Doesn't do anything else but change some attributes.
function disableButton(el, msg) {
  if(el = $(el)) {
    if(el.tagName == 'A') {
      el.originalValue = el.innerHTML;
      el.disabled = true;
      el.innerHTML = msg;
    } else {
      el.setAttribute('originalValue', el.value);
      el.disabled = true;
      el.value = msg;
    }
  }
}

// Reenable's a submit button disabled with disableButton
function enableButton(el) {
  if(el = $(el)) {
    if(el.tagName == 'A') {
      el.disabled = false
      el.innerHtml = el.originalValue;
      el.originalValue = null;
    } else {
      el.value = el.getAttribute('originalValue');
      el.setAttribute('originalValue', null);
      el.disabled = false;
    }
  }
}

// Datejs helper stuff
function dateInputizer(inputel, previewel, initial_date, dropdownel, inputwrapel, dropdown_prefix) {
  // console.log('dateInputizer: ' + inputel);
  updateDateInput(inputel, previewel, initial_date);
  $(dropdownel).hide();
  $(inputwrapel).show();
  $(inputel).observe('keyup', function() {
    dateInputChanged(inputel, dropdown_prefix, previewel);
  });
  $(inputel).dateChanged = function() {
    dateInputChanged(inputel, dropdown_prefix, previewel);
  };
}

function timeInputizer(inputel, previewel, initial_date, dropdownel, inputwrapel, dropdown_prefix) {
  // console.log('timeInputizer: ' + inputel);
  updateTimeInput(inputel, previewel, initial_date);
  $(dropdownel).hide();
  $(inputwrapel).show();
  $(inputel).observe('keyup', function() {
    timeInputChanged(inputel, dropdown_prefix, previewel);
  });
  $(inputel).timeChanged = function() {
    timeInputChanged(inputel, dropdown_prefix, previewel);
  };
}

// New date handler
// This handles both dates and times
DateTextInput = Class.create();
DateTextInput.addMethods({
  initialize: function(options) {
    var default_options = {
      inputel: null,              // id of textfield input element
      previewel: null,            // id of preview element, content is updated
      initial_date: null,         // initial date for the text field
      dropdown_wrapper: null,     // id for wrapper for the dropdown elements
      input_wrapper: null,        // id for wrapper for the input field
      dropdown_prefix: null,      // prefix for the dropdown date/time inputs
      mode: 'date',               // which is this, date or time input.
      wrapper_type: 'div',        // what type of element to create wrappers with
      preview_container: 'span',  // what type of element holds the preview date
      preview_format: null,       // formatting of preview
      
      // callbacks
      onInvalid: null,            // called when an invalid input is entered
      onValid: null,              // called when a valid input is entered
      onEmpty: null,              // called when an empty input is made
      onNotEmpty: null            // called when input is not empty
    };
    this._useAlternativeNames = false; // use _day/_month/_year suffixes instead of _1i,_2i,_3i
    
    this.options = Object.extend(Object.extend({}, default_options), options || {});
    // this.options = $H(this.options).merge(options);
    
    this.event_function = null;
    
    // Build the input stuff
    if(!this.options.dropdown_wrapper || !this.options.input_wrapper || !this.options.inputel || !this.options.previewel) 
      this.buildInputElements();
    
    if(this.options.mode == 'time') {
      if(this.options.initial_date) this.updateTimeInput(this.options.inputel, this.options.previewel, this.options.initial_date);
      
      // hide the dropdown, show the input
      $(this.options.input_wrapper).show();
      $(this.options.dropdown_wrapper).hide();
      
      this.event_function = this.timeInputChanged;
      if(!this.options.preview_format) this.options.preview_format = 'h:mm tt';
    } else { // date
      if(this.options.initial_date) this.updateDateInput(this.options.inputel, this.options.previewel, this.options.initial_date);
      
      // hide the dropdown, show the input
      $(this.options.input_wrapper).show();
      $(this.options.dropdown_wrapper).hide();
      
      this.event_function = this.dateInputChanged;
      if(!this.options.preview_format) this.options.preview_format = 'dddd, MMMM dd, yyyy';
    }
    
    // register event listeners
    $(this.options.inputel).observe('keyup', this.eventHappened.bindAsEventListener(this));
    $(this.options.inputel).dateChanged = this.eventHappened.bindAsEventListener(this);
  },
  
  // Build the input elements that replace the dropdown.
  // The dropdowns should be in a wrapper, they will be wrapped again and a new wrapper with the input field and preview will be added to that wrapper.
  buildInputElements: function() {
    // get the elements
    var dropdown_parent;
    if(this.options.mode == 'time') {
      if($(this.options.dropdown_prefix + '_4i')) {
        dropdown_parent = $(this.options.dropdown_prefix + '_4i').up();
      } else if($(this.options.dropdown_prefix + '_hour')) {
        dropdown_parent = $(this.options.dropdown_prefix + '_hour').up();
        this._useAlternativeNames = true;
      } else {
        dropdown_parent = null;
      }
    } else {
      if($(this.options.dropdown_prefix + '_1i')) {
        dropdown_parent = $(this.options.dropdown_prefix + '_1i').up();
      } else if($(this.options.dropdown_prefix + '_month')) {
        dropdown_parent = $(this.options.dropdown_prefix + '_month').up();
        this._useAlternativeNames = true;
      } else {
        dropdown_parent = null;
      }
    }
    
    if(!dropdown_parent) return false;
    
    var dropdown_html = dropdown_parent.innerHTML;
    var wrapper = new Element(this.options.wrapper_type);
    wrapper.update(dropdown_html);
    this.options.dropdown_wrapper = $(wrapper).identify();
    dropdown_parent.update(wrapper);
    
    // create the input
    var inputel = new Element('input', {type: 'text'});
    this.options.inputel = $(inputel).identify();
    
    // create the preview
    var previewel = new Element(this.options.preview_container);
    this.options.previewel = $(previewel).identify();
    
    // put them together
    var input_wrapper = new Element(this.options.wrapper_type);
    this.options.input_wrapper = $(input_wrapper).identify();
    input_wrapper.insert(inputel);
    input_wrapper.insert(previewel);
    
    // and add it to the parent
    dropdown_parent.insert(input_wrapper);
  },
  
  eventHappened: function() {
    this.event_function();
  },
  
  dateInputChanged: function(inputel, dropdown_prefix, previewel) {
    inputel = inputel || this.options.inputel;
    dropdown_prefix = dropdown_prefix || this.options.dropdown_prefix;
    previewel = previewel || this.options.previewel;
    
    var the_input = $F(inputel);
    var the_month = '',
        the_year = '',
        the_day = '';
        
    if(the_input.isBlank() && this.options.onEmpty) {
      this.options.onEmpty(this);
    } else {
      if(this.options.onNotEmpty) this.options.onNotEmpty(this);
      
      var d = Date.parse(the_input);
      if(d) {
        if(this.options.onValid) this.options.onValid(this);
        $(previewel).update(d.toString(this.options.preview_format));
        
        // IE(7) returns the full year, everything else returns year-1900.  So allow for both.
        the_year = (d.getYear() > 1900) ? d.getYear() : 1900 + d.getYear();
        the_month = d.getMonth() + 1;
        the_day = d.getDate();
      } else {
        if(this.options.onInvalid) this.options.onInvalid(this);
        $(previewel).update("Unknown date");
      }
    }

    $(dropdown_prefix + (this._useAlternativeNames ? '_month' : '_2i')).value = the_month;
    $(dropdown_prefix + (this._useAlternativeNames ? '_day' : '_3i')).value = the_day;
    $(dropdown_prefix + (this._useAlternativeNames ? '_year' : '_1i')).value = the_year;
  },
  
  timeInputChanged: function(inputel, dropdown_prefix, previewel) {
    inputel = inputel || this.options.inputel;
    dropdown_prefix = dropdown_prefix || this.options.dropdown_prefix;
    previewel = previewel || this.options.previewel;

    var the_hour = '',
        the_minute = '',
        the_ampm = '';

    if($F(inputel).isBlank()) {
      // zero out the times
      $(previewel).update("");
    } else {
      var d = Date.parse($F(inputel));
      if(d) {
        $(previewel).update(d.toString(this.options.preview_format));
        var hours = d.getHours();
        var minutes = d.getMinutes();
        var offset = 0;

        if(hours >= 12) {
          offset = 12;
          hours -= 12;
        }

        if(hours < 10 && hours > 0) hours = "0" + String(hours);
        if(minutes < 10) minutes = "0" + String(minutes);
        
        the_hour = hours;
        the_minute = minutes;
        the_ampm = offset;
      } else {
        $(previewel).update('');
      }
    }

    $(dropdown_prefix + (this._useAlternativeNames ? '_hour' : '_4i')).value = the_hour; // Hour
    $(dropdown_prefix + (this._useAlternativeNames ? '_minute' : '_5i')).value = the_minute; // Minute
    $(dropdown_prefix + (this._useAlternativeNames ? '_ampm' : '_7i')).value = the_ampm; // 0=AM 12=PM
  },

  updateDateInput: function(el, previewel, date_value) {
    var d = Date.parse(date_value);
    if(d) {
      if(Date.compare(d, Date.today()) == 0) $(el).value = "Today";
      else if(Date.compare(d, Date.today().add(1).days()) == 0) $(el).value = 'Tomorrow';
      else $(el).value = d.toString('M/d/yyyy');
      $(previewel).update(d.toString('dddd, MMMM dd, yyyy'));
    }
  },

  updateTimeInput: function(el, previewel, time_value) {
    var t = Date.parse(time_value);
    if(t) {
      $(el).value = t.toString('h:mm tt');
      $(previewel).update(t.toString('h:mm tt'));
    }
  },

  // utility methods
  _getDateFromDropdowns: function(prefix) {
    var month = $F(prefix + '_2i');
    var day =   $F(prefix + '_3i');
    var year =  $F(prefix + '_1i');

    day = (day.length == 1 ? '0' + day : day);
    month = (month.length == 1 ? '0' + month : month);

    return year + '-' + month + '-' + day;
  },
  _getTimeFromDropdowns: function(prefix) {
    var hour =   $F(prefix + '_4i');
    var minute = $F(prefix + '_5i');
    var offset = $F(prefix + '_7i');

    return String(Number(hour) + Number(offset)) + ':' + minute;
  }
});
// compatibility aliases
window.getDateFromDropdowns = DateTextInput.prototype._getDateFromDropdowns;
window.getTimeFromDropdowns = DateTextInput.prototype._getTimeFromDropdowns;
window.dateInputChanged = DateTextInput.prototype.dateInputChanged;
window.timeInputChanged = DateTextInput.prototype.timeInputChanged;
window.updateDateInput = DateTextInput.prototype.updateDateInput;
window.updateTimeInput = DateTextInput.prototype.updateTimeInput;

// Photo upload selector thing
MultiUploadSelector = Class.create();
MultiUploadSelector.addMethods({
  initialize: function(the_options) {
    this.options = {}
    this.options.input_container = the_options.input_container || null;
    this.options.file_list = the_options.file_list || null;
    this.options.maximum = the_options.maximum || 8;
    this.options.template = the_options.template || '<span class="filename">#{filename}</span><input type="text" name="caption[]" /><p>Caption or Title <a href="#" class="remove-link">Remove</a></p>';
    this.options.item_container_tag = the_options.item_container_tag || 'li';
    this.options.item_container_attributes = the_options.item_container_attributes || {};
    this.options.file_name = the_options.file_name || 'image[]';
    this.options.input_id = the_options.input_id || null

    this.num_files = 0;
    
    // set the input container
    this.input_container = $(this.options.input_container);
    this.file_list = $(this.options.file_list);
    this.template = new Template(this.options.template);
    
    // attach to the existing input element
    if(this.options.input_id) 
      this.input_el = $(this.options.input_id);
    else
      this.input_el = $(this.input_container).down('input[type="file"]');
    
    this.input_el.boundonchange = this.fileSelected.bindAsEventListener(this);
    this.input_el.observe('change', this.input_el.boundonchange);
  },
  
  fileSelected: function(e) {
    // move the input element to the file_list with other items
    var new_li = new Element(this.options.item_container_tag, this.options.item_container_attributes);
    new_li.update(this.template.evaluate({filename: this.input_el.value}));
    new_li.appendChild(this.input_el);
    
    // hook up the removal thing, which is whatever with class="remove-link"
    var self = this;
    var remove_link = $(new_li).select('.remove-link').each(function(el) {
      el.observe('click', self.removeFile.bindAsEventListener(self));
    });

    // get the old attributes and hide it
    var old_attributes = {
      name: this.input_el.readAttribute('name'),
      style: this.input_el.readAttribute('style'),
      className: this.input_el.readAttribute('class'),
      size: this.input_el.readAttribute('size'),
      type: 'file'
    };

    this.input_el.stopObserving('change', this.input_el.boundonchange);
    this.input_el.writeAttribute('name', this.options.file_name);
    this.input_el.boundonchange = null;
    this.input_el.hide();
    this.file_list.appendChild(new_li);
    
    // create new input and add it
    this.num_files++;
    
    if(this.num_files < this.options.maximum) {
      // don't add any more now
      var new_input = new Element('input', old_attributes);
      this.input_el = $(new_input);
      this.input_container.appendChild(this.input_el);
    
      // add bindings
      this.input_el.boundonchange = this.fileSelected.bindAsEventListener(this);
      this.input_el.observe('change', this.input_el.boundonchange);
    }
    
    resizeIbox();
  },
  
  removeFile: function(e) {
    var li = e.element().up(this.options.item_container_tag);
    li.remove();
  }
});

function parseDate(lbkey) {
  var input_el = $("x-date-input-" + lbkey);
  var output_el = $("x-date-" + lbkey);
  var preview_el = $("x-date-preview-" + lbkey);
  var error_el = $("x-date-error-" + lbkey);
  
  var input = $F(input_el);

  if(input.isBlank()) {
    error_el.show();
    preview_el.update('').hide();
    resizeIbox();
    return false;
  } else {
    var parsed_date = Date.parse(input);
    if(parsed_date) {
      error_el.hide();
      preview_el.update(parsed_date.toString('dddd, MMMM dd, yyyy')).show();
      output_el.value = parsed_date.toString('yyyy-MM-dd');
      return true;
    } else {
      preview_el.update('').hide();
      error_el.show();
      resizeIbox();
      return false;
    }
  }
}

// Login lightbox handlers
Slzr = Class.create();
Slzr.Login = {
  switchLogin: function(el) {
    var root = $(el).up('div.modal_container');
    
    // get the class from this button
    var service;
    if(el.className.match(/button-login-([a-z]+)/)) {
      service = RegExp.$1;
    }
    
    if(service) {
      // update button
      $(root).select('div.login_choices a.current').invoke('removeClassName', 'current');
      $(el).addClassName('current');
      
      // update visible div
      $(root).select('div[class^=container-login]').invoke('hide');
      $(root).down('div.container-login-' + service).show();
    }
    
    resizeIbox();
  }
};

// Base class for the new header menus
Slzr.Menu = Class.create({
  // Initialize a new menu instance.
  //
  // +el+ is the element that triggers the menu
  // +menu_el+ is the element that contains the menu items
  initialize: function(el, menu_el) {
    this.el = $(el);
    this.menu_el = $(menu_el);
    this.menu_shown = false;
    
    this.positionMenu();
    this.hideMenu();
    
    Event.observe(window, 'resize', this.positionMenu.bind(this));
    
    if(!this.ie_iframe_fix && Prototype.Browser.IE && navigator.userAgent.match(/MSIE [56]/)) {
      this.ie_iframe_fix = new Element('iframe', {src: 'javascript:false;', frameborder: 0, tabindex: -1});
      $(this.ie_iframe_fix).setStyle({display: 'block', position: 'absolute', zIndex: -1});
    }
  },
  showMenu: function() {
    this.menu_shown = true;
    this.menu_el.show();
    this.showIESelectFix();
  },
  hideMenu: function() {
    this.menu_shown = false;
    this.menu_el.hide();
    this.hideIESelectFix();
  },
  showIESelectFix: function() {
    if(Prototype.Browser.IE && navigator.userAgent.match(/MSIE [56]/)) {
      var position = this.menu_el.positionedOffset();
      var size = this.menu_el.getDimensions();
      $(this.ie_iframe_fix).setStyle({top: position.top + 'px', left: position.left + 'px', width: size.width + 'px', height: size.height + 'px'});
      this.menu_el.insert({before: this.ie_iframe_fix});
      // alert($(this.ie_iframe_fix).outerHTML);
    }
  },
  hideIESelectFix: function() {
    if(Prototype.Browser.IE && navigator.userAgent.match(/MSIE [56]/) && this.ie_iframe_fix) $(this.ie_iframe_fix).remove();
  },
  // Size and position the menu items below anchor_el
  //
  // Options:
  //   +alignment+ How to align the menu relative to anchor_el, either 'left' or 'center'
  //   +width+ The width of the list element, can be 'none' for no sizing, 
  //           'match' to match anchor's width, 
  //           or a valid css value.
  positionMenu: function(anchor_el, options) {
    var opts = Object.extend({
      alignment: 'left',
      width: 'auto',
      match_width_of: null,
      top_offset: 0,
      width_offset: 0
    }, options);
    var anchor = $(anchor_el);
    
    // check for overrides
    if(this.menu_el.getAttribute('data-menu-width')) opts.width = this.menu_el.getAttribute('data-width');
    if(this.menu_el.getAttribute('data-menu-alignment')) opts.alignment = this.menu_el.getAttribute('data-menu-alignment');
    
    // Calculate top and left
    var position = anchor.positionedOffset();
    var top = position.top + anchor.getHeight();
    
    var left = position.left;
    var right = position.left + anchor.getWidth();
    
    var width = opts.width;
    
    if(opts.width == 'match') {
      if(opts.match_width_of)
        width = ($(opts.match_width_of).getWidth() + opts.width_offset);
      else
        width = (anchor.getWidth() + opts.width_offset);
    } else if(opts.width == 'none') {
      width = null;
    } else if(opts.width == 'auto') {
      // IE needs a set width, otherwise they get really thin.
      // 40 is the padding, etc. on the menu elements.  need to get a way
      // to calculate it automatically with prototype
      width = this.menu_el.getWidth() - 40;
    }
        
    var position_style = {};
    
    if(opts.alignment == 'center') {
      var a_width = anchor.getWidth();
      var m_width = this.menu_el.getWidth();
      
      if(a_width > m_width) {
        // anchor wider than menu
        left = left + ((a_width - m_width) / 2);
      } else {
        // menu wider than anchor
        left = left - ((m_width - a_width) / 2);
      }
      
      position_style.left = left + "px";
    } else if(opts.alignment == 'left') {
      position_style.left = left + "px";
    } else if(opts.alignment == 'right') {
      position_style.left = (right - (width ? width : this.menu_el.getWidth())) + "px";
      // position_style.width = (width ? width : this.menu_el.getWidth()) + "px";
    }
        
    // console.log('Menu for', this.menu_el, 'position', position_style);
    
    this.menu_el
      .absolutize()
      // .clonePosition(anchor)
      .setStyle({width: (width ? width + "px" : null), height: "auto", top: (top + opts.top_offset) + "px"})
      .setStyle(position_style);
  }
});

// Subclass of Slzr.Menu for the locale menu, which appears on
// click and overlays the activation element.
Slzr.ClickMenu = Class.create(Slzr.Menu, {
  initialize: function($super, el, menu_el) {
    $super(el, menu_el);
    
    var $this = this;
    
    $(el).observe('click', this.menuClick.bind(this));
    $(document).observe('click', this.nonMenuClick.bind(this));
    
    // resize the anchor element to the width of the menus
    // var menu_width = this.menu_el.getWidth() - 55; // 55 is the horizontal padding on el
    // $(el).setStyle({width: menu_width + 'px'});
  },
  positionMenu: function($super) {
    $super(this.el.up('ul'), {width: 'match', match_width_of: this.el.up('ul').down(), width_offset: -8, top_offset: -8});
  },
  // Handler for clicking the link element to show the options
  menuClick: function(ev) {
    if(this.menu_shown)
      this.hideMenu();
    else
      this.showMenu();
    ev.stop();
  },
  // Handler for the rest of the page to hide the menu when it is showing.
  nonMenuClick: function(ev) {
    if(this.menu_shown) {
      //if(ev.target.tagName != 'A') 
      this.hideMenu();
    }
  }
});

// Subclass of Slzr.Menu for the other header menus, which appear on hover
// and do not overlay but appear under the activation element.
Slzr.HoverMenu = Class.create(Slzr.Menu, {
  initialize: function($super, el, menu_el) {
    $super(el, menu_el);
    this.hide_timer = null;
    
    $(el)
      .observe('mouseover', this.mouseover.bind(this))
      .observe('mouseout', this.mouseout.bind(this));
    $(menu_el)
      .observe('mouseover', this.mouseover.bind(this))
      .observe('mouseout', this.mouseout.bind(this));
  },
  positionMenu: function($super) {
    $super(this.el, {alignment: 'left', width: 'none'});
  },
  mouseover: function(ev) {
    if(!this.menu_shown) {    
      if(Prototype.Browser.IE) this.positionMenu();
      this.showMenu();
      ev.stop();
    }
    clearTimeout(this.hide_timer);
    this.hide_timer = null;
  },
  mouseout: function(ev) {
    if(this.menu_shown && !this.hide_timer) {
      this.hide_timer = setTimeout(this.hideTimerFired.bind(this), 0);
      ev.stop();
    }
  },
  hideTimerFired: function() {
    this.hideMenu();
  }
});

Event.observe(document, 'dom:loaded', function()  {
  if($('x-menu-toggle')) {
    // clicks for menu dropdown
    Event.observe(document, 'click', function(e) {
      if(Event.element(e).tagName != 'A') HeaderMenu.hide();
    });
    
    Event.observe('x-menu-toggle', 'click', HeaderMenu.toggle.bindAsEventListener(HeaderMenu));
    Event.observe('x-menu-toggle', 'mouseover', HeaderMenu.mouseOver.bindAsEventListener(HeaderMenu));
    Event.observe('x-menu-toggle', 'mouseout', HeaderMenu.mouseOut.bindAsEventListener(HeaderMenu));
    Event.observe('loginbox', 'mouseover', HeaderMenu.mouseOver.bindAsEventListener(HeaderMenu));
    Event.observe('loginbox', 'mouseout', HeaderMenu.mouseOut.bindAsEventListener(HeaderMenu));
  }
  
  // click-based dropdown thing
  if($('locality')) {
    new Slzr.ClickMenu('x-locality-menu', 'x-locality-menu-list');
  }
  
  // All hover menus should have the hover_menu on the activation element,
  // and the id of the menu items should be the id of the activation element + '-list'
  $$('.hover_menu').each(function(el) {
    var list_el;
    if(list_el = $(el.id + '-list')) new Slzr.HoverMenu(el, list_el);
  });
  
  // IE z-index fix from http://richa.avasthi.name/blogs/tepumpkin/2008/01/11/ie7-lessons-learned/
  if(Prototype.Browser.IE) {
    $$('ul.locality_submenu, ul.action_menu, ul.action_menu_items').each(function(menu) {
      menu.ancestors().each(function(a) {
        var pos = a.getStyle('position');

        if(pos == 'relative' ||
           pos == 'absolute' ||
           pos == 'fixed') {
          a.setStyle({zIndex: 99999});
        }
      }); // ancestors
    }); // each menu ul
  } // if IE
});

// Message list functions
Slzr.MessageList = Class.create({
  initialize: function(options) {
    this.options = Object.extend({
      // URL to ping to mark an item as read, or null to disable this functionality
      setReadURL: null,
      // The container element or id of the item listings
      container: null,
      // Support ajax replacement of container
      isAjaxed: false
    }, options || {});
    
    // If this is supporting ajax replacement, we instead listen on document.body
    // and match against a basic CSS selector before continuing on with the click
    //
    // container *must* be an ID to operate in this mode.
    if(this.options.isAjaxed) {
      this.selector = '#' + this.options.container + ' *';
      Event.observe(document.body, 'click', this.bodyClicked.bindAsEventListener(this));
    } else {
      this.element = $(this.options.container);
      if(this.element) {
        this.element.observe('click', this.messageListClicked.bindAsEventListener(this));
      }
    }
  },
  
  bodyClicked: function(ev) {
    var el = $(ev.element());
    
    if(el.match(this.selector)) {
      return this.messageListClicked(ev);
    } else {
      return true;
    }
  },
  
  messageListClicked: function(ev) {
    var el = $(ev.element());

    if(el.hasClassName('delete_item')) return;
    if(el.tagName == 'INPUT' || el.tagName == 'TEXTAREA' || el.tagName == 'A' || el.tagName == 'SELECT' || el.tagName == 'OPTION') return;
    
    var message_item;
    if(el.hasClassName('manage_item')) message_item = el;
    else message_item = el.up('div.manage_item');
    
    if(!message_item) return;
    
    // toggle visibility
    if(message_item.hasClassName('expanded')) {
      this.collapseItem(message_item);
    } else {
      // collapse all the rest
      var my = this;
      $$('div.manage_item.expanded').each(function(e) { my.collapseItem(e); });

      this.expandItem(message_item);
    }
    
    ev.stop();
  },
  
  setReadState: function(id) {
    if(this.options.setReadURL) new Ajax.Request(this.options.setReadURL, {
      parameters: 'id=' + id,
      asynchronous: true,
      evalScripts: true
    });
  },
  
  showActionButtons: function(el) {
    var message_el = $(el).up('div.manage_item');
    $(message_el).down('div.form_trigger').show();
  },
  
  hideActionButtons: function(el) {
    var message_el = $(el).up('div.manage_item');
    $(message_el).down('div.form_trigger').hide();
  },
  
  showReply: function(el) {
    var message_el = $(el).up('div.manage_item');
    
    this.hideForward(el);
    this.hideActionButtons(el);
    
    $(message_el).down('form.reply_form').show();
  },
  
  hideReply: function(el) {
    var message_el = $(el).up('div.manage_item');
    $(message_el).down('form.reply_form').hide();
  },

  showForward: function(el) {
    var message_el = $(el).up('div.manage_item');

    this.hideReply(el);
    this.hideActionButtons(el);

    $(message_el).down('form.forward_form').show();
  },
  
  hideForward: function(el) {
    var message_el = $(el).up('div.manage_item');
    $(message_el).down('form.forward_form').hide();
  },

  cancelEdit: function(el) {
    this.hideForward(el);
    this.hideReply(el);
    this.showActionButtons(el);
  },

  cancelCompose: function() {
    var el = $('x-compose-form');
    el.hide();
    el.previous('.link_action').show();
    this.clearCompose();
  },

  composeMessage: function() {
    var el = $('x-compose-form');
    el.show();
    el.previous('.link_action').hide();
  },

  clearCompose: function() {
    var el = $('x-compose-form');
    el.select('select, textarea, input[type=text]').invoke('setValue', '');
  },
  
  expandItem: function(message_item) {
    var e;
    
    if(e = message_item.down('div.details')) e.show();
    if(e = message_item.down('img.icon_tiny')) e.hide();
    if(e = message_item.down('img.icon_small')) e.show();
    if(e = message_item.down('.preview_text')) e.hide();
    message_item.addClassName('expanded');
    if(message_item.hasClassName('unread') || message_item.down('.unread')) this.setReadState(message_item.id);
    message_item.removeClassName('unread');
    message_item.select('.unread').invoke('removeClassName', 'unread');
  },

  collapseItem: function(message_item) {
    var e;
    
    if(e = message_item.down('div.details')) e.hide();
    if(e = message_item.down('img.icon_tiny')) e.show();
    if(e = message_item.down('img.icon_small')) e.hide();
    if(e = message_item.down('.preview_text')) e.show();
    message_item.removeClassName('expanded');
  }
});

Slzr.RecentActivity = Class.create({
  initialize: function(wrapper_element) {
    this.list = $(wrapper_element);
    
    // attach event handlers
    var el;
    
    if(el = this.list.down('.add_review_button')) el.observe('click', this.showReviewForm.bindAsEventListener(this));
    if(el = this.list.down('.add_comment_button')) el.observe('click', this.showCommentForm.bindAsEventListener(this));
    if(el = this.list.down('.add_photo_button')) el.observe('click', this.showPhotoForm.bindAsEventListener(this));
    
    this.list.select('.cancel_button').invoke('observe', 'click', this.hideAllForms.bind(this));
    
    // hiding onblur if blank here
    var my = this;
    this.list.select('textarea').each(function(el) {
      el.placeholder_value = $F(el); // store placeholder value, as we can't get it otherwise
      el.observe('blur', my.inputBlurHandler.bindAsEventListener(my));
    });
  },
  
  inputBlurHandler: function(ev) {
    if($F(ev.target) == '' || $F(ev.target) == ev.target.placeholder_value) this.blur_timer = this.hideAllForms.bind(this).delay(0.15);
    ev.stop();
  },
  
  showReviewForm: function(ev) {
    this.hideAllForms();
    this.showForm('review');
    ev.stop();
  },
  
  showCommentForm: function(ev) {
    this.hideAllForms();
    this.showForm('comment');
    ev.stop();
  },
  
  showPhotoForm: function(ev) {
    this.hideAllForms();
    this.showForm('photo');
    ev.stop();
  },
  
  hideAllForms: function(ev) {
    if(this.blur_timer) {
      window.clearTimeout(this.blur_timer);
      this.blur_timer = null;
    }
    
    var selectors = $w('comment review photo').map(function(e) {return '#' + this.list.id + "_new_" + e;}.bind(this)).join(', ');
    this.list.select(selectors).invoke('hide');
    this.list.select('.link_action').invoke('show');
    
    if(ev) ev.stop();
  },
  
  showForm: function(form) {
    var id = '#' + this.list.id + '_new_' + form;
    this.list.select(id).invoke('show');
    var e;
    if(e = this.list.down(id).down('textarea')) e.focus();
    this.list.select('.link_action').invoke('hide');
  }
});

// Simple bundle compatibility shim for use with the new streamPublish APIs
Slzr.Facebook = (function() {
  return {
    streamPublish: function(data) {
      FB.ensureInit(function() {
        FB.Connect.streamPublish('', data.attachment, data.actions);
      });
    }
  };
})();

postFBFeedItem = Slzr.Facebook.streamPublish;
Slzr.Comments = (function(){
  var form = null;
  
  function radioChanged(ev) {
    form.select('label.rating').invoke('removeClassName', 'selected');
    $(this.id + "_label").addClassName('selected');
  };
  
  return {
    'applyTo': function(the_form) {
      form = $(the_form);
    },
    'success': function(the_form) {
      form = $(the_form);
      
      // reset form state
      form.select('input[type=text], textarea').invoke('clear');
      form.select('input[type=radio], input[type=checkbox]').each(function(el) {
        el.checked = false;
      });
      form.select('input[type=submit]').each(function(el) {
        el.disabled = false;
        if(el.getAttribute('originalValue')) el.value = el.getAttribute('originalValue');
      });
    },
    'disableSubmit': function(el) {
      el = $(el);
      el.setAttribute('originalValue', el.value);
      el.value = 'Posting...';
      el.disabled = true;
    },
    'enableSubmit': function(el) {
      el = $(el);
      el.value = el.getAttribute('originalValue');
      el.disabled = false;
    }
  }
})();

// Override Effect.Highlight to make it not yellow.
Effect.Highlight = Class.create(Effect.Highlight, {
  initialize: function($super, element) {
    var options = Object.extend({ startcolor: '#068ad2', duration: '2.0' }, arguments[2] || { });
    $super(element, options);
  }
});

// Placeholder text implementation, based off of
// ghosted_field.js from http://github.com/savetheclocktower/javascript-stuff/blob/master/ghosted_field.js
Slzr.InputPlaceholder = Class.create({
  initialize: function(element, options) {
    this.element = $(element);
    this.title = this.element.readAttribute('placeholder') || this.element.readAttribute('title');
    
    this.isGhosted = true;
    
    if(options.cloak) {
      // Wrap the native getValue function so that it never returns the
      // ghosted value. This is optional because it presumes the ghosted
      // value isn't valid input for the field.
      this.element.getValue = this.element.getValue.wrap(this.wrappedGetValue.bind(this));      
    }    
    
    this.addObservers();
    this.onBlur();
  },
  
  wrappedGetValue: function($proceed) {
    var value = $proceed();
    return value === this.title ? "" : value;
  },
  
  addObservers: function() {
    this.element.observe('focus', this.onFocus.bind(this));
    this.element.observe('blur',  this.onBlur.bind(this));
    
    var form = this.element.up('form');
    if (form) {
      form.observe('submit', this.onSubmit.bind(this));
    }
    
    // Firefox's bfcache means that form fields need to be re-initialized
    // when you hit the "back" button to return to the page.
    if (Prototype.Browser.Gecko) {
      window.addEventListener('pageshow', this.onBlur.bind(this), false);
    }
    
    this.element.placeholderBlur = this.onBlur.bind(this);
  },
  
  onFocus: function() {
    if (this.isGhosted) {
      this.element.setValue('');
      this.setGhosted(false);
    }
  },
  
  onBlur: function() {
    var value = this.element.getValue();
    if (value.blank() || value == this.title) {
      this.setGhosted(true);
    } else {
      this.setGhosted(false);
    }
  },
  
  setGhosted: function(isGhosted) {
    this.isGhosted = isGhosted;
    this.element[isGhosted ? 'addClassName' : 'removeClassName']('input_placeholder');
    if (isGhosted) {
      this.element.setValue(this.title);
    }    
  },

  // Hook into the enclosing form's `onsubmit` event so that we clear any
  // ghosted text before the form is sent.
  onSubmit: function() {
    if (this.isGhosted) {
      this.element.setValue('');
    }
  }
});
document.observe('dom:loaded', function() {
  var selector = "textarea[placeholder]";
  // webkit-based browsers support the placeholder attribute on INPUT tags
  if(!Prototype.Browser.WebKit) selector += ", input[placeholder]";
  $$(selector).each(function(el) {
    new Slzr.InputPlaceholder(el, {cloak: true});
  });
});

Slzr.Message = {
  textChanged: function(el) {
    var el = $(el);
    var form = $(el.form);
    var submit = form.down('input[type=submit]');
    
    if($F(el).blank()) {
      submit.disable();
    } else {
      submit.enable();
    }
//    :onchange => %{if($F(this).blank()) {$(this).up('form').down('input[type=submit'])}$(this).up('form').down('input[type=submit']).disabled = !$F(this).blank();} %>
  }
}

// Featured section navigation
Slzr.SectionTabs = Class.create({
  // Initialize the tab navigation code
  //
  // Available options:
  //  title: element containing the section title, contents will be set to the visible title
  //  buttonContainer: all As in this with a "data-title" attribute will be used to navigate tabs, to the href element
  //  tabSelector: selector for all tabs, or hiding non-shown ones
  initialize: function(options) {
    this.title = $(options.title);
    this.buttonContainer = $(options.buttonContainer);
    this.tabSelector = options.tabSelector;
    
    // attach event listeners
    if(this.buttonContainer) this.buttonContainer.select('a["data-title"]').each((function(el) {
      $(el).observe('click', this.changeTab.bind(this));
    }).bind(this));
  },

  changeTab: function(event) {
    var el = event.findElement('a');
    var title = el.getAttribute('data-title') || el.innerHtml;
    this.title.update(title);
    
    // hide all other tabs and show all buttons
    $$(this.tabSelector).invoke('hide');
    this.buttonContainer.select('a["data-title"]').invoke('removeClassName', 'selected_tab_colorfive');
    
    // hide the current button and show the current tab
    el.addClassName('selected_tab_colorfive');
    var tab = el.getAttribute('href').replace(/^(.*)#/, '');
    var e;
    if(e = $(tab)) e.show();
    
    event.stop();
  }
});

// Update a URL with the specified parameters in the query string
// Will not reset other parameters
function updateUrl(params) {
  var new_url = location.search.toQueryParams();
  new_url = $H(new_url).update(params);
  
  location.search = '?' + $H(new_url).toQueryString();
}

// Observe a field for changes and update some other list
//
// Can optionally wait a fraction of a second for further typing.
Slzr.FieldUpdater = Class.create({
  
  // Options:
  //  url: url to request
  //  params: other parameters to pass with URL (can be a function for dynamic evaluation)
  //  delay: time to delay on field changes
  //  update: element to update
  //  onStart: run on start of ajax request
  //  onComplete: run on finish of ajax request
  //  indicatorElement: element to hide/show as necessary indicating request in progress
  initialize: function(element, options) {
    this.element = $(element);
    this.options = Object.extend({
      url: null,
      params: {},
      delay: 0.2,
      update: null,
      onStart: function(){},
      onComplete: function(){},
      indicatorElement: null
    }, options || {});
    this.numRequests = 0;
    
    this.addObservers();
  },
  
  addObservers: function() {
    this.element.observe('keyup', this.onChange.bind(this));
  },
  
  onChange: function() {
    if(this.timer) clearTimeout(this.timer);
    this.timer = this.updateContent.bind(this).delay(this.options.delay);
  },
  
  updateContent: function() {
    this.timer = null;
    
    var my = this;
    new Ajax.Updater(this.options.update, this.options.url, {
      asynchronous: true,
      evalScripts: true,
      //parameters: 'value=' + encodeURIComponent($F(this.element)) + "&date=" + $F(newsletter_content_publish_date),
      parameters: Object.extend(typeof(this.options.params) == 'function' ? this.options.params() : this.options.params, {value: $F(this.element)}),
      onComplete: function() { my.hideIndicator(); (my.options.onComplete.bind(my))(); },
      onLoading: function() { my.showIndicator(); (my.options.onStart().bind(my))(); }
    });
  },
  
  hideIndicator: function() {
    this.numRequests--;
    if($(this.options.indicatorElement) && this.numRequests <= 0) new Effect.Fade(this.options.indicatorElement);
  },
  
  showIndicator: function() {
    this.numRequests++;
    if($(this.options.indicatorElement)) new Effect.Appear(this.options.indicatorElement);
  }
  
});

function toggleFilters(list, link) {
  var list = $(list);
  var link = $(link);
  if(!list) return true;
  if(!link) return true;
  
  var expanded = link.hasAttribute('data-expanded');
  
  var sel = '#' + list.identify();
  
  $$(sel + ' > li[data-additional]').invoke(expanded ? 'hide' : 'show');
  var expanded_label = link.readAttribute('data-expanded-label') || 'Show Less';
  var collapsed_label = link.readAttribute('data-collapsed-label') || 'Show More';
  
  link.update(expanded ? collapsed_label : expanded_label);
  link.writeAttribute('data-expanded', !expanded);
  
  return false;
}
