/*
  Base.js, version 1.1
  Copyright 2006-2007, Dean Edwards
  License: http://www.opensource.org/licenses/mit-license.php
*/

var Base = function() {
  // dummy
};

Base.extend = function(_instance, _static) { // subclass
  var extend = Base.prototype.extend;

  // build the prototype
  Base._prototyping = true;
  var proto = new this;
  extend.call(proto, _instance);
  delete Base._prototyping;

  // create the wrapper for the constructor function
  //var constructor = proto.constructor.valueOf(); //-dean
  var constructor = proto.constructor;
  var klass = proto.constructor = function() {
    if (!Base._prototyping) {
      if (this._constructing || this.constructor == klass) { // instantiation
        this._constructing = true;
        constructor.apply(this, arguments);
        delete this._constructing;
      } else if (arguments[0] != null) { // casting
        return (arguments[0].extend || extend).call(arguments[0], proto);
      }
    }
  };

  // build the class interface
  klass.ancestor = this;
  klass.extend = this.extend;
  klass.forEach = this.forEach;
  klass.implement = this.implement;
  klass.prototype = proto;
  klass.toString = this.toString;
  klass.valueOf = function(type) {
    //return (type == "object") ? klass : constructor; //-dean
    return (type == "object") ? klass : constructor.valueOf();
  };
  extend.call(klass, _static);
  // class initialisation
  if (typeof klass.init == "function") klass.init();
  return klass;
};

Base.prototype = {
  extend: function(source, value) {
    if (arguments.length > 1) { // extending with a name/value pair
      var ancestor = this[source];
      if (ancestor && (typeof value == "function") && // overriding a method?
        // the valueOf() comparison is to avoid circular references
        (!ancestor.valueOf || ancestor.valueOf() != value.valueOf()) &&
        /\bbase\b/.test(value)) {
        // get the underlying method
        var method = value.valueOf();
        // override
        value = function() {
          var previous = this.base || Base.prototype.base;
          this.base = ancestor;
          var returnValue = method.apply(this, arguments);
          this.base = previous;
          return returnValue;
        };
        // point to the underlying method
        value.valueOf = function(type) {
          return (type == "object") ? value : method;
        };
        value.toString = Base.toString;
      }
      this[source] = value;
    } else if (source) { // extending with an object literal
      var extend = Base.prototype.extend;
      // if this object has a customised extend method then use it
      if (!Base._prototyping && typeof this != "function") {
        extend = this.extend || extend;
      }
      var proto = {toSource: null};
      // do the "toString" and other methods manually
      var hidden = ["constructor", "toString", "valueOf"];
      // if we are prototyping then include the constructor
      var i = Base._prototyping ? 0 : 1;
      while (key = hidden[i++]) {
        if (source[key] != proto[key]) {
          extend.call(this, key, source[key]);

        }
      }
      // copy each of the source object's properties to this object
      for (var key in source) {
        if (!proto[key]) extend.call(this, key, source[key]);
      }
    }
    return this;
  },

  base: function() {
    // call this method from any other method to invoke that method's ancestor
  }
};

// initialise
Base = Base.extend({
  constructor: function() {
    this.extend(arguments[0]);
  }
}, {
  ancestor: Object,
  version: "1.1",

  forEach: function(object, block, context) {
    for (var key in object) {
      if (this.prototype[key] === undefined) {
        block.call(context, object[key], key, object);
      }
    }
  },

  implement: function() {
    for (var i = 0; i < arguments.length; i++) {
      if (typeof arguments[i] == "function") {
        // if it's a function, call it
        arguments[i](this.prototype);
      } else {
        // add the interface using the extend method
        this.prototype.extend(arguments[i]);
      }
    }
    return this;
  },

  toString: function() {
    return String(this.valueOf());
  }
});


// Generic JS we want to have everywhere.
// Requires jQuery + livequery. Other libraries such as facebox should
// be checked for before using them.
jQuery(function($) {
  $('a[rel~="delete"]').live("click", function() {
    if ($(this).is('[rel~="confirm"]') && !confirm("Are you sure?"))
      return false;
    return submitMethodFormFor(this, "delete");
  });
  $('a[rel~="put"]').live("click", function() {
    return submitMethodFormFor(this, "put");
  });
  $('a[rel~="post"]').live("click", function() {
    return submitMethodFormFor(this, "post");
  });

  if ($.fn.livequery) {
    $(".fade").livequery(function(){ delayedFade(this); });
  }

  $('input.time').livequery(function () {
    var hour = 10;

    // There's a bug in the scope of the .livequery() function
    $('input.time').each(function (i) {
      if (!$(this).attr('value')) {
        $(this).attr({ value: hour + ':00 am' });
        hour++;
      }
    });
  });

  $('input.date').livequery(function () {
    var $this = $(this)
      , opts  = {
            dateFormat: 'yy-mm-dd'
          , onSelect: function(dateText, inst) {
              if ($this.attr('id') == 'event_start_date') {
                var toDateField = $('#event_end_date');
                if (toDateField.val() < dateText) {
                  toDateField.val(dateText);
                }
              }
  		    }
        }
      , curr  = location.search.slice(6); // e.g. "?date=2010-04-01"

    // Use DB values as authority (format the date as yyyy-mm-dd) for the DatePicker
    if ($this.attr('value')) {
      curr = $this.attr('value');
    }

    curr = curr.split('-');

    // Set defaultDate if available
    if (curr.length == 3) {
      opts.defaultDate = new Date(
        curr[0],
        parseInt(curr[1], 10) - 1, // January is 0 in JS
        curr[2]
      );
    } else {
      opts.defaultDate = new Date();
    }

    // necessary for dynamically added date fields using livequery
    $this.datepicker('destroy');

    $this
      .datepicker(opts)
      .attr({ value: $.datepicker.formatDate(opts.dateFormat, opts.defaultDate) });
  });

  // Automatically add auth tokens to outgoing AJAX
  $("body").ajaxSend(function(evt, xhr, opts) {
    appendAuthenticityToken(opts);
    xhr.setRequestHeader("Content-Type", opts.contentType);
  });

  if ($.fn.facebox) {
    $.extend($.facebox.settings, {
      loadingImage: "/images/liboly/facebox/loading.gif",
      closeImage: "/images/liboly/modal/close.png",
      opacity: .7,
      faceboxHtml  : '\
      <div id="facebox" style="display:none;"> \
        <div class="popup"> \
          <table> \
            <tbody> \
              <tr> \
                <td class="tl"></td> \
                <td class="bt"> \
                  <span class="title"></span> \
                  <a href="" class="close"><img src="" title="close" class="close_image"></a> \
                </td> \
                <td class="tr"></td> \
              </tr> \
              <tr> \
                <td class="b"></td> \
                <td class="body"> \
                  <div class="content"> \
                  </div> \
                </td> \
                <td class="b"></td> \
              </tr> \
              <tr> \
                <td class="bl"></td><td class="bb"></td><td class="br"></td> \
              </tr> \
            </tbody> \
          </table> \
        </div> \
      </div>'
    });
    $('a[rel*="facebox"]').livequery(function() {
      var link = $(this);
      var titleText = link.attr('title') || link.text();
      link.click(function(){$('#facebox .bt .title').text( titleText )} )
        .facebox();
    });
    $(document).bind('close.facebox', function() {
      $('#facebox .bt .title').text('');
    });
    $(document).bind('reveal.facebox', function(){
      $('#facebox_overlay').unbind('click');
    });
  }

  if ($.fn.tablesorter) {
    // Assumes the value passed to format will be an ISO8601 date string
    $.tablesorter.addParser({
      id: "iso8601",
      is: function(s) { return false; },
      format: function(s) { return s; },
      type: "text"
    });

    $.tablesorter.addParser({
      id: "iso8601-distance",
      is: function(s) { return false; },
      format: function(s) { return s; },
      type: "reverseText"
    });
  }

  $('form.throb_on_submit').livequery("submit", function(){ //livequery due to IE bug with live and submit
    var form = $(this);
    form.find("button").attr("disabled", "disabled");
    var img = form.find("button.positive[type=submit] img");

    if(img.length > 0){
      $.data(form.get(0), 'submitImage', img.clone());
      img.replaceWith($(loadingImage).clone());
    }
  });

  // FastFinder et al.
  $(".ctrl.ff input:text").each(function() {
    var input = $(this);
    var ff = FastFinder.finderFor(input, { disableSubmit: true });

    input.data("finder", ff);
    ff.bind();
  });

  $("form.switch-users input:text").livequery(function() { $(this).focus(); });

  if ($(".data-title").length > 0) {
    //// data containers (profile)
    // plural data container
    $(".data-title a.new").click(function() {
      var $link = $(this);
      var $dataBody = $link.parents(".data-title").next(".data-body");

      $link.hide();
      $dataBody.children("form.new").show();

      return false;
    });

    $(".data-body a.edit").live("click", function() {
      var $datum = $(this).parents(".datum");
      $datum.children("div").hide();
      $datum.children("form.edit").show();
      return false;
    });
    $(".data-body a.delete").live("click", function() {
      if (!confirm("Are you sure you want to delete this?"))
        return false;

      var $link = $(this);
      $.ajax({
        type: "DELETE",
        url: this.href,
        success: function() {
          $link.parents(".datum").remove();

          if ($('.datum.organization_membership').length == 1) {
            $('.datum.organization_membership a.delete').hide();
          }
        }
      });
      return false;
    });

    $(".data-body form.new").livequery(function() {
      var $targetForm = null;
      $(this).ajaxForm({
      beforeSubmit: function(data, $form, opts) { $targetForm = $form; },
        success: function(html, status, xhr, $form) {
          $form.before(html);
          $form.find(".errors").remove();
          $form.find(".invalid").andSelf().removeClass("invalid");

          if ($form.find("select#classification_id").length > 0) {
            $form.find("select#classification_id").change();
          }

          if ($('.datum.organization_membership').length > 1) {
            $('.datum.organization_membership a.delete').show();
          }

          $('.orgs_by_school').empty();
        },
        error: function(xhr, status) {
          var $parent = $targetForm.parent();
          $targetForm.replaceWith(xhr.responseText);
          $parent.find("form.new").show();
        },
        resetForm: true
      });
    });

    $(".data-body .datum form.edit").livequery(function() {
      var $targetForm = null;
      $(this).ajaxForm({
        beforeSubmit: function(data, $form, opts) { $targetForm = $form; },
        success: function(html, status, xhr, $form) {
          $form.hide();
          $form.parents(".datum").replaceWith(html);
        },
        error: function(xhr, status) {
          var $parent = $targetForm.parent();
          $targetForm.replaceWith(xhr.responseText);
          $parent.find("form.edit").show();
        }
      });
    });

    // single datum container
    $(".data-title a.edit").live("click", function() {
      var $link = $(this);
      var $dataBody = $link.parents(".data-title").next(".data-body");
      $submitAndCancel = $link.siblings(".submit, .cancel");

      $link.hide();
      $submitAndCancel.show();
      $dataBody.children("div").hide();
      $dataBody.children("form.edit").show();

      return false;
    });

    $(".data-title a.submit").live("click", function() {
      $(this).closest(".data-title").next(".data-body").find("form").submit();
      return false;
    });

    $(".data-body > form.edit").livequery(function() {
      var $targetForm = null;
      $(this).ajaxForm({
        beforeSubmit: function(data, $form, opts) { $targetForm = $form; },
        success: function(html, status, xhr, $form) {
          var $dataBody = $form.parents(".data-body");
          var $dataTitle = $dataBody.prev(".data-title");

          // Some singular data-container forms actually replace the entire data
          // body, since the data-title contains data edited by the form.
          if ($dataBody.hasClass("updates-title")) {
            $dataTitle.before(html);
            $dataBody.remove();
            $dataTitle.remove();
          } else {
            $dataTitle.find("a.new, a.edit").show();
            $dataTitle.find("a.submit, a.cancel").hide();
            $dataBody.html(html);
          }
        },
        error: function(xhr, status) {
          var $parent = $targetForm.parent();
          $targetForm.replaceWith(xhr.responseText);
          $parent.find("form.edit").show();
        }
      });
    });

    // either, but should be refactored to be split up and handle a single case
    $(".data-body form a.cancel").live("click", function() {
      var $form = $(this).parents("form");
      var $dataTitle = $form.parents(".data-body").prev(".data-title");
      var $parentDatum = $form.parents(".datum");

      $form.hide().resetForm();
      if ($parentDatum.length > 0) {
        // plural case, we're inside a datum that we need to show the contents of
        $parentDatum.children("div").show();
      } else {
        // singular case, the datum is a sibling, and we need to restore the title link
        $form.prev(".datum").show();
        if ($form.hasClass("new"))
          $dataTitle.children("a.new").show();
        else if ($form.hasClass("edit"))
          $dataTitle.children("a.edit").show();
      }

      return false;
    });

    $(".data-body form #address_seasonal").live("click", function () {
      var date_range = $(this).closest('.ctrl').siblings('.seasonal_date_range');
      if ($(this).is(':checked')) {
        date_range.show();
      } else {
        date_range.hide();
      }
    });

    $(".data-title a.cancel").live("click", function() {
      var $dataTitle = $(this).closest(".data-title");
      var $form = $dataTitle.next(".data-body").find("form");

      $form.hide().resetForm();

      // singular case, the datum is a sibling, and we need to restore the title link
      $form.prev(".datum").show();
      $dataTitle.find("a.new, a.edit").show();
      $dataTitle.find("a.submit, a.cancel").hide();

      return false;
    });

    $('#profile :checkbox[name="current"]').live("change", function() {
      var checkbox = this;
      var $tspan = $(checkbox).parents("form").find(".timespan");
      if (checkbox.checked) {
        $tspan.find(".date").hide();
        $tspan.find(".present").show();
      } else {
        $tspan.find(".present").hide();
        $tspan.find(".date").show();
      }
    });

    // Only show maiden name if female
    $("#profile select#person_sex").live("change", function() {
      var select = this;
      var $maidenNameControl = $("input#person_maiden_name").parent(".ctrl");
      if (select.value == "f")
        $maidenNameControl.show();
      else
        $maidenNameControl.hide();
    });

    // Education's "I currently attend this school" and "I graduated
    // from this school" are mutually exclusive. Ugly code. =\
    $("#profile :checkbox[name=current]").live("change", function() {
      if (this.checked)
        $(this).parents("form").find(".ctrl :checkbox[name='education[graduated]']").uncheck().trigger("change");
    });

    $('#profile :checkbox[name="education[graduated]"]').live("change", function() {
      if (this.checked)
        $(this).parents("form").find('.ctrl :checkbox[name="current"]').uncheck().trigger("change");
    });
  }

  // dev-toolbar
  $("#dev-toolbar a.hide").click(function() {
    $("#dev-toolbar").hide();
    return false;
  });

  $("#dev-toolbar a.bug").click(function() {
    var script = document.createElement("script");
    script.src = "/__rack_bug__/bookmarklet.js";
    document.getElementsByTagName("head")[0].appendChild(script);
    return false;
  });

  // calendar tooltips
  $('.calendar-day-tooltip').each(function(){
    var tt = $(this);
    var parts = tt.attr('id').split('_');
    var day = [parts[0], parts[1], 'day', parts[3]].join('_');
    var day_image = $(magnifyImage).clone();
    $('#' + day).prepend(day_image);
    day_image.tooltip({tip: tt, offset: [-3, 0]});
  });

  if(!Modernizr.input.autofocus) {
    $("input[autofocus]").livequery(function(){ $(this).focus() });
  }

  // hide announcment bar
  $("#hide_sites_announcement").livequery(function() {
    var anchor = $(this);
    anchor.one('click', function() {
      $.ajax({ type: 'POST', url: anchor.attr('href'), success: function() {
        anchor.parent().parent().slideUp();
      }});
      return false;
    });
  });

});

//preload the loading image
var loadingImage = new Image();
loadingImage.src = "/images/liboly/loader.gif";
loadingImage.alt = "Loading...";

//preload the required image
var requiredImage = new Image();
requiredImage.src = "/images/liboly/required_star.gif";
requiredImage.alt = "required";

//preload the magnify image
var magnifyImage = new Image();
magnifyImage.src = "/images/liboly/famfamfam/magnify.png";
magnifyImage.alt = "Details";
magnifyImage.className = "magnify-day"

// Adds the authenticity_token to relevant outgoing AJAX requests.
function appendAuthenticityToken(opts) {
  if (opts.type)
    opts.type = opts.type.toUpperCase();
  if (!opts.type || opts.type == "GET")
    return;
  if (!opts.data)
    opts.data = "authenticity_token=" + _rails.encoded_token();
  else if (!opts.data.match(/authenticity_token=/))
    opts.data += "&authenticity_token=" + _rails.encoded_token();
}

// Exactly what Rails does.
function submitMethodFormFor(element, method) {
  var f = document.createElement("form");
  f.style.display = "none";
  element.parentNode.appendChild(f);
  f.method = "POST";
  f.action = element.href;

  var m = document.createElement("input");
  m.setAttribute("type", "hidden");
  m.setAttribute("name", "_method");
  m.setAttribute("value", method);
  f.appendChild(m);

  var s = document.createElement("input");
  s.setAttribute("type", "hidden");
  s.setAttribute("name", "authenticity_token");
  s.setAttribute("value", _rails.authenticity_token);
  f.appendChild(s);

  f.submit();
  return false;
}

function delayedFade(elm, delay) {
  delay = delay || 4000;
  if (!elm)
    return;
  $(elm).fadeTo(delay, 1).hide("slow");
}


var PageIdentifier = Base.extend({
  hasPageEditor: function() {
    return $("body.pages.show, body.home.show, body.networked_pages.show").length > 0
      && $("#submit-order-path").length > 0;
  },

  hasCustomFormEditor: function() {
    return $("body.forms.edit").length > 0;
  },

  hasEventEditor: function() {
    return $("form.event").length > 0;
  },

  hasNavigationMenuEditor: function() {
    return $("body.pages.index, body.pages.private").length > 0;
  },

  hasMembershipEditor: function() {
    return $(".membership-editor").length > 0;
  },

  hasPermissionEditor: function() {
    return $(".permissions#role-editor").length > 0;
  },

  hasFileUpload: function() {
    return $("div#file_selectors").length > 0;
  },

  hasSectionedMemberList: function() {
    return $("body.people .section .faces").length > 0;
  },

  hasPhotoAlbumOrganizer: function() {
    return $("ul.photo_block_gallery.organize").length > 0
  },
  
  hasPhotoAlbumsOrganizer: function() {
    return $("ul.photo_block_gallery.organize_albums").length > 0
  },

  hasFileOrganizer: function() {
    return $("ul.file_uploads").length > 0
  },

  hasCoverPhotoSelector: function() {
    return $("div#make_cover").length > 0
  }
});


var NestedList = Base.extend({
  constructor: function($root, options) {
    this.$root = $root;
    this.options = $.extend({}, { sortableOptions: {} }, options);

    var opts = this.options;
    opts.sortableOptions = $.extend({
      axis: "y",
      cursor: "move"
    }, opts.sortableOptions);

    this.buildSortableCallbacks(opts.sortableOptions);

    var self = this;
    this.items().each(function() { self.detectIndentFromClass(this); });
    this.placeArrows();
  },

  root: function() { return this.$root; },
  items: function() { return this.$root.children("li"); },
  id: function(sel) { return $(sel).objectIds()[0]; },

  detectIndentFromClass: function(selector) {
    var $elem = $(selector);

    for (var i = 1; i <= 5; i++) {
      if ($elem.hasClass("d" + i)) {
        $elem.attr("indent", i);
        return i;
      }
    }

    return null;
  },

  setDepth: function(selector, depth) {
    if (depth < 1 || depth > 5)
      return;

    var $elem = $(selector);
    $elem.removeClass("d1 d2 d3 d4 d5");
    $elem.attr("indent", depth).addClass("d" + depth);
  },

  depthOf: function(selector) {
    var $elem = $(selector);
    var val   = $elem.attr("indent") || this.detectIndentFromClass($elem);
    if (!val)
      throw("Element " + selector + " has no 'indent' attribute");

    var depth = parseInt(val);
    if (!$elem.hasClass("d" + depth))
      throw("Element " + selector + " has mismatched 'indent' and 'd<x>' class");

    return depth;
  },

  indent: function(selector) { this.redent(selector, 1); },
  undent: function(selector) { this.redent(selector, -1); },
  redent: function(selector, delta) {
    var $elem = $(selector);
    this.setDepth($elem, this.depthOf($elem) + delta);
    this.placeArrows();
  },

  placeArrows: function() {
    var self = this;

    this.root().find(".arrow").hide();

    this.items().each(function() {
      var $item = $(this);
      if (self.descendantsOf($item).length > 0)
        $item.find(".arrow.open").show();
    });
  },

  descendantsOf: function(selector) {
    var self = this;

    var $parent = $(selector);
    var descendants = [];

    $parent.nextAll().each(function() {
      var $item = $(this);

      if (!$item.hasClass("ui-sortable-placeholder")) {
        if (self.depthOf($item) <= self.depthOf($parent))
          return false;
        descendants.push($item);
      }
    });

    return descendants;
  },

  collapse: function(selector) {
    var $parent = $(selector);
    var descendants = this.descendantsOf($parent);

    if (descendants.length == 0)
      return [];

    $parent.addClass("collapsed").
            find(".arrow.open").hide().end().
            find(".arrow.closed").show();
    $.each(descendants, function() { this.hide(); });

    return descendants;
  },

  expand: function(selector) {
    var $parent = $(selector);
    var descendants = this.descendantsOf($parent);
    if (descendants.length == 0)
      return [];

    $parent.removeClass("collapsed").
            find(".arrow.closed").hide().end().
            find(".arrow.open").show();
    $.each(descendants, function() { this.css("display", "list-item"); });

    return descendants;
  },

  normalize: function() {
    var self = this;

    var prevDepth = null;
    var prevOrigDepth = null;

    this.items().each(function() {
      var $item = $(this);
      var curDepth = self.depthOf($item);
      if (!prevDepth) {
        self.setDepth($item, 1);
        prevDepth = 1;
      } else if (prevOrigDepth && curDepth == prevOrigDepth) {
        self.setDepth($item, prevDepth);
      } else if (curDepth - prevDepth > 1) {
        self.setDepth($item, prevDepth + 1);
        prevOrigDepth = curDepth;
        prevDepth += 1;
      } else {
        prevDepth = curDepth;
        prevOrigDepth = null;
      }
    });
  },

  toTree: function() {
    var self = this;
    var tree = {
      items: [],
      depths: {}
    };

    this.normalize();
    this.items().each(function() {
      var $item = $(this);
      var id = self.id($item);
      tree.items.push(id);
      tree.depths[id] = self.depthOf($item);
    });

    return tree;
  },

  serialize: function() {
    var params = [];
    var tree = this.toTree();

    jQuery.each(tree.items, function(i, v) { params.push("items[]=" + v); });
    jQuery.each(tree.depths, function(i, v) {
      params.push("depths[" + i + "]=" + v);
    });

    return params.join("&");
  },

  enableSorting: function() {
    var root = this.root();
    root.addClass("sort-mode");
    if (root.hasClass("ui-sortable-disabled"))
      root.sortable("enable");
    else
      root.sortable(this.options.sortableOptions);
  },

  disableSorting: function() {
    var root = this.root();
    root.sortable("disable");
    root.removeClass("sort-mode");
  },

  // @private
  buildSortableCallbacks: function(sopts) {
    var self = this;
    var collapsedDescendants = null;

    var startFn = sopts.start;
    sopts.start = function(evt, ui) {
      collapsedDescendants = self.collapse(ui.item);
      if (startFn)
        startFn(evt, ui);
    };

    var stopFn = sopts.stop;
    sopts.stop = function(evt, ui) {
      $.each(collapsedDescendants.reverse(), function() {
        $(this).remove().insertAfter(ui.item);
      });

      self.expand(ui.item);
      if (stopFn)
        stopFn(evt, ui);

      self.root().sortable("refresh");
    };
  }
});


/*
 *  new PageBlockList($("ul.page_blocks"), {
 *    orderPath: "/admin/pages/foo/page_blocks/order"
 *   });
 */
var PageBlockList = Base.extend({
  constructor: function($root, options) {
    this.$root = $root;
    this.options = options || {};
  },

  root: function() { return this.$root; },
  blocks: function() { return this.$root.find("li.page_block"); },

  blockContaining: function(elem) {
    if (elem.is && elem.is("li.page_block"))
      return elem;

    var $elem = $(elem);
    var pb = $elem.parents("li.page_block").eq(0);
    return pb.length > 0 ? pb : null;
  },

  add: function(data, location, layoutLetter, pageEditor) {
    if (pageEditor != undefined && typeof tinyMCE != "undefined")
      $("#facebox").find("textarea.mce").each(pageEditor._removeTinyMCE);

    $("#facebox .content").html("<ul>" + data + "</ul>");

    return data;
  },

  addWrapped: function(data, location, layoutLetter) {
    this.add($('<li class="page_block"></li>').append(data), location, layoutLetter);
  },

  remove: function(elem) {
    var container = this.blockContaining(elem);

    if (typeof tinyMCE != "undefined")
      container.find("textarea.mce").each(function() {
        tinyMCE.execCommand("mceRemoveControl", false, this.id);
      });

    if (container)
      container.remove();
  },

  replace: function(elem, data) {
    var container = this.blockContaining(elem);
    if (!container)
      return;
    container.replaceWith(data);
  },

  enableSorting: function() {
    var self = this;
    var root = this.root();

    root.sortable({
      connectWith: ".page_blocks",
      handle: ".move",
      cursor: "move",
      stop: function(evt, ui) { self.submitOrdering(); }
    });
  },

  disableSorting: function() { this.root().sortable("destroy"); },

  refreshSorting: function() {
    var root = this.root();
    if (root.hasClass("ui-sortable"))
      root.sortable("refresh");
  },

  submitOrdering: function() {
    var layoutBoxData = new Array;

    $("div[id^=layout_box_]").each(function() {
      var layoutLetter = $(this).attr("id").slice(-1);
      var pageBlocks = $(this).children().sortable("serialize", {
        key: "page_blocks[" + layoutLetter + "][]",
        expression: "page_block_(.+)"
      });
      if (pageBlocks.length > 0)
        layoutBoxData.push(pageBlocks);
    });

    $.ajax({
      url: this.options.orderPath,
      type: "PUT",
      data: layoutBoxData.join("&")
    });
  }
});


var PageEditor = Base.extend({
  constructor: function() {
    this.pageBlockList = new PageBlockList($("ul.page_blocks"), {
      orderPath: $("#submit-order-path").attr("href")
    });

    var self = this;
    $(document).bind("close.facebox", function() {
      if (typeof tinyMCE != "undefined")
        $("#facebox").find("textarea.mce").each(self._removeTinyMCE);

      self.showActions();
    });

    $("#facebox form.edit").livequery(function() {
      $form = $(this);
      $form.ajaxForm({
        data: { "wants_path" : true },
        dataType: "json",
        success: function(data) { window.location.pathname = data.path; },
        error: function(xhr, status) { $form.replaceWith(xhr.responseText); }
      });
    });

    $("#facebox form.edit a.cancel").live("click", function() {
      $(document).trigger("close.facebox");
      return false;
    });
  },

  enableEditing: function() {
    var pbl = this.pageBlockList;

    $("#page-edit").hide();
    $("#page-add-item, #page-edit-quit, #page-edit-settings").show();
    $("#title").removeClass("unseen");

    this.showActions();
    pbl.root().addClass("edit-mode").parent().css("background-color", "#f5f5f5")
    pbl.enableSorting();
  },

  disableEditing: function() {
    var pbl = this.pageBlockList;

    $("#page-add-item, #page-edit-quit, #page-edit-settings").hide();
    $("#page-edit").show();

    this.cancelPageBlockEdit(pbl);
    this.hideActions();
    pbl.root().removeClass("edit-mode").parent()
      .css("background-color", "transparent");
    pbl.disableSorting();
  },

  hideActions: function() {
    this.pageBlockList.root().find(".actions").hide();
  },

  showActions: function() {
    this.pageBlockList.root().find(".actions").show();
  },

  pageBlockUnderEdit: function() {
    return this.pageBlockList.root().children(".editing");
  },

  pageBlockBeingCreated: function() {
    return this.pageBlockList.root().children(":not([id])");
  },

  addPageBlock: function(html, where, layoutLetter) {
    var pbl = this.pageBlockList;
    var pageBlockData = pbl.add(html, where, layoutLetter, this);
    var boxPlacement = $("#layout_box_" + layoutLetter).children();

    if (where == "top")
      boxPlacement.prepend(pageBlockData);
    else
      boxPlacement.append(pageBlockData);

    pbl.submitOrdering();
    $(document).trigger("close.facebox");
    this.removePlaceholderPanel();

    if (where == "bottom") {
      var selector = boxPlacement.children(".page_block:last").get(0);
      $("html, body").animate({
        scrollTop: $([selector]).offset().top - 30
      }, 500);
    }
  },

  editPageBlock: function($pb) {
    this.hideActions();
    $pb.addClass("editing");
  },

  cancelPageBlockEdit: function(pbl) {
    $(document).trigger("close.facebox");
    $.each(pbl.blocks(), function() { $(this).removeClass("editing"); });
  },

  // AJAX success callback; data contains a full li.page_block
  savedPageBlock: function(data, status) {
    var pbl = this.pageBlockList;
    var pbue = this.pageBlockUnderEdit();

    pbl.replace(pbue, data);
    $(document).trigger("close.facebox");
  },

  removePlaceholderPanel: function() { $("#page-content > .panel").remove(); },

  // @private
  //
  // In order for TinyMCE to allow you to relaunch an instance on a
  // textarea with an ID which has *already* had an instance, you must
  // remove the control BEFORE removing the original textarea from the
  // DOM.
  //
  // This code really doesn't belong here, but f*ck. It works.
  _removeTinyMCE: function() {
    tinyMCE.execCommand("mceRemoveControl", false, this.id);
  }
});


// Silly but helps legibility in application.js
var PermissionEditor = Base.extend({
  constructor: function(container) {
    this.container = container;
  },

  find: function(selector) {
    return this.container.find(selector);
  }
});


var Inviter = Base.extend({
  constructor: function(form) {
    this.form = form;
    this.inviteeList = $("#invitees-added");
    this.tempHTML = "";
    this.callerName = $("#inviter_caller_name").val();
    this.callerId = $("#inviter_caller_id").val();
  },

  bind: function() {
    var self = this;
    var search = this.form.find('input:text[name="search"]');

    var finder = FastFinder.finderFor(search, {
      disableSubmit: false,
      membershipState: $("#fast_finder_membership_state"),
      control: this.form.find(".scope.individuals")
    });

    search.data("finder", finder);
    finder.bind();

    $('#inviter_add_without_inviting').val('');

    this.form.find(".scope.individuals button").click(function() {
      if (!finder.textField.hasClass("chosen"))
        return false;

      var id = finder.valueField.val();
      var name = finder.textField.val();
      var membership_state = finder.membershipState.val();
      finder.membershipState.val("");

      self.addInvitee(name, id, membership_state);
      self.addInvitee(name, id, null, "#none");
      finder.textField.val("").removeClass("chosen");

      return false;
    });

    this.form.find(".scope.by-group button").click(function() {
      var menu = $(this).siblings("select");
      var group_id = parseInt(menu.val());

      if (isNaN(group_id))
        return false;

      self.disableButtons();
      self.tempHTML = this.innerHTML;
      this.innerHTML = "adding...";

      self.eachGroupMember(group_id, self.callerName, self.callerId, function(person, enable_button) {
        self.addInvitee(finder.makeName(person), person.id, person.membership_state, enable_button);
      });

      return false;
    });

    this.form.find(".scope.by-role button").click(function() {
      var menu = $(this).siblings("select");
      var role_id = parseInt(menu.val());

      if (isNaN(role_id))
        return false;

      self.disableButtons();
      self.tempHTML = this.innerHTML;
      this.innerHTML = "adding...";

      self.eachRoleMember(role_id, self.callerName, self.callerId, function(person, enable_button) {
        self.addInvitee(finder.makeName(person), person.id, person.membership_state, enable_button);
      });

      return false;
    });

    this.form.find("button[name=inviter_add_without_inviting]").click(function(){
      $('#inviter_add_without_inviting').val('1');
    });

    this.form.find("ul#invitees-added a.rm").live("click", function() {
      var id = $(this).siblings("input:hidden").val();
      self.removeInvitee(id);
      return false;
    });
  },

  addInvitee: function(name, person_id, membership_state, enable_button) {
    // Hack: the id of the button to enable is passed after the last item of the collection is processed
    if (enable_button) {
      this.enableButtons();
      this.refreshInviteeList();
      $(enable_button).attr("innerHTML", this.tempHTML);
      return;
    }

    if (this.alreadyInviting(person_id))
      return;

    var input = '<input type="hidden" name="ids[]" value="' + person_id + '">';
    var span  = '<span class="name">' + name;
    if (membership_state) {
      span += " (" + membership_state + " member";
      if (membership_state == "current" || membership_state == "invited")
        span += " - no invite will be sent";
      span += ")";
    }

    span += "</span>";
    var rm    = '<a href="#" class="rm"><img src="/images/liboly/scope/delete_16.png"></a>';

    this.inviteeList.append('<li id="invitee-' + person_id + '">' + input + rm + span + "</li>");
  },

  removeInvitee: function(person_id) {
    this.inviteeList.find("#invitee-" + person_id).remove();
    if (this.inviteeList.children("li").length == 0)
      this.disableSubmit();
  },

  alreadyInviting: function(person_id) {
    return $("#invitee-" + person_id).length > 0;
  },

  refreshInviteeList: function() {
    this.inviteeList.parents(".filled").show();
    this.inviteeList.parents("div.invitees").find(".empty").hide();
    this.enableSubmit();
  },

  enableSubmit: function() {
    this.form.find("> button").attr("disabled", false);
  },

  disableSubmit: function() {
    this.form.find("> button").attr("disabled", "disabled");
  },

  enableButtons: function() {
    this.form.find("button").attr("disabled", false);
  },

  disableButtons: function() {
    this.form.find("button").attr("disabled", "disabled");
  },

  submit: function() { this.form.submit(); },

  eachGroupMember: function(group_id, caller_name, caller_id, callback) {
    var self = this;
    var url = "/api/groups/" + group_id + "/members";

    if (caller_name) {
      url += "?caller_name=" + caller_name + "&caller_id=" + caller_id;
    }

    $.ajax({
      url: url,
      dataType: "json",
      data: { per_page: -1 },
      success: function(obj) {
        $.each(obj.payload, function() { callback.apply(self, [this]); });
        // Hack: simulate an additional call with the id of the button to enable
        callback.apply(self, [obj.payload[0], "#inviter_add_members_by_group"]);
      }
    });
  },

  eachRoleMember: function(role_id, caller_name, caller_id, callback) {
    var self = this;
    var url = "/api/roles/" + role_id + "/members";

    if (caller_name) {
      url += "?caller_name=" + caller_name + "&caller_id=" + caller_id;
    }

    $.ajax({
      url: url,
      dataType: "json",
      data: { per_page: -1 },
      success: function(obj) {
        $.each(obj.payload, function() { callback.apply(self, [this]); });
        // Hack: simulate an additional call with the id of the button to enable
        callback.apply(self, [obj.payload[0], "#inviter_add_members_by_role"]);
      }
    });
  }
});


var MemberFastFinder = Base.extend({
  constructor: function (form, options) {
    this.form       = form;
    this.options    = options || {};
    this.finder     = null;
    this.memberList = $('#recipient-entries');
  },

  bind: function () {
    var self   = this
      , search = this.form.find('input:text[name="search"]');

    this.finder = FastFinder.finderFor(search, {
      disableSubmit: false,
      control: this.form.find(".messaging.individuals"),
      onChoose: this.addPerson
    });

    search.data("finder", this.finder);
    this.finder.bind();

    this.form.find("#recipient-entries a.rm").live("click", function() {
      var id = $(this).siblings("input:hidden").val();
      self.removeEntry(id);
      return false;
    });
  },

  // This callback runs in the context of FastFinder, not MemberFastFinder
  addPerson: function (person) {
    if ($("#entry-" + person.id).length > 0)
      return;

    var input = '<input type="hidden" name="ids[]" value="' + person.id + '">'
      , span  = '&nbsp;<span class="name">' + this.makeName(person) + "</span>"
      , rm    = '<a href="#" class="rm"><img src="/images/liboly/famfamfam/cross.png" width="10px"></a>'
      , self  = this;

    $('#recipient-entries').append('<div class="entry" id="entry-' + person.id + '">' + input + rm + span + "</div>");

    setTimeout(function () { self.textField.val(''); }, 0);
    this.textField.removeClass('chosen');
    this.valueField.val('');
    $('#message_recipient_type_specific').attr('checked', true);
  },

  addSelectOption: function (entry_type, dom_id, prefix) {
    var option_id = $('#' + dom_id).val();

    if (option_id == '')
      return;

    var entry_id = entry_type + '_' + option_id;

    if (this.existingEntry(entry_id)) {
      $('#' + dom_id).val('');
      return;
    }

    var label =  $('#' + dom_id + ' :selected').text()
      , input = '<input type="hidden" name="ids[]" value="' + entry_id + '">'
      , span  = '&nbsp;<span style="font-style: italic;">' + label + '</span>'
      , rm    = '<a href="#" class="rm"><img src="/images/liboly/famfamfam/cross.png" width="10px" alt="remove"></a> ';

    $('#recipient-entries').append('<div class="entry" id="entry-' + entry_id + '">' + input + rm + prefix + span + "</div>");
    $('#' + dom_id).val('');
    $('#message_recipient_type_specific').attr('checked', true);
  },

  removeEntry: function (entry_id) {
    this.memberList.find('#entry-' + entry_id).remove();
  },

  existingEntry: function (entry_id) {
    return $('#entry-' + entry_id).length > 0;
  }
});


// Object representing the *submittable* version of a custom form;
// this has nothing to do with the custom form editor.
var CustomForm = Base.extend({
  constructor: function(form) {
    this.form = $(form);
    this.saveButtons = this.form.parent().find("button.save"); // possibly plural
    this.saveButtonImages = this.saveButtons.find("img");
    this.loadingImage = $(loadingImage).clone();
  },

  bind: function() {
    var customForm = this;
    this.form.ajaxForm({
      beforeSubmit: function() { customForm.displayLoadingImage(); },
      data: { xhr: true },
      success: function(data) {
        if (data.indexOf('invalid_fields') != -1) {
          var xhr = { "status": "422", "responseText": data }
          customForm.notifyOfErrors(xhr);
        } else {
          customForm.form.resetForm();
          customForm.notifyOfSuccess();
          customForm.lineItemsCleanUp();
        }
      },
      error: function(xhr) { customForm.notifyOfErrors(xhr); },
      complete: function() { customForm.displaySaveImage(); }
    });

    this.bindLineItems();
  },

  bindLineItems: function(){
    this.bindLineItemSelect();
    this.bindLineItemActions();
  },

  bindLineItemSelect: function(){
    var customForm = this;
    // Update the value after changing any selection: quantity or item
    var lineItemSelect = this.form.find('.form-line-item-quantity, .form-line-item-select');

    lineItemSelect.change(function(e) {
      var lineItem = $(this).closest('.form-line-item');
      var quantity = lineItem.find('.form-line-item-quantity').val();
      // The value has this form "option.id:value", it has to be split
      var selectedValue = lineItem.find('.form-line-item-select option:selected').val().split(':')[1];
      var lineItemValue = lineItem.find('.form-line-item-value');

      lineItemValue.text(CustomForm.formatAmount(Number(selectedValue) * Number(quantity)));
      customForm.updateLineItemGroupTotal($(this).closest('.form-field'));
    });

    // Only for IE, resize the select tag based on the length on the options
    if($.browser.msie) { lineItemSelect.selecteSizer(); }
  },

  bindLineItemActions: function(){
    var customForm = this;

    this.form.find('.form-line-item a.add-line').click(function(event) {
      event.preventDefault();
      var lineItem = $(this).closest('.form-line-item');
      var newLineItem = lineItem.clone(true).insertAfter(lineItem);
      newLineItem.find('.form-line-item-select')[0].selectedIndex = 0;
      newLineItem.find('.form-line-item-value').text('$');
    });

    this.form.find('.form-line-item a.remove-line').click(function(event) {
      event.preventDefault();
      var lineItem = $(this).closest('.form-line-item');
      var collection = lineItem.parent();

      if (collection.find('.form-line-item').length > 1) {
        lineItem.remove();
        customForm.updateLineItemGroupTotal(collection);
      }
      else alert('This is the last item and can not be removed.');
    });

  },

  calculateLineItemTotalFor: function(group){
    var total = 0;

    $.each(group.find('.form-line-item-value'), function() {
      total += Number($(this).text().substring(2));
    });

    return total;
  },

  updateLineItemGroupTotal: function(group){
    var total = this.calculateLineItemTotalFor(group);

    group.find('.form-line-item-value-total').text(CustomForm.formatAmount(total));
    this.updateLineItemGrandTotal();
  },

  updateLineItemGrandTotal: function(){
    var total = this.calculateLineItemTotalFor(this.form);
    this.form.find('.form-line-item-grand-total .value').text(CustomForm.formatAmount(total));
    return total;
  },

  scrollToTopOfForm: function() {
    $("html, body").animate({ scrollTop: this.form.offset().top - 30 }, 500);
  },

  scrollToErrors: function() { this.form.find(".errors")[0].scrollIntoView(); },

  clearMessages: function() {
    this.form.find(".invalid").andSelf().removeClass("invalid");
    this.form.find(".errors, .success").remove();
  },

  markFieldInvalid: function(fieldId) {
    this.form.find(".form_field_" + fieldId).addClass("invalid");
  },

  displayLoadingImage: function() {
    var cf = this;
    this.saveButtons.attr("disabled", "disabled");
    this.saveButtons.find("img").each(function(){$(this).replaceWith(cf.loadingImage.clone())});
  },

  displaySaveImage: function() {
    var cf = this;
    this.saveButtons.removeAttr("disabled");
    this.saveButtons.find("img").each(function(i, el){$(el).replaceWith(cf.saveButtonImages.eq(i).clone())});
  },

  notifyOfSuccess: function() {
    this.clearMessages();
    this.form.prepend('<div class="success"><p class="heading">Your submission has been recorded.</p></div>');
    this.scrollToTopOfForm();
  },

  lineItemsCleanUp: function() {
    var f = this;
    $.each(this.form.find('.form-field.line-items'), function() {
      var field = $(this);
      var items = field.find('.form-line-item');
      var newLineItem = $(items.get(0)).clone(true);
      var lineItemTotal = field.find('.form-line-item-total');

      newLineItem.find('.form-line-item-value').text('$');
      items.detach();
      newLineItem.insertBefore(lineItemTotal);

      f.updateLineItemGroupTotal(field);
    });
  },

  notifyOfErrors: function(xhr) {
    var customForm = this;
    this.clearMessages();

    var message = '<div class="errors"><p class="heading">There were errors processing your submission.</p>';
    var errorsByField = this.fieldErrorsInResponse(xhr);
    if (errorsByField) {
      message += "<ol>";
      $.each(errorsByField, function(fieldId, errors) {
        customForm.markFieldInvalid(fieldId);
        $.each(errors, function() { message += "<li>" + this + "</li>"; });
      });
      message += "</ol>";
    }

    message += "</div>";
    this.form.prepend(message);
    this.scrollToErrors();
  },

  fieldErrorsInResponse: function(xhr) {
    if (xhr.status != 422)
      return null;

    var obj = $.parseJSON( xhr.responseText );
    if (typeof(obj["invalid_fields"]) == "undefined")
      return null;

    return obj["invalid_fields"];
  }
});

var PaymentForm = CustomForm.extend({
  constructor: function(form) {
    this.base(form);
    this.gatewayForm = this.form.next("form.gateway");
  },

  bind: function() {
    var customForm = this;

    this.form.ajaxForm({
      resetForm: true,
      beforeSubmit: function() {
        customForm.displayLoadingImage();
        customForm.updateLineItemGrandTotal();
      },
      data: { xhr: true },
      success: function(data) {
        if (data.indexOf('zero_amount') != -1) {
          customForm.notifyOfSuccess();
          customForm.lineItemsCleanUp();
          customForm.updateLineItemGrandTotal();
        } else if (data.indexOf('invalid_fields') == -1) {
          customForm.enableGatewayForm($.parseJSON(data));
        } else {
          var xhr = { "status": "422", "responseText": data }
          customForm.notifyOfErrors(xhr);
          customForm.scrollToTopOfForm();
        }
      },
      error: function(xhr) { customForm.notifyOfErrors(xhr); },
      complete: function() { customForm.displaySaveImage(); }
    });

    this.bindGatewayForm();
    this.bindLineItems();
  },

  updateLineItemGrandTotal: function(){
    // Prevent zeroing the payment amount when no line items are present (#768)
    if (this.form.find('.form-line-item').length == 0) return;

    var total = this.base();
    var rawFormattedTotal = CustomForm.formatAmount(total, { includeSymbol: false });
    this.gatewayForm.find('#amount').val(rawFormattedTotal);
    this.gatewayForm.find('.form-field .set .amount').text(rawFormattedTotal);
  },

  bindGatewayForm: function() {
    var paymentForm = this;

    this.gatewayForm.one("submit", function(e) {
      paymentForm.injectDynamicGatewayValues(function() {
        paymentForm.submitGatewayForm();
      });
      e.preventDefault();
    });
  },

  enableGatewayForm: function(attrs) {
    this.gatewayHashPath = attrs["hash_url"];
    this.displaySaveImage();
    this.form.hide();

    this.addHiddenGatewayField("orderid", attrs["orderid"]);
    this.gatewayForm.show();
  },

  addHiddenGatewayField: function(name, value) {
    this.gatewayForm.prepend('<input type="hidden" name="' + name + '" value="' + value + '">');
  },

  injectDynamicGatewayValues: function(callback) {
    var customForm = this;
    var amount = this.gatewayFormAmount();
    var emailReceiptTo = this.gatewayFormEmailReceiptTo();
    $.post(this.gatewayHashPath, { amount: amount, email_receipt_to: emailReceiptTo }, function (response) {
      customForm.addHiddenGatewayField("hash", response["hash"]);
      customForm.addHiddenGatewayField("time", response["time"]);
      customForm.setAmount(amount);
      customForm.setExpiration();
      callback.call();
    }, "json");
  },

  gatewayFormAmount: function() {
    if (this.gatewayForm.find('input[name="amount"]').length > 0) {
      // fixed amount
      return this.gatewayForm.find('input[name="amount"]').val();
    } else {
      // variable amount
      var getAmount = function (form, type) {
        return parseInt(form.find('input[name="amount_' + type + '"]').val());
      }

      var dollars = getAmount(this.gatewayForm, 'dollars');
      var cents   = getAmount(this.gatewayForm, 'cents');

      if (isNaN(dollars) || dollars < 0)
        dollars = '0';

      if (isNaN(cents) || cents < 0)
        cents = '00';
      else if (cents < 10)
        cents = '0' + cents;

      return dollars + '.' + cents;
    }
  },

  gatewayFormEmailReceiptTo: function() {
    return this.gatewayForm.find('input[name="email_receipt_to"]').val();
  },

  submitGatewayForm: function() { this.gatewayForm.get(0).submit(); },

  setAmount: function(amount) {
    var input = this.gatewayForm.find('input[name="amount"]');
    if (input.length > 0)
      input.attr("value", amount);
    else
      this.addHiddenGatewayField("amount", amount);
  },

  setExpiration: function() {
    var month = this.gatewayForm.find('select[name="ccexp_month"]').val();
    var year  = this.gatewayForm.find('select[name="ccexp_year"]').val();
    var mmyy  = month + year.substring(2, 4);
    if (mmyy.length == 3)
      mmyy = "0" + mmyy;
    this.addHiddenGatewayField("ccexp", mmyy);
  }
});

CustomForm.formatAmount = function(amount, options) {
  options = $.extend({ includeSymbol: true }, options);
  var amt = Number(amount);
  var symbol = options['includeSymbol'] ? '$ ' : '';
  return symbol + (isNaN(amt) ? 0 : amt).toFixed(2);
};


var CustomFormEditor = Base.extend({
  constructor: function() {
    this.tabs = {};
    this.fields = [];
    this.fieldMap = {};
    this.deletedFields = [];
    this.selectedField = null;
    this.newFieldCount = 0;
    this.maxOptionsSize = 8; // equals ~ 250px = #field-options-prop.properties:max-height
    this.optionsWidthNoScroll = parseInt($(".option-prop .label").css("width"));
    this.optionsWidthScroll = 85;
    this.basePath = document.location.pathname.replace(/(\/edit)?\/?$/, "");


    this.loadProperties();
  },

  fieldTypes: {
    plain_text: "Descriptive Text",
    input_text: "Single Textbox",
    number: "Number-only",
    email: "Email Address",
    uri: "Web Address",
    textarea_text: "Essay/Comment Box",
    name: "Name",
    phone_number: "Phone Number",
    price: "Price",
    date: "Date",
    drop_down: "Drop-down",
    time: "Time",
    checkbox: "Checkboxes",
    radio: "Multiple Choice",
    line_item: "Line Items",
    file: "Input File",
    address: "Address",
    email_receipt: "Receipt"
  },

  humanFieldTypeName: function(type) { return this.fieldTypes[type]; },

  loadProperties: function() {
    var node = $("form > .properties");
    if (node.length == 0)
      return null;

    var properties = $.parseJSON( node.text() );
    var editor = this;
    $.each(properties, function(key, value) { editor[key] = value; });

    return properties;
  },

  bind: function() {
    var editor = this;

    // Scroll the menu with the window
    $(window).scroll(function() { editor.updateMenuPosition(); });

    // Catch new fields being added
    $("#add-field a").click(function() {

      var fieldType = this.id.replace(/^add-/, "");

      if (editor.hasEmailReceiptField() && fieldType == 'email-receipt') {
        alert("You can have only one Receipt field.")
        return false;
      }

      if (!editor.hasFieldList())
        editor.insertFieldList();

      editor.addNewField(fieldType);

      return false;
    });

    // Hijack the save button
    $("#edit-custom-form-column-one button.save").click(function() {
      editor.save();
      return false;
    });

    // Enable sorting
    $("#custom-form-items").livequery(function() {
      $(this).sortable({
        containment: $("body"),
        axis: "y",
        cursor: "move",
        opacity: .75,
        delay: 100,
        stop: function() { editor.reorderFields(); }
      });
    });

    // This should obviously be moved here, but for now just wrap it
    editor.bindEvents();
  },

  bindEvents: function() {
    var self = this;

    $("#form-label").keyup(function() { self.updateTitle(this.value); });

    $("#form-email").change(function() {
      self.emailDropdownChanged(this.value);
    });

    $("#form-email-list").keyup(function() {
      self.emailListChanged(this.value);
    });

    $("#field-label").keyup(function() {
      if (self.selectedField)
        self.selectedField.updateLabel(this.value);
    });

    $("#field-blurb").keyup(function() {
      if (self.selectedField)
        self.selectedField.updateBlurb(this.value);
    });

    $("#field-size").change(function() {
      if (self.selectedField)
        self.selectedField.updateSize(this.value);
    });

    $("#field-required").click(function() {
      if (self.selectedField)
        self.selectedField.toggleRequired();
    });

    $(".visibility :checkbox").click(function() {
      if (self.selectedField)
        self.selectedField.toggleDisplayField(this.name);
    });

    $("#form_payment_form").change(function() {
      self.payment_form = this.checked;
      self.togglePaymentOptions(this.checked);
      self.setModified();
    });

    $('input[name="amount_type"]').change(function() {
      var needAmountInputs = this.value == "fixed" ? this.checked : !this.checked;
      self.toggleAmountValueInputs(needAmountInputs);
      self.amount = this.value == "fixed" ? self.parseAmountInputs() : null;
      self.setModified();
    });

    $("#amount-value :text").change(function() {
      self.amount = self.parseAmountInputs();
      self.setModified();
    });
  },

  addTabs: function() {
    var editor = this;
    $("#custom-form-menu > li").each(function() { editor.addTab(this); });
  },

  addTab: function(tabNode) {
    var tab = new CustomFormEditor.Tab(this, tabNode);
    this.tabs[tab.id] = tab;
    tab.bind();
  },

  addFields: function() {
    var editor = this;
    $("#custom-form-items li").each(function() { editor.addField(this); });
  },

  addField: function(fieldNode) {
    var field = new CustomFormEditor.Field(this, fieldNode);
    this.fields.push(field);

    if (field.isNew()) {
      this.newFieldCount++;
      field.updateId("new-" + this.newFieldCount);
    }

    this.fieldMap[field.id] = field;
    field.bind();
  },

  addFieldByMarkup: function(html) {
    var elem = $(html);
    $("#custom-form-items").append(elem).sortable("refresh");
    this.addField(elem);
  },

  closeTabsExcept: function(tab) {
    $.each(this.tabs, function() {
      if (this.id != tab.id)
        this.close();
    });
  },

  updateTitle: function(newTitle) {
    this.label = newTitle;
    $("#title h2").text(newTitle);
    this.updateMenuPadding();
    this.setModified();
  },

  emailDropdownChanged: function(selection) {
    if (selection == "list") {
      $("#form-email-list-prop").show();
      this.emailListChanged($("#form-email-list").val());
    } else {
      $("#form-email-list-prop").hide();
      this.updateEmail(selection);
    }
  },

  emailListChanged: function(value) { this.updateEmail(value); },

  updateEmail: function(value) {
    this.email_subs_to = value;
    this.setModified();
  },

  deselectField: function() {
    if (this.selectedField)
      this.selectedField.deselect();
  },

  togglePaymentOptions: function(show) {
    var elem = $("#amount-settings");
    if (show)
      elem.slideDown("normal");
    else
      elem.slideUp("normal");
  },

  toggleAmountValueInputs: function(show) {
    var elem = $("#amount-value");
    if (show)
      elem.show();
    else
      elem.hide();
  },

  parseAmountInputs: function() {
    var dollars = parseInt($("#amount_dollars").val());
    var cents   = parseInt($("#amount_cents").val());

    if (isNaN(dollars) || dollars < 0)
      dollars = 0;
    if (isNaN(cents) || cents < 0)
      cents = 0;

    var amount = dollars * 100 + cents;
    if (amount < 10)
      amount = "0" + amount;
    return amount;
  },

  editFieldProperties: function(field) {
    if (this.selectedField != field) {
      this.clearFieldProperties();
      this.selectedField = field;
    }

    $("#inactive").hide();
    $("#field-type").text(this.humanFieldTypeName(field.type));
    $("#field-type-prop").show();

    if (typeof(field.label) != "undefined") {
      $("#field-label").val(field.label);
      $("#field-label-prop").show();
    } else { $("#field-label-prop").hide(); }

    if (typeof(field.blurb) != "undefined") {
      if (field.type == 'email_receipt') { $("#field-blurb-prop").find('label').text('Receipt Message:') };
      $("#field-blurb").text(field.blurb).val(field.blurb);
      $("#field-blurb-prop").show();
    } else {
      $("#field-blurb-prop").hide();
      $("#field-blurb").text("").val("");
    }

    if (typeof(field.field_size) != "undefined") {
      $("#field-size").val(field.field_size);
      $("#field-size-prop").show();
    } else { $("#field-size-prop").hide(); }

    if (typeof(field.required) != "undefined") {
      if (field.required)
        $("#field-required").check();
      else
        $("#field-required").uncheck();

      $("#field-required").attr("disabled", "");
      $("#field-required-prop").show();
    } else { $("#field-required-prop").hide(); }

    if (typeof(field.options) != "undefined") {
      this.removeOptionProperties();
      this.manageOptionsScrollBar(field);
      $.each(field.options, function() {
        var option = this;
        var $propNode = $("#default-option").clone().appendTo("#field-options-prop");
        $propNode.attr("id", "option-prop-" + option.id).show();

        option.bind($propNode);
      });
      $("#field-options-prop").show();
    } else {
      $("#field-options-prop").hide();
      this.removeOptionProperties();
    }

    if (typeof(field.line_items) != "undefined") {
      this.removeLineItemProperties();
      this.manageLineItemsScrollBar(field);
      $.each(field.line_items, function() {
        var item = this;
        var $propNode = $("#default-line-item").clone().appendTo("#field-line-items-prop");
        $propNode.attr("id", "line-item-prop-" + item.id).show();

        item.bind($propNode);
      });
      $("#field-line-items-prop").show();
    } else {
      $("#field-line-items-prop").hide();
      this.removeLineItemProperties();
    }

    if (typeof(field.visible_fields) != "undefined") {
      $(".visibility").hide();
      var $type_visiblity = $("#" + field.type + "-visibility");

      $type_visiblity.find(":checkbox, :radio").uncheck();

      $.each(field.visible_fields, function() {
        $type_visiblity.find(':checkbox[name="' + this + '"]').check();
      });

      $type_visiblity.show();
    } else { $(".visibility").hide(); }
  },

  clearFieldProperties: function() {
    if (this.selectedField)
      this.selectedField.deselect();
    this.selectedField = null;
    $("#field-type").text("");
    $("#field-label").val("");
    $("#field-blurb").text("");
    this.removeOptionProperties();
    this.removeDisplayProperties();
  },

  removeOptionProperties: function() {
    $(".option-prop:not(#default-option)").remove();
  },

  removeLineItemProperties: function() {
    $(".line-item-prop:not(#default-line-item)").remove();
  },

  removeDisplayProperties: function() {
    $("#field-visible-fields-prop").find("p").slice(1).remove();
  },

  removeWelcomePanel: function() { $(".panel").remove(); },

  resetPropertyForm: function() {
    this.clearFieldProperties();
    this.tabs["field-properties"].setInactive();
  },

  hasFieldList: function() { return $("#custom-form-items").length > 0; },

  insertFieldList: function() {
    $("#edit-custom-form-column-two > form #title").after('<ul id="custom-form-items"></ul>');
  },

  newFieldPath: function() {
    return this.basePath + "/fields/new";
  },

  manageOptionsScrollBar: function(field) {
    this.manageScrollBar(field.options, $(".option-prop"));
  },

  manageLineItemsScrollBar: function(field) {
    this.manageScrollBar(field.line_items, $(".line-item-prop"));
  },

  manageScrollBar: function(items, scope) {
    if (items.length >= this.maxOptionsSize)
      scope.find('.label').css("width", this.optionsWidthScroll + "px");
    else
      scope.find('.label').css("width", this.optionsWidthNoScroll + "px");
  },

  addNewField: function(fieldType) {
    var editor = this;

    $.get(this.newFieldPath(), { type: fieldType }, function(data) {
      editor.removeWelcomePanel();
      editor.addFieldByMarkup(data);
      editor.setModified();
    });

    return false;
  },

  deleteField: function(field) {
    var index = this.fields.indexOf(field);
    if (index < 0)
      return;

    this.fields.splice(index, 1);
    delete this.fieldMap[field.id];

    if (!field.isNew())
      this.deletedFields.push(field);

    this.setModified();
  },

  reorderFields: function() {
    var self = this;
    var order = $("#custom-form-items").sortable("toArray");
    var reorderedFields = [];

    $.each(order, function() {
      var id = this.replace(CustomFormEditor.Field.IdPrefix, "");
      reorderedFields.push(self.fieldMap[id]);
    });

    self.fields = reorderedFields;
    this.setModified();
  },

  setModified: function() {
    $(".flash .notice").show("slow");
    // If there are line items
    var li_count = 0;
    $.each(this.fields, function() {
      if (this.type == 'line_item') li_count += 1;
    });

    if (li_count > 0) {
      $("input[name='amount_type']").attr('disabled', 'disabled');
      $('#amount_type_calculated').attr('checked', true);
    } else {
      $("input[name='amount_type']").removeAttr('disabled');
      $('#amount_type_calculated').attr('disabled', 'disabled');
      $('#amount_type_calculated').attr('checked', false);
    }
  },

  showSavedFlash: function() {
    $(".flash .notice, .flash .error").hide();
    $(".flash .success").show();
    setTimeout(function() { $(".flash .success").hide("slow"); }, 3000);
  },

  showErrorFlash: function() { $(".flash .error").show(); },

  updateMenuPadding: function() {
    var titleHeight = $("#title").height();
    var padding = $("#title").height() + 15;
    $("#edit-custom-form-column-one").css("padding-top", padding + "px");
  },

  updateMenuPosition: function() {
    var scrollTop = $(document).scrollTop();
    var titleTopOffset = $("#title").offset().top;
    var offset = scrollTop - titleTopOffset - $("#title").height();

    var c2h = $("#column-two").innerHeight();
    var c1h = $("#edit-custom-form-column-one").innerHeight();

    if ((offset < 0) || (c1h > c2h)){
      offset = 0;
    }else if ((c1h + offset + $("#title").height()) > c2h){
      offset = $("#column-two").innerHeight() - $("#edit-custom-form-column-one").innerHeight() - ($("#title").height() + 10);
    }

    $("#edit-custom-form-column-one").animate({ top: offset }, {
      duration: 250,
      queue: false
    });
  },

  save: function() {
    var self = this;
    var data = self.serializeData();

    var img = $("button.save img");
    $("button.save").attr("disabled", "disabled");
    $("button.save img").replaceWith(loadingImage);

    $.ajax({
      url: self.basePath,
      data: data,
      dataType: "json",
      type: "PUT",
      complete: function() {
        $("button.save").attr("disabled", false);
        $("button.save img").replaceWith(img);
      },
      success: function(data) {
        if (typeof(data["fields"]) != "undefined")
          $.each(data["fields"], function(old_id, new_id) {
            self.fieldMap[old_id].updateId(new_id);
            self.fieldMap[new_id] = self.fieldMap[old_id];
            delete self.fieldMap[old_id];
          });

        if (typeof(data["options"]) != "undefined") {
          $.each(data["options"], function() {
            object = this;

            option_map = self.fieldMap[object.field_id].option_map;
            option_map[object.old_id].id = object.new_id;
            option_map[object.new_id] = option_map[object.old_id];
            delete option_map[object.old_id];
          });
        }

        if (typeof(data["line_items"]) != "undefined") {
          $.each(data["line_items"], function() {
            object = this;

            line_item_map = self.fieldMap[object.field_id].line_item_map;
            line_item_map[object.old_id].id = object.new_id;
            line_item_map[object.new_id] = line_item_map[object.old_id];
            delete line_item_map[object.old_id];
          });
        }

        self.deletedFields = [];
        $.each(self.fields, function() {
          if (typeof(this.deleted_options) != "undefined")
            this.deleted_options = [];
          if (typeof(this.deleted_line_items) != "undefined")
            this.deleted_line_items = [];
        });

        var $input = $("#form-email-list");
        $input.val($.trim($input.val().replace(/;/g, ',')));

        self.showSavedFlash();
      },
      error: function() { self.showErrorFlash(); }
    });
  },

  serializeData: function() {
    var data = {
      "form[label]": this.label,
      "form[email_subs_to]": this.email_subs_to,
      "form[payment_form]": this.payment_form,
      "form[amount]": this.amount == null ? "" : this.amount
    };
    var order = [];
    var deleted_ids = [];

    $.each(this.fields, function() {
      var field = this;
      order.push(field.id);
      var prefix = "";

      if (field.isNew())
        prefix = "new_form_field";
      else
        prefix = "form_field";

      prefix += "[" + field.id + "]";

      if (field.isNew())
        data[prefix + "[type]"] = field.type;

      if (typeof(field.label) != "undefined")
        data[prefix + "[label]"] = field.label;

      if (typeof(field.blurb) != "undefined")
        data[prefix + "[blurb]"] = field.blurb;

      if (typeof(field.field_size) != "undefined")
        data[prefix + "[field_size]"] = field.field_size;

      if (typeof(field.required) != "undefined")
        data[prefix + "[required]"] = field.required;

      if (typeof(field.options) != "undefined") {
        deleted_ids = [];
        $.each(field.options, function(index) {
          var option = this;
          var new_option = typeof(option.id) == "string" && option.id.match("new");

          if (new_option)
            option_prefix = "[new_form_field_option]";
          else
            option_prefix = "[form_field_option]";

          option_prefix += "[" + option.id + "]";

          data[prefix + option_prefix + "[label]"] = option.label;
          data[prefix + option_prefix + "[default]"] = option.selected;
          data[prefix + option_prefix + "[sort_order]"] = index;
        });

        $.each(field.deleted_options, function() {
          deleted_ids.push(this.id);
        });
        data[prefix + "[delete_options]"] = deleted_ids.join(",");
      }

      if (typeof(field.line_items) != "undefined") {
        deleted_ids = [];
        $.each(field.line_items, function(index) {
          var line_item = this;
          var new_line_item = typeof(line_item.id) == "string" && line_item.id.match("new");

          if (new_line_item)
            line_item_prefix = "[new_form_field_line_item]";
          else
            line_item_prefix = "[form_field_line_item]";

          line_item_prefix += "[" + line_item.id + "]";

          data[prefix + line_item_prefix + "[label]"] = line_item.label;
          data[prefix + line_item_prefix + "[value]"] = line_item.value;
          data[prefix + line_item_prefix + "[default]"] = line_item.selected;
          data[prefix + line_item_prefix + "[sort_order]"] = index;
        });

        $.each(field.deleted_line_items, function() {
          deleted_ids.push(this.id);
        });
        data[prefix + "[delete_line_items]"] = deleted_ids.join(",");
      }

      if (typeof(field.visible_fields) != "undefined")
        data[prefix + "[visible_fields]"] = field.visible_fields.join(",");
    });

    deleted_ids = [];

    $.each(this.deletedFields, function() {
      var field = this;
      deleted_ids.push(field.id);
    });

    if (deleted_ids.length > 0)
      data["delete_fields"] = deleted_ids.join(",");

    data["field_order"] = order.join(",");

    return data;
  },

  hasEmailReceiptField: function() {
    var field_count = 0;
    $.each(this.fields, function() {
      if (this.type == 'email_receipt') field_count += 1;
    });

    return !(field_count == 0);
  }
});


CustomFormEditor.Tab = Base.extend({
  // editor: instance of CustomFormEditor
  // elem:   the <li> element for the tab
  constructor: function(editor, elem){
    this.editor = editor;
    this.elem = $(elem);
    this.title = this.elem.children("a").eq(0);
    this.content = this.elem.children("div").eq(0);
    this.id = this.title.attr("id");
  },

  bind: function(){
    var tab = this;
    this.title.click(function() {
      tab.select();
      return false;
    });
  },

  select: function(){
    var tab = this;

    this.editor.closeTabsExcept(this);
    this.title.addClass("selected");
    this.content.slideDown("normal", function() {
      tab.editor.updateMenuPosition();
    });
  },

  close: function(){
    var editor = this.editor;
    var callback = null;

    this.title.removeClass("selected");
    if (this.id == "field-properties") {
      editor.deselectField();
      callback = function() { editor.resetPropertyForm(); };
    }

    this.content.slideUp("normal", callback);
  },

  setInactive: function() {
    if (this.id != "field-properties")
      return;
    this.elem.find("div.properties").hide();
    this.elem.find("#inactive").show();
  }
});


CustomFormEditor.Field = Base.extend({
  // editor: instance of CustomFormEditor
  // elem:   the <li> element for the field
  constructor: function(editor, elem) {
    this.editor = editor;
    this.elem = $(elem);
    this.new_options_count = 0;

    this.$labelNode = this.elem.find(".form-field > label");
    this.$blurbNode = this.elem.find(".form-field > .blurb");
    this.$amountNode = this.elem.find(".form-field .amount");
    this.$textFieldNode = this.elem.find(".form-field > div").find('input[type=text],input[type=email],input[type=number],input[type=url]');

    this.loadProperties();
  },

  loadProperties: function() {
    var $propertiesNode = this.elem.find(".field-properties");
    var properties = {};

    if ($propertiesNode.length > 0) {
      properties = $.parseJSON( $propertiesNode.text() );
    }

    var self = this;

    var options = null;

    var line_items = null;

    if (properties["options"]) {
      options = properties["options"];
      delete properties["options"];
    }

    if (properties["line_items"]) {
      line_items = properties["line_items"];
      delete properties["line_items"];
    }

    if (typeof(properties["visible_fields"]) != "undefined") {
      if (properties["visible_fields"].length)
        self.visible_fields = properties["visible_fields"].split(",");
      else
        self.visible_fields = [];
      delete properties["visible_fields"];
    }

    $.each(properties, function(i, value) { self[i] = value; });

    if (options != null) {
      self.options = [];
      self.deleted_options = [];
      self.option_map = {};

      if (this.type == "drop_down") {
        this.$optionsNode = this.elem.find(".form-field > div select");
      } else
        this.$optionsNode = this.elem.find(".form-field > div");

      $.each(options, function(index) {
        this.type = self.type;
        option = new CustomFormEditor.Option(self, this, self.$optionsNode.children().eq(index));
        self.options.push(option);
        self.option_map[option.id] = option;
      });
    }

    if (line_items != null) {
      self.line_items = [];
      self.deleted_line_items = [];
      self.line_item_map = {};

      if (this.type == "line_item") {
          this.$lineItemsNode = this.elem.find(".form-field > div > div > select");
        } else
          this.$lineItemsNode = this.elem.find(".form-field > div");

        $.each(line_items, function(index) {
          this.type = self.type;
          line_item = new CustomFormEditor.LineItem(self, this, self.$lineItemsNode.children().eq(index));
          self.line_items.push(line_item);
          self.line_item_map[line_item.id] = line_item;
        });
    }
  },

  bind: function() {
    var field = this;
    this.elem.click(function() {
      if (field.editor.selectedField != self)
        field.editProperties();
      return false;
    });

    this.elem.find(".delete a").click(function() {
      if (confirm("Are you sure you want to delete this form field?"))
        field.deleteSelf();
      return false;
    });
  },

  isNew: function() { return !this.id || /new/.test(this.id); },

  editProperties: function() {
    this.editor.editFieldProperties(this);
    this.elem.addClass("edit");
    this.editor.tabs["field-properties"].select();
  },

  deselect: function() { this.elem.removeClass("edit"); },

  updateId: function(newId) {
    this.id = newId;
    this.elem.attr("id", CustomFormEditor.Field.IdPrefix + newId);
  },

  updateLabel: function(newLabel) {
    this.label = newLabel;
    this.$labelNode.text(newLabel);
    this.editor.setModified();
  },

  updateBlurb: function(newBlurb) {
    if (typeof(this.blurb) == "undefined")
      return;
    this.blurb = newBlurb;
    this.$blurbNode.text(newBlurb);
    this.editor.setModified();
  },

  updateSize: function(newSize) {
    if (typeof(this.field_size) == "undefined")
      return;
    this.field_size = newSize;
    this.$textFieldNode.removeClass("small medium large mega").addClass(newSize);
    this.editor.setModified();
  },

  toggleRequired: function() {
    if (this.required) {
      this.required = false;
      this.$labelNode.next("img").remove();
      $("#field-required").attr('checked', false);
    } else {
      if( this.type == "checkbox" && this.options.length == 1 ){
        $("#field-required").attr('checked', false);
        alert("There must be more than one option to make this field required.");
        return false;
      }
      this.required = true;
      this.$labelNode.after($(requiredImage).clone());
    }
  },

  unselectOptionsExcept: function(exception) {
    $.each(this.options, function() {
      var option = this;
      if (option != exception)
        option.unselect();
    });
  },

  deleteSelf: function() {
    var field = this;
    this.elem.slideUp("normal", function() { field.elem.remove(); });
    this.editor.resetPropertyForm();
    this.editor.deleteField(this);
  },

  toggleDisplayField: function(field) {
    if (this.visible_fields.indexOf(field) >= 0)
      this.hideField(field);
    else
      this.displayField(field);
  },

  displayField: function(field) {
    if (this.visible_fields.indexOf(field) < 0)
      this.visible_fields.push(field);
    this.elem.find("." + field).show();
  },

  hideField: function(field) {
    var index = this.visible_fields.indexOf(field);
    if (index >= 0)
      this.visible_fields.splice(index, 1);
    this.elem.find("." + field).hide();
  }
}, { IdPrefix: "custom-form-" });


CustomFormEditor.Option = Base.extend({
  constructor: function(form_field, object, $containerNode) {
    this.form_field = form_field;
    this.type = object.type;
    this.id = object.id;
    this.label = object.label;
    this.selected = object.selected;
    this.$containerNode = $containerNode;

    if (!this.id) {
      this.form_field.new_options_count++;
      this.id = "new-" + this.form_field.new_options_count;
    }

    if (this.type == "drop_down") {
      this.$labelNode = $containerNode;
      this.$inputNode = $containerNode;
    } else {
      this.$labelNode = $containerNode.find("span");
      this.$inputNode = $containerNode.find(":input");
    }

    this.$propNode = null;
    this.$defaultOption = null;
  },

  bind: function($propNode) {
    var self = this;
    self.$propNode = $propNode;
    var $textField = self.$propNode.find(".label");

    var $addOption = self.$propNode.find(".add_option");
    var $deleteOption = self.$propNode.find(".delete_option");
    self.$defaultOption = self.$propNode.find(".default_option");
    if (self.selected)
      self.select();

    // DOMNodeRemoved doesn't work in IE. I guess this isn't critical,
    // but i like cleaning things up
    if (self.$propNode[0].addEventListener)
      self.$propNode[0].addEventListener("DOMNodeRemoved", function() {
        self.unbind();
      }, false);

    $textField.val(self.label);
    $textField.keyup(function() { self.updateLabel($textField.val()); });

    $addOption.click(function() {
      self.addOptionAfter();
      return false;
    });

    $deleteOption.click(function() {
      self.remove();
      return false;
    });

    self.$defaultOption.click(function() {
      self.toggleSelected();
      return false;
    });
  },

  unbind: function() {
    this.$propNode = null;
    this.$defaultOption = null;
  },

  addOptionAfter: function() {
    var self = this;
    var $defaultOptionProp = $("#default-option");
    var value = $defaultOptionProp.find(":input").val();
    var newOptionList = [];

    $.each(self.form_field.options, function() {
      newOptionList.push(this);

      if (this == self) {
        var $orig = self.$containerNode;

        var $clone = $orig.clone();

        if (self.type == "drop_down")
          // just use real DOM instead of jQuery because of a jQuery safari hack;
          $clone[0].removeAttribute("selected");
        else
          $clone.find(":input").removeAttr("checked").removeAttr("id");

        var new_option = new CustomFormEditor.Option(self.form_field, {
          id: null,
          type: self.type,
          label: self.label,
          selected: false
        }, $clone);
        new_option.updateLabel(value);

        $clone.hide();
        $orig.after($clone);
        $clone.slideDown();

        newOptionList.push(new_option);
        self.form_field.option_map[new_option.id] = new_option;

        if (self.$propNode) {
          var $newPropNode = $("#default-option").clone().insertAfter(self.$propNode);
          $newPropNode.attr("id", "new-").slideDown();

          new_option.bind($newPropNode);
          self.form_field.editor.manageOptionsScrollBar(self.form_field);
        }
      }
    });

    delete self.form_field.options;
    self.form_field.options = newOptionList;
    self.form_field.editor.setModified();
  },

  remove: function() {
    if (this.form_field.options.length == 1) {
      alert("You cannot delete the last option. Please add another option before you delete this one.");
      return;
    }

    if(this.type == "checkbox" && this.form_field.options.length == 2 && this.form_field.required)
      this.form_field.toggleRequired();

    this.$propNode.add(this.$containerNode).slideUp("normal", function() {
      $(this).remove();
    });

    this.form_field.options.splice(this.form_field.options.indexOf(this), 1);
    if (!(typeof(this.id) == "string" && this.id.match("new")))
      this.form_field.deleted_options.push(this);

    this.form_field.editor.setModified();
  },

  toggleSelected: function() {
    if (this.selected)
      this.unselect();
    else
      this.select();
    this.form_field.editor.setModified();
  },

  unselect: function() {
    if (!this.selected)
      return;

    this.selected = false;

    if (this.type == "drop_down") {
      this.$containerNode.removeAttr("selected");

      // safari makes the selectedIndex -1 and thus doesn't show anything
      // when all options are unselected, so select the first option if needed
      if (this.$containerNode[0].parentNode.selectedIndex < 0)
        this.$containerNode[0].parentNode.selectedIndex = 0;
    } else
      this.$containerNode.find(":input").removeAttr("checked");

    if (this.$defaultOption) {
      var $star = this.$defaultOption.find("img");
      $star.attr("src", $star.attr("src").replace("hot", "cold"));
      $star.attr("alt", $star.attr("alt").replace("hot", "cold"));
    }
  },

  select: function() {
    this.selected = true;

    if (this.$defaultOption) {
      if (this.type == "drop_down")
        this.$containerNode.attr("selected", "selected");
      else
        this.$containerNode.find(":input").attr("checked", "checked");

      this.$defaultOption.attr("title", this.$defaultOption.attr("title").replace("Make", "Remove"));

      var $star = this.$defaultOption.find("img");
      $star.attr("src", $star.attr("src").replace("cold", "hot"));
      $star.attr("alt", $star.attr("alt").replace("cold", "hot"));
    }

    if (this.type != "checkbox")
      this.form_field.unselectOptionsExcept(this);
  },

  updateLabel: function(newLabel) {
    this.label = newLabel;
    this.$labelNode.text(newLabel);
    this.$inputNode.val(newLabel);
    this.form_field.editor.setModified();
  }
});


CustomFormEditor.LineItem = Base.extend({
  constructor: function(form_field, object, $containerNode) {
    this.form_field = form_field;
    this.type = object.type;
    this.id = object.id;
    this.label = object.label;
    this.value = object.value;
    this.selected = object.selected;
    this.$containerNode = $containerNode;

    if (!this.id) {
      this.form_field.new_options_count++;
      this.id = "new-" + this.form_field.new_options_count;
    }

    this.$labelNode = $containerNode;
    this.$inputNode = $containerNode;

    this.$propNode = null;
  },

  bind: function($propNode) {
    var self = this;
    self.$propNode = $propNode;
    var $textField = self.$propNode.find(".label");
    var $valueField = self.$propNode.find(".value");

    var $addLineItem = self.$propNode.find(".add_line_item");
    var $deleteLineItem = self.$propNode.find(".delete_line_item");

    if (self.selected)
      self.select();

    // DOMNodeRemoved doesn't work in IE. I guess this isn't critical,
    // but i like cleaning things up
    if (self.$propNode[0].addEventListener)
      self.$propNode[0].addEventListener("DOMNodeRemoved", function() {
        self.unbind();
      }, false);

    $textField.val(self.label);
    $textField.keyup(function() { self.updateLabel($textField.val()); });

    $valueField.val(self.value);
    $valueField.keyup(function() { self.updateValue($valueField.val()); });

    $addLineItem.click(function() {
      self.addLineItemAfter();
      return false;
    });

    $deleteLineItem.click(function() {
      self.remove();
      return false;
    });
  },

  unbind: function() {
    this.$propNode = null;
    //this.$defaultOption = null;
  },

  addLineItemAfter: function() {
    var self = this;
    var $defaultLabelProp = $("#default-line-item");
    var li_label = $defaultLabelProp.find(".label:input").val();
    var li_value = $defaultLabelProp.find(".value:input").val();
    var newList = [];

    $.each(self.form_field.line_items, function() {
      newList.push(this);

      if (this == self) {
        var $orig = self.$containerNode;

        var $clone = $orig.clone();

        // just use real DOM instead of jQuery because of a jQuery safari hack;
        $clone[0].removeAttribute("selected");

        var new_line_item = new CustomFormEditor.LineItem(self.form_field, {
          id: null,
          type: self.type,
          label: self.label,
          value: self.value,
          selected: false
        }, $clone);
        new_line_item.updateLabel(li_label);
        new_line_item.updateValue(li_value);

        $clone.hide();
        $orig.after($clone);
        $clone.slideDown();

        newList.push(new_line_item);
        self.form_field.line_item_map[new_line_item.id] = new_line_item;

        if (self.$propNode) {
          var $newPropNode = $("#default-line-item").clone().insertAfter(self.$propNode);
          $newPropNode.attr("id", "new-").slideDown();

          new_line_item.bind($newPropNode);
          self.form_field.editor.manageLineItemsScrollBar(self.form_field);
        }
      }
    });

    delete self.form_field.line_items;
    self.form_field.line_items = newList;
    self.form_field.editor.setModified();
  },

  remove: function() {
    if (this.form_field.line_items.length == 1) {
      alert("You cannot delete the last item. Please add another item before you delete this one.");
      return;
    }

    this.$propNode.add(this.$containerNode).slideUp("normal", function() {
      $(this).remove();
    });

    this.form_field.line_items.splice(this.form_field.line_items.indexOf(this), 1);
    if (!(typeof(this.id) == "string" && this.id.match("new")))
      this.form_field.deleted_line_items.push(this);

    this.form_field.editor.setModified();
  },

  toggleSelected: function() {
    if (this.selected)
      this.unselect();
    else
      this.select();
    this.form_field.editor.setModified();
  },

  unselect: function() {
    if (!this.selected)
      return;

    this.selected = false;

    if (this.type == "drop_down") {
      this.$containerNode.removeAttr("selected");

      // safari makes the selectedIndex -1 and thus doesn't show anything
      // when all options are unselected, so select the first option if needed
      if (this.$containerNode[0].parentNode.selectedIndex < 0)
        this.$containerNode[0].parentNode.selectedIndex = 0;
    } else
      this.$containerNode.find(":input").removeAttr("checked");
  },

  select: function() {
    this.selected = true;

    if (this.type != "checkbox")
      this.form_field.unselectOptionsExcept(this);
  },

  updateLabel: function(newLabel) {
    this.label = newLabel;
    this.$labelNode.text(newLabel);
    //this.$inputNode.val(newLabel);
    this.form_field.editor.setModified();
  },

  updateValue: function(newValue) {
    this.value = newValue;
    //this.$labelNode.text(newLabel);
    this.$inputNode.val(newValue);
    this.form_field.editor.setModified();
  }
});


jQuery.fn.selecteSizer=function(C,E){var D={floatIndex:777,width:"auto",position:{type:"absolute",topOffset:0,leftOffset:0},callback:null};if(C){jQuery.extend(D,C)}var A=this;var B=0;this.each(function(){jQueryChild=jQuery(this);if(jQueryChild.attr("style")){jQueryChild.attr("ostyle",jQueryChild.attr("style"))}else{jQueryChild.attr("ostyle"," ")}jQueryChild.focus(function(){var G=jQuery(this).offset().top;var I=jQuery(this).offset().left;if(F(this)){G=jQuery(this).position().top;I=jQuery(this).position().left}var H=jQuery(this).css("margin");if(H){H="margin:"+H}else{H=""}jQuery(this).after("<select id='selectGhost' class='"+jQuery(this).attr("class")+"' style='"+H+";"+jQuery(this).attr("ostyle")+"; width:"+(jQuery(this).outerWidth())+"px;visibility:hidden'><option>&nbsp;</option></select>");jQuery(this).change();jQuery(this).css({width:D.width,position:D.position.type,top:G+D.position.topOffset,left:I+D.position.leftOffset,zIndex:D.floatIndex});if(jQuery(this).next("#selectGhost:first").width()>jQuery(this).width()){jQuery(this).attr("style",jQuery(this).attr("ostyle"));jQuery(this).next("#selectGhost:first").remove()}jQuery(this).trigger("mousedown")});jQueryChild.blur(function(){jQuery(this).next("#selectGhost:first").remove();jQuery(this).attr("style",jQuery(this).attr("ostyle"));jQuery(this).trigger('change')});function F(G){var H=G;while(H.parentNode){if(jQuery(H).css("position")=="relative"||jQuery(H).css("position")=="absolute"){return true;break}H=H.parentNode}return false}if(D.callback!=null){D.callback(this)}B++});if(E!=null){E(A)}return jQuery(this)};if(console==undefined){var console={log:function(A){alert(A)}}};

var MembershipEditor = Base.extend({
  // Construct with a jQuery object for the div.membership-editor
  constructor: function($container) {
    this.container = $container;
    this.listing = $container.find("table.listing");
  },

  actionMenu: function() { return this.container.find(".actions > select"); },

  resetActionMenu: function() {
    this.actionMenu().selectOptions('', true);
  },

  onAction: function(action, fn) {
    var self = this;

    this.actionMenu().change(function() {
      var menu = $(this);
      var selected = menu.find(":selected");
      var value = selected.val();
      var match = value.match(action);

      if(match){
        fn.apply(self, [selected.text(), match]);
        self.resetActionMenu();
      }
    });
  },

  noticeRow: function() { return this.listing.find("tr.notice-row"); },

  fadeInNotice:  function(msg) {
    var row = this.noticeRow();
    row.find("td").html(msg);
    row.fadeIn("slow");
  },

  fadeOutNotice: function() { this.noticeRow().fadeOut("slow"); },

  activateSelectorMenu: function() {
    var self = this;
    var selectors = this.container.find(".selectors a");

    selectors.click(function() {
      var anchor = $(this);
      self.selectMembers(anchor.attr("class"));
      return false;
    });
  },

  selectedMemberIds: function() {
    return $.map(this.listing.find("input:checked"), function(input) {
      return input.value;
    });
  },

  selectMembers: function(which) {
    var boxes = this.listing.find('input[type="checkbox"]');
    if (which == "all")
      boxes.check();
    else if (which == "none")
      boxes.uncheck();
    else if (which == "invert")
      boxes.toggleCheck();
    else
      return false;
    return true;
  },

  addSelectedMembersTo: function(roleId, remove, callback) {
    var url = this.container.find("input#transfer-members-path").val();

    var ajaxOpts = {
      type: "POST",
      url: url,
      data: { "member_ids[]": this.selectedMemberIds(), to: roleId },
      dataType: "json"
    };

    if (remove)
      ajaxOpts.data.rm = "1";
    if (callback)
      ajaxOpts.success = callback;

    $.ajax(ajaxOpts);
  },

  transferSelectedMembersTo: function(roleId) {
    var self = this;
    this.addSelectedMembersTo(roleId, true, function(data) {
      self.fadeOutAndRemoveMembers(data["removed_members"]);
    });
  },

  removeSelectedMembers: function() {
    var self = this;
    var url = $("input#remove-members-path").length > 0 ? $("input#remove-members-path").val() : $("input#remove-coordinators-path").val();

    $.ajax({
      type: "DELETE",
      url: url,
      data: { "member_ids[]": this.selectedMemberIds() },
      dataType: "json",
      success: function(data) {
        self.fadeOutAndRemoveMembers(data["removed_members"]);
      }
    });
  },

  acceptSelectedMembers: function() {
    var self = this;
    var url = $("input#accept-members-path").val();

    $.ajax({
      url: url,
      data: { "member_ids[]": this.selectedMemberIds() },
      dataType: "json",
      success: function(data) {
        self.fadeOutAndRemoveMembers(data["accepted_members"]);
      }
    });
  },

  rejectSelectedMembers: function() {
    var self = this;
    var url = $("input#reject-members-path").val();

    $.ajax({
      url: url,
      data: { "member_ids[]": this.selectedMemberIds() },
      dataType: "json",
      success: function(data) {
        self.fadeOutAndRemoveMembers(data["rejected_members"]);
      }
    });
  },

  fadeOutAndRemoveMembers: function(ids) {
    var self = this;
    var selector = $.map(ids, function(id) {
      return "#person_" + id;
    }).join(",");

    this.listing.find(selector).fadeOut("slow", function() {
      $(this).remove();
      self.reapplyCycle();
    });
  },

  reapplyCycle: function() {
    this.listing.find("tbody tr:visible").cycle("odd", "even");
  }
});


var EventEditor = Base.extend({
  constructor: function($form) { this.form = $form; },

  init: function() {
    this.decorateRecurrenceSelectors();
    this.decorateRepeatChoosers();
    this.decorateAllDay();
    this.injectByDayOfWeekInMonthSubform();
    this.massageLabels();
    this.toggleEverything();
    this.setupPostToVisibility();
  },

  decorateRecurrenceSelectors: function() {
    var self = this;

    function manhandle($container, callback) {
      $container.find("input").hide().change(function() { callback($(this)); });
    }

    manhandle(this.form.find("#by_day_of_week"), self.toggleLabel);
    manhandle(this.form.find("#by_month"), self.toggleLabel);
    manhandle(this.form.find("#by_day_of_month"), self.toggleLabel);
    manhandle(this.form.find("#by_day_of_week_in_month"), self.toggleLi);
  },

  injectByDayOfWeekInMonthSubform: function() {
    var subform = $('\
      <form> \
        <select name="week"> \
          <option value="-1">last</option> \
          <option value="1" selected="selected">first</option> \
          <option value="2">second</option> \
          <option value="3">third</option> \
          <option value="4">fourth</option> \
        </select> \
        <select name="dow"> \
          <option value="0">Sunday</option> \
          <option value="1">Monday</option> \
          <option value="2">Tuesday</option> \
          <option value="3">Wednesday</option> \
          <option value="4">Thursday</option> \
          <option value="5">Friday</option> \
          <option value="6">Saturday</option> \
        </select> \
        <input type="submit" value="Add"> \
      </form>');

    this.form.find("#by_day_of_week_in_month").append(subform);

    var self = this;

    subform.submit(function() {
      var inputId = [
        "#event_by_day_of_week_in_month",
        subform.children('select[name="week"]').val(),
        subform.children('select[name="dow"]').val()
      ].join("_");

      var $input = $(inputId);
      if (!$input.attr("checked")) {
        $input.click();
        self.toggleLi($input);
      }
      return false;
    });
  },

  decorateRepeatChoosers: function() {
    var form = this.form;

    function toggleRepeatOptions() {
      form.find("#by_day_of_week, #by_month, #by_day_of_month, #by_day_of_week_in_month").hide();
      toggleRepeatUntil();
      if (form.find("#event_repeats_custom:checked").length != 1)
        return;

      var repeat_type = form.find("#event_frequency").val();
      if (repeat_type == "weekly")
        form.find("#by_day_of_week").show();
      else if (repeat_type == "monthly" || repeat_type == "yearly")
        form.find("#by_day_of_month, #by_day_of_week_in_month").show();

      if (repeat_type == "yearly")
        form.find("#by_month").show();
    }

    function toggleCustomRepeat() {
      var repeat_type = form.find("#event_frequency").val();
      if (repeat_type == "once" || repeat_type == "daily")
        form.find("#repeats_custom_chooser").hide();
      else
        form.find("#repeats_custom_chooser").show();
      toggleRepeatOptions();
    }

    function toggleRepeatUntil() {
      var repeat_type = form.find("#event_frequency").val();
      if (repeat_type == "once")
        form.find("#repeats_until").hide();
      else
        form.find("#repeats_until").show();
    }

    form.find("#event_frequency").change(toggleCustomRepeat);
    form.find("#event_repeats_custom").change(toggleRepeatOptions);
    toggleCustomRepeat();
  },

  decorateAllDay: function() {
    var form = this.form;

    function toggleAllDay() {
      display = form.find("#event_all_day").attr("checked") ? "none" : "";
      form.find("#event_start_time, #event_end_time").css("display", display);
    };

    form.find("#event_all_day").click(toggleAllDay);
    toggleAllDay();
  },

  massageLabels: function() {
    var self = this;

    this.form.find("#by_day_of_week label").each(function() {
      var $this = $(this);
      $this.html($this.html().substring(0, 1));
    });

    this.form.find("#by_month label").each(function() {
      var $this = $(this);
      $this.html($this.html().substring(0, 3));
    });

    this.form.find("#by_day_of_week_in_month li").each(function() {
      var $this  = $(this);
      var $label = $this.children("label");
      $this.prepend($label.html());
      $label.html("-");
    });
  },

  toggleEverything: function() {
    var self = this;
    this.form.find("input").each(function() {
      var $this = $(this);
      self.toggleLabel($(this));
      if ($this.parents("li").length == 1)
        self.toggleLi($this);
    });
  },

  toggleLabel: function($inputElem) {
    var $label = $('label[for="' + $inputElem.attr("id") + '"]');
    if ($inputElem.attr("checked"))
      $label.addClass("selected");
    else
      $label.removeClass("selected");
  },

  toggleLi: function($inputElem) {
    var $li = $inputElem.parent("li");
    if ($inputElem.attr("checked"))
      $li.show();
    else
      $li.hide();
  },

  setupPostToVisibility: function() {
    var self = this;
    $('input[id^="event_visibility"]').live("click", function() {
      if ($(this).val() == "public")
        self.form.find("#post_to .networks").show();
      else
        self.form.find("#post_to .networks").hide();
    });
  }
});



var TagCloud = Base.extend({
  constructor: function($cloud, options) {
    this.$cloud = $cloud;
    this.options = options || {};
    this.bind();
  },

  bind: function() {
    var self = this;

    this.tagElements().click(function() {
      var tagName = $(this).text();
      if (self.options.click && !self.options.click(self.selectedTags(), tagName))
        return null;
      self.toggleTag(tagName);
      return false;
    });

    var input = this.options.input;
    if (input)
      input.bind("keyup", function() { self.updateCloudFromInput(); });
  },

  tags: function() { return this.tagNamesFromElements(this.tagElements()); },
  unselectedTags: function() {
    return this.tagNamesFromElements(this.unselectedTagElements());
  },
  selectedTags: function() {
    return this.tagNamesFromElements(this.selectedTagElements());
  },

  selectTag: function(name) { this.changeTagStatus(name, "add"); },
  unselectTag: function(name) { this.changeTagStatus(name, "remove"); },
  toggleTag: function(name) { this.changeTagStatus(name, "toggle"); },

  // @private
  changeTagStatus: function(name, action) {
    var te = this.tagElementByName(name);
    if (te)
      te[action + "Class"]("selected");
    this.updateTagInInput(te);
    if (this.options.change )
      this.options.change(this.selectedTags(), name);
  },

  tagElements: function() { return this.$cloud.find(".tag"); },
  unselectedTagElements: function() {
    return this.$cloud.find(".tag:not(.selected)");
  },
  selectedTagElements: function() { return this.$cloud.find(".tag.selected"); },

  tagNamesFromElements: function(elems) {
    return $.map(elems, function(e) { return $(e).text(); });
  },

  tagElementByName: function(name) {
    var elems = this.tagElements();
    for (var i = 0, len = elems.length; i < len; i++) {
      var e = elems.eq(i);
      if (e.text() == name)
        return e;
    }
    return null;
  },

  tagsInInput: function() {
    var input = this.options.input;
    if (!input)
      return null;
    return input.val().split(/, */).filter(function(t) { return t != ""; });
  },

  updateTagInInput: function(elem) {
    var input = this.options.input;
    if (!input)
      return;

    var tags = this.tagsInInput();
    if (elem.hasClass("selected"))
      tags.push(elem.text());
    else
      tags = tags.filter(function(t) { return t != elem.text(); });

    input.val($.trim(tags.join(", ")));
    input.change();
  },

  updateCloudFromInput: function() {
    var input = this.options.input;
    if (!input)
      return;

    var tagNames = this.tagsInInput();
    var elems = this.tagElements();
    for (var i = 0, len = elems.length; i < len; i++) {
      var e = elems.eq(i);
      if ($.inArray(e.text(), tagNames) != -1)
        e.addClass("selected");
      else
        e.removeClass("selected");
    }
  }
});


var FastFinder = Base.extend({
  constructor: function(textField, options) {
    this.textField = textField;
    this.options = options || {};

    this.form = this.options.form || textField.parents("form").eq(0);
    this.control = this.options.control || textField.parents(".ctrl.ff").eq(0);
    this.valueField = this.options.valueField || textField.siblings("input:hidden:not([name=url])").eq(0);
    this.membershipState = this.options.membershipState;
  },

  bind: function() {
    var self = this;

    if (self.valueField.val())
      self.textField.addClass("chosen");

    this.textField.attr('autocomplete', 'off');

    this.textField.autocomplete({
      source: function(request, response) {
        $.ajax({
          url: self.url(),
          data: { q: request.term },
          dataType: "json",
          success: function(data) {
            response($.map(data.payload, function(item) {
              return {
                label: self.format(item),
                value: self.insertText(item),
                info: item
              }
            }))
          }
        });
      },
      select: function(event, ui) { self.choose(ui.item.info); }
    });

    this.textField.bind("keypress", function(e) {
      if (e.keyCode == 13 || e.keyCode == 9) // enter and tab respectively
        return;

      self.unchoose();
    });

    this.textField.bind("keydown", function(e) {
      if (e.keyCode == 8 || e.keyCode == 46) // backspace and delete respectively
        self.unchoose();
    });
  },

  choose: function(obj) {
    this.valueField.val(obj.id);
    this.textField.addClass("chosen");
    if (obj.membership_state) this.membershipState.val(obj.membership_state);

    if (this.options.disableSubmit)
      this.form.find(":submit").attr("disabled", false);

    if ($.isFunction(this.options.onChoose))
      this.options.onChoose.apply(this, [obj]);
  },

  unchoose: function(){
    this.valueField.val("");
    this.textField.removeClass("chosen");

    if (this.options.disableSubmit)
      this.form.find(":submit").attr("disabled", "disabled");

    if ($.isFunction(this.options.onUnchoose))
      this.options.onUnchoose.apply(this);
  },

  hilite: function(text) {
    if (!text)
      return null;

    var term = this.textField.val();
    if (!term || !term.length)
      return text;
    reg = new RegExp("(" + term.replace(" ", ".*?") + ")", "gi");
    return text.replace(reg, '<span class="hilited">$1</span>');
  },

  url: function() {
    var specifiedUrl = this.control.children('input[type="hidden"][name="url"]').val();
    return specifiedUrl || this.defaultUrl();
  },

  defaultUrl: function() { throw("This must be overridden."); },
  format: function(obj) { return obj.name; },
  insertText: function(obj) { return obj.name; }
});

FastFinder.finderFor = function(textField, options) {
  var map = {
    person: FastFinder.Person,
    organization: FastFinder.Organization,
    network: FastFinder.Network,
    client: FastFinder.Client,
    greek_national: FastFinder.GreekNational,
    national: FastFinder.GreekNational,
    greek_chapter: FastFinder.GreekChapter,
    chapter: FastFinder.GreekChapter,
    greek_council: FastFinder.GreekCouncil,
    council: FastFinder.GreekCouncil,
    university: FastFinder.University,
    school: FastFinder.University,
    founding_school: FastFinder.University,
    first_school: FastFinder.University
  };

  var name = textField.attr("class").split(' ')[0];
  var type = null;

  if (type = map[name])
    return new type(textField, options);

  throw("Can not determine FF type for input: " + name);
};

FastFinder.Person = FastFinder.extend({
  defaultUrl: function() { return "/api/people"; },

  insertText: function(obj) { return obj.first_name + " " + obj.last_name; },
  format: function(obj) {
    var name = this.hilite(this.makeName(obj));
    var email = this.hilite(obj.email_address);

    if (email)
      return name + "<br><span>" + email + "</span>";
    else
      return name;
  },

  makeName: function(person) {
    var tokens = [person.prefix, person.first_name, person.middle_name, person.last_name, person.suffix];
    var name = [];
    $.each(tokens, function() {
      if (this.length)
        name.push(this);
    });
    return name.join(" ");
  }
});

FastFinder.Network = FastFinder.extend({
  defaultUrl: function() { return "/api/networks"; }
});

FastFinder.Organization = FastFinder.extend({
  defaultUrl: function() { return "/api/organizations"; },
  format: function(obj) {
    var name = this.hilite(obj.name);
    var url  = this.hilite(obj.uri);

    if (url)
      return name + "<br><span>" + url + "</span>";
    else
      return name;
  }
});

FastFinder.Client = FastFinder.Organization.extend({
  defaultUrl: function() { return "/api/clients"; },

  format: function(obj) {
    var name = this.hilite(obj.name);
    var domains = [];

    $.each(obj.domains, function(i, val) {
      domains.push(val.name);
    });

    return name + "<br>" + domains.join(", ");
  }
});

FastFinder.University = FastFinder.Organization.extend({
  defaultUrl: function() { return "/api/schools/universities"; }
});

FastFinder.GreekChapter = FastFinder.Organization.extend({
  defaultUrl: function() { return "/api/greek/chapters"; }
});

FastFinder.GreekNational = FastFinder.Organization.extend({
  defaultUrl: function() { return "/api/greek/nationals"; }
});

FastFinder.GreekCouncil = FastFinder.Organization.extend({
  defaultUrl: function() { return "/api/greek/councils"; }
});


var uservoiceOptions = {
  /* required */
  key: 'celect',
  host: 'celect.uservoice.com', 
  showTab: true,  
  /* optional */
  alignment: 'right',
  background_color:'#EE811F', 
  text_color: 'white',
  hover_color: '#E00120',
  lang: 'en'
};

function _loadUserVoice() {
  var s = document.createElement('script');
  s.setAttribute('type', 'text/javascript');
  s.setAttribute('src', ("https:" == document.location.protocol ? "https://" : "http://") + "cdn.uservoice.com/javascripts/widgets/tab.js");
  document.getElementsByTagName('head')[0].appendChild(s);
}

_loadSuper = window.onload;


/**
 * jQuery lightBox plugin
 * This jQuery plugin was inspired and based on Lightbox 2 by Lokesh Dhakar (http://www.huddletogether.com/projects/lightbox2/)
 * and adapted to me for use like a plugin from jQuery.
 * @name jquery-lightbox-0.5.js
 * @author Leandro Vieira Pinho - http://leandrovieira.com
 * @version 0.5
 * @date April 11, 2008
 * @category jQuery plugin
 * @copyright (c) 2008 Leandro Vieira Pinho (leandrovieira.com)
 * @license CC Attribution-No Derivative Works 2.5 Brazil - http://creativecommons.org/licenses/by-nd/2.5/br/deed.en_US
 * @example Visit http://leandrovieira.com/projects/jquery/lightbox/ for more informations about this jQuery plugin
 */

// Offering a Custom Alias suport - More info: http://docs.jquery.com/Plugins/Authoring#Custom_Alias
(function($) {
	/**
	 * $ is an alias to jQuery object
	 *
	 */
	$.fn.lightBox = function(settings) {
		// Settings to configure the jQuery lightBox plugin how you like
		settings = jQuery.extend({
			// Configuration related to overlay
			overlayBgColor: 		'#000',		// (string) Background color to overlay; inform a hexadecimal value like: #RRGGBB. Where RR, GG, and BB are the hexadecimal values for the red, green, and blue values of the color.
			overlayOpacity:			0.8,		// (integer) Opacity value to overlay; inform: 0.X. Where X are number from 0 to 9
			// Configuration related to navigation
			fixedNavigation:		false,		// (boolean) Boolean that informs if the navigation (next and prev button) will be fixed or not in the interface.
			// Configuration related to images
			imageLoading:			'images/lightbox-ico-loading.gif',		// (string) Path and the name of the loading icon
			imageBtnPrev:			'images/lightbox-btn-prev.gif',			// (string) Path and the name of the prev button image
			imageBtnNext:			'images/lightbox-btn-next.gif',			// (string) Path and the name of the next button image
			imageBtnClose:			'images/lightbox-btn-close.gif',		// (string) Path and the name of the close btn
			imageBlank:				'images/lightbox-blank.gif',			// (string) Path and the name of a blank image (one pixel)
			// Configuration related to container image box
			containerBorderSize:	10,			// (integer) If you adjust the padding in the CSS for the container, #lightbox-container-image-box, you will need to update this value
			containerResizeSpeed:	400,		// (integer) Specify the resize duration of container image. These number are miliseconds. 400 is default.
			// Configuration related to texts in caption. For example: Image 2 of 8. You can alter either "Image" and "of" texts.
			txtImage:				'Image',	// (string) Specify text "Image"
			txtOf:					'of',		// (string) Specify text "of"
			// Configuration related to keyboard navigation
			keyToClose:				'c',		// (string) (c = close) Letter to close the jQuery lightBox interface. Beyond this letter, the letter X and the SCAPE key is used to.
			keyToPrev:				'p',		// (string) (p = previous) Letter to show the previous image
			keyToNext:				'n',		// (string) (n = next) Letter to show the next image.
			// Don´t alter these variables in any way
			imageArray:				[],
			activeImage:			0
		},settings);
		// Caching the jQuery object with all elements matched
		var jQueryMatchedObj = this; // This, in this context, refer to jQuery object
		/**
		 * Initializing the plugin calling the start function
		 *
		 * @return boolean false
		 */
		function _initialize() {
			_start(this,jQueryMatchedObj); // This, in this context, refer to object (link) which the user have clicked
			return false; // Avoid the browser following the link
		}
		/**
		 * Start the jQuery lightBox plugin
		 *
		 * @param object objClicked The object (link) whick the user have clicked
		 * @param object jQueryMatchedObj The jQuery object with all elements matched
		 */
		function _start(objClicked,jQueryMatchedObj) {
			// Hime some elements to avoid conflict with overlay in IE. These elements appear above the overlay.
			$('embed, object, select').css({ 'visibility' : 'hidden' });
			// Call the function to create the markup structure; style some elements; assign events in some elements.
			_set_interface();
			// Unset total images in imageArray
			settings.imageArray.length = 0;
			// Unset image active information
			settings.activeImage = 0;
			// We have an image set? Or just an image? Let´s see it.
			if ( jQueryMatchedObj.length == 1 ) {
				settings.imageArray.push(new Array(objClicked.getAttribute('href'),objClicked.getAttribute('title')));
			} else {
				// Add an Array (as many as we have), with href and title atributes, inside the Array that storage the images references		
				for ( var i = 0; i < jQueryMatchedObj.length; i++ ) {
					settings.imageArray.push(new Array(jQueryMatchedObj[i].getAttribute('href'),jQueryMatchedObj[i].getAttribute('title')));
				}
			}
			while ( settings.imageArray[settings.activeImage][0] != objClicked.getAttribute('href') ) {
				settings.activeImage++;
			}
			// Call the function that prepares image exibition
			_set_image_to_view();
		}
		/**
		 * Create the jQuery lightBox plugin interface
		 *
		 * The HTML markup will be like that:
			<div id="jquery-overlay"></div>
			<div id="jquery-lightbox">
				<div id="lightbox-container-image-box">
					<div id="lightbox-container-image">
						<img src="../fotos/XX.jpg" id="lightbox-image">
						<div id="lightbox-nav">
							<a href="#" id="lightbox-nav-btnPrev"></a>
							<a href="#" id="lightbox-nav-btnNext"></a>
						</div>
						<div id="lightbox-loading">
							<a href="#" id="lightbox-loading-link">
								<img src="../images/lightbox-ico-loading.gif">
							</a>
						</div>
					</div>
				</div>
				<div id="lightbox-container-image-data-box">
					<div id="lightbox-container-image-data">
						<div id="lightbox-image-details">
							<span id="lightbox-image-details-caption"></span>
							<span id="lightbox-image-details-currentNumber"></span>
						</div>
						<div id="lightbox-secNav">
							<a href="#" id="lightbox-secNav-btnClose">
								<img src="../images/lightbox-btn-close.gif">
							</a>
						</div>
					</div>
				</div>
			</div>
		 *
		 */
		function _set_interface() {
			// Apply the HTML markup into body tag
			$('body').append('<div id="jquery-overlay"></div><div id="jquery-lightbox"><div id="lightbox-container-image-box"><div id="lightbox-container-image"><img id="lightbox-image"><div style="" id="lightbox-nav"><a href="#" id="lightbox-nav-btnPrev"></a><a href="#" id="lightbox-nav-btnNext"></a></div><div id="lightbox-loading"><a href="#" id="lightbox-loading-link"><img src="' + settings.imageLoading + '"></a></div></div></div><div id="lightbox-container-image-data-box"><div id="lightbox-container-image-data"><div id="lightbox-image-details"><span id="lightbox-image-details-caption"></span><span id="lightbox-image-details-currentNumber"></span></div><div id="lightbox-secNav"><a href="#" id="lightbox-secNav-btnClose"><img src="' + settings.imageBtnClose + '"></a></div></div></div></div>');	
			// Get page sizes
			var arrPageSizes = ___getPageSize();
			// Style overlay and show it
			$('#jquery-overlay').css({
				backgroundColor:	settings.overlayBgColor,
				opacity:			settings.overlayOpacity,
				width:				arrPageSizes[0],
				height:				arrPageSizes[1]
			}).fadeIn();
			// Get page scroll
			var arrPageScroll = ___getPageScroll();
			// Calculate top and left offset for the jquery-lightbox div object and show it
			$('#jquery-lightbox').css({
				top:	arrPageScroll[1] + (arrPageSizes[3] / 10),
				left:	arrPageScroll[0]
			}).show();
			// Assigning click events in elements to close overlay
			$('#jquery-overlay,#jquery-lightbox').click(function() {
				_finish();									
			});
			// Assign the _finish function to lightbox-loading-link and lightbox-secNav-btnClose objects
			$('#lightbox-loading-link,#lightbox-secNav-btnClose').click(function() {
				_finish();
				return false;
			});
			// If window was resized, calculate the new overlay dimensions
			$(window).resize(function() {
				// Get page sizes
				var arrPageSizes = ___getPageSize();
				// Style overlay and show it
				$('#jquery-overlay').css({
					width:		arrPageSizes[0],
					height:		arrPageSizes[1]
				});
				// Get page scroll
				var arrPageScroll = ___getPageScroll();
				// Calculate top and left offset for the jquery-lightbox div object and show it
				$('#jquery-lightbox').css({
					top:	arrPageScroll[1] + (arrPageSizes[3] / 10),
					left:	arrPageScroll[0]
				});
			});
		}
		/**
		 * Prepares image exibition; doing a image´s preloader to calculate it´s size
		 *
		 */
		function _set_image_to_view() { // show the loading
			// Show the loading
			$('#lightbox-loading').show();
			if ( settings.fixedNavigation ) {
				$('#lightbox-image,#lightbox-container-image-data-box,#lightbox-image-details-currentNumber').hide();
			} else {
				// Hide some elements
				$('#lightbox-image,#lightbox-nav,#lightbox-nav-btnPrev,#lightbox-nav-btnNext,#lightbox-container-image-data-box,#lightbox-image-details-currentNumber').hide();
			}
			// Image preload process
			var objImagePreloader = new Image();
			objImagePreloader.onload = function() {
				$('#lightbox-image').attr('src',settings.imageArray[settings.activeImage][0]);
				// Perfomance an effect in the image container resizing it
				_resize_container_image_box(objImagePreloader.width,objImagePreloader.height);
				//	clear onLoad, IE behaves irratically with animated gifs otherwise
				objImagePreloader.onload=function(){};
			};
			objImagePreloader.src = settings.imageArray[settings.activeImage][0];
		};
		/**
		 * Perfomance an effect in the image container resizing it
		 *
		 * @param integer intImageWidth The image´s width that will be showed
		 * @param integer intImageHeight The image´s height that will be showed
		 */
		function _resize_container_image_box(intImageWidth,intImageHeight) {
			// Get current width and height
			var intCurrentWidth = $('#lightbox-container-image-box').width();
			var intCurrentHeight = $('#lightbox-container-image-box').height();
			// Get the width and height of the selected image plus the padding
			var intWidth = (intImageWidth + (settings.containerBorderSize * 2)); // Plus the image´s width and the left and right padding value
			var intHeight = (intImageHeight + (settings.containerBorderSize * 2)); // Plus the image´s height and the left and right padding value
			// Diferences
			var intDiffW = intCurrentWidth - intWidth;
			var intDiffH = intCurrentHeight - intHeight;
			// Perfomance the effect
			$('#lightbox-container-image-box').animate({ width: intWidth, height: intHeight },settings.containerResizeSpeed,function() { _show_image(); });
			if ( ( intDiffW == 0 ) && ( intDiffH == 0 ) ) {
				if ( $.browser.msie ) {
					___pause(250);
				} else {
					___pause(100);	
				}
			} 
			$('#lightbox-container-image-data-box').css({ width: intImageWidth });
			$('#lightbox-nav-btnPrev,#lightbox-nav-btnNext').css({ height: intImageHeight + (settings.containerBorderSize * 2) });
		};
		/**
		 * Show the prepared image
		 *
		 */
		function _show_image() {
			$('#lightbox-loading').hide();
			$('#lightbox-image').fadeIn(function() {
				_show_image_data();
				_set_navigation();
			});
			_preload_neighbor_images();
		};
		/**
		 * Show the image information
		 *
		 */
		function _show_image_data() {
			$('#lightbox-container-image-data-box').slideDown('fast');
			$('#lightbox-image-details-caption').hide();
			if ( settings.imageArray[settings.activeImage][1] ) {
				$('#lightbox-image-details-caption').html(settings.imageArray[settings.activeImage][1]).show();
			}
			// If we have a image set, display 'Image X of X'
			if ( settings.imageArray.length > 1 ) {
				$('#lightbox-image-details-currentNumber').html(settings.txtImage + ' ' + ( settings.activeImage + 1 ) + ' ' + settings.txtOf + ' ' + settings.imageArray.length).show();
			}		
		}
		/**
		 * Display the button navigations
		 *
		 */
		function _set_navigation() {
			$('#lightbox-nav').show();

			// Instead to define this configuration in CSS file, we define here. And it´s need to IE. Just.
			$('#lightbox-nav-btnPrev,#lightbox-nav-btnNext').css({ 'background' : 'transparent url(' + settings.imageBlank + ') no-repeat' });
			
			// Show the prev button, if not the first image in set
			if ( settings.activeImage != 0 ) {
				if ( settings.fixedNavigation ) {
					$('#lightbox-nav-btnPrev').css({ 'background' : 'url(' + settings.imageBtnPrev + ') left 15% no-repeat' })
						.unbind()
						.bind('click',function() {
							settings.activeImage = settings.activeImage - 1;
							_set_image_to_view();
							return false;
						});
				} else {
					// Show the images button for Next buttons
					$('#lightbox-nav-btnPrev').unbind().hover(function() {
						$(this).css({ 'background' : 'url(' + settings.imageBtnPrev + ') left 15% no-repeat' });
					},function() {
						$(this).css({ 'background' : 'transparent url(' + settings.imageBlank + ') no-repeat' });
					}).show().bind('click',function() {
						settings.activeImage = settings.activeImage - 1;
						_set_image_to_view();
						return false;
					});
				}
			}
			
			// Show the next button, if not the last image in set
			if ( settings.activeImage != ( settings.imageArray.length -1 ) ) {
				if ( settings.fixedNavigation ) {
					$('#lightbox-nav-btnNext').css({ 'background' : 'url(' + settings.imageBtnNext + ') right 15% no-repeat' })
						.unbind()
						.bind('click',function() {
							settings.activeImage = settings.activeImage + 1;
							_set_image_to_view();
							return false;
						});
				} else {
					// Show the images button for Next buttons
					$('#lightbox-nav-btnNext').unbind().hover(function() {
						$(this).css({ 'background' : 'url(' + settings.imageBtnNext + ') right 15% no-repeat' });
					},function() {
						$(this).css({ 'background' : 'transparent url(' + settings.imageBlank + ') no-repeat' });
					}).show().bind('click',function() {
						settings.activeImage = settings.activeImage + 1;
						_set_image_to_view();
						return false;
					});
				}
			}
			// Enable keyboard navigation
			_enable_keyboard_navigation();
		}
		/**
		 * Enable a support to keyboard navigation
		 *
		 */
		function _enable_keyboard_navigation() {
			$(document).keydown(function(objEvent) {
				_keyboard_action(objEvent);
			});
		}
		/**
		 * Disable the support to keyboard navigation
		 *
		 */
		function _disable_keyboard_navigation() {
			$(document).unbind();
		}
		/**
		 * Perform the keyboard actions
		 *
		 */
		function _keyboard_action(objEvent) {
			// To ie
			if ( objEvent == null ) {
				keycode = event.keyCode;
				escapeKey = 27;
			// To Mozilla
			} else {
				keycode = objEvent.keyCode;
				escapeKey = objEvent.DOM_VK_ESCAPE;
			}
			// Get the key in lower case form
			key = String.fromCharCode(keycode).toLowerCase();
			// Verify the keys to close the ligthBox
			if ( ( key == settings.keyToClose ) || ( key == 'x' ) || ( keycode == escapeKey ) ) {
				_finish();
			}
			// Verify the key to show the previous image
			if ( ( key == settings.keyToPrev ) || ( keycode == 37 ) ) {
				// If we´re not showing the first image, call the previous
				if ( settings.activeImage != 0 ) {
					settings.activeImage = settings.activeImage - 1;
					_set_image_to_view();
					_disable_keyboard_navigation();
				}
			}
			// Verify the key to show the next image
			if ( ( key == settings.keyToNext ) || ( keycode == 39 ) ) {
				// If we´re not showing the last image, call the next
				if ( settings.activeImage != ( settings.imageArray.length - 1 ) ) {
					settings.activeImage = settings.activeImage + 1;
					_set_image_to_view();
					_disable_keyboard_navigation();
				}
			}
		}
		/**
		 * Preload prev and next images being showed
		 *
		 */
		function _preload_neighbor_images() {
			if ( (settings.imageArray.length -1) > settings.activeImage ) {
				objNext = new Image();
				objNext.src = settings.imageArray[settings.activeImage + 1][0];
			}
			if ( settings.activeImage > 0 ) {
				objPrev = new Image();
				objPrev.src = settings.imageArray[settings.activeImage -1][0];
			}
		}
		/**
		 * Remove jQuery lightBox plugin HTML markup
		 *
		 */
		function _finish() {
			$('#jquery-lightbox').remove();
			$('#jquery-overlay').fadeOut(function() { $('#jquery-overlay').remove(); });
			// Show some elements to avoid conflict with overlay in IE. These elements appear above the overlay.
			$('embed, object, select').css({ 'visibility' : 'visible' });
		}
		/**
		 / THIRD FUNCTION
		 * getPageSize() by quirksmode.com
		 *
		 * @return Array Return an array with page width, height and window width, height
		 */
		function ___getPageSize() {
			var xScroll, yScroll;
			if (window.innerHeight && window.scrollMaxY) {	
				xScroll = window.innerWidth + window.scrollMaxX;
				yScroll = window.innerHeight + window.scrollMaxY;
			} else if (document.body.scrollHeight > document.body.offsetHeight){ // all but Explorer Mac
				xScroll = document.body.scrollWidth;
				yScroll = document.body.scrollHeight;
			} else { // Explorer Mac...would also work in Explorer 6 Strict, Mozilla and Safari
				xScroll = document.body.offsetWidth;
				yScroll = document.body.offsetHeight;
			}
			var windowWidth, windowHeight;
			if (self.innerHeight) {	// all except Explorer
				if(document.documentElement.clientWidth){
					windowWidth = document.documentElement.clientWidth; 
				} else {
					windowWidth = self.innerWidth;
				}
				windowHeight = self.innerHeight;
			} else if (document.documentElement && document.documentElement.clientHeight) { // Explorer 6 Strict Mode
				windowWidth = document.documentElement.clientWidth;
				windowHeight = document.documentElement.clientHeight;
			} else if (document.body) { // other Explorers
				windowWidth = document.body.clientWidth;
				windowHeight = document.body.clientHeight;
			}	
			// for small pages with total height less then height of the viewport
			if(yScroll < windowHeight){
				pageHeight = windowHeight;
			} else { 
				pageHeight = yScroll;
			}
			// for small pages with total width less then width of the viewport
			if(xScroll < windowWidth){	
				pageWidth = xScroll;		
			} else {
				pageWidth = windowWidth;
			}
			arrayPageSize = new Array(pageWidth,pageHeight,windowWidth,windowHeight);
			return arrayPageSize;
		};
		/**
		 / THIRD FUNCTION
		 * getPageScroll() by quirksmode.com
		 *
		 * @return Array Return an array with x,y page scroll values.
		 */
		function ___getPageScroll() {
			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;	
			}
			arrayPageScroll = new Array(xScroll,yScroll);
			return arrayPageScroll;
		};
		 /**
		  * Stop the code execution from a escified time in milisecond
		  *
		  */
		 function ___pause(ms) {
			var date = new Date(); 
			curDate = null;
			do { var curDate = new Date(); }
			while ( curDate - date < ms);
		 };
		// Return the jQuery object for chaining. The unbind method is used to avoid click conflict when the plugin is called more than once
		return this.unbind('click').click(_initialize);
	};
})(jQuery); // Call and execute the function immediately passing the jQuery object

jQuery(function($) {
  var filters_box = $('form .search_filters');
  var new_filter_box = $('.filter:last', filters_box);

  new_filter_box.hide();
  $(':input', new_filter_box).attr('disabled', 'disabled');

  $('a.add', filters_box).click(
    function(){
      var filter_box = new_filter_box.clone(true);
      filter_box.insertBefore(new_filter_box);
      $(':input', filter_box).attr('disabled', '');
      filter_box.show();
      return false;
    }
  );

  $('a.remove', filters_box).click(
    function(){
      $(this).parent().remove();
      return false;
    }
  );

  var onChange = function(){
    var type = $(this).parent().attr('class');
    var filter_box = $(this).parent().parent();
    var attribute = $('.attribute select', filter_box).attr('value');
    var operator = type == 'operator' ? $('.operator select', filter_box).attr('value') : null;
    var value = $('.value :input', filter_box).attr('value');
    var asset_class = $('input#asset_class', filter_box.parent()).attr('value')
    var network_id = $('input#network_id', filter_box.parent()).attr('value')

    $.get(
      'search_filters',
      {operator: operator||'', attribute: attribute||'', value: value||''},
      function(data){
        if(operator){
          $('.value', filter_box).html(data);
        }
        else
        {
          $('.value', filter_box).html(null);
          $('.operator', filter_box).html(data);
          $('.operator select', filter_box).change(onChange);
        }
      }
    );
  }

  $('.attribute select, .operator select', filters_box).change(onChange);
});


(function(b){var e,d,a=[],c=window;b.fn.tinymce=function(j){var p=this,g,k,h,m,i,l="",n="";if(!p.length){return p}if(!j){return tinyMCE.get(p[0].id)}function o(){var r=[],q=0;if(f){f();f=null}p.each(function(t,u){var s,w=u.id,v=j.oninit;if(!w){u.id=w=tinymce.DOM.uniqueId()}s=new tinymce.Editor(w,j);r.push(s);if(v){s.onInit.add(function(){var x,y=v;if(++q==r.length){if(tinymce.is(y,"string")){x=(y.indexOf(".")===-1)?null:tinymce.resolve(y.replace(/\.\w+$/,""));y=tinymce.resolve(y)}y.apply(x||tinymce,r)}})}});b.each(r,function(t,s){s.render()})}if(!c.tinymce&&!d&&(g=j.script_url)){d=1;h=g.substring(0,g.lastIndexOf("/"));if(/_(src|dev)\.js/g.test(g)){n="_src"}m=g.lastIndexOf("?");if(m!=-1){l=g.substring(m+1)}c.tinyMCEPreInit=c.tinyMCEPreInit||{base:h,suffix:n,query:l};if(g.indexOf("gzip")!=-1){i=j.language||"en";g=g+(/\?/.test(g)?"&":"?")+"js=true&core=true&suffix="+escape(n)+"&themes="+escape(j.theme)+"&plugins="+escape(j.plugins)+"&languages="+i;if(!c.tinyMCE_GZ){tinyMCE_GZ={start:function(){tinymce.suffix=n;function q(r){tinymce.ScriptLoader.markDone(tinyMCE.baseURI.toAbsolute(r))}q("langs/"+i+".js");q("themes/"+j.theme+"/editor_template"+n+".js");q("themes/"+j.theme+"/langs/"+i+".js");b.each(j.plugins.split(","),function(s,r){if(r){q("plugins/"+r+"/editor_plugin"+n+".js");q("plugins/"+r+"/langs/"+i+".js")}})},end:function(){}}}}b.ajax({type:"GET",url:g,dataType:"script",cache:true,success:function(){tinymce.dom.Event.domLoaded=1;d=2;if(j.script_loaded){j.script_loaded()}o();b.each(a,function(q,r){r()})}})}else{if(d===1){a.push(o)}else{o()}}return p};b.extend(b.expr[":"],{tinymce:function(g){return g.id&&!!tinyMCE.get(g.id)}});function f(){function i(l){if(l==="remove"){this.each(function(n,o){var m=h(o);if(m){m.remove()}})}this.find("span.mceEditor,div.mceEditor").each(function(n,o){var m=tinyMCE.get(o.id.replace(/_parent$/,""));if(m){m.remove()}})}function k(n){var m=this,l;if(n!==e){i.call(m);m.each(function(p,q){var o;if(o=tinyMCE.get(q.id)){o.setContent(n)}})}else{if(m.length>0){if(l=tinyMCE.get(m[0].id)){return l.getContent()}}}}function h(m){var l=null;(m)&&(m.id)&&(c.tinymce)&&(l=tinyMCE.get(m.id));return l}function g(l){return !!((l)&&(l.length)&&(c.tinymce)&&(l.is(":tinymce")))}var j={};b.each(["text","html","val"],function(n,l){var o=j[l]=b.fn[l],m=(l==="text");b.fn[l]=function(s){var p=this;if(!g(p)){return o.apply(p,arguments)}if(s!==e){k.call(p.filter(":tinymce"),s);o.apply(p.not(":tinymce"),arguments);return p}else{var r="";var q=arguments;(m?p:p.eq(0)).each(function(u,v){var t=h(v);r+=t?(m?t.getContent().replace(/<(?:"[^"]*"|'[^']*'|[^'">])*>/g,""):t.getContent()):o.apply(b(v),q)});return r}}});b.each(["append","prepend"],function(n,m){var o=j[m]=b.fn[m],l=(m==="prepend");b.fn[m]=function(q){var p=this;if(!g(p)){return o.apply(p,arguments)}if(q!==e){p.filter(":tinymce").each(function(s,t){var r=h(t);r&&r.setContent(l?q+r.getContent():r.getContent()+q)});o.apply(p.not(":tinymce"),arguments);return p}}});b.each(["remove","replaceWith","replaceAll","empty"],function(m,l){var n=j[l]=b.fn[l];b.fn[l]=function(){i.call(this,l);return n.apply(this,arguments)}});j.attr=b.fn.attr;b.fn.attr=function(n,q,o){var m=this;if((!n)||(n!=="value")||(!g(m))){return j.attr.call(m,n,q,o)}if(q!==e){k.call(m.filter(":tinymce"),q);j.attr.call(m.not(":tinymce"),n,q,o);return m}else{var p=m[0],l=h(p);return l?l.getContent():j.attr.call(b(p),n,q,o)}}}})(jQuery);

/**
 * Contains all code for handling comments
 */
$(document).ready(function () {
  $('#facebox #new_comment').livequery(function () {
    $(this).ajaxForm({
      success: function (data) {
        var count;

        $(document).trigger('close.facebox');

        if ($('.wall .body').length > 0) {
          count = parseInt($('.wall .heading .count').html(), 10);

          $('.wall .body').prepend(data);
          $('.wall .heading .count').html(++count);

          if (count == 1) {
            $('.wall .panel').hide();
          }
        } else {
          $('.wall').prepend(data);
        }
      }
    });
  });

  $('#facebox #delete_comment').livequery(function () {
    var action = $(this).attr('action').replace('?', '/delete?')
      , frags  = action.split('/')
      , domId;

    domId = ['#', 'comment', '_', frags[frags.length - 1]].join('');

    $(this).ajaxForm({
      success: function (data) {
        var count;

        $(document).trigger('close.facebox');

        if ($('.wall .body').length > 0) {
          count = parseInt($('.wall .heading .count').html(), 10);

          $('.wall .body').html(data);
          $('.wall .heading .count').html(--count);

          if (count == 0) {
            $('.wall .panel').show();
          }
        } else {
          $(domId).remove();
        }
      }
    });
  });

  $('#new_comment')
    .ajaxForm({
         url: this.action
       , type: 'POST'
       , success: function (data) {
                    $('#new_comment')
                        .toggleClass('hidden')
                        .find('#comment_body')
                          .val('');

                    $('#add_comment_trigger')
                        .toggle();

                    $('#comments')
                      .append(data)
                      .fadeIn('fast');
                  }
    });

  $('#add_comment_trigger, #new_comment a.cancel').click(function (event) {
    $('#add_comment_trigger').toggle();
    $('#new_comment').toggleClass('hidden');
    event.preventDefault();
  });
});


/**

  This version of paginateForm works with jQuery Tools instead of with the regular jQueryUI Tabs

  Dependencies
    - jQuery Tools
 
*/

(function($) {

  $.fn.paginateFormUITools = function(settings) {
    var config = {pageSize: 2};
    if (settings) {
      $.extend(config, settings);
    }

    this.each(function() {
      var $this = $(this);

      var $pageBreaks = $this.find('div.page_break');

      if ($pageBreaks.length > 0) {
        // wrap everything in a single div to give tabs something to attach to
        // TODO don't like this - probably needs to create the div, detach and add children and add div back to form
        $this.children().wrapAll('<div id="wizard" />').wrapAll('<div class="panes" />');

        // find the indexes of all the page breaks
        var pageBreakIndices = [];
        var $fields = $('div.panes').children();
        $('div.page_break').each(function(i) {
          var parent = $(this).parent();
          pageBreakIndices[i] = $fields.index(parent);
        });

        var numOfPages = pageBreakIndices.length + 1;

        // split the divs into groups based on the position of the page break divs
        var slice_start = 0
            , slice_end = 0;

        for (var i = 0; i < pageBreakIndices.length; i++) {
          slice_end = pageBreakIndices[i];
          $fields.slice(slice_start, slice_end).wrapAll('<div></div>');
          slice_start = slice_end + 1;
        }
        $fields.slice(slice_end + 1).wrapAll('<div></div>');

        // add tab links
        $this.find('div#wizard').prepend(tabLinks(numOfPages));

        // remove the page break divs from dom so they don't mess things up
        // TODO figure out how to do this so it doesn't use .form-field
        $('div.panes').children('.form-field').remove();

        numOfPages = $('div.panes > div').size();

        addNavButons(numOfPages);

      	$('ul.tabs', '#wizard').tabs('div.panes > div');
        addNavigationClickHandlers($('ul.tabs', '#wizard').data('tabs'));

      }
    });

    return this;
  };


  function tabLinks(numTabs) {
    var links = '<ul class="tabs">';
    for (var i = 1; i < numTabs + 1; i++) {
      links += ('<li><a href="#">' + i + '</a></li>');
    }
    links += '</ul>';

    return links;
  };

  function addNavButons(numOfPages) {
    $('div.panes').children().each(function(index) {
      var $div = $('<div></div>');
      if (index + 1 == 1) {
        $div.append(nextPage());
      }
      else if (index + 1 == numOfPages) {
        $div.append(previousPage());
      }
      else {
        $div.append(previousPage()).append(nextPage());
      }
      $(this).append($div);
    });
  };
  function nextPage() {
    return '<input type="button" class="next positive" value="Next Page &rarr;"/>';
  };
  function previousPage() {
    return '<input type="button" class="previous positive" value="&larr; Previous Page"/>';
  };

  function addNavigationClickHandlers(api) {
    $("input.next", '#wizard').click(function() {
      api.next();
    });

    $("input.previous", '#wizard').click(function() {
      api.prev();
    });
  };

  function debug($obj) {
    if (window.console && window.console.log)
      window.console.log('Panes found: ' + $obj.size());
  };
})(jQuery);


// IE Sucks
// https://developer.mozilla.org/En/Core_JavaScript_1.5_Reference:Objects:Array:indexOf#Compatibility
if (!Array.prototype.indexOf) {
  Array.prototype.indexOf = function(elt /*, from*/) {
    var len = this.length
      , from = Number(arguments[1]) || 0;

    from = (from < 0) ? Math.ceil(from) : Math.floor(from);

    if (from < 0)
      from += len;

    for (; from < len; from++) {
      if (from in this && this[from] === elt)
      return from;
    }
    return -1;
  };
}

jQuery(function ($) {
  $('form.custom_form').paginateFormUITools();
  
  var pageIdentifier = new PageIdentifier();

  $('.ctrl.ff input:text').livequery(function () {
    var input = $(this)
      , ff;

    if (input.data('finder'))
      return;

    ff = FastFinder.finderFor(input, { disableSubmit: true });
    input.data('finder', ff);
    ff.bind();
  });

  // facebox centering fix for after tinymce loads
  var fixFaceboxCentering = function () {
    var e = $('#page-content')
      , newColor = e.css('color')
      , newBgColor = e.css('background-color');

    while (newBgColor == 'transparent' || newBgColor.indexOf('rgba') != -1) {
      if(e.is('html')) // don't go past the root element!
        break;
      e = e.parent();
      newBgColor = e.css('background-color');
    }

    $('iframe')
      .contents()
      .find('body#tinymce')
        .css({ 'background-color': newBgColor, 'color': newColor })
        .parent()
          .css('background-color', newBgColor);

    // ff needs the timeout...
    setTimeout(function () {
      var windowWidth  = $(window).width()
        , $facebox     = $('#facebox')
        , faceboxWidth = $facebox.width();

      $facebox.css('left', windowWidth / 2 - faceboxWidth / 2);
    }, 0);
  };

  /////// Launch TinyMCE on any textareas with class="mce"
  var tinyMCEContentCss = [];

  $('link[type=text/css]').each(function () {
    tinyMCEContentCss.push($(this).attr('href'));
  });

  // We use our own custom file browser. Please see:
  //
  // * http://wiki.moxiecode.com/index.php/TinyMCE:Configuration/file_browser_callback
  // * http://wiki.moxiecode.com/index.php/TinyMCE:Custom_filebrowser
  $('textarea.mce').livequery(function () {
    $(this)
      .tinymce({
        script_url: '/javascripts/liboly/tiny_mce/tiny_mce.js',
        content_css: tinyMCEContentCss.join(','),
        theme: 'advanced',
        file_browser_callback: 'tinyMCEfileBrowserButtonClicked', // insert custom file browser
        plugins: 'table,advimageupload,media,inlinepopups,paste',
        theme_advanced_toolbar_location: 'top',
        theme_advanced_toolbar_align: 'left',
        theme_advanced_buttons1: 'bold, italic, underline, strikethrough, |, \
                                  justifyleft, justifycenter, justifyright, \
                                  justifyfull, |, formatselect, fontselect, \
                                  fontsizeselect, forecolor, |, sub, sup, |, undo, \
                                  redo, |, code',
        theme_advanced_buttons2: 'selectall, |, bullist, numlist, |, charmap, |, \
                                  outdent, indent, |, link, unlink, image, media, \
                                  |, hr, removeformat, |, tablecontrols, |, help.',
        theme_advanced_buttons3: '',
        paste_auto_cleanup_on_paste: true,
        extended_valid_elements: 'style,link[href|type:text/css|rel:stylesheet],button[*],iframe[*]',
        element_format: 'html',
        convert_urls : false,
        init_instance_callback: fixFaceboxCentering
      });
  });

  //////// Lightbox on any page content links with rel="lightbox"
  $('.page_block a[rel="lightbox"]').lightBox({
    imageLoading:  'images/lightbox/loading.gif',
    imageBtnPrev:  'images/lightbox/prev.gif',
    imageBtnNext:  'images/lightbox/next.gif',
    imageBtnClose: 'images/lightbox/close.gif',
    imageBlank:    'images/lightbox/blank.gif'
  });

  //////// Enable multiple file uploads
  if (pageIdentifier.hasFileUpload())
    $("#add_file_selector").click(function() {
      var field = $("#file_selectors").find(":first").html();
      $("#file_selectors").append(field);
      return false;
    });

  //////// Event editing
  if (pageIdentifier.hasEventEditor()) {
    var eventEditor = new EventEditor($("form.event").eq(0));
    eventEditor.init();
    new TagCloud($(".cloud"), { input: $("#event_tag_list") });
  }

  $("#attendance form").submit(function() {
    var $button = $(this).find("button.send-invites");
    $button.attr("disabled", "disabled");
    $button.find("img").replaceWith(loadingImage);
  });

  //////// Page editing
  if (pageIdentifier.hasPageEditor()) {
    var pageEditor = new PageEditor();
    var pageTitle = $("#title h2").text();
    var pageTitleHidden = $("#title").hasClass("unseen");

    // Enabling and disabling page edit mode:
    $("#page-edit a").click(function() {
      pageEditor.enableEditing();
      return false;
    });

    $("#page-edit-quit a").click(function() {
      pageEditor.disableEditing();
      if (pageTitleHidden)
        $("#title").addClass("unseen");
      return false;
    });

    // Adding a new page block:
    var placeAtLocation = null;
    var layoutLetter = null;
    $("#choose_page_block_type").livequery(function() {
      pageEditor.hideActions();

      $(document).bind("close.facebox", function() {
        pageEditor.showActions();
      });

      $(this).ajaxForm({
        beforeSubmit: function(fields, form, opts) {
          for (var k in fields) {
            if (fields[k].name == "place_at")
              placeAtLocation = fields[k].value;
            else if (fields[k].name == "layout_letter")
              layoutLetter = fields[k].value;
          }
        },

        complete: function(xhr, statusText) {
          if (xhr.status == 201) // created
            pageEditor.addPageBlock(xhr.responseText, placeAtLocation, layoutLetter);
          else if (xhr.status == 200) // ok, needs to be configured
            pageEditor.pageBlockList.add(xhr.responseText, placeAtLocation, layoutLetter);
        }
      });
    });

    $("#new_page_block .cancel").live("click", function() {
      pageEditor.cancelPageBlockEdit();
      return false;
    });

    // Editing a page block:
    $(".page_block .actions .edit").live("click", function() {
      pageEditor.editPageBlock(pageEditor.pageBlockList.blockContaining(this));
      return false;
    });

    $("#edit_page_block .cancel").live("click", function() {
      pageEditor.cancelPageBlockEdit(pageEditor.pageBlockList);
      return false;
    });

    $("#new_page_block").livequery(function() {
      $(this).ajaxForm({
        beforeSerialize: function() {
          if (typeof tinyMCE != "undefined")
            tinyMCE.triggerSave();
        },
        success: function(data) {
          pageEditor.addPageBlock(data, placeAtLocation, layoutLetter);
        },
        error: function(xhr, status) {
          $('#facebox .content > ul').html(xhr.responseText);
        }
      });
    });

    $("#edit_page_block").livequery(function() {
      $(this).ajaxForm({
        beforeSerialize: function() {
          if (typeof tinyMCE != "undefined")
            tinyMCE.triggerSave();
        },
        success: function(data, status) {
          pageEditor.savedPageBlock(data, status);
        },
        error: function(xhr, status) {
          $('#facebox .content').html(xhr.responseText);
        }
      });
    });

    // Deleting a page block:
    $(".actions .delete").live("click", function() {
      if (!confirm("Are you sure you want to remove this item? There is no undo!"))
        return false;

      var anchor = this;
      var $pb = pageEditor.pageBlockList.blockContaining(this);
      if ($(anchor).is(".fake"))
        $pb.remove();
      else {
        $.ajax({
          url: anchor.href,
          type: "DELETE",
          success: function() { $pb.remove(); }
        });
      }

      return false;
    });

    // page column defaults
    var twoColumnArr = new Array(2, 3, 4, 12);
    var threeColumnArr = new Array(5, 6, 7, 8, 9, 10, 11, 13, 14, 15);

    var setColumnDefaults = function(left, center, right) {
      $("#page_left_column_width").val(left);
      $("#page_center_column_width").val(center);
      $("#page_right_column_width").val(right);
    };

    $("form[id^=edit_page_]").livequery(function() {
      var layoutId = parseInt($("input[name=page[page_layout_id]]:checked").val());

      if (layoutId == "1") {
        $("#page_right_column_width").parent().hide().prev().hide();
      } else if (jQuery.inArray(layoutId, twoColumnArr) != -1) {
        $("#page_right_column_width").parent().hide().prev().children('label').html('Right');
      }
    });

    $("input[name=page[page_layout_id]]").live("change", function() {
      var layoutId = parseInt($(this).val());

      if (jQuery.inArray(layoutId, threeColumnArr) != -1) {
        $("#page_right_column_width").parent().show().prev().show().children('label').html('Center');
        setColumnDefaults(33, 34, 33);
      } else if (jQuery.inArray(layoutId, twoColumnArr) != -1) {
        $("#page_right_column_width").parent().hide().prev().show().children('label').html('Right');
        setColumnDefaults(50, 50, 0);
      } else {
        $("#page_right_column_width").parent().hide().prev().hide();
        setColumnDefaults(100, 0, 0);
      }
    });

    // Calendar page blocks use a tag cloud
    $("#new_page_block .cloud, #edit_page_block .cloud").livequery(function() {
      new TagCloud($(this), { input: $("#page_block_tag_list") });
    });
  }

  //////// Navigation Menu editors
  if (pageIdentifier.hasNavigationMenuEditor()) {
    var pageList = new NestedList($("#pages"));

    $("li.page .indent").live("click", function() {
      pageList.indent($(this).parents("li.page").eq(0));
    });
    $("li.page .undent").live("click", function() {
      pageList.undent($(this).parents("li.page").eq(0));
    });

    var updateNavigation = function(callback) {
      var pageType = $("body.pages.index").length > 0 ? "nonprivate" : "private";
      $.post($("#submit-order-path").attr("href"), pageList.serialize() + "&page_type=" + pageType, function(data) {
        if (pageType == "private")
          $("#private-page-navigation").replaceWith(data);
        else
          $("#page-navigation").replaceWith(data);
        if (callback != undefined)
          callback();
      });
    };

    var enablePageSorting = function() {
      $("#pages-reorder, #page-add").hide();
      $("#reorder-save, #reorder-cancel").show();
      $(".page .actions .mod").hide();
      $(".page .actions .move").show();
      pageList.enableSorting();
      return false;
    };

    var disablePageSorting = function() {
      $("#reorder-save, #reorder-cancel").hide();
      $("#pages-reorder, #page-add").show();
      $(".page .actions .move").hide();
      $(".page .actions .mod").show();
      pageList.disableSorting();
      pageList.root().removeClass('ui-state-disabled');
    };

    $("#pages-reorder a").click(enablePageSorting);
    $("#reorder-save a").click(function() {
      updateNavigation(disablePageSorting);
      return false;
    });
    $("#reorder-cancel a").click(function() { window.location.reload(); });

    $(".arrow.open").live("click", function() {
      pageList.collapse($(this).parents(".page").eq(0));
    });
    $(".arrow.closed").live("click", function() {
      pageList.expand($(this).parents(".page").eq(0));
    });

    $("#pages .page a.edit").live("click", function() {
      var $li = $(this).parents("li.page").eq(0);
      $.get(this.href, function(data) {
        $li.find(":visible").addClass("bkup").hide();
        $li.append(data);
      });
      return false;
    });

    $("#pages .page a.delete").live("click", function() {
      if (!confirm("Are you sure you want to delete this page? There is no undo!"))
        return false;
      var $page = $(this).parents(".page").eq(0);
      $.ajax({
        type: "DELETE",
        url: this.href,
        success: function(data) { $page.remove(); updateNavigation(); }
      });
      return false;
    });

    $("#new_page").livequery(function() {
      var form = $(this);

      form.ajaxForm({
        success: function(data) {
          updateNavigation(function() {
            $("ul#pages").append(data);
            $(document).trigger("close.facebox");
          });
        },
        error: function(xhr, status) { form.replaceWith(xhr.responseText); }
      });
    });

    $(".page form.edit").livequery(function() {
      var $form = $(this);
      var $li = $form.parents("li.page").eq(0);

      $form.ajaxForm({
        success: function(data) {
          // If we just made this the home page, then be sure to
          // remove the mark from any other pages in the list.
          if ($(data).find("em.hp").length > 0)
            $("em.hp").remove();
          $li.replaceWith(data);
          updateNavigation();
        },
        error: function(xhr, status) { $form.replaceWith(xhr.responseText); }
      });

      $form.find("a.cancel").click(function() {
        $form.remove();
        $li.find(".bkup").removeClass("bkup").show();
        return false;
      });
    });
  }

  //////// Member list page block editor
  if (pageIdentifier.hasPageEditor()) {
    var extractStep = function(elem) {
      return Number($(elem).parents("li.step").attr("id").replace(/step_/, ""));
    };

    var showStep = function(stepNum) {
      $("#steps .step").hide();
      $("#step_" + stepNum).show();
      $("#step_links a").removeClass("current");
      $("#step_link_" + stepNum).addClass("current");
    };

    var updateFieldSelectorVisibility = function() {
      var layout = $('#steps input[name="page_block[layout]"]:checked').val();
      var all_members = ($("#page_block_role_id").val() == "");

      var $position_field = $('#page_block_fields_position').closest('p');

      if (layout == "1" || layout == "4") {
        if (all_members)
          $("#fields").hide();
        else {
          $("#fields").show();
          $("#fields p").hide();
          $position_field.show();
        }
      } else {
        if (all_members) {
          $("#fields").show().children().show();
          $position_field.hide();
        }
        else
          $("#fields").show().children().show();
      }

      $("#layout_preview").attr("src", "/images/member_list_layouts/" + layout + "a.jpg");
    };

    $("#steps").livequery(function() {
      var $this = $(this);
      var $nav = $this.find(".nav");

      $nav.find(".next").click(function() {
        showStep(extractStep(this) + 1);
        return false;
      });
      $nav.find(".back").click(function() {
        showStep(extractStep(this) - 1);
        return false;
      });

      $nav.find(".submit").click(function() {
        $(this).parents("form").submit();
        return false;
      });

      $this.parents("form").find("#step_links a").click(function() {
        var anchor = this;
        showStep(anchor.id.replace(/step_link_/, ""));
        return false;
      });

      $("#page_block_submit").parent().hide();
      $("#layouts input, #page_block_role_id").change(updateFieldSelectorVisibility);

      showStep(1);
      updateFieldSelectorVisibility();
      $("#steps").show();
    });
  }

  //////// Registration
  $(".register .filtered_selector").filteredSelector();
  $(".register .organization .all").click(function() {
    $(this).parents(".organization").find("li :checkbox").attr("checked", "checked");
    return false;
  });
  $(".register .organization .none").click(function() {
    $(this).parents(".organization").find("li :checkbox").attr("checked", false);
    return false;
  });

  ///////// Membership Editor
  if (pageIdentifier.hasMembershipEditor()) {
    var membershipEditor = new MembershipEditor($(".membership-editor"));

    membershipEditor.resetActionMenu();
    membershipEditor.activateSelectorMenu();

    membershipEditor.onAction("remove", function() {
      if (confirm("Are you sure you want to remove these people? There is no undo!"))
        membershipEditor.removeSelectedMembers();
    });

    membershipEditor.onAction("accept", function() {
      if (confirm("Are you sure you want to accept these people?"))
        membershipEditor.acceptSelectedMembers();
    });

    membershipEditor.onAction("reject", function() {
      if (confirm("Are you sure you want to reject these people?"))
        membershipEditor.rejectSelectedMembers();
    });

    membershipEditor.onAction(/^move-(\d+)$/, function(text, match) {
      if (confirm("Are you sure you want to transfer these members to the " + text + " role?"))
        membershipEditor.transferSelectedMembersTo(match[1]);
    });

    membershipEditor.onAction(/^add-(\d+)$/, function(text, match) {
      if (confirm("Are you sure you want to add these members to the " + text + " role?"))
        membershipEditor.addSelectedMembersTo(match[1], false, function() {
          membershipEditor.fadeInNotice("The members have been added to the " + text + " role.");
        });
    });

    // reordering role members
    var tableWidthFix = function(e, ui) {
      ui.children().each(function() { $(this).width($(this).width()); });
      return ui;
    };

    $("#reorder-members").live("click", function() {
      if ($(this).hasClass("disabled")) {
        alert("You may only reorder members in a role with fewer than 50 members.");
        return false;
      }

      $(this).parent("#title").children(".action").toggleClass("hidden");
      $("#members tbody").sortable({
        axis: "y",
        helper: tableWidthFix,
        placeholder: "ui-sortable-placeholder",
        opacity: .75,
        start: function() {
          $(".ui-sortable-placeholder").html('<td colspan="6">&nbsp;</td>');
        },
        update: function() { $(this).children(":not(:first)").cycle(); }
      });
    });

    $("#reorder-save").live("click", function() {
      var self = this;
      $.ajax({
        url: window.location.pathname.concat("/order"),
        data: $("#members tbody").sortable("serialize", {
                key: "member_ids[]",
                expression: "person_(.+)"
              }),
        type: "PUT",
        success: function() {
          $("#members tbody").sortable("destroy");
          $(self).parent("#title").children(".action").toggleClass("hidden");
        },
        error: function() {
          alert("There was an error saving your changes.");
        }
      });
    });
  }

  ///////// Roles and Groups:
  if (pageIdentifier.hasPermissionEditor()) {
    var permissionEditor = new PermissionEditor($("#role-editor.permissions"));

    permissionEditor.find(".noun h3").click(function() {
      $(this).parent().toggleClass("selected");
      $(this).nextAll("form").slideToggle();
    });

    permissionEditor.find("button.negative").click(function() {
      $(this).parents("form").slideToggle();
    });

    permissionEditor.find(".noun form").ajaxForm({
      beforeSubmit: function(data, form) {
        var img = form.find("button.positive img");
        this.permissionForm = form;
        form.find("button").attr("disabled", "disabled");
        this.originalImage = img.clone();
        img.replaceWith($(loadingImage).clone());
      },

      success: function() {
        var success = $('<div class="flash"><div class="success">Saved!</div></div>');

        this.permissionForm.find("button.positive img").replaceWith(this.originalImage);
        this.permissionForm.find("button").removeAttr("disabled");

        this.permissionForm.prepend(success);
        setTimeout(function() {
          success.hide("slow", function() { $(this).remove(); });
        }, 2000);
      }
    });

    permissionEditor.find("#verbs_private_page_view_none").live("click", function() {
      $(this).parent().prev().find(":checked").removeAttr("checked");
    });
  }

  // Role positions
  $("#members.listing .position div").live("click", function() {
    if ($(this).find("#new_position").length == 0) {
      var placeholder = "<span>add position</span>";
      var originalPosition = $.trim($(this).html());
      if (originalPosition.match(/<span>add position<\/span>/i))
        originalPosition = "";
      $(this)
        .html('<input id="new_position" type="text" value="' + originalPosition + '">')
        .find("#new_position").focus().blur(function() {
          var parent = $(this).parent();
          var newPosition = $.trim($(this).val());
          var person_id = parent.parents("tr").attr("id").split("_")[1];
          if (originalPosition == newPosition)
            parent.html(originalPosition || placeholder)
          else
            $.ajax({
              url: window.location.pathname.concat("/" + person_id),
              data: { "position" : newPosition },
              type: "PUT",
              beforeSend: function() { parent.hide(); },
              success: function() {
                parent.html(newPosition || placeholder)
                parent.addClass("success");
              },
              error: function() {
                parent.html(originalPosition || placeholder)
                parent.addClass("error");
              },
              complete: function() {
                parent.fadeIn(1200, function() {
                  setTimeout(function() {
                    parent.removeClass("success error"); }, 750);
                });
              }
            });
        });
    }
  });

  // group forums
  // delete
  $("#delete_topic").livequery(function() {
    var uri = $(this).attr("action");
    $(this).ajaxForm({
      success: function(data) {
        $(document).trigger("close.facebox");
        if ($(".topics .body").length > 0) {
          var count = parseInt($(".topics .heading .count").html());

          $(".topics .heading .count").html(--count);
          if (count == 0)
            $(".topics .panel").show();
        }
        $('a[href="' + uri + '"]').parents(".topic").remove();
      }
    });
  });

  //////// Custom Forms
  // form listing

  // See Ticket #634
  $.tablesorter.addParser({
    id: "betterInteger",
    is: function (s) { return false; },
    type: "numeric",
    format: function (s) {
      var r = /\d+/;
      return r.test(s) ? s.match(r)[0] : "0";
    }
  });

  $(".tablesorter#forms").tablesorter({
    headers: {
      1: { sorter: "betterInteger" },
      3: { sorter: false }
    },
    textExtraction: function (n) { return $(n).text(); }
  });

  $(".tablesorter#forms").bind("sortEnd", function() {
    $(this).find("tbody tr:visible").cycle();
  });

  // custom form submission
  $("form.custom_form").livequery(function() {
    var form;
    if ($(this).hasClass("payment"))
      form = new PaymentForm(this);
    else
      form = new CustomForm(this);
    form.bind();
  });

  // hackery to decorate gateway forms without payment forms
  // (which show up after authorization errors)
  // TODO refactor, prolly to a GatewayForm object that simplifies this
  if ($("body.pay").length > 0 && $("form.gateway").length > 0) {
    var paymentForm = new PaymentForm(document);
    paymentForm.gatewayForm = $("form.gateway");
    var submissionId = paymentForm.gatewayForm.find('input[name="orderid"]').get(0).value.split("-")[1];
    paymentForm.gatewayHashPath = paymentForm.gatewayForm.find('input[name="redirect"]').get(0).value.replace("/pay", "/" + submissionId + "/hash");
    paymentForm.bindGatewayForm();
  }

  //albums organizer
  if (pageIdentifier.hasPhotoAlbumsOrganizer()) {
    var PhotoAlbumsOrganizer = Base.extend({
      constructor: function() {
        this.ordering = [];
        this.organize_url = window.location.pathname;
        this.order_url = this.organize_url.replace(/organize$/, "order");
        this.back_url = this.organize_url.replace(/\/organize$/, "");
      },
      order: function() {
        album = this;
        $("ul.photo_block_gallery.organize_albums li").each(function() {
          album.ordering.push(this.id);
        });
      },
      reOrder: function() {
        var ordered = $("ul.photo_block_gallery.organize_albums").sortable("toArray");
        var reordered = [];

        $.each(ordered, function() {
          var id = this;
          reordered.push(this);
        });

        this.ordering = reordered;
      },
      bind: function() {
        var album = this;
        $("ul.photo_block_gallery.organize_albums").sortable({
          cursor: "move",
          opacity: .75,
          stop: function() { album.reOrder(); }
        });

        // Hijack the save button
        $(".photo_block_gallery_organizer button.save").click(function() {
          album.save();
          return false;
        });
        // Hijack the back button
        $(".photo_block_gallery_organizer button.back").click(function() {
          window.location = album.back_url;
          return false;
        });
      },
      save: function() {
        var data = {};
        data["order"] = this.ordering.join(",");
        var orig_img = $("button.save img").clone();
        $("button.save").attr("disabled", "disabled");
        $("button.save img").replaceWith($(loadingImage).clone());
        $.ajax({
          url: album.order_url,
          data: data,
          dataType: "json",
          type: "PUT",
          success: function(data) { window.location=data["url"]; },
          error: function() {
            $("button.save").attr("disabled", false);
            $("button.save img").replaceWith($(orig_img).clone());
            $(".flash .error").show();
          }
        });
      }
    });

    var albumOrganizer = new PhotoAlbumsOrganizer();
    albumOrganizer.order();
    albumOrganizer.bind();
  }

  //photo album organizing
  if (pageIdentifier.hasPhotoAlbumOrganizer()) {
    var PhotoAlbumOrganizer = Base.extend({
      constructor: function() {
        this.ordering = [];
        this.organize_url = window.location.pathname;
        this.order_url = this.organize_url.replace(/organize$/, "order");
        this.back_url = this.organize_url.replace(/\/organize$/, "");
      },
      order: function() {
        album = this;
        $("ul.photo_block_gallery.organize li").each(function() {
          album.ordering.push(this.id);
        });
      },
      reOrder: function() {
        var ordered = $("ul.photo_block_gallery.organize").sortable("toArray");
        var reordered = [];

        $.each(ordered, function() {
          var id = this;
          reordered.push(this);
        });

        this.ordering = reordered;
      },
      bind: function() {
        var album = this;
        $("ul.photo_block_gallery.organize").sortable({
          cursor: "move",
          opacity: .75,
          stop: function() { album.reOrder(); }
        });

        // Hijack the save button
        $(".photo_block_gallery_organizer button.save").click(function() {
          album.save();
          return false;
        });
        // Hijack the back button
        $(".photo_block_gallery_organizer button.back").click(function() {
          window.location = album.back_url;
          return false;
        });
      },
      save: function() {
        var data = {};
        data["order"] = this.ordering.join(",");
        var orig_img = $("button.save img").clone();
        $("button.save").attr("disabled", "disabled");
        $("button.save img").replaceWith($(loadingImage).clone());
        $.ajax({
          url: album.order_url,
          data: data,
          dataType: "json",
          type: "PUT",
          success: function(data) { window.location=data["url"]; },
          error: function() {
            $("button.save").attr("disabled", false);
            $("button.save img").replaceWith($(orig_img).clone());
            $(".flash .error").show();
          }
        });
      }
    });

    var organizer = new PhotoAlbumOrganizer();
    organizer.order();
    organizer.bind();
  }

  //photo album cover selection
  if (pageIdentifier.hasCoverPhotoSelector()) {
    var CoverPhotoSelector = Base.extend({
      constructor: function() {},
      bind: function() {
        page = this;
        $("ul.cover_photo_block_gallery .cancel").live("click", function() {
          $(document).trigger("close.facebox");
          return false;
        });

        $("ul.cover_photo_block_gallery img.cover_photo").live("click", function() {
          page.setCover(this.id);
          return false;
        });
      },
      setCover: function(photo_id) {
        $.ajax({
          url: window.location.pathname.replace(/edit$/,"photos/").concat(photo_id).concat("/set_cover"),
          type: "PUT",
          success: function(data) {
            $(document).trigger("close.facebox");
            $("#make_cover img").replaceWith(data);
          },
          error: function() { $(document).trigger("close.facebox"); }
        });
      }
    });

    coverPhotoSelector = new CoverPhotoSelector();
    coverPhotoSelector.bind();
  };

  //file/folder organizing
  if (pageIdentifier.hasFileOrganizer()) {
    var FileOrganizer = Base.extend({
      constructor: function() {
        this.ordering = [];
        this.organize_url = window.location.pathname;
        this.order_url = this.organize_url.replace(/organize$/, "order");
        this.back_url = this.organize_url.replace(/\/organize$/, "");
      },
      bind: function() {
        listing = this;
         $("ul.file_uploads.organize").sortable({
            cursor: "move",
            axis: "y",
            opacity: .75,
            stop: function() { listing.reOrder(); }
          });
          // Hijack the save button
          $(".listing_organizer button.save").click(function() {
            listing.save();
            return false;
          });
          // Hijack the back button
          $(".listing_organizer button.back").click(function() {
            window.location = listing.back_url;
            return false;
          });
      },
      order: function() {
        listing = this;
        $("ul.file_uploads.organize li").each(function() {
          listing.ordering.push(this.id);
        });
      },
      reOrder: function() {
        var ordered = $("ul.file_uploads.organize").sortable("toArray");
        var reordered = [];

        $.each(ordered, function() {
          reordered.push(this);
        });

        this.ordering = reordered;
      },
      save: function() {
        var data = {};
        data["order"] = this.ordering.join(",");
        var orig_img = $("button.save img").clone();
        $("button.save").attr("disabled", "disabled");
        $("button.save img").replaceWith($(loadingImage).clone());
        $.ajax({
          url: listing.order_url,
          data: data,
          dataType: "json",
          type: "PUT",
          success: function(data) { window.location=data["url"]; },
          error: function() {
            $("button.save").attr("disabled", false);
            $("button.save img").replaceWith($(orig_img).clone());
            $(".flash .error").show();
          }
        });
      }
    });

    var f_organizer = new FileOrganizer();
    f_organizer.order();
    f_organizer.bind();

  }

  // form editing
  if (pageIdentifier.hasCustomFormEditor()) {
    var editor = new CustomFormEditor();

    editor.updateMenuPadding();
    editor.addTabs();
    editor.addFields();
    editor.bind();
  }

  //////// Event / group sectioned member list view
  if (pageIdentifier.hasSectionedMemberList()) {
    $(".pagination a").live("click", function() {
      var anchor = $(this);
      $.get(anchor.attr("href"), function(data) {
        anchor.parents(".section").replaceWith(data);
      });
      return false;
    });
  }

  // Event attendance declaration form
  $("p.attending a.change").click(function() {
    var link = $(this);
    link.parents("p.attending").next("form.attendance").slideDown();
  });

  // Event / group invitation sender
  $("form.invite").livequery(function() {
    var inviter = new Inviter($(this));
    inviter.bind();
  });

  // Site settings panel
  $("input#site_flash_header, input#site_flash_splash").change(function() {
    var checkbox = this;
    var subopts  = $(checkbox).parent(".ctrl").next(".sub-options");
    if (checkbox.checked)
      subopts.slideDown();
    else
      subopts.slideUp();
  });

  $(".site_settings #quicknav_items").sortable({
    axis: "y",
    items: '.datum',
    opacity: .75,
    cursor: "move",
    update: function() {
      var $nav_items = $(this);
      var order_url = $nav_items.find('#new_quicknav_item').attr('action') + '/order'

      $.ajax({
        url: order_url,
        data: $nav_items.sortable("serialize"),
        type: "PUT",
        error: function() {
          alert("There was an error saving the order.");
        }
      });

    }
  });

  //// member profile pictures
  if ($('#profile.pictures').length > 0) {
    $('#profile.pictures .data-title a.new').facebox();
    $(document).bind('close.facebox', function() {
      $('#profile.pictures .data-title a.new').show();
    });
  }

  //// org edit profile pictures
  if ($('#photo_bar').length > 0) {
    $('.organizations.edit #photo_bar a.box-button').facebox();
    $(document).bind('close.facebox', function() {
      $('.organizations.edit #photo_bar a.box-button').show();
    });
  }

  //// messaging members
  if ($("#member_message").length > 0) {
    // Hooking up MemberFasFinder with the Message Members form
    var memberFastFinder = new MemberFastFinder($("form#member_message"));
    memberFastFinder.bind();

    $("#group_id").change(function() {
      memberFastFinder.addSelectOption('group', 'group_id', 'Group:');
    });

    $("#role_id").change(function() {
      memberFastFinder.addSelectOption('role', 'role_id', 'Role:');
    });

    // setup ul.subnav to work as tabs for each div directly under div.panes
    $(".message-tabs").tabs(".tabdata", {
      onClick: function(tabIndex) {
        $("#message_message_type").val(tabIndex);
        $("ul.subnav").children().toggleClass('current');
      }
    });
  }

  //for pending members i.e. system/members/pending/_duplicate facebox
  $("form.pending_member_role").submit(function() {
    form = $(this);
    role_id = form.selectedValues()[0];
    path = form.attr('action') + "?pending_member_role_id=" + role_id;
    jQuery.facebox(function() {
      jQuery.get(path, function(data) { $.facebox(data); });
      $('#facebox .bt .title').text(form.attr('title'));
    });
    return false;
  });

  //// Under Construction popup
  if ($('body.under_construction').length > 0) {
    $(document).ready(function() {
      jQuery.facebox(function() {
        path = '/under_construction_popup.html';
        jQuery.get(path, function(data) { $.facebox(data); });
        $('#facebox .bt .title').text('Site Under Construction');
      });
      $(document).bind("close.facebox", function() {
        jQuery.post('/site/session/hide_under_construction_popup');
       });
      $('#u_c_dialog a#close_facebox').live("click", function() {
        $(document).trigger('close.facebox');
      });
    });
  };

  //// org profiles
  $("select[id^='page_block_organization_profile_id_']").livequery("change", function() {
    var network_id = $(this).val();
    if (network_id != "") {
      $.ajax({
        url: "/api/networks/" + network_id + "/organizations?per_page=-1",
        dataType: "json",
        beforeSend: function() {
          $("#page_block_submit").attr("disabled", "disabled");
          $("#network_organizations").html(loadingImage)
            .parent().show().next().show();
        },
        success: function(data) {
          var list_items = "";
          $.each(data.payload, function() {
            list_items += '<li><span class="handle"><img src="/images/liboly/scope/move_16.png" alt="Move_16"></span><input id="organization_id_' + this.id + '" type="checkbox" name="page_block[organization_ids][]" value="' + this.id + '" checked><label for="organization_id_' + this.id + '">' + this.name + '</label></li>';
          });
          $("#network_organizations").html('<ul id="organization_list">' + list_items + '</ul>');
        },
        error: function() {
          $("#network_organizations").html("<p>There was an error retrieving this network's organizations. Please try again later.</p>");
        },
        complete: function () {
          $("#page_block_submit").removeAttr("disabled");
        }
      });
    } else { $("#network_organizations_container").hide().next().hide(); }
  });

  $("#network_organizations_container .selectors a, #export_fields_container .selectors a").live("click", function() {
    var which = $(this).text();
    var boxes = $(this).parent().next().find('input[type="checkbox"]');
    if (which == "all")
      boxes.check();
    else if (which == "none")
      boxes.uncheck();
    else if (which == "invert")
      boxes.toggleCheck();
    else if (which == "A-Z")
      $("#organization_list>li").tsort("label");
    else if (which == "Z-A")
      $("#organization_list>li").tsort("label", { order : "desc" });
    return false;
  });

  $("#export_data").live("click", function() {
    if (typeof($('#export_fields input:checkbox:checked').val()) == 'undefined') {
      alert('Please select at least one data field to export.');
      return false;
    } else $(document).trigger('close.facebox');
  });

  $(".article .brief a.read-more, .bio .brief a.read-more").live("click", function(e) {
      e.preventDefault();
      var section = $(this).closest('.brief');
      section.hide();
      section.next().show();
  });

  $(".article .full a.read-less, .bio .full a.read-less").live("click", function(e) {
      e.preventDefault();
      var section = $(this).closest('.full');
      section.hide();
      section.prev().show();
  });

  $("#organization_list").livequery(function(){
    $(this).sortable({
        cursor: 'move'
      , axis: 'y'
      , containment: "#organization_list"
      , handle: '.handle'
    });
  });
});

// @see http://wiki.moxiecode.com/index.php/TinyMCE:Custom_filebrowser
function tinyMCEfileBrowserButtonClicked (field_name, url, type, win, editor_id) {
  var cmsURL = '/site/tiny_mce_file_browser';
  cmsURL    += (cmsURL.indexOf('?') < 0 ? '?' : '&') + 'type=' + type;

  tinyMCE.activeEditor.windowManager.open({
    file: cmsURL,
    title: type + ' browser',
    height: 200,
    resizable: 'yes',
    inline: 'yes',  // This parameter only has an effect if you use the inlinepopups plugin!
    close_previous: 'no'
  }, {
    window: win,
    input: field_name
  });

  return false;
}
