// /usr/local/idealist/htdocs/javascript/jquery/jquery.facebox.js
/*
 * Facebox (for jQuery)
 * version: 1.0 (12/19/2007)
 * @requires jQuery v1.2 or later
 *
 * Examples at http://famspam.com/facebox/
 *
 * Licensed under the MIT:
 *   http://www.opensource.org/licenses/mit-license.php
 *
 * Copyright 2007 Chris Wanstrath [ chris@ozmm.org ]
 *
 * Usage:
 *  
 *  jQuery(document).ready(function() {
 *    jQuery('a[@rel*=facebox]').facebox() 
 *  })
 *
 *  <a href="#terms" rel="facebox">Terms</a>
 *    Loads the #terms div in the box
 *
 *  <a href="terms.html" rel="facebox">Terms</a>
 *    Loads the terms.html page in the box
 *
 *  <a href="terms.png" rel="facebox">Terms</a>
 *    Loads the terms.png image in the box
 *
 *
 *  You can also use it programmatically:
 * 
 *    jQuery.facebox('some html')
 *
 *  This will open a facebox with "some html" as the content.
 *    
 *    jQuery.facebox(function() { ajaxes })
 *
 *  This will show a loading screen before the passed function is called,
 *  allowing for a better ajax experience.
 *
 */
(function($) {
  $.facebox = function(data) {
    $.facebox.init()
    $.facebox.loading()
    $.isFunction(data) ? data.call() : $.facebox.reveal(data)
  }

  $.facebox.settings = {
    loading_image : '/images/facebox/loading.gif',
    close_image   : '/images/facebox/closelabel.gif',
    image_types   : [ 'png', 'jpg', 'jpeg', 'gif' ],
    facebox_html  : '\
  <div id="facebox" style="display:none;"> \
    <div class="popup"> \
      <table> \
        <tbody> \
          <tr> \
            <td class="tl"/><td class="b"/><td class="tr"/> \
          </tr> \
          <tr> \
            <td class="b"/> \
            <td class="body"> \
              <div class="content"> \
              </div> \
              <div class="footer"> \
                <a href="#" class="close"> \
                  <img src="/images/blank.gif" title="close" class="close_image" /> \
                </a> \
              </div> \
            </td> \
            <td class="b"/> \
          </tr> \
          <tr> \
            <td class="bl"/><td class="b"/><td class="br"/> \
          </tr> \
        </tbody> \
      </table> \
    </div> \
  </div>'
  }

  $.facebox.loading = function() {
    if ($('#facebox .loading').length == 1) return true

    $('#facebox .content').empty()
    $('#facebox .body').children().hide().end().
      append('<div class="loading"><img src="'+$.facebox.settings.loading_image+'"/></div>')

    var pageScroll = $.facebox.getPageScroll()
    $('#facebox').css({
      top:	pageScroll[1] + ($.facebox.getPageHeight() / 10),
      left:	pageScroll[0]
    }).show()

    $(document).bind('keydown.facebox', function(e) {
      if (e.keyCode == 27) $.facebox.close()
    })
  }

  $.facebox.reveal = function(data, klass) {
    if (klass) $('#facebox .content').addClass(klass)
    $('#facebox .content').append(data)
    $('#facebox .loading').remove()
    $('#facebox .body').children().fadeIn('normal')
  }

  $.facebox.close = function() {
    $(document).unbind('keydown.facebox')
    $('#facebox').fadeOut(function() {
      $('#facebox .content').removeClass().addClass('content')
    })
    return false
  }

  $.fn.facebox = function() {
    $.facebox.init()

    var image_types = $.facebox.settings.image_types.join('|')
    image_types = new RegExp('\.' + image_types + '$', 'i')

    function click_handler() {
      $.facebox.loading(true)

      // support for rel="facebox[.inline_popup]" syntax, to add a class
      var klass = this.rel.match(/facebox\[\.(\w+)\]/)
      if (klass) klass = klass[1]

      // div
      if (this.href.match(/#/)) {
        var url    = window.location.href.split('#')[0]
        var target = this.href.replace(url,'')
        $.facebox.reveal($(target).clone().show(), klass)

      // image
      } else if (this.href.match(image_types)) {
        var image = new Image()
        image.onload = function() {
          $.facebox.reveal('<div class="image"><img src="' + image.src + '" /></div>', klass)
        }
        image.src = this.href

      // ajax
      } else {
        $.get(this.href, function(data) { $.facebox.reveal(data, klass) })
      }

      return false
    }

    this.click(click_handler)
    return this
  }

  $.facebox.init = function() {
    if ($.facebox.settings.inited) {
      return true
    } else {
      $.facebox.settings.inited = true
    }

    $('body').append($.facebox.settings.facebox_html)

    var preload = [ new Image(), new Image() ]
    preload[0].src = $.facebox.settings.close_image
    preload[1].src = $.facebox.settings.loading_image

    $('#facebox').find('.b:first, .bl, .br, .tl, .tr').each(function() {
      preload.push(new Image())
      preload.slice(-1).src = $(this).css('background-image').replace(/url\((.+)\)/, '$1')
    })

    $('#facebox .close').click($.facebox.close)
    $('#facebox .close_image').attr('src', $.facebox.settings.close_image)
  }

  // getPageScroll() by quirksmode.com
  $.facebox.getPageScroll = function() {
    var xScroll, yScroll;
    if (self.pageYOffset) {
      yScroll = self.pageYOffset;
      xScroll = self.pageXOffset;
    } else if (document.documentElement && document.documentElement.scrollTop) {	 // Explorer 6 Strict
      yScroll = document.documentElement.scrollTop;
      xScroll = document.documentElement.scrollLeft;
    } else if (document.body) {// all other Explorers
      yScroll = document.body.scrollTop;
      xScroll = document.body.scrollLeft;	
    }
    return new Array(xScroll,yScroll) 
  }

  // adapter from getPageSize() by quirksmode.com
  $.facebox.getPageHeight = function() {
    var windowHeight
    if (self.innerHeight) {	// all except Explorer
      windowHeight = self.innerHeight;
    } else if (document.documentElement && document.documentElement.clientHeight) { // Explorer 6 Strict Mode
      windowHeight = document.documentElement.clientHeight;
    } else if (document.body) { // other Explorers
      windowHeight = document.body.clientHeight;
    }	
    return windowHeight
  }
})(jQuery);
// /usr/local/idealist/htdocs/javascript/jquery/ui.tabs.js
/*
 * Tabs 3 - New Wave Tabs
 *
 * Copyright (c) 2007 Klaus Hartl (stilbuero.de)
 * Dual licensed under the MIT (MIT-LICENSE.txt)
 * and GPL (GPL-LICENSE.txt) licenses.
 */
(function($){$.ui=$.ui||{};$.fn.tabs=function(initial,options){if(initial&&initial.constructor==Object){options=initial;initial=null;}options=options||{};initial=initial&&initial.constructor==Number&&--initial||0;return this.each(function(){new $.ui.tabs(this,$.extend(options,{initial:initial}));});};$.each(['Add','Remove','Enable','Disable','Click','Load','Href'],function(i,method){$.fn['tabs'+method]=function(){var args=arguments;return this.each(function(){var instance=$.ui.tabs.getInstance(this);instance[method.toLowerCase()].apply(instance,args);});};});$.fn.tabsSelected=function(){var selected=-1;if(this[0]){var instance=$.ui.tabs.getInstance(this[0]),$lis=$('li',this);selected=$lis.index($lis.filter('.'+instance.options.selectedClass)[0]);}return selected>=0?++selected:-1;};$.ui.tabs=function(el,options){this.source=el;this.options=$.extend({initial:0,event:'click',disabled:[],cookie:null,unselected:false,unselect:options.unselected?true:false,spinner:'Loading&#8230;',cache:false,idPrefix:'ui-tabs-',ajaxOptions:{},fxSpeed:'normal',add:function(){},remove:function(){},enable:function(){},disable:function(){},click:function(){},hide:function(){},show:function(){},load:function(){},tabTemplate:'<li><a href="#{href}"><span>#{text}</span></a></li>',panelTemplate:'<div></div>',navClass:'ui-tabs-nav',selectedClass:'ui-tabs-selected',unselectClass:'ui-tabs-unselect',disabledClass:'ui-tabs-disabled',panelClass:'ui-tabs-panel',hideClass:'ui-tabs-hide',loadingClass:'ui-tabs-loading'},options);this.options.event+='.ui-tabs';this.options.cookie=$.cookie&&$.cookie.constructor==Function&&this.options.cookie;$.data(el,$.ui.tabs.INSTANCE_KEY,this);this.tabify(true);};$.ui.tabs.INSTANCE_KEY='ui_tabs_instance';$.ui.tabs.getInstance=function(el){return $.data(el,$.ui.tabs.INSTANCE_KEY);};$.extend($.ui.tabs.prototype,{tabId:function(a){return a.title?a.title.replace(/\s/g,'_'):this.options.idPrefix+$.data(a);},tabify:function(init){this.$lis=$('li:has(a[href])',this.source);this.$tabs=this.$lis.map(function(){return $('a',this)[0]});this.$panels=$([]);var self=this,o=this.options;this.$tabs.each(function(i,a){if(a.hash&&a.hash.replace('#','')){self.$panels=self.$panels.add(a.hash);}else if($(a).attr('href')!='#'){$.data(a,'href',a.href);var id=self.tabId(a);a.href='#'+id;self.$panels=self.$panels.add($('#'+id)[0]||$(o.panelTemplate).attr('id',id).addClass(o.panelClass).insertAfter(self.$panels[i-1]||self.source));}else{o.disabled.push(i+1);}});if(init){$(this.source).hasClass(o.navClass)||$(this.source).addClass(o.navClass);this.$panels.each(function(){var $this=$(this);$this.hasClass(o.panelClass)||$this.addClass(o.panelClass);});for(var i=0,position;position=o.disabled[i];i++){this.disable(position);}this.$tabs.each(function(i,a){if(location.hash){if(a.hash==location.hash){o.initial=i;if($.browser.msie||$.browser.opera){var $toShow=$(location.hash),toShowId=$toShow.attr('id');$toShow.attr('id','');setTimeout(function(){$toShow.attr('id',toShowId);},500);}scrollTo(0,0);return false;}}else if(o.cookie){o.initial=parseInt($.cookie($.ui.tabs.INSTANCE_KEY+$.data(self.source)))||0;return false;}else if(self.$lis.eq(i).hasClass(o.selectedClass)){o.initial=i;return false;}});var n=this.$lis.length;while(this.$lis.eq(o.initial).hasClass(o.disabledClass)&&n){o.initial=++o.initial<this.$lis.length?o.initial:0;n--;}if(!n){o.unselected=o.unselect=true;}this.$panels.addClass(o.hideClass);this.$lis.removeClass(o.selectedClass);if(!o.unselected){this.$panels.eq(o.initial).show().removeClass(o.hideClass);this.$lis.eq(o.initial).addClass(o.selectedClass);}var href=!o.unselected&&$.data(this.$tabs[o.initial],'href');if(href){this.load(o.initial+1,href);}if(!/^click/.test(o.event)){this.$tabs.bind('click',function(e){e.preventDefault();});}}var showAnim={},showSpeed=o.fxShowSpeed||o.fxSpeed,hideAnim={},hideSpeed=o.fxHideSpeed||o.fxSpeed;if(o.fxSlide||o.fxFade){if(o.fxSlide){showAnim['height']='show';hideAnim['height']='hide';}if(o.fxFade){showAnim['opacity']='show';hideAnim['opacity']='hide';}}else{if(o.fxShow){showAnim=o.fxShow;}else{showAnim['min-width']=0;showSpeed=1;}if(o.fxHide){hideAnim=o.fxHide;}else{hideAnim['min-width']=0;hideSpeed=1;}}var resetCSS={display:'',overflow:'',height:''};if(!$.browser.msie){resetCSS['opacity']='';}function hideTab(clicked,$hide,$show){$hide.animate(hideAnim,hideSpeed,function(){$hide.addClass(o.hideClass).css(resetCSS);if($.browser.msie&&hideAnim['opacity']){$hide[0].style.filter='';}o.hide(clicked,$hide[0],$show&&$show[0]||null);if($show){showTab(clicked,$show,$hide);}});}function showTab(clicked,$show,$hide){if(!(o.fxSlide||o.fxFade||o.fxShow)){$show.css('display','block');}$show.animate(showAnim,showSpeed,function(){$show.removeClass(o.hideClass).css(resetCSS);if($.browser.msie&&showAnim['opacity']){$show[0].style.filter='';}o.show(clicked,$show[0],$hide&&$hide[0]||null);});}function switchTab(clicked,$li,$hide,$show){$li.addClass(o.selectedClass).siblings().removeClass(o.selectedClass);hideTab(clicked,$hide,$show);}this.$tabs.unbind(o.event).bind(o.event,function(){var $li=$(this).parents('li:eq(0)'),$hide=self.$panels.filter(':visible'),$show=$(this.hash);if(($li.hasClass(o.selectedClass)&&!o.unselect)||$li.hasClass(o.disabledClass)||o.click(this,$show[0],$hide[0])===false){this.blur();return false;}if(o.cookie){$.cookie($.ui.tabs.INSTANCE_KEY+$.data(self.source),self.$tabs.index(this),o.cookie);}if(o.unselect){if($li.hasClass(o.selectedClass)){$li.removeClass(o.selectedClass);self.$panels.stop();hideTab(this,$hide);this.blur();return false;}else if(!$hide.length){self.$panels.stop();if($.data(this,'href')){var a=this;self.load(self.$tabs.index(this)+1,$.data(this,'href'),function(){$li.addClass(o.selectedClass).addClass(o.unselectClass);showTab(a,$show);});}else{$li.addClass(o.selectedClass).addClass(o.unselectClass);showTab(this,$show);}this.blur();return false;}}self.$panels.stop();if($show.length){if($.data(this,'href')){var a=this;self.load(self.$tabs.index(this)+1,$.data(this,'href'),function(){switchTab(a,$li,$hide,$show);});}else{switchTab(this,$li,$hide,$show);}}else{throw'jQuery UI Tabs: Mismatching fragment identifier.';}if($.browser.msie){this.blur();}return false;});},add:function(url,text,position){if(url&&text){position=position||this.$tabs.length;var o=this.options,$li=$(o.tabTemplate.replace(/#\{href\}/,url).replace(/#\{text\}/,text));var id=url.indexOf('#')==0?url.replace('#',''):this.tabId($('a:first-child',$li)[0]);var $panel=$('#'+id);$panel=$panel.length&&$panel||$(o.panelTemplate).attr('id',id).addClass(o.panelClass).addClass(o.hideClass);if(position>=this.$lis.length){$li.appendTo(this.source);$panel.appendTo(this.source.parentNode);}else{$li.insertBefore(this.$lis[position-1]);$panel.insertBefore(this.$panels[position-1]);}this.tabify();if(this.$tabs.length==1){$li.addClass(o.selectedClass);$panel.removeClass(o.hideClass);var href=$.data(this.$tabs[0],'href');if(href){this.load(position+1,href);}}o.add(this.$tabs[position],this.$panels[position]);}else{throw'jQuery UI Tabs: Not enough arguments to add tab.';}},remove:function(position){if(position&&position.constructor==Number){var o=this.options,$li=this.$lis.eq(position-1).remove(),$panel=this.$panels.eq(position-1).remove();if($li.hasClass(o.selectedClass)&&this.$tabs.length>1){this.click(position+(position<this.$tabs.length?1:-1));}this.tabify();o.remove($li.end()[0],$panel[0]);}},enable:function(position){var o=this.options,$li=this.$lis.eq(position-1);$li.removeClass(o.disabledClass);if($.browser.safari){$li.css('display','inline-block');setTimeout(function(){$li.css('display','block')},0)}o.enable(this.$tabs[position-1],this.$panels[position-1]);},disable:function(position){var o=this.options;this.$lis.eq(position-1).addClass(o.disabledClass);o.disable(this.$tabs[position-1],this.$panels[position-1]);},click:function(position){this.$tabs.eq(position-1).trigger(this.options.event);},load:function(position,url,callback){var self=this,o=this.options,$a=this.$tabs.eq(position-1),a=$a[0],$span=$('span',a);if(url&&url.constructor==Function){callback=url;url=null;}if(url){$.data(a,'href',url);}else{url=$.data(a,'href');}if(o.spinner){$.data(a,'title',$span.html());$span.html('<em>'+o.spinner+'</em>');}var finish=function(){self.$tabs.filter('.'+o.loadingClass).each(function(){$(this).removeClass(o.loadingClass);if(o.spinner){$('span',this).html($.data(this,'title'));}});self.xhr=null;};var ajaxOptions=$.extend(o.ajaxOptions,{url:url,success:function(r){$(a.hash).html(r);finish();if(callback&&callback.constructor==Function){callback();}if(o.cache){$.removeData(a,'href');}o.load(self.$tabs[position-1],self.$panels[position-1]);}});if(this.xhr){this.xhr.abort();finish();}$a.addClass(o.loadingClass);setTimeout(function(){self.xhr=$.ajax(ajaxOptions);},0);},href:function(position,href){$.data(this.$tabs.eq(position-1)[0],'href',href);}});})(jQuery);// /usr/local/idealist/htdocs/javascript/IF/Component.js
// These methods just get inherited by subclasses
// so that they "conform" to a certain interface.
// They still need to set these values themselves.

function IFComponent() {
	
};

IFComponent.prototype = {
	
	// this stub needs to be here to prevent it all from yacking:
	initialize: function() {
		
	}
	
};
// /usr/local/idealist/htdocs/javascript/IF/FormComponent.js
// This is NOT a "FORM" component in the
// sense you might think: that is Form.js.
// This is a component that >belongs< to a
// form (eg. TextField, Text, SubmitButton, etc.)
// but since it can be arbitrarily complex,
// I can't call it FormElement, because that
// would be misleading too.

function IFFormComponent() {
	
};
		
IFFormComponent.prototype.register = function(uniqueId, bindingName) {
	this.uniqueId = uniqueId;
	this.bindingName = bindingName;
	this.errorMessages = {};
	this.element = jQuery('#'+uniqueId)[0];
	if (! this.element) { return false; }
	this.element.controller = this;
	IFForm.registerFormComponent(this);
	return true;
};		
		
IFFormComponent.prototype.requiredErrorMessage = function() {
	return this.errorMessageForKey("IS_REQUIRED");
};
		
IFFormComponent.prototype.setRequiredErrorMessage = function(msg) {
	this.setErrorMessageForKey(msg, "IS_REQUIRED");
};
		
		// the default implementation of this will just check for a simple
		// value and if the field is required, it will return false.
		// Note that this default implementation won't work with complex
		// FormComponent subclasses that return objects.
IFFormComponent.prototype.hasValidValues = function() {
	if (this.isRequired() && !this.value()) {
		return false;
	}
	return true;
};
		
		// Another light implementation:
		// TODO: fix this to work with >different< validation failures
IFFormComponent.prototype.indicateValidationFailure = function() {
	if (!this._backupStyle) {
		this._backupStyle = {
			//backgroundColor: c.element.style.backgroundColor,
			border: jQuery(this.element).css('border')
		};
	}
	if (! this._backupStyle['border']) { this._backupStyle['border'] = '' };
	jQuery(this.element).css('border', "1px solid red");
	
	// special case that should work with most components:
	if (this.isRequired() && !this.value() && this.requiredErrorMessage()) {
		this.displayErrorMessage(this.requiredErrorMessage());
	}
	var _backupStyle = this._backupStyle;
	jQuery(this.element).one('change', function() { 
		jQuery(this).css(_backupStyle);	
		jQuery('#'+this.controller.uniqueId + "-error").html('').hide();
	 });
	jQuery(this.element).one('keypress', function() { 
		jQuery(this).css(_backupStyle);	
		jQuery('#'+this.controller.uniqueId + "-error").html('').hide();	
	});
};

IFFormComponent.prototype.removeValidationFailure = function(c) {
	c.removeValidationFailureMessage();
	jQuery(c.element).css(c._backupStyle);
};
		
IFFormComponent.prototype.bindChangeEvents = function() {
//	jQuery(this.element).once('change', this, this.removeValidationFailure);
//	jQuery(this.element).once('keydown', this, this.removeValidationFailure);
};
		
IFFormComponent.prototype.displayValidationFailureMessage = function(msg) {
	this.displayErrorMessage(msg);
};
		
IFFormComponent.prototype.removeValidationFailureMessage = function() {
	jQuery('#'+this.uniqueId + "-error").html('').hide('normal');
};
		
IFFormComponent.prototype.displayErrorMessage = function(msg) {
	jQuery('#'+this.uniqueId + "-error").html(msg).show('normal').css("error");
};
		
IFFormComponent.prototype.displayErrorMessageForKey = function(key) {
	this.displayErrorMessage(this.errorMessageForKey(key));
};
		
IFFormComponent.prototype.errorMessageForKey = function(key) {
	var em = this.errorMessages[key];
	if (!em) {
		em = this.form.controller.errorMessageForKey(key);
		if (!em) { return key; }
	}
	return em;
};
		
IFFormComponent.prototype.setErrorMessageForKey = function(msg, key) {
	this.errorMessages[key] = msg;
};

IFFormComponent.prototype.isRequired = function() {
	return this._isRequired;
};

IFFormComponent.prototype.setIsRequired = function(value) {
	this._isRequired = value;
};

IFFormComponent.prototype.validator = function() {
	if (this._validator) { return this._validator; }
	return this;
};

IFFormComponent.prototype.setValidator = function(value) {
	this._validator = value;
};
// /usr/local/idealist/htdocs/javascript/IF/Form.js
// Form: this js class attempts to model the client-side
// behaviour of HTML forms.  It is bound directly into the
// DOM tree as the "controller" property of the
// FORM tag that it is controlling.  Enclosed Form Components 
// that comply with its basic API will be accessible to it
// and it will be able to perform validation, manipulation, etc
// of the values.

// initialise variables if they haven't been initialised yet
if (! IF._registeredFormsById) {
	IF._registeredFormsByBindingName = new Object();
	IF._registeredFormsById = new Object();
}

// This is the guts of the Form class
// ---- constructor ----

function IFForm(uniqueId, bindingName) {
	this.uniqueId = uniqueId;
	this.bindingName = bindingName;
	this._submitted = 0;
	this._components = new Array();
	this._componentsById = new Object();
	this._componentsByBindingName = new Object();
	this.element = jQuery('#'+uniqueId).get(0);
	if (!this.element) {
		console.log("No form found with id " + uniqueId);
	}
	this.element.controller = this;
	IFForm.registerForm(this);

	jQuery(this.element).submit(function() { 
		return this.controller.onSubmit() 
	});
}

IFForm.prototype = {
		
	// components are the really interesting things:
	registerFormComponent: function(component) {
		if (!component) { return; }

		this._components[this._components.length] = component;
		this._componentsById[component.uniqueId] = component;
		
		if (this._componentsByBindingName[component.bindingName]) {
			var l = this._componentsByBindingName[component.bindingName].length;
			this._componentsByBindingName[component.bindingName][l] = component;		
		} else {
			this._componentsByBindingName[component.bindingName] = [component];
		}
		
		//console.log("--- registering component " + component.bindingName());
	},
	
	bestMatchingFormComponentWithBindingName: function(name) {
		var matches = [];
		for (var i=0; i< this.components().length; i++) {
			var c = this.components()[i];
			var index = c.bindingName.indexOf(name)
			if (index == 0) {
				// exact match
				return c;
			} else if (index > 0) {
				matches[matches.length] = c;
			}
		}
		return matches[0]; // cheesy for now
	},
	
	formComponentWithBindingName: function(name) {
		return this.bestMatchingFormComponentWithBindingName(name);
	},
	
	formComponentWithId: function(id) {
		return this._componentsById[id];
	},
	
	components: function() {
		return this._components;
	},
	
	// components need to written to have
	// controllers associated with them.
	// if they do, then their object is called
	// to get the value of the component.
	valueOfFormComponent: function(component) {
		if (!component) { return null; }
		return component.value();
	},
	
	// these are for callbacks, etc.
	onSubmit: function() {
		if (this._submitted && this.canOnlyBeSubmittedOnce()) {
			// we need some kind of error here, but what's the
			// best way to do it?
			return false;
		}
		
		this._submitted++;
		var isOk;
		if (this._whoSubmitted && (! this._whoSubmitted.shouldValidateForm())) {
			isOk = true;
		} else {
			isOk = this.hasValidValues();
		}
		if(this._whoSubmitted) {
			if (!isOk) {
				this._whoSubmitted.setWasClicked(false);
			} else {
				this._whoSubmitted.updateButtonStatusForValidSubmission();
			}
		}

		// handle a post validation event if there is one 
		// and validation succeeded
		if (isOk && this._postValidationEventListener) {
			isOk = this._postValidationEventListener.call();
		}	
		this._whoSubmitted = null;
		// remember to 'unsubmit' the form if the validation failed. 
		if (!isOk) {
			this._submitted--;
		}
		return isOk;
	},
	
	// TODO: do we only need one validation method?
	hasValidValues: function() {
		console.log("Checking validation on all form components");
		var isOk = true;
		var validationFailedComponents = new Array();
		jQuery.each(this.components(),
			function () {
				if (this.validator().hasValidValues && jQuery(this.element).is(':visible')) {
					var cOk = this.validator().hasValidValues(this);
					if (!cOk) { validationFailedComponents.push(this); }
				}
			});
		if (validationFailedComponents.length) { isOk = false; }
		
		jQuery.each(validationFailedComponents,
			function() {
				this.validator().indicateValidationFailure(this);
				console.log("Validation failed on " + this.bindingName + "/" + this.uniqueId);			
			});
		
		if (! isOk) {
			var top = this.getTopElement(validationFailedComponents);
			jQuery('html,body').focus().animate({scrollTop: jQuery(top.element).offset().top - 25}, 700);
		}
		console.log("Validation returned " + isOk);
		return isOk;
	},
	
	getTopElement: function(controllers) {
		var start = jQuery(controllers[0].element).offset();
		var winner = { offset: start, controller: controllers[0]};
		jQuery.each(controllers, function() {
			var ofs = jQuery(this.element).offset();
			if ((ofs.top < winner.offset.top) && (ofs.left < winner.offset.left)) {
				winner.offset = ofs;
				winner.controller = this;
			} 
		});
		return winner.controller;
	},
	
	canOnlyBeSubmittedOnce: function() {
		return this._canOnlyBeSubmittedOnce;
	},
	
	setCanOnlyBeSubmittedOnce: function(value) {
		this._canOnlyBeSubmittedOnce = value;
	},
	
	validationFunction: function() {
		return this._validationFunction;
	},
	
	setValidationFunction: function(f) {
		this._validationFunction = f;
	},

	// so a form can have its own error messages that it can share
	// with its form elements.
	errorMessageForKey: function(key) {
		var em = this.errorMessages()[key];
		if (!em) {
			return key;
		}
		return em;
	},
	
	setErrorMessageForKey: function(msg, key) {
		this.errorMessages()[key] = msg;
	},
	
	errorMessages: function() {
		if (!this._errorMessages) {
			this._errorMessages = new Array();
		}
		return this._errorMessages;
	},
		
	// not sure what this should do... maybe the whole thing serialized?
	//value: function() {
	//	return this.objects;
	//},
	
	registerPostValidationEventListener: function(handler) {
		this._postValidationEventListener = handler;
	}
	//---

};

// class methods

IFForm.parentFormOfElement = function(element) {
	return jQuery(element).parents().filter('form')[0];
}

// These handle registration in a page of form components.
IFForm.registerFormComponent = function(component) {
	if (!component) { return; }
	var element;
	if (component.element) {
		element = component.element;
	}
	if (!element) { console.log("Component doesn't have an element property"); return; }
	
	var form = IFForm.parentFormOfElement(element);
	if (!form) { console.log("Couldn't find parent form for " + element.id); return; }
	var controller = form.controller;
	if (!controller) { 
		console.log("Form has no controller object ("+element.controller.bindingName+"), triggering form enumeration"); 
		IF.buildFormControllers();
		controller = form.controller;
	}
	if (!controller) { 
		console.log("Form still has no controller object ("+element.controller.bindingName+"), giving up"); 
		return; 
	}
	controller.registerFormComponent(element.controller);
	
	component.form = form;
}

// IFForm keeps a registry of all the forms in the page, which can be
// accessed by binding name or uniqueId
IFForm.registerForm = function(f) {
	IF._registeredFormsByBindingName[f.bindingName] = f;
	IF._registeredFormsById[f.uniqueId] = f;
}

IFForm.dumpAllForms = function() {
	for (var bindingName in IF._registeredFormsByBindingName) {
		console.log('FORM:  ' + bindingName);
		var form = IFForm.formWithBindingName(bindingName);
		var components = form.components();
		for (var i=0; i < components.length; i++) {
			var component = components[i];
			console.log('--COMPONENT (' + component.element.type + '):  ' + component.bindingName);
		}
	}
}

IFForm.formWithBindingName = function(name) {
	var matches = [];
	for (var i in IF._registeredFormsByBindingName) {
		if (i.indexOf(name) >= 0) {
			matches[matches.length] = i;
		}
	}
	return IF._registeredFormsByBindingName[matches[0]];
}

IFForm.formWithId = function(id) {
	return IF._registeredFormsById[id];
}

// this breaks the naming rules but it's just to make client code cleaner
IFForm.formComponentInForm = function(componentName, formName) {
	var form = IFForm.formWithBindingName(formName);
	if (form) {
		return form.formComponentWithBindingName(componentName);
	}
	console.log("No such form: " + formName);
	return;
}

// static functions for backwards compatibility
// these have been moved under the namespace IFForm,
// but there are plain old methods for compatibility
// that forward the calls to the right place.  These
// will be removed once the older code is ported.

IFForm.formElementsBelowElement = function(element) {
	return jQuery(element).find(':input');
}
formElementsBelowElement = IFForm.formElementsBelowElement;

//---------------------------------------------------
IFForm.setValueOfFormElement = function(value, element) {
	//alert("setting value for form element " + element);
	if (element) {
		switch (element.type) {
			case "text":
			case "textarea":
			case "hidden":
			case "select-one":
			case "select-multiple":
				jQuery(element).val(value);
				break;
			case "checkbox":
				IFForm.setValuesOfCheckBoxGroup(value, element);
				break;
			case "radio":
				IFForm.setValuesOfRadioButtonGroup(value, element);
				break;
			default:
				alert("something else! " + element.type);
				break;
		}
	}
}
function setValueOfFormElement(value, element) {
	return IFForm.setValueOfFormElement(value, element);
}
//---------------------------------------------------

//---------------------------------------------------
IFForm.cleanUpReturnValue = function(value) {
	if (value == "undefined" || !value) {
		return '';
	}
	return value;
}
cleanUpReturnValue = IFForm.cleanUpReturnValue;
//---------------------------------------------------

//---------------------------------------------------
IFForm.valueOfFormElement = function(element) {
	if (element) {
		switch (element.type) {
			case "text":
			case "textarea":
			case "hidden":
			case "select-one":
			case "select-multiple":
				return cleanUpReturnValue(jQuery(element).val());
				break;
			case "checkbox":
				return cleanUpReturnValue(valueOfCheckBoxGroup(element));
				break;
			case "radio":
				return cleanUpReturnValue(valueOfRadioButtonGroup(element));
				break;
			default:
				//alert("something else! " + element.type);
				break;
		}
	}
}
valueOfFormElement = IFForm.valueOfFormElement;

IFForm.valueOfTextField = function(textfield) {
	return jQuery(textfield).val();
}
valueOfTextField = IFForm.valueOfTextField;

IFForm.setValueOfTextField = function(value, textfield) {
	if (! jQuery(textfield).val(value).size() ) {
		console.log("can't find text field " + textfield);
	}
}
setValueOfTextField = IFForm.setValueOfTextField;

IFForm.valueOfSelect = function(select) {
	return jQuery(select).val()[0];
}
valueOfSelect = IFForm.valueOfSelect;

IFForm.valuesOfSelect = function(select) {
	return jQuery(select).val();
}
valuesOfSelect = IFForm.valuesOfSelect;

IFForm.setValueOfSelect = function(value, select) {
	if (! jQuery(select).val(value).size() ) {
		console.log("can't find select " + select);
	}
}
setValueOfSelect = IFForm.setValueOfSelect;

IFForm.setValuesOfSelect = function(values, select) {
	if (! jQuery(select).val(values).size() ) {
		console.log("can't find select " + select);
	}
}
setValuesOfSelect = IFForm.setValuesOfSelect;

IFForm.valuesOfCheckBoxGroup = function(element) {
	var values = [];
	jQuery(element).find('input:checked').each(function() { values.push(this.value) } );
	return values;
}
valuesOfCheckBoxGroup = IFForm.valuesOfCheckBoxGroup;

IFForm.setValuesOfCheckBoxGroup = function(values, group) {
	if (! jQuery(group).find(':checkbox').val(values).size() ) {
		console.log("can't find checkbox group " + group);
	}
}
setValuesOfCheckBoxGroup = IFForm.setValuesOfCheckBoxGroup;

IFForm.checkedStateOfCheckBox = function(box) {
	return jQuery(box).val();
}
checkedStateOfCheckBox = IFForm.checkedStateOfCheckBox;

IFForm.setCheckedStateOfCheckBox = function(value, box) {
	if (! jQuery(box).val(value).size() ) {
		alert("Couldn't find checkbox " + box);
	}
}
setCheckedStateOfCheckBox = IFForm.setCheckedStateOfCheckBox;

IFForm.valueOfRadioButtonGroup = function(element) {
	return jQuery(element).find(':radio:checked').val();
}
valueOfRadioButtonGroup = IFForm.valueOfRadioButtonGroup;

IFForm.setValueOfRadioButtonGroup = function(value, group) {
	if (!jQuery(group).find(':radio').val([value]).size() ) {
		alert("Couldn't find radio button group " + group);
	}
}
setValueOfRadioButtonGroup = IFForm.setValueOfRadioButtonGroup;// /usr/local/idealist/htdocs/javascript/Idealist/ComponentRegistry.js
// ---- constructor ----

function ComponentRegistry() {
	this.components = new Array();
	this.componentNames = new Array();
};
var MAIN_CONTENT_KEY = "MAIN_CONTENT"; // this is pretty lame

ComponentRegistry.prototype = {
		
	// store and retrieve components by name using these
	// methods.
	registerComponentWithName: function(component, name) {
		var existingComponent = this.components[name];
		if (existingComponent) {
			this.components[name][this.components[name].length] = component;
		} else {
			this.components[name] = new Array();
			this.components[name][0] = component;
		}
		// add the name
		this.componentNames[this.componentNames.length] = name;
	},
	
	componentWithName: function(name) {
		return this.componentWithNameRelativeToComponent(name, null);
	},
	
	componentWithNameRelativeToComponent: function(name, from) {
		var bestName = this.bestMatchingComponentNameForNameRelativeToComponent(name, from);
		var existingComponent = this.components[bestName];
		if (existingComponent) {
			// TODO allow the consumer to fetch more than one?
			return existingComponent[0];
		}
		return null;
	},
	
	componentsWithName: function(name) {
		return this.componentsWithNameRelativeToComponent(name, null);
	},
	
	componentsWithNameRelativeToComponent: function(name, from) {
		var bestName = this.bestMatchingComponentNameForNameRelativeToComponent(name, from);
		var existingComponents = this.components[bestName];		
		if (existingComponents) {
			return existingComponents;
		}
		return new Array();
	},
	
	bestMatchingComponentNameForNameRelativeToComponent: function(name, from) {
		// if there's a 'from', check relative to the 'from':
		if (from) {
			var c = from.bindingName();
			// console.log("Checking for component " + name + " relative to " + c);
			if (c) {
				var n = c + "/" + name;
				var e = this.components[n];
				if (e) {
					return n;
				}
			}
		}
		
		// brute force for the name
		var matches = [];
		for (var i in this.components) {
			if (i.indexOf(name) >= 0) {
				matches[matches.length] = i;
			}
		}
		if (matches.length) {
			return matches[0];
		}
		
		// otherwise, try the name directly
		var e = this.components[name];
		if (e) {
			return name;
		}
		
		return "";
	},
	
	// Specialty methods used for page-building
	
	mainContent: function() {
		if (this._mainContent) {
			return this._mainContent;
		}
		this._mainContent = $(MAIN_CONTENT_KEY);
		return this._mainContent;
	},
	
	reloadMainContentFromUrl: function(url) {
		var mc = this.mainContent();
		if (!mc) {
			return;
		}
		// here we would set up the "please wait" goop
		mc.innerHTML = "..."; // max can do a nice spinning gif or something here.
		// add an on success callback here to focus back the the top of the page
    	new Ajax.Updater(mc, url, { asynchronous:true, evalScripts:true });
	},
	
	// Page has wrapper
	pageHasWrapper: function() {
		var mc = this.mainContent();
		return (mc != null);
	}
};

// create an instance of the component registry if it doesn't exist.
if (typeof componentRegistry == "undefined") {
	var componentRegistry = new ComponentRegistry();
}
// /usr/local/idealist/htdocs/javascript/Idealist/AsynchronousComponent.js
// This allows us to represent a component
// on the client side with a javascript
// client.

// ---- constructor ----
function AsynchronousComponent(uniqueId, bindingName, renderContextNumber) {
    this.uniqueId = uniqueId;
	this.bindingName = bindingName;
	this.renderContextNumber = renderContextNumber;
    this.component = jQuery('#'+uniqueId);
    if (!this.component) {
        alert("Couldn't find component " + uniqueId);
    }
}

// ---- instance methods ----
AsynchronousComponent.prototype.init = function() {
    // alert("Init of " + this.uniqueId);
}

AsynchronousComponent.prototype.initWithValues = function(rootUrl, action, queryString) {
	this.init();
	this.rootUrl = rootUrl;
	this.action = action;
	this.queryString = queryString;
    this.updateUrl = rootUrl + action + "?" + queryString;	
}

AsynchronousComponent.prototype.reloadFromUrl = function(url, qd, type) {
	var targetId = this.uniqueId;
	jQuery('#'+targetId).html('<div class="whileLoading">&nbsp;</div>');
	type = type || 'GET';
	jQuery.ajax({
		url: url,
		data: qd,
		type: type,
		success: function(data){
			jQuery('#'+targetId).html(data).IFBuildFormControllers();
		},
		error: function(XMLHttpRequest, textStatus, errorThrown) {
			console.log('Error: '+textStatus+' - '+errorThrown);
		}
	 });
}

AsynchronousComponent.prototype.reload = function() {
	console.log("Loading from "+ this.updateUrl + "...");
	this.reloadFromUrl(this.updateUrl);
}

AsynchronousComponent.prototype.queryStringFromExtrasDictionary = function(dictionary) {
	// i had to restore this code because the ajax() code above doesn't do the same
    // thing... we need to overwrite, not join.  sorry this is so hopeless and low-level but
    // i couldn't see an easy way in jQuery to do this
    var url = this.updateUrl;
    var qs = "";
    if (url.indexOf('?') != -1) {
    	var parts = url.split('?');
    	url = parts[0];
    	qs = parts[1];
    }

    var qd = new Object();
    var kvps = qs.split("&");
    for (var i=0; i<kvps.length; i++) {
        var kvs = kvps[i].split("="); // this doesn't take quoted shit into account
        var key = kvs[0];
        var value = kvs[1];
        qd[key] = value;
    }
    
    // stomp
    for (var key in dictionary) {
        qd[key] = dictionary[key];
    }
    
    //rebuild
    qs = "";
    for (var key in qd) {
        var value = qd[key];
        qs = qs + key + "=" + value + "&";
    }
	return qs;
}

AsynchronousComponent.prototype.reloadWithQueryDictionary = function (dictionary) {
	var queryString = "";
	// do some javascripty stuff to generate a query string from a dictionary
	this.reloadWithQueryString(queryString);
}

AsynchronousComponent.prototype.reloadWithExtraQueryKeyValuePairs = function (dictionary) {
	var qs = this.queryStringFromExtrasDictionary(dictionary);
    this.reloadFromUrl(this.rootUrl + this.action + "?" + qs);
}

AsynchronousComponent.prototype.reloadWithActionAndExtraQueryKeyValuePairs = function(action, dictionary) {
	var qs = this.queryStringFromExtrasDictionary(dictionary);
    this.reloadFromUrl(this.rootUrl + action + "?" + qs);	
}

AsynchronousComponent.prototype.reloadWithQueryString = function (string) {
	// reload from the URL with qs
	//alert("Reloading!");
	this.reloadFromUrl(this.updateUrl + "&" + string)
}

AsynchronousComponent.prototype.submitWithActionAndSerializedForm = function (action, form) {
	var qd = jQuery.extend(jQuery(form).serializeArray(), 
		{ '_async' : this.uniqueId, '_ucid=' : this.renderContextNumber });
	var url = this.rootUrl + action;
	this.reloadFromUrl(url, qd, 'POST');
}

AsynchronousComponent.prototype.failureMessage = function (status) {
	if (404 == status)
		return 'Resource not found. '+status;
	return 'Temporary server failure. Please try again shortly. '+status;
}

AsynchronousComponent.prototype.uniqueId = function() {
	return this.uniqueId;
}

AsynchronousComponent.prototype.bindingName = function() {
	return this.bindingName;
}// /usr/local/idealist/htdocs/javascript/Idealist/Session.js
// ---- constructor ----
/**
*
*  UTF-8 data encode / decode
*  http://www.webtoolkit.info/
* (SW: encode() removed for space conservation)
**/

var Utf8 = {
    // public method for url decoding
    decode : function (utftext) {
        var string = "";
        var i = 0;
        var c = c1 = c2 = 0;

        while ( i < utftext.length ) {

            c = utftext.charCodeAt(i);

            if (c < 128) {
                string += String.fromCharCode(c);
                i++;
            }
            else if((c > 191) && (c < 224)) {
                c2 = utftext.charCodeAt(i+1);
                string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
                i += 2;
            }
            else {
                c2 = utftext.charCodeAt(i+1);
                c3 = utftext.charCodeAt(i+2);
                string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
                i += 3;
            }

        }

        return string;
    }

}

function Session() {
	this.name = Utf8.decode(this.getCookie('first-name'));
	this.email = this.getCookie('user-name');
	this.sid = this.getCookie('sid');
	// fill the name with the email if name is missing
	// as it is for first time returning users
	if (this.name == "") { this.name = this.email; }	

	this.isIdentified = 0;
	this.isAuthenticated = 0;
	this.parseAuthenticationStatus();
	//alert(this.sid + " : " + this.userId + "(" + this.name + ") isId: " + this.isIdentified + ' isAuth: ' + this.isAuthenticated);
	if (this.name != "") { 
		this.isKnown = 1; 
	} else {
		this.isKnown = 0;
	}
}

Session.prototype = {
	
	// ---- instance methods ----	
    
	getCookie: function(name) {
		var nameEQ = name + "=";
		var ca = document.cookie.split(';');
		for(var i=0;i < ca.length;i++)
		{
			var c = ca[i];
			while (c.charAt(0)==' ') c = c.substring(1,c.length);
			if (c.indexOf(nameEQ) == 0) return unescape(unescape(c.substring(nameEQ.length,c.length)));
		}
		return "";
	},

	// this  working correctly 
	parseAuthenticationStatus: function () {
		if(this.sid == "") { return; }
		var sidParts = this.sid.split('-');
		if (sidParts.length < 2) { console.log('bad sid'); return; }
		var authPart = sidParts[2];
		this.isIdentified = (authPart.charCodeAt(0) + 1) % 2;
		this.isAuthenticated = (authPart.charCodeAt(1) + 1) % 2;	
	},
	
	//This is a quick function that invalidates the current session
	//on the clientside.  This will leave dangling sessions that will timeout. 
	invalidate: function() {
		IF.createCookie('user-name', '', -1);
		IF.createCookie('first-name', '', -1);
		IF.createCookie('sid', '', -1);
		this.sid = '';
		this.isIdentified = 0;
		this.isAuthenticated = 0;
		this.isKnown = 0;
	}
};

// create an instance of the session if it doesn't exist.
if (typeof session == "undefined") {
    session = new Session();
}

// /usr/local/idealist/htdocs/javascript/Idealist/StatusMessagesViewer.js
// Javascript for StatusMessagesViewer

var _statusMessageViewers = new Array();

function StatusMessagesViewer(uniqueId) {
	this.uniqueId = uniqueId;
	this.component = jQuery('#'+uniqueId)[0];
	if (!this.component) {
		//alert("No status messages viewer " + uniqueId + " found");
	}
}

StatusMessagesViewer.addStatusMessagesViewer = function(viewer) {
	_statusMessageViewers[_statusMessageViewers.length] = viewer;
}

StatusMessagesViewer.primaryStatusMessagesViewer = function(viewer) {
	if (_statusMessageViewers.length == 0) {
		//alert("No primary status messages viewer found!");
		return;
	}
	return _statusMessageViewers[0];
}

// ------ instance methods -------

StatusMessagesViewer.prototype.init = function() {
	StatusMessagesViewer.addStatusMessagesViewer(this);
}

StatusMessagesViewer.prototype.postStatusMessages = function(messages, classes, hasErrors) {
	this.clearStatusMessages();
	var statDiv = jQuery('#'+this.uniqueId + '-messages')[0];
	if (messages.length < 1) {
		statDiv.hide();
	} else {
		statDiv.show();
	}
	
	if (hasErrors) {
		statDiv.css('error-messages');
	} else {
		statDiv.css('status-messages');
	}
	
	for(i=0; i < messages.length; i++) {
		jQuery('#' + this.uniqueId + '-message-list').append('<li class="'+classes[i]+'">'+messages[i]+'</li>');	
	}
}

StatusMessagesViewer.prototype.clearStatusMessages = function() {
	var msgList =  jQuery('#'+this.uniqueId + '-message-list')[0];
	var nodeList = jQuery(msgList).find('li').each(function(){
			this.parent.removeChild(node);
	});
}
// /usr/local/idealist/htdocs/javascript/Idealist/PageWrapper.js
/* PAGE WRAPPER INIT */
jQuery(document).ready(function() {
	jQuery('.boxModel03, .contentBoxMain, .optionsBox, .contentBox, .boxHeader, .boxFooter').roundCorners();
	// Open all external links that are in the main content area in an 	  external window
	// if they don't already have a target set.
	jQuery('#MAIN_CONTENT a[href]').not('[target]').not('[href^=/]').not('[href^=#]').not('[href^=javascript]').attr('target', '_blank');
	// Any links labeled with class rssFeed in the Main content should add themselves to the 
	// header as the rss for that page. 
	jQuery('#MAIN_CONTENT a.rssFeed').markAsRssFeed();
	// If facebox is loaded, set all links with a rel for faceboxing
	if (jQuery.facebox) {
		jQuery('a[rel*=facebox]').facebox();		 
	}
});
// /usr/local/idealist/htdocs/javascript/IF/Span.js
// span tag that is tied in to the js framework so we can
// easily get / set its value

IFSpan = IF.extend(IFFormComponent, function(uniqueId, bindingName) {
		this.uniqueId = uniqueId;
		this.bindingName = bindingName;
		this.element = jQuery('#'+uniqueId)[0];
		if (!this.element) {
			console.log("No span found with id "+uniqueId);
			return;
		}
		this.element.controller = this;
	},
{
		value: function() {
			jQuery(this.element).html();
		},

		setValue: function(value) {
			jQuery(this.element).html(value);
		},

		show: function() {
			jQuery(this.element).show();
		},

		hide: function() {
			jQuery(this.element).hide();
		}
});
// /usr/local/idealist/htdocs/javascript/IF/PopUpMenu.js
// IFPopUpMenu
// This componentises the behaviour of a pop up menu

// ---- constructor ----


IFPopUpMenu = IF.extend(IFFormComponent, function(uniqueId, bindingName) {
	if (! this.register(uniqueId, bindingName)) {
		console.log("No popup menu found with id "+uniqueId);
	}

	// grab the popUpMenu component and remember its element
	var els = IFForm.formElementsBelowElement(this.element);
	this._selection = els[0];
});

IFPopUpMenu.prototype.value = function() {
	return IFForm.valueOfFormElement(this._selection); // just return the value of the actual text box
};
	
IFPopUpMenu.prototype.setValue = function(value) {
	IFForm.setValueOfFormElement(value, this._selection);
};
		
IFPopUpMenu.prototype.otherValue = function () {
	return this._otherValue;
};
		
IFPopUpMenu.prototype.setOtherValue = function (value) {
	this._otherValue = value;
};
		
IFPopUpMenu.prototype.otherTextField = function() {
	return this._otherTextField;
};
		
IFPopUpMenu.prototype.setOtherTextField = function(value) {
	this._otherTextField = value;
};
		
IFPopUpMenu.prototype.initializeOtherHandlingWithValueAndOtherValue = function(value, otherValue) {
	this.setOtherValue(otherValue);
	this._otherElement = jQuery('#OTHER_' + this.uniqueId)[0];
	this.setOtherTextField(IFForm.formElementsBelowElement(this._otherElement)[0]);
	Event.observe(this.uniqueId, 'change', this.toggleOtherBasedOnSelection.bindAsEventListener(this));
	// Check to see if selection value is found in the list		
	if (value != '') {
		var isFound = false;
		for (var i=0; i < this._selection.options.length; i++) {
			if (this._selection.options[i].value == value) {
				isFound = true;
				break;
			}
		}
		if (! isFound) {
			// Set the selection box to Other...
			this.setValue(otherValue);
			// ...and set the text box value 
			IFForm.setValueOfFormElement(value, this.otherTextField())
		} 
	}
	//Call this in case of the page being called using the back button
	this.toggleOtherBasedOnSelection();
};
		
IFPopUpMenu.prototype.toggleOtherBasedOnSelection = function(event) {
	if (this.otherValue() == this.value()) {
		this._otherElement.show();
		this.otherTextField().focus();
	} else {
		this._otherElement.hide();
	}
	
};// /usr/local/idealist/htdocs/javascript/IF/SubmitButton.js
// IFSubmitButton
// ---- constructor ----

IFSubmitButton = IF.extend(IFFormComponent, function(uniqueId, bindingName) {
	if (! this.register(uniqueId, bindingName)) {
		console.log("No submitbutton found with id "+uniqueId);
	}
	if (! this.element) {
		return false;
	}
	this._wasClicked = false;
	this._shouldValidateForm = 1;
	this._clientOnClickHandler = false;
	
	jQuery(this.element).click(function() { return this.controller.onClick(); });
});

IFSubmitButton.prototype.onClick = function() {
	if (this._wasClicked && this.canOnlyBeClickedOnce()) {
		// we need some kind off error here, but what's the
		// best way to do it?

		return false;
	}
	if (this._clientOnClickHandler) {
		if (! this._clientOnClickHandler()) {
			return false;
		}
	}
	// we should let the form do this
	//this.setValue(this._alternateValue);
	this.setWasClicked(true);
	this.form.controller._whoSubmitted = this;
	return true;
};
		
IFSubmitButton.prototype.canOnlyBeClickedOnce = function() {
	return this._canOnlyBeClickedOnce;
};
		
IFSubmitButton.prototype.setCanOnlyBeClickedOnce = function(value) {
	this._canOnlyBeClickedOnce = value;
};
		
IFSubmitButton.prototype.wasClicked = function() {
	return this._wasClicked;
};
		
IFSubmitButton.prototype.setWasClicked = function(v) {
	this._wasClicked = v;
};
		
IFSubmitButton.prototype.updateButtonText = function() {
	this.setValue(this._alternateValue);
}
		
IFSubmitButton.prototype.updateButtonStatusForValidSubmission = function() {
	this.updateButtonText();
	if (this.canOnlyBeClickedOnce() ||
		this.form.controller.canOnlyBeSubmittedOnce()) {
		
		jQuery(this.element).hide();
		if (this._alternateValue) {
			//jQuery(this.element).before("<span>" + this._alternateValue + "</span>");
			jQuery(this.element).innerHtml = "<span>" + this._alternateValue + "</span>";
		}
		//alert("Hid button");
	}
};
		
IFSubmitButton.prototype.value = function() {
	return jQuery(this.element).val(); // just return the value of the label
};
	
IFSubmitButton.prototype.setValue = function(value) {
	jQuery(this.element).val(value);
};
		
IFSubmitButton.prototype.shouldValidateForm = function() {
	return this._shouldValidateForm;
};
		
IFSubmitButton.prototype.setShouldValidateForm = function(value) {
	this._shouldValidateForm = value;
}
		
IFSubmitButton.prototype.onClickHandler = function() {
	return this._clientOnClickHandler;
}
		
IFSubmitButton.prototype.setOnClickHandler = function(value) {
	this._clientOnClickHandler = value;
};
		
IFSubmitButton.prototype.alternateValue = function() {
	return this._alternateValue; 
};
	
IFSubmitButton.prototype.setAlternateValue = function(value) {
	this._alternateValue = value;
};

// /usr/local/idealist/htdocs/javascript/IF/CheckBoxGroup.js
// IFCheckBoxGroup
// This componentises the behaviour of a checkbox group.
// This is a complicated one because HTML doesn't bind checkboxes very well together
// so many checkboxes in different forms could have the same name.
// ---- constructor ----


IFCheckBoxGroup = IF.extend(IFFormComponent, function(uniqueId, bindingName, name) {
	if (! this.register(uniqueId, bindingName)) {
		console.log("No checkbox group found with id "+uniqueId);
	}
});
	
IFCheckBoxGroup.prototype.value = function() {
	return IFForm.valuesOfCheckBoxGroup(this.element);
};
	
IFCheckBoxGroup.prototype.setValue = function(value) {
	IFForm.setValuesOfCheckBoxGroup(value, this.element);
};
// /usr/local/idealist/htdocs/javascript/IF/TextField.js
// IFTextField
// This componentises the behaviour of a simple text field, so
// that the field can be manipulated by the client-side framework
// if needed.

// ---- constructor ----


IFTextField = IF.extend(IFFormComponent, function(uniqueId, bindingName) {
	if (! this.register(uniqueId, bindingName)) {
		console.log("No textfield found with id "+uniqueId);
	}
});

IFTextField.prototype.value = function() {
    return jQuery(this.element).val(); 
};

IFTextField.prototype.setValue = function(value) {
    jQuery(this.element).val(value);
};

// /usr/local/idealist/htdocs/javascript/IF/ClientSideConditional.js
// IFClientSideConditional
if (! IF._registeredConditionals) {
	IF._registeredConditionalsByBindingName = Object();
}
// ---- constructor ----

IFClientSideConditional = IF.extend(IFComponent, function(uniqueId, bindingName, expressionFunction) {
	this.expressionFunction = expressionFunction;
	this.uniqueId = uniqueId;
	this.bindingName = bindingName;
	this.element = jQuery('#'+uniqueId)[0];
	if (!this.element) {
		console.log("No ClientSideConditional found with id "+uniqueId);
		return;
	}          
	this.element.controller = this;
},
{
	evaluate: function() {
		return this.expressionFunction.call(window);
	},
	
	refresh: function() {
		var val = this.evaluate();
		if (val) {
			jQuery(this.element).slideDown('slow');
		} else {
			jQuery(this.element).slideUp('slow');
		}
	}   	
});

IFClientSideConditional.registerConditional = function(c) {
	IF._registeredConditionalsByBindingName[c.bindingName] = c;
}

IFClientSideConditional.conditionalWithName = function(name) {
	// A bit dopey...finds the first that matches
	for (key in IF._registeredConditionalsByBindingName) {
		if (key.indexOf(name) >= 0) {
			return IF._registeredConditionalsByBindingName[key];
		}
	}
	console.log('Conditional with name (' + name + ') was not found');
}

// /usr/local/idealist/htdocs/javascript/IF/ScrollingList.js
// IFScrollingList
// This componentises the behaviour of a pop up menu

// ---- constructor ----


IFScrollingList = IF.extend(IFFormComponent, function(uniqueId, bindingName) {
	if (! this.register(uniqueId, bindingName)) {
		console.log("No scrolling list found with id "+uniqueId);
	}
	// grab the popUpMenu component and remember its element
	var els = IFForm.formElementsBelowElement(this.element);
	this._selection = els[0];
});

IFScrollingList.prototype.value = function() {
	return IFForm.valuesOfSelect(this._selection); // just return the value of the actual text box
};
	
IFScrollingList.prototype.setValue = function(value) {
	IFForm.setValuesOfSelect(value, this._selection);
};
// /usr/local/idealist/htdocs/javascript/IF/EmailField.js
// IFTextField
// This componentises the behaviour of a simple text field, so
// that the field can be manipulated by the client-side framework
// if needed.

// ---- constructor ----

IFEmailField = IF.extend(IFFormComponent, function(uniqueId, bindingName) {
	if (! this.register(uniqueId, bindingName)) {
		console.log("No email field found with id "+uniqueId);
	}
},
{		
	hasValidValues: function(c) {
		console.log("Validating email field with value " + c.value());
		// If the value is empty, then it doesn't validate the email. (isRequired function instead) -lg i-1446
		if (c.value() && !IFValidator.isValidEmailAddress(c.value())) {
			c.indicateValidationFailure();
			c.displayErrorMessageForKey("VALID_EMAIL_REQUIRED");
			return false;
		}
           if (c.isRequired() && !c.value()) {
               return false;
           }
		return true;
	},
	
	// these accessors are mandatory for any component that wants to interact
	// with its form.
	value: function() {
		return this.element.value; // just return the value of the actual text box
	},

	setValue: function(value) {
		this.element.value = value;
	}
});
// /usr/local/idealist/htdocs/javascript/IF/Form.js
// Form: this js class attempts to model the client-side
// behaviour of HTML forms.  It is bound directly into the
// DOM tree as the "controller" property of the
// FORM tag that it is controlling.  Enclosed Form Components 
// that comply with its basic API will be accessible to it
// and it will be able to perform validation, manipulation, etc
// of the values.

// initialise variables if they haven't been initialised yet
if (! IF._registeredFormsById) {
	IF._registeredFormsByBindingName = new Object();
	IF._registeredFormsById = new Object();
}

// This is the guts of the Form class
// ---- constructor ----

function IFForm(uniqueId, bindingName) {
	this.uniqueId = uniqueId;
	this.bindingName = bindingName;
	this._submitted = 0;
	this._components = new Array();
	this._componentsById = new Object();
	this._componentsByBindingName = new Object();
	this.element = jQuery('#'+uniqueId).get(0);
	if (!this.element) {
		console.log("No form found with id " + uniqueId);
	}
	this.element.controller = this;
	IFForm.registerForm(this);

	jQuery(this.element).submit(function() { 
		return this.controller.onSubmit() 
	});
}

IFForm.prototype = {
		
	// components are the really interesting things:
	registerFormComponent: function(component) {
		if (!component) { return; }

		this._components[this._components.length] = component;
		this._componentsById[component.uniqueId] = component;
		
		if (this._componentsByBindingName[component.bindingName]) {
			var l = this._componentsByBindingName[component.bindingName].length;
			this._componentsByBindingName[component.bindingName][l] = component;		
		} else {
			this._componentsByBindingName[component.bindingName] = [component];
		}
		
		//console.log("--- registering component " + component.bindingName());
	},
	
	bestMatchingFormComponentWithBindingName: function(name) {
		var matches = [];
		for (var i=0; i< this.components().length; i++) {
			var c = this.components()[i];
			var index = c.bindingName.indexOf(name)
			if (index == 0) {
				// exact match
				return c;
			} else if (index > 0) {
				matches[matches.length] = c;
			}
		}
		return matches[0]; // cheesy for now
	},
	
	formComponentWithBindingName: function(name) {
		return this.bestMatchingFormComponentWithBindingName(name);
	},
	
	formComponentWithId: function(id) {
		return this._componentsById[id];
	},
	
	components: function() {
		return this._components;
	},
	
	// components need to written to have
	// controllers associated with them.
	// if they do, then their object is called
	// to get the value of the component.
	valueOfFormComponent: function(component) {
		if (!component) { return null; }
		return component.value();
	},
	
	// these are for callbacks, etc.
	onSubmit: function() {
		if (this._submitted && this.canOnlyBeSubmittedOnce()) {
			// we need some kind of error here, but what's the
			// best way to do it?
			return false;
		}
		
		this._submitted++;
		var isOk;
		if (this._whoSubmitted && (! this._whoSubmitted.shouldValidateForm())) {
			isOk = true;
		} else {
			isOk = this.hasValidValues();
		}
		if(this._whoSubmitted) {
			if (!isOk) {
				this._whoSubmitted.setWasClicked(false);
			} else {
				this._whoSubmitted.updateButtonStatusForValidSubmission();
			}
		}

		// handle a post validation event if there is one 
		// and validation succeeded
		if (isOk && this._postValidationEventListener) {
			isOk = this._postValidationEventListener.call();
		}	
		this._whoSubmitted = null;
		// remember to 'unsubmit' the form if the validation failed. 
		if (!isOk) {
			this._submitted--;
		}
		return isOk;
	},
	
	// TODO: do we only need one validation method?
	hasValidValues: function() {
		console.log("Checking validation on all form components");
		var isOk = true;
		var validationFailedComponents = new Array();
		jQuery.each(this.components(),
			function () {
				if (this.validator().hasValidValues && jQuery(this.element).is(':visible')) {
					var cOk = this.validator().hasValidValues(this);
					if (!cOk) { validationFailedComponents.push(this); }
				}
			});
		if (validationFailedComponents.length) { isOk = false; }
		
		jQuery.each(validationFailedComponents,
			function() {
				this.validator().indicateValidationFailure(this);
				console.log("Validation failed on " + this.bindingName + "/" + this.uniqueId);			
			});
		
		if (! isOk) {
			var top = this.getTopElement(validationFailedComponents);
			jQuery('html,body').focus().animate({scrollTop: jQuery(top.element).offset().top - 25}, 700);
		}
		console.log("Validation returned " + isOk);
		return isOk;
	},
	
	getTopElement: function(controllers) {
		var start = jQuery(controllers[0].element).offset();
		var winner = { offset: start, controller: controllers[0]};
		jQuery.each(controllers, function() {
			var ofs = jQuery(this.element).offset();
			if ((ofs.top < winner.offset.top) && (ofs.left < winner.offset.left)) {
				winner.offset = ofs;
				winner.controller = this;
			} 
		});
		return winner.controller;
	},
	
	canOnlyBeSubmittedOnce: function() {
		return this._canOnlyBeSubmittedOnce;
	},
	
	setCanOnlyBeSubmittedOnce: function(value) {
		this._canOnlyBeSubmittedOnce = value;
	},
	
	validationFunction: function() {
		return this._validationFunction;
	},
	
	setValidationFunction: function(f) {
		this._validationFunction = f;
	},

	// so a form can have its own error messages that it can share
	// with its form elements.
	errorMessageForKey: function(key) {
		var em = this.errorMessages()[key];
		if (!em) {
			return key;
		}
		return em;
	},
	
	setErrorMessageForKey: function(msg, key) {
		this.errorMessages()[key] = msg;
	},
	
	errorMessages: function() {
		if (!this._errorMessages) {
			this._errorMessages = new Array();
		}
		return this._errorMessages;
	},
		
	// not sure what this should do... maybe the whole thing serialized?
	//value: function() {
	//	return this.objects;
	//},
	
	registerPostValidationEventListener: function(handler) {
		this._postValidationEventListener = handler;
	}
	//---

};

// class methods

IFForm.parentFormOfElement = function(element) {
	return jQuery(element).parents().filter('form')[0];
}

// These handle registration in a page of form components.
IFForm.registerFormComponent = function(component) {
	if (!component) { return; }
	var element;
	if (component.element) {
		element = component.element;
	}
	if (!element) { console.log("Component doesn't have an element property"); return; }
	
	var form = IFForm.parentFormOfElement(element);
	if (!form) { console.log("Couldn't find parent form for " + element.id); return; }
	var controller = form.controller;
	if (!controller) { 
		console.log("Form has no controller object ("+element.controller.bindingName+"), triggering form enumeration"); 
		IF.buildFormControllers();
		controller = form.controller;
	}
	if (!controller) { 
		console.log("Form still has no controller object ("+element.controller.bindingName+"), giving up"); 
		return; 
	}
	controller.registerFormComponent(element.controller);
	
	component.form = form;
}

// IFForm keeps a registry of all the forms in the page, which can be
// accessed by binding name or uniqueId
IFForm.registerForm = function(f) {
	IF._registeredFormsByBindingName[f.bindingName] = f;
	IF._registeredFormsById[f.uniqueId] = f;
}

IFForm.dumpAllForms = function() {
	for (var bindingName in IF._registeredFormsByBindingName) {
		console.log('FORM:  ' + bindingName);
		var form = IFForm.formWithBindingName(bindingName);
		var components = form.components();
		for (var i=0; i < components.length; i++) {
			var component = components[i];
			console.log('--COMPONENT (' + component.element.type + '):  ' + component.bindingName);
		}
	}
}

IFForm.formWithBindingName = function(name) {
	var matches = [];
	for (var i in IF._registeredFormsByBindingName) {
		if (i.indexOf(name) >= 0) {
			matches[matches.length] = i;
		}
	}
	return IF._registeredFormsByBindingName[matches[0]];
}

IFForm.formWithId = function(id) {
	return IF._registeredFormsById[id];
}

// this breaks the naming rules but it's just to make client code cleaner
IFForm.formComponentInForm = function(componentName, formName) {
	var form = IFForm.formWithBindingName(formName);
	if (form) {
		return form.formComponentWithBindingName(componentName);
	}
	console.log("No such form: " + formName);
	return;
}

// static functions for backwards compatibility
// these have been moved under the namespace IFForm,
// but there are plain old methods for compatibility
// that forward the calls to the right place.  These
// will be removed once the older code is ported.

IFForm.formElementsBelowElement = function(element) {
	return jQuery(element).find(':input');
}
formElementsBelowElement = IFForm.formElementsBelowElement;

//---------------------------------------------------
IFForm.setValueOfFormElement = function(value, element) {
	//alert("setting value for form element " + element);
	if (element) {
		switch (element.type) {
			case "text":
			case "textarea":
			case "hidden":
			case "select-one":
			case "select-multiple":
				jQuery(element).val(value);
				break;
			case "checkbox":
				IFForm.setValuesOfCheckBoxGroup(value, element);
				break;
			case "radio":
				IFForm.setValuesOfRadioButtonGroup(value, element);
				break;
			default:
				alert("something else! " + element.type);
				break;
		}
	}
}
function setValueOfFormElement(value, element) {
	return IFForm.setValueOfFormElement(value, element);
}
//---------------------------------------------------

//---------------------------------------------------
IFForm.cleanUpReturnValue = function(value) {
	if (value == "undefined" || !value) {
		return '';
	}
	return value;
}
cleanUpReturnValue = IFForm.cleanUpReturnValue;
//---------------------------------------------------

//---------------------------------------------------
IFForm.valueOfFormElement = function(element) {
	if (element) {
		switch (element.type) {
			case "text":
			case "textarea":
			case "hidden":
			case "select-one":
			case "select-multiple":
				return cleanUpReturnValue(jQuery(element).val());
				break;
			case "checkbox":
				return cleanUpReturnValue(valueOfCheckBoxGroup(element));
				break;
			case "radio":
				return cleanUpReturnValue(valueOfRadioButtonGroup(element));
				break;
			default:
				//alert("something else! " + element.type);
				break;
		}
	}
}
valueOfFormElement = IFForm.valueOfFormElement;

IFForm.valueOfTextField = function(textfield) {
	return jQuery(textfield).val();
}
valueOfTextField = IFForm.valueOfTextField;

IFForm.setValueOfTextField = function(value, textfield) {
	if (! jQuery(textfield).val(value).size() ) {
		console.log("can't find text field " + textfield);
	}
}
setValueOfTextField = IFForm.setValueOfTextField;

IFForm.valueOfSelect = function(select) {
	return jQuery(select).val()[0];
}
valueOfSelect = IFForm.valueOfSelect;

IFForm.valuesOfSelect = function(select) {
	return jQuery(select).val();
}
valuesOfSelect = IFForm.valuesOfSelect;

IFForm.setValueOfSelect = function(value, select) {
	if (! jQuery(select).val(value).size() ) {
		console.log("can't find select " + select);
	}
}
setValueOfSelect = IFForm.setValueOfSelect;

IFForm.setValuesOfSelect = function(values, select) {
	if (! jQuery(select).val(values).size() ) {
		console.log("can't find select " + select);
	}
}
setValuesOfSelect = IFForm.setValuesOfSelect;

IFForm.valuesOfCheckBoxGroup = function(element) {
	var values = [];
	jQuery(element).find('input:checked').each(function() { values.push(this.value) } );
	return values;
}
valuesOfCheckBoxGroup = IFForm.valuesOfCheckBoxGroup;

IFForm.setValuesOfCheckBoxGroup = function(values, group) {
	if (! jQuery(group).find(':checkbox').val(values).size() ) {
		console.log("can't find checkbox group " + group);
	}
}
setValuesOfCheckBoxGroup = IFForm.setValuesOfCheckBoxGroup;

IFForm.checkedStateOfCheckBox = function(box) {
	return jQuery(box).val();
}
checkedStateOfCheckBox = IFForm.checkedStateOfCheckBox;

IFForm.setCheckedStateOfCheckBox = function(value, box) {
	if (! jQuery(box).val(value).size() ) {
		alert("Couldn't find checkbox " + box);
	}
}
setCheckedStateOfCheckBox = IFForm.setCheckedStateOfCheckBox;

IFForm.valueOfRadioButtonGroup = function(element) {
	return jQuery(element).find(':radio:checked').val();
}
valueOfRadioButtonGroup = IFForm.valueOfRadioButtonGroup;

IFForm.setValueOfRadioButtonGroup = function(value, group) {
	if (!jQuery(group).find(':radio').val([value]).size() ) {
		alert("Couldn't find radio button group " + group);
	}
}
setValueOfRadioButtonGroup = IFForm.setValueOfRadioButtonGroup;// /usr/local/idealist/htdocs/javascript/IF/RadioButtonGroup.js
// IFRadioButtonGroup
// This componentises the behaviour of a radio button group.
// This is a complicated one because HTML doesn't bind radio buttons very well together
// so many rb's in different forms could have the same name.
// ---- constructor ----


IFRadioButtonGroup = IF.extend(IFFormComponent,function(uniqueId, bindingName, name) {
	if (! this.register(uniqueId, bindingName)) {
		console.log("No checkbox group found with id "+uniqueId);
	}	
});

IFRadioButtonGroup.prototype.value = function() {
	return jQuery(this.element).find(':radio:checked').val();
};
	
IFRadioButtonGroup.prototype.setValue = function(value) {
	jQuery(this.element).find(':radio').val([value]).size() 
};
// /usr/local/idealist/htdocs/javascript/IF/CheckBox.js
// IFCheckBox
// This componentises the behaviour of a checkbox single checkbox.
// Note that checkbox components are NOT used by the checkbox
// group component; it renders its own checkboxes.

IFCheckBox = IF.extend(IFFormComponent, function(uniqueId, bindingName) {
	if (! uniqueId) return;
	if (! this.register(uniqueId, bindingName)) {
		console.log("No checkbox found with id " + uniqueId);
	}	
});

IFCheckBox.prototype.indicateValidationFailure = function() {
	jQuery(this.element).wrap("<span style='border: 2px solid red'></span>");
	jQuery(this.element).one('change', function() { 
		this.controller.removeValidationFailure();
	 });
	this.displayErrorMessage(this.requiredErrorMessage());	
}

IFCheckBox.prototype.removeValidationFailure = function() {
	jQuery(this.element).parent().css({ 'border': '0px' });	
	this.removeValidationFailureMessage();
}

IFCheckBox.prototype.value = function() {
	return jQuery(this.element).is(':checked'); 
};
	
IFCheckBox.prototype.setValue = function(value) {
	jQuery(this.element).attr('checked',value);
};
// /usr/local/idealist/htdocs/javascript/IF/FormComponent.js
// This is NOT a "FORM" component in the
// sense you might think: that is Form.js.
// This is a component that >belongs< to a
// form (eg. TextField, Text, SubmitButton, etc.)
// but since it can be arbitrarily complex,
// I can't call it FormElement, because that
// would be misleading too.

function IFFormComponent() {
	
};
		
IFFormComponent.prototype.register = function(uniqueId, bindingName) {
	this.uniqueId = uniqueId;
	this.bindingName = bindingName;
	this.errorMessages = {};
	this.element = jQuery('#'+uniqueId)[0];
	if (! this.element) { return false; }
	this.element.controller = this;
	IFForm.registerFormComponent(this);
	return true;
};		
		
IFFormComponent.prototype.requiredErrorMessage = function() {
	return this.errorMessageForKey("IS_REQUIRED");
};
		
IFFormComponent.prototype.setRequiredErrorMessage = function(msg) {
	this.setErrorMessageForKey(msg, "IS_REQUIRED");
};
		
		// the default implementation of this will just check for a simple
		// value and if the field is required, it will return false.
		// Note that this default implementation won't work with complex
		// FormComponent subclasses that return objects.
IFFormComponent.prototype.hasValidValues = function() {
	if (this.isRequired() && !this.value()) {
		return false;
	}
	return true;
};
		
		// Another light implementation:
		// TODO: fix this to work with >different< validation failures
IFFormComponent.prototype.indicateValidationFailure = function() {
	if (!this._backupStyle) {
		this._backupStyle = {
			//backgroundColor: c.element.style.backgroundColor,
			border: jQuery(this.element).css('border')
		};
	}
	if (! this._backupStyle['border']) { this._backupStyle['border'] = '' };
	jQuery(this.element).css('border', "1px solid red");
	
	// special case that should work with most components:
	if (this.isRequired() && !this.value() && this.requiredErrorMessage()) {
		this.displayErrorMessage(this.requiredErrorMessage());
	}
	var _backupStyle = this._backupStyle;
	jQuery(this.element).one('change', function() { 
		jQuery(this).css(_backupStyle);	
		jQuery('#'+this.controller.uniqueId + "-error").html('').hide();
	 });
	jQuery(this.element).one('keypress', function() { 
		jQuery(this).css(_backupStyle);	
		jQuery('#'+this.controller.uniqueId + "-error").html('').hide();	
	});
};

IFFormComponent.prototype.removeValidationFailure = function(c) {
	c.removeValidationFailureMessage();
	jQuery(c.element).css(c._backupStyle);
};
		
IFFormComponent.prototype.bindChangeEvents = function() {
//	jQuery(this.element).once('change', this, this.removeValidationFailure);
//	jQuery(this.element).once('keydown', this, this.removeValidationFailure);
};
		
IFFormComponent.prototype.displayValidationFailureMessage = function(msg) {
	this.displayErrorMessage(msg);
};
		
IFFormComponent.prototype.removeValidationFailureMessage = function() {
	jQuery('#'+this.uniqueId + "-error").html('').hide('normal');
};
		
IFFormComponent.prototype.displayErrorMessage = function(msg) {
	jQuery('#'+this.uniqueId + "-error").html(msg).show('normal').css("error");
};
		
IFFormComponent.prototype.displayErrorMessageForKey = function(key) {
	this.displayErrorMessage(this.errorMessageForKey(key));
};
		
IFFormComponent.prototype.errorMessageForKey = function(key) {
	var em = this.errorMessages[key];
	if (!em) {
		em = this.form.controller.errorMessageForKey(key);
		if (!em) { return key; }
	}
	return em;
};
		
IFFormComponent.prototype.setErrorMessageForKey = function(msg, key) {
	this.errorMessages[key] = msg;
};

IFFormComponent.prototype.isRequired = function() {
	return this._isRequired;
};

IFFormComponent.prototype.setIsRequired = function(value) {
	this._isRequired = value;
};

IFFormComponent.prototype.validator = function() {
	if (this._validator) { return this._validator; }
	return this;
};

IFFormComponent.prototype.setValidator = function(value) {
	this._validator = value;
};
// /usr/local/idealist/htdocs/javascript/IF/Component.js
// These methods just get inherited by subclasses
// so that they "conform" to a certain interface.
// They still need to set these values themselves.

function IFComponent() {
	
};

IFComponent.prototype = {
	
	// this stub needs to be here to prevent it all from yacking:
	initialize: function() {
		
	}
	
};
// /usr/local/idealist/htdocs/javascript/IF/Validator.js
// This fairly empty class just provides a harness
// for you to implement your own custom validation
// on your components

function IFValidator(validationFn) {
	if (validationFn) { this.hasValidValues = validationFn }
};

IFValidator.prototype = {
	
	// this is what you'll need to override
	hasValidValues: function(object) {
		if (object.isRequired() && !object.value()) {
			return false;
		}
		return true;
	},

	// Another light implementation... your component
	// or your validator will most probably override this.
	indicateValidationFailure: function(c) {
		c.indicateValidationFailure(c);
	}
	
};

// useful static methods
IFValidator.isValidEmailAddress = function(v) {
	var re = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/;
//	var re = /^[\w-]+(\.[\w-]+)*\+?(\.[\w-]+)?@([\w-]+\.)+[a-zA-Z]{2,7}$/;
	return v.match(re);
};

IFValidator.isNumber = function(v) {
	var re = /^\d+(\.\d+)?$/;
	return n.match(re);
};
// /usr/local/idealist/htdocs/javascript/Idealist/ReusableComponent/ConnectionStatus.js
// This allows us to represent a component
// on the client side with a javascript
// client.

ConnectionStatus = IF.extend(IFFormComponent, function(uniqueId, bindingName) {
			this.uniqueId = uniqueId;
			this.bindingName = bindingName;
			this._properties = new Array(); // useful to put stuff in!
			jQuery('#'+uniqueId)[0].controller = this;
		},
	{		
		wrapper: function() {
		    if (this._wrapper) {
		        return this._wrapper;
		    }
		    this._wrapper = componentRegistry.componentWithName(this.bindingName);
		    if (!this._wrapper) {
				alert ("Couldn't find async wrapper for ConnectionStatus using binding name " + this.bindingName);
				return false;
			}
		    return this._wrapper;
		},

        // This is only relevant if we decide to use this as an async component
        // and not an iframe...
		connectionEditor: function() {
			// for now, just hack it with CONNECTION_EDITOR, but later
			// make it work with full binding paths
			this._connectionEditor = componentRegistry.componentWithNameRelativeToComponent("CONNECTION_EDITOR", this);
			if (!this._connectionEditor) {
				console.log("Couldn't find async wrapper for ConnectionEditor using binding name CONNECTION_EDITOR");
			}
			return this._connectionEditor;			
		},
		
		toggleDetailWithKeyForConnection: function(key, connectionExternalId) {
			console.log('Attempting to toggle key ' + key + ' on connection ' + connectionExternalId);
			this.reloadWithDictionaryAndDirectAction({
				'detail-key': key,
				'connection-eid': connectionExternalId 
			}, 'toggleDetail');
		},
	
		connectWithType: function(connectionTypeExternalId, shouldShowConnectionEditor, shouldForceConnection) {
		    console.log('Attempting to connect ' + this.sourceExternalId() + ' to ' + this.targetExternalId() + ' for type of ' + connectionTypeExternalId);
			if (shouldShowConnectionEditor) {
				//This will use the target and source
				this.displayConnectionEditor(shouldForceConnection);
			} else {
    		    this.reloadWithDictionaryAndDirectAction({'connection-type-eid': connectionTypeExternalId}, 'connect');			    
			}
		},
		
		disconnect: function(connectionExternalId) {
		    console.log('Attempting to disconnect ' + this.sourceExternalId() + ' from ' + this.targetExternalId());
		    this.reloadWithDictionaryAndDirectAction({'connection-eid': connectionExternalId}, 'disconnect');
		},
	
		displayConnectionEditor: function(shouldForceConnection) {
		    console.log('Attempting to light box the connection options for the connectivity of '+ this.sourceExternalId() + ' to ' + this.targetExternalId());
		    var aw = this.wrapper();
		    if (!aw) {
		        return false;
		    }
			// ah crap, i hate these stupid flags
			var force = "";
			if (shouldForceConnection) {
				force = "&should-force-connection=1";
			}
			var url = aw.rootUrl + 'displayConnectionEditor' + "?_lightBox=1" + force + "&" + aw.queryString;
			console.log(url);
			this.displayLightBox(url, 600, 300);
			return false;
		},
		
		// quick hack to allow editing of a single connection
		displayConnectionEditorForConnection: function(cid) {
		    console.log('Attempting to light box the connection options for the connectivity of '+ this.sourceExternalId() + ' with ' + cid);
		    var aw = this.wrapper();
		    if (!aw) {
		        return false;
		    }
			// ah crap, i hate these stupid flags
			var url = aw.rootUrl + 'displayConnectionEditor' + "?_lightBox=1&connection-eid=" + cid + "&" + aw.queryString;
		    this.displayLightBox(url, 600, 300);
			return false;		    
		},
						
		displayLightBox: function(url) {
			var reloadController = this;
			//==== Swap in a wrapper around facebox's close handler
			// 		to trigget a reload of the async components
			var originalFaceboxCloseHandler = jQuery.facebox.close
			jQuery.facebox.close = function() {
				jQuery('body').triggerHandler('connectionStatusReload'); 
				originalFaceboxCloseHandler();
			}
			//==== 
			var successHandler = function(data) {
				jQuery.facebox(data)
				jQuery(function () { jQuery('#facebox').IFBuildFormControllers() })
				// insert script
				console.log('boxing done')
				jQuery.globalEval('console.log("triggering doc ready"); jQuery.ready();')
			};
			var formSubmitHandler = function(event) {
				event.preventDefault();
				//  perform client side validation
				if (this.controller.hasValidValues()) {
					jQuery.ajax({
						url: this.action, 
						data: jQuery(this).serializeArray(),
						error: function(XMLHttpRequest, textStatus, errorThrown) {
							console.log(textStatus);
							console.log(errorThrown);
						},
						success: successHandler,
						complete: function() { 
							console.log('begin nested complete')
							jQuery('#facebox form').submit(formSubmitHandler);
						}
						//	jQuery.facebox.close();
					});
				}
				return false; 
			};
			var completeHandler = function() {
				console.log('begin complete')
				jQuery('#facebox form').submit(formSubmitHandler);
			};
			jQuery.facebox(function() {
				//jQuery('#facebox').bind('ajaxComplete',function () { console.log('global complete event') })
				jQuery.ajax({
					url: url, 
					success: successHandler,
					complete: completeHandler
				})
			});
		},
		
		
		reloadWithDictionaryAndDirectAction: function (dictionary, directAction) {
		    return this.reload(dictionary, directAction);
		},

		// TOTALLY stole this from LocationEditor... do we want to 
		// look at unifying them with a parent class?
		reload: function( dictionary, action ) {
			var aw = this.wrapper();
			if (!aw) {
				return false;
			}
			if (!dictionary) {
				dictionary = {};
			}
			// copy all 'properties' to the dictionary
			var h = this._properties;
			jQuery.each(this._properties, function(k,v) {
				dictionary[k] = v;
			});  
			this.showLoadingAnimation();
			if (action) {
				aw.reloadWithActionAndExtraQueryKeyValuePairs(action, dictionary);
			} else {
				aw.reloadWithExtraQueryKeyValuePairs(dictionary);
			}
		},
		
		showLoadingAnimation: function() {
			// whatever!
		},
		
		//Properties
		sourceExternalId: function() {
			return this._sourceExternalId;
		},
	
		setSourceExternalId: function(value) {
			this._sourceExternalId = value;
		},
	
		sourceAssetTypeExternalId: function() {
			return this._sourceAssetTypeExternalId;
		},
	
		setSourceAssetTypeExternalId: function(value) {
			this._sourceAssetTypeExternalId = value;
		},
	
		targetExternalId: function() {
			return this._targetExternalId;
		},
	
		setTargetExternalId: function(value) {
			this._targetExternalId = value;
		},
	
		targetAssetTypeExternalId: function() {
			return this._targetAssetTypeExternalId;
		},
	
		setTargetAssetTypeExternalId: function(value) {
			this._targetAssetTypeExternalId = value;
		},
		
		properties: function() {
    	    return this._properties;
    	},

    	setProperties: function(p) {
    	    this._properties = p;
    	}
	
});// /usr/local/idealist/htdocs/javascript/Idealist/ReusableComponent/ImageQuote.js
// This renders one of the overlib divs with image / comment

ImageQuote = IF.extend(IFComponent, function(uniqueId, bindingName) {
			this.uniqueId = uniqueId;
			this.bindingName = bindingName;
			this._shouldDisplay = true;
			this._properties = new Array(); // useful to put stuff in!
		},
{		
		translationDictionary: function() {
			if (! this._translationDictionary) {
				console.log("Translation Dictionary was not set and it should have been.  Initializing in English.");
		        this.setTranslationDictionary({ 'IQ_CLICK_TO_READ_MORE': 'Click to read more' });
			}
		    return this._translationDictionary;
		},
		
		setTranslationDictionary: function(value) {
		    this._translationDictionary = value;
		},
		
		properties: function() {
    	    return this._properties;
    	},

    	setProperties: function(p) {
    	    this._properties = p;
    	},

		render: function() {
            var props = this.properties();
            var dic = this.translationDictionary();

			var htmlStr = "<div class='PhotoGalleryOverlibBox'><div height='126' style='position:relative; height:126px; text-align:center; padding:5px; '>";
			if (('imageUrl' in props) && props.imageUrl.length) {
				htmlStr = htmlStr + "<img src='"+props.imageUrl+"'>";
			}
			htmlStr = htmlStr + "</div><div class='PhotoGalleryOverlibComment'>"+props.quoteText+"</div><div class='PhotoGalleryOverlibName highlight'>"+props.creditText+"<div class='color03 fontSmall'>"+dic.IQ_CLICK_TO_READ_MORE+"</div></div></div>";
			return overlib(htmlStr, HAUTO, VAUTO, FULLHTML);
		},

		hide: function() {
			return nd();
		}
});
// /usr/local/idealist/htdocs/javascript/Idealist/ReusableComponent/RSSViewer.js
// NOTE:  The title attributes on the span tags in the html prototype are
//  	crucial to us finding them and filling them with content

function RSSViewer(uniqueId, url, options) {
	if (!uniqueId) {
		return;
	}
	defaultOptions = { descriptionLength: 400 }
	this.options = jQuery.extend(defaultOptions, options || {});
	this.uniqueId = uniqueId;
	this.url = url;
	this.items = {};
	var controller = this;
	
	jQuery.ajax({
		url: url,
		//async: false,
		dataType: (jQuery.browser.msie) ? "text" : "xml", 
		success: function(data) {
            var xml; 
            if (typeof data == 'string') { 
                xml = new ActiveXObject("Microsoft.XMLDOM"); 
                xml.async = false; 
                xml.loadXML(data); 
            } else { 
                xml = data; 
            } 		    
		    controller.onSuccess(xml)
		},
		error: function(xhr, status, error) { controller.onFailure(xhr, status, error); }
	})
}

RSSViewer.prototype = {

	onFailure: function (XMLHttpRequest, textStatus, errorThrown) {
	  	console.log('ajax request error: '+textStatus+' '+errorThrown);
		var errMsg = 'An error occurred loading rss feed: '+textStatus+' '+errorThrown;
		jQuery('li.rssLoading').html(errMsg);
	},
	
	onSuccess : function( xml ) {
		var options = this.options;
		var feed = jQuery(xml);
		
		var domRoot = jQuery('#'+this.uniqueId);
		domRoot.find('.rssTitle').html(feed.find("channel title").text());

		var list = domRoot.find('ul.rssItemList');
		var proto = domRoot.find('li.rssPrototype');
		var loading = domRoot.find('li.rssLoading');

		// delete the row with the loading message
		loading.remove();	

	    feed.find('item').each(function() {
			var item = jQuery(this);
			// grab a deep copy of the prototype row
			var row = jQuery(proto).clone();

			var description = item.find('description').text();
			var stripped = IF.stripTags(description);
			var shortDescription = stripped.substr(0,options.descriptionLength);
			if (shortDescription.length < stripped.length) {
				description = shortDescription + '...';
			} 

			row.find('span[title=_author]').html(item.find('author').text());
			row.find('span[title=_date]').html(item.find('pubDate').text());
			row.find('span[title=_description]').html(description);

			row.find('a').attr('href',item.find('link').text()).html(item.find('title').text());

			// counter-intuitive, but because the original row has an id, and is also
			// hidden, easiest thing to do is slap a fresh anonymous row around the content
			row.appendTo(list).show();
	   })
	}
}// /usr/local/idealist/htdocs/javascript/Idealist/ReusableComponent/AssetMapController.js
// Represent a map of assets using Google Maps.
// It's better to show things that have already been geocoded
// because calling the google geocoder a zillion times from the client
// is a bit bogus.
//

AssetMapController = IF.extend(
    IFFormComponent,
        function(uniqueId, bindingName, updateUrl) {
			this.uniqueId = uniqueId;
			this.bindingName = bindingName;
			this.updateUrl = updateUrl;
			this.element = jQuery('#'+uniqueId)[0];
            this.zoomCount = 0;
            this.assetsByType = [];
            this.iconsByType = [];
		},
		{
		    bindToMap: function(map) {
		        this.setMap(map);
		        map.setController(this);
		        
		        var foo = this;
		        // add event listeners here
		        // map.map()? really?
		        GEvent.addListener(map.map(), "zoomend", function() {
		            //var dim = map.dimensions(); 
		            console.log("new zoom");
		            if (foo.zoomCount > 0) {
		                foo.refreshMap();
		            }
		            foo.zoomCount++;
		        });
		    },
            refreshMap: function() {
                var map = this.map();
                map.map().clearOverlays();
                map.clearAssets();
                var types = jQuery('input[name='+this.uniqueId+'-assetTypes]:checked');
                var limit = jQuery('input[name='+this.uniqueId+'-limit]:checked').val();
                var assetTypes = new Array();

                types.each(function () {
                    assetTypes[assetTypes.length] = jQuery(this).val();
                });

                //console.log("Loading data for asset types " + assetTypes);
                var d = map.dimensions();
                var qd = { assetType: assetTypes,
                               limit: limit,
                            "_async": 1,
                               width: d.width,
                              height: d.height,
                              centre: d.centre.toUrlValue(),
                              radius: d.radius
                };

                jQuery.ajax({
                    url: this.updateUrl,
                    data: qd,
                    type: 'GET',
                    success: function(data) {
                        // strip the dopey xml header
                        data = data.replace('<?xml version="1.0" encoding="utf-8" ?>', "");
                	    var _items = eval(data);
                	    map.addAssets(_items);
                	    map.render();
                    },
                    error: function(XMLHttpRequest, textStatus, errorThrown) {
                	    console.log('Error: '+textStatus+' - '+errorThrown);
                    }
                });
                if (types.length == 0) {
                    map.render();
                }
                return false;
            },
            
            markerAtPointForItem: function(point, item) {
                //console.log("adding marker for item " + item.mappingTitle);
                var foo = this;
                if (!item.mappingSummary) {
                    var marker = new GMarker(point, this.iconForItem(item));
        			// now add an event listener?
        			GEvent.addListener(marker, "click", function() {
        				var info = "";

        				if (item.mappingViewerUrl) {
        					info = info + "<h2><a href='" + item.mappingViewerUrl + "' target='_blank'>" + item.mappingTitle + "</a></h2>"
        				} else {
        					info = info + "<h2>" + item.mappingTitle + "</h2>"
        				}

        				info = info + "<div style='overflow:auto;width:300px;height:60px;font-size:9px;'>" + item.mappingDescription + "</div><br />";			
        				info = info + "<em>" + item.mappingAddress + "</em>";

        				info = "<div class='map-info-window'>" + info + "</div>";

        				marker.openInfoWindowHtml(info);
        			});
        			return marker;
                }
                var marker = new GMarker(point, this.iconForItem(item));
                GEvent.addListener(marker, "click", function() {
                    var d = foo.map().dimensions();
                    var width = d.width;
                    var ratio = width / 125;
                    console.log("zoom ratio is " + ratio);
                    
                    var czl = foo.map().map().getZoom();
                    console.log("czl is " + czl);
                    var nextZoomLevel = czl + ratio;
                    console.log("nzl is " + nextZoomLevel);
                    if (nextZoomLevel > 12) { nextZoomLevel = 12 }
                    
                    foo.map().map().setCenter(marker.getLatLng());
                    foo.map().map().setZoom(nextZoomLevel);
                });
                return marker;
            },

    		iconForItem: function(item) {
    		    var type = item.mappingAssetType;
    		    
    		    if (this.iconsByType[type] && !item.mappingSummary) {
    		        return this.iconsByType[type];
    		    }
    		    var icon = new GIcon();
    		    if (item.mappingSummary) {
    		        icon.image = '/images/gmaps/idealistIcon.png';
    		    } else if (item.mappingAssetType == "Org") {
    		        icon.image = '/images/gmaps/org.png';
    		    } else if (item.mappingAssetType == "User") {
    		        icon.image = '/images/gmaps/user.png';
    		    } else {
    	            icon.image = '/images/gmaps/listing.png';
    	        }
    	        icon.shadow = '/images/gmaps/idealistIconShadow.png';
    	        icon.iconSize = new GSize(21, 35);
    	        icon.shadowSize = new GSize(43, 35);
    	        icon.iconAnchor = new GPoint(10, 35);
    	        icon.infoWindowAnchor = new GPoint(10, 2);
    	        icon.infoShadowAnchor = new GPoint(10, 2);
    	        if (!item.mappingSummary) {
        	        this.iconsByType[type] = icon;
        	    }
        	    return icon;
    		},
    		
            map: function() {
    	        return this._map;
    	    },
    	    setMap: function(map) {
                this._map = map;
    	    }
	    }
);// /usr/local/idealist/htdocs/javascript/Idealist/ReusableComponent/LocationEditor.js
// This allows us to represent a component
// on the client side with a javascript
// client.
//----- (c)GPL, apv
String.prototype.ucFirst = function () {
   return this.substr(0,1).toUpperCase() + this.substr(1,this.length);
}
// ---- constructor ----
LocationEditor = IF.extend(IFFormComponent, function(uniqueId, bindingName) {
		this.register(uniqueId, bindingName);
		this._properties = {};
		this._originalValues = {};
	},
	{
	registerHiddenFields: function( ) {
	    // register the field
		var f = this.form;
		if (!f) {
			alert("Couldn't find form for " + this.bindingName());
			return;
		}
		
	},
	
	reload: function( dictionary ) {
		var aw = componentRegistry.componentWithName(this.bindingName);
		if (!aw) {
			alert ("Couldn't find async wrapper for LocationEditor");
			return false;
		}
		if (!dictionary) {
			dictionary = {};
		}
		dictionary.clientSideName = this.bindingName;
		dictionary.clientSideId = this.uniqueId;
		if (this.shouldSetRegion()) {
		    dictionary.regions   = this.regions().join('::');
		}
		if (this.countries()) {
			dictionary.countries = this.countries().join("::");
		}
		if (this.states()) {
			dictionary.states    = this.states().join("::");
		}
		if (this.cities()) {
			dictionary.cities    = this.cities().join("::");
		}



		// copy all 'properties' to the dictionary
		var h = this._properties;
		jQuery.each(this._properties, function(k,v) {
			dictionary[k] = v;
		});  
		
		dictionary.sid       = "x"; // IF::Session::NULL_SESSION_ID()

		this.showLoadingAnimation();
		aw.reloadWithExtraQueryKeyValuePairs(dictionary);
	},

	reloadWithOriginalValues: function( dictionary ) {
		this.setRegions(this.originalValueForKey("regions"));
		this.setCountries(this.originalValueForKey("countries"));
		this.setStates(this.originalValueForKey("states"));
		this.setCities(this.originalValueForKey("cities"));
		this.setStateTypeName();
		
		this.reload(dictionary);
	},

	showLoadingAnimation: function() {
		var wrapper = jQuery('#'+this.uniqueId + "-wrapper")[0];
		if (wrapper) {
			wrapper.innerHTML = '<div style="padding:3px; text-align:center;"><img src="/images/loading.gif" alt="please wait..."/></div>';
		} else {
			console.log("couldn't find wrapper for "+this.uniqueId);
		}
	},
	
    takeValueForKey: function(value, key) {
        var setterMethodName = "set" + key.ucFirst();
        //alert(setterMethodName);
        setterMethod = this[setterMethodName];
        if (setterMethod) {
            //alert("Setting "+key+" to "+value);
            setterMethod.apply(this, [[value]]);
        } else {
            alert("object has no method named "+setterMethodName);
        }
    },
    
	takeValueAtIndexForKey: function(value, index, key) {
		getterMethod = this[key];
		if (getterMethod) {
			var values = getterMethod.apply(this);
			values[index] = value;
	        setterMethod = this["set"+key.ucFirst()];
	        if (setterMethod) {
	            console.log("Setting "+key+" to "+ values);
	            setterMethod.apply(this, [values]);
			}
		} else {
			alert("Object has no method named "+getterMethodName);
		}
	},
	
	shouldSetRegion: function() {
		return this._properties.shouldSetRegion;
	},
	
	setShouldSetRegion: function( value ) {
		this._properties.shouldSetRegion = value;
	},
	
	regions: function() {
		return this._regions;
	},
	
	setRegions: function( array ) {
	    if (!this.shouldSetRegion()) {
	        return;
	    }
		if (this.arraysAreDifferent(this._regions, array)) {
			this._regions = array;
			jQuery(this.regionFieldSelector())[0].controller.setValue(array.join("::"));
			//alert("Setting regions to " + this._regionElement.val());
		}
		this.setCountries(new Array());
	},
	
	countries: function() {
		return this._countries;
	},
	
	setCountries: function( array ) {
		if (this.arraysAreDifferent(this._countries, array)) {
			this._countries = array;
			jQuery(this.countryFieldSelector())[0].controller.setValue(array.join("::"));
		}
		this.setStates(new Array());
		this.setStateTypeName("");
	},
	
	states: function() {
		return this._states;
	},

	setStates: function( array ) {
		if (this.arraysAreDifferent(this._states, array)) {
			this._states = array;
			jQuery(this.stateFieldSelector())[0].controller.setValue(array.join("::"));
		}
		this.setCities(new Array());
	},	
	
	cities: function() {
		return this._cities;
	},
	
	setCities: function( array ) {
		this._cities = array;
		jQuery(this.cityFieldSelector())[0].controller.setValue(array.join("::"));
		console.log("Setting city to " + array[0]);
	},
	
	originalValueForKey: function( key ) {
		return this._originalValues[key];
	},
	
	setOriginalValueForKey: function( value, key ) {
		this._originalValues[key] = value;
	},
 	
	stateTypeName: function () {
		return this._properties.stateTypeName;
	},
	
	setStateTypeName: function (value) {
		//alert("setting to " + value);
		this._properties.stateTypeName = value;
	},
	
	allowsMultipleLeafNodes: function () {
		return this._properties.allowsMultipleLeafNodes;
	},
	
	setAllowsMultipleLeafNodes: function (value) {
		this._properties.allowsMultipleLeafNodes = value;
	},
	
	properties: function() {
	    return this._properties;
	},
	
	setProperties: function(p) {
	    this._properties = p;
	},
	
	regionFieldSelector: function() {
		return '[name='+this.regionFieldName()+']:hidden'
	},
	regionFieldName: function() {
	    return this._properties.regionFieldName || this.uniqueId + "-regions";
	},
	countryFieldSelector: function() {
		return '[name='+this.countryFieldName()+']:hidden'
	},
	countryFieldName: function() {
	    return this._properties.countryFieldName || this.uniqueId + "-countries";
	},
	stateFieldSelector: function() {
		return '[name='+this.stateFieldName()+']:hidden'
	},
	stateFieldName: function() {
	    return this._properties.stateFieldName || this.uniqueId + "-states";
	},
	cityFieldSelector: function() {
		return '[name='+this.cityFieldName()+']:hidden'
	},
	cityFieldName: function() {
	    return this._properties.cityFieldName || this.uniqueId + "-cities";
	},
	
	
	
	locationString: function () {
		var locations = new Array();
		if (this.cities().length > 0) {
			locations[locations.length] = this.cities().join(", ");
		}
		if (this.states().length > 0) {
			locations[locations.length] = this.states().join(", ");
		}
		if (this.countries().length > 0) {
			locations[locations.length] = this.countries().join(", ");
		}
		return locations.join(", ");	
	},
	
	renderMapIntoElementWithId: function (id) {
	  if (GBrowserIsCompatible()) {
		var gc = this._gClientGeoCoder();
		if (!gc) { alert ("No google maps geocoder found!"); return; }
		//alert("Trying to map " + this.locationString());
		gc.getLatLng(this.locationString(), function(ll) {
			if (!ll) {
				//alert("Couldn't find that address!");
				return;
			}
			//alert("Lat is " + ll.lat() + " Lon is " + ll.lng());
		    var map = new GMap2(jQuery('#'+id)[0]);
		    map.setCenter(ll, 6);			
		});
	  }		
	},
	
	_gClientGeoCoder: function () {
		this._geoCoder = new GClientGeocoder();
		return this._geoCoder;
	},
	
	// useful
	arraysAreDifferent: function( a, b ) {
		if (!a || !b) { return true; }
		if (a.length != b.length) { return true; }
		for (var i=0; i<a.length; i++) {
			if (a[i] != b[i]) {
				return true;
			}
		}
		return false;
	}
}
);
// /usr/local/idealist/htdocs/javascript/Idealist/ReusableComponent/ShowHideCheckBox.js
// JS class for the checkbox that reveals/hides other content

// ---- constructor ----
ShowHideCheckBox = IF.extend(IFCheckBox, function(uniqueId, bindingName) {
			if (! this.register(uniqueId, bindingName)) {
				console.log("No show/hide checkbox found with id "+uniqueId);
				return;
			}

			// find the form
			var f = this.form;
			if (!f) {
				console.log("Couldn't find form for " + uniqueId);
				return;
			}
			
			// find the content
			this._content = jQuery('#'+uniqueId+'-content')[0];
			if (! this._content) {
				console.log("Couldn't find the content for the ShowHideCheckBox " + uniqueId);
			}

			// find the checkbox
			this._checkbox = jQuery(this.element).children().filter(':checkbox')[0];
			if (!this._checkbox) {
				console.log("Couldn't find the checkbox for the ShowHideCheckBox " + uniqueId);
			}
			
			this.update();
		},
{				
		update: function() {
			if (!this._checkbox || this._checkbox.checked) {
				this.show();
			} else {
				this.hide();
			}
		},
		
		show: function() {
			jQuery(this._content).show();			
		},
		
		hide: function() {
			jQuery(this._content).hide();		
		}
});
// /usr/local/idealist/htdocs/javascript/Idealist/ReusableComponent/KeyValueChooser.js
// KeyValueChooser

KeyValueChooser = IF.extend(IFFormComponent, function(uniqueId, bindingName) {
	this.uniqueId = uniqueId;
	this.bindingName = bindingName;
	this.element = jQuery('#'+uniqueId)[0];
	if (!this.element) {
		console.log("No textfield found with id "+uniqueId);
	}
	this.element.controller = this;
	
	// grab the popUpMenu component and remember its element
	var els = IFForm.formElementsBelowElement(this.element);
	this._select = els[0];
	
	IFForm.registerFormComponent(this);
});

KeyValueChooser.prototype.value = function() {
	return IFForm.valueOfFormElement(this._select); // just return the value of the actual text box
};
	
KeyValueChooser.prototype.setValue = function(value) {
	IFForm.setValueOfFormElement(this._select, value);
};
// /usr/local/idealist/htdocs/javascript/Idealist/ReusableComponent/ViewerController.js
// This function allows us to add viewers to an array
// and handle them to show/hide 
//

var viewerBoxes = new Array();
function selectViewerBox(ViewerBox) { 
	jQuery.each(viewerBoxes, function() { this.hide() }) 
	jQuery(ViewerBox).show();
} 

// /usr/local/idealist/htdocs/javascript/Idealist/ReusableComponent/swfobject.js
/**
 * SWFObject v1.5: Flash Player detection and embed - http://blog.deconcept.com/swfobject/
 *
 * SWFObject is (c) 2007 Geoff Stearns and is released under the MIT License:
 * http://www.opensource.org/licenses/mit-license.php
 *
 */
if(typeof deconcept=="undefined"){var deconcept=new Object();}if(typeof deconcept.util=="undefined"){deconcept.util=new Object();}if(typeof deconcept.SWFObjectUtil=="undefined"){deconcept.SWFObjectUtil=new Object();}deconcept.SWFObject=function(_1,id,w,h,_5,c,_7,_8,_9,_a){if(!document.getElementById){return;}this.DETECT_KEY=_a?_a:"detectflash";this.skipDetect=deconcept.util.getRequestParameter(this.DETECT_KEY);this.params=new Object();this.variables=new Object();this.attributes=new Array();if(_1){this.setAttribute("swf",_1);}if(id){this.setAttribute("id",id);}if(w){this.setAttribute("width",w);}if(h){this.setAttribute("height",h);}if(_5){this.setAttribute("version",new deconcept.PlayerVersion(_5.toString().split(".")));}this.installedVer=deconcept.SWFObjectUtil.getPlayerVersion();if(!window.opera&&document.all&&this.installedVer.major>7){deconcept.SWFObject.doPrepUnload=true;}if(c){this.addParam("bgcolor",c);}var q=_7?_7:"high";this.addParam("quality",q);this.setAttribute("useExpressInstall",false);this.setAttribute("doExpressInstall",false);var _c=(_8)?_8:window.location;this.setAttribute("xiRedirectUrl",_c);this.setAttribute("redirectUrl","");if(_9){this.setAttribute("redirectUrl",_9);}};deconcept.SWFObject.prototype={useExpressInstall:function(_d){this.xiSWFPath=!_d?"expressinstall.swf":_d;this.setAttribute("useExpressInstall",true);},setAttribute:function(_e,_f){this.attributes[_e]=_f;},getAttribute:function(_10){return this.attributes[_10];},addParam:function(_11,_12){this.params[_11]=_12;},getParams:function(){return this.params;},addVariable:function(_13,_14){this.variables[_13]=_14;},getVariable:function(_15){return this.variables[_15];},getVariables:function(){return this.variables;},getVariablePairs:function(){var _16=new Array();var key;var _18=this.getVariables();for(key in _18){_16[_16.length]=key+"="+_18[key];}return _16;},getSWFHTML:function(){var _19="";if(navigator.plugins&&navigator.mimeTypes&&navigator.mimeTypes.length){if(this.getAttribute("doExpressInstall")){this.addVariable("MMplayerType","PlugIn");this.setAttribute("swf",this.xiSWFPath);}_19="<embed type=\"application/x-shockwave-flash\" src=\""+this.getAttribute("swf")+"\" width=\""+this.getAttribute("width")+"\" height=\""+this.getAttribute("height")+"\" style=\""+this.getAttribute("style")+"\"";_19+=" id=\""+this.getAttribute("id")+"\" name=\""+this.getAttribute("id")+"\" ";var _1a=this.getParams();for(var key in _1a){_19+=[key]+"=\""+_1a[key]+"\" ";}var _1c=this.getVariablePairs().join("&");if(_1c.length>0){_19+="flashvars=\""+_1c+"\"";}_19+="/>";}else{if(this.getAttribute("doExpressInstall")){this.addVariable("MMplayerType","ActiveX");this.setAttribute("swf",this.xiSWFPath);}_19="<object id=\""+this.getAttribute("id")+"\" classid=\"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000\" width=\""+this.getAttribute("width")+"\" height=\""+this.getAttribute("height")+"\" style=\""+this.getAttribute("style")+"\">";_19+="<param name=\"movie\" value=\""+this.getAttribute("swf")+"\" />";var _1d=this.getParams();for(var key in _1d){_19+="<param name=\""+key+"\" value=\""+_1d[key]+"\" />";}var _1f=this.getVariablePairs().join("&");if(_1f.length>0){_19+="<param name=\"flashvars\" value=\""+_1f+"\" />";}_19+="</object>";}return _19;},write:function(_20){if(this.getAttribute("useExpressInstall")){var _21=new deconcept.PlayerVersion([6,0,65]);if(this.installedVer.versionIsValid(_21)&&!this.installedVer.versionIsValid(this.getAttribute("version"))){this.setAttribute("doExpressInstall",true);this.addVariable("MMredirectURL",escape(this.getAttribute("xiRedirectUrl")));document.title=document.title.slice(0,47)+" - Flash Player Installation";this.addVariable("MMdoctitle",document.title);}}if(this.skipDetect||this.getAttribute("doExpressInstall")||this.installedVer.versionIsValid(this.getAttribute("version"))){var n=(typeof _20=="string")?document.getElementById(_20):_20;n.innerHTML=this.getSWFHTML();return true;}else{if(this.getAttribute("redirectUrl")!=""){document.location.replace(this.getAttribute("redirectUrl"));}}return false;}};deconcept.SWFObjectUtil.getPlayerVersion=function(){var _23=new deconcept.PlayerVersion([0,0,0]);if(navigator.plugins&&navigator.mimeTypes.length){var x=navigator.plugins["Shockwave Flash"];if(x&&x.description){_23=new deconcept.PlayerVersion(x.description.replace(/([a-zA-Z]|\s)+/,"").replace(/(\s+r|\s+b[0-9]+)/,".").split("."));}}else{if(navigator.userAgent&&navigator.userAgent.indexOf("Windows CE")>=0){var axo=1;var _26=3;while(axo){try{_26++;axo=new ActiveXObject("ShockwaveFlash.ShockwaveFlash."+_26);_23=new deconcept.PlayerVersion([_26,0,0]);}catch(e){axo=null;}}}else{try{var axo=new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7");}catch(e){try{var axo=new ActiveXObject("ShockwaveFlash.ShockwaveFlash.6");_23=new deconcept.PlayerVersion([6,0,21]);axo.AllowScriptAccess="always";}catch(e){if(_23.major==6){return _23;}}try{axo=new ActiveXObject("ShockwaveFlash.ShockwaveFlash");}catch(e){}}if(axo!=null){_23=new deconcept.PlayerVersion(axo.GetVariable("$version").split(" ")[1].split(","));}}}return _23;};deconcept.PlayerVersion=function(_29){this.major=_29[0]!=null?parseInt(_29[0]):0;this.minor=_29[1]!=null?parseInt(_29[1]):0;this.rev=_29[2]!=null?parseInt(_29[2]):0;};deconcept.PlayerVersion.prototype.versionIsValid=function(fv){if(this.major<fv.major){return false;}if(this.major>fv.major){return true;}if(this.minor<fv.minor){return false;}if(this.minor>fv.minor){return true;}if(this.rev<fv.rev){return false;}return true;};deconcept.util={getRequestParameter:function(_2b){var q=document.location.search||document.location.hash;if(_2b==null){return q;}if(q){var _2d=q.substring(1).split("&");for(var i=0;i<_2d.length;i++){if(_2d[i].substring(0,_2d[i].indexOf("="))==_2b){return _2d[i].substring((_2d[i].indexOf("=")+1));}}}return "";}};deconcept.SWFObjectUtil.cleanupSWFs=function(){var _2f=document.getElementsByTagName("OBJECT");for(var i=_2f.length-1;i>=0;i--){_2f[i].style.display="none";for(var x in _2f[i]){if(typeof _2f[i][x]=="function"){_2f[i][x]=function(){};}}}};if(deconcept.SWFObject.doPrepUnload){if(!deconcept.unloadSet){deconcept.SWFObjectUtil.prepUnload=function(){__flash_unloadHandler=function(){};__flash_savedUnloadHandler=function(){};window.attachEvent("onunload",deconcept.SWFObjectUtil.cleanupSWFs);};window.attachEvent("onbeforeunload",deconcept.SWFObjectUtil.prepUnload);deconcept.unloadSet=true;}}if(!document.getElementById&&document.all){document.getElementById=function(id){return document.all[id];};}var getQueryParamValue=deconcept.util.getRequestParameter;var FlashObject=deconcept.SWFObject;var SWFObject=deconcept.SWFObject;// /usr/local/idealist/htdocs/javascript/Idealist/ReusableComponent/TimeDifference.js
// This allows us to represent a component
// on the client side with a javascript
// client.

TimeDifference = IF.extend(IFFormComponent,function(uniqueId, bindingName) {
		this.uniqueId = uniqueId;
		this.bindingName = bindingName;
		this._shouldDisplay = true;
		this._properties = new Array(); // useful to put stuff in!
	},
{		
		calculateSinceUnixTime: function (unixTime) {
		    //A blank date is the same as Now
		    var firstDate = new Date();
		    var secondDate = new Date(unixTime * 1000);
		    this.calculate(firstDate, secondDate);
		},
		
		calculate: function(firstDate, secondDate) {
		    //Diff is in milliseconds
			var diff = firstDate.getTime() - secondDate.getTime();
			var prop = this.properties();
			if (prop && prop['maximumNumberOfMilliseconds']) {
				if (diff > prop['maximumNumberOfMilliseconds']) {
					this.setShouldDisplay(false);
				}
			}
		    //Constants
            var second = 1000, minute = 60 * second, hour = 60 * minute, day = 24 * hour;
            this.setDays(Math.floor(diff / day));
        	diff -= this.days() * day;
        	this.setHours(Math.floor(diff / hour));
        	diff -= this.hours() * hour;
        	this.setMinutes(Math.floor(diff / minute));
        	diff -= this.minutes() * minute;
        	this.setSeconds(Math.floor(diff / second));
		},
		
		shouldDisplay: function() {
			return this._shouldDisplay;
		},
		
		setShouldDisplay: function(value) {
			this._shouldDisplay = value;
		},
		
		updateDisplay: function() {
		    this.updateDisplayWithId(this.uniqueId + '_display');
		},
		
		updateDisplayWithId: function(displayId) {
		    var props = this.properties();		    
		    var dic = this.translationDictionary();
		    var display = '';
		    if (props['displayStyle'] == 'STANDARD') {
		        if (this.days() > 0) {
		            display += this.days() + ' ' + (this.days() == 1 ? dic['TD_DAY'] : dic['TD_DAYS']);
		        } else if (this.hours() > 0) {
		            display += this.hours() + ' ' + (this.hours() == 1 ? dic['TD_HOUR'] : dic['TD_HOURS']);
		        } else if (this.minutes() > 0) {
		            display += this.minutes() + ' ' + (this.minutes() == 1 ? dic['TD_MINUTE'] : dic['TD_MINUTES']);
		        } else {
		            display += this.seconds() + ' ' + (this.seconds() == 1 ? dic['TD_SECOND'] : dic['TD_SECONDS']);
		        }
		    } else if (props['displayStyle'] == 'FULL') {
		        if (this.days() > 0) {
		            display += this.days() + ' ' + (this.days() == 1 ? dic['TD_DAY'] : dic['TD_DAYS']) + ', ';
		        }
		        if (this.hours() > 0) {
		            display += this.hours() + ' ' + (this.hours() == 1 ? dic['TD_HOUR'] : dic['TD_HOURS']) + ', ';
		        }
		        if (this.minutes() > 0) {
		            display += this.minutes() + ' ' + (this.minutes() == 1 ? dic['TD_MINUTE'] : dic['TD_MINUTES']) + ', ';
		        }
		        if (this.seconds() > 0) {
		            display += this.seconds() + ' ' + (this.seconds() == 1 ? dic['TD_SECOND'] : dic['TD_SECONDS']);
		        }
		    }
			if (props['template']) {
				var template = dic[props['template']];
				if (template) {
					var re = new RegExp("\\$\\{timeDifference\\}", "g");
					display = template.replace(re, display);
				}
			}
			if (this.shouldDisplay()) {
				jQuery('#' + displayId).text(display);
			}
		},
		
		days: function() {
		    return this._days;
		},
		
		setDays: function(value) {
		    this._days = value;
		},
		
		hours: function() {
		    return this._hours;
		},
		
		setHours: function(value) {
		    this._hours = value;
		},
		
		minutes: function() {
		    return this._minutes;
		},
		
		setMinutes: function(value) {
		    this._minutes = value;
		},
		
		seconds: function() {
		    return this._seconds;
		},
		
		setSeconds: function(value) {
		    this._seconds = value;
		},
		
		translationDictionary: function() {
			if (! this._translationDictionary) {
				console.log("Translation Dictionary was not set and it should have been.  Initializing in English.");
		        this.setTranslationDictionary({
		                                        'TD_UPDATED_TEMPLATE': 'Updated ${timeDifference} ago',
		                                        'TD_DAYS': 'days', 
		                                        'TD_DAY': 'day', 
		                                        'TD_HOURS': 'hours', 
		                                        'TD_HOUR': 'hour', 
		                                        'TD_MINUTES': 'minutes',
		                                        'TD_MINUTE': 'minute',
		                                        'TD_SECONDS': 'seconds',
		                                        'TD_SECOND': 'second'
		                                        });
			}
		    return this._translationDictionary;
		},
		
		setTranslationDictionary: function(value) {
		    this._translationDictionary = value;
		},
		
		properties: function() {
			if (!this._properties) {
		       console.log("Properties were not set and they should have been.  Initializing with defaults.");
		       this.setProperties({
		                            'displayStyle': 'STANDARD',
		                            'template': 'TD_UPDATED_TEMPLATE'
		                        });
		    }
    	    return this._properties;
    	},

    	setProperties: function(p) {
    	    this._properties = p;
    	}

});// /usr/local/idealist/htdocs/javascript/Idealist/ReusableComponent/ObjectArrayEditor.js
// This allows us to represent a component
// on the client side with a javascript
// client.

// ---- constructor ----
ObjectArrayEditor = IF.extend(IFFormComponent, function(uniqueId, bindingName) {
    this.uniqueId = uniqueId;
	this.bindingName = bindingName;
    this.element = jQuery('#'+uniqueId)[0];
    if (!this.element) {
        console.log("Couldn't find component " + uniqueId);
    }
	this.editor = jQuery('#'+this.uniqueId + "-editors")[0];
	if (!this.editor) {
		 console.log ("Couldn't find editor field");
	}
	
	this.template = jQuery('#'+this.uniqueId + "-template")[0];
	if (!this.template) {
		 console.log ("Couldn't find editor template");
	}
	this.element.controller = this;
	IFForm.registerFormComponent(this); // register with my form, also sets my form property
	IF.controllers[uniqueId] = this;
	this.init();
});
	
	// initialise with basic sane values
ObjectArrayEditor.prototype.init = function() {
	this.userCanChangeSize = true;
	this.minimumNumberOfFields = 1;
	this.maximumNumberOfFields = 0;
	this.allowsNoSelection = true;
	this.startingNumberOfFields = 1;
	this.editorClass = "";
	this.editors = new Array();
	this.objects = new Array();
};

	// initWith is a bit cheesy but it'll have
	// to do for now; no named argument parsing/passing
	// in javascript!
ObjectArrayEditor.prototype.initWith = function(parameters) {
	this.init();
	var properties = ['userCanChangeSize', 'minimumNumberOfFields', 'maximumNumberOfFields', 'allowsNoSelection', 'startingNumberOfFields', 'editorClass'];
	for (var i=0; i<properties.length; i++) {
		if (parameters[properties[i]]) {
			this[properties[i]] = parameters[properties[i]];
		}
	}

	if (this.objects.length < this.startingNumberOfFields &&
		this.objects.length >= this.minimumNumberOfFields) {
		this.numberOfVisibleFields = this.objects.length;
	} else {
		this.numberOfVisibleFields = this.startingNumberOfFields;
	}
};
	
	// this renders the array editor
ObjectArrayEditor.prototype.render = function() {
	// clear what's there:
	jQuery(this.editor).html(""); 
	
	for (var i=0; i<this.numberOfVisibleFields; i++) {
		// create the snippet from the template and
		// renumber it
		var newNode = this.newNode();
		newNode.setAttribute("id", this.uniqueId + "-editor-" + i);
		this.renumberChildNodesOfNodeWithNumber(newNode, i);
		this.editor.appendChild(newNode);
		
		// create a new editor object
		var newEditor = this.newEditorAtIndex(i);
		
		var object;
		if (i >= this.objects.length) {
			object = newEditor.newObject();
		} else {
			object = this.objects[i];
		}
		newEditor.setObject(object);
		this.editors[i] = newEditor;
	}
	
	this.showOrHideControls();
};

	//
ObjectArrayEditor.prototype.newNode = function() {
		var newEntry = this.template.cloneNode(true);
		newEntry.style.display = "block";
		return newEntry;
};
	
	// Add a field to the array
	// and re-render.  This is called
	// when the user clicks to add a field
ObjectArrayEditor.prototype.addField = function() {
		this.updateFromFields();
		this.numberOfVisibleFields++;
		this.render();
		// add an editor
};
	
	// Remove a field from the array
	// and re-render.
ObjectArrayEditor.prototype.removeField = function() {
		this.updateFromFields();
		this.numberOfVisibleFields--;
		this.render();
};
	
	// this simply adds an object
ObjectArrayEditor.prototype.addObject = function(object) {
	this.objects[this.objects.length] = object;
	if (this.objects.length > this.numberOfVisibleFields 
		&& ((this.maximumNumberOfFields > 0 && this.objects.length < this.maximumNumberOfFields)
		 	|| this.maximumNumberOfFields == 0)) {
		this.addField();
	}
	//window.console.log("Added object " + object);
};

ObjectArrayEditor.prototype.newEditorAtIndex = function(index) {
	var newNodeId = this.uniqueId + "-editor-" + index;
	var newString = "new "+ this.editorClass + "('"+ newNodeId + "')";
	//alert(newString);
	var newEditor = eval(newString);
	if (!newEditor) {
		alert("Error creating instance of "+ this.editorClass);
		return null;
	}
	return newEditor;
};
	
	// Based on the min/max parameters, show or hide
	// the add/remove links
ObjectArrayEditor.prototype.showOrHideControls = function() {
	if (!this.userCanChangeSize) {
		return;
	}
	var addControl    = jQuery('#'+this.uniqueId + "-add-control");
	var removeControl = jQuery('#'+this.uniqueId + "-remove-control");
	if (!addControl.size() || !removeControl.size()) {
		alert("Can't find both controls");
		return;
	}
			
	var shouldShowAddControl = (this.maximumNumberOfFields == 0 || this.numberOfVisibleFields < this.maximumNumberOfFields);
	var shouldShowRemoveControl = (this.numberOfVisibleFields > this.minimumNumberOfFields);
	
	if (shouldShowAddControl) {
		addControl.css('display','block');
	} else {
		addControl.hide();
	}
	
	if (shouldShowRemoveControl) {
		removeControl.css('display','block');
	} else {
		removeControl.hide();
	}
};
	
	// fix up the naming and ids of nodes
ObjectArrayEditor.prototype.renumberChildNodesOfNodeWithNumber = function(node, number) {
	if (node) {
		if (node.hasChildNodes()) {
			for (var j=0; j<node.childNodes.length; j++) {
				this.renumberChildNodesOfNodeWithNumber(node.childNodes[j], number);
			}
		}
		var re = /([0-9-]+\.?)+$/g;
		if (node.name != undefined) {
			if (node.name.match(re)) {
				var nodeName = node.getAttribute("name");
				node.setAttribute("name", nodeName + "-" + number );
				var nodeId = node.getAttribute("id");
				node.setAttribute("id", nodeId + "-" + number );
			}
		}
	}
};
	
	// update from fields before we refresh
ObjectArrayEditor.prototype.updateFromFields = function() {
	//alert("Updating objects from editors");
	for (var i=0; i<this.editors.length; i++) {
		//alert("Fetching object " + i + " from " +this.editors[i]);
		this.objects[i] = this.editors[i].object();
	}
};

// /usr/local/idealist/htdocs/javascript/Idealist/ReusableComponent/AssetMap.js
// Represent a map of assets using Google Maps.
// It's better to show things that have already been geocoded
// because calling the google geocoder a zillion times from the client
// is a bit bogus.
//

AssetMap = IF.extend(
    IFFormComponent,
        function(uniqueId, bindingName) {
			this.uniqueId = uniqueId;
			this.bindingName = bindingName;
			this.element = jQuery('#'+uniqueId)[0];
			this.assets = new Array();
			this._markers = new Array();
			this._markerCount = 0;
			
			if (!this.element) {
				alert("Can't find element " + uniqueId);
			}
			this._mapElement = jQuery('#'+this.uniqueId + "-map")[0];
			if (!this._mapElement) {
				alert("Couldn't find map element for " + uniqueId);
			}
            
			if (GBrowserIsCompatible()) {
				this.setMap(new GMap2(jQuery(this._mapElement)[0]));
				this.map().setCenter(new GLatLng(0.0, 0.0), 8);
				//this.map().setMapType(G_SATELLITE_MAP);
				this.map().addControl(new GSmallMapControl());
				this.map().addControl(new GMapTypeControl());
				this.map().enableContinuousZoom();
				this.map().enableScrollWheelZoom();
			}
		},
		{
		render: function() {
			if (typeof this.map() == undefined) {
				console.log("Couldn't find map");
				return;
			}
			
			// refactor this to use load event instead
			while (!this.map().isLoaded()) {
				console.log("Map is not yet loaded");
				return;
			}
			console.log("Foux da fa fa");
			// console.log(this._assets);
		
		    this.map().clearOverlays();
		    
			for (var i=0; i<this._assets.length; i++) {
				this.lookupLatLngForItem(this._assets[i]);
				//console.log("Looked up coords for item " + i);
			}
		},
	
		lookupLatLngForItem: function(item) {
			var foo = this; // this is for the closures used in the callbacks
			
			// if the item knows its lat and lon then use it
		    if (item.mappingLatitude != "" && item.mappingLongitude != "") {
                var marker = foo.markerAtPointForItem(new GLatLng(item.mappingLatitude, item.mappingLongitude), item);
				foo.map().addOverlay(marker);
				foo.addMarker(marker);
				if (foo.shouldZoomMap()) {
				    foo.zoomMapToMarkers();
				}
				if (foo.map() && foo.map().isLoaded() && foo.shouldZoomMap()) {
					//GEvent.addListener(foo.map(), "infowindowclose", function() {
					//	foo.zoomMapToMarkers();
					//});
				}
				return;
		    }
		    
			// otherwise call up the google geocoder and ask it nicely.
			var gc = this._gClientGeoCoder();
			if (!gc) { console.log ("No google maps geocoder found!"); return; }

			gc.getLatLng(
			    item.mappingAddress,
					function(point) {
						if (point) {
							var marker = foo.markerAtPointForItem(point, item);
							foo.map().addOverlay(marker);
							foo.addMarker(marker);
							if (foo.map() && foo.map().isLoaded() && foo.shouldZoomMap()) {
								// console.log("zooming map to markers");
								foo.zoomMapToMarkers();
								
								// GEvent.addListener(foo.map(), "infowindowclose", function() {
								//	foo.zoomMapToMarkers();
								//});
							}
						} else {
							// add an empty marker
							foo.addMarker();
						}
					}
			);			
		},

		shouldZoomMap: function() {
			//console.log("assets length = " + this._assets.length + " and markers length is " + this._markerCount);
			if (this._assets.length == this._markerCount) {
				return true;
			}
			return false;
		},
		
		zoomMapToMarkers: function() {
			var bounds;
			
			for (var j=0; j<this._markers.length; j++) {
				if (!bounds) {
					bounds = new GLatLngBounds(this._markers[j].getPoint());
				} else {
					bounds.extend(this._markers[j].getPoint());
				}
			}

			this.setBounds(bounds);
		},

		setBounds: function(bounds) {
		    if (!bounds) {
		        return;
		    }
			this._bounds = bounds;
			var center = bounds.getCenter();
			this.map().setCenter(center);
			var zoomLevel = this.map().getBoundsZoomLevel(bounds);
			this.map().setZoom(zoomLevel);
		},
		
		bounds: function() {
			return this._bounds;
		},
		
		dimensions: function() {
            var SW = this.map().getBounds().getSouthWest() 
            var NE = this.map().getBounds().getNorthEast() 
            var NW = new GLatLng(NE.lat(),SW.lng()); 
            var latHeight = SW.distanceFrom(NW); //meters 
            var lonWidth = NE.distanceFrom(NW); //meters 
            var latHeightKm = latHeight / 1000; 
            var lonWidthKm = lonWidth / 1000; 
            var centre = this.map().getCenter();
            console.log("centre is " + centre);
            var radius = centre.distanceFrom(SW) / 1000;

            return { width: lonWidthKm, 
                    height: latHeightKm,
                 southWest: SW,
                 northEast: NE,
                    centre: centre,
                    radius: radius
            };
		},
		
		addMarker: function(m) {
			this._markerCount++;
			if (m) {
				this._markers[this._markers.length] = m;
			}
		},
		
		// we really should use DOM nodes instead of
		// building HTML like this, but for now it's ok.
		markerAtPointForItem: function(point, item) {
		    //var marker = new GMarker(point, this.iconForItem(item));
		    if (this.controller()) {
		        return this.controller().markerAtPointForItem(point, item);
		    }
            return new GMarker(point);
        },
		
		_gClientGeoCoder: function () {
			this._geoCoder = new GClientGeocoder();
			return this._geoCoder;
		},
		
		map: function() {
			return this._map;
		},
		
		setMap: function(value) {
			this._map = value;
		},
		
		clearAssets : function() {
		    this.setAssets(new Array());
		    this._markers = new Array();
		},
		
		addAssets : function(value) {
		    var arr = this._assets;
		    if (arr && typeof arr == "object") {
		        arr = arr.concat(value);
		    }
		    this._assets = arr;
		},
		
		assets: function() {
			return this._assets;
		},

		setAssets: function(value) {
			this._assets = value;
		},
		
		controller: function() {
		    return this._controller;
		},
		
		setController: function(value) {
		    this._controller = value;
		}
	}
);