/*
 * Metadata - jQuery plugin for parsing metadata from elements
 *
 * Copyright (c) 2006 John Resig, Yehuda Katz, Jörn Zaefferer, Paul McLanahan
 *
 * Dual licensed under the MIT and GPL licenses:
 *   http://www.opensource.org/licenses/mit-license.php
 *   http://www.gnu.org/licenses/gpl.html
 *
 * Revision: $Id$
 *
 */

/**
 * Sets the type of metadata to use. Metadata is encoded in JSON, and each property
 * in the JSON will become a property of the element itself.
 *
 * There are three supported types of metadata storage:
 *
 *   attr:  Inside an attribute. The name parameter indicates *which* attribute.
 *          
 *   class: Inside the class attribute, wrapped in curly braces: { }
 *   
 *   elem:  Inside a child element (e.g. a script tag). The
 *          name parameter indicates *which* element.
 *          
 * The metadata for an element is loaded the first time the element is accessed via jQuery.
 *
 * As a result, you can define the metadata type, use $(expr) to load the metadata into the elements
 * matched by expr, then redefine the metadata type and run another $(expr) for other elements.
 * 
 * @name $.metadata.setType
 *
 * @example <p id="one" class="some_class {item_id: 1, item_label: 'Label'}">This is a p</p>
 * @before $.metadata.setType("class")
 * @after $("#one").metadata().item_id == 1; $("#one").metadata().item_label == "Label"
 * @desc Reads metadata from the class attribute
 * 
 * @example <p id="one" class="some_class" data="{item_id: 1, item_label: 'Label'}">This is a p</p>
 * @before $.metadata.setType("attr", "data")
 * @after $("#one").metadata().item_id == 1; $("#one").metadata().item_label == "Label"
 * @desc Reads metadata from a "data" attribute
 * 
 * @example <p id="one" class="some_class"><script>{item_id: 1, item_label: 'Label'}</script>This is a p</p>
 * @before $.metadata.setType("elem", "script")
 * @after $("#one").metadata().item_id == 1; $("#one").metadata().item_label == "Label"
 * @desc Reads metadata from a nested script element
 * 
 * @param String type The encoding type
 * @param String name The name of the attribute to be used to get metadata (optional)
 * @cat Plugins/Metadata
 * @descr Sets the type of encoding to be used when loading metadata for the first time
 * @type undefined
 * @see metadata()
 */

(function($) {

$.extend({
	metadata : {
		defaults : {
			type: 'class',
			name: 'metadata',
			cre: /({.*})/,
			single: 'metadata'
		},
		setType: function( type, name ){
			this.defaults.type = type;
			this.defaults.name = name;
		},
		get: function( elem, opts ){
			var settings = $.extend({},this.defaults,opts);
			// check for empty string in single property
			if ( !settings.single.length ) settings.single = 'metadata';
			
			var data = $.data(elem, settings.single);
			// returned cached data if it already exists
			if ( data ) return data;
			
			data = "{}";
			
			if ( settings.type == "class" ) {
				var m = settings.cre.exec( elem.className );
				if ( m )
					data = m[1];
			} else if ( settings.type == "elem" ) {
				if( !elem.getElementsByTagName ) return;
				var e = elem.getElementsByTagName(settings.name);
				if ( e.length )
					data = $.trim(e[0].innerHTML);
			} else if ( elem.getAttribute != undefined ) {
				var attr = elem.getAttribute( settings.name );
				if ( attr )
					data = attr;
			}
			
			if ( data.indexOf( '{' ) <0 )
			data = "{" + data + "}";
			
			data = eval("(" + data + ")");
			
			$.data( elem, settings.single, data );
			return data;
		}
	}
});

/**
 * Returns the metadata object for the first member of the jQuery object.
 *
 * @name metadata
 * @descr Returns element's metadata object
 * @param Object opts An object contianing settings to override the defaults
 * @type jQuery
 * @cat Plugins/Metadata
 */
$.fn.metadata = function( opts ){
	return $.metadata.get( this[0], opts );
};

})(jQuery);

var App = {};

(function($) {
  // this is from Amiel Martin
  
  // register a variable for use within the App namespace
  // there is an optional scope
  // You may want to use the +js_var+ helper found in javascript_helper.rb
  App.register_variable = function(key, value, scope) {
      if (scope) {
          if (typeof App[scope] === "undefined") App[scope] = {};
          App[scope][key] = value;
      } else {
          App[key] = value;
      }
  };
  App.reg = App.register_variable;

  // apply is used way to much here //

  // log to the firebug console if window.console is available
  App.console = function(method) {
    if (App.DEBUG && window.console && window.console[method])
      window.console[method](Array.prototype.slice.apply(arguments, [1]));
  };


  // methods for logging
  // this gives us App.log, App.console.log, App.debug, etc
  (function(methods){
    for (i in methods)
      (function(method_name){
        App[method_name] = App.console[method_name] = function() {
          App.console.apply(null, [ method_name, 'App', "[" + (new Date).toLocaleTimeString() + "]" ].concat(Array.prototype.slice.apply(arguments)));
        };
      })(methods[i]);
  })(['log', 'debug', 'info', 'warn', 'error']);

  // all other firebug helpful methods
  // this gives us App.console.assert, App.console.dir, etc
  (function(methods){
    for (i in methods)
      (function(method_name){
        App.console[method_name] = function() {
          App.console.apply(null, [ method_name ].concat(Array.prototype.slice.apply(arguments)));
        };
      })(methods[i]);
  })(['assert', 'dir', 'dirxml', 'trace', 'group', 'groupEnd', 'time', 'timeEnd', 'profile', 'profileEnd', 'count']);
  
  // end Amiel Martin's badassery

  // trace ajax operations when "App.DEBUG == true"
  $(window).bind("ajaxStart ajaxSend ajaxComplete ajaxSuccess", App.debug);
  
  // Utility namespace for random useful site-wide stuff
  App.DEBUG = true;
  App.utility = {
    placeholderToken: null,
    placeholderRegExp: null,
    setPlaceholder: function(uuid){
      App.debug("Setting placeholder", uuid);
      App.utility.placeholderToken = uuid;
      App.utility.placeholderRegExp = new RegExp(uuid, "g");
    },
    addObjectLink: function (trigger, content) {
      var where = /(\w+)\s(.+)/.exec(trigger.attr("rev"));
      var method = where[1];
      var selector = where[2];
      var newObjectId = new Date().getTime();
      App.debug("Injecting", newObjectId, "to:", where);
      content = $(content.replace(App.utility.placeholderRegExp, newObjectId));
      $(selector)[method](content.hide());
      App.initializeAll(content);
      content[jQuery.browser.msie ? "show" : "fadeIn"]();
    }
  };

  // separate forms on the page can independently increment promptCount to make
  // sure the user gets prompted on onsaved changes.
  App.promptCount = 0;
  App.promptAtExit = function (on, adj) {
    if (on === undefined) { on = true; }
    if (adj === undefined) { adj = on ? 1 : -1; }
    App.promptCount += adj;
    if (App.promptCount < 0) { App.promptCount = 0; }
    window.onbeforeunload = App.promptCount > 0 ? App.confirmExit : null;
  };
  App.noPromptAtExit = function (adj) { App.promptAtExit(false, adj || -1); };
  App.absolutelyNoPromptAtExit = function () { App.noPromptAtExit(-1000); };
  App.confirmExit = function () {
    return "You haven't submitted your changes, and it looks \n" +
           "like you're trying to browse away from this page. \n" +
           "If you leave this page, your current changes will be lost.";
  };

  // Flash messaging
  App.flash = function( message, status ) {
    status = status || "notice";
    var title = "Notice";
    var image = "/images/icons/success.png";
    if ( status == "error" ) {
      title = "Error";
      image = "/images/icons/error.png";
    }
    var unique_id = $.gritter.add({
				title: title,
				text: message,
				image: image,
				time: '5000'
			});
  };
  $.extend(App.flash, {
    timeout: 7000,
    initialize: function(){
      var flash = $("#flash-messages").hide();
      flash.find("h3").each( function(){
        var self = $(this);
        var message = $.trim( self.html() );
        self.remove();
        if (message) { App.flash( message, this.className ); }
      });
    },
    notice: function(message){
      App.flash( message, "notice" );
    },
    error: function(message){
      App.flash( message, "error" );
    }
  });

  // by default, expect JSON data responses to AJAX requests
  $.ajaxSetup({ dataType: "json" });

  // add authenticity token to POSTs if its not there already
  $(window).ajaxSend(function(e, request, settings) {
    if ( settings.type.toUpperCase() == 'POST' ) {
      // Always send the authenticity_token with ajax POSTs
      // this will override any hidden fields in the rendered form
      settings.data = (settings.data ? settings.data + "&" : "") +
        ("authenticity_token=" + encodeURIComponent(App.AUTH_TOKEN));
      request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
    }
  });

  // JS response type on the server puts flash messages as JSON in X-JSON-FLASH
  // Global AJAX config to display AJAX flash:
  $(window).ajaxComplete(function(e, xhr, ajaxOptions) {
    App.debug('ajaxComplete - options', ajaxOptions);
    if (ajaxOptions['noflash']) return;
    var flashMessages = xhr.getResponseHeader("X-JSON-FLASH");
    if (flashMessages) {
      // FIXME: should use json2.js from json.org and not be so naive about eval'ing server response header
      flashMessages = window.eval("("+ flashMessages +")");
      $.each(flashMessages, function(status, msg) {
        App.flash(msg, status);   // see App.flash, above
      });
    }
  });

  $.escapeRegExp = function(str) { return str.replace(/([.*+?^${}()|[\]\/\\])/g, '\\$1'); };

  // this is how I'm handling DOM insertions for now:
  //  site-wide js that should be run by default when new content is injected (eg., dynamic forms, ajax modals)
  //  should be registered with "App.registerInitializer(key, initFn)".
  //  App.initializeAll will be with called when new content is inserted, with the new content container as the arg
  //  initializers will be called with 'this' as the scoping element of the dom from which to base their .find() calls
  App.init = {};
  App.registerInitializer = function (key, fn) {
    var init = App.init[key];
    if (!init) {
      init = App.init[key] = function(scope) {
        App.info("Initializing "+ key, init.callbacks);
        $.each(init.callbacks, function(i, fn) {
          fn(scope);
        });
      };
      init.callbacks = [];
    }
    init.callbacks.push(fn);
  };
  App.registerInitializers = function (initObj) {
    $.each(initObj, App.registerInitializer);
  };
  // initializeAll takes a selector or jQuery object and calls all registered
  // initializers with the jQuery object as the sole argument.
  // Use this when content is dynamically injected to ensure that
  // any relevant behaviors get applied, on DOM load or whenever.
  // I long for the day that jQuery.fn.live() will support focus, mouseenter, and mouseleave
  App.initializeAll = function (scope) {
    scope = $(scope);
    $.each(App.init, function(key, init) {
      init(scope);
    });
  };
})(jQuery);


// alias jQuery -> $ in this private scope
(function ($) {

  // Placeholder jQuery plugin... $('finder').placeholder();
  // This makes the input attribute, placeholder, work in older non-HTML5 browsers
  $.fn.placeholder = function() {
    if (this[0] && 'placeholder' in document.createElement("input")) return this;
    return this
      .focus(function(){
        var $this = $(this);
        if ($this.val() === $this.attr('placeholder')) {
          $this.val('').removeClass('placeholder');
        }
      })
      .blur(function(){
        var $this = $(this);
        if ($this.val() === '') {
          $this.val($this.attr('placeholder')).addClass('placeholder');
        }
      }).blur();
  };

  App.registerInitializers({
    helpButtons: function (scope) {
      if (!$.fn.helpButton) { return; }
      scope.find(".help-button, .ssl-icon").helpButton();
    },
    avatars: function (scope) {
      if (!$.fn.avatar) { return; }
      scope.find(".avatar").avatar();
    },
    autoFillDropDowns: function (scope) {
      if (!$.fn.ncAutoFill) { return; }
      scope.find('.auto_fill_drop_down').ncAutoFill();
    },
    datetimeRanges: function (scope) {
      if (!$.fn.datetimeRange) { return; }
      scope.find('.datetime-range').datetimeRange();
    },
    datepickers: function (scope) {
      if (!$.fn.ncDatepicker) { return; }
      scope.find('.date-input').ncDatepicker();
    },
    timepickers: function (scope) {
      if (!$.fn.ncTimepicker) { return; }
      scope.find('.time-input').ncTimepicker();
    },
    growfield: function (scope) {
      if (!$.fn.growfield) { return; }
      scope.find(".new-comment textarea, textarea.growfield").growfield();
    },
    scheduleBuilder: function (scope) {
      if (!$.fn.ncScheduleBuilder) { return; }
      scope.find("#treatment_schedules form").ncScheduleBuilder();
    },
    schedulable: function (scope) {
      if (!$.fn.ncSchedulable) { return; }
      scope.find(".schedulable").ncSchedulable();
    },
    placeholder: function(scope){
      scope.find("input[placeholder]").placeholder();
    },
    // Confirm exit from page if any form input has been modified
    dirtyForm: function(scope){
      // search form does not trigger prompt-at-exit or clear it
      var forms = scope.find("form:not(:has(button[value=Search]))");
      forms.submit(App.absolutelyNoPromptAtExit);
      forms.find(":input:not(.likes-it-dirty)").change(App.promptAtExit);
      forms.find("span.or-cancel a").click(App.absolutelyNoPromptAtExit);
      $('button[type=submit]:not([value=Search])', scope[0]).live('click', App.absolutelyNoPromptAtExit);
      // attempt to prevent false positives by cleaning up after any JS-init related change events
      setTimeout(App.absolutelyNoPromptAtExit, 250);
    },
    // Date picker classes
    datetimeSelects: function (scope) {
      scope.find(".datetime select[id*='2i']").addClass("month");
      scope.find(".datetime select[id*='3i']").addClass("day");
      scope.find(".datetime select[id*='1i']").addClass("year");
      scope.find(".datetime select[id*='4i']").addClass("hour");
      scope.find(".datetime select[id*='5i']").addClass("minute");
      scope.find(".datetime select[id*='7i']").addClass("meridiem");
    },
    // Days-of-cycle and days-of-week
    dayOfField: function (scope) {
      // Hoverstate for day of field
      // FIXME: this should be handled in CSS
      scope.find(".days-of-field label").bind("mouseenter mouseleave", function (e) {
        $(this).toggleClass("hover");
      });
      
      // send the checked state of checkboxes upstream to labels.
      scope.find('.days-of-field input[type=checkbox]:checked + label').addClass('selected');
    },
    // FIXME: this should be handled in CSS
    // IE6/7 sucks so we emulateNthChildSelectors
    emulateNthChildSelectorsGeneral: function(scope){
      // I like .first and .last on my LIs, but I don't like doing it manually, or within the app code. This is easier and DRYer.
      scope.find("li:first-child").addClass("first");
      scope.find("li:last-child").addClass("last");
      scope.find("tr:first-child, th:first-child, td:first-child").addClass("first");
      scope.find("tr:last-child,  th:last-child,  td:last-child").addClass("last");
      scope.find("p:first-child").addClass("first");
      scope.find("p:last-child").addClass("last");
    },
    emulateNthChildSelectorsSpecific: function(scope){
      // Browse Block headings
      scope.find("#content #filters h4:first").addClass("first");
      // Big copy headings
      scope.find("body.cancer-resources #primary > h3:first").addClass("first");
      // slightly different for these, since I can't do multiple class selectors in CSS
      scope.find(".block-group .block:first-child").addClass("first-block");
      scope.find(".block-group .block:last-child").addClass("last-block");
      // first/last on nav-blocks in the cancer-browser side bar
      scope.find(".navigation-block .nav-block:first-child").addClass("first");
      scope.find(".navigation-block .nav-block:last-child").addClass("last");
    },
    printButtons: function(scope){
      scope.find('.print-button').prepend('<a href="#print">Print</a>');
    },
    // FIXME: this should be handled in CSS
    // IE sucks so we emulate :after content
    breadCrumbs: function(scope){
      scope.find('ol.breadcrumbs li:not(:last-child)').append(' &raquo; ');
    },
    // FIXME: this should be handled in CSS
    // IE sucks so we emulate :after content
    formLabels: function(scope){
      scope.find('label.required').append('<span> *</span>');
    },
    cancerResourceFontSize: function() {
      // Reset Font Size
      var originalFontSize = "12px";
      var pxFontSize = "12px";
      $('#primary').css('font-size', originalFontSize);
      $(".reset-font").click(function(){
        $('#primary').css('font-size', originalFontSize);
      });
      // Increase Font Size
      $(".increase-font").click(function(){
        var currentFontSize = $('#primary').css('font-size');
        var currentFontSizeNum = parseFloat(currentFontSize, 10);
        var newFontSize = currentFontSizeNum + 2;
        $('#primary').css('font-size', newFontSize);
        $('#tertiary, .font-sizes').css('font-size', pxFontSize);
        return false;
      });
      // Decrease Font Size
      $(".decrease-font").click(function(){
        var currentFontSize = $('#primary').css('font-size');
        var currentFontSizeNum = parseFloat(currentFontSize, 10);
        var newFontSize = currentFontSizeNum - 2;
        $('#primary').css('font-size', newFontSize);
        $('#tertiary, .font-sizes').css('font-size', pxFontSize);
        return false;
      });
    },
    settingsChangeSpanHover: function (scope) {
      scope.find("body.my-settings h4 a span").bind("mouseenter mouseleave", function(e){
        $(this).toggleClass("hover");
      });
    },
    reminderToggles: function (scope) {
			$("input[rev=toggle-disabled]").change(function(e){
	       var $this = $(this);
	       var target = $($this.attr("rel"));
	       target.each(function(i){
   			  if($this.attr("checked")){
   			    $(this).removeAttr("disabled");
   			  }else{
   			    $(this).attr("disabled", true);
   			    if(i==0){
   			      $(this).attr("value", '');
   			    }
   			  }
   			});
	     }).change();
		},
    modals: function(scope) {
      scope.find(".modal").each( function(e){
        $(this).bgiframe();
        $(this).overlay({
          mask: {
            color: '#333',
            loadSpeed: 200,
            opacity: 0.3
          },
          closeOnClick: false,
          load: false });
        $(this).find("a.close").click(function(e){
          e.preventDefault();
        });
      });
    },
    // jquery form change events don't bubble in ie, so we need to put a click event on the label
    // clicks are before changes, so we need to switch the label value ourselves.
    journal_taken: function(scope) {
      scope.find('label.taken').bind( "click", function(){
        var input = $(this).parent().find('input:checkbox.taken');
        if ( input.is(":checked") )
          input.attr('checked', false);
        else
          input.attr('checked', true);
        $('.spinner.original').clone().removeClass('original').insertAfter( input ).show();
        $(this).addClass("hover").removeClass("selected");
        App.log("form", $(this).closest('form'));
        $(this).closest('form').submit();
      });
    }
  });

  // custom jQuery method to reset the busy button on a form's submit.
  // $('form element').unsetBusy();
  $.fn.unsetBusy = function(){
    return this.each( function(){
      $(this).find("button[type=submit]:not(.no-wait)").each( function(i) {
        var buttonText = $(this).find("div").attr("title");
        $(this).removeAttr("disabled")
          .removeClass('onbeforeunload')
          .find('div')
            .html( $(this).data( "buttonText" ) );
      });
    });
  };

  // Document Ready
  $(function() {
    // App.initializeAll is for everything that must be repeated for dynamic content.
    // At the current time, this means focus, mouseenter, and mouseleave handlers,
    //  as well as any styles that are being applied by javascript
    App.initializeAll(document);     // see App.registerInitializers() arguments above
    App.flash.initialize();
    
    if ($.fn.timeago) { $('abbr.time_ago').timeago(); }
    
    $('html.ie6 .doctor-dir').hide();



    // hook up modals to .modal-open links.
    $(".modal-open").live( "click", function(e){
      var modal = $( $(this).attr("rel") ); //should probably just be attr("href") --payne
      modal.data("overlay").load();
      e.preventDefault();
    });

    // submit inputs should be disabled on click for usability
    // $('form').submit(App.utility.formStatus.setBusy);
    $('form').live('submit', function(e){
      var image = new Image().src = "/images/disabled_button_loader.gif";
      var buttons = $(this).find("button[type=submit]:not(.no-wait)");
      buttons.each(function(i) {
        $(this).data( "buttonText", $(this).find("div").text() );
        $(this).attr("disabled", "disabled")
          .addClass('onbeforeunload')
          .find('div').html("<span>Please Wait</span>");
      });
    });

    // form submit events don't bubble in IE 6,7,8
    if (jQuery.browser.msie){
      $("form:not([data-remote])").delegate("input[type='submit'],button[type='submit'],input[name='commit']", 'click', function(e) {
        $(this).parents("form").submit();
      });

      // binding to a form submit somehow/sometimes prevents using the return/enter key to submit a form
      $('form:not([data-remote]) input[type="text"], form:not([data-remote]) input[type="password"]').keypress(function(e){
        c = e.which ? e.which : e.keyCode;
        if (c == 13){
          $(this).parents("form").submit();
        }
      });
    }

    // To override the default delete confirmation message, include an empty span inside the
    // <a> tag with a title attribute set to the desired message.
    (function(){
      function clickHandlerGenerator(submit){
        return function(e){
          var confirmation = "Are you sure you want to delete this item?";
          var link = $(e.target);
          if (link.find("span.confirmation").length > 0) {
            confirmation = link.find("span.confirmation").attr("title");
          }
          e.type = "beforeDelete";
          $(e.target).trigger(e);
          var prevented = e.isDefaultPrevented();
          e.preventDefault();
          if (!prevented && window.confirm(confirmation)) {
            submit(e);
          }
        };
      }
      $("a[rev=delete]").live("click", clickHandlerGenerator(function(e){
        $.post(e.target.href, {
            '_method': 'delete',
            'commit': e.target.rev
          },
          function(responseData){
            e.type = "delete";
            $(e.target).trigger(e);
          });
      }));
      // FIXME: need to settle on consistent principles for when to use rel/rev
      $("a[rel=delete]").live("click", clickHandlerGenerator(function(e){
        e.type = "delete";
        $(e.target).trigger(e);
        $("<form></form>").attr({ action: e.target.href, method: "POST" })
          .append($("<input/>").attr({ type: 'hidden', name: '_method', value: 'delete' }))
          .append($("<input/>").attr({ type: 'hidden', name: 'commit',  value: this.rev }))
          .append($("<input/>").attr({ type: 'hidden', name: 'authenticity_token', value: App.AUTH_TOKEN }))
          .appendTo("body")
          .submit();
      }));
    })();
    
    // Search rejigger-er
    setTimeout( function(){
      $('td.gsc-search-button').each(function(){
        var searchButton = '<td class="gsc-search-button"><div class="gsc-search-button"><span class="button go-submit-button"><button value="Search" type="submit" title="search" class="no-wait"><div>Search</div></button></span></div></td>';
        $(this).replaceWith(searchButton);
        $('td.gsc-clear-button').remove();
      });
    }, 100 );

    // Help blocks
    $('#close-help-block form').ajaxForm({
      beforeSubmit: function(){
        $('.help-block').slideFadeOut(700);
        $('#open-help-block').slideFadeIn(700);
        return true;
        }
      });
      
    $('#close-help-block a').click(function(){
      $(this).parent('form').submit();
      return false;
    });

    $('#open-help-block form').ajaxForm({
      beforeSubmit: function(){
        $('#open-help-block').slideFadeOut(700);
        $('.help-block').slideFadeIn(700);
        return false;
      }
    });
    
    $('#open-help-block a').click(function(){
      $(this).parent('form').submit();
      return false;
    });
    
    
    // rev attribute expresses the relationship of the current document to the link target
    // we use it here to express the action of the link on its target in the current document
     
     
    $("a[rev=toggle]").live("click", function(e){
			var anchor = $(this);
			var target = $(this.rel);
			target.slideToggle("fast", function(){
				if (target.is(":visible")) {
  		  	anchor.removeClass("collapsed");
	        anchor.trigger('expanded.toggle');
				} else {
					anchor.addClass("collapsed");
          anchor.trigger('collapsed.toggle');
				}
			});
      e.preventDefault();
		});

    $("a[rev=reveal]").live("click", function(e){
      var anchor = $(this), target = $(this.rel);
      target.trigger("reveal");
      return e.preventDefault();
    });

    $(".schedule h5.reveal").live("click", function(){
      var builder = $(this).parents('.schedule').find('.schedule-builder'),
          toggle = builder.is(":visible");

      $(this).toggleClass('hide-details', !toggle);
      toggle ? builder.slideUp('fast') : builder.slideDown('fast');
    });

    // checkbox show/hide related inputs
    $("input[rev=toggle]").live( "change", function(e){
      App.debug("toggle on change applied", this);
      if(parseInt($(this).val(),10) && $(this).attr("checked") ){
        $($(this).attr("rel")).slideDown("fast");
      }else{
        $($(this).attr("rel")).slideUp("fast");
      }
    }).change();

    // checkbox enable/disable related inputs
    $("input[rev=toggle-disabled]").live("click", function(e){      
      var input = $(this);
			var target = $(this.rel);
			target.each(function(i){
			  if(input.attr("checked")){
			    input.removeAttr("disabled");
			  }else{
			    input.attr("disabled", true);
			    input.attr("value", '');
			  }
			});
		});

    // radio Yes/No show/hide related div
    $("input[rev=show]").live( "change", function(){
      if ($(this).attr("checked"))
        $($(this).attr("rel")).slideDown("fast");
    }).change();
    $("input[rev=hide]").live( "change", function(){
      if ($(this).attr("checked"))
        $($(this).attr("rel")).slideUp("fast");
    }).change();
    
    $("input[rev=radio-toggle]").live("change",function(){
      var this_radio_set = $('input[name="'+$(this).attr("name")+'"]');
      this_radio_set.each(function(){
        var el = $(this);
        if (el.attr("checked"))
          $(el.attr("rel")).slideDown("fast");
        else
          $(el.attr("rel")).slideUp("fast");
      });
    }).change();
    
    $("input[auto-update]").live("change", function(){
      var el = $(this);
      //only used for radios right now, so this works
      if (el.attr('checked')) {
        var form = el.closest('form');
        $.ajax({
          type: 'PUT',
          url:  form.attr("action"),
          data: el.serialize(),
          success: function(){},
          noflash: true
        });
      }
    });
    
    // Improved dropdowns.
    $('div.dropdown-box span.current').each(function(){
      var options = $(this).next();
      
      // FIXME: this should be handled in CSS
      if  (options.outerWidth() < $(this).outerWidth()) {
        options.width($(this).outerWidth() - 2);
      }
      
      $(this).click(function(){
        $("ul.dropdown-options-list:visible").slideUp(75);
        if (options.is(":visible")){
          options.slideUp(200); // Slide up on mouseout
        }
        else {
          options.slideDown(200); // Drop down on click
          $("body").one("click", function() {
            options.slideUp(75); // Slide up on mouseout
          });
        }
        return false;
      });
    });

    // FIXME: this should be handled in CSS (or just nuke this whole thing)
    // Old Dropdown
    // var dropDowns = $(".drop-down .selected");
    // Get the width of the parent link
    $(".drop-down .selected").each(function() {
      var list = $(this).parent().find("ul.options");
      if (list.outerWidth() > $(this).outerWidth()) {
        var horizontalPadding = parseInt($(this).css("padding-left"), 10) + parseInt($(this).css("padding-right"), 10);
        $(this).width(list.outerWidth() - horizontalPadding);
      } else {
        list.width($(this).outerWidth());
      }
    }).click(function() {
      var options = $(this).parent().find("ul.options");
      //Following events are applied to the subnav itself (moving subnav up and down)
      options.slideDown(75); // Drop down on click
      $(this).parent().one("mouseleave", function() {
        options.slideUp(75); // Slide up on mouseout
      });
      return false;
    });

    // Discussion / Group landing more-categories links & lists
    $("a.more-categories-link").live("click", function() {
      $(this).next(".more-categories").toggle();
      return false;
    });

    // External link happiness
    $("a[rel=external]").live("click", function() {
      window.open( $(this).attr('href') );
      return false;
    });
    
    

    // Wah hoo! Comments!
    (function() {
      // handlers pass the context, and these private helpers handle the traversal
      var getNote    = function (ctx) { return $(ctx).closest(".note"); },
          newComment = function (ctx) { return getNote(ctx).find('div.new-comment'); },
          comments   = function (ctx) { return getNote(ctx).find('div.comment'); };

      // can be replaced with "toggle" --payne
      $(".note a.show-comments").live("click", function(e){
        comments(this).slideToggle("fast");
        e.preventDefault();
      });

      $('.note .or-cancel a').live("click", function(e){
        newComment(this).slideToggle("fast");
        e.preventDefault();
      });

      // can be replaced with "toggle" --payne
      $('.note a.add-comment').live("click", function(e){
        newComment(this).slideToggle("fast");
        e.preventDefault();
      });

      // Show new comment box (can be replaced with "toggle" --payne)
      $('a.new-comment').live("click", function(e){
        $('form.new_comment').slideToggle("fast");
        e.preventDefault();
      });
    })();

    // Health Tracker, also see 'dayOfField' initializer above
     $(".days-of-field input[type=checkbox]").change( function (e) {
       $("label[for="+ this.id +"]").toggleClass("selected", this.checked);
     });
    
    
    // Disabled Buttons should return false
    $('span.disabled-button a').live("click", function(){
      return false;
    });

    $('.print-button a').live("click", function() {
      window.print();
      return false;
    });
    
    $('.has-actions').live("mouseover", function() {
      $(this).find(".actions").show();
    });
    $('.has-actions').live("mouseout", function() {
      $(this).find(".actions").hide();
    });

    $('.has-actions').live("mouseover", function() {
      $(this).find(".actions").show();
    });
    $('.has-actions').live("mouseout", function() {
      $(this).find(".actions").hide();
    });

  });

  // lovely little IE hack from: http://norman.walsh.name/2009/03/24/jQueryIE#comment0012
  // makes IE's change event act like other browsers'
  $(function () {
    if ($.browser.msie) {
      $("input:radio, input:checkbox").live("click", function(){
        this.blur();
        this.focus();
      });
    }
  });
})(jQuery);




(function ($) {
  var initialized = "help_button_initialized";
  var plugin = $.helpButton = {
    tooltip: {
      predelay: 200,
      position: "bottom",
      relative: true,
      opacity: 0.95,
      offset: [20, -220],
      effect: "slide"
    },
    defaults: {
      
    }
  };

  $.fn.helpButton = function() {
    if (!$.fn.tooltip) { throw "jQuery Tools Tooltip not found."; }
    return this.each(function() {
      if ($(this).is("."+ initialized)) { return; }
      // Help bubbles
      var container = $(this).addClass(initialized);
      container.find("a").click(function () { return false; });
      container.find("a").tooltip(plugin.tooltip);
    });
  };
})(jQuery);

// @requires: jQuery Tools tooltip
(function($) {
  var plugin = $.avatar = {
    initialized: "avatar_initialized",
    tooltip: {
      predelay: 200,
      position: "bottom",
      relative: true,
      opacity: 0.95,
      offset: [30, -52]
    }
  };
  
  $.fn.avatar = function() {
    return this.each(function() {
      var container = $(this);
      if (container.hasClass(plugin.initialized)) { return; }
      container.addClass(plugin.initialized).find('.interior').tooltip(plugin.tooltip);
    });
  };
})(jQuery);

(function ($) {
  /**
   * The lists are stored in the App object to allow sharing between
   * multiple autofills of a given type per page
   */
  App.autoFillData = App.autoFillData || {};
  App.autoFill = App.autoFill || {};
  App.autoFill.mapping = App.autoFill.mapping || {};
  App.autoFill.getData = function (type){
    // translate auto fill association name to URL fragment. this also prevents
    // duplication where the association name is different, but the data source the same
    var typeData = [], dataType = App.autoFill.mapping[type];
    if (dataType in App.autoFillData) {
      typeData = App.autoFillData[dataType];
    } else {
      // TODO: blow up because we don't support this auto fill type
    }
    return typeData;
  };
  /**
   * @param Object opts must include templateText key with a template string
   */
  $.fn.ncAutoFill = function(opts){
    return this.each(function(){
      var container = $(this), input = container.find("input[type=text]");
      if (input.attr("autocomplete") == "off") return;

      // the span enclosing the text input contains configuration values
      var confSpan = container.find("span:first"),
          type     = confSpan[0].className,
          formName = confSpan[0].rel,
          conf = $.extend(true, {
            radioName: formName + "["+ type +"_auto_fill_id]",
            autocomplete: { list: App.autoFill.getData(type) },
            overlay:      { onBeforeLoad: modalOnBeforeLoad }
          }, plugin.conf, opts),
          // initialize the overlay directly, because links to it will come and go
          modal = $(conf.overlay.target),
          overlay = modal.overlay(conf.overlay),
          // this is what actually gets submitted as autofill id
          hidden = container.find("input[type=hidden]");

      // jQuery Tools Overlay stores the Overlay object in $.data and returns
      // the stored instance on init if one exists. Multiple auto fills in one page
      // all attempt to initialize the same div as an overlay (conf.overlay.target)
      $(conf.overlay.target).removeData("overlay");

      input
        // when a user types in the text input, dump the stored id
        .bind("keydown", function(e){
          var k = e.which || e.keyCode; // in IE e.which is undefined
          var KEY = $.ui.autocomplete.KEY; // use $.ui.keyCode when moving to jquery.ui autocomplete
          switch(k) {
            case KEY.UP:
            case KEY.DOWN:
            case KEY.SHIFT:
            case KEY.CTRL:
            case KEY.ALT:
            case KEY.CMD:
            case KEY.HOME:
            case KEY.END:
            case KEY.PAGE_DOWN:
            case KEY.PAGE_UP:
            case KEY.CAPS_LOCK:
            case KEY.INSERT:
            case KEY.LEFT:
            case KEY.RIGHT:
              break;
            default:
              App.info("keydown in ", k, input);
              hidden.removeAttr("value");
          }
        })
        .bind("activated.autocomplete", function(e, activeObject, activeItem){
          // set the hidden field value to the selected item's auto fill id
          App.info("activated.autocomplete in ", activeObject, input);
          hidden.val(activeObject && activeObject.auto_fill_id);
          // under some circumstances (not sure when) autocomplete activates with no activeItem
          if (!activeItem) { return; }
          // if the add-link has no rel, don't load the modal: follow the link instead
          if (activeItem.hasClass(conf.addRecordClass)) {
            var addLink = activeItem.find(".add-link");
            // if the add link has a rel, load the modal and populate it with a form
            if (addLink.attr("rel")) {
              var event = $.Event();
              event.overlayTrigger = addLink[0];
              overlay.load(event);
            } else {
              // if the add-link has no rel, skip the modal and follow the link instead
              window.location.href = addLink.attr("href");
            }
            return;
          }
        })
				.bind('keyup', function(){
           $.data(input[0], "originalValue", input.val());
				})
        .autocomplete(conf.autocomplete);

      container
        .bind("mousedown.auto_fill-overlay", function(e){
          var anchor = $(e.target);
          if (anchor.is("a[rel="+ conf.overlay.target +"]")) {
            var event = $.Event();
            event.overlayTrigger = anchor[0];
            overlay.load(event);
            return e.preventDefault();
          }
        })
        .find("a.arrow").click(function(e){
          input.focus();
          return e.preventDefault();
        });

      /**
       * GET the modal form content (via xhr), showing busy state while in progress
       *    also, copy input.val() to first text input if new form
       *
       * @param e the event object that triggered this overlay load
       * @return e.preventDefault() to prevent loading this modal
       */
      function modalOnBeforeLoad (e) {
        var href = (e.overlayTrigger && e.overlayTrigger.href),
            closeOverlay = function(e) { overlay.close(); return e.preventDefault(); };
        if (!href) { return e.preventDefault(); }

        // close the autocomplete list and restore the user's input
        $("body").triggerHandler("cancel.autocomplete");

        // empty the content wrapper inside modal element
        modal.find("div.wrap").empty();
        // show busy status indicator
        modal.addClass("in-progress");

        // load the page specified in the trigger
        // using ?modal=true to request the form partial with no layout
        $.get(href, { modal: true }, function(html) {
          // modal close button because we're rendering content without one
          var closeLink   = $("<a class='close close-button'>&times;</a>").click(closeOverlay),
              closeButton = $("<span class='close-button'></span>").append(closeLink);

          modal.find("div.wrap").prepend(closeButton).append(html);
          // get a reference to the newly inserted form
          var form = modal.find('form');
          form.submit(modalFormSubmitted);
          // submit cancel links should dismiss the modal
          form.find(".or-cancel a").click(closeOverlay);

          // DON'T copy input.val() to first text input if form for new record
          // if ($(e.overlayTrigger).hasClass("add-link")) {
          //   form.find('input[type=text]:first').val(input.val());
          // }

          // focus first input for user
          form.find('input[type=text]:first').focus();
          // initialize any other needed javascript in the modal
          //    (auto fills, date/time pickers, etc)
          App.initializeAll(form);

          // remove busy status indicator,
          //  and center the modal (now that is has content, it has width)
          modal
            .removeClass("in-progress")
            .animate({ left: (($(window).width() - modal.width()) / 2) });

        }, "html");       // request html response (ie., not json)
      }
      /**
       * POST the modal form content (via xhr), showing busy state while in progress
       *    also, copy val from first text input to input.val()
       *
       * @param e the event object that triggered this overlay load
       * @return return false to prevent loading this modal
       */
      function modalFormSubmitted (e) {
        var form = $(this), formData = form.serialize();

        // no need to make the user wait...
        overlay.close();
        // reflect the user's modal input in the page form
        // FIXME: unreliable way to pick the appropriate value
        input.val(form.find("input[type=text]:first").val());
        modal.addClass("in-progress");
        form.enable(false).find("input").enable(false);

        var xhr = $.ajax({
          type: form.attr('method'),
          url:  form.attr('action'),
          data: formData,
          complete: function(xhr, textStatus) {
            // don't remove "in-progress" class, instead:
            //  leave activity indicator displayed for the next time the modal is shown
            overlay.getContent().find(".wrap").empty();
          },
          success: function(data) {
            modalFormSuccessResponse(data, xhr.getResponseHeader("Location"));
          },
          error: function (xhr, textStatus, errorThrown) {
            App.flash.error(App.I18n.auto_fill_modal_error || "There was an error with your submission. Please try again.");
          }
        });

        // prevent normal submission of modal form
        return false;
      }
      
      function modalFormSuccessResponse(data, location) {
        // get the first (and hopefully only) key in data,
        // which is the underscored model name
        for (var recordClass in data) break;
        var list = conf.autocomplete.list,
            record = parseRecord(data[recordClass], location),
            existingIndex, existing;

        // existingIndex will be the index of the last element in list
        // that matches record.auto_fill_id
        existing = $.grep(list, function(elm, i){
          return existingIndex = (elm.auto_fill_id == record.auto_fill_id) ? i : existingIndex;
        });
        if (existingIndex !== undefined) {
          App.debug("there was already a radio with that value (", record.auto_fill_id, ") so, we're replacing it in the list");
          list.splice(existingIndex, 1, record);
        } else {
          // insert new items at the second-to-last position in the list
          list.splice(list.length - 1, 0, record);
        }

        // reflect the user's modal input in the page form
        input.val(record.primary_label);
        hidden.val(record.auto_fill_id);
        App.info("modalFormSuccessResponse in ", record, input);
      }

      function parseRecord(record, location){
        return $.extend(record, {
            css_class:        "user_record",
            radio_name:       conf.radioName,
            primary_label:    record.name,
            secondary_label:  "",
            display_link:     true,
            // TODO: dump the translated link text from Ruby to JS using js_var
            link_text:        "Edit",
            link_attrs: {
              href:    location + "/edit",
              "class": "edit-link",
              rel:     "#modal_auto_fill_form"
            }
          });
      }
    });
  };

  $.nc = $.nc || {};
  var plugin = $.nc.autoFill = {
    initialized: "nc-auto_fill-initialized",
    conf: {
      addRecordClass: "add_record",
      overlay: {
        // get a reference to the jQuery Tools Overlay object
        api: true,
        target: "#modal_auto_fill_form",
        // prevent accidental modal dismissal
        closeOnClick: false,
        // give a pretty animation on modal load
        expose: {
          color: "#333",
          loadSpeed: 200,
          zIndex: 9990
        }
      },
      autocomplete: {
        autoFill: true,
        threshold: 250,
        zIndex: 1000,
        timeout: 150,
        innerWrapper: "<ol class='auto_fill_list options'></ol>",
        wrapper: "<div class='dropdown auto_fill'></div>",
        instructions: "<div class='instructions'>Type an entry or select:</div>",
        insertText: function(object, originalValue) {
          return object.auto_fill_id ? object.primary_label : originalValue;
        },
        // capturing, case insensitive regex substring matcher
        matcher: function (typed){ return new RegExp("("+ $.escapeRegExp(typed) +")", "ig"); },
        // return true if matcher matches primary_label or secondary_label
        //  also highlight matches with em tags
        match: function(object, matcher) {
          // add item has no auto_fill_id and should always be displayed
          if (!object.auto_fill_id) { return true; }
          var primaryLabel   = object.primary_label,
              // secondary label is optional and may be blank
              secondaryLabel = object.secondary_label || "";
          object.primaryMatchText   = primaryLabel.replace(matcher, "<em>$1</em>");
          object.secondaryMatchText = secondaryLabel.replace(matcher, "<em>$1</em>");
          // if a replacement was made above, a match was found
          //  (and one of the matchTexts won't equal the corresponding label text)
          return (object.primaryMatchText   != primaryLabel) ||
                 (object.secondaryMatchText != secondaryLabel);
        },
        buildListWrapper: function(listItems){
          var innerWrapper = $(plugin.conf.autocomplete.innerWrapper).append(listItems);
          var wrapper = $(plugin.conf.autocomplete.wrapper).append($(plugin.conf.autocomplete.instructions));
          return wrapper.append(innerWrapper);
        },
        // override displayList because we insert the list after the input,
        // rather than at the end of body
        displayList: function(input, container) {
          var position = input.position();
          container.css({
            top:      position.top + input.outerHeight(),
            left:     position.left,
            minWidth: input.outerWidth() - 2,
            width: $.browser.msie ? input.outerWidth() - 2 : "auto",
            zIndex:   plugin.conf.autocomplete.zIndex
          });
          input.after(container);
          container.bgiframe({
            width: "500px"
          });
          return container;
        },
        template: function (object) {
          var link_attrs = [];
          for (attr in object.link_attrs) {
            link_attrs.push(attr +"='"+ object.link_attrs[attr] +"'");
          }
          link_attrs = link_attrs.join(" ");

          return ["<li class='"+ object.css_class +"'>",
            (!object.display_link) ? "" :
              ("<a "+ link_attrs +">"+ object.link_text +"</a>"),
            "<label>",
              "<input type='radio' name='"+ object.radio_name +"' value='"+ object.auto_fill_id +"' class='js-hide' />",
              "<span class='primary-label'>"+ (object.primaryMatchText || object.primary_label) +"</span>",
              (!object.secondary_label) ? "" :
                ("<span class='secondary-label'>"+ (object.secondaryMatchText || object.secondary_label) +"</span>"),
            "</label>",
          "</li>"].join("");
        }
      }
    }
  };
})(jQuery);


/*!
 * jQuery Form Plugin
 * version: 2.43 (12-MAR-2010)
 * @requires jQuery v1.3.2 or later
 *
 * Examples and documentation at: http://malsup.com/jquery/form/
 * Dual licensed under the MIT and GPL licenses:
 *   http://www.opensource.org/licenses/mit-license.php
 *   http://www.gnu.org/licenses/gpl.html
 */
;(function($) {

/*
	Usage Note:
	-----------
	Do not use both ajaxSubmit and ajaxForm on the same form.  These
	functions are intended to be exclusive.  Use ajaxSubmit if you want
	to bind your own submit handler to the form.  For example,

	$(document).ready(function() {
		$('#myForm').bind('submit', function() {
			$(this).ajaxSubmit({
				target: '#output'
			});
			return false; // <-- important!
		});
	});

	Use ajaxForm when you want the plugin to manage all the event binding
	for you.  For example,

	$(document).ready(function() {
		$('#myForm').ajaxForm({
			target: '#output'
		});
	});

	When using ajaxForm, the ajaxSubmit function will be invoked for you
	at the appropriate time.
*/

/**
 * ajaxSubmit() provides a mechanism for immediately submitting
 * an HTML form using AJAX.
 */
$.fn.ajaxSubmit = function(options) {
	// fast fail if nothing selected (http://dev.jquery.com/ticket/2752)
	if (!this.length) {
		log('ajaxSubmit: skipping submit process - no element selected');
		return this;
	}

	if (typeof options == 'function')
		options = { success: options };

	var url = $.trim(this.attr('action'));
	if (url) {
		// clean url (don't include hash vaue)
		url = (url.match(/^([^#]+)/)||[])[1];
   	}
   	url = url || window.location.href || '';

	options = $.extend({
		url:  url,
		type: this.attr('method') || 'GET',
		iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank'
	}, options || {});

	// hook for manipulating the form data before it is extracted;
	// convenient for use with rich editors like tinyMCE or FCKEditor
	var veto = {};
	this.trigger('form-pre-serialize', [this, options, veto]);
	if (veto.veto) {
		log('ajaxSubmit: submit vetoed via form-pre-serialize trigger');
		return this;
	}

	// provide opportunity to alter form data before it is serialized
	if (options.beforeSerialize && options.beforeSerialize(this, options) === false) {
		log('ajaxSubmit: submit aborted via beforeSerialize callback');
		return this;
	}

	var a = this.formToArray(options.semantic);
	if (options.data) {
		options.extraData = options.data;
		for (var n in options.data) {
		  if(options.data[n] instanceof Array) {
			for (var k in options.data[n])
			  a.push( { name: n, value: options.data[n][k] } );
		  }
		  else
			 a.push( { name: n, value: options.data[n] } );
		}
	}

	// give pre-submit callback an opportunity to abort the submit
	if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) {
		log('ajaxSubmit: submit aborted via beforeSubmit callback');
		return this;
	}

	// fire vetoable 'validate' event
	this.trigger('form-submit-validate', [a, this, options, veto]);
	if (veto.veto) {
		log('ajaxSubmit: submit vetoed via form-submit-validate trigger');
		return this;
	}

	var q = $.param(a);

	if (options.type.toUpperCase() == 'GET') {
		options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q;
		options.data = null;  // data is null for 'get'
	}
	else
		options.data = q; // data is the query string for 'post'

	var $form = this, callbacks = [];
	if (options.resetForm) callbacks.push(function() { $form.resetForm(); });
	if (options.clearForm) callbacks.push(function() { $form.clearForm(); });

	// perform a load on the target only if dataType is not provided
	if (!options.dataType && options.target) {
		var oldSuccess = options.success || function(){};
		callbacks.push(function(data) {
			var fn = options.replaceTarget ? 'replaceWith' : 'html';
			$(options.target)[fn](data).each(oldSuccess, arguments);
		});
	}
	else if (options.success)
		callbacks.push(options.success);

	options.success = function(data, status, xhr) { // jQuery 1.4+ passes xhr as 3rd arg
		for (var i=0, max=callbacks.length; i < max; i++)
			callbacks[i].apply(options, [data, status, xhr || $form, $form]);
	};

	// are there files to upload?
	var files = $('input:file', this).fieldValue();
	var found = false;
	for (var j=0; j < files.length; j++)
		if (files[j])
			found = true;

	var multipart = false;
//	var mp = 'multipart/form-data';
//	multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp);

	// options.iframe allows user to force iframe mode
	// 06-NOV-09: now defaulting to iframe mode if file input is detected
   if ((files.length && options.iframe !== false) || options.iframe || found || multipart) {
	   // hack to fix Safari hang (thanks to Tim Molendijk for this)
	   // see:  http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d
	   if (options.closeKeepAlive)
		   $.get(options.closeKeepAlive, fileUpload);
	   else
		   fileUpload();
	   }
   else
	   $.ajax(options);

	// fire 'notify' event
	this.trigger('form-submit-notify', [this, options]);
	return this;


	// private function for handling file uploads (hat tip to YAHOO!)
	function fileUpload() {
		var form = $form[0];

		if ($(':input[name=submit]', form).length) {
			alert('Error: Form elements must not be named "submit".');
			return;
		}

		var opts = $.extend({}, $.ajaxSettings, options);
		var s = $.extend(true, {}, $.extend(true, {}, $.ajaxSettings), opts);

		var id = 'jqFormIO' + (new Date().getTime());
		var $io = $('<iframe id="' + id + '" name="' + id + '" src="'+ opts.iframeSrc +'" onload="(jQuery(this).data(\'form-plugin-onload\'))()" />');
		var io = $io[0];

		$io.css({ position: 'absolute', top: '-1000px', left: '-1000px' });

		var xhr = { // mock object
			aborted: 0,
			responseText: null,
			responseXML: null,
			status: 0,
			statusText: 'n/a',
			getAllResponseHeaders: function() {},
			getResponseHeader: function() {},
			setRequestHeader: function() {},
			abort: function() {
				this.aborted = 1;
				$io.attr('src', opts.iframeSrc); // abort op in progress
			}
		};

		var g = opts.global;
		// trigger ajax global events so that activity/block indicators work like normal
		if (g && ! $.active++) $.event.trigger("ajaxStart");
		if (g) $.event.trigger("ajaxSend", [xhr, opts]);

		if (s.beforeSend && s.beforeSend(xhr, s) === false) {
			s.global && $.active--;
			return;
		}
		if (xhr.aborted)
			return;

		var cbInvoked = false;
		var timedOut = 0;

		// add submitting element to data if we know it
		var sub = form.clk;
		if (sub) {
			var n = sub.name;
			if (n && !sub.disabled) {
				opts.extraData = opts.extraData || {};
				opts.extraData[n] = sub.value;
				if (sub.type == "image") {
					opts.extraData[n+'.x'] = form.clk_x;
					opts.extraData[n+'.y'] = form.clk_y;
				}
			}
		}

		// take a breath so that pending repaints get some cpu time before the upload starts
		function doSubmit() {
			// make sure form attrs are set
			var t = $form.attr('target'), a = $form.attr('action');

			// update form attrs in IE friendly way
			form.setAttribute('target',id);
			if (form.getAttribute('method') != 'POST')
				form.setAttribute('method', 'POST');
			if (form.getAttribute('action') != opts.url)
				form.setAttribute('action', opts.url);

			// ie borks in some cases when setting encoding
			if (! opts.skipEncodingOverride) {
				$form.attr({
					encoding: 'multipart/form-data',
					enctype:  'multipart/form-data'
				});
			}

			// support timout
			if (opts.timeout)
				setTimeout(function() { timedOut = true; cb(); }, opts.timeout);

			// add "extra" data to form if provided in options
			var extraInputs = [];
			try {
				if (opts.extraData)
					for (var n in opts.extraData)
						extraInputs.push(
							$('<input type="hidden" name="'+n+'" value="'+opts.extraData[n]+'" />')
								.appendTo(form)[0]);

				// add iframe to doc and submit the form
				$io.appendTo('body');
				$io.data('form-plugin-onload', cb);
				form.submit();
			}
			finally {
				// reset attrs and remove "extra" input elements
				form.setAttribute('action',a);
				t ? form.setAttribute('target', t) : $form.removeAttr('target');
				$(extraInputs).remove();
			}
		};

		if (opts.forceSync)
			doSubmit();
		else
			setTimeout(doSubmit, 10); // this lets dom updates render

		var domCheckCount = 100;

		function cb() {
			if (cbInvoked)
				return;

			var ok = true;
			try {
				if (timedOut) throw 'timeout';
				// extract the server response from the iframe
				var data, doc;

				doc = io.contentWindow ? io.contentWindow.document : io.contentDocument ? io.contentDocument : io.document;

				var isXml = opts.dataType == 'xml' || doc.XMLDocument || $.isXMLDoc(doc);
				log('isXml='+isXml);
				if (!isXml && (doc.body == null || doc.body.innerHTML == '')) {
				 	if (--domCheckCount) {
						// in some browsers (Opera) the iframe DOM is not always traversable when
						// the onload callback fires, so we loop a bit to accommodate
				 		log('requeing onLoad callback, DOM not available');
						setTimeout(cb, 250);
						return;
					}
					log('Could not access iframe DOM after 100 tries.');
					return;
				}

				log('response detected');
				cbInvoked = true;
				xhr.responseText = doc.body ? doc.body.innerHTML : null;
				xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;
				xhr.getResponseHeader = function(header){
					var headers = {'content-type': opts.dataType};
					return headers[header];
				};

				if (opts.dataType == 'json' || opts.dataType == 'script') {
					// see if user embedded response in textarea
					var ta = doc.getElementsByTagName('textarea')[0];
					if (ta)
						xhr.responseText = ta.value;
					else {
						// account for browsers injecting pre around json response
						var pre = doc.getElementsByTagName('pre')[0];
						if (pre)
							xhr.responseText = pre.innerHTML;
					}
				}
				else if (opts.dataType == 'xml' && !xhr.responseXML && xhr.responseText != null) {
					xhr.responseXML = toXml(xhr.responseText);
				}
				data = $.httpData(xhr, opts.dataType);
			}
			catch(e){
				log('error caught:',e);
				ok = false;
				xhr.error = e;
				$.handleError(opts, xhr, 'error', e);
			}

			// ordering of these callbacks/triggers is odd, but that's how $.ajax does it
			if (ok) {
				opts.success(data, 'success');
				if (g) $.event.trigger("ajaxSuccess", [xhr, opts]);
			}
			if (g) $.event.trigger("ajaxComplete", [xhr, opts]);
			if (g && ! --$.active) $.event.trigger("ajaxStop");
			if (opts.complete) opts.complete(xhr, ok ? 'success' : 'error');

			// clean up
			setTimeout(function() {
				$io.removeData('form-plugin-onload');
				$io.remove();
				xhr.responseXML = null;
			}, 100);
		};

		function toXml(s, doc) {
			if (window.ActiveXObject) {
				doc = new ActiveXObject('Microsoft.XMLDOM');
				doc.async = 'false';
				doc.loadXML(s);
			}
			else
				doc = (new DOMParser()).parseFromString(s, 'text/xml');
			return (doc && doc.documentElement && doc.documentElement.tagName != 'parsererror') ? doc : null;
		};
	};
};

/**
 * ajaxForm() provides a mechanism for fully automating form submission.
 *
 * The advantages of using this method instead of ajaxSubmit() are:
 *
 * 1: This method will include coordinates for <input type="image" /> elements (if the element
 *	is used to submit the form).
 * 2. This method will include the submit element's name/value data (for the element that was
 *	used to submit the form).
 * 3. This method binds the submit() method to the form for you.
 *
 * The options argument for ajaxForm works exactly as it does for ajaxSubmit.  ajaxForm merely
 * passes the options argument along after properly binding events for submit elements and
 * the form itself.
 */
$.fn.ajaxForm = function(options) {
	return this.ajaxFormUnbind().bind('submit.form-plugin', function(e) {
		e.preventDefault();
		$(this).ajaxSubmit(options);
	}).bind('click.form-plugin', function(e) {
		var target = e.target;
		var $el = $(target);
		if (!($el.is(":submit,input:image"))) {
			// is this a child element of the submit el?  (ex: a span within a button)
			var t = $el.closest(':submit');
			if (t.length == 0)
				return;
			target = t[0];
		}
		var form = this;
		form.clk = target;
		if (target.type == 'image') {
			if (e.offsetX != undefined) {
				form.clk_x = e.offsetX;
				form.clk_y = e.offsetY;
			} else if (typeof $.fn.offset == 'function') { // try to use dimensions plugin
				var offset = $el.offset();
				form.clk_x = e.pageX - offset.left;
				form.clk_y = e.pageY - offset.top;
			} else {
				form.clk_x = e.pageX - target.offsetLeft;
				form.clk_y = e.pageY - target.offsetTop;
			}
		}
		// clear form vars
		setTimeout(function() { form.clk = form.clk_x = form.clk_y = null; }, 100);
	});
};

// ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm
$.fn.ajaxFormUnbind = function() {
	return this.unbind('submit.form-plugin click.form-plugin');
};

/**
 * formToArray() gathers form element data into an array of objects that can
 * be passed to any of the following ajax functions: $.get, $.post, or load.
 * Each object in the array has both a 'name' and 'value' property.  An example of
 * an array for a simple login form might be:
 *
 * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
 *
 * It is this array that is passed to pre-submit callback functions provided to the
 * ajaxSubmit() and ajaxForm() methods.
 */
$.fn.formToArray = function(semantic) {
	var a = [];
	if (this.length == 0) return a;

	var form = this[0];
	var els = semantic ? form.getElementsByTagName('*') : form.elements;
	if (!els) return a;
	for(var i=0, max=els.length; i < max; i++) {
		var el = els[i];
		var n = el.name;
		if (!n) continue;

		if (semantic && form.clk && el.type == "image") {
			// handle image inputs on the fly when semantic == true
			if(!el.disabled && form.clk == el) {
				a.push({name: n, value: $(el).val()});
				a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
			}
			continue;
		}

		var v = $.fieldValue(el, true);
		if (v && v.constructor == Array) {
			for(var j=0, jmax=v.length; j < jmax; j++)
				a.push({name: n, value: v[j]});
		}
		else if (v !== null && typeof v != 'undefined')
			a.push({name: n, value: v});
	}

	if (!semantic && form.clk) {
		// input type=='image' are not found in elements array! handle it here
		var $input = $(form.clk), input = $input[0], n = input.name;
		if (n && !input.disabled && input.type == 'image') {
			a.push({name: n, value: $input.val()});
			a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
		}
	}
	return a;
};

/**
 * Serializes form data into a 'submittable' string. This method will return a string
 * in the format: name1=value1&amp;name2=value2
 */
$.fn.formSerialize = function(semantic) {
	//hand off to jQuery.param for proper encoding
	return $.param(this.formToArray(semantic));
};

/**
 * Serializes all field elements in the jQuery object into a query string.
 * This method will return a string in the format: name1=value1&amp;name2=value2
 */
$.fn.fieldSerialize = function(successful) {
	var a = [];
	this.each(function() {
		var n = this.name;
		if (!n) return;
		var v = $.fieldValue(this, successful);
		if (v && v.constructor == Array) {
			for (var i=0,max=v.length; i < max; i++)
				a.push({name: n, value: v[i]});
		}
		else if (v !== null && typeof v != 'undefined')
			a.push({name: this.name, value: v});
	});
	//hand off to jQuery.param for proper encoding
	return $.param(a);
};

/**
 * Returns the value(s) of the element in the matched set.  For example, consider the following form:
 *
 *  <form><fieldset>
 *	  <input name="A" type="text" />
 *	  <input name="A" type="text" />
 *	  <input name="B" type="checkbox" value="B1" />
 *	  <input name="B" type="checkbox" value="B2"/>
 *	  <input name="C" type="radio" value="C1" />
 *	  <input name="C" type="radio" value="C2" />
 *  </fieldset></form>
 *
 *  var v = $(':text').fieldValue();
 *  // if no values are entered into the text inputs
 *  v == ['','']
 *  // if values entered into the text inputs are 'foo' and 'bar'
 *  v == ['foo','bar']
 *
 *  var v = $(':checkbox').fieldValue();
 *  // if neither checkbox is checked
 *  v === undefined
 *  // if both checkboxes are checked
 *  v == ['B1', 'B2']
 *
 *  var v = $(':radio').fieldValue();
 *  // if neither radio is checked
 *  v === undefined
 *  // if first radio is checked
 *  v == ['C1']
 *
 * The successful argument controls whether or not the field element must be 'successful'
 * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
 * The default value of the successful argument is true.  If this value is false the value(s)
 * for each element is returned.
 *
 * Note: This method *always* returns an array.  If no valid value can be determined the
 *	   array will be empty, otherwise it will contain one or more values.
 */
$.fn.fieldValue = function(successful) {
	for (var val=[], i=0, max=this.length; i < max; i++) {
		var el = this[i];
		var v = $.fieldValue(el, successful);
		if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length))
			continue;
		v.constructor == Array ? $.merge(val, v) : val.push(v);
	}
	return val;
};

/**
 * Returns the value of the field element.
 */
$.fieldValue = function(el, successful) {
	var n = el.name, t = el.type, tag = el.tagName.toLowerCase();
	if (typeof successful == 'undefined') successful = true;

	if (successful && (!n || el.disabled || t == 'reset' || t == 'button' ||
		(t == 'checkbox' || t == 'radio') && !el.checked ||
		(t == 'submit' || t == 'image') && el.form && el.form.clk != el ||
		tag == 'select' && el.selectedIndex == -1))
			return null;

	if (tag == 'select') {
		var index = el.selectedIndex;
		if (index < 0) return null;
		var a = [], ops = el.options;
		var one = (t == 'select-one');
		var max = (one ? index+1 : ops.length);
		for(var i=(one ? index : 0); i < max; i++) {
			var op = ops[i];
			if (op.selected) {
				var v = op.value;
				if (!v) // extra pain for IE...
					v = (op.attributes && op.attributes['value'] && !(op.attributes['value'].specified)) ? op.text : op.value;
				if (one) return v;
				a.push(v);
			}
		}
		return a;
	}
	return el.value;
};

/**
 * Clears the form data.  Takes the following actions on the form's input fields:
 *  - input text fields will have their 'value' property set to the empty string
 *  - select elements will have their 'selectedIndex' property set to -1
 *  - checkbox and radio inputs will have their 'checked' property set to false
 *  - inputs of type submit, button, reset, and hidden will *not* be effected
 *  - button elements will *not* be effected
 */
$.fn.clearForm = function() {
	return this.each(function() {
		$('input,select,textarea', this).clearFields();
	});
};

/**
 * Clears the selected form elements.
 */
$.fn.clearFields = $.fn.clearInputs = function() {
	return this.each(function() {
		var t = this.type, tag = this.tagName.toLowerCase();
		if (t == 'text' || t == 'password' || tag == 'textarea')
			this.value = '';
		else if (t == 'checkbox' || t == 'radio')
			this.checked = false;
		else if (tag == 'select')
			this.selectedIndex = -1;
	});
};

/**
 * Resets the form data.  Causes all form elements to be reset to their original value.
 */
$.fn.resetForm = function() {
	return this.each(function() {
		// guard against an input with the name of 'reset'
		// note that IE reports the reset function as an 'object'
		if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType))
			this.reset();
	});
};

/**
 * Enables or disables any matching elements.
 */
$.fn.enable = function(b) {
	if (b == undefined) b = true;
	return this.each(function() {
		this.disabled = !b;
	});
};

/**
 * Checks/unchecks any matching checkboxes or radio buttons and
 * selects/deselects and matching option elements.
 */
$.fn.selected = function(select) {
	if (select == undefined) select = true;
	return this.each(function() {
		var t = this.type;
		if (t == 'checkbox' || t == 'radio')
			this.checked = select;
		else if (this.tagName.toLowerCase() == 'option') {
			var $sel = $(this).parent('select');
			if (select && $sel[0] && $sel[0].type == 'select-one') {
				// deselect all other options
				$sel.find('option').selected(false);
			}
			this.selected = select;
		}
	});
};

// helper fn for console logging
// set $.fn.ajaxSubmit.debug to true to enable debug logging
function log() {
	if ($.fn.ajaxSubmit.debug) {
		var msg = '[jquery.form] ' + Array.prototype.join.call(arguments,'');
		if (window.console && window.console.log)
			window.console.log(msg);
		else if (window.opera && window.opera.postError)
			window.opera.postError(msg);
	}
};

})(jQuery);


jQuery(function ($) {
    var csrf_token = $('meta[name=csrf-token]').attr('content'),
        csrf_param = $('meta[name=csrf-param]').attr('content');

    $.fn.extend({
        /**
         * Triggers a custom event on an element and returns the event result
         * this is used to get around not being able to ensure callbacks are placed
         * at the end of the chain.
         *
         * TODO: deprecate with jQuery 1.4.2 release, in favor of subscribing to our
         *       own events and placing ourselves at the end of the chain.
         */
        triggerAndReturn: function (name, data) {
            var event = new $.Event(name);
            this.trigger(event, data);

            return event.result !== false;
        },

        /**
         * Handles execution of remote calls firing overridable events along the way
         */
        callRemote: function () {
            var el      = this,
                method  = el.attr('method') || el.attr('data-method') || 'GET',
                url     = el.attr('action') || el.attr('href'),
                dataType  = el.attr('data-type')  || 'script';

            if (url === undefined) {
              throw "No URL specified for remote call (action or href must be present).";
            } else {
                if (el.triggerAndReturn('ajax:before')) {
                    var data = el.is('form') ? el.serializeArray() : [];
                    $.ajax({
                        url: url,
                        data: data,
                        dataType: dataType,
                        type: method.toUpperCase(),
                        beforeSend: function (xhr) {
                            el.trigger('ajax:loading', xhr);
                        },
                        success: function (data, status, xhr) {
                            el.trigger('ajax:success', [data, status, xhr]);
                        },
                        complete: function (xhr) {
                            el.trigger('ajax:complete', xhr);
                        },
                        error: function (xhr, status, error) {
                            el.trigger('ajax:failure', [xhr, status, error]);
                        }
                    });
                }

                el.trigger('ajax:after');
            }
        }
    });

    /**
     *  confirmation handler
     */
    $('a[data-confirm],input[data-confirm]').live('click', function () {
        var el = $(this);
        if (el.triggerAndReturn('confirm')) {
            if (!confirm(el.attr('data-confirm'))) {
                return false;
            }
        }
    });


    /**
     * remote handlers
     */
    $('form[data-remote]').live('submit', function (e) {
        $(this).callRemote();
        e.preventDefault();
    });
    
    if (jQuery.browser.msie){
      $("form[data-remote]").delegate("input[type='submit'],button[type='submit'],input[name='commit']", 'click', function(e) {
        $(this).parents("form[data-remote]").submit();
        if(e.preventDefault) e.preventDefault();
      });
    }

    $('a[data-remote],input[data-remote]').live('click', function (e) {
        $(this).callRemote();
        e.preventDefault();
    });

    $('a[data-method]:not([data-remote])').live('click', function (e){
        var link = $(this),
            href = link.attr('href'),
            method = link.attr('data-method'),
            form = $('<form method="post" action="'+href+'"></form>'),
            metadata_input = '<input name="_method" value="'+method+'" type="hidden" />';

        if (csrf_param != null && csrf_token != null) {
          metadata_input += '<input name="'+csrf_param+'" value="'+csrf_token+'" type="hidden" />';
        }

        form.hide()
            .append(metadata_input)
            .appendTo('body');

        e.preventDefault();
        form.submit();
    });

    /**
     * disable-with handlers
     */
    var disable_with_input_selector = 'input[data-disable-with]';
    var disable_with_form_selector = 'form[data-remote]:has(' + disable_with_input_selector + ')';

    $(disable_with_form_selector).live('ajax:before', function () {
        $(this).find(disable_with_input_selector).each(function () {
            var input = $(this);
            input.data('enable-with', input.val())
                 .attr('value', input.attr('data-disable-with'))
                 .attr('disabled', 'disabled');
        });
    });

    $(disable_with_form_selector).live('ajax:complete', function () {
        $(this).find(disable_with_input_selector).each(function () {
            var input = $(this);
            input.removeAttr('disabled')
                 .val(input.data('enable-with'));
        });
    });
});


/*
 * timeago: a jQuery plugin, version: 0.8.2 (2010-02-16)
 * @requires jQuery v1.2.3 or later
 *
 * Timeago is a jQuery plugin that makes it easy to support automatically
 * updating fuzzy timestamps (e.g. "4 minutes ago" or "about 1 day ago").
 *
 * For usage and examples, visit:
 * http://timeago.yarp.com/
 *
 * Licensed under the MIT:
 * http://www.opensource.org/licenses/mit-license.php
 *
 * Copyright (c) 2008-2010, Ryan McGeary (ryanonjavascript -[at]- mcgeary [*dot*] org)
 */
(function($) {
  $.timeago = function(timestamp) {
    if (timestamp instanceof Date) return inWords(timestamp);
    else if (typeof timestamp == "string") return inWords($.timeago.parse(timestamp));
    else return inWords($.timeago.datetime(timestamp));
  };
  var $t = $.timeago;

  $.extend($.timeago, {
    settings: {
      refreshMillis: 60000,
      allowFuture: false,
      strings: {
        prefixAgo: null,
        prefixFromNow: null,
        suffixAgo: "ago",
        suffixFromNow: "from now",
        ago: null, // DEPRECATED, use suffixAgo
        fromNow: null, // DEPRECATED, use suffixFromNow
        seconds: "less than a minute",
        minute: "about a minute",
        minutes: "%d minutes",
        hour: "about an hour",
        hours: "about %d hours",
        day: "a day",
        days: "%d days",
        month: "about a month",
        months: "%d months",
        year: "about a year",
        years: "%d years"
      }
    },
    inWords: function(distanceMillis) {
      var $l = this.settings.strings;
      var prefix = $l.prefixAgo;
      var suffix = $l.suffixAgo || $l.ago;
      if (this.settings.allowFuture) {
        if (distanceMillis < 0) {
          prefix = $l.prefixFromNow;
          suffix = $l.suffixFromNow || $l.fromNow;
        }
        distanceMillis = Math.abs(distanceMillis);
      }

      var seconds = distanceMillis / 1000;
      var minutes = seconds / 60;
      var hours = minutes / 60;
      var days = hours / 24;
      var years = days / 365;

      var words = seconds < 45 && substitute($l.seconds, Math.round(seconds)) ||
        seconds < 90 && substitute($l.minute, 1) ||
        minutes < 45 && substitute($l.minutes, Math.round(minutes)) ||
        minutes < 90 && substitute($l.hour, 1) ||
        hours < 24 && substitute($l.hours, Math.round(hours)) ||
        hours < 48 && substitute($l.day, 1) ||
        days < 30 && substitute($l.days, Math.floor(days)) ||
        days < 60 && substitute($l.month, 1) ||
        days < 365 && substitute($l.months, Math.floor(days / 30)) ||
        years < 2 && substitute($l.year, 1) ||
        substitute($l.years, Math.floor(years));

      return $.trim([prefix, words, suffix].join(" "));
    },
    parse: function(iso8601) {
      var s = $.trim(iso8601);
      s = s.replace(/-/,"/").replace(/-/,"/");
      s = s.replace(/T/," ").replace(/Z/," UTC");
      s = s.replace(/([\+-]\d\d)\:?(\d\d)/," $1$2"); // -04:00 -> -0400
      return new Date(s);
    },
    datetime: function(elem) {
      // jQuery's `is()` doesn't play well with HTML5 in IE
      var isTime = $(elem).get(0).tagName.toLowerCase() == "time"; // $(elem).is("time");
      var iso8601 = isTime ? $(elem).attr("datetime") : $(elem).attr("title");
      return $t.parse(iso8601);
    }
  });

  $.fn.timeago = function() {
    var self = this;
    self.each(refresh);

    var $s = $t.settings;
    if ($s.refreshMillis > 0) {
      setInterval(function() { self.each(refresh); }, $s.refreshMillis);
    }
    return self;
  };

  function refresh() {
    var data = prepareData(this);
    if (!isNaN(data.datetime)) {
      $(this).text(inWords(data.datetime));
    }
    return this;
  }

  function prepareData(element) {
    element = $(element);
    if (!element.data("timeago")) {
      element.data("timeago", { datetime: $t.datetime(element) });
      var text = $.trim(element.text());
      if (text.length > 0) element.attr("title", text);
    }
    return element.data("timeago");
  }

  function inWords(date) {
    return $t.inWords(distance(date));
  }

  function distance(date) {
    return (new Date().getTime() - date.getTime());
  }

  function substitute(stringOrFunction, value) {
    var string = $.isFunction(stringOrFunction) ? stringOrFunction(value) : stringOrFunction;
    return string.replace(/%d/i, value);
  }

  // fix for IE6 suckage
  document.createElement("abbr");
  document.createElement("time");
})(jQuery);


jQuery.fn.extend({
  slideFadeToggle: function(speed, easing, callback) {
    return this.animate({opacity: 'toggle', height: 'toggle'}, speed, easing, callback);  
  },
  slideFadeIn: function(speed, easing, callback) {
    return this.animate({opacity: 'show', height: 'show'}, speed, easing, callback);  
  },
  slideFadeOut: function(speed, easing, callback) {
    return this.animate({opacity: 'hide', height: 'hide', marginTop: 'hide', marginBottom: 'hide', paddingTop: 'hide', paddingBottom: 'hide'}, speed, easing, callback);
  }
});

/*
 * jQuery Growfield Library 2
 *
 * http://code.google.com/p/jquery-dynamic/
 * licensed under the MIT license
 *
 * autor: john kuindji
 */
(function(C){if(C.support==undefined){C.support={boxModel:C.boxModel}}var A=false;C(window).one("load",function(){A=true});C.fx.prototype.originalUpdate=C.fx.prototype.update;C.fx.prototype.update=false;C.fx.prototype.update=function(){if(!this.options.inline){return this.originalUpdate.call(this)}if(this.options.step){this.options.step.call(this.elem,this.now,this)}(jQuery.fx.step[this.prop]||jQuery.fx.step._default)(this)};var B=function(D){this.dom=D;this.o=C(D);this.opt={auto:true,animate:100,easing:null,min:false,max:false,restore:false,step:false};this.enabled=this.dummy=this.busy=this.initial=this.sizeRelated=this.prevH=this.firstH=false};B.prototype={toggle:function(D){if((D=="disable"||D===false)&&this.enabled){return this.setEvents("off")}if((D=="enable"||D===true)&&!this.enabled){return this.setEvents("on")}return this},setEvents:function(I){var H=this.o,E=this.opt,G=this,D=false;if(I=="on"&&!this.enabled){var F=H.height()==0?true:false;if(!F||A){C(function(){G.prepareSizeRelated()})}else{C(window).one("load",function(){G.prepareSizeRelated()})}if(E.auto){H.bind("keyup.growfield",function(J){G.keyUp(J);return true});H.bind("focus.growfield",function(J){G.focus(J);return true});H.bind("blur.growfield",function(J){G.blur(J);return true});D={overflow:H.css("overflow"),cssResize:H.css("resize")};if(C.browser.safari){H.css("resize","none")}this.initial=D;H.css({overflow:"hidden"});if(!F||A){C(function(){G.createDummy()})}else{C(window).one("load",function(){G.createDummy()})}}else{H.bind("keydown.growfield",function(J){G.manualKeyUp(J);return true});H.css("overflow-y","auto");if(!F||A){C(function(){G.update(H.height())})}else{C(window).one("load",function(){G.update(H.height())})}}H.addClass("growfield");this.enabled=true}else{if(I=="off"&&this.enabled){if(this.dummy){this.dummy.remove();this.dummy=false}H.unbind(".growfield").css("overflow",this.initial.overflow);if(C.browser.safari){H.css("resize",this.initial.cssResize)}this.enabled=false}}return this},setOptions:function(D){var E=this.opt,F=this.o;C.extend(E,D);if(!C.easing){E.easing=null}},update:function(N,G){var D=this.sizeRelated,J=this.o.val(),F=this.opt,M=this.dom,H=this.o,E=this,K=this.prevH;var I=!F.auto,L=F.auto;N=this.convertHeight(Math.round(N),"inner");N=F.min>N?F.min:F.max&&N>F.max?F.max:F.auto&&!J?F.min:N;if(F.max&&F.auto){if(K!=F.max&&N==F.max){H.css("overflow-y","scroll");if(!F.animate){H.focus()}I=true;L=false}if(K==F.max&&N<F.max){H.css("overflow-y","hidden");if(!F.animate){H.focus()}L=false}}if(N==K){return true}this.prevH=N;if(G){E.busy=true;H.animate({height:N},{duration:F.animate,easing:F.easing,overflow:null,inline:true,complete:function(){if(!I){H.css("overflow","hidden")}if(!L){H.focus()}E.busy=false},queue:false})}else{M.style.height=N+"px"}},manualKeyUp:function(D){if(!D.ctrlKey){return }if(D.keyCode!=38&&D.keyCode!=40){return }this.update(this.o.outerHeight()+(this.opt.step*(D.keyCode==38?-1:1)),this.opt.animate)},keyUp:function(D){if(this.busy){return true}if(C.inArray(D.keyCode,[37,38,39,40])!=-1){return true}this.update(this.getDummyHeight(),this.opt.animate)},focus:function(D){if(this.busy){return true}if(this.opt.restore){this.update(this.getDummyHeight(),this.opt.animate)}},blur:function(D){if(this.busy){return true}if(this.opt.restore){this.update(0,false)}},getDummyHeight:function(){var G=this.o.val(),E=0,D=this.sizeRelated,F="\n111\n111";if(C.browser.safari){G=G.substring(0,G.length-1)}if(!D.lh||!D.fs){G+=F}this.dummy.val(G);if(C.browser.msie){this.dummy[0].style.height=this.dummy[0].scrollHeight+"px"}E=this.dummy[0].scrollHeight;if(D.lh&&D.fs){E+=D.lh>D.fs?D.lh+D.fs:D.fs*2}if(C.browser.msie){this.dummy[0].style.height="20px"}return E},createDummy:function(){var F=this.o,E=this.o.val();var D=F.clone().addClass("growfieldDummy").attr("name","").attr("tabindex",-9999).css({position:"absolute",left:-9999,top:0,height:"20px",resize:"none"}).insertBefore(F).show();if(!E){D.val("dummy text")}this.dummy=D;this.update(!jQuery.trim(E)?0:this.getDummyHeight(),false)},convertHeight:function(F,H){var E=this.sizeRelated,D=(H=="inner"?-1:1),G=C.support.boxModel;return F+(G?E.bt:0)*D+(G?E.bb:0)*D+(G?E.pt:0)*D+(G?E.pb:0)*D},prepareSizeRelated:function(){var F=this.o,D=this.opt;if(!D.min){D.min=parseInt(F.css("min-height"),10)||this.firstH||parseInt(F.height(),10)||20;if(D.min<=0){D.min=20}if(!this.firstH){this.firstH=D.min}}if(!D.max){D.max=parseInt(F.css("max-height"),10)||false;if(D.max<=0){D.max=false}}if(!D.step){D.step=parseInt(F.css("line-height"),10)||parseInt(F.css("font-size"),10)||20}var E={pt:parseInt(F.css("paddingTop"),10)||0,pb:parseInt(F.css("paddingBottom"),10)||0,bt:parseInt(F.css("borderTopWidth"),10)||0,bb:parseInt(F.css("borderBottomWidth"),10)||0,lh:parseInt(F.css("lineHeight"),10)||false,fs:parseInt(F.css("fontSize"),10)||false};this.sizeRelated=E}};C.fn.growfield=function(D){if("destroy"==D){return this.each(function(){var F=C(this).data("growfield");if(F==undefined){return true}F.toggle(false);C(this).removeData("growfield");return true})}if("restart"==D){return this.each(function(){var F=C(this).data("growfield");if(F==undefined){return true}F.toggle(false).toggle(true)})}var E=typeof D;return this.each(function(){if(!/textarea/i.test(this.tagName)||C(this).hasClass("growfieldDummy")){return true}var F=false,J=C(this),H=J.data("growfield");if(H==undefined){F=true;J.data("growfield",new B(this));H=J.data("growfield")}if(F){var G=C.extend({},C.fn.growfield.defaults,D);H.setOptions(G)}if(!F&&(!D||E=="object")){H.setOptions(D)}if(E=="string"){if(D.indexOf("!")==0&&C.fn.growfield.presets[D.substr(1)]){J.unbind("."+i+"."+D.substr(1))}else{if(C.fn.growfield.presets[D]){var I=C.fn.growfield.presets[D];H.setOptions(I,D)}}}if(F&&!G.skipEnable){H.toggle(true)}if(!F&&(E=="boolean"||D=="enable"||D=="disable")){H.toggle(D)}})};C.fn.growfield.defaults={};C.fn.growfield.presets={}})(jQuery);