/*
*  stranger.js
*
*  gloabl javascript for thestranger.com
*/

/* Utility Functions */

document.domain = 'thestranger.com';
var disableAutoLinkTargeting = (disableAutoLinkTargeting != undefined) ? disableAutoLinkTargeting : false;

function addLoadEvent(func) {
  var oldonload = window.onload;
  if (typeof window.onload != 'function') {
    window.onload = func;
  } else {
    window.onload = function() {
      oldonload();
      func();
    }
  }
}

function getElementsByClass(searchClass,node,tag) {
  var classElements = new Array();
  if ( node == null )
    node = document;
  if ( tag == null )
    tag = '*';
  var els = node.getElementsByTagName(tag);
  var elsLen = els.length;
  var pattern = new RegExp("(^|\\s)"+searchClass+"(\\s|$)");
  for (i = 0, j = 0; i < elsLen; i++) {
    if ( pattern.test(els[i].className) ) {
      classElements[j] = els[i];
      j++;
    }
  }
  return classElements;
}

function insertAfter(newElement,targetElement) {
  var parent = targetElement.parentNode;
  if (parent.lastChild == targetElement) {
    parent.appendChild(newElement);
  } else {
    parent.insertBefore(newElement,targetElement.nextSibling);
  }
}

function addClass(element,value) {
  if (!element.className) {
    element.className = value;
  } else {
    newClassName = element.className;
    newClassName+= " ";
    newClassName+= value;
    element.className = newClassName;
  }
}

function idxPageDimensions () {
    var res = [0,0];
    var woh = ["Width","Height"];
    var doc = document.documentElement ?
        document.documentElement : document.body;
    
    for (var i = 0; i < woh.length; i++) {
        if (doc["client" + woh[i]] && doc["client" + woh[i]]) {
        res[i] = doc["client" + woh[i]];
      }

      if (doc["scroll" + woh[i]] && doc["scroll" + woh[i]] > res[i]) {
        res[i] = doc["scroll" + woh[i]];
      }

      if (doc["offset" + woh[i]] && doc["offset" + woh[i]] > res[i]) {
        res[i] = doc["offset" + woh[i]];
        }

        if (self["inner" + woh[i]] && self["inner" + woh[i]] > res[i]) {
            res[i] = self["inner" + woh[i]];
        }
    }
            
    return res;
}

function formatDate(date,format_code) {
  if ((typeof date == "object") && (date instanceof Date)) {
    switch(format_code) {
      case 1: // ex: 2006-10-23
        var mymonth = date.getMonth() + 1;
        return date.getFullYear() + '-' + mymonth + '-' + date.getDate();
        break;
      case 2: // ex: Monday, Oct. 23
        return weekday[date.getDay()] + ', ' + month[date.getMonth()] + '. ' + date.getDate();
        break;
    }
  } else {
    return false;
  }
}

function checkLibraryVersion(library,required) {
  var required = (required.split('.').length < 3) ? required + '.0' : required;
  if ((typeof window[library] == 'undefined') || (convertVersionString(window[library].Version) < convertVersionString(required))) {
    return false;
  } else {
    return true;
  }
}

function convertVersionString(versionString) {
  var r = versionString.split('.');
  return parseInt(r[0])*100000 + parseInt(r[1])*1000 + parseInt(r[2]);
}
function toggleConciseText(target) {
  if(target) {
    moreLink = $(target+'-MoreLink');
    lessLink = $(target+'-LessLink');
    target = $(target);
    if(target.hasClassName('conciseText')) {
      target.removeClassName('conciseText');
      if(lessLink){lessLink.show();}
      if(moreLink){moreLink.hide();}
    } else {
      target.addClassName('conciseText');
      if(lessLink){lessLink.hide();}
      if(moreLink){moreLink.show();}
      new Effect.ScrollTo(target, {duration:.2,offset:-42});
    }
  }
}
function toggleDivs(showDiv,hideDiv) {
  $(hideDiv).hide();
  $(showDiv).show();
  $(showDiv).pulsate({duration:.5,pulses:1});
}
function toggleElements() {
  // usage: toggleElements('someDiv','someOtherDiv','whateverOtherDiv');
  for( var i = 0; i < arguments.length; i++ ) {
    var div = arguments[i];
    if ($(div)) { $(div).toggle(); };
  }
}
function toggleHelpfulBubble(bubble){
  if($(bubble)) {
    Effect.toggle(bubble,'appear',{ duration: .25 });
  }
  
}

function showHide(togglethisID,imgID,effect) {
  // imgID usage (optional): if image to be swapped is expand-image.gif, will be replaced with expand-image-toggle.gif
  // ensure that the -toggle image exists on the server
  if (!document.getElementById(togglethisID)) return true;
  var theElement = $(togglethisID);
  var imgID = (imgID != null) ? imgID : null;
  if (imgID && document.getElementById(imgID)) {
    var theImg = document.getElementById(imgID);
    // using stupid IE-specific getAttribute "iFlag" to prevent it returning an absolute path
    var imgName = theImg.getAttribute("src",2).split(".")[0];
    var imgName = imgName.split("-toggled")[0];
  }
  if (!theElement.visible()) {
    if (effect) {
      switch(effect) {
        case 'slide':
          new Effect.SlideDown(theElement,{duration:0.3});
          break;
        case 'blind':
          new Effect.BlindDown(theElement,{duration:0.3});
          break;
      }
    } else {
      theElement.show();
    }    
    if (theImg) {
      theImg.setAttribute("src", imgName+"-toggled.gif");
    }
  } else {
    if (effect) {
      switch(effect) {
        case 'slide':
          new Effect.SlideUp(theElement,{duration:0.3});
          break;
        case 'blind':
          new Effect.BlindUp(theElement,{duration:0.3});
          break;
      }
    } else {
      theElement.hide();
    }
    if (theImg) {
      theImg.setAttribute("src", imgName+".gif");
    }
  }
  return false;
}

function anchorJump(anchorName) {
  // probably should use Scriptaculous Effect.ScrollTo() instead, which doesn't require YUI (but does require scriptaculous and prototype, of course)
  // it's also a nicer effect
  // SEE animateAnchorLinks() below
  for (var i=0;i < document.anchors.length; i++) {
    if (document.anchors[i].name == anchorName) {
      var a = document.anchors[i];
      var pos = YAHOO.util.Dom.getXY(a);
      window.scrollTo(pos[0],pos[1]);
      break;
    }
  }
}

function jumptoID(id) {
  var pos = YAHOO.util.Dom.getXY(document.getElementById(id));
  window.scrollTo(pos[0],pos[1]);
}

/* Cookie Functions */

function setCookie(cookieName,cookieValue,nDays) {
   var today = new Date();
   var expire = new Date();
   if (nDays==null || nDays==0) nDays=1;
   expire.setTime(today.getTime() + 3600000*24*nDays);
   document.cookie = cookieName+"="+escape(cookieValue) + "; expires="+expire.toGMTString() + "; path=/";
}
  
function getCookie(cookieName) {
  if (document.cookie.length > 0) {
    cookieStart = document.cookie.indexOf(cookieName + "=");
    if (cookieStart != -1) {
      cookieStart = cookieStart + cookieName.length+1;
      cookieEnd = document.cookie.indexOf(";",cookieStart);
      if (cookieEnd == -1) cookieEnd = document.cookie.length;
      return unescape(document.cookie.substring(cookieStart,cookieEnd));
    }
  }
  return "";
}
function readCookie(name) {
  var nameEQ = name + "=";
  var ca = document.cookie.split(';');
  for(var i=0;i < ca.length;i++) {
    var c = ca[i];
    while (c.charAt(0)==' ') c = c.substring(1,c.length);
    if (c.indexOf(nameEQ) == 0) return unescape(c.substring(nameEQ.length,c.length));
  }
  return null;
}

function acceptsCookies() {
  var cookieName = 'strCheck';
  var timestamp =  new Date().getTime();
  setCookie(cookieName,timestamp,'1');
  if (getCookie(cookieName) == timestamp) {
    document.cookiesOn = true;
  } else {
    document.cookiesOn = false;
  }
}

/* onload setup functions */

function setupLinkBehavior() {
  if (!document.getElementsByTagName) return false;
  if (!document.getElementById) return false;
  var autolink = (disableAutoLinkTargeting != undefined) ? disableAutoLinkTargeting : false;
  var links = document.getElementsByTagName('a');
  for (var i=0;i<links.length;i++) {
    if (links[i].className.match('playLink') || links[i].className.match('playlink')) {
      links[i].onclick = function() {
          var mp3 = this.getAttribute('href');
          var title = this.getAttribute('title');
          OpenWindow('http://www.thestranger.com/scripts/flashAudioPlayer.php?f='+mp3+'&amp;t='+title,'300','50','player');
          return false;
        };
    } else if (links[i].className.match('emailThisLink')) {
      links[i].onclick = function() {
          OpenWindow(this.getAttribute('href'),'370','470');
          return false;
        };
    } else if (links[i].className.match('tglink')) {
      links[i].onclick = function() {
        tabToggler(this);    
        return false;
      };
    } else if (links[i].id == 'profanity_filter_link') {
      links[i].onclick = function(e) {
        if (typeof($) == "function") {
          toggleProfanity();
        }
        return false;
      }
    }  else if (links[i].className.match('remindMeLink')) {
      links[i].onclick = function() {
        var eventid = this.id.split('rml')[1];
        var target = $('rm'+eventid);
        var link = $(this);
        var link_content = $(this).innerHTML;
        new Ajax.Request(this.getAttribute('href')+'&ajax=1', {
            method:'get',
            onCreate: function() {
              link.update('One moment...');
            },
            onSuccess: function(transport) {
              target.update(transport.responseText);
              remindMe(eventid);
              new Effect.Appear(target);
              link.update(link_content);
            }
          });
        //OpenWindow(this.getAttribute('href'), '500','300','RemindMe',{'scrollbars':'no','status':'no'});
        return false;
      };
    } else if (links[i].className.match('tools_remind')) {
      links[i].onclick = function() {
        OpenWindow(this.getAttribute('href'), '500','300','RemindMe',{'scrollbars':'no','status':'no'});
        return false;
      };
    }  else if (links[i].className.match('quoteCommentLink')) {
        links[i].onclick = function() {
          var quotedID = this.id.split(':')[2];
          var quoteText = $('commentbody-'+quotedID).innerHTML;
          var commentform = $('commentForm');
          var textarea = commentform['comment'];
          textarea.value = '[quote="'+this.id.split(':')[1]+'"]'+quoteText+'[\/quote]';
          $('comment').focus();
          return false;
        };
      } else if (links[i].className.match('clicktrack')) {
        links[i].onclick = function() {
          var clickurl = 'http://www.thestranger.com/seattle/click';
          var clickparams = '?source='+this.getAttribute('data-linksource')+'&partner='+this.getAttribute('data-linkpartner')+'&dest='+escape(this.getAttribute('href'));
          OpenWindow(clickurl+clickparams,'980','700','Click');
          return false;
        }
      }
    if (links[i].getAttribute('href') && !links[i].getAttribute('target') && !disableAutoLinkTargeting) {
      var thisURL = links[i].getAttribute('href').replace(/^http:\/\//,'');
      if (thisURL.match(/^media\.thestranger\.com\/slideshows(.*)?/)) {
        links[i].onclick = function() {
          OpenWindow(this.getAttribute('href'),'975','720','Slideshow');
          return false;
        };
      } else   if (thisURL.match(/^(.*\.)?thestranger\.com|^\/|^#|^\?|^javascript/)) {
        links[i].setAttribute('target','_self');
      } else {
        links[i].setAttribute('target','_blank');
      }
    }
  }
}

function toggleProfanity() {
  if (!$('profanity_filter_image')) return false;
  if ($('wrapper_page')) {
    $('wrapper_page').toggle();
    $('header').toggle();
    $$('body').first().toggleClassName('emptybg');
    $('profanity_filter').toggleClassName('emptybg');
    if ($('profanity_filter_image').getAttribute('src').match(/hide_profanity/)) {
      $('profanity_filter_image').setAttribute('src', 'http://www.thestranger.com/images/buttons/show_profanity.gif');
    } else {
      $('profanity_filter_image').setAttribute('src', 'http://www.thestranger.com/images/buttons/hide_profanity.gif');
    }
  }
}

function remindMe(eventid) {
  var remindForm = $('reminderForm'+eventid);
  var stockRemindTime = remindForm['stockRemindTime'];
  if (stockRemindTime) {
    var customFields = $('customTimeFields'+eventid);
    if ($F(stockRemindTime) != "other") { 
      $(customFields).hide();
    } else {
      $(customFields).show();
    }
    Event.observe(stockRemindTime, 'change', function() { 
      if ($F(stockRemindTime) == 'other') {
        $(customFields).show();
      } else {
        $(customFields).hide();
      }
    });
  }
}

function tabToggler(thelink) {  
  // DEPENDENCIES: Prototype
  if (!checkLibraryVersion('Prototype','1.5.1')) return false;
  var mylink = $(thelink);
  if (mylink.getAttribute('id').match(/^(tgtab|tglink)_([a-zA-Z0-9]+)_([a-zA-Z0-9]+)$/)) {
    var tgName = mylink.getAttribute('id').match(/^(tgtab|tglink)_([a-zA-Z0-9]+)_([a-zA-Z0-9]+)$/)[2];
    var linkID = mylink.getAttribute('id').match(/^(tgtab|tglink)_([a-zA-Z0-9]+)_([a-zA-Z0-9]+)$/)[3];
  } else {
    return true;
  }
  var allTabs = document.getElementsByClassName('tgtab_'+tgName); // get all tabs in this toggler instance
  allTabs.each(function(z) {   // loop through the tabs
    if (z.getAttribute('id').match(/^(tgtab|tglink)_[a-zA-Z0-9]+_([a-zA-Z0-9]+)$/)) {
      var tabID = z.getAttribute('id').match(/^(tgtab|tglink)_[a-zA-Z0-9]+_([a-zA-Z0-9]+)$/)[2];
    } else {
      return true;
    }
    if (tabID == linkID) { // the clicked tab
      if (!z.hasClassName(tgName+'_current')) z.addClassName(tgName+'_current');
      z.blur(); // gets rid of annoying firefox link border
      if ($('tgcontainer_'+tgName+'_'+tabID)) $('tgcontainer_'+tgName+'_'+tabID).show();
    } else { // other tabs
      if (z.hasClassName(tgName+'_current')) z.removeClassName(tgName+'_current');
      if ($('tgcontainer_'+tgName+'_'+tabID)) $('tgcontainer_'+tgName+'_'+tabID).hide();
    }
  });
}

function animateAnchorLinks() {
  // WARNING: only use this if prototype and scriptaculous effects are being loaded
  // should probably do some checking to make sure functions are available
  // also, doesn't seem to work in IE
  if (!checkLibraryVersion('Prototype','1.5.1') || !checkLibraryVersion('Scriptaculous','1.7.1') || !$$('a')) return false;
  var all_links = $$('a');
  all_links.each(function(thelink) {
    if (thelink.getAttribute('href') && thelink.getAttribute('href').match(/^#/) && $(thelink.getAttribute('href').split('#')[1]) && !thelink.getAttribute('onclick')) {
      Event.observe(thelink, 'click', function(e) {
        new Effect.ScrollTo(thelink.getAttribute('href').split('#')[1]);
        Event.stop(e);
      });
    }
  });
}
function scrollToBlurb(blurb){
  new Effect.ScrollTo(blurb,{duration:.2,offset:-15});
  return false;
}

function prepareForms() {
  $$('.prepTextForm').each(function(form) { prepareTextInputFields(form); });
  $$('.validateForm').each(function(form) { Event.observe(form,'submit',validateForm); });
}

function prepareTextInputFields(theform) {
  var inputs = theform.getElementsByTagName('input');
  for (var i=0;i<inputs.length;i++) {
    var element = inputs[i];
    if (element.getAttribute('type') == 'text' && element.getAttribute('title')) {
      if  (element.value == '' || element.value == element.getAttribute('title')) {
        element.value = element.getAttribute('title');
      }
      element.onfocus = function() {
        if (this.value == this.getAttribute('title')) {
          this.value = '';
        }
      };
      element.onblur = function() {
        if (this.value == '') {
          this.value = this.getAttribute('title');
        }
      };
    }
  }
}

function flipFieldPrefill(field,focusedText,blurredText,label) {
  if(label){label = $(label);}
  var notes = field.name+'Notes'; notes = $(notes);
  var submitButton = field.name+'Submit'; submitButton = $(submitButton);
  if(field.value==blurredText) {
    field.value = focusedText;
    field.removeClassName('blurredText');
    if(label){label.removeClassName('blurredLabel');}
    if(notes){notes.show();}
    if(submitButton){submitButton.show();}
  } else if(field.value == focusedText || field.value == "") {
    field.value = blurredText;
    field.addClassName('blurredText');
    if(label){label.addClassName('blurredLabel');}
    if(notes){notes.hide();}
    if(submitButton){submitButton.hide();}
  }
  return false;
}

function validateForm(e) {
  var reqs = $$('#'+this.id+' .required');
  reqs.each(function(field) {
    // check field type and determine if it has a value. if not, do some kind of error and cancel submit
  });
}

/* scroller box */

function Scroller() {
  var target, playButton, pauseButton, dataURL, itemCount, initCount, delay, mode, slide;
  this.setTarget = function(value) { target = value; };
  this.setURL = function(value) { dataURL = value; };
  this.setItemCount = function(value) { itemCount = value; };
  this.setInitCount = function(value) { initCount = value; };
  this.setDelay = function(value) { delay = value; };
  this.setPlayButtonID = function(value) { playButton = value; };
  this.setPauseButtonID = function(value) { pauseButton = value; };
  this.setHtmlConstructor = function(value) { constructHTML = value; };
  this.setMode = function(value) { mode = value; };
  this.setSlide = function(value) { slide = value; };
  // set defaults
  if (!itemCount) itemCount = 30;
  if (!initCount) initCount = 0;
  if (!delay) delay = 8;
  if (!slide) slide = false;
  if (!mode) mode = 'normal';
  // internal variables
  var REQUIRED_VERSIONS = { 'Prototype' : '1.5.1.1', 'Scriptaculous' : '1.7.1' };
  var count = 0;
  var data = null;
  var myPE = null;
  var maxItems = (initCount != 0) ? initCount * 2 : 10;
  this.init = function() {
    if (!require('Prototype') || !target || !dataURL || !$(target) || !constructHTML) return;
    if ($(pauseButton) && $(playButton)) {
      Event.observe(pauseButton, 'click', function(e) {
        swapButtons('play');
        myPE.stop();
        Event.stop(e);
      });
      Event.observe(playButton, 'click', function(e) { startScroller(); Event.stop(e); });
    }
    Event.observe(target, 'mouseover', function() {
      swapButtons('play');
      myPE.stop();
    });
    Event.observe(target, 'mouseout', startScroller);
    getData();
  };
  var getData = function() {
    new Ajax.Request(dataURL, {
      method: 'get',
      parameters: { q : itemCount },
      onSuccess: function(transport) {
        data = transport.responseText.evalJSON();
        loadInitial(data);
      }
    });
  };
  var loadInitial = function(data) {
    data.each(function(i) {
      if (count == initCount) throw $break;
      var newhtml = constructHTML(i, target+'_item', count);
      new Insertion.Top($(target), newhtml);
      ++count;
    });
    startScroller();
  };
  var startScroller = function() {
    swapButtons('pause');
    if (!$(target+'_inner') && slide) {
      var innerDiv = document.createElement('div');
      innerDiv.id = target+'_inner';
      var existingItems = $(target).immediateDescendants();
      existingItems.each(function(i) {
        var r = i.remove();
        innerDiv.appendChild(r);
      });
      $(target).appendChild(innerDiv);
    }
    myPE = new PeriodicalExecuter(function(pe) {
      var newhtml = constructHTML(data[count], target+'_item', count);
      if (slide) {
        var lastThing = ($(target).firstDescendant().id != target+'_inner') ? $(target).firstDescendant().remove() : null;
        if (lastThing) $(target+'_inner').insertBefore(lastThing, $(target+'_inner').firstDescendant());
      }
      (mode == 'replace') ? $(target).innerHTML = newhtml : new Insertion.Top($(target), newhtml);
      var newThing = $(target).firstDescendant();
      if (require('Scriptaculous')) {
        var offset = newThing.offsetHeight;
        newThing.hide();
        if (slide) new Effect.Move($(target+'_inner'), { x:0, y:offset, queue: {position:'end', scope:'scroller'} });
        new Effect.Appear(newThing, {queue: {position:'end', scope:'scroller'} });
      }
      ++count;
      if (count >= data.length) count = 0;
      var itemsArray = (slide) ? $(target+'_inner').immediateDescendants() : $(target).immediateDescendants();
      if (itemsArray.length > maxItems) {
        $(itemsArray.last()).remove();
      }
    }, delay);
  };
  var swapButtons = function(which) {
    if ($(pauseButton) && $(playButton)) {
      switch(which) {
        case 'pause':
          $(pauseButton).show();
          $(playButton).hide();
          break;
        case 'play':
          $(playButton).show();
          $(pauseButton).hide();
          break;
      }
    }
  };
  var require = function(library) {
    if ((typeof window[library] == 'undefined') || (convertVersionString(window[library].Version) < convertVersionString(REQUIRED_VERSIONS[library]))) {
      return false;
    } else {
      return true;
    }
  };
  var convertVersionString = function(versionString) {
    var r = versionString.split('.');
    return parseInt(r[0])*100000 + parseInt(r[1])*1000 + parseInt(r[2]);
  };
}

var SideScroller = Class.create({
  // vers.0.5 - needs considerable work and additional features
  // todo: ajax data loading, controls, go-around, etc.
  initialize: function(container,items_per_set,delay) {
    var container = $(container);
    if (!container) return;
    var scrollable = container.select('.scrollable')[0];
    var items = scrollable.childElements();
    var item_count = items.size();
    var current_set = 1;
    var sets = ((item_count % items_per_set) == 0) ? parseInt(item_count / items_per_set) : parseInt(item_count / items_per_set) + 1;
    var indicator = this.createIndicator(container,sets);
    var indicator_items = indicator.childElements();
    var direction = 'left';
    var cwidth = parseInt(container.getWidth());
    var bordl = parseInt(container.getStyle('border-left-width').sub('px',''));
    var bordr = parseInt(container.getStyle('border-right-width').sub('px',''));
    var padl = parseInt(container.getStyle('padding-left').sub('px',''));
    var padr = parseInt(container.getStyle('padding-right').sub('px',''));
    var inside_width = (cwidth - (bordl + bordr + padl + padr));
    new PeriodicalExecuter(function(pe) {
      var move_x = (direction == 'left') ? -inside_width : inside_width;
      new Effect.Move(scrollable, { x:move_x, y:0 });
      current_set = (direction == 'left') ? current_set += 1 : current_set -= 1;
      indicator_items.each(function(ind, i) {
        (i == (current_set - 1)) ? ind.addClassName('active') : ind.removeClassName('active');
      });
      if (current_set == sets || current_set == 1) {
        direction = (direction == 'left') ? 'right' : 'left';
      }
    }, delay);
  },
  createIndicator: function(parent,count) {
    var list = document.createElement('ul');
    Element.extend(list);
    list.setStyle({width:count*14+'px'});
    list.id = 'sideScrollIndicators';
    var li = '<li>&bull;<\/li>';
    for (var x=0;x<count;x++) {
      list.insert(li);
    }
    list.childElements().first().addClassName('active');
    parent.insert( { bottom: list } );
    return list;
  }
});

var FloatingPanel = Class.create({
  // PREREQUISITES: Prototype 1.6+, Scriptaculous 1.8+ (effects.js module only)
  // REQUIRED PARAMETERS:
  //   trigger: id of link (or other triggering element)
  //  panel: id of panel to open (MUST be set to display:none inline, not in linked CSS)
  // AVAILABLE OPTIONS:
  //   triggerEvent - possible values are click, focus, and mouseover. default is mouseover.
  //   closeButton - set to id of a link to close the panel (link must be within the panel element).
  //               if set, panel will stay open until link is clicked, canceling default auto-close.
  //  allowDefault - to allow the default action of the trigger to happen, set allowDefault to true (does not apply to 'click' triggerEvents)
  //   target - set to make the panel appear next to an element other than the trigger
  //  closeDelay - set to change how long (in seconds) before a panel is closed on mouseout
  //  leftOffset, topOffset - adjust position of panel relative to default (10px right of trigger or target)
  //   effects - turn off open/close effects by setting this to 'off'
  //  effectDuration - set length of effect (in seconds)
  //  callbacks - fire a function before a panel opens or after one closes by setting beforeOpen or afterClose options
  //   new callback - beforeClose
  //  ajaxUrl and loading - fill panel with ajax request
  //  correctPosition - set to false to prevent default adjustment of position of panel near bottom of viewport
  //  new callback - afterOpen: will fire after any ajax loading happens
  togglePanel: function(e) {
    if ($(this.panel).visible()) {
      this.closePanel();
    } else {
      this.event("beforeOpen");
      var target_pos = $(this.panelTarget).cumulativeOffset();
      var target_viewpos = $(this.panelTarget).viewportOffset();
      var doc_height = document.viewport.getHeight();
      var doc_width = document.viewport.getWidth();
      var panel_height = $(this.panel).getHeight();
      var panel_width = $(this.panel).getWidth();
      var px_from_bottom = (doc_height - target_viewpos[1] - 10);
      var px_from_right = (doc_width - target_viewpos[0] - 10);
      var need_vert_px = (this.correctPosition) ? panel_height - px_from_bottom : 0;
      var need_horiz_px = (this.correctPosition) ? panel_width - px_from_right : 0;
      var panel_top = (need_vert_px > 0) ? (target_pos[1] - need_vert_px) : target_pos[1] + parseInt(this.topOffset);
      var panel_left = (need_horiz_px > 0) ? (target_pos[0] - need_horiz_px) : target_pos[0] + $(this.panelTarget).getWidth() + parseInt(this.rightOffset);
      $(this.panel).setStyle({ left: panel_left+'px', top: panel_top+'px' });
      if (window.external && typeof window.XMLHttpRequest == "undefined") {
        // if IE6, do iframe shim hack to preserve panel's z-index
        this.insertIframe(panel_top,panel_left,panel_width,panel_height); 
      }
      if (this.effects) {
        new Effect.Appear($(this.panel), { duration:this.effectDuration });
      } else {
        $(this.panel).show();
      }
      if (this.ajaxUrl) {
        new Ajax.Updater($(this.panel), this.ajaxUrl, {method:'get', onComplete:this.event("afterOpen")});
      } else {
        this.event("afterOpen");
      }
    }
    if (!this.allowDefault) Event.stop(e);
  },
  insertIframe: function(ptop, pleft, pwidth, pheight) {
    this.myFrame = document.createElement('IFRAME'); 
    this.myFrame.frameBorder = 0;
    this.myFrame.scrolling = 'no';
    this.myFrame.style.zIndex = 1;
    this.myFrame.style.position = 'absolute';
    Element.extend(this.myFrame);
    this.myFrame.setStyle({ top: ptop+'px', left: pleft+'px', width: pwidth+'px', height: pheight+'px' });
    document.body.appendChild(this.myFrame);
  },
  closePanel: function(e) {
    this.event("beforeClose");
    if (this.myFrame) {
     Element.remove(this.myFrame);
    }
    if (this.effects) {
      new Effect.Fade($(this.panel), { duration:this.effectDuration });
    } else {
      $(this.panel).hide();
    }
    if (e) Event.stop(e);
    this.event("afterClose", this.effectDuration);
  },
  doClosePanel: function() {
    this.fp_delay = this.closePanel.bind(this).delay(this.closeDelay);
  },
  stopClosePanel: function() {
    window.clearTimeout(this.fp_delay);
  },
  startObserving: function() {
    Event.observe(this.trigger, this.triggerEvent, this.togglePanel.bindAsEventListener(this));
    if (this.triggerEvent == 'mouseover' && !this.sticky) {
      Event.observe(this.trigger, 'mouseout', this.doClosePanel.bindAsEventListener(this));
      Event.observe(this.panel, 'mouseover', this.stopClosePanel.bindAsEventListener(this));
      Event.observe(this.panel, 'mouseout', this.doClosePanel.bindAsEventListener(this));
    } else if (this.triggerEvent == 'focus' && !this.sticky) {
      Event.observe(this.trigger, 'blur', this.closePanel.bindAsEventListener(this));
    }
    if (this.sticky) {
      Event.observe(this.closeButton, 'click', this.closePanel.bindAsEventListener(this));
    }
    if (!this.allowDefault && this.triggerEvent != 'click') Event.observe(this.trigger, 'click', function(e) { Event.stop(e); });
  },
  event: function(eventName,delay) {
    if(this.events[eventName]) {
      var cbDelay = (delay != undefined) ? delay : 0;
      this.events[eventName].delay(cbDelay);
    }
  },
  initialize: function(trigger, panel, opts) {
    // check that necessary libraries and elements are present
    if (!checkLibraryVersion('Prototype','1.6') || !checkLibraryVersion('Scriptaculous','1.8')) return;
    if (!trigger || !panel || !$(trigger) || !$(panel)) return;
    // set defaults
    this.trigger = trigger;
    this.panel = panel;
    this.triggerEvent = 'mouseover';
    this.sticky = false;
    this.closeButton = null;
    this.allowDefault = false;
    this.correctPosition = true;
    this.panelTarget = this.trigger;
    this.closeDelay = .45;
    this.rightOffset = 10;
    this.topOffset = 0;
    this.fp_delay = '';
    this.effectDuration = .3;
    this.effects = true;
    this.events = {};
    this.ajaxUrl = null;
    this.loading = '/images/loading/spinner-trans.gif';
    // set options
    if (opts) {
      this.opts = opts;
      if (this.opts['triggerEvent']) this.triggerEvent = this.opts['triggerEvent'];
      if (this.opts['closeButton'] && $$('#'+this.panel+' #'+this.opts['closeButton']+'').length > 0) this.closeButton = this.opts['closeButton'];
      if (this.closeButton) this.sticky = true;
      if (this.opts['allowDefault'] == true) this.allowDefault = true;
      if (this.opts['correctPosition'] == false) this.correctPosition = false;
      if (this.opts['target']) this.panelTarget = this.opts['target'];
      if (this.opts['closeDelay']) this.closeDelay = this.opts['closeDelay'];
      if (this.opts['rightOffset']) this.rightOffset = this.opts['rightOffset'];
      if (this.opts['topOffset']) this.topOffset = this.opts['topOffset'];
      if (this.opts['effects'] == 'off') this.effects = false;
      if (this.opts['effectDuration']) this.effectDuration = this.opts['effectDuration'];
      if (this.opts['beforeClose'] && typeof this.opts['beforeClose'] == 'function') this.events["beforeClose"] = this.opts['beforeClose'];
      if (this.opts['afterClose'] && typeof this.opts['afterClose'] == 'function') this.events["afterClose"] = this.opts['afterClose'];
      if (this.opts['beforeOpen'] && typeof this.opts['beforeOpen'] == 'function') this.events["beforeOpen"] = this.opts['beforeOpen'];
      if (this.opts['afterOpen'] && typeof this.opts['afterOpen'] == 'function') this.events["afterOpen"] = this.opts['afterOpen'];
      if (this.opts['ajaxUrl']) this.ajaxUrl = this.opts['ajaxUrl'];
      if (this.opts['loading']) this.loading = this.opts['loading'];
    }
    this.startObserving();
  }
});

// FADING MESSAGE CLASS
var GoAway = Class.create({
  initialize: function(message, delay) {
    this.message = message;
    this.delay = delay;
    new Effect.Fade(this.message, {delay: this.delay});
  }
});

/* simple utilities - many redundancies here, rewriting/consolidating is in order */

function jumpto(x) {
if (document.jumpForm.jumpmenu.value != "null") {
  document.location.href = x
  }
}

function Popit(url, name, height, width) {
  window.open(url, name, 'height='+height+',width='+width+',scrollbars=yes,menubar=no,resizable=yes,status=yes');
  return false;
}

function OpenWindow(url, width, height, name, options) {
  var name = (name == null) ? 'TheStranger' : name.replace(/[^a-z]/gi, '');
  var options = (options == null) ? {'scrollbars':'yes','menubar':'no','resizable':'yes','status':'yes'} : options;
  if (!options['scrollbars'] || !options['scrollbars'].match(/^(yes|no)$/)) {
    options['scrollbars'] = 'yes';
  }
  if (!options['menubar'] || !options['menubar'].match(/^(yes|no)$/)) {
    options['menubar'] = 'no';
  }
  if (!options['resizable'] || !options['resizable'].match(/^(yes|no)$/)) {
    options['resizable'] = 'yes';
  }
  if (!options['status'] || !options['status'].match(/^(yes|no)$/)) {
    options['status'] = 'yes';
  }
  var toolbars = options['menubar'];
  var atts = 'height='+height+', width='+width+', scrollbars='+options['scrollbars']+', menubar='+toolbars+', toolbar='+toolbars+', resizable='+options['resizable']+', status='+options['status'];
  var mew = window.open(url, name, atts);
  mew.focus();
  return false;
}

function popupWindow (name, url, w, h, l, t, statusbar) {
    var opts = "scrollbars,resizable";
    if (statusbar) opts += ",status";
    opts += ",width=" + w + ",height=" + h;
    opts += ",left=" + l + ",top=" + t;

    var popup = window.open(url, name, opts);
    popup.focus();
    return false;
};

/* Ajax Utilities */

function displayLoading(element,imgpath,imgclass,clobber) {
  var image = document.createElement('img');
  var imgclass = (imgclass) ? imgclass : '';
  var clobber = (clobber) ? clobber : false;
  image.setAttribute('alt','loading...');
  image.setAttribute('src',imgpath);
  image.id = 'loading';
  image.className = imgclass;
  if (clobber) element.innerHTML = '';
  element.appendChild(image);
}

/* Global Arrays */

var weekday=new Array(7)
weekday[0]="Sunday"
weekday[1]="Monday"
weekday[2]="Tuesday"
weekday[3]="Wednesday"
weekday[4]="Thursday"
weekday[5]="Friday"
weekday[6]="Saturday"

var month=new Array(12)
month[0]="Jan"
month[1]="Feb"
month[2]="Mar"
month[3]="Apr"
month[4]="May"
month[5]="June"
month[6]="July"
month[7]="Aug"
month[8]="Sept"
month[9]="Oct"
month[10]="Nov"
month[11]="Dec"

var userMessages = {
  'logout' : 'You have departed myStrangerFace.<br \/>Be careful out there.',
  'test' : 'OMG This is so queuele.<br \/><strong>Rly, rly, rly kewl.<\/strong> Kinda.'
};

function loginBar(listId,type,active) {
  var mytype = (type == undefined) ? null : type;
  var myactive = (active == 0 || active == 1) ? active : null;
  var login = readCookie('strlogin');
  var prefs = readCookie('strprefs');
  if ($(listId)) {
    if (login && prefs) {
      var userid = login.split(':')[1];
      if (type == 'face') {
        //$(listId).select('li')[0].innerHTML = '<a class="faceHome" href="/seattle/Dash">Dashboard<\/a>';
        $(listId).select('li')[0].innerHTML = '<a class="faceProfile" href="/seattle/Profile">Profile..<\/a>';
        $(listId).select('li')[1].innerHTML = '<a class="faceSettings" href="/seattle/Settings">Settings..<\/a>';
        //var myface = '<li><a class="faceHome" href="/seattle/Dash">Dashboard<\/a><\/li>';
        //$(listId).insert({top:myface});
        if (myactive != null) {
          $(listId).select('li')[myactive].addClassName('active');
        }
      } else {
        $(listId).select('li')[0].innerHTML = '<a href="http://thestranger.com/seattle/Profile">Profile<\/a> | ';
        $(listId).select('li')[1].innerHTML = '<a href="http://thestranger.com/seattle/LogOut">Log Out<\/a>';
        var greeting = '<li>Hey there, <strong>'+prefs+'<\/strong> | <\/li>';
        $(listId).insert({top:greeting});
      }
    }
  }
}

// --------------------------------------------
// Star Ratings
// --------------------------------------------

function dnSetupStarRatings(selects, starOffImgSrc, starOnImgSrc) {
    // First, create a function for updating the rating spans.
    var updateRatingDisplay = function (ratingSpan, rating) {
        ratingSpan.select("img").each(function (img, i) {
            img.src = i + 1 <= rating ? starOnImgSrc : starOffImgSrc;
        });
    };
    
    $A(selects).each(function (sel) {
        // Use a span to hold the ratings star images.
        var ratingSpan = new Element("span", { "class": "dnStarRating" });
        ratingSpan.setStyle({ cursor: "pointer"});
        
        // Create the ratings span elements with the curent value.
        var curRating = sel.value ? parseInt(sel.value) : 0;
        for (var i = 1; i <= 5; i++) {
            ratingSpan.insert( new Element("img", {
                src: i <= curRating ? starOnImgSrc : starOffImgSrc,
                title: i == 1 ? "1 star" : i + " stars",
                alt: i  // Keep the rating value in the alt attribute.
            }));
        }
        
        // Attach event handlers to the star images.
        ratingSpan.select("img").each(function (img) {
            img.observe("click", function (e) {
                sel.value = img.alt;
                updateRatingDisplay(ratingSpan, parseInt(this.alt));
            });
        
            img.observe("mouseover", function (e) {
                if (this.dnStarRatingsMouseIn) return;
                this.dnStarRatingsMouseIn = true;
                updateRatingDisplay(ratingSpan, parseInt(this.alt));
            });
            
            img.observe("mouseout", function (e) {
                this.dnStarRatingsMouseIn = false;
                updateRatingDisplay(ratingSpan, parseInt(sel.value));
            })
        });
        
        // Hide the select and add the new ratings elements.
        $(sel).hide().insert({ after: ratingSpan });
    });
};

function fixEmptyAds() {
  var ads = $$('.fixAdMargin');
  ads.each(function(a) {
    var my_ad = a;
    if (my_ad != null && !my_ad.hasClassName('noAdjust')) {
      var adjust = false;
      var ads_kids = my_ad.childElements();
      ads_kids.each(function(ak) {
        if (ak.nodeName != "SCRIPT" && ak.nodeName != "NOSCRIPT") {
          adjust = true;
          return;
        }
      });
      if (adjust) {
        if (my_ad.hasClassName('bottom')) {
          my_ad.setStyle({ marginBottom: '10px' });
        } else {
          my_ad.setStyle({ marginTop: '10px' });
        }
      }
    }
  });
}

function hideEmptyAds(ad_id) {
  var ad = $(ad_id);
  var adtags = ad.select('img', 'a');
  if (adtags.length == 0) {
    ad.hide();
  }
}

function ajaxObject(url,targetID,params) {
  //if(params){params=params.toQueryParams();}
  var that=this;
  var updating = false;
  this.callback = function() {}
  this.doUpdate = function(params) {
    if (updating==true) { return false; }
    updating=true;
    var AJAX = null;
    if (window.XMLHttpRequest) {
      AJAX=new XMLHttpRequest();
    } else {
      AJAX=new ActiveXObject("Microsoft.XMLHTTP");
    }
    if (AJAX==null) {
      // The browser doesn't support AJAX.
      return false;
    } else {
      AJAX.onreadystatechange = function() {
        if (AJAX.readyState==4 || AJAX.readyState=="complete") {
          if(params){params=params.toQueryParams();}
          if(params && params.statusTarget) {
            $(params.statusTarget).removeClassName('loading');
          }
          if(AJAX.responseText.isJSON()) {
            
            var result = AJAX.responseText.evalJSON(true);
            if(result.targetReplacement) {
              theTarget.replace(result.targetReplacement);
            } else if(result.targetUpdate) {
              theTarget.update(result.targetUpdate);
            } else if(result.targetInsert) {
              theTarget.insert({top:result.targetInsert});
            } else if(result.flashMessage) {
              if(params && params.message) {
                message = $(params.message);
              } else {
                if(!$(targetID+'-StatusMessage')) {$(targetID).insert({top:'<div id="'+targetID+'-StatusMessage" onclick="this.hide();"></div>'});}
              }
              message = $(targetID+'-StatusMessage');
              if(message){message.update(result.flashMessage);}
            }
            if(result.addMessageClass) {
              message.addClassName(result.addMessageClass);
            }
            if(result.addTargetClass) {
              theTarget.addClassName(result.addTargetClass);
            }
          } else {
            
            if(params && params.loadType=='replace') {
              $(targetID).replace(AJAX.responseText);
            } else {
              $(targetID).update(AJAX.responseText);
            }
          }
          delete AJAX;
          updating=false;
          that.callback();
        }
      }
      if(params && params.method=="POST") {
        AJAX.open("POST", uri, true);
      } else {
        AJAX.open("GET", uri, true);
      }
      // AJAX.setRequestHeader("Content-type", "text/html; charset=ISO-8859-1");
      AJAX.send(null);
      return true;
    }
  }
  var theTarget = $(targetID);
  var urlCall = url;
}

function loadComponent(componentID,targetID,params) {
  // optional: params.message[divID], params.loadType[replace|update] params.activeMessageClass[cssClassName]
  var timestamp = new Date().getTime();
  if(params && params.task) {
    uriHash = $H({ task: params.task });
  } else if(componentID.match(/\?/)) {
    uriHash = componentID.gsub('^.*\?','').toQueryParams();
  } else {
    uriHash = $H({ show:componentID });
  }
  poundHash = $H(window.location.search.substring(1).toQueryParams()); // #hash
  querystringHash = $H(window.location.hash.substring(1).toQueryParams()); // ?querystring
  if(poundHash) {
    uriHash.update(poundHash);
  }
  if(querystringHash){
    uriHash.update(querystringHash);
  }
  uriHash.update({ show:componentID, cb:timestamp, ajax:'true' });
  if(params){
    uriHash.update(params);
    if(params.message) {
      message = $(params.message);
    } else {
      $(targetID).insert({top:'<div id="'+targetID+'-StatusMessage" onclick="this.hide();"></div>'});
      message = $(targetID+'-StatusMessage');
    }
    if(params.loadingMessage) {
      loadingMessageDiv = '<p class="statusMessage" onclick="this.hide();">'+params.loadingMessage+'</p>';
      message.update(loadingMessageDiv);
    }
    if(params.statusTarget) {
      statusTarget = $(params.statusTarget);
      statusTarget.setAttribute('onclick','return false');
      statusTarget.update('Loading');
      statusTarget.addClassName('loading');
    }
    if(params.loading) {
      if(params.loading=='default') {
        $(targetID).insert('<div id="'+targetID+'-LoadStatus" class="loadStatus" onclick="this.hide();"><img src="/images/spacer.gif" class="spinner" /> Loading</div>');
      }
    }
  }
  uri = '/seattle/Macros/LoadComponent?'+uriHash.toQueryString();
  var myLoader = new ajaxObject(uri,targetID,params);
  myLoader.doUpdate(uri);
  return false;
}

/* ------------------------------------------------- */
/* Ajax Helpers */
/* ------------------------------------------------- */
function jObject(url, callbackFunction) {
  var that=this;
  this.updating = false;
  this.abort = function() {
    if (that.updating) {
      that.updating=false;
      that.AJAX.abort();
      that.AJAX=null;
    }
  }
  this.update = function(passData,postMethod) {
    if (that.updating) { return false; }
    that.AJAX = null;
    if (window.XMLHttpRequest) {
      that.AJAX=new XMLHttpRequest();
    } else {
      that.AJAX=new ActiveXObject("Microsoft.XMLHTTP");
    }
    if (that.AJAX==null) {
      return false;
    } else {
      that.AJAX.onreadystatechange = function() {
        if (that.AJAX.readyState==4) {
          that.updating=false;
          that.callback(that.AJAX.responseText,that.AJAX.status,that.AJAX.responseXML);
          that.AJAX=null;
        }
      }
      that.updating = new Date();
      if (/post/i.test(postMethod)) {
        var uri=urlCall+'?'+that.updating.getTime();
        that.AJAX.open("POST", uri, true);
        that.AJAX.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
        that.AJAX.setRequestHeader("Content-Length", passData.length);
        that.AJAX.send(passData);
      } else {
        var uri=urlCall+'?'+passData+'&timestamp='+(that.updating.getTime());
        that.AJAX.open("GET", uri, true);
        that.AJAX.send(null);
      }
      return true;
    }
  }
  var urlCall = url;
  this.callback = callbackFunction || function () { };
}

function appendToElement(element,file,params) {
  var myRequest = new jObject(file);
  if(params){params=$H(params).toQueryString();}
  myRequest.update(params,'post');
  myRequest.callback = function(responseText, responseStatus, responseXML) {
    $(element).insert(responseText);
  }
}

function updateElement(element,file,params) {
  var myRequest = new jObject(file);
  if(params){params=$H(params).toQueryString();}
  myRequest.update(params,'post');
  myRequest.callback = function(responseText, responseStatus, responseXML) {
    $(element).update(responseText);
  }
}
function replaceElement(element,file,params) {
  var myRequest = new jObject(file);
  if(params){params=$H(params).toQueryString();}
  $(element).update('Loading...');
  myRequest.update(params,'post');
  myRequest.callback = function(responseText, responseStatus, responseXML) {
    $(element).replace(responseText);
  }
}

function submitAjaxForm(formId,formAction,elementToUpdate,label,img) {
  var label = label ? label : 'Loading...';
  var img = img ? '<img src="/images/loading/'+img+'" />' : '<img src="/images/loading/spinner-small-trans.gif" />';
  var params = $(formId).serialize(true);
  var actionPath = '/seattle/custom/actions/';
  var elementToUpdate = elementToUpdate || formId;
  $(elementToUpdate).update(img + label);
  updateElement(elementToUpdate,actionPath+formAction,params);
}
/* ------------------------------------------------- */
/* END Ajax Helpers */
/* ------------------------------------------------- */


/* Global Load Events */

addLoadEvent(setupLinkBehavior);
addLoadEvent(acceptsCookies);
addLoadEvent(prepareForms);
addLoadEvent(animateAnchorLinks);
addLoadEvent(fixEmptyAds);