// A way over complicated mouseover
Mblst.Mouseover = Class.create();
Mblst.Mouseover.prototype = {
  /**
   * Constructor
   */
  initialize: function(imgElem, options) {
    this.imgElem = $(imgElem);

    this.options = Object.extend({
      imgHoverClassName: 'imgHover'
    }, options || {});

    Element.forceHandCursor(this.imgElem);
    this.preloadHoverImage();

    this.mouseoverListener = this.onEnterImgHover.bindAsEventListener(this);
    this.mouseoutListener = this.onLeaveImgHover.bindAsEventListener(this);
    Event.observe(this.imgElem, 'mouseover', this.mouseoverListener);
    Event.observe(this.imgElem, 'mouseout', this.mouseoutListener);
  },
  /**
   * Control Methods
   */

  enterTriggerHover: function() {
    this.onEnterTriggerHover();
  },
  leaveTriggerHover: function() {
    this.onLeaveTriggerHover();
  },
  /**
   * Event handlers
   */
  onEnterImgHover: function() {
    if(navigator.appVersion.indexOf('MSIE')>0) {
      var filterValue = Element.getStyle(this.hoverImage, 'filter');
      if (filterValue) Element.setStyle(this.imgElem, {filter: filterValue});
    } else {
      this.imgElem.src = this.hoverImage.src;
    }
  },
  onLeaveImgHover: function() {
    if (this.imgElem && !this.freezingHoverMode) {
      if(navigator.appVersion.indexOf('MSIE')>0) {
        var filterValue = Element.getStyle(this.imgElem, 'filter');
        if (filterValue) {
          var newFilterValue = filterValue.replace(/_on(\.png)/, '_off$1');
          Element.setStyle(this.imgElem, {filter: newFilterValue});
        }
      } else {
        this.imgElem.src = this.imgElem.src.replace(/_on(\.[^.]+)$/, '_off$1');
      }
    }
  },
  /**
   * Utility methods
   */
  preloadHoverImage: function() {
    this.hoverImage = new Image();
    if(navigator.appVersion.indexOf('MSIE')>0) {
      var filterValue = Element.getStyle(this.imgElem, 'filter');
      if (filterValue) {
        var newFilterValue = filterValue.replace(/_off(\.png)/, '_on$1');
        Element.setStyle(this.hoverImage, {filter: newFilterValue});
      }
    } else {
      this.hoverImage.src = this.imgElem.src.replace(/_off(\.[^.]+)$/, '_on$1');
    }
  },
  dispose: function() {
    Event.stopObserving(this.imgElem, 'mouseover', this.mouseoverListener);
    Event.stopObserving(this.imgElem, 'mouseout', this.mouseoutListener);
  }
};


// This is the base moblastic trigger class
Mblst.Trigger = Class.create();
Mblst.Trigger.prototype = {
  /**
   * Constructor
   */
  initialize: function(controlObj, triggerElem, options) {
    this.freezingHoverMode = false;
    this.controlObj = controlObj;
    this.triggerElem = $(triggerElem);

    this.options = Object.extend({
      referenceElem: $(triggerElem),
      triggerElem: triggerElem,
      auxTriggerElems: [],
      controlShowingClassName: 'triggerControlShowing',
      triggerHoverClassName: 'triggerHover',
      forceHandCursor: true,

      beforeShowInternal: this.onControlShow.bind(this),
      afterHideInternal: this.onControlHide.bind(this)
    }, options || {});

    /**
     * If a trigger image is given as an option, assign it.  Otherwise,
     * if the triggerElem is an img, assign it as the triggerImage.
     */
    if (this.options.triggerImage) {
      this.triggerImage = $(this.options.triggerImage);
      if (this.options.forceHandCursor) Element.forceHandCursor(this.triggerImage);
      this.preloadHoverImage();
    } else {
      if(this.triggerElem && this.triggerElem.tagName == 'IMG') {
        this.triggerImage = this.triggerElem;
        if (this.options.forceHandCursor) Element.forceHandCursor(this.triggerImage);
        this.preloadHoverImage();
      } else {
        this.triggerImage = null;
      }
    }

    this.onclickListener = this.trigger.bindAsEventListener(this);
    this.onmouseupListener = this.clearLink.bindAsEventListener(this);

    if (this.triggerElem) {
      Event.observe(this.triggerElem, 'click', this.onclickListener);
      if (this.triggerElem.tagName == 'A') {
        Event.observe(this.triggerElem, 'mouseup', this.onmouseupListener);
      }
    }

    if (this.options.auxTriggerElems) {
      var onclicklistener = this.onclickListener;
      var onmouseuplistener = this.onmouseupListener;
      this.options.auxTriggerElems.each( function(elem){
        Event.observe(elem, 'click', onclicklistener);
        if (elem.tagName == 'A') Event.observe(elem, 'mouseup', onmouseuplistener);
      });
    }

    if (this.triggerImage) {
      this.mouseoverListener = this.onEnterTriggerHover.bindAsEventListener(this);
      this.mouseoutListener = this.onLeaveTriggerHover.bindAsEventListener(this);
      Event.observe(this.triggerElem, 'mouseover', this.mouseoverListener);
      Event.observe(this.triggerElem, 'mouseout', this.mouseoutListener);
    }

  },
  /**
   * Control Methods
   */
  trigger: function() {
    this.controlObj.trigger(this.options);
    return false;
  },
  onControlShow: function() {
    this.freezingHoverMode = true;
    this.enterTriggerHover();
    if (this.triggerElem) Element.addClassName(this.triggerElem, this.options.controlShowingClassName);
    if (this.triggerImage) Element.addClassName(this.triggerImage, this.options.controlShowingClassName)
  },
  onControlHide: function() {
    this.freezingHoverMode = false;
    this.leaveTriggerHover();
    if (this.triggerElem) Element.removeClassName(this.triggerElem, this.options.controlShowingClassName);
    if (this.triggerImage) Element.removeClassName(this.triggerImage, this.options.controlShowingClassName)
  },
  enterTriggerHover: function() {
    this.onEnterTriggerHover();
  },
  leaveTriggerHover: function() {
    this.onLeaveTriggerHover();
  },
  /**
   * Event handlers
   */
  onEnterTriggerHover: function() {
    if (this.triggerImage) {
      if(navigator.appVersion.indexOf('MSIE')>0) {
        var filterValue = Element.getStyle(this.triggerHoverImage, 'filter');
        if (filterValue) Element.setStyle(this.triggerImage, {filter: filterValue});
      } else {
        this.triggerImage.src = this.triggerHoverImage.src;
      }
    }
  },
  onLeaveTriggerHover: function() {
    if (this.triggerImage && !this.freezingHoverMode) {
      if(navigator.appVersion.indexOf('MSIE')>0) {
        var filterValue = Element.getStyle(this.triggerImage, 'filter');
        if (filterValue) {
          var newFilterValue = filterValue.replace(/_on(\.png)/, '_off$1');
          Element.setStyle(this.triggerImage, {filter: newFilterValue});
        }
      } else {
        this.triggerImage.src = this.triggerImage.src.replace(/_on(\.[^.]+)$/, '_off$1');
      }
    }
  },
  /**
   * Utility methods
   */
  preloadHoverImage: function() {
    if (this.triggerImage) {
		this.triggerHoverImage = new Image();
        if(navigator.appVersion.indexOf('MSIE')>0) {
          var filterValue = Element.getStyle(this.triggerImage, 'filter');
          if (filterValue) {
            var newFilterValue = filterValue.replace(/_off(\.png)/, '_on$1');
            Element.setStyle(this.triggerHoverImage, {filter: newFilterValue});
          }
        } else {
          this.triggerHoverImage.src = this.triggerImage.src.replace(/_off(\.[^.]+)$/, '_on$1');
        }
    }
  },
  clearLink: function() {
    if (this.triggerElem.tagName == 'A') this.triggerElem.blur();
  },
  dispose: function() {
    Event.stopObserving(this.triggerElem, 'click', this.onclickListener);
    Event.stopObserving(this.triggerElem, 'mouseup', this.onmouseupListener);
    if (this.triggerImage) {
      Event.stopObserving(this.triggerElem, 'mouseover', this.mouseoverListener);
      Event.stopObserving(this.triggerElem, 'mouseout', this.mouseoutListener);
    }    
  }
};

// This object triggers a control after an element has been hovered for
// a specified amount of time.


Mblst.HoverTrigger = Class.create();
Mblst.HoverTrigger.prototype = {
  /**
   * Constructor
   */
  initialize: function(controlObj, triggerElem, options) {
    this.controlObj = controlObj;
    this.triggerElem = $(triggerElem);
    this.triggerList = [];
    this.triggeredByThis = false;
    this.hoveredByThis = false;
    this.showProcId = null;
    this.hideProcId = null;

    this.options = Object.extend({
      containerIsTrigger: true,
      positionToCursor: true,
      referenceElem: document.body,
      triggerElem: triggerElem,
      auxTriggerElems: [],
      positionOffsets: Mblst.Position.topLeftOnTopLeft,
      controlShowingClassName: 'triggerControlShowing',
      triggerHoverClassName: 'triggerHover',
      hideDelay: 1000,
      showDelay: 500,
      beforeShowInternal: this.onControlShow.bind(this),
      afterHideInternal: this.onControlHide.bind(this)
    }, options || {});

    this.mouseoverListener = this.onEnterTriggerHover.bindAsEventListener(this);
    this.mouseoutListener = this.onLeaveTriggerHover.bindAsEventListener(this);
    this.clickListener = this.onClickTrigger.bindAsEventListener(this);

    if (this.triggerElem) {
      if(this.triggerElem.tagName == 'IMG') {
        this.triggerElem.title = '';
        this.triggerElem.alt = '';
      }
      var images = this.triggerElem.getElementsByTagName('IMG');
      images = $A(images);
      images.each( function(elem){
        elem.title = '';
        elem.alt = '';
      });

      if(this.triggerElem.tagName == 'A') this.triggerElem.title = '';
      var links = this.triggerElem.getElementsByTagName('A');
      links = $A(links);
      links.each( function(elem){
        elem.title = '';
      });
      Event.observe(this.triggerElem, 'mouseover', this.mouseoverListener);
      Event.observe(this.triggerElem, 'mouseout', this.mouseoutListener);
      Event.observe(this.triggerElem, 'click', this.clickListener);
      this.triggerList.push(this.triggerElem);
    }

    if (this.options.auxTriggerElems) {
      var mouseoverlistener = this.mouseoverListener;
      var mouseoutlistener = this.mouseoutListener;
      var clicklistener = this.clickListener;
      this.options.auxTriggerElems = this.options.auxTriggerElems.collect( function(elem){
        return $(elem);
      });
      this.options.auxTriggerElems.each( function(elem){
        Event.observe(elem, 'mouseover', mouseoverlistener);
        Event.observe(elem, 'mouseout', mouseoutlistener);
        Event.observe(elem, 'click', clicklistener);
      });

      this.triggerList.concat(this.options.auxTriggerElems);
    }

    if (this.options.containerIsTrigger) {
      this.controlmouseoverListener = this.onEnterControlHover.bindAsEventListener(this);
      this.controlmouseoutListener = this.onLeaveTriggerHover.bindAsEventListener(this);
      Event.observe(this.controlObj.containerElem, 'mouseover', this.controlmouseoverListener);
      Event.observe(this.controlObj.containerElem, 'mouseout', this.controlmouseoutListener);
      this.triggerList.push(this.container);
    }
  },
  /**
   * Control Methods
   */
  trigger: function() {
    this.controlObj.trigger(this.options);
    this.triggeredByThis = true;
    return false;
  },
  dismiss: function() {
    if ($(this.controlObj.options.triggerElem) != $(this.options.triggerElem)) {//only handle your own
      this.triggeredByThis = false;
      return false;
    }

    this.triggeredByThis = false;
    this.controlObj.dismiss();
    return false;
  },
  onControlShow: function() {},
  onControlHide: function() {},
  /**
   * Event handlers
   */
  onClickTrigger: function(evt) {
	if (this.showProcId) {
		clearTimeout(this.showProcId);
	}
  },
  onEnterControlHover: function(evt) {
    if ($(this.controlObj.options.triggerElem) != $(this.options.triggerElem)) return false; //only handle your own
    if (this.hideProcId) clearTimeout(this.hideProcId);
    this.hoveredByThis = true;
  },
  onEnterTriggerHover: function(evt) {
    if (this.hoveredByThis) return; // handle once
    if (this.triggeredByThis) { //maintain hover status for any trigger
      if (this.hideProcId) clearTimeout(this.hideProcId);
      this.hoveredByThis = true;
      return;
    }

    // In cases where we are net yet triggered, but a pending hide exists
    // from a previous hover, we need to clear the pending hide.
	if (this.hideProcId) {
		clearTimeout(this.hideProcId);
	}

    this.hoveredByThis = true;
	this.showProcId = this.doShow(evt);
  },
  onLeaveTriggerHover: function(evt) {
    if (!this.hoveredByThis) return; // handle once
	if (this.showProcId) {
		clearTimeout(this.showProcId); // not hovering anymore, so clear pending shows
	}
	this.hideProcId = setTimeout(this.dismiss.bind(this), this.options.hideDelay);
    this.hoveredByThis = false;
  },
  doShow: function(evt){
    if (this.options.positionToCursor) {
      var yOffset = 25;
      this.options.refOffsetLeft = Event.pointerX(evt);
      this.options.refOffsetTop = Event.pointerY(evt) + yOffset;
    }

    return setTimeout( this.trigger.bind(this), this.options.showDelay);
  },
  dispose: function() {
    Event.stopObserving(this.triggerElem, 'mouseover', this.mouseoverListener);
    Event.stopObserving(this.triggerElem, 'mouseout', this.mouseoutListener);
  }
};


//Base class for moblastic controls
//
// Implements action listener
Mblst.Container = Class.create();
Mblst.Container.className = 'jsContainer';
Mblst.Control = Class.create();
Mblst.Control.instanceNonce = 0;
Mblst.Control.prototype = {
  /**
   * Constructor
   */
  initialize: function(options) {
    Mblst.Control.instanceNonce++;
    this.containerElem  = null;
    this.state          = 'idle';
    this.showing        = false;

    this.options        = Object.extend({
      containerClassName: 'controlContainer',
      processingClassName: 'controlProcessing',
      showingClassName: 'controlShowing',
      hoverClassName: 'controlHover'
    }, options || {});
    this.lastOptions    = this.options;

    /**
     * - If a trigger Elem is given => create trigger obj & assign
     */
    if (!this.options.triggerObject && this.options.triggerElem) {
      var triggerImage = this.options.triggerImage || null;
      this.options.triggerObject = new Mblst.Trigger(this, this.options.triggerElem, {triggerImage: triggerImage});
    } else if (!this.options.triggerObject) {
      this.options.triggerObject = null;
    }

    /**
     * Build container, if one has not been provided
     */
    if (this.options.containerElem) {
      this.containerElem = $(this.options.containerElem);
    } else {
      if(!this.options.containerElemId) {
        this.options.containerElemId = "control_container-"+Mblst.Control.instanceNonce;
        if ($(this.options.containerElemId)) {
          // there's already an elem with that id, don't specify an id
          this.options.containerElemId = null;
        }
      }
      this.containerElem = document.createElement("div");
      this.containerElem.className = this.options.containerClassName;
      if (this.options.containerElemId) this.containerElem.id = this.options.containerElemId;
      this.event('beforeCreate');
      if(this.create) this.create();
      this.event('afterCreate');
      // insert container into the DOM (may be absolute pos or inline)
      this.event('beforeInsert');
      this.insert();
      this.event('afterInsert');
    }
    
    /* prevent memory leaks in IE */
    //if (navigator.appVersion.match(/\bMSIE\b/))
      //Event.observe(window, 'unload', this._unloadControlEventCallbacks.bind(this), false);    
  },

  /**
   * Control methods
   */
  trigger: function(options) {
    if (this.state != 'idle') {
      if (options.triggerElem && options.triggerElem == this.options.triggerElem){
        this.event('beforeDismiss');
        this.dismiss(true);
        this.event('afterDismiss');
        return;
      }
      this.event('beforeDismiss');
      this.dismiss(true);
      this.event('afterDismiss');
	}

    this._setOptions(options);
    this.event('beforeLoad');
    if(this.load) this.load();
    this.event('afterLoad');

	this.state = 'showing';

    //this.enterTriggerHover();

    this.event('beforeShow');
    this.show(true);
    this.event('afterShow');
  },
  dismiss: function(noEffect, delay) {
	this.state = 'idle';
    this.event('beforeHide');
	this.hide(noEffect, delay);
    this.event('afterHide');

    this.event('beforeReset');
    if(this.reset) this.reset();
    this.event('afterReset');

	this._resetOptions();
	return false
  },
  /**
   *
   */
  addTriggerListener: function(triggerListener) {

  },
  event: function(eventName) {
    if(this.options && this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
    if(this.options && this.options[eventName]) this.options[eventName](this);
  },
  _setOptions: function(options) {
    this.lastOptions = this.options;
    this.options = Object.extend(this.options, options || {});
  },
  _resetOptions: function() {
    this.options = this.lastOptions;
  },
  _unloadControlEventCallbacks: function() {
    if (!this.options) return;
    try {  
      $H(this.options).each(function(pair) {
        if( pair.value &&
            (typeof pair.value == "function") &&
            (typeof pair.key == "string") &&
            (pair.key.indexOf("before") === 0 || pair.key.indexOf("after") === 0))
        {
          this.options[pair.key][0] = null;
          pair.value[0] = null;
        }
      });
    } catch (e) {}
    this.options = false;
  }
};

Mblst.Paginate = Class.create();
Object.extend(Object.extend(Mblst.Paginate.prototype, Mblst.Control.prototype), {
  initialize: function(element, url, page, count, options) {
    this.url = url;
    this.page = page || 1;
    this.count = count || 10;
    this.total = this.count + 1;
    this.last = 2;
    this.element = $(element);
    this.loaded = false;

    this.options = Object.extend({
      containerElem: this.element,
      dismissErrors: true,
      loadOnInit: true,
      alwaysReload: true,
      useLoadingTriggerRefY: false,
      auxQueryParams: '',
      feedbackObj: Mblst.FeedbackElem.PrimaryFeedback,
      onComplete: function(transport, element) {},
      onFailure: function(transport) {},
      callback: Mblst.paginateRequestSerialize.bind(this),
      emptyText: 'No results found :-(',
      loadingText: 'Loading...',
      loadingClassName: 'loading',
      disabledClassName: 'disabled',
      currentPageId: 'ajax_pagin_curr_page'
    }, options || {});


    this.options.mblstReqOptions = Object.extend({
      outputType: 'text/html',
      auxJSON: 'pagination'
    }, this.options.mblstReqOptions || {});

    /**
     * If not highlight end color is specified, set to background-color of first non-transparent ancestor
     */
    if (!this.options.highlightendcolor) {
      this.options.highlightendcolor = Element.getVisibleBackgroundColor(element);
    }

    if (!this.options.highlightcolor) {
      this.options.highlightcolor = Mblst.calcHighlightColor(this.options.highlightendcolor);
    }
    
    // If useLoadingTriggerRefY is specified, update indicator object
    if (this.options.useLoadingTriggerRefY && this.options.indicatorObj) {
      this.options.indicatorObj.options.useTriggerElemRefY = true;
    }

    this.onmouseupListener = this.clearLink.bindAsEventListener(this);
    if (this.options.firstElem) {
      this.options.firstElem = $(this.options.firstElem);
      this.onclickFirst = this.loadFirst.bindAsEventListener(this);
      Event.observe(this.options.firstElem, 'click', this.onclickFirst);
      if (this.options.firstElem.tagName == 'A') {
        Event.observe(this.options.firstElem, 'mouseup', this.onmouseupListener);
      }
    }
    if (this.options.auxFirstElem) {
      this.options.auxFirstElem = $(this.options.auxFirstElem);
      Event.observe(this.options.auxFirstElem, 'click', this.onclickFirst);
      if (this.options.auxFirstElem.tagName == 'A') {
        Event.observe(this.options.auxFirstElem, 'mouseup', this.onmouseupListener);
      }
    }    
    if (this.options.prevElem) {
      this.options.prevElem = $(this.options.prevElem);
      this.onclickPrev = this.loadPrev.bindAsEventListener(this);
      Event.observe(this.options.prevElem, 'click', this.onclickPrev);
      if (this.options.prevElem.tagName == 'A') {
        Event.observe(this.options.prevElem, 'mouseup', this.onmouseupListener);
      }
    }
    if (this.options.auxPrevElem) {
      this.options.auxPrevElem = $(this.options.auxPrevElem);
      Event.observe(this.options.auxPrevElem, 'click', this.onclickPrev);
      if (this.options.auxPrevElem.tagName == 'A') {
        Event.observe(this.options.auxPrevElem, 'mouseup', this.onmouseupListener);
      }
    }    
    if (this.options.nextElem) {
      this.options.nextElem = $(this.options.nextElem);
      this.onclickNext = this.loadNext.bindAsEventListener(this);
      Event.observe(this.options.nextElem, 'click', this.onclickNext);
      if (this.options.nextElem.tagName == 'A') {
        Event.observe(this.options.nextElem, 'mouseup', this.onmouseupListener);
      }
    }
    if (this.options.auxNextElem) {
      this.options.auxNextElem = $(this.options.auxNextElem);
      Event.observe(this.options.auxNextElem, 'click', this.onclickNext);
      if (this.options.auxNextElem.tagName == 'A') {
        Event.observe(this.options.auxNextElem, 'mouseup', this.onmouseupListener);
      }
    }        
    if (this.options.lastElem) {
      this.options.lastElem = $(this.options.lastElem);
      this.onclickLast = this.loadLast.bindAsEventListener(this);
      Event.observe(this.options.lastElem, 'click', this.onclickLast);
      if (this.options.lastElem.tagName == 'A') {
         Event.observe(this.options.lastElem, 'mouseup', this.onmouseupListener);
      }
    }
    if (this.options.auxLastElem) {
      this.options.auxLastElem = $(this.options.auxLastElem);
      Event.observe(this.options.auxLastElem, 'click', this.onclickLast);
      if (this.options.auxLastElem.tagName == 'A') {
        Event.observe(this.options.auxLastElem, 'mouseup', this.onmouseupListener);
      }
    }      

    // Call parent constructor w/ these options
    Mblst.Control.prototype.initialize.call(this, this.options);

    //Load the control
    if (this.options.loadOnInit) this.load();
  },
  /**
   * Control methods
   */
  show: function() {},
  hide: function() {},
  clear: function() {
    this.element.innerHTML = '';
  },
  load: function() {
    if (!this.loaded || this.options.alwaysReload) {
      this.loadCurrent();
    }
  },
  setUrl: function(url, page, count) {
    this.url = url;
    this.page = page || 1;
    if(count) this.count = count;
    this.total = null;
    this.last = null;
    this.loaded = false;
  },
  loadUrl: function(url, page, count) {
    this.setUrl(url, page, count);
    return this.loadCurrent();
  },
  /**
   * Deprecated. use loadUrl
   */
  resetUrl: function(url, page, count) {
    return this.loadUrl(url, page, count);
  },
  setResults: function(totalRows)
  {
    q = totalRows / this.count;
    this.last = Math.ceil(q);
    this.total = totalRows;
  },
  setPrevText: function(text)
  {
    if (this.options.prevElem) this.options.prevElem.innerHTML = text;
    if (this.options.auxPrevElem) this.options.auxPrevElem.innerHTML = text;
  },
  setNextText: function(text)
  {
    if (this.options.nextElem) this.options.nextElem.innerHTML = text;
    if (this.options.auxNextElem) this.options.auxNextElem.innerHTML = text;
  },
  loadFirst: function(evt){
    this.prepareLoading(evt);
    return this.onSubmit(1);
  },
  loadPrev: function(evt){
    this.prepareLoading(evt);
    if (this.page <= 1) return false;
    this.onSubmit(this.page - 1);
    return false;
  },
  loadCurrent: function(){
    this.onSubmit(this.page);
    return false;
  },
  loadNext: function(evt) {
    this.prepareLoading(evt);
    if (!this.last || this.page >= this.last) return false;
    this.onSubmit(this.page + 1);
    return false;
  },
  loadLast: function(evt) {
    this.prepareLoading(evt);
    if (!this.last) return false;
    return this.onSubmit(this.last);
  },
  onSubmit: function(page) {
    this.onLoading();
    new Ajax.Request(
      this.url,
      Object.extend({
        asynchronous: true,
        parameters: this.options.callback(page, this.count, null, this.options.auxQueryParams),
        onFailure: this.onFailure.bind(this),
        onSuccess: this.onSuccess.bind(this)
      }, this.options.ajaxOptions)
    );
    return false;
  },
  onLoading: function() {
    this.loading = true;
    this.showLoading();
  },
  prepareLoading: function(evt) {
    if (this.options.useLoadingTriggerRefY && evt) {
      var triggerElem = Event.element(evt);    
      this.options.indicatorObj.options.triggerElem = triggerElem;
    }    
  },
  showLoading: function() {
    if (this.options.indicatorObj) {
      this.options.indicatorObj.show(this.options.loadingText);
    }
    Element.show(this.element);
  },
  hideLoading: function() {
    if (this.options.useLoadingTriggerRefY) {
      this.options.indicatorObj.options.triggerElem = null;
    }     
    if (this.options.indicatorObj) {
      this.options.indicatorObj.hide();
    }   
  },
  onSuccess: function(transport, json) {
    this.hideLoading();
    try {
      json = transport.getResponseHeader('X-JSON');
    } catch (e) {}
    try {
      json = eval(json);
    } catch (e) {}
    var updateText = (transport.responseText) ? transport.responseText :
                                                this.options.emptyText;
    Element.update(this.element, updateText);
    this.setResults(json[0].total);
    this.page = json[0].page;

    if (this.total == 0) {
      this.disableAll();
    } else {
      if (this.total > 0 && this.page == 1) {
        if (this.options.firstElem) this.options.firstElem.id = this.options.currentPageId;
        this.disablePrev();
      }else{
        if (this.options.firstElem) this.options.firstElem.id = '';
        this.enablePrev();
      }
      if (this.last && this.page == this.last) {
        if (this.options.lastElem) this.options.lastElem.id = this.options.currentPageId;
        this.disableNext();
      } else {
        if (this.options.lastElem) this.options.lastElem.id = '';
        this.enableNext();
      }
    }
    this.options.onComplete(transport, this.element);
    this.loaded = true;
    this.state = 'showing';    
  },
  onFailure: function(transport) {
    this.hideLoading();
    var feedbackMsg = (transport.responseText) ? transport.responseText.stripTags() : 'We could not process your request :-( Please try again later.';
    if (!this.options.supressFeedback) this.options.feedbackObj.showError(feedbackMsg, this.options.dismissErrors);

    this.options.onFailure(transport);
    return false;
  },
  enableFirst: function() {
    if (this.options.firstElem) Element.removeClassName(this.options.firstElem, this.options.disabledClassName);
    if (this.options.auxFirstElem) Element.removeClassName(this.options.auxFirstElem, this.options.disabledClassName);
  },
  enablePrev: function() {
    if (this.options.prevElem) Element.removeClassName(this.options.prevElem, this.options.disabledClassName);
    if (this.options.auxPrevElem) Element.removeClassName(this.options.auxPrevElem, this.options.disabledClassName);
  },
  enableNext: function() {
    if (this.options.nextElem) Element.removeClassName(this.options.nextElem, this.options.disabledClassName);
    if (this.options.auxNextElem) Element.removeClassName(this.options.auxNextElem, this.options.disabledClassName);
  },
  enableLast: function() {
    if (this.options.lastElem) Element.removeClassName(this.options.lastElem, this.options.disabledClassName);
    if (this.options.auxLastElem) Element.removeClassName(this.options.auxLastElem, this.options.disabledClassName);
  },
  disableFirst: function() {
    if (this.options.firstElem) Element.addClassName(this.options.firstElem, this.options.disabledClassName);
    if (this.options.auxFirstElem) Element.addClassName(this.options.auxFirstElem, this.options.disabledClassName);
  },
  disablePrev: function() {
    if (this.options.prevElem) Element.addClassName(this.options.prevElem, this.options.disabledClassName);
    if (this.options.auxPrevElem) Element.addClassName(this.options.auxPrevElem, this.options.disabledClassName);
  },
  disableNext: function() {
    if (this.options.nextElem) Element.addClassName(this.options.nextElem, this.options.disabledClassName);
    if (this.options.auxNextElem) Element.addClassName(this.options.auxNextElem, this.options.disabledClassName);
  },
  disableLast: function() {
    if (this.options.lastElem) Element.addClassName(this.options.lastElem, this.options.disabledClassName);
    if (this.options.auxLastElem) Element.addClassName(this.options.auxLastElem, this.options.disabledClassName);
  },
  disableAll: function() {
    this.disableFirst();
    this.disablePrev();
    this.disableNext();
    this.disableLast();
  },
  clearLink: function(evt) {
    if (Event.element(evt).tagName == 'A') Event.element(evt).blur();
  },
  dispose: function() {
    if (this.oldInnerHTML) {
      this.element.innerHTML = this.oldInnerHTML;
    }
    if (this.options.firstElem) {
      Event.stopObserving(this.options.firstElem, 'click', this.onclickFirst);
      if (this.options.firstElem.tagName == 'A') {
        Event.stopObserving(this.options.firstElem, 'mouseup', this.onmouseupListener);
      }
    }
    if (this.options.prevElem) {
      Event.stopObserving(this.options.prevElem, 'click', this.onclickPrev);
      if (this.options.prevElem.tagName == 'A') {
        Event.stopObserving(this.options.prevElem, 'mouseup', this.onmouseupListener);
      }
    }
    if (this.options.nextElem) {
      Event.stopObserving(this.options.nextElem, 'click', this.onclickNext);
      if (this.options.nextElem.tagName == 'A') {
        Event.stopObserving(this.options.nextElem, 'mouseup', this.onmouseupListener);
      }
    }
    if (this.options.lastElem) {
      Event.stopObserving(this.options.lastElem, 'click', this.onclickLast);
      if (this.options.lastElem.tagName == 'A') {
        Event.stopObserving(this.options.lastElem, 'mouseup', this.onmouseupListener);
      }
    }
    if (this.options.auxFirstElem) {
      Event.stopObserving(this.options.auxFirstElem, 'click', this.onclickFirst);
      if (this.options.auxFirstElem.tagName == 'A') {
        Event.stopObserving(this.options.auxFirstElem, 'mouseup', this.onmouseupListener);
      }
    }
    if (this.options.auxFirstElem) {
      Event.stopObserving(this.options.auxFirstElem, 'click', this.onclickPrev);
      if (this.options.auxFirstElem.tagName == 'A') {
        Event.stopObserving(this.options.auxFirstElem, 'mouseup', this.onmouseupListener);
      }
    }
    if (this.options.auxNextElem) {
      Event.stopObserving(this.options.auxNextElem, 'click', this.onclickNext);
      if (this.options.auxNextElem.tagName == 'A') {
        Event.stopObserving(this.options.auxNextElem, 'mouseup', this.onmouseupListener);
      }
    }
    if (this.options.auxLastElem) {
      Event.stopObserving(this.options.auxLastElem, 'click', this.onclickLast);
      if (this.options.auxLastElem.tagName == 'A') {
        Event.stopObserving(this.options.auxLastElem, 'mouseup', this.onmouseupListener);
      }
    }    
  }
});

Mblst.PaginateRelative = Class.create();
Object.extend(Object.extend(Mblst.PaginateRelative.prototype, Mblst.Paginate.prototype), {
  initialize: function(element, urlFunc, initResid, uid, count, options) {
    this.urlFunc = urlFunc
    this.initResid = initResid;
    this.uid = uid;
    this.collection = [];
    this.currIdx = -1;
    this.paginationFetched = false;
    this.collectionFetched = false;
    this.pendingReqRelation = null;

    this.options = Object.extend({
      total: null,
      relativeDataSort: '',
      relativeDataFilter: 'userpublic',
      relativeDataOrdinality: 'upload_date',
      onComplete: function(transport, element) {},
      paginationUrlFunc: null,
      paginationLoaderCallback: function() {return Mblst.paginateRequestSerialize(1, 1, {view: 'none', auxJSON: 'pagination'});},
      loaderCallback: function() {return Mblst.paginateRequestSerialize(null, 4, {view: 'none', auxJSON: 'next_prev_sq'});},
      callback: function() {return Mblst.paginateRequestSerialize(null, 1, {view: 'none', auxJSON: 'next_prev_sq'});},
      
      collectionStartMarker: 'start',
      collectionEndMarker: 'end'
    }, options || {});

    // not relevant in the 'relative' context
    this.options.firstElem = null;
    this.options.lastElem = null;

    // init url builder
    this.urlBuilder = Mblst.UrlBuilder.relativePage( this.urlFunc,
                                                     this.options.relativeDataSort,
                                                     this.options.relativeDataFilter,
                                                     this.options.relativeDataOrdinality );

    // Only setup pagination if we have a url func
    var paginationURL = null;
    if (this.options.total) {
      this.setResults(this.options.total);
      this.page = 1;
      this.updateCountDisplay();
      this.paginationFetched = true;
    } else if (this.options.paginationUrlFunc) {
      paginationURL = Mblst.Url.pageBase( this.options.paginationUrlFunc(uid),
                                          this.uid,
                                          null,
                                          this.options.sort,
                                          this.options.filter );
    } else {
      this.paginationFetched = true;
    }
    
    // Call parent constructor w/ these options
    Mblst.Paginate.prototype.initialize.call(this, element, paginationURL, null, count, this.options);
  },
  /**
   * Control methods
   */  
  load: function(){
    if ( this.paginationFetched && this.collectionFetched ) return;
    
    this.onLoading();
    this.fetchCollection();
    if (this.options.paginationUrlFunc) this.fetchPagination(this.page);
  },
  show: function() {
    Element.show(this.containerElem);
  },
  hide: function() {
    Element.hide(this.containerElem);
  },  
  /**
   * Abstract methods
   */
  updateCountDisplay: function() {},
  updatePrev: function() {},
  updateNext: function() {},
  update: function() {},
  /**
   * Control method helpers
   */ 
  isPrevFirst: function() {
    return (this.collection[this.currIdx - 1] == this.options.collectionStartMarker);
  },
  isLeftFirst: function() {
    return (this.currIdx < 1 || this.collection[this.currIdx] == this.options.collectionStartMarker);
  },  
  isRightLast: function() {
    return (this.currIdx == -1 || this.currIdx >= this.collection.length - 2 || this.collection[this.currIdx+1] == this.options.collectionEndMarker);
  },  
  isNextLast: function() {
    return (this.collection[this.currIdx + 2] == this.options.collectionEndMarker);
  },
  isPrevLocal: function() {
    return (this.currIdx > 1);
  },
  isNextLocal: function() {
    return (this.currIdx < this.collection.length - 3);
  },
  getPrevData: function() {
    if (this.isPrevFirst() || this.isLeftFirst()) {
      return null;
    } else {
      return this.collection[this.currIdx-1];
    }
  },
  getLeftData: function() {
    if (this.isLeftFirst()) {
      return null;
    } else {
      return this.collection[this.currIdx];
    }    
  },
  getRightData: function() {
    if (this.isRightLast()) {
      return null;
    } else {
      return this.collection[this.currIdx+1];
    }    
  },
  getNextData: function() {
    if (this.isNextLast() || this.isRightLast()) {
      return null;
    } else {
      return this.collection[this.currIdx+2];
    }    
  },  
  loadPrev: function(){
    if (this.isLeftFirst()) {
      return false;
    } else if (this.isPrevLocal() || this.isPrevFirst()) {
      this.currIdx--;
      this.updatePrev();
      return false;
    } else {
      this.onSubmit(this.collection[this.currIdx-1].res_id, 'prev');
      return false;
    }
  },
  loadNext: function(){
    if (this.isRightLast()) {
      return false;
    } else if (this.isNextLocal() || this.isNextLast()) {
      this.currIdx++;
      this.updateNext();
      return false;
    } else {
      this.onSubmit(this.collection[this.currIdx + 2].res_id, 'next');
      return false;
    }
  },
  /**
   * AJAX Requests
   */
  fetchPagination: function(page) {
    new Ajax.Request(
      this.url,
      Object.extend({
        asynchronous: true,
        parameters: this.options.paginationLoaderCallback(),
        onFailure: this.onFetchPaginationFailure.bind(this),
        onSuccess: this.onFetchPaginationSuccess.bind(this)
      }, this.options.ajaxOptions)
    );
    return false;
  },
  fetchCollection: function() {
    new Ajax.Request(
      this.urlBuilder(this.uid, this.initResid, "split"),
      Object.extend({
        asynchronous: true,
        parameters: this.options.loaderCallback(null, this.options.auxQueryParams),
        onFailure: this.onFetchCollectionFailure.bind(this),
        onSuccess: this.onFetchCollectionSuccess.bind(this)
      }, this.options.ajaxOptions)
    );
    return false;
  },
  onSubmit: function(resid, relation) {
    new Ajax.Request(
      this.urlBuilder(this.uid, resid, relation),
      Object.extend({
        asynchronous: true,
        parameters: this.options.callback(null, this.options.auxQueryParams),
        onFailure: this.onFailure.bind(this),
        onSuccess: this.onSuccess.bind(this)
      }, this.options.ajaxOptions)
    );
    this.pendingReqRelation = relation;
    return false;
  },    
  /**
   * AJAX Callbacks
   */    
  onFetchPaginationSuccess: function(transport, json) {
    try {
      json = transport.getResponseHeader('X-JSON');
    } catch (e) {}
    try {
      json = eval(json);
    } catch (e) {}
    this.setResults(json[0].total);
    this.page = json[0].page;
    this.paginationFetched = true;
    this.updateCountDisplay();
    this.fetchTaskFinished();
  },
  onFetchPaginationFailure: function(transport) {
    hideLoading();
    var feedbackMsg = (transport.responseText) ? transport.responseText.stripTags() : 'We could not process your request :-( Please try again later.';
    if (!this.options.supressFeedback) this.options.feedbackObj.showError(feedbackMsg, this.options.dismissErrors);
    this.options.onFailure(transport);
    return false;
  },
  onFetchCollectionSuccess: function(transport, json) {
    try {
      json = transport.getResponseHeader('X-JSON');
    } catch (e) {}
    try {
      json = eval(json);
    } catch (e) {}
    
    if (!json[0].before || json[0].before.length == 0) {
      var before = [this.options.collectionStartMarker];
    } else if (json[0].before && json[0].before.length == 1) {
      var before = json[0].before;
      before.unshift(this.options.collectionStartMarker);
    } else {
      var before = json[0].before;
    }
    
    if (!json[0].after || json[0].after.length == 0) {
      var after = [this.options.collectionEndMarker];
    } else if (json[0].after && json[0].after.length == 1) {
      var after = json[0].after;
      after.push(this.options.collectionEndMarker);
    } else {
      var after = json[0].after;
    }
    
    if (before.length == 1 && after.length == 1) {
      this.disableAll();
    } else if (before.length == 1) {
      this.currIdx = 0;
    } else {
      this.currIdx = 1;   
    }
    
    this.collection = before.concat(after);
    this.update();
    
    this.collectionFetched = true;
    this.fetchTaskFinished();    
  },
  onFetchCollectionFailure: function(transport) {
    hideLoading();
    var feedbackMsg = (transport.responseText) ? transport.responseText.stripTags() : 'We could not process your request :-( Please try again later.';
    if (!this.options.supressFeedback) this.options.feedbackObj.showError(feedbackMsg, this.options.dismissErrors);
    this.options.onFailure(transport);
    return false;
  },    
  onSuccess: function(transport, json) {
    try {
      json = transport.getResponseHeader('X-JSON');
    } catch (e) {}
    try {
      json = eval(json);
    } catch (e) {}
    
    var rsrcData = (json && json[0]) ? json[0] : null;
    
    if (this.pendingReqRelation == 'prev') {
      if (!rsrcData) {
        this.collection.unshift(this.options.collectionStartMarker);
        this.currIdx = 1; // prefetched prev, and it's start. left is not start
      } else {
        this.collection.unshift(rsrcData);
      }
      this.updatePrev();
    } else {
      if (!rsrcData) {
        this.collection.push(this.options.collectionEndMarker);
        this.currIdx = this.collection.length - 3; // prefetched next, and it's end. right is not end
      } else {
        this.collection.push(rsrcData);
        this.currIdx++;
      }
      this.updateNext();
    }

    this.options.onComplete.bind(this)(transport);
  },
  onFailure: function(transport) {
    hideLoading();
    var feedbackMsg = (transport.responseText) ? transport.responseText.stripTags() : 'We could not process your request :-( Please try again later.';
    if (!this.options.supressFeedback) this.options.feedbackObj.showError(feedbackMsg, this.options.dismissErrors);

    this.pendingReqRelation = null;
    this.options.onFailure(transport);
    return false;
  },
  /**
   * Event handlers
   */
  fetchTaskFinished: function() {
    if (this.paginationFetched && this.collectionFetched) {
      this.hideLoading();
      this.state = 'showing';
    }
  }
});

Mblst.PrevNext = Class.create();
Object.extend(Object.extend(Mblst.PrevNext.prototype, Mblst.PaginateRelative.prototype), {
  initialize: function(containerElem, photoDisplayElem, urlFunc, initResid, uid, options) {
    this.photoDisplayElem = $(photoDisplayElem);
    this.countDisplayElem = null;
    //this.prevIsPlaceholder = false;
    //this.nextIsPlaceholder = false;
    
    this.options = Object.extend({
      userTagged: false,
      
      prevNextImgClassName: 'prevNextImg',
      prevNextLinkClassName: 'prevNextLink',
      placeholderClassName: 'prevNextPlaceholder',
      
      firstImgSrc: '/images/firstphoto_sq.gif',
      lastImgSrc: '/images/lastphoto_sq.gif',
      emptyImgSrc: '/images/ltgraypix.gif'
    }, options || {});

    if (this.options.countDisplayElem) this.countDisplayElem = $(this.options.countDisplayElem);

    this.create();

    // Call parent constructor w/ these options
    Mblst.PaginateRelative.prototype.initialize.call(this, containerElem, urlFunc, initResid, uid, 1, this.options);
  },
  /**
   * Control methods
   */
  create: function() {    
    this.prevImgLinkElem = this.photoDisplayElem.appendChild(this.createPlaceholderElem());
    this.leftImgLinkElem = this.photoDisplayElem.appendChild(this.createPlaceholderElem());
    this.rightImgLinkElem = this.photoDisplayElem.appendChild(this.createPlaceholderElem());
    this.nextImgLinkElem = this.photoDisplayElem.appendChild(this.createPlaceholderElem());
  },
  createResourceElem: function(data) {
    var resourceImgElem = document.createElement("img");
    resourceImgElem.src = data.url;
    resourceImgElem.alt = "Goto photo page";
    resourceImgElem.className = this.options.prevNextImgClassName;
    resourceImgElem.galleryimg = "no";
    var resourceImgLinkElem = document.createElement("a");
    if (this.options.userTagged) {
      resourceImgLinkElem.href = data.drilldown_url+'&user_id='+this.uid;
    } else {
      resourceImgLinkElem.href = data.drilldown_url;
    }
    resourceImgLinkElem.className = this.options.prevNextLinkClassName;   
    resourceImgLinkElem.appendChild(resourceImgElem);
    Element.hide(resourceImgLinkElem);
    return {link: resourceImgLinkElem, img: resourceImgElem};
  },
  createPlaceholderElem: function() {
    var placeholderElem = document.createElement("img");
    placeholderElem.src = this.options.emptyImgSrc;
    var placeholderLinkElem = document.createElement("a");
    placeholderLinkElem.href = "javascript:void(0)";
    placeholderLinkElem.className = this.options.placeholderClassName;   
    placeholderLinkElem.appendChild(placeholderElem);
    Element.hide(placeholderLinkElem);
    return placeholderLinkElem;
  },    
  createFirstElem: function() {
    var firstImgElem = document.createElement("img");
    firstImgElem.src = this.options.firstImgSrc;
    firstImgElem.alt = "First photo in this set";
    firstImgElem.className = this.options.prevNextImgClassName;
    firstImgElem.galleryimg = "no";
    var firstImgLinkElem = document.createElement("a");
    firstImgLinkElem.href = "javascript:void(0)";
    firstImgLinkElem.className = this.options.prevNextLinkClassName;   
    firstImgLinkElem.appendChild(firstImgElem);
    Element.hide(firstImgLinkElem);
    return {link: firstImgLinkElem, img: firstImgElem};
  },  
  createLastElem: function() {
    var lastImgElem = document.createElement("img");
    lastImgElem.src = this.options.lastImgSrc;
    lastImgElem.alt = "Last photo in this set";
    lastImgElem.className = this.options.prevNextImgClassName;
    lastImgElem.galleryimg = "no";
    var lastImgLinkElem = document.createElement("a");
    lastImgLinkElem.href = "javascript:void(0)";
    lastImgLinkElem.className = this.options.prevNextLinkClassName;   
    lastImgLinkElem.appendChild(lastImgElem);
    Element.hide(lastImgLinkElem);
    return {link: lastImgLinkElem, img: lastImgElem};
  },   
  /**
   * Abstract method implementations
   */
  updateCountDisplay: function() {
    if (this.countDisplayElem) this.countDisplayElem.innerHTML = this.total;
  },
  updatePrev: function() {
    // enable next, we can always go back where we came from
    this.enableNext();
    
    // remove next elem
    Element.remove(this.nextImgLinkElem);    
    
    // update pointers
    this.nextImgLinkElem = this.rightImgLinkElem;
    this.rightImgLinkElem = this.leftImgLinkElem;
    this.leftImgLinkElem = this.prevImgLinkElem;    

    // update prev elem
    if (this.isLeftFirst()) {
      this.disablePrev();
      var placeholder = this.createPlaceholderElem();
      Element.prependChild(this.photoDisplayElem, placeholder);
      this.prevImgLinkElem = placeholder;
    } else if (this.isPrevFirst()) {
      this.enablePrev();
      var first = this.createFirstElem();
      Element.prependChild(this.photoDisplayElem, first.link);
      this.prevImgLinkElem = first.link;    
    } else {
      this.enablePrev();
      var prev = this.createResourceElem(this.getPrevData());
      Element.prependChild(this.photoDisplayElem, prev.link);
      this.prevImgLinkElem = prev.link;
    }
    
    new Effect.BlindRight(this.prevImgLinkElem);
    //new Effect.SlideRightIntoView(this.prevImgLinkElem);
  },
  updateNext: function() {
    // enable next, we can always go back where we came from
    this.enablePrev();
    
    // remove prev elem
    new Effect.SlideRightOutOfView(this.prevImgLinkElem, {afterFinish: function(effect){Element.hide(effect.remove);}});
    //Element.remove(this.prevImgLinkElem);     
    
    // update pointers
    this.prevImgLinkElem = this.leftImgLinkElem;    
    this.leftImgLinkElem = this.rightImgLinkElem;    
    this.rightImgLinkElem = this.nextImgLinkElem;    

    // update next elem
    if (this.isRightLast()) {
      this.disableNext();
      var placeholder = this.createPlaceholderElem();
      this.photoDisplayElem.appendChild(placeholder);
      this.nextImgLinkElem = placeholder;
    } else if (this.isNextLast()) {
      this.enableNext();
      var last = this.createLastElem();
      this.photoDisplayElem.appendChild(last.link);
      this.nextImgLinkElem = last.link;     
    } else {
      this.enableNext();
      var next = this.createResourceElem(this.getNextData());
      this.photoDisplayElem.appendChild(next.link);
      this.nextImgLinkElem = next.link;
    }
    
    new Element.show(this.nextImgLinkElem);
  },
  update: function() {
    // update prev elem, if needed.
    if (this.isLeftFirst()) {
      this.disablePrev();    
    } else if (this.isPrevFirst()) {
      this.enablePrev();
      var first = this.createFirstElem();
      this.photoDisplayElem.replaceChild(first.link, this.prevImgLinkElem);
      this.prevImgLinkElem = first.link;
    } else {
      this.enablePrev();
      var prev = this.createResourceElem(this.getPrevData());
      this.photoDisplayElem.replaceChild(prev.link, this.prevImgLinkElem);
      this.prevImgLinkElem = prev.link;
    }
    
    // update next elem, if needed.
    if (this.isRightLast()) {
      this.disableNext();    
    } else if (this.isNextLast()) {
      this.enableNext();
      var last = this.createLastElem();
      this.photoDisplayElem.replaceChild(last.link, this.nextImgLinkElem);
      this.nextImgLinkElem = last.link;   
    } else {
      this.enableNext();
      var next = this.createResourceElem(this.getNextData());
      this.photoDisplayElem.replaceChild(next.link, this.nextImgLinkElem);
      this.nextImgLinkElem = next.link;
    }
    
    // update left elem
    if (this.isLeftFirst()) {
      var first = this.createFirstElem();
      this.photoDisplayElem.replaceChild(first.link, this.leftImgLinkElem);
      this.leftImgLinkElem = first.link;    
    } else {
      var left = this.createResourceElem(this.getLeftData());
      this.photoDisplayElem.replaceChild(left.link, this.leftImgLinkElem);
      this.leftImgLinkElem = left.link;    
    }
    
    // update right elem
    if (this.isRightLast()) {
      var last = this.createLastElem();
      this.photoDisplayElem.replaceChild(last.link, this.rightImgLinkElem);
      this.rightImgLinkElem = last.link;   
    } else {
      var right = this.createResourceElem(this.getRightData());
      this.photoDisplayElem.replaceChild(right.link, this.rightImgLinkElem);
      this.rightImgLinkElem = right.link;     
    }
    
    Element.show(this.prevImgLinkElem);
    Element.show(this.leftImgLinkElem);
    Element.show(this.rightImgLinkElem);
    Element.show(this.nextImgLinkElem);
  }
});

Mblst.PaginateFilter = Class.create();
Mblst.PaginateFilter.prototype = {
  /**
   * Constructor
   */
  initialize: function(paginateObj, tagURLBuilder, allElem, tagFilterElems, options) {
    this.paginateObj = paginateObj;
    this.tagURLBuilder = tagURLBuilder;
    this.allElem = $(allElem);
    this.tagFilterElems = [];
    this.currentTag = null;

    this.options = Object.extend({
      defaultFilterElem: null,
      selectionExtractionRegEx: /^.*-([0-9a-zA-Z]+)$/,
      currentFilterClass: 'current'
    }, options || {});

    // init filter elems
    if (tagFilterElems) {
      var onclicktaglistener = this.onclickTag.bindAsEventListener(this);
      this.tagFilterElems = tagFilterElems.collect( function(elem){
        Event.observe(elem, 'click', onclicktaglistener);
        return $(elem);
      });
    }
       
    this.onclickAllListener = this.onclickAll.bindAsEventListener(this);
    Event.observe(this.allElem, 'click', this.onclickAllListener);
    
    if (this.options.defaultFilterElem) {
      var tag = $(this.options.defaultFilterElem).id.match(this.options.selectionExtractionRegEx)[1].toLowerCase();
      this.currentTag = tag;
      this.setTagFilter(tag);      
    }
  },

  /**
   * Control methods
   */
  loadTagFilter: function(tag) {
    var url = Mblst.Url.pageBase(this.tagURLBuilder(tag));
    this.paginateObj.loadUrl(url, 1);
  },
  setTagFilter: function(tag) {
    var url = Mblst.Url.pageBase(this.tagURLBuilder(tag));
    this.paginateObj.setUrl(url, 1);
  },
  setUrlBuilder: function(builder, page, count) {
    this.tagURLBuilder = builder;
    var url = Mblst.Url.pageBase(this.tagURLBuilder(this.currentTag));
    this.paginateObj.setUrl(url, page, count);    
  },
  reload: function() {
    this.loadTagFilter(this.currentTag);
  },
  /**
   * DOM Event handlers
   */
  onclickAll: function(evt) {
    this.currentTag = null;
    this.loadTagFilter();
    for (i = 0; i < this.tagFilterElems.length; i++) {
      Element.removeClassName(this.tagFilterElems[i], this.options.currentFilterClass);
    }
    Element.addClassName(this.allElem, this.options.currentFilterClass);
    return false;
  },
  onclickTag: function(evt) {
    var selElem = Event.element(evt);
    var tag = selElem.id.match(this.options.selectionExtractionRegEx)[1].toLowerCase();
    this.currentTag = tag;
    this.loadTagFilter(tag);
    
    Element.removeClassName(this.allElem, this.options.currentFilterClass);
    for (i = 0; i < this.tagFilterElems.length; i++) {
      if (this.tagFilterElems[i].id == selElem.id) {
        Element.addClassName(this.tagFilterElems[i], this.options.currentFilterClass);
      } else {
        Element.removeClassName(this.tagFilterElems[i], this.options.currentFilterClass);
      }
    }       
    return false;
  }  
};


//Base class for positioned controls
Mblst.PositionedControl = Class.create();
Object.extend(Object.extend(Mblst.PositionedControl.prototype, Mblst.Control.prototype), {
  /**
   * Constructor
   */
  initialize: function(triggerElem, options) {
    this.containerElem = null;

    this.options = Object.extend({
      useShowHideEffects: true,
      supressShowIEFix: false,
      triggerElem: triggerElem,
      refOffsetLeft: 0,
      refOffsetTop: 0,
      scrollViewportY: true,
      adjustPositionToViewportY: false,
      useTriggerElemRefY: false,
      positionOffsets: Mblst.Position.topLeftOnBottomLeft,
      onShow: function(referenceElem, containerElem, offsets, useEffects, scrollViewportY, adjustPositionToViewportY, altSourceY){
        if(!containerElem.style.position || containerElem.style.position=='absolute') {
          containerElem.style.position = 'absolute';
          Position.cloned(referenceElem, containerElem, {setHeight: false, setWidth: false, offsetLeft: offsets.offsetLeft, offsetTop: offsets.offsetTop, scrollViewportY: scrollViewportY, adjustPositionToViewportX: true, adjustPositionToViewportY: adjustPositionToViewportY, altSourceY: altSourceY});
          containerElem.style.zIndex = 1000;
        }
        if(useEffects) {
          Effect.Appear(containerElem,{duration:0.15});
        } else {
          Element.show(containerElem);
        }
      },
      onHide: function(referenceElem, containerElem, useEffects, delay){
        if(useEffects) {
          new Effect.Fade(containerElem,{duration:0.15, delay: (delay)? delay : 0.0});
        } else {
          Element.hide(containerElem);
        }
      }
    }, options || {});

    /**
     * Determine where this object will be inserted into the DOM.
     * Default: after triggerElem
     */
    if (!this.options.containerElem && this.options.injectionElem) {
      this.injectionElem = $(this.options.injectionElem);
    } else if (!this.options.containerElem) {
      this.injectionElem = document.body.firstChild;
    }

    /**
     * Determine which element this object should use as its
     * reference for positioning.  Default: triggerElem
     */
    if (this.options.referenceElem) {
      this.options.referenceElem = $(this.options.referenceElem);
    } else {
      this.options.referenceElem = null;
    }

    // Call parent constructor w/ these options
    Mblst.Control.prototype.initialize.call(this, this.options);
  },

  /**
   * Control methods
   */
  insert: function() {
    Element.hide(this.containerElem);
    if (this.injectionElem.parentNode) {
      this.injectionElem.parentNode.insertBefore(this.containerElem, this.injectionElem);
    } else {
      this.injectionElem.appendChild(this.containerElem);
    }
    this.containerElem.style.position = 'absolute';
  },
  show: function() {
    var altSourceY = null;
    if(this.options.useTriggerElemRefY && this.options.triggerElem) {
      altSourceY = this.options.triggerElem;
    }
    var offsets = this.options.positionOffsets.bind(this)(this.options.referenceElem, this.containerElem);
    if(Element.getStyle(this.containerElem, 'display')=='none')
      this.options.onShow(this.options.referenceElem, this.containerElem, offsets, this.options.useShowHideEffects, this.options.scrollViewportY, this.options.adjustPositionToViewportY, altSourceY);
    if(!this.options.supressShowIEFix && !this.iefix &&
      (navigator.appVersion.indexOf('MSIE')>0) &&
      (navigator.userAgent.indexOf('Opera')<0) &&
      (Element.getStyle(this.containerElem, 'position')=='absolute')) {
      new Insertion.After(this.containerElem,
       '<iframe id="' + this.containerElem.id + '_iefix" '+
       'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
       'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
      this.iefix = $(this.containerElem.id+'_iefix');
    }
    if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
  },

  fixIEOverlapping: function() {
    Position.clone(this.containerElem, this.iefix);
    this.iefix.style.zIndex = 999;
    this.containerElem.style.zIndex = 1000;
    Element.show(this.iefix);
  },

  hide: function(noEffect, delay) {
    if(Element.getStyle(this.containerElem, 'display')!='none')
        this.options.onHide(this.options.referenceElem,
                            this.containerElem,
                            this.options.useShowHideEffects && !noEffect,
                            delay);
    if(this.iefix) Element.hide(this.iefix);
  },

  dispose: function() {
    this.dismiss();
  }
});

//Positioned feedback element
Mblst.FeedbackElem = Class.create();
Mblst.FeedbackElem.PrimaryFeedback = {}
Mblst.FeedbackElem.PrimaryFeedback.showOk = function(){}
Mblst.FeedbackElem.PrimaryFeedback.showError = function(){}
Object.extend(Object.extend(Mblst.FeedbackElem.prototype, Mblst.PositionedControl.prototype), {
  /**
   * Constructor
   */
  initialize: function(positionRefElem, options) {

    this.options = Object.extend({
      dismissDelay: 7.0,
      errorDismissDelay: 10.0,
      referenceElem: positionRefElem,
      positionOffsets: Mblst.Position.topCenterOnTopCenter,
      scrollViewportY: false,
      adjustPositionToViewportY: true,

      errorFeedbackClassName: 'errorFeedbackElem',
      okFeedbackClassName: 'okFeedbackElem'
    }, options || {});

    // Call parent constructor w/ these options
    Mblst.PositionedControl.prototype.initialize.call(this, null, this.options);
  },

  /**
   * Control methods
   */
  getID: function() {
    return (this.containerElem) ? this.containerElem.id : null;
  },
  showOk: function(message, autodismiss) {
    if(Element.getStyle(this.containerElem, 'display')!='none') this.hide(true);

    //Update elem
    //Element.setInnerText(this.containerElem, message)
    this.containerElem.innerHTML = message;
    this.containerElem.className = this.options.okFeedbackClassName;

    //defaults autodismiss to true
    var dismiss = (autodismiss == null) ? true : autodismiss;
    this.show();
    if (dismiss) new Effect.Fade(this.containerElem, {duration: 0.15, delay: this.options.dismissDelay});
  },
  showError: function(message, autodismiss) {
    if(Element.getStyle(this.containerElem, 'display')!='none') this.hide(true);

    //Update elem
    //Element.setInnerText(this.containerElem, (message) ? message : "Your request could not be completed at this time.  Please try again later.");
    this.containerElem.innerHTML = (message) ? message : "Your request could not be completed at this time.  Please try again later.";
    this.containerElem.className = this.options.errorFeedbackClassName;

    //defaults autodismiss to false
    var dismiss = (autodismiss == null) ? false : autodismiss;
    this.show();
    if (dismiss) new Effect.Fade(this.containerElem, {duration: 0.15, delay: this.options.errorDismissDelay});
  }
});


// Simple trigger for indicator element
Mblst.IndicatorTrigger = Class.create();
Mblst.IndicatorTrigger.prototype = {
  /**
   * Constructor
   */
  initialize: function(indicatorObj, referenceElem, options) {
    this.indicatorObj = indicatorObj;

    this.options = Object.extend({
      useShowHideEffects: false,
      position: true,
      referenceElem: $(referenceElem)
    }, options || {});
  },
  /**
   * Control Methods
   */
  show: function(message) {
    if (message) this.options.triggerSetText = message;
    this.indicatorObj.trigger(this.options);
    return false;
  },
  hide: function() {
    this.indicatorObj.dismiss();
  }
};

// Positioned indicator elem
Mblst.IndicatorElem = Class.create();
Object.extend(Object.extend(Mblst.IndicatorElem.prototype, Mblst.PositionedControl.prototype), {
  /**
   * Constructor
   */
  initialize: function(referenceElem, options) {

    this.options = Object.extend({
      space2nbsp: true,
      useShowHideEffects: false,
      position: false,
      referenceElem: referenceElem,
      injectionElem: referenceElem,
      containerClassName: 'defaultIndicatorElem',
      triggerSetText: '', //don't change this from an empty string default
      positionOffsets: Mblst.Position.topLeftOnTopLeft
    }, options || {});

    // Call parent constructor w/ these options
    Mblst.PositionedControl.prototype.initialize.call(this, null, this.options);

    /**
     * Build indicator elem, if one has not been provided
     */
    if (this.options.containerElem && this.options.indicatorTextElem) {
        this.indicatorTextElem = $(this.options.indicatorTextElem);
    } else {
        this.indicatorTextElem = this.containerElem;
    }
  },

  /**
   * Control methods
   */
  getID: function() {
    return (this.containerElem) ? this.containerElem.id : null;
  },
  show: function(message) {
    if(Element.getStyle(this.containerElem, 'display')!='none') this.hide();

    //Update elem
    message = (message && message !== true) ? message : this.options.triggerSetText;
    if (this.options.space2nbsp) message = message.replace(/\s/gi, '&nbsp;');

    //Element.setInnerText(this.indicatorTextElem, message);
    this.indicatorTextElem.innerHTML = message;

    if ( this.options.position ) {
      Mblst.PositionedControl.prototype.show.call(this);
    } else {
      Element.show(this.containerElem);
    }
  }
});


// This object triggers our DHTML ToolTip
Mblst.ToolTipTrigger = Class.create();
Object.extend(Object.extend(Mblst.ToolTipTrigger.prototype, Mblst.Trigger.prototype), {
  /**
   * Constructor
   */
  initialize: function(toolTipObj, triggerElem, options) {
    this.toolTipObj = toolTipObj;
    this.triggerElem = $(triggerElem);

    this.options = Object.extend({
      refOffsetLeft: 0,
      refOffsetTop: 0,
      scrollViewportY: true,
      positionOffsets: Mblst.Position.topLeftOnBottomLeft,
      elementBody: false,
      headingText: 'tip!',
      bodyText: ''
    }, options || {});

    Mblst.Trigger.prototype.initialize.call(this, toolTipObj, triggerElem, this.options);
  }
});
Mblst.HoverToolTipTrigger = Class.create();
Object.extend(Object.extend(Mblst.HoverToolTipTrigger.prototype, Mblst.HoverTrigger.prototype), {
  /**
   * Constructor
   */
  initialize: function(toolTipObj, triggerElem, options) {
    this.toolTipObj = toolTipObj;
    this.triggerElem = $(triggerElem);

    this.options = Object.extend({
      scrollViewportY: false,
      elementBody: false,
      headingText: 'tip!',
      bodyText: ''
    }, options || {});

    Mblst.HoverTrigger.prototype.initialize.call(this, toolTipObj, triggerElem, this.options);
  }
});

// Our DHTML Tooltip
Mblst.ToolTip = Class.create();
Object.extend(Object.extend(Mblst.ToolTip.prototype, Mblst.PositionedControl.prototype), {
  /**
   * Constructor
   */
  initialize: function(triggerElem, options) {

    this.options = Object.extend({
      containerClassName: 'toolTipContainer',
      extraMainClassName: 'extraMain',
      mainClassName: 'main',
      headerClassName: 'header',
      bodyClassName: 'body',
      extraBottomClassName: 'extraBottom',
      extraBottomBodyClassName: 'inside',

      headingText: 'tip!',
      bodyText: '',

      afterShow: function(control) {
        document.onmousedown = control.onmousedownListener;
        //Event.observe(document, 'mousedown', this.onmousedownListener);
      },
      beforeHide: function(control) {
        document.onmousedown = function(){};
      }
    }, options || {});

    // Call parent constructor w/ these options
    Mblst.PositionedControl.prototype.initialize.call(this, triggerElem, this.options);

    this.onmousedownListener = this.mousedownFired.bindAsEventListener(this);
  },

  /**
   * Control methods
   */
  create: function() {
    var headerText = document.createTextNode(this.options.headingText);

    this.tipExtraMainDiv = document.createElement("div");
    this.tipExtraMainDiv.className = this.options.extraMainClassName;

    this.tipHeader = document.createElement("div");
    this.tipHeader.className = this.options.headerClassName;

    this.tipBodyDiv = document.createElement("div");
    this.tipBodyDiv.className = this.options.bodyClassName;

    this.tipMainDiv = document.createElement("div");
    this.tipMainDiv.className = this.options.mainClassName;

    this.tipExtraBottomDiv = document.createElement("div");
    this.tipExtraBottomDiv.className = this.options.extraBottomClassName;

    this.tipExtraBottomBodyDiv = document.createElement("div");
    this.tipExtraBottomBodyDiv.className = this.options.extraBottomBodyClassName;

    this.tipHeader.appendChild(headerText);
    this.tipMainDiv.appendChild(this.tipExtraMainDiv);
    this.tipMainDiv.appendChild(this.tipHeader);
    this.tipMainDiv.appendChild(this.tipBodyDiv);
    this.tipExtraBottomDiv.appendChild(this.tipExtraBottomBodyDiv);
    this.containerElem.appendChild(this.tipMainDiv);
    this.containerElem.appendChild(this.tipExtraBottomDiv);
  },
  load: function() {
    if(this.options.containerElem) return;
    this.tipHeader.innerHTML = this.options.headingText;
    if(this.options.extContentContainer) {
      this.contentContainer = this.options.extContentContainer;
      Element.moveChildren(this.contentContainer, this.tipBodyDiv);
    } else {
      Element.removeChildren(this.tipBodyDiv);
      this.tipBodyDiv.innerHTML = this.options.bodyText;
    }
  },
  reset: function(noEffect) {
    if(this.options.containerElem) return;
    if(this.contentContainer) {
      Element.moveChildren(this.tipBodyDiv, this.contentContainer);
      this.contentContainer = null;
    } else {
      Element.removeChildren(this.tipBodyDiv);
    }
  },
  mousedownFired: function(evt) {
  	if (Event.element(evt) == $(this.options.triggerElem)){
	  document.onmousedown = function(){};
	} else if(Event.element(evt).tagName == 'A'){
      return;
  } else {
		this.dismiss();
	}
  }
});

//Positioned dialog
Mblst.PopupDialog = Class.create();
Object.extend(Object.extend(Mblst.PopupDialog.prototype, Mblst.PositionedControl.prototype), {
  /**
   * Constructor
   */
  initialize: function(options) {

    this.options = Object.extend({
      referenceElem: document.body,
      useShowHideEffects: false,
      underlayClassName: "dialog",
      refOffsetLeft: 0,
      refOffsetTop: 150,
      positionOffsets: Mblst.Position.topCenterOnTopCenter
    }, options || {});

    // Call parent constructor w/ these options
    Mblst.PositionedControl.prototype.initialize.call(this, null, this.options);

    this.createUnderlay();
  },

  /**
   * Control methods
   */
  createUnderlay: function() {
    this.underlayDiv = document.createElement("div");
    this.underlayDiv.className = this.options.underlayClassName;

    if (this.containerElem.parentNode) {
      this.containerElem.parentNode.insertBefore(this.underlayDiv, this.containerElem);
    } else {
      this.containerElem.appendChild(this.underlayDiv);
    }
    Element.hide(this.underlayDiv);
  },
  show: function() {
    Position.cloned(document.body, this.underlayDiv, {viewportPaddingX: 0, viewportPaddingY: 0});
    Element.setOpacity(this.underlayDiv, .7);
    Element.show(this.underlayDiv);
    Mblst.PositionedControl.prototype.show.call(this);
  },
  hide: function() {
    Element.hide(this.underlayDiv);
    Mblst.PositionedControl.prototype.hide.call(this, true);
  }
});


//Positioned dialog
Mblst.Alert = Class.create();
Object.extend(Object.extend(Mblst.Alert.prototype, Mblst.PopupDialog.prototype), {
  /**
   * Constructor
   */
  initialize: function(options) {
    this.lastAlertedInput = null;

    this.options = Object.extend({
      headingText: 'Alert!',
      okText: 'OK',
      
      containerClassName: 'dialogPopup',
      controlHeaderClassName: 'alertHeader',
      controlBodyClassName: 'alertBody',
      controlFooterClassName: 'alertFooter',
      
      invalidInputClassName: 'invalidInput',
      invalidCheckRadioClassName: 'invalidCheckRadioInput'
      
    }, options || {});

    // Call parent constructor w/ these options
    Mblst.PopupDialog.prototype.initialize.call(this, this.options);
  },

  /**
   * Control methods
   */
  create: function() {
    var headerText = document.createTextNode(this.options.headingText);
    this.controlHeader = document.createElement("div");
    this.controlHeader.className = this.options.controlHeaderClassName;
    this.controlHeader.appendChild(headerText);

    this.controlBodyDiv = document.createElement("div");
    this.controlBodyDiv.className = this.options.controlBodyClassName;

    this.controlFooter = document.createElement("div");
    this.controlFooter.className = this.options.controlFooterClassName;

    okButton = document.createElement("input");
    okButton.type = "submit";
    okButton.value = this.options.okText;
    this.controlFooter.appendChild(okButton);

    this.containerElem.appendChild(this.controlHeader);
    this.containerElem.appendChild(this.controlBodyDiv);
    this.containerElem.appendChild(this.controlFooter);
    
    this.clickokListener = this.onclickOk.bindAsEventListener(this);
    Event.observe(okButton, 'click', this.clickokListener);     
  },
  show: function(message, invalidInput) {
    if(this.lastAlertedInput) {
      this.removeErrorClass(this.lastAlertedInput);
      this.lastAlertedInput = null;
    }

    this.controlBodyDiv.innerHTML = message;

    this.addErrorClass(invalidInput);
    this.lastAlertedInput = invalidInput;
    
    Mblst.PopupDialog.prototype.show.call(this);
  },
  addErrorClass: function(invalidInput) {
    if (!invalidInput) return;
    var input = $(invalidInput);
    if ( input.type == "radio" || input.type == "checkbox" ) {
      Element.addClassName(input, this.options.invalidCheckRadioClassName);
    } else {
      Element.addClassName(input, this.options.invalidInputClassName);
    }
  },
  removeErrorClass: function(invalidInput) {
    if (!invalidInput) return;
    var input = $(invalidInput);
    if ( input.type == "radio" || input.type == "checkbox" ) {
      Element.removeClassName(input, this.options.invalidCheckRadioClassName);
    } else {
      Element.removeClassName(input, this.options.invalidInputClassName);
    }    
  },
  /**
   * DOM Event handlers
   */
  onclickOk: function() {
    this.hide();
    return false;
  }  
});


//In-place color picker
Mblst.ColorInput = Class.create();
Object.extend(Object.extend(Mblst.ColorInput.prototype, Mblst.Trigger.prototype), {
  /**
   * Constructor
   */
  initialize: function(colorPickerObj, hexInputElem, colorDisplayElem, options) {
    this.colorPickerObj = colorPickerObj;
    this.hexInputElem = $(hexInputElem);
    this.colorDisplayElem = $(colorDisplayElem);
    this.lastValue = this.hexInputElem.value;

    this.options = Object.extend({
      referenceElem: $(this.colorDisplayElem.id),
      triggerElem: this.colorDisplayElem.id, //this one is only passed to the control, not used as trigger
      colorSelected: this.colorSelected.bind(this),
      onUpdate: function(color){}
    }, options || {});

    Mblst.Trigger.prototype.initialize.call(this, colorPickerObj, null, this.options);

    this.onchangeListener = this.hexValueChanged.bindAsEventListener(this);
    Event.observe(this.hexInputElem, 'change', this.onchangeListener);

  },

  /**
   * Control methods
   */
  colorSelected: function(color) {
    this.hexInputElem.value = color;
    this.colorDisplayElem.style.backgroundColor = ('#'+color).parseColor();
    this.lastValue = color;
    this.options.onUpdate(('#'+color).parseColor());
  },
  updateColorDisplay: function() {
    if(valHexColor(this.hexInputElem.value)) {
      this.colorDisplayElem.style.backgroundColor = ('#'+this.hexInputElem.value).parseColor();
      this.options.onUpdate(('#'+this.hexInputElem.value).parseColor());
    } else {
      this.hexInputElem.value = this.lastValue;
      this.options.onUpdate(('#'+this.hexInputElem.value).parseColor());
    }
  },
  hexValueChanged: function() {
    this.updateColorDisplay();
    this.lastValue = this.hexInputElem.value;
  }
});


//In-place color picker
Mblst.ColorPicker = Class.create();
Object.extend(Object.extend(Mblst.ColorPicker.prototype, Mblst.PositionedControl.prototype), {
  /**
   * Constructor
   */
  initialize: function(triggerElem, paletteElem, options) {

    this.options = Object.extend({
      useShowHideEffects: false,
      containerElem: paletteElem,
      extendScrollRangePastPageBottom: false,
      
      afterShow: function(control) {
        document.onmousedown = control.onmousedownListener;
      },
      beforeHide: function(control) {
        document.onmousedown = function(){};
      }      
    }, options || {});

    // Call parent constructor w/ these options
    Mblst.PositionedControl.prototype.initialize.call(this, triggerElem, this.options);

    if (this.options.displayPreviewTextElem) {
      this.displayPreviewTextElem = $(this.options.displayPreviewTextElem);
    }

    if (this.options.displayPreviewColorElem) {
      this.displayPreviewColorElem = $(this.options.displayPreviewColorElem);
    }
    
    this.onmousedownListener = this.mousedownFired.bindAsEventListener(this);
  },

  /**
   * Control methods
   */
  previewColor: function(color) {
    if (this.displayPreviewTextElem) {
      this.displayPreviewTextElem.innerHTML = '#'+color;
    }
    if (this.displayPreviewColorElem) {
      this.displayPreviewColorElem.style.backgroundColor = ('#'+color).parseColor();
    }
  },
  setColor: function(color) {
    if (!this.options.colorSelected) return;
    this.options.colorSelected(color);
    this.dismiss();
  },
  cancel: function() {
    return this.dismiss();
  },
  mousedownFired: function(evt){
    if (Element.descendantOf(Event.element(evt), this.containerElem)) {
      return true;
    } else {
      this.dismiss();
    }
  }
});


// This object triggers our AJAX controls
Mblst.AjaxControlTrigger = Class.create();
Object.extend(Object.extend(Mblst.AjaxControlTrigger.prototype, Mblst.Trigger.prototype), {
  /**
   * Constructor
   */
  initialize: function(ajaxControlObj, triggerElem, options) {
    this.ajaxControlObj = ajaxControlObj;
    this.triggerElem = $(triggerElem);

    this.options = Object.extend({
      highlightcolor: Mblst.defaultHighlightColor,
      highlighterrorcolor: Mblst.defaultErrorHighlightColor,
      highlightendcolor: "#FFFFFF",
      afterProcessInternal: this.highlightElementSuccess.bind(this),
      failureInternal: this.highlightElementFailure.bind(this)
    }, options || {});

    Mblst.Trigger.prototype.initialize.call(this, ajaxControlObj, triggerElem, this.options);

    /**
     * If an onCompleteHighlightElem object is specified in options,
     * then that element will see a highlight effect upon completion
     * of this controls action.
     */
    if (this.options.onCompleteHighlightElem) {
      this.options.onCompleteHighlightElem = $(this.options.onCompleteHighlightElem);
      this.onCompHighElemOrigBackground = Element.getStyle(this.options.onCompleteHighlightElem, 'background-color');
    } else {
      this.options.onCompleteHighlightElem = null;
      this.onCompHighElemOrigBackground = "transparent";
    }
  },

  /**
   * Utility functions
   */
  highlightElementSuccess: function() {
    if (this.options.onCompleteHighlightElem)
    {
      if (this.onCompleteEffect) {
        this.onCompleteEffect.cancel();
      }
      this.onCompleteEffect = new Effect.Highlight(this.options.onCompleteHighlightElem, {
        startcolor: this.options.highlightcolor,
        endcolor: '',
        restorecolor: this.onCompHighElemOrigBackground
        });
    }
  },
  highlightElementFailure: function() {
    if (this.options.onCompleteHighlightElem)
    {
      if (this.onCompleteEffect) {
        this.onCompleteEffect.cancel();
      }
      this.onCompleteEffect = new Effect.Highlight(this.options.onCompleteHighlightElem, {
        startcolor: this.options.highlighterrorcolor,
        endcolor: '',
        restorecolor: this.onCompHighElemOrigBackground
        });
    }
  }
});

//In-place button form
Mblst.AjaxControl = {}
Mblst.AjaxControl.Base = Class.create();
Mblst.defaultHighlightColor = "#FFFF99";
Mblst.defaultErrorHighlightColor = "#FF6666";
Object.extend(Object.extend(Mblst.AjaxControl.Base.prototype, Mblst.PositionedControl.prototype), {
  /**
   * Constructor
   */
  initialize: function(triggerElem, url, options) {
    this.triggerElem = $(triggerElem);

    this.options = Object.extend({
      processURL: url,

      position: true,
      supressFeedback: false,
      dismissErrors: true,
      displayBlurb: false,

      okText: "ok",
      cancelText: "cancel",
      headingText: "",
      blurbText: "",
      loadingText: "loading...",
      processingText: "processing...",

      containerClassName: 'ajaxControlContainer',
      controlBorderClassName: 'emphLightBorder',
      controlHeaderClassName: 'emphLightHeader',
      controlBodyClassName: 'inside',
      controlBodyBlurbClassName: 'blurb',
      formClassName: 'ajaxControlForm',
      formElemsClass: 'ajaxControlFormElems',
      footerClass: 'ajaxControlFormFooter',
      okButtonClass: 'button',
      cancelClassName: 'cancelLink',

      afterCreateInternal: function(control) {
        control.createFooter();
      },

      onSuccess: function(transport, triggerElem) {},
      onFetchSuccess: function(transport, triggerElem) {},
      onFailure: function(transport) {},
      callback: Mblst.requestSerialize.bind(this),
      loaderCallback: Mblst.loadSerialize.bind(this),
      mblstLoadOptions: {},
      loaderAjaxOptions: {},
      mblstReqOptions: {},
      ajaxOptions: {}
    }, options || {});

    // Call parent constructor w/ these options
    Mblst.PositionedControl.prototype.initialize.call(this, triggerElem, this.options);

    if (!this.options.feedbackObj) {
      this.options.feedbackObj = new Mblst.FeedbackElem(this.containerElem.id, {});
    }

    if (!this.options.displayBlurb && this.controlBodyBlurbDiv) {
      Element.hide(this.controlBodyBlurbDiv);
    } else if (this.options.displayBlurb && this.controlBodyBlurbDiv && this.options.blurbText) {
      this.controlBodyBlurbDiv.innerHTML = this.options.blurbText;
    }

  },

  /**
   * Control methods
   */
  create: function() {
    var headerText = document.createTextNode(this.options.headingText);
    this.controlHeader = document.createElement("div");
    this.controlHeader.className = this.options.controlHeaderClassName;
    this.controlHeader.appendChild(headerText);

    this.controlBodyBorderDiv = document.createElement("div");
    this.controlBodyBorderDiv.className = this.options.controlBorderClassName;

    this.controlBodyDiv = document.createElement("div");
    this.controlBodyDiv.className = this.options.controlBodyClassName;
    this.controlBodyBorderDiv.appendChild(this.controlBodyDiv);

    this.controlBodyBlurbDiv = document.createElement("div");
    this.controlBodyBlurbDiv.className = this.options.controlBodyBlurbClassName;
    this.controlBodyDiv.appendChild(this.controlBodyBlurbDiv);

    this.form = document.createElement("form");
    if(!this.options.formId) {
      this.options.formId = this.options.containerElemId + "-form";
      if ($(this.options.formId)) {
        // there's already a form with that name, don't specify an id
        this.options.formId = null;
      }
    }
    this.form.id = this.options.formId;
    this.form.className = this.options.formClassName;
    this.form.onsubmit = function(){return false};
    this.controlBodyDiv.appendChild(this.form);

    this.formElemsDiv = document.createElement("div");
    this.formElemsDiv.className = this.options.formElemsClass;
    this.form.appendChild(this.formElemsDiv);

    this.containerElem.appendChild(this.controlHeader);
    this.containerElem.appendChild(this.controlBodyBorderDiv);

    this.submitformListener = this.onclickSubmit.bindAsEventListener(this);
    Event.observe(this.form, 'submit', this.submitformListener);
  },
  createFooter: function() {
    this.formFooter = document.createElement("div");
    this.formFooter.className = this.options.footerClass;
    if(!this.options.formFooterId) this.options.formFooterId = document.validateId(this.options.containerElemId + "-formfooter");
    this.formFooter.id = this.options.formFooterId;

    if (!this.options.indicatorObj) {
      this.options.indicatorObj = new Mblst.IndicatorElem(this.formFooter, {injectionElem: this.formFooter, position: true, refOffsetTop: 7, refOffsetLeft: 5});
    }

    okButton = document.createElement("input");
    okButton.type = "submit";
    okButton.value = this.options.okText;
    okButton.className = this.options.okButtonClass;
    this.formFooter.appendChild(okButton);

    cancelLink = document.createElement("a");
    cancelLink.href = "javascript: void(0);";
    cancelLink.className = this.options.cancelClassName;
    cancelLink.appendChild(document.createTextNode(this.options.cancelText));
    this.formFooter.appendChild(cancelLink);

    this.form.appendChild(this.formFooter);
    
    this.clickcancelListener = this.onclickCancel.bindAsEventListener(this);
    Event.observe(cancelLink, 'click', this.clickcancelListener);      
  },
  load: function() {
    if (this.controlHeader) this.controlHeader.innerHTML = this.options.headingText;
    if (this.options.loadFormElemsURL) this.fetch();
  },
  fetch: function() {
    this.showLoading();
    this.event('beforeFetch');
    this.request();
    this.state = 'loading';
    //this.event('afterFetch'); //don't call after request until we get the callback
  },
  request: function() {


    new Ajax.Request(
      this.options.loadFormElemsURL,
      Object.extend({
        asynchronous: true,
        parameters: this.options.loaderCallback(),
        onFailure: this.onFetchFailure.bind(this),
        onSuccess: this.onFetchSuccess.bind(this)
      }, this.options.loaderAjaxOptions)
    );

      /**
    new Ajax.Updater(
      {
        success: this.formElemsDiv,
         // don't update on failure (this could be an option)
        failure: null
      },
      this.options.loadFormElemsURL,
      Object.extend({
        asynchronous: true,
        parameters: this.options.loaderCallback(),
        onSuccess: this.onFetchSuccess.bind(this),
        onFailure: this.onFetchFailure.bind(this)
      }, this.options.loaderAjaxOptions)
    );
       */
  },
  submit: function() {
    this.showProcessing();
    this.event('beforeProcess');
    this.process();
    this.state = 'processing';
    return false;
  },
  process: function() {
    new Ajax.Request(
      this.options.processURL,
      Object.extend({
        asynchronous: true,
        parameters: this.options.callback(this.form, this.getInputValue()),
        onSuccess: this.onSuccess.bind(this),
        onFailure: this.onFailure.bind(this)
      }, this.options.ajaxOptions)
    );
  },
  cancel: function() {
    this.event('beforeDismiss');
    this.dismiss();
    this.event('afterDismiss');
    return false;
  },

  /**
   * AJAX Callbacks
   */
  onFetchSuccess: function(transport) {
    this.hideLoading();

    ////If the response is sent as XML, use DOM methods
    //if ((this.header('Content-type', transport) || '').match(/^application\/xml/i)) {
    //  //alert(transport.responseText);
    //  this.formElemsDiv.innerHTML = transport.responseText;
    //} else {
    Element.update(this.formElemsDiv, transport.responseText);
    //}

    //new Insertion.Top(this.formElemsDiv, transport.responseText);
    this.state = 'showing';
    this.event('afterFetch');

    //Position.scrollIntoView(this.formContainer);
    this.containerElem.scrollIntoView(false);
    this.options.onFetchSuccess(transport);
  },
  onFetchFailure: function(transport) {
    this.hideLoading();
    this.dismiss();
    this.event('failure');

    var feedbackMsg = (transport.responseText) ? transport.responseText.stripTags() : 'We could not process your request. Please try again later.';
    if (!this.options.supressFeedback) this.options.feedbackObj.showError(feedbackMsg, this.options.dismissErrors);

    this.options.onFailure(transport);
    return false;
  },
  onSuccess: function(transport) {
    this.hideProcessing();
    this.dismiss();
    this.event('afterProcess');

    var feedbackMsg = (transport.responseText) ? transport.responseText : 'Success!';
    if (!this.options.supressFeedback) this.options.feedbackObj.showOk(feedbackMsg);

    this.options.onSuccess.bind(this)(transport, this.triggerElem);
  },
  onFailure: function(transport) {
    this.hideProcessing();
    this.dismiss();
    this.event('failure');

    var feedbackMsg = (transport.responseText) ? transport.responseText.stripTags() : 'We could not process your request. Please try again later.';
    if (!this.options.supressFeedback) this.options.feedbackObj.showError(feedbackMsg, this.options.dismissErrors);

    this.options.onFailure(transport);
    return false;
  },

  /**
   * DOM Event handlers
   */
  onclickSubmit: function() {
    this.event('beforeSubmit');
    this.submit();
    this.event('afterSubmit');
    return false;
  },
  onclickCancel: function() {
    return this.cancel();
  },

  /**
   * Utility functions
   */
  showLoading: function() {
    if (this.formElemsDiv) {
      Element.setInnerText(this.formElemsDiv, this.options.loadingText);
    } else {
      if (this.options.indicatorObj) this.options.indicatorObj.show(this.options.loadingText);
    }
  },
  hideLoading: function() {
    if (this.formElemsDiv) {
      Element.setInnerText(this.formElemsDiv, '');
    } else {
      if (this.options.indicatorObj) this.options.indicatorObj.hide();
    }
  },
  showProcessing: function() {
    if (this.form) Form.disable(this.form);
    Element.addClassName(this.form, this.options.processingClassName);
    if (this.options.indicatorObj) this.options.indicatorObj.show(this.options.processingText);
  },
  hideProcessing: function() {
    if (this.options.indicatorObj)
    {
      this.options.indicatorObj.hide();
    }
    Element.removeClassName(this.form, this.options.processingClassName);
    if (this.form) Form.enable(this.form);
  },
  removeForm: function() {
    if(this.form) {
      Element.remove(this.form);
      this.form = null;
    }
  },
  getInputValue: function () {
    return null;
  },
  header: function(name, transport) {
    try {
      return transport.getResponseHeader(name);
    } catch (e) {}
  }
});



Mblst.AjaxControl.SelectList = Class.create();
Object.extend(Object.extend(Mblst.AjaxControl.SelectList.prototype, Mblst.AjaxControl.Base.prototype), {
  initialize: function(triggerElem, url, options) {
    this.clickedElemExternalId = 0;

    this.options = Object.extend({
      headingText: "Select item below",
      handleElemClicks: true,
      displayBlurb: true,

      selectedIdExtractionRegEx: /^.*-([0-9]+)$/,

      afterFetchInternal: function(control) {
        control.registerSelectItems();
      },

      selectListElemClass: 'selectListElem',
      confirmationTextClass: 'confirmationText',
      selectHoverClassName: 'selectElemHover',
      selectImgElemClassSuffix: '-img'

    }, options || {});

    // Call parent constructor w/ these options
    Mblst.AjaxControl.Base.prototype.initialize.call(this, triggerElem, url, this.options);
  },
  /**
   * Control methods
   */
  load: function() {
    this.controlHeader.innerHTML = this.options.headingText;
    if (this.options.blurbText) this.controlBodyBlurbDiv.innerHTML = this.options.blurbText;
    if (this.options.loadFormElemsURL) {
      this.fetch();
    } else if (this.options.formElems) {
      this.formElemsDiv.appendChild($(this.options.formElems));
      this.registerSelectItems();
    } else {
      var textPara = document.createElement("p");
      Element.setInnerText(textPara, 'no data loaded');
      this.formElemsDiv.appendChild(textPara);
    }
  },
  reset: function () {
    this.clickedElemExternalId = 0;
  },
  /**
   * Event handlers
   */
  onclickSelectElem: function(evt) {
    var selElem = Event.element(evt);
    var i=0;
    while ( i < 3 && !Element.hasClassName(selElem, this.options.selectListElemClass)) {
      selElem = selElem.parentNode; i++;
    }
    this.clickedElemExternalId = selElem.id.match(this.options.selectedIdExtractionRegEx)[1];
    this.submit();
    this.dismiss();
    return false;
  },
  onEnterSelectElemHover: function(evt) {
    var selElem = Event.element(evt);
    var i=0;
    while ( i < 3 && !Element.hasClassName(selElem, this.options.selectListElemClass)) {
      selElem = selElem.parentNode; i++;
    }
    var selElemImg = $(selElem.id+this.options.selectImgElemClassSuffix);
    if (selElemImg) {
      selElemImg.src = selElemImg.src.replace(/_off(\.[^.]+)$/, '_on$1');
    }
    Element.addClassName(selElem, this.options.selectHoverClassName)
  },
  onLeaveSelectElemHover: function(evt) {
    var selElem = Event.element(evt);
    var i=0;
    while ( i < 3 && !Element.hasClassName(selElem, this.options.selectListElemClass)) {
      selElem = selElem.parentNode; i++;
    }
    var selElemImg = $(selElem.id+this.options.selectImgElemClassSuffix);
    if (selElemImg) {
      selElemImg.src = selElemImg.src.replace(/_on(\.[^.]+)$/, '_off$1');
    }
    Element.removeClassName(selElem, this.options.selectHoverClassName);
  },

  /**
   * Utility methods
   */
  createFooter: function() {
    var footerPara = document.createElement("div");
    footerPara.className = this.options.footerClass;
    if (!this.options.indicatorObj) {
      this.options.indicatorObj = new Mblst.IndicatorElem(footerPara, {position: true, refOffsetTop: 7, refOffsetLeft: 5});
    }
    cancelLink = document.createElement("a");
    cancelLink.href = "javascript: void(0)";
    cancelLink.className = this.options.cancelClassName;
    cancelLink.appendChild(document.createTextNode(this.options.cancelText));
    footerPara.appendChild(cancelLink);

    this.form.appendChild(footerPara);
    
    this.clickcancelListener = this.onclickCancel.bindAsEventListener(this);
    Event.observe(cancelLink, 'click', this.clickcancelListener);      
  },
  registerSelectItems: function() {
    //Add event listeners
    this.selItems = document.getElementsByClassName(this.options.selectListElemClass);
    if (this.options.handleElemClicks) this.onclickSelectItemListener = this.onclickSelectElem.bindAsEventListener(this);
    this.mouseoverSelectItemListener = this.onEnterSelectElemHover.bindAsEventListener(this);
    this.mouseoutSelectItemListener = this.onLeaveSelectElemHover.bindAsEventListener(this);
    for (var i=0; i < this.selItems.length ; i++) {
      Element.forceHandCursor(this.selItems[i]);
      if (this.options.handleElemClicks) Event.observe(this.selItems[i], 'click', this.onclickSelectItemListener);
      Event.observe(this.selItems[i], 'mouseover', this.mouseoverSelectItemListener);
      Event.observe(this.selItems[i], 'mouseout', this.mouseoutSelectItemListener);
    }
  },
  getInputValue: function () {
    return this.clickedElemExternalId;
  }
});



Mblst.AjaxControl.Textfield = Class.create();
Object.extend(Object.extend(Mblst.AjaxControl.Textfield.prototype, Mblst.AjaxControl.Base.prototype), {
  initialize: function(triggerElem, url, options) {

    this.options = Object.extend({
      rows: 1,
      displayCharCount: true,
      maxchars: 130,

      textBoxClass: 'ajaxControlTextbox',
      textAreaClass: 'ajaxControlTextarea',

      afterCreateInternal: function(control) {
        control.createBody();
        control.createFooter();
      },
      afterSubmitInternal: function(control) {
        if (control.charCountElem) Element.hide(control.charCountElem);
      },
      beforeShowInternal: function(control) {
        if (control.charCountElem) Element.show(control.charCountElem);
      },

      maxcharhighlightendcolor: "#FFFFFF"
    }, options || {});

    // Call parent constructor w/ these options
    Mblst.AjaxControl.Base.prototype.initialize.call(this, triggerElem, url, this.options);


    if(!this.options.editFieldId) {
      this.options.editFieldId =  this.options.containerElemId + "-editfield";
      if ($(this.options.editFieldId)) {
        // there's already an elem with that id, don't specify an id
        this.options.editFieldId = null;
      }
    }
  },
  /**
   * Control methods
   */
  load: function() {
    this.controlHeader.innerHTML = this.options.headingText;
  },
  submit: function() {
    if(this.getInputValue() == '' || this.getInputValue().replace(/^\s+/gmi,'').replace(/\s+$/gmi,'') == '') {
      this.editField.value = "";
      this.updateFieldCount();
      return false;
    }
    this.showProcessing();
    this.event('beforeProcess');
    this.process();
    this.state = 'processing';
    return false;
  },      
  reset: function () {
    this.editField.value = "";
    this.updateFieldCount();
    if (this.charCountElem) Element.show(this.charCountElem);
  },
  dispose: function() {
    this.dismiss();
    if (this.onKeyPressListener) Event.stopObserving(this.editField, "keypress", this.onKeyPressListener);
  },
  /**
   * Control method helpers
   */
  createFooter: function() {
    var footerPara = document.createElement("div");
    footerPara.className = this.options.footerClass;
    if(!this.options.formFooterId) this.options.formFooterId = document.validateId(this.options.containerElemId + "-formfooter");
    footerPara.id = this.options.formFooterId;

    if (!this.options.indicatorObj) {
      this.options.indicatorObj = new Mblst.IndicatorElem(footerPara, {position: true, refOffsetTop: 7, refOffsetLeft: 5});
    }

    okButton = document.createElement("input");
    okButton.type = "submit";
    okButton.value = this.options.okText;
    okButton.className = this.options.okButtonClass;
    footerPara.appendChild(okButton);

    cancelLink = document.createElement("a");
    cancelLink.href = "javascript: void(0);";
    cancelLink.className = this.options.cancelClassName;
    cancelLink.appendChild(document.createTextNode(this.options.cancelText));
    footerPara.appendChild(cancelLink);

    this.charCountElem = document.createElement("div");
    this.charCountElem.innerHTML = this.options.maxchars;
    this.charCountElem.className = this.options.formClassName + "-counter";
    footerPara.appendChild(this.charCountElem);

    this.form.appendChild(footerPara);
    
    this.clickcancelListener = this.onclickCancel.bindAsEventListener(this);
    Event.observe(cancelLink, 'click', this.clickcancelListener);      
  },
  createBody:function() {
    if (this.options.rows == 1) {
      this.options.textarea = false;
      var textField = document.createElement("input");
      textField.type = "text";
      textField.name = "value";
      //textField.value = this.getText();
      textField.className = this.options.textBoxClass;
      textField.id = this.options.editFieldId;
      textField.style.backgroundColor = this.options.highlightcolor;
      var size = this.options.size || this.options.cols || 0;
      if (size != 0)
        textField.size = size;
      this.formElemsDiv.appendChild(textField);
      this.editField = textField;
    } else {
      this.options.textarea = true;
      var textArea = document.createElement("textarea");
      textArea.name = "value";
      //textArea.value = this.getText();
      textArea.className = this.options.textAreaClass;
      textArea.id = 'testid';
      textArea.rows = this.options.rows;
      textArea.cols = this.options.cols || 27;
      this.formElemsDiv.appendChild(textArea);
      this.editField = textArea;
    }

    this.onKeyPressListener = this.onKeyPress.bindAsEventListener(this);
    Event.observe(this.editField, "keypress", this.onKeyPressListener);

    this.onKeyUpListener = this.onKeyUp.bindAsEventListener(this);
    Event.observe(this.editField, "keyup", this.onKeyUpListener);
  },
  /**
   * Event handlers
   */
  onKeyPress: function(event) {
    if (this.state == 'showing') {
      switch(event.keyCode) {
       case Event.KEY_TAB:
       case Event.KEY_RETURN:
         this.submit();
         return;
       case Event.KEY_ESC:
         this.cancel();
         Event.stop(event);
         return;
      }
      this.updateFieldCount();
    }
  },
  onKeyUp: function(event) {
    if (this.state == 'showing') {
      switch(event.keyCode) {
       case Event.KEY_TAB:
       case Event.KEY_RETURN:
       case Event.KEY_ESC:
         return;
      }
      this.updateFieldCount();
    }
  },
  /**
   * Utility functions
   */
  updateFieldCount: function() {
    if (this.editField) {
      var value = this.editField.value;
      if (value.length > this.options.maxchars) {
        this.editField.value = value.substring(0, value.length - 1);
        if (this.charCountElem) {
          if (this.onMaxCharsEffect) this.onMaxCharsEffect.cancel();
          this.onMaxCharsEffect = new Effect.Highlight(this.charCountElem, {
            startcolor: "#CC2222",
            endcolor: '',
            restorecolor: "transparent"
          });
        }
      } else {
        if (this.charCountElem) {
          while ( this.charCountElem.hasChildNodes() ) this.charCountElem.removeChild(this.charCountElem.firstChild);
          this.charCountElem.appendChild(document.createTextNode(this.options.maxchars - value.length));
        }
      }
    }
  },
  getInputValue: function () {
    return this.editField.value;
  }
});

Mblst.InternalFeedbackTextfield = Class.create();
Object.extend(Object.extend(Mblst.InternalFeedbackTextfield.prototype, Mblst.AjaxControl.Textfield.prototype), {
  initialize: function(triggerElem, url, options) {
    this.options = Object.extend({
      controlBodyBlurbClassName: 'feedbackBlurb'
    }, options || {});
    // Call parent constructor
    Mblst.AjaxControl.Textfield.prototype.initialize.call(this, triggerElem, url, this.options);
  },

  /**
   * Control methods
   */
  load: function() {
    this.controlHeader.innerHTML = this.options.headingText;
    Element.hide(this.controlBodyBlurbDiv);
    Element.show(this.controlHeader);
    Element.show(this.form);
  },
  onSuccess: function(transport) {
    var feedbackMsg = (transport.responseText) ? transport.responseText.stripTags() : 'Success!';
    this.hideProcessing();
    Element.hide(this.controlHeader);
    Element.hide(this.form);
    this.controlBodyBlurbDiv.innerHTML = feedbackMsg;
    Element.show(this.controlBodyBlurbDiv);
    this.dismiss(false, 4.0);
    this.event('afterProcess');

    this.options.onSuccess.bind(this)(transport, this.triggerElem);
  }
});


Mblst.TextareaCounter = Class.create();
Mblst.TextareaCounter.prototype = {
  /**
   * Constructor
   */
  initialize: function(textareaElem, charCountElem, options) {
    this.textareaElem = $(textareaElem);
    this.charCountElem = $(charCountElem);

    this.options = Object.extend({
      maxchars: 100,
      maxcharhighlightendcolor: "#FFFFFF"
    }, options || {});

    this.onKeyDownListener = this.onKeyDown.bindAsEventListener(this);
    Event.observe(this.textareaElem, "keydown", this.onKeyDownListener);

    this.onKeyUpListener = this.onKeyUp.bindAsEventListener(this);
    Event.observe(this.textareaElem, "keyup", this.onKeyUpListener);

    this.updateFieldCount();
  },
  updateFieldCount: function() {
    if (this.textareaElem) {
      var value = this.textareaElem.value;
      if (value.length > this.options.maxchars) {
        this.textareaElem.value = value.substring(0, value.length - 1);
        if (this.charCountElem) {
          if (this.onMaxCharsEffect) this.onMaxCharsEffect.cancel();
          this.onMaxCharsEffect = new Effect.Highlight(this.charCountElem, {
            startcolor: "#CC2222",
            endcolor: '',
            restorecolor: "transparent"
          });
        }
      } else {
        if (this.charCountElem) {
          while ( this.charCountElem.hasChildNodes() ) this.charCountElem.removeChild(this.charCountElem.firstChild);
          this.charCountElem.appendChild(document.createTextNode(this.options.maxchars - value.length));
        }
      }
    }
  },
  reset: function () {
    this.textareaElem.value = "";
    this.updateFieldCount();
    if (this.charCountElem) Element.show(this.charCountElem);
  },
  dispose: function() {
    if (this.onKeyDownListener) Event.stopObserving(this.textareaElem, "keydown", this.onKeyDownListener);
    if (this.onKeyUpListener) Event.stopObserving(this.textareaElem, "keyup", this.onKeyUpListener);
  },
  /**
   * Event handlers
   */
  onKeyDown: function(event) {
    this.updateFieldCount();
  },
  onKeyUp: function(event) {
    this.updateFieldCount();
  }
};




Mblst.AjaxControl.Confirm = Class.create();
Object.extend(Object.extend(Mblst.AjaxControl.Confirm.prototype, Mblst.AjaxControl.Base.prototype), {
  initialize: function(triggerElem, url, options) {

    this.options = Object.extend({
      confirmationText: 'Are you sure?',
      headingText: "Confirm",

      beforeProcessInternal: function(control) {
        control.dismiss();
      },

      confirmationTextClass: 'confirmationText'
    }, options || {});

    // Call parent constructor w/ these options
    Mblst.AjaxControl.Base.prototype.initialize.call(this, triggerElem, url, this.options);

  },
  /**
   * Control methods
   */
  load: function() {
    var confirmTextPara = document.createElement("p");
    confirmTextPara.className = this.options.confirmationTextClass;
    //Element.setInnerText(confirmTextPara, this.options.confirmationText);
    confirmTextPara.innerHTML = this.options.confirmationText;
    this.formElemsDiv.appendChild(confirmTextPara);
  },
  reset: function(noEffect) {
    Element.removeChildren(this.formElemsDiv);
  }
});




//In-place button form
Mblst.SimpleRequestMonitor = Class.create();
Mblst.SimpleRequestMonitor.prototype = {
  /**
   * Constructor
   */
  initialize: function(triggerElem, url, options) {
    this.actionURL = url;
    this.triggerElem = $(triggerElem);

    this.options = Object.extend({

      processingText: "processing request...",
      supressFeedback: false,
      supressFailureFeedback: false,
      processingClassName: 'simpleReqMonitorProcessing',
      hoverClassName: 'simpleReqMonitorHover',

      highlightcolor: Mblst.defaultHighlightColor,
      highlighterrorcolor: Mblst.defaultHighlightErrorColor,
      highlightendcolor: "#FFFFFF",

      onSuccess: function(transport, triggerElem) {},
      onFailure: function(transport) {},
      callback: Mblst.requestSerialize.bind(this),
      mblstReqOptions: {},
      ajaxOptions: {}
    }, options || {});


    /**
     * If a trigger image is given as an option, assign it.  Otherwise,
     * if the triggerElem is an img, assign it as the triggerImage.
     */
    if (this.options.triggerImage) {
      this.triggerImage = $(this.options.triggerImage);
      Element.forceHandCursor(this.triggerImage);
      this.preloadHoverImage();
    } else {
      if(this.triggerElem.tagName == 'IMG') {
        this.triggerImage = this.triggerElem;
        Element.forceHandCursor(this.triggerImage);
        this.preloadHoverImage();
      } else {
        this.triggerImage = null;
      }
    }

    /**
     * If an onCompleteHighlightElem object is specified in options,
     * then that element will see a highlight effect upon completion
     * of this controls action.
     */
    if (this.options.onCompleteHighlightElem) {
      this.onCompleteHighlightElem = $(this.options.onCompleteHighlightElem);
      this.onCompHighElemOrigBackground = Element.getStyle(this.onCompleteHighlightElem, 'background-color');
    } else {
      this.onCompleteHighlightElem = null;
      this.onCompHighElemOrigBackground = "transparent";
    }

    if (!this.options.feedbackObj) {
      this.options.feedbackObj = new Mblst.FeedbackElem(this.triggerElem, {});
    }

    if (!this.options.indicatorObj) {
      this.options.indicatorObj = new Mblst.IndicatorElem(this.triggerElem, {position: true, refOffsetLeft: 3, refOffsetTop: 0, positionOffsets: Mblst.Position.topLeftOnTopRight});
    }

    this.onclickListener = this.onClickTrigger.bindAsEventListener(this);
    this.mouseoverListener = this.onEnterTriggerHover.bindAsEventListener(this);
    this.mouseoutListener = this.onLeaveTriggerHover.bindAsEventListener(this);
    Event.observe(this.triggerElem, 'click', this.onclickListener);
    Event.observe(this.triggerElem, 'mouseover', this.mouseoverListener);
    Event.observe(this.triggerElem, 'mouseout', this.mouseoutListener);
  },

  /**
   * Control methods
   */
  showProcessing: function() {
    this.processing = true;
    this.options.indicatorObj.show(this.options.processingText);
  },
  hideProcessing: function() {
    this.options.indicatorObj.hide();
    this.processing = false;
    this.onLeaveTriggerHover();
  },
  dispose: function() {
    this.leaveEditMode();
    Event.stopObserving(this.triggerElem, 'click', this.onclickListener);
    Event.stopObserving(this.triggerElem, 'mouseover', this.mouseoverListener);
    Event.stopObserving(this.triggerElem, 'mouseout', this.mouseoutListener);
  },
  /**
   * Event handlers
   */
  onEnterTriggerHover: function() {
    if (this.processing) return;
    if (this.triggerImage) {
      if(navigator.appVersion.indexOf('MSIE')>0) {
        var filterValue = Element.getStyle(this.triggerHoverImage, 'filter');
        if (filterValue) Element.setStyle(this.triggerImage, {filter: filterValue});
      } else {
        this.triggerImage.src = this.triggerHoverImage.src;
      }
    }
  },
  onLeaveTriggerHover: function() {
    if (this.processing) return;
    if (this.triggerImage && !this.freezingHoverMode) {
      if(navigator.appVersion.indexOf('MSIE')>0) {
        var filterValue = Element.getStyle(this.triggerImage, 'filter');
        if (filterValue) {
          var newFilterValue = filterValue.replace(/_on(\.png)/, '_off$1');
          Element.setStyle(this.triggerImage, {filter: newFilterValue});
        }
      } else {
        this.triggerImage.src = this.triggerImage.src.replace(/_on(\.[^.]+)$/, '_off$1');
      }
    }
  },
  preloadHoverImage: function() {
    if (this.triggerImage) {
		this.triggerHoverImage = new Image();
        if(navigator.appVersion.indexOf('MSIE')>0) {
          var filterValue = Element.getStyle(this.triggerImage, 'filter');
          if (filterValue) {
            var newFilterValue = filterValue.replace(/_off(\.png)/, '_on$1');
            Element.setStyle(this.triggerHoverImage, {filter: newFilterValue});
          }
        } else {
          this.triggerHoverImage.src = this.triggerImage.src.replace(/_off(\.[^.]+)$/, '_on$1');
        }
    }
  },
  onClickTrigger: function() {
    this.onProcessing();
    new Ajax.Request(
      this.actionURL,
      Object.extend({
        asynchronous: true,
        parameters: this.options.callback(),
        onSuccess: this.onSuccess.bind(this),
        onFailure: this.onFailure.bind(this)
      }, this.options.ajaxOptions)
    );
    return false;
  },
  onProcessing: function() {
    this.showProcessing();
  },
  onSuccess: function(transport) {
    this.hideProcessing();
    this.options.onSuccess.bind(this)(transport, this.triggerElem);
    if (this.onCompleteHighlightElem)
    {
      if (this.onCompleteEffect) {
        this.onCompleteEffect.cancel();
      }
      this.onCompleteEffect = new Effect.Highlight(this.onCompleteHighlightElem, {
        startcolor: this.options.highlightcolor,
        endcolor: '',
        restorecolor: this.onCompHighElemOrigBackground
        });
    }
    var feedbackMsg = (transport.responseText) ? transport.responseText.stripTags() : 'Success!';
    if (!this.options.supressFeedback) this.options.feedbackObj.showOk(feedbackMsg);
  },
  onFailure: function(transport) {
    this.hideProcessing();
    this.options.onFailure(transport);
    if (this.onCompleteHighlightElem)
    {
      if (this.onCompleteEffect) {
        this.onCompleteEffect.cancel();
      }
      this.onCompleteEffect = new Effect.Highlight(this.onCompleteHighlightElem, {
        startcolor: this.options.highlighterrorcolor,
        endcolor: '',
        restorecolor: this.onCompHighElemOrigBackground
        });
    }
    var feedbackMsg = (transport.responseText) ? transport.responseText.stripTags() : 'Your request could not be completed.';
    if (!this.options.supressFailureFeedback) this.options.feedbackObj.showError(feedbackMsg);
    return false;
  }
};


Mblst.StyledInPlaceEditor = Class.create();
Object.extend(Object.extend(Mblst.StyledInPlaceEditor.prototype, Ajax.InPlaceEditor.prototype), {
  initialize: function(element, url, options) {

    this.options = Object.extend({
      okText: "OK",
      okButtonClass: "button",
      textInputClass: "inplaceEditorTextinput",
      savingClassName: 'inplaceEditorSaving',
      formClassName: 'inplaceEditorForm',
      instructionClassName: 'fontHintText',
      instructionText: '',
      maxChars: 150,
      n2br: false,
      br2n: true,
      pIsLineBreak: false,
      feedbackObj: main_feedback,

      processURL: url,

      callback: function(form, value) {
        if (this.options.n2br)
          value = this.toHTMLLineBreaks(value);
        value = value.substr(0, this.options.maxChars);
        return Mblst.requestSerialize.bind(this)(form, value);
      }.bind(this),
      onFailure: function(transport) {
        if (transport) {
          if (this.options.feedbackObj) {
            this.options.feedbackObj.showError(transport.responseText, true);
          } else {
            (transport.responseText.stripTags());
          }
        }
      }.bind(this),
      onComplete: function(transport, element) {
        if (transport && Mblst.responseIsSuccess(transport)) {
            if (this.options.emptyReplacement && !transport.responseText) {
                this.element.innerHTML = this.options.emptyReplacement;
            }
          new Effect.Highlight(element, {startcolor: this.options.highlightcolor});
        }
      }.bind(this),
      mblstReqOptions: {}
    }, options || {});

    /**
     * If not highlight end color is specified, set to background-color of first non-transparent ancestor
     */
    if (!this.options.highlightendcolor) {
      this.options.highlightendcolor = Element.getVisibleBackgroundColor(element);
    }

    if (!this.options.highlightcolor) {
      this.options.highlightcolor = Mblst.calcHighlightColor(this.options.highlightendcolor);
    }

    // Call parent constructor
    Ajax.InPlaceEditor.prototype.initialize.call(this, element, url, this.options);

    if (this.options.externalControl) {
      this.element.title = '';
      Event.stopObserving(this.element, 'click', this.onclickListener);
      Event.stopObserving(this.element, 'mouseover', this.mouseoverListener);
      Event.stopObserving(this.element, 'mouseout', this.mouseoutListener);
    }
  },
  toHTMLLineBreaks: function(string) {
    return string.replace(/\n\n/gi, "<br><br>");
  },
  hasHTMLLineBreaks: function(string) {
    if (!this.options.handleLineBreaks) return false;
    return string.match(/<br/i) || (this.options.pIsLineBreak && string.match(/<p>/i));
  },
  convertHTMLLineBreaks: function(string) {
    var ret = string.replace(/<br>/gi, "\n").replace(/<br\/>/gi, "\n");
    if (this.options.pIsLineBreak) ret = ret.replace(/<\/p>/gi, "\n").replace(/<p>/gi, "");
    return ret;
  },
  preserveHTMLLineBreaks: function(string) {
    var ret = string.replace(/([^\n])<br>/gi, "$1\n<br>").replace(/<br>([^\n])/gi, "<br>\n$1").replace(/([^\n])<br\/>/gi, "$1\n<br>").replace(/<br\/>([^\n])/gi, "<br>\n$1");
    return ret;
  },
  createForm: function() {
    this.form = document.createElement("form");
    this.form.id = this.options.formId;
    this.form.onsubmit = function(){return false};
    Element.addClassName(this.form, this.options.formClassName)

    this.createEditField();

    if (this.options.textarea) {
      var br = document.createElement("br");
      this.form.appendChild(br);
    }

    okButton = document.createElement("input");
    okButton.type = "submit";
    okButton.value = this.options.okText;
    okButton.className = this.options.okButtonClass;
    this.form.appendChild(okButton);

    cancelLink = document.createElement("a");
    cancelLink.href = "javascript: void(0);";
    cancelLink.className = this.options.cancelClassName;
    cancelLink.appendChild(document.createTextNode(this.options.cancelText));
    this.form.appendChild(cancelLink);

    if (this.options.instructionText) {
      instructionPara = document.createElement("p");
      instructionPara.className = this.options.instructionClassName;
      instructionPara.appendChild(document.createTextNode(this.options.instructionText));
      this.form.appendChild(instructionPara);
    }

    this.submitformListener = this.onSubmit.bindAsEventListener(this);
    Event.observe(this.form, 'submit', this.submitformListener);    
    this.clickcancelListener = this.onclickCancel.bindAsEventListener(this);
    Event.observe(cancelLink, 'click', this.clickcancelListener);      
  },
  createEditField: function() {
    Ajax.InPlaceEditor.prototype.createEditField.call(this);
    if (this.options.textarea == false) {
      this.editField.className = this.options.textInputClass;
      this.editField.maxLength = this.options.maxChars;
      if (this.options.textInputSize) this.editField.size = this.options.textInputSize;
    } else {
      var text = this.getText();
      if (this.options.br2n) {
        text = this.convertHTMLLineBreaks(text);
      } else {
        text = this.preserveHTMLLineBreaks(text);
      }
      this.editField.value = text;
    }
  },
  enterHover: function() {
    if (this.saving) return;
    if (!this.options.externalControl) {
      this.element.style.backgroundColor = this.options.highlightcolor;
      if (this.effect) {
        this.effect.cancel();
      }
    }
    Element.addClassName(this.element, this.options.hoverClassName)
  },
  leaveHover: function() {
    Element.removeClassName(this.element, this.options.hoverClassName)
    if (this.saving) return;

    if (!this.options.externalControl) {
      if (this.options.backgroundColor) {
        this.element.style.backgroundColor = this.oldBackground;
      }
      this.effect = new Effect.Highlight(this.element, {
        startcolor: this.options.highlightcolor,
        endcolor: this.options.highlightendcolor,
        restorecolor: this.originalBackground
      });
    }
  },
  enterEditMode: function() {
    Ajax.InPlaceEditor.prototype.enterEditMode.call(this);
    Field.select(this.editField);
  },
  onEnterEditMode: function() {
    if (this.options.externalControlContainer) {
      Element.hide(this.options.externalControlContainer);
    }
  },
  onLeaveEditMode: function() {
    if (this.options.externalControlContainer) {
      Element.show(this.options.externalControlContainer);
    }
  },
  onLoading: function() {
    this.saving = true;
    this.removeForm();
    this.leaveHover();
    this.showSaving();
    //add override of this.url so a trigger can override this value
    if (this.options.processURL) this.url = this.options.processURL;
  }
});

Mblst.StyledElementAdder = Class.create();
Object.extend(Object.extend(Mblst.StyledElementAdder.prototype, Mblst.StyledInPlaceEditor.prototype), {
  initialize: function(element, url, options) {

    this.options = Object.extend({
    }, options || {});

    // Call parent constructor
    Mblst.StyledInPlaceEditor.prototype.initialize.call(this, element, url, this.options);

    // Refelement is the element that will be replaced during hover mode.
    // If one is not specified via options, element is used.
    if (this.options.refElement) {
      this.refElement = $(this.options.refElement);
    } else {
      this.refElement = $(element);
    }
  },
  enterEditMode: function() {
    if (this.saving) return;
    if (this.editing) return;
    this.editing = true;
    this.onEnterEditMode();
    if (this.options.externalControl) {
      Element.hide(this.options.externalControl);
    }
    Element.hide(this.refElement); //override to use refElement
    this.createForm();

    this.refElement.parentNode.insertBefore(this.form, this.refElement);//override to use refElement
    Field.focus(this.editField);
    this.editField.scrollIntoView(false);
    // stop the event to avoid a page refresh in Safari
    if (arguments.length > 1) {
      Event.stop(arguments[0]);
    }
    Field.select(this.editField);
    return false;
  },
  getText: function() {return '';} //override to ensure text field is blank
});






/*--------------------------------------------------------------------------*/

var DraggableListObserver = Class.create();
DraggableListObserver.prototype = {
  initialize: function(element, addItemObserver, emptiedListObserver) {
    this.element   = $(element);
    this.addItemObserver  = addItemObserver;
    this.emptiedListObserver = emptiedListObserver;
    this.lastValue = Mblst.DraggableList.serialize(this.element);
  },
  onStart: function() {
    this.lastValue = Mblst.DraggableList.serialize(this.element);
  },
  onEnd: function() {
    Sortable.unmark();
    var thisValue = Mblst.DraggableList.serialize(this.element);

    if(thisValue.length > this.lastValue.length) {
      var gainedSortable = thisValue.diff(this.lastValue)[0];
      this.addItemObserver(this.element, gainedSortable);
    }else if (thisValue.length < this.lastValue.length && thisValue.length == 0) {
      this.emptiedListObserver(this.element);
    }
  }
}

/**
 * Static extension of Sortable
 */
Mblst.DraggableList = {
  constants: { listIdPrefix: 'friends_sublist-' },
  create: function(element) {
    element = $(element);
    var options = Object.extend({
      name:        'sublist',
      tag:         'LI',
      listIdPrefix: 'friends_sublist-',
      placeholderClass: 'draggablePlaceholderElem',
      highlightOnMoveComplete: true,
      moveActionUrlBase: '/rest',
      changedElem: null,
      onAddItem:    function(elem, gainedSortable) {
        if (gainedSortable != null) {
          new Ajax.Request(Sortable.options(elem).moveActionUrlBase, {
            method: 'put',
            asynchronous: true,
            evalScripts: true,
            //parameters: Mblst.DraggableList.serializeMove(elem)+'&'+'confirmView=sublist',
            parameters: 'buddy_user_id='+gainedSortable+'&'+'confirmView=sublist',
            onSuccess: Mblst.DraggableList.onMoveReqComplete,
            onFailure: Mblst.DraggableList.onMoveReqFailure
          });
          if (Sortable.options(elem).changedElem)
            Element.show(Sortable.options(elem).changedElem);
        }
      },
      deleteList: function(externalID) {},
      showDeleteList: function(externalID) {
        var deleteElemID = 'delete_link_span_'+externalID;
        var deleteElem = $(deleteElemID);
        if (deleteElem) Element.show(deleteElem);
      },
      hideDeleteList: function(externalID) {
        var deleteElemID = 'delete_link_span_'+externalID;
        var deleteElem = $(deleteElemID);
        if (deleteElem) Element.hide(deleteElem);
      },
      onEmptiedList: function(elem) {
        var options = Sortable.options(elem);
        options.isEmpty = true;

        options.showDeleteList(elem.id.split("-")[1]);
        Mblst.DraggableList.handleEmptyList(elem);
      },
      sortCriteria: function(elem) {
        return ((arr = elem.innerHTML.explode(' ')).length > 1) ? arr[1]+arr[0] : '0'; //second token within innerHTML (presumably text)
      },
      sortByElemClass: 'editFriendSortbyElem'

    }, arguments[1] || {});

    // Call parent constructor
    Sortable.create(element, options);
    // update options
    options = Sortable.options(element);



    // Remove the droppables added by Sortable.create since it adds droppables
    // for each list element, and we only want the whole list to be droppable.
    (Sortable.findElements(element, options) || []).each( function(e) {
      Droppables.remove(e);
    });



    // build options for the droppables
    var options_for_droppable = {
      overlap:     options.overlap,
      containment: options.containment,
      hoverclass:  options.hoverclass,
      onHover:     Sortable.onHover,
      greedy:      !options.dropOnEmpty
    }

    // Add list as droppable (if options.dropOnEmpty was true, then it was already added)
    if(!options.dropOnEmpty) {
      Droppables.add(element, options_for_droppable);
      options.droppables.push(element);
    }

    // Remove SortableObserver and add DraggableListObserver
    Draggables.removeObserver(element);
    Draggables.addObserver(new DraggableListObserver(element, options.onAddItem.bind(this), options.onEmptiedList.bind(this)));

    // Create a placeholder li to use when the list is empty
    var emptyText = document.createTextNode("Empty");
    var textContainer = document.createElement("div");
    textContainer.className = options.placeholderClass;
    textContainer.appendChild(emptyText);
    options.emptyPlaceholder = document.createElement("li");
    options.emptyPlaceholder.appendChild(textContainer);

    // Insert the placeholder if elem is empty.
    var items = Mblst.DraggableList.serialize(element);
    if (items.length == 0) {
      options.isEmpty = true;
      element.appendChild(options.emptyPlaceholder);
      options.showDeleteList(element.id.split("-")[1]);
    } else {
      options.isEmpty = false;
    }
  },
  handleEmptyList: function(elem) {
    elem.appendChild(Sortable.options(elem).emptyPlaceholder);
  },
  resortElements: function(elem) {
    //resort sublist elems on update
    var options = Sortable.options(elem);
    var sortableElems = Sortable.findElements(elem, options || []);
     //remove from DOM
    sortableElems.each( function(e) { elem.removeChild(e);});
    //sort
    var sorted = sortableElems.sortBy( function(e) {
      var sortByElem = Element.Class.childrenWith(e, options.sortByElemClass)[0];
      return options.sortCriteria(sortByElem);
    });
    //reinject back into DOM
    sorted.each( function(e) { elem.insertBefore(e, elem.lastChild);});
  },
  serializeMove: function(element) {
    return Sortable.options(element).name+'='+element.id.split("-")[1];
  },
  serialize: function(element) {
    element = $(element);
    var sortableOptions = Sortable.options(element);
    var options = Object.extend({
      tag:  sortableOptions.tag
    }, arguments[1] || {});
    return $A(element.childNodes).collect( function(item) {
      if (item.tagName != options.tag) throw $continue;
      return (item.id.split("-")[1]);
    });
  },
  onMoveReqComplete: function(transport) {
    element = $(Mblst.DraggableList.constants.listIdPrefix+transport.responseText);
    var options = Sortable.options(element);
    if (options.isEmpty) {
      options.isEmpty = false;
      element.removeChild(options.emptyPlaceholder);
      options.hideDeleteList(element.id.split("-")[1]);
    } else {
      Mblst.DraggableList.resortElements(element);
    }
    if (options.highlightOnMoveComplete)
      new Effect.Highlight(element, {});
  },
  onMoveReqFailure: function(transport) {
    ("Error communicating with the server: " + transport.responseText.stripTags());
    return false;
  }
};











Mblst.DefaultAutocompleteMarkupBuilder = Class.create();
Mblst.DefaultAutocompleteMarkupBuilder.prototype = {
  initialize: function(){}
  ,
  process: function(data) {
    var choiceData = data.options;
    var buffer = new StringBuffer();

    buffer.append("<ul class=\"choices\" >");

    for (var i = 0; i < choiceData.length; i++ ) {
      buffer.append("<li class=\"choice\"");
      buffer.append("id=\"choice-");
      buffer.append(choiceData[i].id);
      buffer.append("\" >");
  
      //name markup
      buffer.append("<div class=\"name\">");
      buffer.append(choiceData[i].name);
      buffer.append("</div>");
      
      buffer.append("</li>");
    }
    
    buffer.append("</ul>");
  return buffer.toString();
  }
};

Mblst.LocalAutocompleter = {}
Mblst.LocalAutocompleter = Class.create();
Mblst.LocalAutocompleter.prototype = Object.extend(Autocompleter.Base.prototype, {
  initialize: function(element, update, array, options) {
    this.baseInitialize(element, update, options);
    this.options.array = array;
    /**
     * This hash is too expensive to build when n > ~50
     */
    //this.lookupTable = hashify(this.options.array, this.matches.bind(this));

    // initialize markup builder.
    if (this.options.markupTemplateId) {
      this.markupBuilder = TrimPath.parseDOMTemplate(this.options.markupTemplateId);
    } else {
      this.markupBuilder = new Mblst.DefaultAutocompleteMarkupBuilder();
    }

    // activate input, if specified
    if (this.options.activateElement) {
      Field.scrollFreeActivate(this.element);
    }

    // assign observers
    this.onblurListener = this.onBlur.bindAsEventListener(this);
    this.keypressListener = this.onKeyPress.bindAsEventListener(this);
  },
  //show: function() {
  //  this.updateControl.show();
  //},
  //hide: function() {
  //  this.updateControl.hide();
  //},
  getUpdatedChoices: function() {
    var choices = this.options.selector(this);
    this.updateChoices(choices);
    if (this.entryCount == 0) {
      this.validateSelections();
    }
  },
  setOptions: function(options) {
    this.options = Object.extend({
      choices: 10,
      partialSearch: true,
      activateElement: false,
      partialChars: 1,
      ignoreCase: true,
      fullSearch: false,
      frequency: 0.2,
      onOptionSelected: function(selectedObj) {},
      onOptionDeSelected: function() {},
      afterUpdateElement: function(element, selectedElement) {
        this.validateSelections();
      }.bind(this),
      selector: function(instance) {
        var ret = [], partial = [], entry = instance.getToken(), count = 0, nameMarkup = '';
        //var candidates = (cArr = instance.lookupTable[entry.charAt(0).toLowerCase()]) ? cArr : [];
        var candidates = instance.options.array;

        for (var i = 0; i < candidates.length &&
          ret.length < instance.options.choices ; i++) {

          var elem = candidates[i].name;
          var foundPos = instance.options.ignoreCase ?
            elem.toLowerCase().indexOf(entry.toLowerCase()) :
            elem.indexOf(entry);
          while (foundPos != -1) {
            if (foundPos == 0) {
              nameMarkup = "<strong>" + elem.substr(0, entry.length) + "</strong>" + elem.substr(entry.length);
              var markedCandidate = new Object();
              Object.extend(Object.extend(markedCandidate, candidates[i]), {name: nameMarkup});
              ret.push(markedCandidate);
              break;
            } else if (entry.length >= instance.options.partialChars &&
              instance.options.partialSearch && foundPos != -1) {
              if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
                var buffer = new StringBuffer();
                buffer.append(elem.substr(0, foundPos));
                buffer.append("<strong>");
                buffer.append(elem.substr(foundPos, entry.length));
                buffer.append("</strong>");
                buffer.append(elem.substr(foundPos + entry.length));
                nameMarkup = buffer.toString();
                var markedCandidate = new Object();
                Object.extend(Object.extend(markedCandidate, candidates[i]), {name: nameMarkup});
                partial.push(markedCandidate);
                break;
              }
            }
            foundPos = instance.options.ignoreCase ?
              elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
              elem.indexOf(entry, foundPos + 1);
          }
        }
        if (partial.length)
          ret = ret.concat(partial.slice(0, instance.options.choices - ret.length));
        return instance.markupBuilder.process({options: ret});
      }
    }, options || {});
  },
  setElement: function(element) {
    //displose of previous elem references
    if (this.element) {
      Event.stopObserving(this.element, 'blur', this.onblurListener);
      Event.stopObserving(this.element, 'keypress', this.keypressListener);
    }

    //set new references
    element = $(element);
    this.element = element;
    this.updateControl.options.referenceElem = element;

    //update observers
    this.element.setAttribute('autocomplete','off');
    Event.observe(this.element, "blur", this.onblurListener);
    Event.observe(this.element, "keypress", this.keypressListener);
  },
  validateSelections: function() {
    var currToken = this.getToken();
    var noWhitespace = currToken.replace(/^\s+/,'').replace(/\s+$/,'');
    if (!noWhitespace.length) {
      this.options.onInputCleared();
      return;
    }

    var matchedArr = this.matches(noWhitespace);
    var exact = false;
    if (matchedArr.length > 0) {
      for (var i = 0; i < matchedArr.length; i++) {
        if (matchedArr[i].name == noWhitespace) {
          exact = true;
          this.options.onOptionSelected(matchedArr[i]);
          break;
        }
      }
    }
    
    if (!exact) this.options.onOptionDeSelected();
  },
  matches: function(entry) {
    var ret = [];
    for (var i = 0; i < this.options.array.length ; i++) {
      var elem = this.options.array[i].name;
      var foundPos = elem.toLowerCase().indexOf(entry.toLowerCase());
      if (foundPos != -1) ret.push(this.options.array[i]);
    }
    return ret;
  }
});

Mblst.LocalAutocompleteList = Class.create();
Object.extend(Object.extend(Mblst.LocalAutocompleteList.prototype, Mblst.LocalAutocompleter.prototype), {
  initialize: function(element, update, array, options) {

    var options = Object.extend({
      choices: 100,
      frequency: 0.04,
      minChars: -1,
      onShow: function(element, update){
        Element.show(update);
      },
      onHide: function(element, update){
        //Element.hide(update);
      }         
    }, options || {});

    // Call parent constructor w/ these options
    Mblst.LocalAutocompleter.prototype.initialize.call(this, element, update, array, options);
  },
  show: function() {
    if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
    Element.show(this.update);
  },
  hide: function() {
    if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
    this.stopIndicator();
  },
  updateChoices: function(choices) {
    if(!this.changed && this.hasFocus) {
      this.update.innerHTML = choices;
      Element.cleanWhitespace(this.update);
      Element.cleanWhitespace(this.update.down());

      if(this.update.firstChild) {
        updateChildren = this.update.down().childNodes;
        if(updateChildren) {
          this.entryCount = updateChildren.length;
          for (var i = 0; i < this.entryCount; i++) {
            var entry = this.getEntry(i);
            entry.autocompleteIndex = i;
            this.addObservers(entry);
          }
        }
      } else { 
        this.entryCount = 0;
      }

      this.stopIndicator();
      this.index = 0;
      this.render();
    }
  }  
});

Mblst.LocalInplaceAutocompleteEditor = Class.create();
Object.extend(Object.extend(Mblst.LocalInplaceAutocompleteEditor.prototype, Mblst.StyledInPlaceEditor.prototype), {
  initialize: function(element, update, url, array, options) {
    this.options = Object.extend({
      clickToEditText: '',
      onComplete: function(transport, element) {
        if (transport && Mblst.responseIsSuccess(transport)) {
          new Effect.Highlight(element, {startcolor: this.options.highlightcolor});
          if (element.tagName == 'A') {
            this.options.updateHref(transport, element);
          }
          if (this.options.onCompleteClass) element.className = this.options.onCompleteClass;
        }
      },
      updateHref: function() {},
      onOptionSelected: function(){},
      onOptionDeSelected: function(){},
      onInputCleared: function(){}
    }, options || {});

    // Call parent constructor
    Mblst.StyledInPlaceEditor.prototype.initialize.call(this, element, url, this.options)


    // call create form to initialize editField, and give the
    // autocompleter a required param.
    Mblst.StyledInPlaceEditor.prototype.createForm.call(this);

    this.autocompleter = new Mblst.LocalAutocompleter(
      this.editField,
      update,
      array,
      this.options);
  },
  createForm: function() {
    Mblst.StyledInPlaceEditor.prototype.createForm.call(this);
    this.autocompleter.setElement(this.editField);
  }
});

Mblst.LocalInplaceAutocompleteAdder = Class.create();
Object.extend(Object.extend(Mblst.LocalInplaceAutocompleteAdder.prototype, Mblst.LocalInplaceAutocompleteEditor.prototype), {
  initialize: function(element, update, url, array, options) {

    this.options = Object.extend({

    }, options || {});

    // Call parent constructor
    Mblst.LocalInplaceAutocompleteEditor.prototype.initialize.call(this, element, update, url, array, this.options);

    // Refelement is the element that will be replaced during hover mode.
    // If one is not specified via options, element is used.
    if (this.options.refElement) {
      this.refElement = $(this.options.refElement);
    } else {
      this.refElement = $(element);
    }
  },
  enterEditMode: function() {
    if (this.saving) return;
    if (this.editing) return;
    this.editing = true;
    this.onEnterEditMode();
    if (this.options.externalControl) {
      Element.hide(this.options.externalControl);
    }
    Element.hide(this.refElement); //override to use refElement
    this.createForm();

    this.refElement.parentNode.insertBefore(this.form, this.refElement);//override to use refElement
    Field.focus(this.editField);
    this.editField.scrollIntoView(false);
    // stop the event to avoid a page refresh in Safari
    if (arguments.length > 1) {
      Event.stop(arguments[0]);
    }
    Field.select(this.editField);
    return false;
  },
  getText: function() {return '';} //override to ensure text field is blank
});


Mblst.ContactAutocompleter = {}
Mblst.ContactAutocompleter.Local = Class.create();
Mblst.ContactAutocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
  initialize: function(element, update, array, options) {
    this.baseInitialize(element, update, options);
    this.options.array = array;
    /**
     * This hash is too expensive to build when n > ~50
     */
    //this.lookupTable = hashify(this.options.array, this.matches.bind(this));
    this.validContacts = new Array;
    this.validFlists = new Array;
    this.thisValueArr = [''];
    this.refValueArr = [''];
  },
  serializeContacts: function() {
    var value = new Array();
    for(name in this.validContacts) {
      if (this.validContacts[name] && typeof this.validContacts[name] == "string") {
        value.push(this.validContacts[name]);
      }
    }
    return  encodeURIComponent(this.options.contactParamName) + '=' +
            encodeURIComponent(value);
  },
  serializeFlists: function() {
    var value = new Array();
    for(name in this.validFlists) {
      if (this.validFlists[name] && typeof this.validFlists[name] == "string") {
        value.push(this.validFlists[name]);
      }
    }
    return  encodeURIComponent(this.options.flistParamName) + '=' +
            encodeURIComponent(value);
  },
  validateSelections: function() {
    var nameArr = [];

    for(name in this.validContacts) {
      if (this.validContacts[name] && typeof this.validContacts[name] == "string") {
        if (this.element.value.toLowerCase().indexOf('['+name.toLowerCase()+']') == -1) {
          delete this.validContacts[name];
        }
        nameArr.push(name);
      } else {
        if (!this.validContacts[name]) ('An unknown error has occurred. code:334');
      }
    }
    for(name in this.validFlists) {
      if (this.validFlists[name] && typeof this.validFlists[name] == "string") {
        if (this.element.value.toLowerCase().indexOf('['+name.toLowerCase()+']') == -1) {
          delete this.validFlists[name];
        }
        nameArr.push(name);
      } else {
        if (!this.validFlists[name]) ('An unknown error has occurred. code:334');
      }
    }
    var currToken = this.findToken();
    var noWhitespace = this.thisValueArr[currToken.tokenIdx].replace(/^\s+/,'').replace(/\s+$/,'');
    var tokenVal = noWhitespace.replace(/\[/,'').replace(/\]/,'');

    var matchedArr = this.matches(tokenVal);
    if (matchedArr.length == 1 && matchedArr[0].name == tokenVal) {
      //exact match
      this.element.value = this.element.value.replace(noWhitespace, '['+tokenVal+']');
    } else {
      this.element.value = this.element.value.replace(noWhitespace, tokenVal);
    }
  },
  setOptions: function(options) {
    this.options = Object.extend({
      choices: 10,
      partialSearch: true,
      partialChars: 1,
      ignoreCase: true,
      fullSearch: false,
      frequency: 0.2,
      selectedIdExtractionRegEx: /^.*-([0-9]+)$/,
      choicesClass: 'choices',
      contactClass: 'contact',
      friendlistClass: 'list',
      flistParamName: 'list_id',
      contactParamName: 'to_user_id',
      leftValidDelim: '[',
      rightValidDelim: ']',
      afterUpdateElement: function(element, selectedElement, value) {
        if (Element.hasClassName(selectedElement, this.options.friendlistClass)) {
          this.validFlists[value] = selectedElement.id.match(this.options.selectedIdExtractionRegEx)[1];
        } else if(Element.hasClassName(selectedElement, this.options.contactClass)){
          this.validContacts[value] = selectedElement.id.match(this.options.selectedIdExtractionRegEx)[1];
        }
      }.bind(this),
      onShow: function(element, update){
      if(!update.style.position || update.style.position!='absolute') {
        update.style.position = 'absolute';
        Position.clone(element, update, {setHeight: false, offsetTop: element.offsetHeight});
      }
      new Effect.Appear(update,{duration:0.05});
      },
      selector: function(instance) {
        var ret = [], partial = [], entry = instance.getToken(), count = 0, nameMarkup = '';

        //var candidates = (cArr = instance.lookupTable[entry.charAt(0).toLowerCase()]) ? cArr : [];
        var candidates = instance.options.array;


        for (var i = 0; i < candidates.length &&
          ret.length < instance.options.choices ; i++) {

          var elem = candidates[i].name;
          var foundPos = instance.options.ignoreCase ?
            elem.toLowerCase().indexOf(entry.toLowerCase()) :
            elem.indexOf(entry);

          while (foundPos != -1) {
            if (foundPos == 0 && elem.length != entry.length) {
              nameMarkup = "<strong>" + elem.substr(0, entry.length) + "</strong>" + elem.substr(entry.length);
              ret.push(instance.buildChoiceMarkup(candidates[i], nameMarkup));
              break;
            } else if (entry.length >= instance.options.partialChars &&
              instance.options.partialSearch && foundPos != -1) {
              if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
                var buffer = new StringBuffer();
                buffer.append(elem.substr(0, foundPos));
                buffer.append("<strong>");
                buffer.append(elem.substr(foundPos, entry.length));
                buffer.append("</strong>");
                buffer.append(elem.substr(foundPos + entry.length));
                nameMarkup = buffer.toString();
                partial.push(instance.buildChoiceMarkup(candidates[i], nameMarkup));
                break;
              }
            }
            foundPos = instance.options.ignoreCase ?
              elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
              elem.indexOf(entry, foundPos + 1);
          }
        }
        if (partial.length)
          ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
        return "<ul class=\""+instance.options.choicesClass+"\">" + ret.join('') + "</ul>";
      }
    }, options || {});
  },
  getUpdatedChoices: function() {
    var choices = this.options.selector(this);
    this.updateChoices(choices);
    if (this.entryCount == 0) {
      this.validateSelections();
      //(this.validContacts);
      //(this.validFlists);
    }
  },
  getToken: function() {
    var tokenPos = this.findToken();
    var ret = this.thisValueArr[tokenPos.tokenIdx].replace(/^\s+/,'').replace(/\s+$/,'');

    return /\n/.test(ret) ? '' : ret;
  },
  findToken: function() {
    var tokenPos = -1;
    var tokenIdx = 0;

    //compare all tokens, find index of tokens that do not match.
    for (var i=0; i < this.thisValueArr.length; i++) {
      if (i > 0 || i >= this.refValueArr.length) {
        tokenPos += this.thisValueArr[i-1].length + 1;
      }
      tokenIdx = i;
      if (i >= this.refValueArr.length || this.thisValueArr[i] != this.refValueArr[i]) {
        break;
      }
    }
    return {stringOffset: tokenPos, tokenIdx: tokenIdx};
  },
  onObserverEvent: function() {
    this.changed = false;
    this.refValueArr = this.thisValueArr;
    this.thisValueArr = this.element.value.explode(this.options.tokens);

    if(this.getToken().length>=this.options.minChars) {
      this.startIndicator();
      this.getUpdatedChoices();
    } else {
      this.active = false;
      this.hide();
    }
  },
  updateElement: function(selectedElement) {
    if (this.options.updateElement) {
      this.options.updateElement(selectedElement);
      return;
    }

    var value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
    this.thisValueArr = this.element.value.explode(this.options.tokens);
    var tokenPos = this.findToken();
    var preToken = '';
    var newToken = '';
    if (tokenPos.stringOffset != -1) {
      var preToken = this.element.value.substr(0, tokenPos.stringOffset + 1);
      var whitespace = this.element.value.substr(tokenPos.stringOffset + 1).match(/^\s+/);
      if (whitespace) newToken += whitespace[0];
      newToken += this.options.leftValidDelim+value+this.options.rightValidDelim;
    } else {
      newToken = this.options.leftValidDelim+value+this.options.rightValidDelim;
    }
    if (tokenPos.tokenIdx + 1 >= this.thisValueArr.length) {
      postToken = '';
    } else {
      postToken = this.element.value.substr(tokenPos.stringOffset + 1 + this.thisValueArr[tokenPos.tokenIdx].length);
    }
    this.element.value = preToken + newToken + postToken;
    this.thisValueArr = this.element.value.explode(this.options.tokens); //refresh val array
    this.element.focus();

    if (this.options.afterUpdateElement)
      this.options.afterUpdateElement(this.element, selectedElement, value);
  },
  matches: function(entry) {
    var ret = [];

    for (var i = 0; i < this.options.array.length ; i++) {
      var elem = this.options.array[i].name;
      var foundPos = elem.toLowerCase().indexOf(entry.toLowerCase());
      if (foundPos != -1) ret.push(this.options.array[i]);
    }
    return ret;
  },
  // @todo Weak, weak sauce here.  Implement a more general template solution
  buildChoiceMarkup: function(choiceData, name) {
    var buffer = new StringBuffer();

    buffer.append("<li class=\"");
    //if (choiceData.detail) {
    //  buffer.append(this.options.friendlistClass);
    //} else {
    //  buffer.append(this.options.contactClass);
    //}
    buffer.append(this.options.contactClass);
    buffer.append("\" id=\"choice-");
    buffer.append(choiceData.id);
    buffer.append("\" >");

    if(choiceData.imgurl) {
      buffer.append("<div class=\"image\"><img src=\"");
      buffer.append(choiceData.imgurl);
      buffer.append("\" height=\"32\" width=\"32\" alt=\"profile pic\" /></div>");
    } else {
      buffer.append("<div class=\"image\"><img src=\"images/logos/logo32x32.gif\" width=\"32\" height=\"32\" alt=\"profile pic\" /></div>");
    }

    if(choiceData.detail) {
      //name markup
      buffer.append("<div class=\"name\" >");
      buffer.append(name);
      buffer.append("</div>");

      buffer.append("<div class=\"detail\">");
      buffer.append("<span class=\"informal\">");
      buffer.append(choiceData.detail);
      buffer.append("</span></div>");
    } else {
      //name markup
      buffer.append("<div class=\"name\">");
      buffer.append(name);
      buffer.append("</div>");
    }
    buffer.append("</li>");
  return buffer.toString();
  }
});




// created as gap solution for reimplementing Textfield with
// a more general container.
Mblst.AjaxControl.GeneralTextfield = Class.create();
Object.extend(Object.extend(Mblst.AjaxControl.GeneralTextfield.prototype, Mblst.AjaxControl.Textfield.prototype), {
  initialize: function(triggerElem, url, options) {

    this.options = Object.extend({
      
      rows: 3,
      cols: 26,

      containerElemId: "txtpopup",

      extraMainClassName: 'extraMain',
      mainClassName: 'main',
      headerClassName: 'header',
      blurbClassName: 'blurb',
      bodyClassName: 'body',
      extraTopClassName: 'top',
      extraBottomClassName: 'extraBottom',
      extraBottomBodyClassName: 'inside',
      
      afterCreateInternal: function(control) {
        control.createForm();
        control.createBody();
        control.createFooter();
      },
      
      maxcharhighlightendcolor: "#FFFFFF"
      
    }, options || {});

    // Call parent constructor w/ these options
    Mblst.AjaxControl.Textfield.prototype.initialize.call(this, triggerElem, url, this.options);  

  },
  /**
   * Control methods
   */
  create: function() {
    var headerText = document.createTextNode(this.options.headingText);

    this.controlExtraTopDiv = document.createElement("div");
    this.controlExtraTopDiv.className = this.options.extraTopClassName;

    this.controlExtraMainDiv = document.createElement("div");
    this.controlExtraMainDiv.className = this.options.extraMainClassName;

    this.controlHeader = document.createElement("div");
    this.controlHeader.className = this.options.headerClassName;

    this.controlBlurbDiv = document.createElement("div");
    this.controlBlurbDiv.className = this.options.blurbClassName;

    this.controlBodyDiv = document.createElement("div");
    this.controlBodyDiv.className = this.options.bodyClassName;

    this.controlMainDiv = document.createElement("div");
    this.controlMainDiv.className = this.options.mainClassName;

    this.controlExtraBottomDiv = document.createElement("div");
    this.controlExtraBottomDiv.className = this.options.extraBottomClassName;

    this.controlExtraBottomBodyDiv = document.createElement("div");
    this.controlExtraBottomBodyDiv.className = this.options.extraBottomBodyClassName;

    this.controlHeader.appendChild(headerText);
    this.controlMainDiv.appendChild(this.controlExtraMainDiv);
    this.controlMainDiv.appendChild(this.controlHeader);
    this.controlMainDiv.appendChild(this.controlBlurbDiv);
    this.controlMainDiv.appendChild(this.controlBodyDiv);
    this.controlExtraBottomDiv.appendChild(this.controlExtraBottomBodyDiv);
    this.containerElem.appendChild(this.controlExtraTopDiv);
    this.containerElem.appendChild(this.controlMainDiv);
    this.containerElem.appendChild(this.controlExtraBottomDiv);
  },
  createForm: function() {
    this.form = document.createElement("form");
    if(!this.options.formId) {
      this.options.formId = this.options.containerElemId + "-form";
      if ($(this.options.formId)) {
        // there's already a form with that name, don't specify an id
        this.options.formId = null;
      }
    }
    this.form.id = this.options.formId;
    this.form.className = this.options.formClassName;
    this.form.onsubmit = function(){return false};
    this.controlBodyDiv.appendChild(this.form);

    this.formElemsDiv = document.createElement("div");
    this.formElemsDiv.className = this.options.formElemsClass;
    this.form.appendChild(this.formElemsDiv);
    
    this.submitformListener = this.onclickSubmit.bindAsEventListener(this);
    Event.observe(this.form, 'submit', this.submitformListener);     
  },
  createFooter: function() {
    var footerPara = document.createElement("div");
    footerPara.className = this.options.footerClass;
    if(!this.options.formFooterId) this.options.formFooterId = document.validateId(this.options.containerElemId + "-formfooter");
    footerPara.id = this.options.formFooterId;

    if (!this.options.indicatorObj) {
      this.options.indicatorObj = new Mblst.IndicatorElem(footerPara, {position: true, refOffsetTop: 7, refOffsetLeft: 5});
    }

    okButton = document.createElement("input");
    okButton.type = "submit";
    okButton.value = this.options.okText;
    okButton.className = this.options.okButtonClass;
    footerPara.appendChild(okButton);

    cancelLink = document.createElement("a");
    cancelLink.href = "javascript: void(0)";
    cancelLink.className = this.options.cancelClassName;
    cancelLink.appendChild(document.createTextNode(this.options.cancelText));
    footerPara.appendChild(cancelLink);

    this.charCountElem = document.createElement("div");
    this.charCountElem.innerHTML = this.options.maxchars;
    this.charCountElem.className = this.options.formClassName + "-counter";
    footerPara.appendChild(this.charCountElem);

    this.form.appendChild(footerPara);
    
    this.clickcancelListener = this.onclickCancel.bindAsEventListener(this);
    Event.observe(cancelLink, 'click', this.clickcancelListener);    
  }
});





Mblst.TxtPopupTrigger = Class.create();
Object.extend(Object.extend(Mblst.TxtPopupTrigger.prototype, Mblst.AjaxControlTrigger.prototype), {
  /**
   * Constructor
   */
  initialize: function(ajaxControlObj, triggerElem, options) {

    this.options = Object.extend({
      recipientID: 0,
      messageID: 0,
      extHeaderContainer: null,
      extBlurbContainer: null,
      loadMinimized: false,
      playAlertSound: false,
      pulsate: false
    }, options || {});      

    Mblst.AjaxControlTrigger.prototype.initialize.call(this, ajaxControlObj, triggerElem, this.options);
        
  }
});


Mblst.NewTxtEventTrigger = Class.create();
Mblst.NewTxtEventTrigger.prototype = {
  /**
   * Constructor
   */
  initialize: function(controlObj, uid, options) {
    this.controlObj = controlObj;
    this.uid = uid;
    this.triggeredByThis = false;
    this.processingEvent = false;
    this.threadFetched = false;
    this.profileFetched = false;
    this.eventRemoved = false;
    this.messageUnseen = false;
    this.errorProcessingEvent = false;

    this.options = Object.extend({
      recipientID: 0,
      messageID: 0,
      pollInterval: 15,
      idlenessDelay: 60,
      referenceElem: document.body,
      loadMinimized: false,
      playAlertSound: true,
      pulsate: true,

      beforeShowInternal: this.onControlShow.bind(this),
      afterHideInternal: this.onControlHide.bind(this)
    }, options || {});
    
    /**
     * Determine where this object will be inserted into the DOM.
     * Default: after triggerElem
     */
    if (this.options.injectionElem) {
      this.injectionElem = $(this.options.injectionElem);
    } else {
      this.injectionElem = document.body.firstChild;
    }
    
    /**
     * Create containers to store markup that will be
     * injected into popup control
     */
    this.createContainers();

    /**
     * Assign a unique trigger elem so this dismisses properly
     */
    this.options.triggerElem = this.options.extHeaderContainer;

    /**
     * Create listener to detect activity after a message is triggered
     */
    this.controlMouseoverListener = this.onControlMouseover.bindAsEventListener(this);

    /**
     * Start polling onload
     */
    this.loadlistener = this.startPolling.bindAsEventListener(this);
    Event.observe(window, 'load', this.loadlistener);
    
    /* prevent memory leaks in IE */
    if (navigator.appVersion.match(/\bMSIE\b/))
      Event.observe(window, 'unload', this.unloadPolling.bind(this));        
  },

  /**
   * Control methods
   */
  createContainers: function() {
    this.options.extHeaderContainer = document.createElement("div");
    this.options.extBlurbContainer = document.createElement("div");

    Element.hide(this.options.extHeaderContainer);
    Element.hide(this.options.extBlurbContainer);
    if (this.injectionElem.parentNode) {
      this.injectionElem.parentNode.insertBefore(this.options.extHeaderContainer, this.injectionElem);
      this.injectionElem.parentNode.insertBefore(this.options.extBlurbContainer, this.injectionElem);
    } else {
      this.injectionElem.appendChild(this.options.extHeaderContainer);
      this.injectionElem.appendChild(this.options.extBlurbContainer);
    }
  },
  /**
   * Control Methods
   */
  trigger: function() {
    if (this.triggeredByThis) {
      this.controlObj.extHeaderContainer = null;
      this.controlObj.extBlurbContainer = null;
      this.controlObj.dismiss(true);
    }
    
    this.controlObj.trigger(this.options);
    this.reset();
    this.triggeredByThis = true;
    this.beginIdleness();
    return false;
  },
  beginIdleness: function() {
    this.messageUnseen = true;
    this.processingEvent = true;
    Event.observe(this.controlObj.containerElem, 'mouseover', this.controlMouseoverListener);
    setTimeout(this.reportIdleness.bind(this), this.options.idlenessDelay * 1000);
  },
  clearIdleness: function() {
    this.messageUnseen = false;
    this.processingEvent = false;
    Event.stopObserving(this.controlObj.containerElem, 'mouseover', this.controlMouseoverListener);
  },
  reportIdleness: function() {
    if (this.messageUnseen) {
      if (this.triggeredByThis) this.controlObj.reportIdleness();
      this.clearIdleness();
    }
  },  
  startPolling: function() {
    this.executer = new PeriodicalExecuter(this.poll.bind(this), this.options.pollInterval);
  },
  poll: function()
  {
    if (!this.processingEvent && !this.errorProcessingEvent) {
      var cacheBuster = 'rn='+encodeURIComponent(Math.random());
  
      new Ajax.Request(
        Mblst.Url.eventC(this.uid),
        {
          asynchronous: true,
          method: 'get',
          parameters: cacheBuster,
          onSuccess: this.onPollSuccess.bind(this),
          onFailure: this.onPollFailure.bind(this)
        }
      );
    }
  },
  fetchMessageData: function() {
    if (!this.options.recipientID || !this.options.messageID) {
      this.errorProcessingEvent = true;
      return false;
    }
    this.processingEvent = true;
    this.fetchProfile();
    this.fetchThread();
  },
  clearExpiredEvent: function() {
    if (!this.options.messageID) {
      this.errorProcessingEvent = true;
      return false;
    }
    this.processingEvent = true;
    this.removeExpiredEvent();
  },  
  subProcessFinished: function() {
    if (this.threadFetched && this.profileFetched) {
      this.trigger();
    }
  },  
  fetchThread: function() {
    new Ajax.Request(
      Mblst.Url.conversationByMessage(this.options.recipientID, this.options.messageID), {
        asynchronous: true,
        parameters: Mblst.requestSerialize(null, null, {view: 'popup_thread'}),
        onSuccess: this.onFetchThreadSuccess.bind(this),
        onFailure: this.onFetchThreadFailure.bind(this),
        method: 'get'
      }
    );
  },
  fetchProfile: function() {
    new Ajax.Request(
      Mblst.Url.profile(this.options.recipientID), {
        asynchronous: true,
        parameters: Mblst.requestSerialize(null, null, {view: 'mod_minimal_profile'}),
        onSuccess: this.onFetchProfileSuccess.bind(this),
        onFailure: this.onFetchProfileFailure.bind(this),
        method: 'get'
      }
    );
  },
  removeEvent: function() {
    new Ajax.Request(
      Mblst.Url.event(this.uid, 'NEW_MESSAGE', this.options.messageID), {
        asynchronous: true,
        onSuccess: this.onRemoveEventSuccess.bind(this),
        onFailure: this.onRemoveEventFailure.bind(this),
        method: 'delete'
      }
    );
  },  
  removeExpiredEvent: function() {
    new Ajax.Request(
      Mblst.Url.event(this.uid, 'NEW_MESSAGE', this.options.messageID), {
        asynchronous: true,
        onSuccess: this.onRemoveExpiredEventSuccess.bind(this),
        onFailure: this.onRemoveExpiredEventFailure.bind(this),
        method: 'delete'
      }
    );
  },
  /**
   * AJAX Callbacks
   */
  onFetchThreadSuccess: function(transport) {
    Element.update(this.options.extBlurbContainer, transport.responseText);
    this.threadFetched = true;
    this.subProcessFinished();
  },
  onFetchThreadFailure: function(transport) {
    this.errorProcessingEvent = true;
    return false;
  },
  onFetchProfileSuccess: function(transport) {
    Element.update(this.options.extHeaderContainer, transport.responseText);
    this.profileFetched = true;
    this.subProcessFinished();
  },
  onFetchProfileFailure: function(transport) {
    this.errorProcessingEvent = true;
    return false;
  },  
  onRemoveEventSuccess: function(transport) {
    this.eventRemoved = true;
    this.clearIdleness();
  },
  onRemoveEventFailure: function(transport) {
    this.errorProcessingEvent = true;
    return false;
  },
  onRemoveExpiredEventSuccess: function(transport) {
    this.processingEvent = false;
    return false;  
  },
  onRemoveExpiredEventFailure: function(transport) {
    this.errorProcessingEvent = true;
    return false;
  },  
  onPollSuccess: function(transport, json)
  {
    json = transport.responseText;
    try {
      json = eval(json);
    } catch (e) {}
  
    if ( json && json[0] ) {
      var hash = $H(json[0]);
      var now = new Date();
      
      if(hash) {
        var keys = hash.keys();
        if (keys.length > 0) {
          this.options.recipientID = json[0][keys[0]].from_user_id;
          this.options.messageID = json[0][keys[0]].fk_id;
          if ((json[0][keys[0]].expiration_date - ((now.getTime())/1000.0)) < 0) {
            // we have an expired event
            this.clearExpiredEvent();
          } else {
            // we have a valid event
            this.fetchMessageData();
          }
        }
      }
    }
  },
  onPollFailure: function(transport) {
    if (transport.status != 404) this.errorProcessingEvent = true;
    return false;
  },  
  /**
   * Event handlers
   */
  onControlShow: function() {},
  onControlHide: function() {
    this.triggeredByThis = false;
  },
  onControlMouseover: function() {
    if (this.messageUnseen && this.triggeredByThis && this.options.messageID != 0) {
      this.messageUnseen = false;
      this.removeEvent();
    }
  },
  /**
   * Utility methods
   */
  reset: function() {
    this.processingEvent = false;
    this.threadFetched = false;
    this.profileFetched = false;
    this.eventRemoved = false;
  },
  dispose: function() {
    Event.stopObserving(this.triggerElem, 'click', this.onclickListener);
    Event.stopObserving(this.triggerElem, 'mouseup', this.onmouseupListener);
  },
  unloadPolling: function() {
    this.executer.stop();
    this.executer.callback = Prototype.emptyFunction;
  }
};


Mblst.HoverTxtPopupTrigger = Class.create();
Object.extend(Object.extend(Mblst.HoverTxtPopupTrigger.prototype, Mblst.HoverTrigger.prototype), {
  /**
   * Constructor
   */
  initialize: function(txtPopupObj, triggerElem, options) {

    this.options = Object.extend({
      containerIsTrigger: true,
      hideDelay: 500,
      showDelay: 300
    }, options || {});
    
    Mblst.HoverTrigger.prototype.initialize.call(this, txtPopupObj, triggerElem, this.options);
    
    Event.observe(this.controlObj.containerElem, 'click', this.clickListener);
  },
  /**
   * Control Methods
   */
  trigger: function() {
    if (this.controlObj.state != 'minimized') return false; // only trigger if minimized 
    this.controlObj.slideUp(this.options);
    this.triggeredByThis = true;
    return false;
  },
  dismiss: function() {
    if (this.controlObj.state != 'minimized') { // only dismiss if minimized 
      this.triggeredByThis = false;
      return false;
    }

    this.triggeredByThis = false;
    this.controlObj.minimize();
    return false;
  },
  /**
   * Event handlers
   */
  onClickTrigger: function(evt) {
	if (this.showProcId) {
		clearTimeout(this.showProcId);
	}
    if (this.controlObj.state != 'minimized') return false; // only maximize if minimized
    this.controlObj.maximize();
  },
  onEnterControlHover: function(evt) {
    if (this.controlObj.state != 'minimized') return false; // only handle if minimized
    if (this.hideProcId) clearTimeout(this.hideProcId);
    this.hoveredByThis = true;
  }
});

Mblst.AjaxControl.TxtPopup = Class.create();
Object.extend(Object.extend(Mblst.AjaxControl.TxtPopup.prototype, Mblst.AjaxControl.GeneralTextfield.prototype), {
  initialize: function(uid, options) {
    this.uid = uid;
    this.focused = false;
    this.newMessageEffect = null;

    this.options = Object.extend({
      recipientID: 0,
      messageID: 0,
      extHeaderContainer: null,
      extBlurbContainer: null,
      
      profileImgSrc: "images/logos/mb_logo_bl_ssq.gif",
      processingText: 'Sending txt...',
      idlenessReportText: '<p class="fontHintText">- TXT was delivered to your phone<br> &nbsp;&nbsp;because you look idle.</p>',
      
      containerElemId: "txtpopup",
     
      textAreaClass: 'txtPopupTextarea',
      containerClassName: 'txtPopupContainer',
      containerMinimizedClassName: 'txtPopupContainerMinimized',
      internalProfileClassName: 'txtPopupInternalProfile',
      formClassName: 'txtPopupForm',
      minmaxClassName: 'minmaxLink',
      closeClassName: 'closeLink',

      loadMinimized: false,
      playAlertSound: false,
      alertSoundFile: "/beep.mp3",

      pulsate: false,
      initEditFieldText: '( Type your txt msg here, hit enter to send )',

      mblstReqOptions: {
        dataSetRsrc: 'conversation',
        dataSet: 'default_arr',
        view: 'popup_thread',
        valueName: 'message'
      },
      ajaxOptions: {method: 'post'},
      afterCreateInternal: function(control) {
        control.createForm();
        control.createBody();
        control.createFooter();
        control.createControlLinks();
        control.createMinProfile();
      }
      
    }, options || {});

    // Call parent constructor w/ these options
    Mblst.AjaxControl.GeneralTextfield.prototype.initialize.call(this, null, null, this.options);

    // Init progress indicator
    if (this.options.indicatorObj) {
      this.options.indicatorObj = new Mblst.IndicatorTrigger(
        this.options.indicatorObj,
        this.editField,
        {position: true, positionOffsets: Mblst.Position.topLeftOnTopLeft, offsetLeft: 0, offsetRight: 0}
      );
    }

    // Init feedback elem
    this.options.feedbackObj = new Mblst.FeedbackElem(document.body, {
      dismissDelay: 3.0,
      containerElemId: "txtpopup_feedback",
      injectionElem: "js_injection_container",
      refOffsetLeft: -120,
      positionOffsets: Mblst.Position.bottomRightOnBottomRight,
      onShow: function(referenceElem, containerElem, offsets, useEffects, scrollViewportY){
          containerElem.style.position = 'absolute';
          Position.cloned(referenceElem, containerElem, {setHeight: false, setWidth: false, viewportPaddingX: 20, viewportPaddingY: 50, offsetLeft: offsets.offsetLeft, offsetTop: offsets.offsetTop, scrollViewportY: false, adjustPositionToViewportX: true, adjustPositionToViewportY: true});
          containerElem.style.zIndex = 1000;          
          Effect.Appear(containerElem,{duration:0.15});
      }
    });

    // Pre-load background image
    preloadImages(['/images/borders/bordertxtpopup.gif']);
   
    // Register page load event
    this.onloadlistener = this.onPageLoadListener.bindAsEventListener(this);
    addMarkupTailEvent(this.onloadlistener);
   
    // init edit field focus handler
    this.focuslistener = this.onFocusListener.bindAsEventListener(this);
    Event.observe(this.editField, 'focus', this.focuslistener);       

    //this.onwindowactivitylistener = this.onWindowActivityListener.bindAsEventListener(this);
    //Event.observe(window, 'mouseover', this.onwindowactivitylistener);

    this.options.referenceElem = document.body;
    
    // Create a trigger to slide up when minimized
    new Mblst.HoverTxtPopupTrigger(this, this.controlExtraTopDiv);
    
    // Init text field
    this.resetTextField();
  },
  /**
   * Control methods
   */
  insert: function() {
    Element.hide(this.containerElem);
    if (this.injectionElem.parentNode) {
      this.injectionElem.parentNode.insertBefore(this.containerElem, this.injectionElem);
    } else {
      this.injectionElem.appendChild(this.containerElem);
    }
  },
  createControlLinks: function() {
    closeLink = document.createElement("a");
    closeLink.href = "#";
    closeLink.className = this.options.closeClassName;
    closeLink.appendChild(document.createTextNode(""));
    this.containerElem.appendChild(closeLink);

    minmaxLink = document.createElement("a");
    minmaxLink.href = "#";
    minmaxLink.className = this.options.minmaxClassName;
    minmaxLink.appendChild(document.createTextNode(""));
    this.containerElem.appendChild(minmaxLink);
    
    this.clickcancelListener = this.onclickCancel.bindAsEventListener(this);
    this.clickminmaxListener = this.onclickMinMax.bindAsEventListener(this);
    this.mouseuplinkListener = this.onmouseupLink.bindAsEventListener(this);
    Event.observe(closeLink, 'click', this.clickcancelListener);
    Event.observe(minmaxLink, 'click', this.clickminmaxListener);
    Event.observe(minmaxLink, 'mouseup', this.mouseuplinkListener);
  },
  createMinProfile: function() {
    this.profileDivContainer = document.createElement("div");
    this.profileDiv = document.createElement("div");
    this.profileDiv.className = this.options.internalProfileClassName;
    this.profileImgLink = document.createElement("a");    
    this.profileImg = document.createElement("img");
    this.profileImg.src = this.options.profileImgSrc;
    this.profileImg.alt = "";
    var displayName = document.createTextNode(this.options.headingText);
    this.profileDisplayName = document.createElement("span");

    this.profileImgLink.appendChild(this.profileImg);
    this.profileDisplayName.appendChild(displayName);
    this.profileDiv.appendChild(this.profileImgLink);
    this.profileDiv.appendChild(this.profileDisplayName);
    this.profileDivContainer.appendChild(this.profileDiv);
  },  
  load: function() {
    // init send url
    if(this.options.messageID) {
      this.options.processURL = Mblst.Url.blast(this.options.recipientID, this.options.messageID);
    } else {
      this.options.processURL = Mblst.Url.blastC(this.options.recipientID);
    }
    
    // init header
    this.controlExtraTopDiv.innerHTML = this.options.headingText;
    if(this.options.extHeaderContainer) {
      this.extHeaderContainer = this.options.extHeaderContainer;
      Element.moveChildren(this.extHeaderContainer, this.controlHeader);
    } else {
      Element.moveChildren(this.profileDivContainer, this.controlHeader);
      this.updateProfile();
      Element.show(this.profileDiv);
    }
    // init blurb
    if(this.options.extBlurbContainer) {
      Element.hide(this.profileDiv);
      this.extBlurbContainer = this.options.extBlurbContainer;
      Element.moveChildren(this.extBlurbContainer, this.controlBlurbDiv);
      this.controlBlurbDiv.scrollTop = this.controlBlurbDiv.scrollHeight;
    }else{
      Element.removeChildren(this.controlBlurbDiv);
    }
    // play alert, if specified
    if (this.options.playAlertSound && this.alertSoundObject) {
      this.alertSoundObject.setVolume(60);      
      this.alertSoundObject.loadSound(this.options.alertSoundFile, true);
    }
    // prepare to show as maximized.
    this.prepareMaximized();
    // start visual alert effect
    //if (this.pulsate) this.newMessageEffect = new Effect.SlowPulsate(this.containerElem);
    // attempt to bring focus to browser window
    if (window.focus) window.focus();
  },
  show: function() {
    if (this.options.loadMinimized) {
      this.minimize();
      Element.show(this.containerElem);
      this.controlBlurbDiv.scrollTop = this.controlBlurbDiv.scrollHeight;
      this.options.loadMinimized = false;
    } else {
      Effect.Appear(this.containerElem,{duration:0.15, afterFinish: function() {this.controlBlurbDiv.scrollTop = this.controlBlurbDiv.scrollHeight;}.bind(this)});
    }
  },
  hide: function(noEffect) {
    if (noEffect) {
      Element.hide(this.containerElem);
    } else {
      Effect.Fade(this.containerElem,{duration:0.15});
    }
  },
  maximize: function() {
    this.prepareMaximized();
    this.state = 'showing';
  },
  minimize: function() {
    this.prepareMinimized();
    this.state = 'minimized';
  },
  reportIdleness: function() {
    new Insertion.Bottom(this.controlBlurbDiv, this.options.idlenessReportText);
    this.controlBlurbDiv.scrollTop = this.controlBlurbDiv.scrollHeight;
  },  
  reset: function(noEffect) {
    this.resetTextField();
    
    if(this.extHeaderContainer) {
      Element.moveChildren(this.controlHeader, this.extHeaderContainer);
      this.extHeaderContainer = null;
    } else {
      Element.moveChildren(this.controlHeader, this.profileDivContainer);
    }
    if(this.extBlurbContainer) {
      Element.moveChildren(this.controlBlurbDiv, this.extBlurbContainer);
      this.extBlurbContainer = null;
    } else {
      Element.removeChildren(this.controlBlurbDiv);
    }  
  },
  setPopupSessionState: function(state) {
    // we only have state if we have a thread, i.e. this.extBlurbContainer
    if (!this.extBlurbContainer) return;
    new Ajax.Request(
      Mblst.Url.txtPopup(this.uid), {
        asynchronous: true,
        parameters: 'state='+state,
        method: 'put'
      }
    );
  },  
  /**
   * Control method helpers
   */
  updateProfile: function() {
    this.profileImgLink.href = "/"+this.options.recipientID;
    this.profileImg.src = this.options.profileImgSrc;
    this.profileDisplayName.innerHTML = this.options.headingText;
  },
  slideUp: function() {
    new Effect.BlindDown(this.controlMainDiv, {
      duration: .5,
      afterFinishInternal: function(effect) {
        Element.undoClipping(effect.element);
        Element.setOpacity(this.containerElem, 1.0);
      }.bind(this)
    });
  },
  slideDown: function() {
    new Effect.BlindUp(this.controlMainDiv, {
      duration: .5,
      afterFinishInternal: function(effect) {
        effect.element.hide().undoClipping();
        this.minimize();
      }.bind(this)
    });
  },
  resetTextField: function() {
    Mblst.AjaxControl.Textfield.prototype.reset.call(this);
    // reset focus handler
    this.editField.value = this.options.initEditFieldText;
    this.focused = false;    
  },
  prepareMaximized: function() {
    Element.removeClassName(this.containerElem, this.options.containerMinimizedClassName);
    Element.show(this.controlMainDiv);
    Element.setOpacity(this.containerElem, 1.0);
  },
  prepareMinimized: function() {
    Element.addClassName(this.containerElem, this.options.containerMinimizedClassName);
    Element.hide(this.controlMainDiv);
    Element.setOpacity(this.containerElem, .6);
  },  
  /**
   * AJAX Callbacks
   */
  onSuccess: function(transport) {
    this.hideProcessing();
    this.event('afterProcess');
    this.resetTextField();

    var feedbackMsg = 'Your txt was sent!';
    if (!this.options.supressFeedback) this.options.feedbackObj.showOk(feedbackMsg);
    Element.update(this.controlBlurbDiv, transport.responseText);
    this.options.onSuccess.bind(this)(transport, this.triggerElem);
    this.maximize();
    this.controlBlurbDiv.scrollTop = this.controlBlurbDiv.scrollHeight;
  },  
  /**
   * DOM Event handlers
   */
  onmouseupLink: function(evt) {
    if (evt && Event.element(evt).tagName == 'A') Event.element(evt).blur();
  },
  onclickCancel: function() {
    this.setPopupSessionState('closed');
    return this.cancel();
  },  
  onclickMinMax: function() {
    if (this.state == 'minimized') {
      this.maximize();
      this.setPopupSessionState('maximized');
    } else {
      this.slideDown();
      this.setPopupSessionState('minimized');
    }
    return false;
  },
  onFocusListener: function() {
    if(!this.focused) {
      this.focused = true;
      Field.clear(this.editField);
    }
  },
  onWindowActivityListener: function() {
    if(this.newMessageEffect) {
      this.newMessageEffect.cancel();
      this.newMessageEffect = null;
      Element.setOpacity(this.containerElem, 1.0);
    }    
  },
  onPageLoadListener: function() {
    if (this.options.alertSoundFile) {
      this.alertSoundObject = new Sound();
    }
  }
});

Effect.SlowPulsate = function(element) {
  element = $(element);
  var options    = arguments[1] || {};
  var oldOpacity = Element.getInlineOpacity(element);
  var transition = options.transition || Effect.Transitions.sinoidal;
  var reverser   = function(pos){ return transition(1-Effect.Transitions.pulse(pos*5.0)) };
  reverser.bind(transition);
  return new Effect.Opacity(element, 
    Object.extend(Object.extend({ duration: 15.0, from: 0,
      afterFinishInternal: function(effect) { Element.setStyle(effect.element, {opacity: oldOpacity}); }
    }, options), {transition: reverser}));
}








Mblst.AjaxControl.QuickFriends = Class.create();
Object.extend(Object.extend(Mblst.AjaxControl.QuickFriends.prototype, Mblst.AjaxControl.Base.prototype), {
  initialize: function(triggerElem, containerElem, formElemsDiv, uid, options) {
    this.triggerElem = $(triggerElem);
    this.formElemsDiv = $(formElemsDiv);
    this.uid = uid;
    this.loaded = false;

    this.options = Object.extend({
      displayStatic: true,
      loadingMarkup: '<p class="loading">loading...</p>',
      mblstLoadOptions: {view: 'mod_quick_friends'},
      loaderAjaxOptions: {method: 'get'},
      afterFetchInternal: function(control) {
        control.loaded = true;
      },
      containerElem: $(containerElem)
    }, options || {});

    // Assign fetch url if control is not displayed statically
    this.options.loadFormElemsURL = Mblst.Url.friendC(this.uid);
    if (this.options.displayStatic) this.loaded = true;

    // Call parent constructor w/ these options
    Mblst.AjaxControl.Base.prototype.initialize.call(this, this.triggerElem, null, this.options);

    if (this.options.displayStatic) {
      this.options.triggerObject.trigger();
    } 
  },
  /**
   * Control methods
   */
  load: function() {
    if (!this.loaded && !this.options.displayStatic) {
      // load data asynchronously
      if (this.options.loadFormElemsURL) this.fetch();
      return;
    }
  },
  update: function() {
    if (this.options.loadFormElemsURL) this.fetch();
    return;
  },    
  show: function() {
    Element.show(this.containerElem);
  },
  hide: function() {
    Element.hide(this.containerElem);
  },
  /**
   * AJAX Callbacks
   */
  onFetchSuccess: function(transport) {
    this.hideLoading();
    this.iefix = null;
    Element.update(this.formElemsDiv, transport.responseText);

    // initialize select elems
    var elems = this.formElemsDiv.getElementsByClassName('ajaxToggleSelectElem');
    for (var i=0; i < elems.length ; i++) new Mblst.ToggleSelectElemCtor(elems[i]);      

    // highlight if this is a re-load
    if (this.loaded) new Effect.Highlight(this.formElemsDiv);

    this.state = 'showing';
    this.event('afterFetch');
    this.loaded = true;

    this.options.onFetchSuccess(transport);
  },  
  /**
   * Utility functions
   */
  showLoading: function() {
    if (this.formElemsDiv && !this.loaded) {
      this.formElemsDiv.innerHTML = this.options.loadingMarkup;
    }
  }  
});


Mblst.QuickFriendsPopupTrigger = Class.create();
Object.extend(Object.extend(Mblst.QuickFriendsPopupTrigger.prototype, Mblst.HoverTrigger.prototype), {
  /**
   * Constructor
   */
  initialize: function(popup, triggerElem, containerElem, options) {
    this.popup = popup;
    this.triggerElem = $(triggerElem);

    this.options = Object.extend({
      containerElem: $(containerElem),
      referenceElem: this.triggerElem,
      positionToCursor: false,
      scrollViewportY: false,
      adjustPositionToViewportY: true,
      hideDelay: 500,
      showDelay: 300,
      refOffsetLeft: 0,
      refOffsetTop: 0,
      positionOffsets: Mblst.Position.topLeftOnTopRight
    }, options || {});

    Mblst.HoverTrigger.prototype.initialize.call(this, popup, triggerElem, this.options);
    
    // Reset the control event listeners since this.options.containerElem will
    // probabably not be this.controlObject.containerElem when init is called.
    Event.observe(this.options.containerElem, 'mouseover', this.controlmouseoverListener);
    Event.observe(this.options.containerElem, 'mouseout', this.controlmouseoutListener);
    
    this.onclickListener = this.onClickFriend.bindAsEventListener(this);
    this.onmouseupListener = this.clearLink.bindAsEventListener(this);
    Event.observe(this.triggerElem, 'click', this.onclickListener);
    Event.observe(this.triggerElem, 'mouseup', this.onmouseupListener);    
    
    // Ensure iefix is still in the dom
    if (this.popup.iefix) this.popup.iefix = null;
  },
  onClickFriend: function() {
    this.controlObj.dismiss(true, 0);
  },
  clearLink: function() {
    if (this.triggerElem.tagName == 'A') this.triggerElem.blur();
  }  
});

Mblst.QuickFriendsPopup = Class.create();
Object.extend(Object.extend(Mblst.QuickFriendsPopup.prototype, Mblst.PositionedControl.prototype), {
  /**
   * Constructor
   */
  initialize: function(triggerElem, options) {
    
    this.options = Object.extend({
      scrollViewportY: false
    }, options || {});    
    
    // Call parent constructor
    Mblst.PositionedControl.prototype.initialize.call(this, triggerElem, options);
  },
  /**
   * Control methods
   */
  load: function() {
    if (this.options.containerElem) {
      this.containerElem = $(this.options.containerElem);
      this.clickpopuplistener = this.onclickPopup.bindAsEventListener(this);
      Event.observe(this.containerElem, 'click', this.clickpopuplistener);      
    }
  },
  /**
   * Event Handlers
   */  
  onclickPopup: function(evt) {
	if(Event.element(evt).tagName == 'A') this.dismiss();
  }    
});


Mblst.TextInputHint = Class.create();
Mblst.TextInputHint.prototype = {
  /**
   * Constructor
   */
  initialize: function(inputElem, hintText, options) {
    this.inputElem = $(inputElem);
    this.hintText = hintText;

    this.options = Object.extend({
    }, options || {});

    this.focusListener = this.onInputFocus.bindAsEventListener(this);
    this.blurListener = this.onInputBlur.bindAsEventListener(this);
    Event.observe(this.inputElem, 'focus', this.focusListener);
    Event.observe(this.inputElem, 'blur', this.blurListener);
  },

  /**
   * Event Handlers
   */
  onInputFocus: function() {
    if (this.inputElem.value == this.hintText)
      Field.clear(this.inputElem);
  },
  onInputBlur: function() {
    if (!this.inputElem.value)
      this.inputElem.value = this.hintText;
  }
};

Mblst.CodeGrabber = Class.create();
Mblst.CodeGrabber.prototype = {
  /**
   * Constructor
   */
  initialize: function(inputElem, options) {
    this.inputElem = $(inputElem);
    this.code = null;

    this.options = Object.extend({
    }, options || {});

    this.code = this.inputElem.value;

    this.focusListener = this.onInputFocus.bindAsEventListener(this);
    this.keyupListener = this.onInputKeyup.bindAsEventListener(this);
    this.mouseupListener = this.onInputMouseup.bindAsEventListener(this);
    Event.observe(this.inputElem, 'focus', this.focusListener);
    Event.observe(this.inputElem, 'keyup', this.keyupListener);
    Event.observe(this.inputElem, 'mouseup', this.mouseupListener);
  },
  /**
   * Event Handlers
   */
  onInputFocus: function() {
     Field.select(this.inputElem);
  },
  onInputKeyup: function() {
    this.inputElem.value = this.code;
    Field.select(this.inputElem);
  },
  onInputMouseup: function() {
    Field.select(this.inputElem);
  }  
};



Mblst.PeopleTagDisplay = Class.create();
Mblst.PeopleTagDisplay.prototype = {
  /**
   * Constructor
   */
  initialize: function(containerElem, triggerElem, photoElem, xcoord, ycoord, options) {
    this.containerElem = $(containerElem);
    this.triggerElem = $(triggerElem);
    this.photoElem = $(photoElem);
    this.xcoord = xcoord;
    this.ycoord = ycoord;

    this.options = Object.extend({
      overlayClassName: 'peopleTagOverlay',
      triggerHoverClassName: 'triggerHover',
      overlayHoverClassName: 'overlayHover',
      
      scaleFactor: 1.0,
      displayname: null,
      uid: null

    }, options || {});
    this.lastOptions = this.options;
    
    this.mouseoverOverlayListener = this.onEnterOverlayHover.bindAsEventListener(this);
    this.mouseoutOverlayListener = this.onLeaveOverlayHover.bindAsEventListener(this);
    this.mouseoverTriggerListener = this.onEnterTriggerHover.bindAsEventListener(this);
    this.mouseoutTriggerListener = this.onLeaveTriggerHover.bindAsEventListener(this);
    
    this.loadListener = this.create.bindAsEventListener(this);
    
    if (document.readyState && document.readyState != "complete") {
      addMarkupTailEvent(this.loadListener);
    } else {
      this.create();
    }
  },

  /**
   * Control methods
   */
  create: function() {       
    this.overlayElem = document.createElement("div");
    this.overlayElem.className = this.options.overlayClassName;
    
    if (this.options.displayname) {
      var displaynameText = document.createTextNode(this.options.displayname);
      if (this.options.uid) {
        this.overlayLink = document.createElement("a");
        this.overlayLink.href = "/" + this.options.uid;
        this.overlayLink.appendChild(displaynameText);
        this.overlayElem.appendChild(this.overlayLink);
      } else {
        this.overlayElem.appendChild(displaynameText);
      }
    }
        
    this.containerElem.appendChild(this.overlayElem);
    Position.clone( this.photoElem, this.overlayElem, { setHeight: false,
                                                        setWidth: false,
                                                        offsetLeft: Math.floor(this.xcoord * this.options.scaleFactor),
                                                        offsetTop: Math.floor(this.ycoord * this.options.scaleFactor) });
      
    Event.observe(this.overlayElem, 'mouseover', this.mouseoverOverlayListener);
    Event.observe(this.overlayElem, 'mouseout', this.mouseoutOverlayListener);     
    Event.observe(this.triggerElem, 'mouseover', this.mouseoverTriggerListener);
    Event.observe(this.triggerElem, 'mouseout', this.mouseoutTriggerListener);
    
    if (this.options.auxTriggerElems) {
      var mouseoverTriggerListener = this.mouseoverTriggerListener;
      var mouseoutTriggerListener = this.mouseoutTriggerListener;
      this.options.auxTriggerElems.each( function(elem){
        Event.observe(elem, 'mouseover', mouseoverTriggerListener);
        Event.observe(elem, 'mouseout', mouseoutTriggerListener);
      });
    }    
  },
  /**
   * Event handlers
   */
  onEnterTriggerHover: function() {
    Element.addClassName(this.overlayElem, this.options.triggerHoverClassName);
  },
  onLeaveTriggerHover: function() {
    Element.removeClassName(this.overlayElem, this.options.triggerHoverClassName);
  },
  onEnterOverlayHover: function() {
    Element.addClassName(this.overlayElem, this.options.overlayHoverClassName);
    Element.addClassName(this.triggerElem, this.options.overlayHoverClassName);
  },
  onLeaveOverlayHover: function() {
    Element.removeClassName(this.overlayElem, this.options.overlayHoverClassName);
    Element.removeClassName(this.triggerElem, this.options.overlayHoverClassName);
  },  
  dispose: function() {
    Event.stopObserving(this.overlayElem, 'mouseover', this.mouseoverOverlayListener);
    Event.stopObserving(this.overlayElem, 'mouseout', this.mouseoutOverlayListener);    
    Event.stopObserving(this.triggerElem, 'mouseover', this.mouseoverTriggerListener);
    Event.stopObserving(this.triggerElem, 'mouseout', this.mouseoutTriggerListener);
  }  
};


Mblst.PeopleTagAdderTrigger = Class.create();
Object.extend(Object.extend(Mblst.PeopleTagAdderTrigger.prototype, Mblst.Trigger.prototype), {
  /**
   * Constructor
   */
  initialize: function(adder, url, triggerElem, tagLinkContainerElem, options) {
    this.adder = adder;
    this.photoPos = null;
    this.photoDims = null;    

    this.options = Object.extend({
      processURL: url,
      tagLinkContainerElem: $(tagLinkContainerElem),        
        
      forceHandCursor: false,
      scaleFactor: 1.0,
      overlayDim: 150,
      overlayPad: 5,
      
      overlayClassName: 'addPeopleTagOverlay',
      
      positionOffsets: Mblst.Position.topRightOnTopLeft
    }, options || {});

    Mblst.Trigger.prototype.initialize.call(this, adder, triggerElem, this.options);
    this.clickOverlayListener = this.onClickOverlay.bindAsEventListener(this);
    this.create();

    this.loadListener = this.loadPhotoBox.bindAsEventListener(this);
    if (document.readyState && document.readyState != "complete") {
      addMarkupTailEvent(this.loadListener);
    } else {
      this.loadPhotoBox();
    }    
  },
  create: function() {       
    this.overlayElem = document.createElement("div");
    this.overlayElem.className = this.options.overlayClassName;
    Element.hide(this.overlayElem);

    if (this.triggerElem.parentNode) {
      this.triggerElem.parentNode.insertBefore(this.overlayElem, this.triggerElem);
    } else {
      this.triggerElem.appendChild(this.triggerElem);
    }

    Event.observe(this.overlayElem, 'click', this.clickOverlayListener);   
  },
  loadPhotoBox: function() {
    if (!this.photoPos) this.photoPos = Position.cumulativeOffset(this.triggerElem);
    if (!this.photoDims) this.photoDims = Element.getDimensions(this.triggerElem);     
  },
  setOffsets: function(clickX, clickY) {
    this.loadPhotoBox();   
    
    var xOffset = clickX - (this.options.overlayDim/2.0);
    xOffset = Math.max(this.photoPos[0], xOffset);
    xOffset = Math.min(this.photoPos[0] + this.photoDims.width - this.options.overlayDim, xOffset);
    
    var yOffset = clickY - (this.options.overlayDim/2.0);
    yOffset = Math.max(this.photoPos[1], yOffset);
    yOffset = Math.min(this.photoPos[1] + this.photoDims.height - this.options.overlayDim, yOffset);
    
    this.options.refOffsetLeft = xOffset - this.photoPos[0] - this.options.overlayPad;
    this.options.refOffsetTop = yOffset - this.photoPos[1];
    
    //alert(clickX + "," + clickY + "," + this.photoPos[0] + "," + this.photoPos[1] + "," + this.photoDims.width + "," + this.photoDims.height);
    
    this.adder.tagCoordX = (xOffset - this.photoPos[0])/this.options.scaleFactor;
    this.adder.tagCoordY = (yOffset - this.photoPos[1])/this.options.scaleFactor;
  },  
  /**
   * DOM Event handlers
   */
  trigger: function(evt){
    if (!this.adder.active) return false;
    
    var x = Event.pointerX(evt);
    var y = Event.pointerY(evt);
    
    //alert(x+", "+y);
    
    this.setOffsets(x, y);
    
    if (this.adder.state != 'idle') {
      this.adder.state = 'idle';
      this.adder.event('beforeHide');
      this.adder.hide(true);
      this.adder.event('afterHide');
    }
    
    Position.clone( this.triggerElem, this.overlayElem, { setHeight: false,
                                                          setWidth: false,
                                                          offsetLeft: Math.floor(this.adder.tagCoordX * this.options.scaleFactor),
                                                          offsetTop: Math.floor(this.adder.tagCoordY * this.options.scaleFactor) });    
    
    Element.show(this.overlayElem);
    this.adder.trigger(this.options);

    return false;    
  },
  onClickOverlay: function(evt) {
    this.trigger(evt);
    return false;
  },
  onControlHide: function() {
    Element.hide(this.overlayElem);
    Mblst.Trigger.prototype.onControlHide.call(this);   
  }
});

Mblst.PeopleTagAdder = Class.create();
Object.extend(Object.extend(Mblst.PeopleTagAdder.prototype, Mblst.AjaxControl.Base.prototype), {
  /**
   * Constructor
   */
  initialize: function(uid, acOptionArray, acMarkupTemplateId, options) {
    this.uid = uid;
    this.clickedElemExternalId = 0;
    this.acOptionArray = acOptionArray;
    this.acMarkupTemplateId = acMarkupTemplateId;
    this.tagCoordX = 0;
    this.tagCoordY = 0;
    this.active = false;
    this.loaded = false;

    this.options = Object.extend({
      acElemId: 'people_tag_adder_ac_input',
      acContainerId: 'people_tag_adder_ac_container',
      acStatusDivId: 'people_tag_adder_ac_status',
        
      displayBlurb: false,
      deactivateOnCancel: true,
      useShowHideEffects: false,
      supressShowIEFix: true,
      requestValidator: null,
      extStaticFormElemsContainer: null,
      loadingText: "loading friends list...",
      processingText: "",
      feedbackObj: main_feedback,

      containerElemId: 'people_tag_adder_container',
      meButtonClassName: 'addPeopleTagMeButton',
      meButtonContainerClassName: 'addPeopleTagMeButtonContainer',
      inputLabelClassName: 'addPeopleTagInputLabel',
      newContactDivClassName: 'newContactContainer',
      activeClassName: 'tagAdderActive',
      selectListElemClass: 'contactCheckboxInput',
      selectHoverClassName: 'contactCheckboxInputHover',

      afterCreateInternal: function(control) {
        control.createBody();
        control.createFooter();
      },
      afterFetchInternal: function(control) {
        control.registerSelectItems();
        window.focus();
        control.loaded = true;
      },
      afterShowInternal: function(control) {
        Field.scrollFreeActivate(control.autocompleter.element);
      },      
      
      callback: function(form, value, options) {return Mblst.requestSerialize.bind(this)(form, value, options) + '&x_coord=' + this.tagCoordX + '&y_coord=' + this.tagCoordY;}.bind(this),
      
      ajaxOptions: {method: 'post'}

    }, options || {});

    // Call parent constructor w/ these options
    Mblst.AjaxControl.Base.prototype.initialize.call(this, null, null, this.options);
    this.options.indicatorObj = null;
  },

  /**
   * Control methods
   */
  load: function() {
    if (!this.loaded) {
      this.autocompleter = new Mblst.LocalAutocompleteList(
        this.acElem,
        this.acContainer,
        this.acOptionArray, {
          activateElement: false,
          markupTemplateId: this.acMarkupTemplateId,
          onOptionSelected: this.onOptionSelected.bind(this),
          onOptionDeSelected: this.onOptionDeSelected.bind(this),
          onInputCleared: this.onInputCleared.bind(this),
          onShow: function(element, update){
            Element.hide(this.newContactDiv);
            Element.show(this.acContainerLabelElem);
            Element.show(update);
          }.bind(this)  
        }
      );
      this.loaded = true;
      this.autocompleter.activate();
    }
  },
  activate: function() {
    this.event('beforeLoad');
    this.load();
    this.event('afterLoad');    
    this.active = true;
  },
  deactivate: function() {
    if (this.state == 'showing') {
      this.event('beforeDismiss');
      this.dismiss(true);
      this.event('afterDismiss');
	}
    this.active = false;    
  },
  createBody: function() {
    
    this.meButton = document.createElement("input");
    this.meButton.type = "button";
    this.meButton.value = "This is me!";
    this.meButton.className = this.options.meButtonClassName;
    this.meButtonContainer = document.createElement("div");
    this.meButtonContainer.className = this.options.meButtonContainerClassName;
    this.meButtonContainer.appendChild(this.meButton);
    this.formElemsDiv.appendChild(this.meButtonContainer);    

    this.acLabelElem = document.createElement("div");
    this.acLabelElem.className = this.options.inputLabelClassName;
    this.acLabelElem.appendChild(document.createTextNode("or type this person's name:"));
    this.formElemsDiv.appendChild(this.acLabelElem);
    
    this.acElem = document.createElement("input");
    this.acElem.type = "text";
    this.acElem.name = "display_name";
    this.acElem.id = this.options.acElemId;
    this.formElemsDiv.appendChild(this.acElem);

    this.acContainerLabelElem = document.createElement("div");
    this.acContainerLabelElem.className = this.options.inputLabelClassName;
    this.acContainerLabelElem.appendChild(document.createTextNode("or click a friend below:"));
    this.formElemsDiv.appendChild(this.acContainerLabelElem);

    this.acContainer = document.createElement("div");
    this.acContainer.id = this.options.acContainerId;
    this.formElemsDiv.appendChild(this.acContainer);

    this.newContactInputLabel = document.createElement("label");
    this.newContactInputLabel.appendChild(document.createTextNode("cell number: "));
    this.newContactInput = document.createElement("input");
    this.newContactInput.type = "text";
    this.newContactInput.name = "mobile_phone_number";
    this.newContactInputContainer = document.createElement("div");
    this.newContactInputContainer.appendChild(this.newContactInputLabel);
    this.newContactInputContainer.appendChild(this.newContactInput);
    this.newContactNote = document.createElement("div");
    this.newContactNote.appendChild(document.createTextNode("NOTE: To protect our users, entering fake numbers will get you blocked!"));
    this.newContactNote.className = "fontHintText";
    
    this.newContactDiv = document.createElement("div");
    this.newContactDiv.className = this.options.newContactDivClassName;
    this.newContactDiv.appendChild(document.createTextNode("Enter this person's cell number to tag & add as a moblastic friend."));
    this.newContactDiv.appendChild(this.newContactInputContainer);
    this.newContactDiv.appendChild(this.newContactNote);
    Element.hide(this.newContactDiv);
    this.formElemsDiv.appendChild(this.newContactDiv);
    
    this.clickmelistener = this.onclickMe.bindAsEventListener(this);
    Event.observe(this.meButton, 'click', this.clickmelistener);  
  },  
  reset: function () {
    this.form.reset();
    this.clickedElemExternalId = 0;
    if (this.autocompleter) this.autocompleter.activate();
  },
  getInputValue: function () {
    return this.clickedElemExternalId;
  },  
  /**
   * AJAX Callbacks
   */
  onSuccess: function(transport) {
    this.hideProcessing();
    this.dismiss(true);
    this.event('afterProcess');

    var feedbackMsg = 'Your photo was tagged!';
    if (!this.options.supressFeedback) this.options.feedbackObj.showOk(feedbackMsg);
    Element.update(this.options.tagLinkContainerElem, transport.responseText);

   this.options.onSuccess.bind(this)(transport, this.triggerElem);
  },
  /**
   * Event handlers
   */
  onclickSubmit: function() {
    if (!this.getInputValue() && !this.valNewContactFormElems()) return false;
    this.event('beforeSubmit');
    this.submit();
    this.event('afterSubmit');
    return false;
  },
  onclickCancel: function() {
    this.event('beforeCancel');
    if (this.options.deactivateOnCancel) {
      this.deactivate();
    } else {
      this.cancel();
    }
    this.event('afterCancel');
    return false;
  },  
  onclickMe: function() {
    this.clickedElemExternalId = this.uid;
    this.event('beforeSubmit');
    this.submit();
    this.event('afterSubmit');
    return false;
  },  
  onOptionSelected: function() {
    this.clickedElemExternalId = this.autocompleter.getCurrentEntry().id.match(/^.*-([0-9]+)$/)[1];
    this.event('beforeSubmit');
    this.submit();
    this.event('afterSubmit');
    return false;
  },
  onOptionDeSelected: function() {
    Element.hide(this.acContainerLabelElem);
    Element.hide(this.acContainer);
    Element.show(this.newContactDiv);
  },
  onInputCleared: function() {
    Element.hide(this.newContactDiv);
    Element.show(this.acContainerLabelElem);
    Element.show(this.acContainer);
  },
  /**
   * Utility methods
   */  
  valNewContactFormElems: function() {
    var membername = $F(this.acElem);
    if (!membername) {
        mblstalert("Please enter a name for this person.", this.acElem);
        return false;
    }
    else if (membername.match(/ /)) {
        mblstalert("Please enter a name WITHOUT spaces.", this.acElem);
        return false;
    }
    else if (!membername.match(/^[a-zA-Z0-9_]+$/)) {
        mblstalert('Name can only contain letters, numbers, and "_".', this.acElem);
        return false;
    }
    else if (!valMobileNumberInput(this.newContactInput) ) {
      return false;
    }
    return true;
  }  
});

Mblst.FriendSelector = Class.create();
Object.extend(Object.extend(Mblst.FriendSelector.prototype, Mblst.Control.prototype), {
  /**
   * Constructor
   */
  initialize: function(container, uid, acOptionArray, acMarkupTemplateId, options) {
    this.uid = uid;
    this.clickedElemExternalId = 0;
    this.clickedElemName = null;
    this.acOptionArray = acOptionArray;
    this.acMarkupTemplateId = acMarkupTemplateId;
    this.active = false;
    this.loaded = false;

    this.options = Object.extend({
      containerElem: $(container),
      allowSelectSelf: true,
      
      friendParamName: 'uid',
      acElemId: 'friend_selector_ac_input',
      acContainerId: 'friend_selector_ac_container',
      acStatusDivId: 'friend_selector_ac_status',
        
      requestValidator: null,
      loadingText: "loading friends list...",
      processingText: "",

      containerElemId: 'friend_selector_container',
      formElemsClass: 'friendSelectorFormElems',
      meButtonClassName: 'friendSelectorMeButton',
      meButtonContainerClassName: 'friendSelectorMeButtonContainer',
      inputLabelClassName: 'friendSelectorInputLabel',
      noContactDivClassName: 'noContactContainer',
      selectedDisplayDivClassName: 'selectedDisplay',
      activeClassName: 'tagAdderActive',
      selectListElemClass: 'contactCheckboxInput',
      selectHoverClassName: 'contactCheckboxInputHover',

      afterShowInternal: function(control) {
        Field.scrollFreeActivate(control.autocompleter.element);
      }
      
    }, options || {});

    // Call parent constructor w/ these options
    Mblst.Control.prototype.initialize.call(this, this.options);
    
    this.create();
    this.activate();
  },

  /**
   * Control methods
   */
  load: function() {
    if (!this.loaded) {
      this.autocompleter = new Mblst.LocalAutocompleteList(
        this.acElem,
        this.acContainer,
        this.acOptionArray, {
          choicesClass: 'contacts',
          choiceClass: 'contact',
          activateElement: false,
          markupTemplateId: this.acMarkupTemplateId,
          onOptionSelected: this.onOptionSelected.bind(this),
          onOptionDeSelected: this.onOptionDeSelected.bind(this),
          onInputCleared: this.onInputCleared.bind(this),
          onShow: function(element, update){
            Element.hide(this.noContactDiv);
            Element.show(this.acContainerLabelElem);
            Element.show(update);
          }.bind(this)  
        }
      );
      this.loaded = true;
      this.autocompleter.activate();
    }
  },
  activate: function() {
    this.event('beforeLoad');
    this.load();
    this.event('afterLoad');    
    this.active = true;
  },
  deactivate: function() {
    if (this.state == 'showing') {
      this.event('beforeDismiss');
      this.dismiss(true);
      this.event('afterDismiss');
	}
    this.active = false;    
  },
  create: function() {

    this.formElemsDiv = document.createElement("div");
    this.formElemsDiv.className = this.options.formElemsClass;
    
    this.meButton = document.createElement("input");
    this.meButton.type = "button";
    this.meButton.value = "Your Profile";
    this.meButton.className = this.options.meButtonClassName;
    this.meButtonContainer = document.createElement("div");
    this.meButtonContainer.className = this.options.meButtonContainerClassName;
    this.meButtonContainer.appendChild(this.meButton);
    this.formElemsDiv.appendChild(this.meButtonContainer);
    if (!this.options.allowSelectSelf) Element.hide(this.meButtonContainer);

    this.acLabelElem = document.createElement("div");
    var acLabelText = (this.options.allowSelectSelf) ? "OR, type a friend's name:" : "Type a friend's name:";
    this.acLabelElem.className = this.options.inputLabelClassName;
    this.acLabelElem.appendChild(document.createTextNode(acLabelText));
    this.formElemsDiv.appendChild(this.acLabelElem);
    
    this.acElem = document.createElement("input");
    this.acElem.type = "text";
    this.acElem.name = "display_name";
    this.acElem.id = this.options.acElemId;
    this.formElemsDiv.appendChild(this.acElem);

    this.acContainerLabelElem = document.createElement("div");
    this.acContainerLabelElem.className = this.options.inputLabelClassName;
    this.acContainerLabelElem.appendChild(document.createTextNode("OR, click a friend below:"));
    this.formElemsDiv.appendChild(this.acContainerLabelElem);

    this.acContainer = document.createElement("div");
    this.acContainer.id = this.options.acContainerId;
    this.formElemsDiv.appendChild(this.acContainer);
    
    this.noContactDiv = document.createElement("div");
    this.noContactDiv.className = this.options.noContactDivClassName;
    this.noContactDiv.appendChild(document.createTextNode("No matching friends found."));

    Element.hide(this.noContactDiv);
    this.formElemsDiv.appendChild(this.noContactDiv);

    this.selectedInput = document.createElement("input");
    this.selectedInput.type = "text";
    this.selectedInput.name = "selected_friend";
    this.selectedInputDisplay = document.createElement("div");
    this.selectedInputDisplay.appendChild(this.selectedInput);
    this.selectedTip = document.createElement("div");
    this.selectedTip.appendChild(document.createTextNode("click to change"));
    this.selectedTip.className = "fontHintText";
    
    this.selectedDisplayDiv = document.createElement("div");
    this.selectedDisplayDiv.className = this.options.selectedDisplayDivClassName;
    this.selectedDisplayDiv.appendChild(this.selectedInputDisplay);
    this.selectedDisplayDiv.appendChild(this.selectedTip);
    Element.hide(this.selectedDisplayDiv);
    this.formElemsDiv.appendChild(this.selectedDisplayDiv);
    
    this.containerElem.appendChild(this.formElemsDiv);
    
    this.clickmelistener = this.onclickMe.bindAsEventListener(this);
    this.clickselectedlistener = this.onclickSelected.bindAsEventListener(this);
    Event.observe(this.meButton, 'click', this.clickmelistener);
    Event.observe(this.selectedInput, 'click', this.clickselectedlistener);
    Event.observe(this.selectedTip, 'click', this.clickselectedlistener);
  },  
  reset: function () {
    Field.clear(this.acElem);
    this.clickedElemExternalId = 0;
    this.clickedElemName = null;
    if (this.autocompleter) this.autocompleter.activate();
  },
  select: function() {
    this.selectedInput.value = this.clickedElemName;
    Element.hide(this.meButtonContainer);
    Element.hide(this.acLabelElem);
    Element.hide(this.acElem);
    Element.hide(this.acContainerLabelElem);
    Element.hide(this.acContainer);
    Element.hide(this.noContactDiv);
    Element.show(this.selectedDisplayDiv);
  },
  deselect: function() {
    this.selectedInput.value = this.clickedElemName;
    this.reset();
    Element.hide(this.selectedDisplayDiv);
    Element.show(this.meButtonContainer);
    Element.show(this.acLabelElem);
    Element.show(this.acElem);
    Element.show(this.acContainerLabelElem);
    Element.show(this.acContainer);
  },  
  getInputValue: function () {
    return this.clickedElemExternalId || '';
  },
  getInputText: function() {
    return this.acElem.value;
  },
  serialize: function() {
    return  encodeURIComponent(this.options.friendParamName) + '=' +
            encodeURIComponent(this.getInputValue());
  },  
  validateSelection: function() {
    return (this.getInputValue()) ? true : false;
  },
  /**
   * Event handlers
   */
  onclickMe: function() {
    this.clickedElemExternalId = this.uid;
    this.clickedElemName = "your profile";
    this.select();
    return false;
  },
  onclickSelected: function() {
    this.deselect();
    return false;
  },    
  onOptionSelected: function() {
    this.clickedElemExternalId = this.autocompleter.getCurrentEntry().id.match(/^.*-([0-9]+)$/)[1];
    this.clickedElemName = this.acElem.value;
    this.select();
    return false;
  },
  onOptionDeSelected: function() {
    Element.hide(this.acContainerLabelElem);
    Element.hide(this.acContainer);
    Element.show(this.noContactDiv);
  },
  onInputCleared: function() {
    Element.hide(this.noContactDiv);
    Element.show(this.acContainerLabelElem);
    Element.show(this.acContainer);
  }
  /**
   * Utility methods
   */  
});


Mblst.Select = Class.create();
Object.extend(Object.extend(Mblst.Select.prototype, Mblst.PositionedControl.prototype), {
  /**
   * Constructor
   */
  initialize: function(triggerElem, optionData, url, options) {
    this.triggerElem = $(triggerElem);
    this.optionData = optionData;
    this.inputValue = null;
    this.selected = null;

    this.options = Object.extend({
      processURL: url,
      position: true,
      supressFeedback: false,
      dismissErrors: true,
      hideDelay: 300,

      containerClassName: 'ajaxSelectContainer',
      triggerImgClassName: 'ajaxSelectTriggerImg',
      indicatorClassName: 'ajaxSelectIndicator',
      optionsClassName: 'ajaxSelectOptions',
      optionClassName: 'ajaxSelectOption',
      optionHoverClassName: 'hover',
      selectedClassName: 'selected',
      processingClassName: 'processing',

      afterShow: function(control) {
        document.onmousedown = control.onmousedownListener;
        //Event.observe(document, 'mousedown', this.onmousedownListener);
      },
      beforeHide: function(control) {
        document.onmousedown = function(){};
      },

      positionOffsets: Mblst.Position.bottomLeftOnBottomLeft,
      onShow: function(referenceElem, containerElem, offsets, useEffects, scrollViewportY, adjustPositionToViewportY){
        if(!containerElem.style.position || containerElem.style.position=='absolute') {
          containerElem.style.position = 'absolute';
          Position.cloned(referenceElem, containerElem, {setHeight: false, setWidth: true, offsetLeft: offsets.offsetLeft, offsetTop: offsets.offsetTop, scrollViewportY: false, adjustPositionToViewportX: false, adjustPositionToViewportY: false});
          containerElem.style.zIndex = 1000;
        }
        Element.show(containerElem);
      },

      onSuccess: function(transport, triggerElem) {},
      onFetchSuccess: function(transport, triggerElem) {},
      onFailure: function(transport) {},
      callback: Mblst.requestSerialize.bind(this),
      mblstReqOptions: {},
      ajaxOptions: {method: 'put'}
    }, options || {});

    // Only pass an injection elem if we have a jsContainer
    if(this.options.jsContainer) this.options.injectionElem = this.triggerElem;

    // Call parent constructor w/ these options
    Mblst.PositionedControl.prototype.initialize.call(this, this.triggerElem, this.options);

    this.onmousedownListener = this.mousedownFired.bindAsEventListener(this);

    if (!this.options.feedbackObj && !this.options.supressFeedback) {
      this.options.feedbackObj = new Mblst.FeedbackElem(
                                  this.triggerElem, {
                                  positionOffsets: Mblst.Position.topLeftOnTopRight,
                                  refOffsetTop: -5,
                                  refOffsetLeft: 30,
                                  dismissDelay: 2.0,
                                  injectionElem: "js_injection_container"});
    }
  },
  /**
   * Control methods
   */
  create: function() {
    this.controlOptions = document.createElement("ul");
    this.controlOptions.className = this.options.optionsClassName;

    this.onclickSelectItemListener = this.onclickSelectElem.bindAsEventListener(this);
    this.mouseoverSelectItemListener = this.onEnterSelectElemHover.bindAsEventListener(this);
    this.mouseoutSelectItemListener = this.onLeaveSelectElemHover.bindAsEventListener(this);
    for (var i=0; i < this.optionData.length ; i++) {
      var optionElem = document.createElement("li");
      optionElem.innerHTML = this.optionData[i].text;
      optionElem.value = this.optionData[i].value;
      optionElem.className = this.options.optionClassName;
      optionElem = this.controlOptions.appendChild(optionElem);
      //Element.forceHandCursor(optionElem);
      if (this.optionData[i].selected) this.selected = this.optionData[i].value;
      if (this.optionData[i].img) {
        if (i == 0) Element.addClassName(this.triggerElem, this.options.triggerImgClassName);
        optionElem.style.backgroundImage = 'url("'+this.optionData[i].img+'")';
      }
      Event.observe(optionElem, 'click', this.onclickSelectItemListener);
      Event.observe(optionElem, 'mouseover', this.mouseoverSelectItemListener);
      Event.observe(optionElem, 'mouseout', this.mouseoutSelectItemListener);
    }
    this.containerElem.appendChild(this.controlOptions);
    this.updateSelected();
    this.addContainerListeners();
    
    this.indicatorElem = document.createElement("span");
    this.indicatorElem.className = this.options.indicatorClassName;
    this.triggerElem.appendChild(this.indicatorElem);    
  },
  submit: function() {
    this.showProcessing();
    this.event('beforeProcess');
    this.process();
    this.state = 'processing';
    return false;
  },
  process: function() {
    new Ajax.Request(
      this.options.processURL,
      Object.extend({
        asynchronous: true,
        parameters: this.options.callback(this.form, this.getInputValue()),
        onSuccess: this.onSuccess.bind(this),
        onFailure: this.onFailure.bind(this)
      }, this.options.ajaxOptions)
    );
  },
  updateSelected: function() {
    if (this.selected == null) return false;
    
    options = Element.Class.childrenWith(this.controlOptions, this.options.optionClassName);
    for (var i=0; i < options.length ; i++) {
      if(options[i].value == this.selected) {
        var markup = "";
        if (options[i].style.backgroundImage){
          markup = '<img src="' + options[i].style.backgroundImage.replace(/^.*url\(["']?([^)"']*)["']?\).*$/, '$1') + '" alt="">';
        }
        this.triggerElem.innerHTML = markup + options[i].innerHTML;
        Element.addClassName(options[i], this.options.selectedClassName);
        break;
      }
    }    
  },
  addContainerListeners: function() {
    if(this.options.jsContainer) {
      this.mouseoutJSContainerListener = this.onLeaveJSContainerHover.bindAsEventListener(this);
      this.mouseoverJSContainerListener = this.onEnterJSContainerHover.bindAsEventListener(this);
      Event.observe(this.options.jsContainer, 'mouseout', this.mouseoutJSContainerListener);
      Event.observe(this.options.jsContainer, 'mouseover', this.mouseoverJSContainerListener);
    }
    return false;
  },  
  /**
   * AJAX Callbacks
   */
  onSuccess: function(transport) {
    this.hideProcessing();
    this.dismiss();
    this.event('afterProcess');

    this.updateSelected();
    var feedbackMsg = (transport.responseText) ? transport.responseText : 'Success!';
    if (!this.options.supressFeedback) this.options.feedbackObj.showOk(feedbackMsg);

    // total hack, should not have this dependency
    if (quickFriendsObj) quickFriendsObj.update();

    this.options.onSuccess.bind(this)(transport, this.triggerElem);
  },
  onFailure: function(transport) {
    this.hideProcessing();
    this.dismiss();
    this.event('failure');

    this.selected = null;
    var feedbackMsg = (transport.responseText) ? transport.responseText.stripTags() : 'We could not process your request. Please try again later.';
    if (!this.options.supressFeedback) this.options.feedbackObj.showError(feedbackMsg, this.options.dismissErrors);

    this.options.onFailure(transport);
    return false;
  },
  reset: function () {
    this.inputValue = null;
  },
  /**
   * Event handlers
   */
  onclickSelectElem: function(evt) {
    var selElem = Event.element(evt);
    var i=0;
    while ( i < 3 && !Element.hasClassName(selElem, this.options.optionClassName)) {
      selElem = selElem.parentNode; i++;
    }
    if(Element.hasClassName(selElem, this.options.selectedClassName)) return false; // do not process clicks on the selected elem
    this.inputValue = (attr = $A(selElem.attributes).find(function(a){return (a.nodeName == "value");})) ? attr.value : null;
    this.selected = this.inputValue;
    this.submit();
    this.dismiss();
    return false;
  },
  onEnterSelectElemHover: function(evt) {
    var selElem = Event.element(evt);
    var i=0;
    while ( i < 3 && !Element.hasClassName(selElem, this.options.optionClassName)) {
      selElem = selElem.parentNode; i++;
    }
    Element.addClassName(selElem, this.options.optionHoverClassName)
  },
  onLeaveSelectElemHover: function(evt) {
    var selElem = Event.element(evt);
    var i=0;
    while ( i < 3 && !Element.hasClassName(selElem, this.options.optionClassName)) {
      selElem = selElem.parentNode; i++;
    }
    Element.removeClassName(selElem, this.options.optionHoverClassName);
  },
  onLeaveJSContainerHover: function(evt) {
    if (!this.hoveredByThis) return; // handle once
	this.hideProcId = setTimeout(this.dismiss.bind(this), this.options.hideDelay);
    this.hoveredByThis = false;    
    //if (Element.hasClassName(Event.element(evt), Mblst.Container.className)) this.dismiss();
    return false;
  },
  onEnterJSContainerHover: function(evt) {
    if (this.hideProcId) clearTimeout(this.hideProcId);
    this.hoveredByThis = true;
  },
  mousedownFired: function(evt) {
	if (Event.element(evt) == $(this.options.triggerElem)){
	  document.onmousedown = function(){};
	}else if(Event.element(evt).tagName == 'A'){
      return;
    }else{
		this.dismiss();
	}
  },  
  /**
   * Utility functions
   */
  showProcessing: function() {
    Element.addClassName(this.triggerElem, this.options.processingClassName);
  },
  hideProcessing: function() {
    Element.removeClassName(this.triggerElem, this.options.processingClassName);
  },
  getInputValue: function () {
    return this.inputValue;
  }  
});

Mblst.SelectElemCtor = Class.create();
Mblst.SelectElemCtor.prototype = {
  /**
   * Constructor
   */
  initialize: function(selectElem, options) {
    this.selectElem = $(selectElem);
    this.url = "#";
    this.optionData = [];

    this.options = Object.extend({
      triggerClassName: 'ajaxSelectTrigger',
      mblstReqOptions: {valueName: this.selectElem.name}
    }, options || {});

    this.url = (attr = $A(this.selectElem.attributes).find(function(a){return (a.nodeName == "url");})) ? this.parseURL(attr.value) : null;
    this.options.jsContainer = (attr = $A(this.selectElem.attributes).find(function(a){return (a.nodeName == "container");})) ? attr.value : null;
    var optionElems = this.selectElem.getElementsByTagName('OPTION');
    for (var i=0; i < optionElems.length ; i++) {
      option = {};
      option.selected = (optionElems[i].selected) ? 1 : 0;
      option.text = optionElems[i].innerHTML;
      option.value = optionElems[i].value;
      option.img = (attr = $A(optionElems[i].attributes).find(function(a){return (a.nodeName == "src");})) ? attr.value : null;
      this.optionData.push(option);
    }
    new Insertion.After(this.selectElem,
     '<a href="javascript: void(0)" id="select_trigger-' + Mblst.Control.instanceNonce + '"></a>');
    triggerElem = $('select_trigger-' + Mblst.Control.instanceNonce);
    Element.addClassName(triggerElem, this.options.triggerClassName);
    Element.hide(this.selectElem);

    new Mblst.Select(triggerElem, this.optionData, this.url, this.options);                  
  },
  /**
   * Helper methods
   */
  parseURL: function(urlString) {       
    if (urlString.indexOf("javascript:") == 0) {
      try {
        return eval(urlString.split(":")[1]);
      } catch (e) {}
      return "#";
    } else {
      return urlString;
    }
  } 
};

Mblst.ToggleSelectElemCtor = Class.create();
Object.extend(Object.extend(Mblst.ToggleSelectElemCtor.prototype, Mblst.SelectElemCtor.prototype), {
  /**
   * Constructor
   */
  initialize: function(selectElem, options) {

    this.options = Object.extend({
      beforeProcessInternal: function(control) {
        if(control.selected == 1) {
          control.options.ajaxOptions.method = 'post';
        } else {
          control.options.ajaxOptions.method = 'delete';
        }
      }
    }, options || {});

    // Call parent constructor w/ these options
    Mblst.SelectElemCtor.prototype.initialize.call(this, selectElem, this.options);
  }
});

Mblst.TicketPoller = Class.create();
Mblst.TicketPoller.prototype = {
  /**
   * Constructor
   */
  initialize: function(tickets, options) {
    this.pending = tickets;
    this.pendingStr = tickets.join(',');
    this.failed = [].toJSON();
    this.succeeded = [].toJSON();
    this.start_number = tickets.length;
    this.checks = 0;
    this.stash = null;

    this.options = Object.extend({
      initialDelay: 500,
      intervalScaleFactor: 1000,
      onPollSuccess: function(succeeded, failed, pending) {},
      onComplete: function() {},
      onFailure: function() {}
    }, options || {});
    
    this.tim = this.options.initialDelay;
    this.intervalScaleFactor = this.options.intervalScaleFactor;    

    /**
     * Start polling
     */
    this.startPolling();        
  },

  /**
   * Control Methods
   */
  startPolling: function() {
    setTimeout(this.poll.bind(this), 500);
  },
  poll: function() {
    this.checks++;
    cacheBuster = 'rn='+encodeURIComponent(Math.random());
    this.tim = Math.pow(2, Math.round(this.checks/2));
    if (this.tim > 16) this.tim = 16;
    this.tim = this.tim*this.intervalScaleFactor;
    new Ajax.Request(
      Mblst.Url.tickets(this.succeeded, this.failed, this.pending), {
        asynchronous: true,
        parameters: cacheBuster,
        onSuccess: this.onPollSuccess.bind(this),
        onFailure: this.onPollFailure.bind(this),
        method: 'get'
      }
    );
  },
  /**
   * AJAX Callbacks
   */
  onPollSuccess: function(transport, json)
  {
    var A = transport.responseText.split('|');

    if (A.length != 3) {
        setTimeout(this.poll.bind(this), this.tim);
        return;
    }
    this.pendingStr = A[0];
    this.pending = this.pendingStr.split(',');
    this.failed = A[2];
    this.succeeded = A[1];

    if (this.pending.length == 0 || this.pending[0] == '') {
      try {
        this.stash = eval(this.succeeded);
      } catch (e) {}      
      
      this.options.onComplete(this.stash);
    } else {
      this.options.onPollSuccess(this.succeeded, this.failed, this.pending);
      setTimeout(this.poll.bind(this), this.tim);
    }
  },
  onPollFailure: function(transport) {
    this.options.onFailure();
    return false;
  },  
  /**
   * Utility methods
   */
  dispose: function() {}
};

Mblst.TempPhotoUploadInput = Class.create();
Mblst.TempPhotoUploadInput.prototype = {
  /**
   * Constructor
   */
  initialize: function(options) {
    this.ticketId = 0;

    this.options = Object.extend({
      
    }, options || {});
       
    // preload indicator image
    preloadImage('/images/indicator200x18.gif');
  },

  /**
   * Control methods
   */
  getTicketId: function() {
    return this.ticketId;
  },
  setTicketId: function(tid) {
    this.ticketId = tid;
    if (this.ticketId) ControlEvent.notify('submit', this);
  }  
};  
 
 
/*--------------------------------------------------------------------------*/

Mblst.Draggable = Class.create();
Object.extend(Object.extend(Mblst.Draggable.prototype, Draggable.prototype), {
  /**
   * Constructor
   */
  initialize: function(element, options) {

    this.options = Object.extend({
      proportion: null,
      escapeScrollDiv: false,
      abortDragCondition: function(draggable) {return false;}
    }, options || {});

    // Ghosting is required for scrollDivEscapable
    if (this.options.escapeScrollDiv) {
      this.options.escapeScrollDiv = $(this.options.escapeScrollDiv);
      this.options.reverteffect = function(element, top_offset, left_offset) {
        Element.show(element);
      };
      this.options.ghosting = true;
    }

    // Call parent constructor w/ these options
    Draggable.prototype.initialize.call(this, element, this.options);
  },
  initDrag: function(event) {
    if(typeof Draggable._dragging[this.element] != 'undefined' &&
      Draggable._dragging[this.element]) return;
    
    // add abort condition func as an option
    if(this.options.abortDragCondition && this.options.abortDragCondition(this)) return;
    
    if(Event.isLeftClick(event)) {    
      // abort on form elements, fixes a Firefox issue
      var src = Event.element(event);
      if((tag_name = src.tagName.toUpperCase()) && (
        tag_name=='INPUT' ||
        tag_name=='SELECT' ||
        tag_name=='OPTION' ||
        tag_name=='BUTTON' ||
        tag_name=='TEXTAREA')) return;
        
      var pointer = [Event.pointerX(event), Event.pointerY(event)];
      var pos = Position.cumulativeOffset(this.element);      
      if (this.options.escapeScrollDiv) {
        pos[0] -= this.options.escapeScrollDiv.scrollLeft;
        pos[1] -= this.options.escapeScrollDiv.scrollTop;
      } 
      this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
      
      Draggables.activate(this);
      Event.stop(event);
    }
  },
  startDrag: function(event) {
    Draggable.prototype.startDrag.call(this, event);
    if(this.options.escapeScrollDiv) {
      document.body.appendChild(this.element);           
    }        
  },
  finishDrag: function(event, success) {
    if(this.options.escapeScrollDiv) {
      Element.hide(this.element);
      this._clone.parentNode.insertBefore(this.element, this._clone);
      Position.clone(this._clone, this.element);
    }    
    Draggable.prototype.finishDrag.call(this, event, success);
    // Fix ie: Element was getting extra left:2px before relativize.
    if(this.options.escapeScrollDiv) {
      this.element.style.left  = 0;
    }
  },  
  draw: function(point) {
    var pos = Position.cumulativeOffset(this.element);
    if(this.options.ghosting) {
      var r   = Position.realOffset(this.element);
      pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY;
    }
    
    var d = this.currentDelta();
    pos[0] -= d[0]; pos[1] -= d[1];
    
    if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) {
      pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
      pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
    }
    
    var p = [0,1].map(function(i){ 
      return (point[i]-pos[i]-this.offset[i]) 
    }.bind(this));
    
    if(this.options.snap) {
      if(typeof this.options.snap == 'function') {
        p = this.options.snap(p[0],p[1],this);
      } else {
      if(this.options.snap instanceof Array) {
        p = p.map( function(v, i) {
          return Math.round(v/this.options.snap[i])*this.options.snap[i] }.bind(this))
      } else {
        p = p.map( function(v) {
          return Math.round(v/this.options.snap)*this.options.snap }.bind(this))
      }
    }}
    
    var style = this.element.style;
    if((this.options.constraint=='proportional') && this.options.proportion) {
      var proportion = (p[1]) ? (p[0]/p[1]) : 0;
      if(proportion >= this.options.proportion) { // too wide, adjust p[0]
        style.top  = p[1] + "px";
        style.left = (this.options.proportion*p[1]) + "px";
      } else { // too skinny, adjust p[1]
        style.left = p[0] + "px";
        style.top = (p[0]/this.options.proportion) + "px";        
      }
    } else {
      if((!this.options.constraint) || (this.options.constraint=='horizontal'))
        style.left = p[0] + "px";
      if((!this.options.constraint) || (this.options.constraint=='vertical'))
        style.top  = p[1] + "px";
    }
    
    if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
  }  
});

Mblst.Sizeable = Class.create();
Mblst.Sizeable.prototype = {
  /**
   * Constructor
   */
  initialize: function(element, options) {
    this.element = $(element);
    this.origW = 0;
    this.origH = 0;

    this.options = Object.extend({
      moveHandle: false,
      moveHandleClassName: 'moveHandle',
      moveChange: function(draggable) {},
      moveStarteffect: function(element) {
        Element.addClassName(element, 'moving');
        element._opacity = Element.getOpacity(element);
        Draggable._dragging[element] = true;
        new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7});
      },      
      moveEndeffect: function(element) {
        var toOpacity = typeof element._opacity == 'number' ? element._opacity : 1.0;
        new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, 
          queue: {scope:'_draggable', position:'end'},
          afterFinish: function(){ 
            Draggable._dragging[element] = false 
          }
        });
        Element.removeClassName(element, 'moving');
      },            
      
      resizeHandle: true, 
      resizeConstraint: 'proportional',     
      resizeChange: function(draggable) {
        draggable.element.parentNode.style.width = parseInt(Element.getStyle(draggable.element,'left') || '0')+'px';
        if(draggable.options.constraint != 'horizontal') draggable.element.parentNode.style.height = parseInt(Element.getStyle(draggable.element,'top') || '0')+'px';
      },
      resizeStarteffect: function(element) {
        Draggable._dragging[element] = true;
        Element.addClassName(element.parentNode, 'resizing');
      },      
      resizeEndeffect: function(element) {
        Draggable._dragging[element] = false;
        Element.removeClassName(element.parentNode, 'resizing');        
      }      
    }, options || {});

    // Assign resize handle class name
    if (this.options.resizeHandle && this.options.resizeConstraint == 'horizontal') {
      this.resizeHandleClassName = 'resizeHandleHorizontal';
    } else if (this.options.resizeHandle) {
      this.resizeHandleClassName = 'resizeHandle';
    } else {
      this.resizeHandleClassName = null;
    }
    

    // build options for move draggable
    var options_for_move_draggable = {
      revert:      false,
      zindex:      0,
      scroll:      this.options.scroll,
      scrollSpeed: this.options.scrollSpeed,
      scrollSensitivity: this.options.scrollSensitivity,
      delay:       this.options.delay,
      ghosting:    this.options.ghosting,
      constraint:  this.options.constraint,
      handle:      false ,
      starteffect: this.options.moveStarteffect,
      endeffect:   this.options.moveEndeffect,      
      change:      this.options.moveChange,
      abortDragCondition: this.options.abortDragCondition};
      
    // build options for resize draggable
    var options_for_resize_draggable = {
      revert:      false,
      scroll:      this.options.scroll,
      scrollSpeed: this.options.scrollSpeed,
      scrollSensitivity: this.options.scrollSensitivity,
      delay:       this.options.delay,
      ghosting:    this.options.ghosting,
      constraint:  this.options.resizeConstraint,
      handle:      false,
      starteffect: this.options.resizeStarteffect,
      endeffect:   this.options.resizeEndeffect,
      change:      this.options.resizeChange,
      abortDragCondition: this.options.abortDragCondition};      

    // add move handle elem
    if (this.options.moveHandle) {
      this.moveHandleElem = document.createElement("div");
      this.moveHandleElem.style.position = 'absolute';
      if (this.options.moveHandleClassName) Element.addClassName(this.moveHandleElem, this.options.moveHandleClassName);
      this.element.appendChild(this.moveHandleElem);
    }
    
    // add resize elem 
    this.resizeElem = document.createElement("div");
    this.resizeElem.style.position = 'absolute';
    if (this.resizeHandleClassName) Element.addClassName(this.resizeElem, this.resizeHandleClassName);
    this.element.appendChild(this.resizeElem);

    // create draggables
    this.elementDraggable = new Mblst.Draggable(this.element, options_for_move_draggable);
    this.resizeElemDraggable = new Mblst.Draggable(this.resizeElem, options_for_resize_draggable);
    
    // init resize elem pos
    var origW = parseInt(Element.getStyle(this.element,'width') || '0');   
    this.resizeElem.style.left = origW+'px';
    this.options.resizeChange(this.resizeElemDraggable);
  },

  /**
   * Control methods
   */
  abortMove: function(event) {
    this.elementDraggable.finishDrag(event, false);
    Event.stop(event);
  },
  initResizeHandle: function(canResize, resizeProportional) {
    if(canResize) {
      var origW = parseInt(Element.getStyle(this.element,'width') || '0');
      var origH = parseInt(Element.getStyle(this.element,'height') || '0');
      
      if(resizeProportional) {
        this.resizeElemDraggable.options.proportion = (origH) ? origW/origH : 0;
        this.resizeElemDraggable.options.constraint = 'proportional';
      } else {
        this.resizeElemDraggable.options.constraint = '';
      }
      
      this.resizeElem.style.left = origW+'px';
      this.resizeElem.style.top = origH+'px';
      Element.show(this.resizeElem);
    } else {
      Element.hide(this.resizeElem);
    }
  },
  /**
   * Helpers
   */  
  dispose: function() {
    this.resizeElemDraggable.destroy();
    this.elementDraggable.destroy();
  }    
};




// init MBLST elements

function initMblstToggleSelectElems() {
  var elems = $("page").getElementsByClassName('ajaxToggleSelectElem');
  for (var i=0; i < elems.length ; i++) new Mblst.ToggleSelectElemCtor(elems[i]);  
}

addMarkupTailEvent(initMblstToggleSelectElems);
//addDOMLoadEvent(initMblstToggleSelectElems);