new function() {
// compiled on Sat 07/26/2008 21:53:50.29

// ***
// jquery-push-button.js

var global_push_button = {
	SEARCH_KEY: 'push_button',
	selected: {},
	defaults: {
		container: "div",  // 'none' | 'div' | 'table'
		buttons: null,
		mode: "single", // single | multiple | unconnected
		canUnselect: false,  // only for single mode
		beforeText: "",
		afterText: "",
		className: "pushbutton"
	},
	button_defaults: {
		on: false,
		title: "",
		beforeText: "",
		afterText: "",
		selected: null,
		className: null,
		id: null,
		name: "button"
	}
};

plugin('pushButton', function(id, settings) {
	var g = global_push_button;
	settings = $.extend({}, g.defaults, settings);
	var holder;
	var isTable = false;
	var append = true;
	switch (settings.container) {
	case "table":
		holder = $(document.createElement("table"));
		isTable = true;
		break;
	case "div":
		holder = $(document.createElement("div"));
		break;
	default:
		append = false;
		if (!this[0]) {
			holder = $(document.createElement("div"));
		}
		else {
			holder = this;
		}
	}
	if (append) {
		this.append(holder);
	}
	holder.addClass(settings.className);
	return new pushButton(holder, settings, isTable);
	
	function pushButton(srcNode, settings, isTable) {
		var me = this;
		this.container = srcNode;
		
		var idMap = {};
		var buttons = {};  //node: , selected:
		
		if (isTable) {
			var tbody = srcNode.dom("tbody");
			srcNode = tbody.dom("tr");
		}
		
		this.selected = function() {
			return manager.current;
		}
		
		this.button = function(id) {
			return idMap[id].node
		}
		
		this.updateButtonText = function(id, text) {
			$('span:first', idMap[id].node).html(text);
		}
		
		var manager = {
			mode: "single",
			canUnselect: false,
			current: null,
			select: function(node) {
				var buttonObj = buttons[$(node).attr("id")];
				if (buttonObj) {
					switch(this.mode) {
						case "single":
							if (this.current) {
								$(this.current.node).attr("disabled", false);
								$(this.current.node).removeClass("e-on");
							}
							if (this.current && (node == this.current.node)) {
								this.current = null;
							}
							else {
								this.current = buttonObj;
								if (!this.canUnselect) {
									$(buttonObj.node).attr("disabled", true);
								}
								$(buttonObj.node).addClass("e-on");
								buttonObj.selected = true;
							}
							break;
						case "multiple":
							if (!this.current) {
								this.current = [];
							}
							var found = false;
							var me = this;
							$(this.current).each(function(idx) {
								if (this.node == node) {
									$(this.node).attr("disabled", false);
									$(this.node).removeClass("e-on");
									this.selected = false;
									me.current.splice(idx, 1);
									found = true;
									return false;
								}
							});
							if (!found) {
								this.current.push(buttonObj);
								$(buttonObj.node).addClass("e-on");
								buttonObj.selected = true;
							}
							break;
					}
				}
			}
		};
		
		manager.mode = settings.mode;
		manager.canUnselect = settings.canUnselect;

		if (settings.buttons) {
			var pNode;
			//$(settings.buttons).each(function() {
			for (var key in settings.buttons) {
				var buttonItem = settings.buttons[key];
				var buttonSettings = $.extend({}, g.button_defaults, buttonItem);
				pNode = isTable ? srcNode.dom("td").addClass("cell-"+buttonSettings.className) : srcNode;
				var id = guid();
				if (!buttonSettings.id || buttonSettings.id.length == 0) {
					buttonSettings.id = id;
				}
				if (buttonSettings.beforeText) {
					if (buttonSettings.beforeText.data || buttonSettings.beforeText.nodeName) {
						pNode.append(buttonSettings.beforeText);
					}
					else {
						pNode.span().addClass("text").html(buttonSettings.beforeText);
					}
				}
				var button = pNode.dom(buttonSettings.name).attr("id", id);
        // if "button" element is created, it needs a type="button",
        // because according to HTML4 standard, the default is type="submit",
        // which is going to cause a POST submission of the page
        // Note that only FF3 and Safari correctly support the standard.
        if (buttonSettings.name == 'button') {
          try {
						button.attr("type", "button");
					}
					catch(e) {}
        }
				if (buttonSettings.title.data || buttonSettings.title.nodeName) {
					button.append(buttonSettings.title);
				}
				else {
					button.dom('span').html(buttonSettings.title);;
				}
				button.addClass('button');
				if (buttonSettings.className) {
					button.addClass(buttonSettings.className);
				}
				buttons[id] = {node: button, selected: false};
				idMap[buttonSettings.id] = buttons[id];
				if (buttonSettings.on) {
					manager.select(button);
				}
				if (buttonSettings.selected) {
					$(button).click(function() {
						manager.select(this);
						buttonSettings.selected(this);
					});
				}
				if (buttonSettings.afterText) {
					if (buttonSettings.afterText.data || buttonSettings.afterText.nodeName) {
						pNode.append(buttonSettings.afterText);
					}
					else {
						pNode.span().addClass("text").html(buttonSettings.afterText);
					}
				}
			};
		}
		/*
		else {
			$(srcNode).each(function() {
				var button = $(this).dom("button");
			});
		}
		*/
		this.Buttons = idMap;
	}
});

// ***
// jquery.ajah.js
/*
================================================================================
jquery.ajah.js
Provides ajax capability to work with html results.  
Taylored to work with estrada presentation behaviors.

Uses the options available to the jQuery ajax call.
Documentation Link: http://docs.jquery.com/Ajax/jQuery.ajax#options

================================================================================
*/

// ajah without callbacks processing. Mimicks (hell, partially stolen from)
// $.load
plugin('ajah', function(url, params, callback, errorCallback) {

  // get selector
  var off = url.indexOf(' ');
  if (off >= 0) {
    var selector = url.slice(off, url.length);
    url = url.slice(0, off);
  }

	callback = callback || nop;
  errorCallback = errorCallback || nop;

  // Default to a GET request
  var type = 'GET';

  // If the second parameter was provided
  if (params) {
    // If it's a function
    if ($.isFunction(params)) {
      // We assume that it's the callback
      callback = params;
      params = null;
    // Otherwise, build a param string
    } else {
      params = $.param(params);
      type = 'POST';
    }
  }
  
  // Request the remote document
  $.ajax({ url: url, type: type, dataType: 'html', data: params,
    beforeSend: function(xhr) {
      // this is a special header that lets the Engine know that it's
      // dealing with an XMLHttpRequest object, which always follows the
      // redirects. Thus, in case of a redirect, the Engine will return
      // a text/plain document with the URL as its contents
      type == 'POST' && xhr.setRequestHeader("x-puny-xhr", "alas");
      
      // add this header to force fresh content in IE
      xhr.setRequestHeader("If-Modified-Since", new Date());
    },
    complete: function(res, status) {
      // If successful, inject the HTML into all the matched elements
      if ( status == 'success' || status == 'notmodified' ) {
        if (type == 'POST' && res.getResponseHeader('Content-Type')
            .indexOf('text/plain') == 0) {
          callback.call({}, status, res.responseText);
          return;
        }
        // make sure the document has loaded
        $(function() {
          var html = res.responseText;
          var prefix = guid();
          var startPos = html.indexOf('body>');
          if (startPos >= 0) {
            startPos += 5;
            var endPos = html.indexOf('</body', startPos);
            if (endPos >= 0) {
              html = html.substring(startPos, endPos)
                .replace(/id(\s)*=(\s)*'/g, 'id=\'' + prefix + '-');
              var store = $(document.body).div().css({ position: 'absolute',
                left: '-1972px', width: '10px', height: '10px',
                overflow: 'hidden' }).html(html);
              if (selector) {
                store.find(selector).each(callback);
              } else {
                store.each(callback);
              }
              document.body.removeChild(store[0]);
            }
          }
        });
      } else {
        errorCallback.call({}, res.status, res.responseText);
      }
    }
  });
  return this;

}, true);

$.ajah2 = $.ajah;
// ***
// jquery.box.js
// calculates total size (including margins, border, and padding) of an element
// returns an object that contains "width" and "height" members

// TODO(dglazkov): add margin/padding information
// TODO(dglazkov): fix the problem with this in IE (does not correctly
//                calculate)
plugin('box', function() {
  var w = this.width();
  var h = this.height();
  var e = this[0];
  
  var style = (document.defaultView
    && document.defaultView.getComputedStyle
    && document.defaultView.getComputedStyle(e, null)) || e.currentStyle
    || e.style;
    
  return {
    width: w + cv(style, 'paddingLeft') + cv(style, 'paddingRight')
      + cv(style, 'marginLeft') + cv(style, 'marginRight')
      + (style.borderLeftStyle == 'none' ?
        0 : cv(style, 'borderLeftWidth'))
      + (style.borderRightStyle == 'none' ?
        0 : cv(style, 'borderRightWidth')),
    height: h + cv(style, 'paddingTop') + cv(style, 'paddingBottom')
      + cv(style, 'marginTop') + cv(style, 'marginBottom')
      + (style.borderTopStyle == 'none' ?
        0 : cv(style, 'borderTopWidth'))
      + (style.borderBottomStyle == 'none' ?
        0 : cv(style, 'borderBottomWidth'))
  };
  

  function cv(s, n) {
    var p = parseInt(s[n].replace(/px$/, ''));
    if (isNaN(p)) {
      // It's either 'auto', or I give up
      return 0;
    }
    return p;
  };
    
});
// ***
// jquery.calendar-grid.js
  
// basic calendar grid, provides rendering of a calendar grid and general
// event handling framework, using "inversion of control" pattern

// uses plugins:
// * table

// TODO(dglazkov): add support for the "year" grid
// TODO(dglazkov): add support for invalid ranges (one at first)
// TODO(dglazkov): assign correct classes to cells (in-range, invalid, today,
// and selected)

// global scope object
var calendar_grid_globals = {
  // global storage for all calendar instances
  all: {},
  // default settings for the calendarGrid. The supplied settings are always
  // merged into these
  default_settings: {
    // called when the grid loads (initially or as a result of setting
    // changing)
    // * this -- Grid Object Model (GOM), an object that encapsulates
    //   manipulation of the grid
    // * params -- parameters of the grid (either defaults or passed via change
    //   invocation)
    load: nop,
    // called when a cell in the grid is selected
    // * this -- TODO(dglazkov): figure out what it is
    // * date -- a Date object, initialized to the date selected
    // * invalid -- true if the selected date is inside of the invalid range(s),
    //   false otherwise
    select: nop,
    // specifies whether and how to show the week labels. Valid values:
    // * 'abbr' -- show abbreviated labels, S, M, T, etc.
    // * 'full' -- show full labels: Monday, Tuesday, etc.
    // * 'none' or any other value -- do not show labels
    labels: 'none',
    showMonthName: false,
    className: 'calendar-grid'
  },
  // day-of-week labels for the grid
  labels: {
    abbr: ['S', 'M', 'T', 'W', 'T', 'F', 'S'],
    full: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday',
           'Saturday']
  },
  firstDayOfWeek: function(date) {
    var dow = date.getDay();
    if (dow) {
      var adjusted = this.zeroDate(date);
      adjusted.setDate(date.getDate() - dow);
      return adjusted;
    }
    return new Date(date.getTime());
  },
  zeroDate: function(date) {
    var adjusted = new Date(date.getTime());
    adjusted.setHours(0);
    adjusted.setMinutes(0);
    adjusted.setSeconds(0);
    adjusted.setMilliseconds(0);
    return adjusted;
  },
  getMonthsSpan: function(start, end) {
	var sDate, eDate;   
    var d1 = start.getFullYear() * 12 + start.getMonth();
    var d2 = end.getFullYear() * 12 + end.getMonth();
    var sign;
    var months = 0;

    if (start == end) {
        months = 0;
    } 
    else if (d1 == d2) {      //same year and month
        months = Math.ceil((end.getDate() - start.getDate())/this.getLastDayOfMonth(start));
    } 
    else {
        if (d1 <  d2) {
            sDate = start;
            eDate = end;
            sign = 1;
        } else {
            sDate = end;
            eDate = start;
            sign = -1;
        }
        
        var lastday = this.getLastDayOfMonth(sDate);
        var sAdj = lastday - sDate.getDate();
        var eAdj = eDate.getDate();
        var adj = Math.ceil((sAdj + eAdj)/lastday -1);
        
        months = ((d2 - d1) + adj) * sign;
    }
    return months;
  },
  rangeCoversMonth: function(range) {
    if ((range.start.getDate() == 1) && isLastDay(range.start, range.end)) {
			return this.getMonthsSpan(range.start, range.end);
		}
		return 0;
  
    function isLastDay(start, end) {
		var test = new Date(end.getTime());
		test.setDate(test.getDate() + 1);
		var diff = test.getMonth() - end.getMonth();
		return ((diff == 1) || (diff == -11));
	}
  },
  createMonthRange: function(date, range, context) {
    var start = this.zeroDate(date);
    start.setDate(1);
    var end = new Date(start.getTime());
    end.setMonth(end.getMonth() + range);
    end.setDate(end.getDate() - 1);
    context.start = start;
    context.end = this.zeroDate(end);
    return context;
  },
  getDaysSpan: function(start, end) {
	var offset = start.getTimezoneOffset() - end.getTimezoneOffset();
	return (((end.getTime() - start.getTime()) + (offset*60*1000)) / (1000*60*60*24));
  },
  getLastDayOfMonth: function(date) {
	var d = new Date(date.getFullYear(), date.getMonth() + 1, 0);
	return d.getDate();
  },
  ATTR: ['onclick="$.calendarGrid(\'', 1, '\').select(this)" id="', 3,
         '" class="', 5, '"'],
  CELL: ['<div class="day">', 1, '</div><div class="cell"></div>']
};

// jQuery module: calendarGrid
// returns CalendarGrid instance
// * id -- string id of the calendar, by which it can be referenced
//   (optional, an internal global id is assigned if not provided)
// * settings -- initial settings of the calendar, optional, see
//   calendar_grid.default_settings for the list of available parameters
// * params -- initial parameters for the grid (optional, DefaultParams is
//   is used if not provided)
plugin('calendarGrid', function(id, settings, params) {
  // abbreviate global scope
  var g = calendar_grid_globals;
  // assign unique id, if not specified
  id = guid(id);
  var instance = g.all[id];
  if (!instance) {
    
    // merge provided args with the defaults to complete missing (if any)
    // members
    var complete_settings = $.extend({}, g.default_settings, settings);
    var complete_params = $.extend({}, new DefaultParams(), params);
    
    return g.all[id] = new CalendarGrid(id, this, complete_settings,
                                        complete_params);
  }
  return instance;

  
  // default parameters for the calendarGrid. Using function/object notation
  // because dates will need to be initialized every time
  function DefaultParams() {
    
    // default range is:
    // * start == first day of the current month,
    // * end == last day of the current month
    g.createMonthRange(new Date(), 1, this);
    
    // default "invalid" ranges array is empty
    this.invalid = [];
  }
  
  // calendar grid instance
  // calling the constructor creates the grid
	function CalendarGrid(id, node, settings, params) {
		// lookup table: from cell id to date
		var dates = {};
		// lookup table: from date ticks to cell id;
		var cells = {};
		// indicates whether the range sliding uses month or explicit range
		// mode: 0 month, 1 days  span: number of days or months
		var rangeincrement = {mode: -1, monthSpan: 0, daySpan: 0};
		var renderMonthName = settings.showMonthName;

		// generate initial calendar grid:
		var table = node.table('class="' + settings.className + '"');
		// first, the labels
		$(g.labels[settings.labels] || ear).each(function() {
			table.cell(this);
		})
		table.row();
		setRangeIncrement(params);
		renderDays(params);
    
		function setRangeIncrement(params) {
			if (!params.rangeincrement || (params.rangeincrement.mode == -1)) {
				if ((rangeincrement.monthSpan = g.rangeCoversMonth(params)) > 0) {
					rangeincrement.mode = 0;
				}
				else { 
					rangeincrement.mode = 1;
				}
			}
		}
		
		// changes the parameters of the grid
		// - new_params -- new parameters. If new_params is a number, moves range
		//   relative to current range (number can be positive or negative)
		this.change = function(new_params) {
			if (new_params != null) {
				if (!isNaN(new_params)) {
					if (new_params != 0) {
						$.extend(params, slide(new_params));
					}
				}
				else {
					$.extend(params, new_params);
					setRangeIncrement(params);
				}
			}
			else {
				rangeincrement.mode = 0;
				g.createMonthRange(g.zeroDate(new Date()), 1, params);
			}
      table.clear(true);
			renderDays(params);
			// move relative to the current range
			function slide(n) {
				if (rangeincrement.mode == 0) {
					// change by months
					var start = new Date(params.start.getTime());
					start.setMonth(start.getMonth() + (n*rangeincrement.monthSpan));
					return g.createMonthRange(start, rangeincrement.monthSpan, {});
				} 
				// change by explicit range
				var start;
				var end;
				if (n > 0) {
					start = g.zeroDate(params.end);
					start.setDate(start.getDate() + 1);
					end = new Date(start.getTime());
					end.setDate(end.getDate() + (rangeincrement.daySpan*n));
				}
				else {
					end = g.zeroDate(params.start);
					end.setDate(end.getDate() - 1);
					start = new Date(end.getTime());
					start.setDate(start.getDate() + (rangeincrement.daySpan*n));
				}
				return { start: start, end: end };
			}
		}
    
		// removes the grid
		this.remove = function() {
		}
    
		this.select = function(node) {
			settings.select.call({}, dates[node.id])
		}
    
		function renderDays(params) {
			rangeincrement.daySpan =  g.getDaysSpan(params.start, params.end);
			cells = {};
			var cdate = new Date();
			var today = [3];
			today[0] = cdate.getDate();
			today[1] = cdate.getMonth();
			today[2] = cdate.getFullYear();
			var completeRange = {};
			var date = g.firstDayOfWeek(params.start);
			completeRange['start'] = new Date(date.getTime());
			var lastDay = g.getLastDayOfMonth(date);
			var calMonth = 0;
			var startTicks = params.start.getTime();
			var endTicks = params.end.getTime();
			while(date <= params.end) {
				for(var j = 0; j < 7; j++) {
					var d = date.getDate();
					if (d == 1) {
						lastDay = g.getLastDayOfMonth(date);
						calMonth = date.getMonth();
					}
					// set up attributes (table id and unique id for the cell)

					g.ATTR[1] = id;
					var cell_id = g.ATTR[3] = guid();
					var ticks = date.getTime();
					dates[cell_id] = new Date(ticks);
					cells[ticks] = cell_id;
					// TODO(dglazkov): set correct class names
					g.ATTR[5] = ((ticks >= startTicks) && (ticks <= endTicks))
						? 'in-range'
						: 'out-range';
					if ((d == today[0]) 
							&& (date.getMonth() == today[1]) 
								&& (date.getFullYear() == today[2])) {
						g.ATTR[5] += ' today';
					}
					// set up cell content
					g.CELL[1] = (renderMonthName && 
						(
							(d == 1) || 
							((d == lastDay) && (date.getMonth() != calMonth))
						)) 
						? $.dates.format(date, "MMM") + " " + d
						: d;
					if (renderMonthName && (calMonth == 0) && (d == 1)) {
						g.CELL[1] += ", " + $.dates.format(date, "yyyy");
					}
						
					//g.CELL[1] = d;
					table.cell(g.CELL.join(''), g.ATTR.join(''));
					date.setDate(d + 1);
				}
				table.row();
			}
			completeRange['end'] = new Date(date.setDate(date.getDate()-1));
			params['completeRange'] = completeRange;
			// render initial view of the table
			table.render();
			params['rangeincrement'] = rangeincrement;
			// invoke load handler
			settings.load.call(new GOM(cells), params);
		}
	}
  
  // Grid Object Model (GOM)
  // exposes usable structure of the calendar grid without revealing too much
  function GOM(cells) {
    // provides access to the inside of a calendar grid cell
    // * date -- a Date() instance
    // returns:
    // * null if the provided date is not inside of the range, currently
    //   displayed by the grid
    // * a jQuery object of the cell. What is returned is not the actual cell,
    //   but rather a container where you can put stuff
    this.cell = function(date) {
      var ticks = g.zeroDate(date).getTime();
      var cell = cells[ticks];
      if (cell) {
        if (typeof(cell) == 'string') {
          // cache result of jQuery traversal prior to returning
          return cells[ticks] = $('#' + cell + ' .cell:first');
        }
        return cell;
      }
    }
  }
  
  // IMPORTANT: The entire DateMatrix is currently not used  
  // represents the two-dimensional array (matrix) that contains the references
  // to nodes and provides abilities to find a node based on the Date and
  // vise versa
  function DateMatrix() {
    this._matrix = [];
    this.selected = {
      equals: function(year, month, day) {
        if (!month) {
          return year == this._getValue();
        }
        return this.year == year && this.month == month && this.day == day;
      },
      set: function(o, id) {
        this.year = o.year;
        this.month = o.month;
        this.day = o.day;
        this.id = id;
        this._value = null;
      },
      clear: function() {
        this.year = null;
        this.month = null;
        this.id = null;
        this.day = null;
        this._value = null;
      },
      get: function() {
        return this.year ?
          ((this.month + 1) + "/" + this.day + "/" + this.year) : "";
      },
      _getValue: function() {
        if (!this._value) {
          this._value = new Date(this.year, this.month, this.day).valueOf();
        }
        return this._value;
      }
    };
    this.rlookup = {
      reset: function() {
        this._table = {};
      },
      set: function(year, month, day, id) {
        this._table[id] = { year: year, month: month, day: day };
      },
      get: function(id) {
        return this._table[id];
      }
    }
  }
    
  DateMatrix.prototype.months = ["January", "February", "March", "April", "May",
                               "June", "July", "August", "September", "October",
                               "November", "December"];
  
  DateMatrix.prototype.initWeek =  function() {
    this._week && this._matrix.push(this._week);
    this._week = [];
  };
  
  DateMatrix.prototype.initDay = function(node) {
    var id = node.id;
    this._week.push(id);
  };
  
  DateMatrix.prototype.initMonth = function(context) {
    this._matrix.push(this._week);
    this._title = context.month;
  };
  
  DateMatrix.prototype.delta = function(delta) {
    var m = this.month + delta % 12;
    var yd = parseInt(delta / 12);
    if (m > 11) {
      m = 0;
      yd++;
    }
    if (m < 0) {
      m = 11;
      yd--;
    }
    this.populate(this.year + yd, m);
  };
  
  
  DateMatrix.prototype.select = function(node) {
    var day = parseInt(node.innerHTML);
    var id = this.selected.id;
    if (id) {
      var old = document.getElementById(id);
      old.className = old.className.replace(/ e-selected/, "");
    }
    node.className += " e-selected";
    this.selected.set(this.rlookup.get(node.id), node.id);
  };
  
  DateMatrix.prototype.populate = function(year, month) {
    this.rlookup.reset();
    this.year = year;
    this.month = month;
    this._title.innerHTML = this.months[month] + " " + this.year;
    var date = new Date(year, month, 1);
    var now = new Date();
    now =
      (new Date(now.getFullYear(), now.getMonth(), now.getDate())).valueOf();
    var dow = date.getDay();
    var d = dow > 0 ? -dow : -7;
    var week = 0;
    var other;
    var selected;
    var node;
    var value;
    var m;
    for(var week = 0; week < 6; week++) {
      for(dow = 0; dow < 7; dow++) {
        date.setDate(++d);
        d = date.getDate();
        m = date.getMonth();
        value = date.valueOf();
        node = document.getElementById(this._matrix[week][dow]);
        node.innerHTML = d;
        selected = this.selected.equals(value);
        this.rlookup.set(date.getFullYear(), m, date.getDate(), node.id);
        if (selected) this.selected.id = node.id;
        other = m != month;
        node.className = (other ? "e-other-month" : "e-day") +
          (value == now ? "-now" : "") + (selected ? " e-selected" : "")
      }
    }
  }
});

// ***
// jquery.category-picker.js
// using sheet, provides a dialog box for picking categories

/*
settings:
	select(itemArray) - callback when 'ok' button is selected
		itemArray - array of currently selected category values
	inheritUp - true | false
		specifies that if selected, all ancestor categories are also selected.
	inheritDown - true | false
		specifies that if selected, all descendant categories are also selected.

*/
		

var category_picker = {
	CATEGORYPICKER_KEY: 'category_picker',
	defaults: {
		title: 'Select Categories',
		select: nop,
		inheritUp: true,
		inheritDown: false,
		categories: null,
		close: nop
	}
};

plugin('categoryPicker', function(settings) {
	var g = category_picker;
	var srcNode = this;
	settings = $.extend({}, g.defaults, settings);
	var picker = $(this).data(g.CATEGORYPICKER_KEY);
	if (!picker) {
		picker = new categorySheet(); 
	}
	else {
		picker.open();
	}
	return picker;
	
	function categorySheet() { 
		var me = this;
		var results = new categoryResults(settings.inheritUp, settings.inheritDown);
		var picker = new categoryPicker(results);
		var areas = [];
		if (settings.categories) {
			$.each(settings.categories, function() {
				areas.push(picker.parseSelect(this));
			});
		}
		else {
			$(srcNode).each(function() {
				var nodeName = $(this).get(0).nodeName.toLowerCase();
				switch (nodeName) {
					case "select":
						areas.push(picker.parseSelect(this));
						$(this).remove();
						break;
					default:
						break;
				}
			});
		}
		
		this.open = function() {
			$(srcNode).sheet({
					width: '535px',
					title: settings.title,
					load: function(done, manager) {
						categorySheetHandler(this, areas);
						this.phrase({
							load: function() {
								this.button('CANCEL', function() {
									manager.close();
									settings.close();
								});
								this.button('OK', 'ok', function(done) {
									manager.enabled(false);
									manager.close();
									settings.select(results.getSelected());
									return true;
								});
							}
						});
					},
					close: function() {
						settings.close();
					}
				});
		}
		
		me.open();
		
		function categorySheetHandler(workArea, areas) {
			$(workArea).addClass("category-picker");
			if (picker.count > 100) {
				$(workArea).parent().addClass("large");
			}
			else if (picker.count > 50) {
				$(workArea).parent().addClass("medium");
			}
			else {
				$(workArea).parent().addClass("small");
			}
			$(workArea).cols(
				{
					one: { className: "pickerarea" },
					two: { className: "resultsarea" }
				});

			var outer = $('.pickerarea', workArea).div().addClass('outer-scroll');
			picker.area = outer.div().addClass('scrollarea');
			outer = $('.resultsarea', workArea).div().addClass('outer-scroll');
			results.area = outer.div().addClass('scrollarea');
			$(areas).each(function() {
				picker.area.append(this);
			});
			picker.area.treeview({
				collapsed: true,
				animated: "medium",
				persist: "location"
			});
			results.init();
		}
	}
	
	function categoryPicker(results) {
		var me = this;
		this.area = null;
		this.count = 0;
	
		this.parseSelect = function(select) {
			var nbsp = String.fromCharCode(160);
			var holder = $('<div class="category-segment" />');
			holder.div().addClass("title").text(select.title);
			var curr = holder;
			var prevLevel = 0;
			var inputName = $(select).attr("name");
			$("option", select).each(function() {
				var item = getItem($(this));
				if (item) {
					item.value = inputName + '.' + item.value;
					var cnt = item.level - prevLevel;
					if (cnt != 0) {
						if (cnt > 0) {
							for (;cnt > 0; cnt--) {
								curr = curr.dom("ul");
							}
						}
						else if (cnt < 0){
							while (curr && (cnt < 0)) {
								if (curr.attr("nodeName").toLowerCase() == "ul") {
									++cnt;
								}
								curr = curr.parent();
							}
						}
						prevLevel = item.level;
					}
					var id = guid();
					curr = curr.attr("nodeName").toLowerCase() == "li" 
						? curr.parent().dom("li").addClass("item-checkbox")
						: curr.dom("li").addClass("item-checkbox");
					curr.dom("a").attr("name", id);
					var checkbox = curr.dom("div").attr("id", id).addClass("checkbox").click(function() {
						results.change($(this));
					});
					if (item.inherited) {
						curr.dom("span").addClass('inherited-flag');
						curr.addClass('inherited');
						checkbox.addClass('inherited');
						results.inherited.push(id);
					}
					curr.dom("span").addClass(item.value).text(item.text);
					if (item.selected) {
						results.defaultSelected.push(id);
					}
					
					++me.count
				}
			});

			return holder;
			
			function getItem(node) {
				var text = node.text();
				var value = node.attr('value');
				if (text == '-- select --') return null;
				var selected = node.attr('selected') ? node.attr('selected') : false;
				var inherited = node.hasClass('inherited');
				var e = text.split(nbsp);
				return {level: e.length, 
					text: e[e.length-1], 
					value: value, 
					selected: selected,
					inherited: inherited
				};
			}
		}
	
	}
	
	function categoryResults(inheritUp, inheritDown) {
		var me = this;
		var results = {};
		var regx = new RegExp('checked-disabled');
		this.area;
		var renderArea;
		this.defaultSelected = [];
		this.inherited = [];
		
		this.init = function() {
			me.area.div().addClass("title").text("Current Selection");
			renderArea = me.area.div();
			jQuery.each(me.defaultSelected, function() {
				me.change($('#' + this));
			});
		}
		
		this.change = function(jNode) {
			if (jNode.hasClass('checked-disabled') || jNode.hasClass('inherited')) {
				return;
			}
			if (!jNode.attr("id")) {
				jNode.attr("id", guid());
			}
			var id = jNode.attr("id");
			if (!results[id]) {
				add(jNode, true, id);
			}
			else {
				remove(jNode, true, id);
			}
			render();
		}
		
		this.getSelected = function() {
			var selected = {};
			var item;
			for (var key in results) {
				item = results[key];
				var v = item.value.split('.');
				var name = v[0];
				var value = v.slice(1).join('');
				var items = selected[name];
				if (!items) {
					items = [];
					selected[name] = items;
				}
				items.push(value);
			}
			return selected;
		}
		
		/*
		this.getSelected = function() {
			var items = [];
			var item;
			for (var key in results) {
				item = results[key];
				items.push(item.value);
			}
			return items;
		}
		*/
		
		function render() {
			var item;
			var items = [];
			for (var key in results) {
				item = results[key];
				items.push([item.text, item.checkbox]);
			}
			var idx = 0;
			renderArea.empty();
			if (items.length > 0) {
				items.sort();
				$(items).each(function(idx) {
					item = this;
					var a = renderArea.div().addClass('item-selected');
					var id = "remove-" + $(item[idx,1]).attr("id");
					a.dom("dom").attr("id", id).attr("title", "Remove Category")
						.addClass("remove")
						.click(function() {
							var id = $(this).attr("id").replace("remove-","");
							$((results[id]).checkbox).click();
						})
						.dom('b');
					a.dom("span").addClass("title")
						.dom("a").attr("href", "#" + $(item[idx,1]).attr("id"))
						.attr("title", "Go to Category")
						.text(this[idx, 0])
						.click(function() {
							var id = $(this).attr("href").replace("#", "");
							$(results[id].checkbox).parents().filter(".expandable")
								.find(">.hitarea")
								.each(function() {
									$(this).click();
								});

						});
				});
			}
		}
		
		function add(node, updateTree, id) {
			if (!id) {
				id = node.attr("id");
			}
			var parent = node.parent();
			var item = $('span:first', parent);
			if ((inheritUp || inheritDown) && updateTree) removeCheckedAncestor(parent);
			results[id] = {checkbox: node, text: item.text(), value: item.attr('className')};
			node.removeClass("checked-disabled");
			node.addClass("checkbox-checked");
			parent.addClass("checked");
			if (inheritUp && updateTree) {
				setAncestors(parent, true);
				setDescendants(parent, false);
			}
			if (inheritDown && updateTree) setDescendants(parent, true);
		}
		
		function remove(node, updateTree, id) {
			if (!id) {
				id = node.attr("id");
			}
			delete results[id];
			node.removeClass("checkbox-checked");
			var parent = node.parent();
			parent.removeClass("checked");
			if (inheritUp && updateTree) setAncestors(parent, false);
			if (inheritDown && updateTree) setDescendants(parent, false);
		}
		
		function isChecked(node) {
			return node.hasClass("checkbox-checked") || node.hasClass("checked");
		}
		
		function setDescendants(node, checked) {
		
			descend(node);
			function descend(el) {
				$("li", el).each(function() {
					var checkbox = $(".checkbox:first", this);
					if (checkbox) {
						if (isChecked(checkbox)) {
							remove(checkbox, false);
						}
						if (checked) {
							checkbox.addClass("checked-disabled");
						}
						else {
							checkbox.removeClass("checked-disabled");
						}
					}
					descend($(this));
				});
			}
		}
		
		function setAncestors(node, checked) {
			node.parents("li.item-checkbox").each(function() {
				var name = $(this).attr("nodeName").toLowerCase();
				var checkbox = $(">.checkbox", this);
				if (checkbox) {
					if (isChecked(checkbox)) {
						remove(checkbox, false);
					}
					checkboxNode = checkbox.get(0);
					if (checked) {
						checkboxNode.className += " checked-disabled";
					}
					else {
						checkboxNode.className = checkboxNode.className.replace(regx, "");
					}
				} 
			});
		}
		
		function removeCheckedAncestor(node) {
			node.parents("li").each(function() {
				if (isChecked($(this))) {
					var checkbox = $(".checkbox:first", this);
					if (checkbox) {
						remove(checkbox, true);
						return true;
					}
				}
			});
		}
	}
});
// ***
// jquery.content-query.js
// General content query processor

var global_content_query = {
	DATA_KEY: 'content_query_data',
	setQuery: function (query, name, value) {
		if (!query || query.length == 0) {
			query = "?";
		}
		else {
			query += "&";
		}
		//query += escape(name) + "=" + escape(value);
		query += encodeURIComponent(name) + "=" + encodeURIComponent(value);
		return query;
	},
	// the collection of registered renderers
	renderers: {},
	// This is the renderer that is used when no renderer is found for the
	// specified key or the key is not specified
	// also, this renderer is merged with all renderers to supply missing/optional
	// members
	default_renderer: {
		titleCapitalized: 'List',
		title: 'list',
		className: 'list',
		selector: '.content ul:first',
		
		itemLoader: function(config, node) {
			var items = [];
			$('li', node).each(function() {
				var item = $(this);
				items.push(item);
			});
			config.items = items;
		},
		
		// Called to create the overall presentation.
		// Parameters:
		// * config -- the configuration object
		render: function(config) {
			var grid = config.queryJNode.listGrid('default_list', 
				{
					load: function(params) {
					},
					render: function(params) {
						grid = this;
						grid.row();
						$.each(config.items, function() {
							grid.cell(this.html());
							grid.row();
						});
						grid.render();
					}
				});
		},
		noFormRenderer: function(config) {
			if (!config) {
				document.body.append('<div>No data</div>');
				return;
			}
			config.queryJNode.remove();
		}
	}
};


// The main method has two modes:
// * registration mode, which is used to register a custom renderer
// * processing mode, which is used to process a content query (or potential
//   content query)
// The modes are chosen transparently by using different call signatures.
//
// Registration usage:
// * key:String -- a string key that identifies a custom renderer
// * renderer:CustomRendererObject -- an object that has all the methods and
//   properties, necessary to create a custom renderer
// Progessing usage -- call with no parameters on the node that is to be
// processed
plugin('contentQuery', function(key, renderer) {
	var g = global_content_query;
	if (!key) {
		// processing mode
		// for each query inside, process content query
		var cqForm = $('.content-query > form');
		if (cqForm.length > 0) {
			cqForm.each(function() {
					// indicates that there is a form and the query was processed
					var processed;
					var query = $(this).parent();
					var config = new queryConfig(query, this);					
					config.renderer.itemLoader(config, query);
					// remove query markup from DOM, because we don't need it anymore
					query.empty();
					// exit and set to continue after layout is finished
					// this will (perceptually) speed up loading of the page
					window.setTimeout(function() {
						config.renderer.render.call(query, config);
					}, 0);
			});
		}
		else {
			var config = new queryConfig(cq, null);
			var cqKey = $('.content-query span.key');
			if (cqKey.length > 0) {
				var cq = cqKey.parents('.content-query:first');
				var k = queryKey(cq);
				config.key = k.key;
				config.queryJNode = cq;
				config.renderer = k.renderer;
				k.renderer.noFormRenderer(config);
			}
		}
	} else if (renderer) {
		// registration mode
		g.renderers[key] = renderer;
	}
	return this;
	
	function queryKey(node) {
		if (node && node.length > 0) {
			var key = $("span.key", node).text();
			// get renderer
			var renderer = $.extend({}, g.default_renderer,
				g.renderers[key] || {});
			return {key: key, renderer: renderer};
		}
		return {key: null, renderer: g.default_renderer};
	}
	
	function queryConfig(queryNode, formNode) {
		
		var me = this;
		var querySpecial = "";
		this.baseUrl = window.location.pathname;
		this.mode = "";
		this.loadRequired = true;
		this.loadDisabled = false;
		this.queryJNode = queryNode;
		this.head = "";
		
		var allDateRange = {
			start: null,
			end: null
		};
		
		urlQuery = new queryString();
		querySpecial = urlQuery.setQuery('draft-preview', querySpecial);
	
		var k = queryKey(formNode);
		this.key = k.key;
		this.renderer = k.renderer;
		
		this.items = null;
		this.itemMap = null;
		
		//this.filters = null;
	
		// range
		this.range = {
			start: null,
			end: null,
			extended: null,
			set: function(start, end, extended_start, extended_end) {
				this.start.value.set(start);
				this.end.value.set(end);
				if (extended_start && extended_end) {
					this.extended = {
						start: new dataValue(this.start),
						end: new dataValue(this.end)
					}
					this.extended.start.set(extended_start)
					this.extended.end.set(extended_end)
				}
				else {
					this.extended = null;
				}
			},
			init: function() {
				this.start = new configItem(null, null);
				this.start.label = "Start";
				this.end = new configItem(null, null);
				this.end.label = "End";
			}
		};
		
		//filters
		this.filters = {
			filterList: null,
			set: function(name, value) {
				me.filters.filterList[name].value.set(value);
			},
			toQuery: function(q) {
				for (var key in me.filters.filterList) {
					q = me.filters.filterList[key].value.toQuery(q);
				}
				return q;
			}
		};
		
		if (formNode) {
			// range
			me.range.start = new configItem($('.range input.from', formNode));
			if (!me.range.start.label) me.range.start.label = "Start";
			me.range.end = new configItem($('.range input.to', formNode));
			if (!me.range.end.label) me.range.end.label = "End";
			// search
			this.search = new configItem($('input.search', formNode), true);
			if (!this.search.label) this.search.label = "Search";
			// filters
			var filterItems = $('select.filter', formNode);
			if (filterItems.length > 0) {
				me.filters.filterList = {};
			}
			filterItems.each(function() {
				var item = new configItem($(this), true);
				me.filters.filterList[item.name] = item;
			});
		}
		
		if (me.filters.filterList) {
			me.filters.selects = [];
			for (var key in me.filters.filterList) {
				me.filters.selects.push(me.filters.filterList[key].input);
			}
		}
		
		if (me.range.start) {
			allDateRange.start =  new dataValue(me.range.start);
			var d = new Date();
			d.setFullYear(d.getFullYear()-100);
			allDateRange.start.set(d);
		}
		if (me.range.end) {
			allDateRange.end =  new dataValue(me.range.end);
			var d = new Date();
			d.setFullYear(d.getFullYear()+100);
			allDateRange.end.set(d);
		}
				
		// Url used for posting
		this.postUrl = function(params) {
			var q = "";
			if ((!params || params.range) && me.range) {
				if (me.range.start) {
					q = me.range.extended 
						? me.range.extended.start.toQuery(q)
						: me.range.start.value.toQuery(q);
				}
				if (me.range.end) {
					q = me.range.extended 
						? me.range.extended.end.toQuery(q)
						: me.range.end.value.toQuery(q);
				}
			}
			else {
				q = allDateRange.start.toQuery(q);
				q = allDateRange.end.toQuery(q);
			}
			if ((!params || params.search) && me.search) {
				q = me.search.value.toQuery(q);
			}
			if (me.filters.filterList) {
				q = me.filters.toQuery(q);
			}
			q += ((q.length > 0) ? '&' : '?') + querySpecial;
			return me.baseUrl + q;
		};
		
		// Generic object for handling inputs.
		function configItem(items, useInput) {
			//if (!items) return null;
			var i = this;
			
			var otherNames;
			var inputNode;
			items && items.each(function(idx) {
				if (idx == 0) {
					inputNode = this;
				}
				else {
					if (!otherNames) otherNames = [];
					otherNames.push(this.name);
				}
			});
				
			this.name = null;
			this.type = null;
			this.label = null;
			this.input = null;
			this.moreNames = otherNames;
			
			this.value = new dataValue(this);
			
			if (inputNode) {
				i.name = inputNode.name;
				i.type = inputNode.type;
			}

			switch(i.type) {
				case "text":
				case "textarea":
					i.value.set(inputNode.value)
					break;
				case "select-one":
				case "select-multiple":
					$(inputNode.options).each(function() {
						if (this.selected) {
							i.value.add(this.value);
						}
					});
					break;
			}
			
			if (useInput) {
				this.input = inputNode;
			}
			
			var labelNode = $(inputNode).prev("label").get(0);
			if (labelNode) {
				i.label = $(labelNode).text();
			}
		}
		
		function dataValue(cfgItem) {
			var val;
			this.get = function () {
				return val;
			}
			this.set = function(v) {
				val = v;
			}
			this.add = function(v) {
				if (!val || !val.push) {
					val = [];
				}
				val.push(v);
			}
			this.toString = function(format) {
				var retval = "";
				if (!format) format = "";
				if (val) {
					if (val.constructor === Array) {
						var cnt = 0;
						$(val).each(function() {
							++cnt;
							if (cnt > 1) query += ";";
							query += str(this, format);
						});
					}
					else {
						retval = str(val, format);
					}
				}
				
				return retval;
				
				function str(v, format) {
					if (v.getDate) {
						if (!format) format = "M/d/yyyy";
						return $.dates.format(v, format);
					}
					else {
						return v.toString();
					}
				}
			}
			
			this.toQuery = function(query) {
				if (cfgItem && val) {
					var v = '';
					if (val.constructor === Array) {
						$(val).each(function(idx) {
							v += ((idx == 0) ? '' : ',') + str(this);
						});
					}
					else {
						v = str(val);
					}
					if (v.length > 0) {
						query = g.setQuery(query, cfgItem.name, v);
						if (cfgItem.moreNames) {
							$.each(cfgItem.moreNames, function() {
								query = g.setQuery(query, this, v);
							});
						}
					}
				}
				
				return query;
				
				function str(v) {
					if (v.getDate) {
						return $.dates.querydate(v); //$.dates.iso8601date(v);
					}
					else {
						return v.toString();
					};
				}
			};
		}
	}
	
	function queryString() {
		var me = this;
		if (window.location.search) {
			var list = window.location.search.split("?")[1].split("&");
			for(var i = 0; i < list.length; i ++)
			{
				var pair = list[i].split("=");
				this[pair[0].toLowerCase()] = pair[1];
			}
		}
		
		this.setQuery = function(key, query) {
			if (me[key]) {
				query += ((query && query.length > 0) ? '&' : '')
					+ key + '=' + me[key];
			}
			return query;
		}
	}
});

// ***
// jquery.date-picker.js
// provides an inline date/time picker

// TODO(dglazkov): implement date picker
// TODO(dglazkov): implement time picker (later)
// TODO(dglazkov): add documentation
plugin('datePicker', function() {
  // TODO(dglazkov): add functionality
});
// ***
// jquery.dates.js
// provides parsing of dates.  Takes a string as input and attempts
// to parse out a date time value.  Returns a date object.

// TODO(nchampion): comment for documentation
//					add aditional datePatterns
//					add two digit year support
//					order date patterns according to hit probability

plugin('dates', function() {
	var monthNames = [
		'January',
		'February',
		'March',
		'April',
		'May',
		'June',
		'July',
		'August',
		'September',
		'October',
		'November',
		'December'
	];
	var dayNames = [
		'Sunday',
		'Monday',
		'Tuesday',
		'Wednesday',
		'Thursday',
		'Friday',
		'Saturday'
	];
	var datePatterns = [
		// +++ Required date indicator, create default date
		{	regex: /^\+\+\+$/,
			handler: function() {
       return new Date();
			}
		},
		// yyyy-mm-ddThh:mm:ss
		{	regex: /(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})/,
			handler: function(strDate, matched) {
        var d = new Date();
				d.setFullYear(matched[1]);
				d.setMonth(parseInt(matched[2], 10) - 1);
				d.setDate(parseInt(matched[3], 10));
        d.setHours(parseInt(matched[4], 10));
				d.setMinutes(parseInt(matched[5], 10));
				d.setSeconds(parseInt(matched[6], 10));
				return d;
			}
		},
		// yyyy-mm-dd or yyyy/mm/dd
		// 2008-04-12 or 2007/11/30
		{	regex: /(\d{4})[\/-](\d{1,2})[\/-](\d{1,2})/,
			handler: function(strDate, matched) {
				var d = zeroDate(new Date());
				d.setFullYear(matched[1]);
				d.setMonth(parseInt(matched[2], 10) - 1);
				d.setDate(parseInt(matched[3], 10));
				return checktime(d,strDate);
			}
		},
		// mm/dd/yyyy or mm-dd-yyyy or mm/dd/yy or mm-dd-yy
		// 10/23/2008 or 3-5-1999 or 1/21/08
		{	regex: /(\d{1,2})[\/|-](\d{1,2})[\/|-](\d{2,4})/,
			handler: function(strDate, matched) {
				var d = zeroDate(new Date());
				var y = parseInt(matched[3],10);
				if (y <= 50) { y = "" + (y + 2000); }
				else if (y <=99) { y = "" + (y + 1900);	}
				else if (y < 999) y=""+(y-0+1900);
				d.setFullYear(y);
				d.setMonth(parseInt(matched[1], 10) - 1);
				d.setDate(parseInt(matched[2], 10));
				return checktime(d,strDate);
			}
		},
		// Mon dd[st,h,rd,nd] hh:mm:ss UTC yyyy (standard unix date command format)
		// Mon Jan  6 15:44:21 PST 1997
		{	regex: /(\w+) +(\d{1,2}) +(\d{1,2}):(\d{1,2}):(\d{1,2}) +(?:\w+) +(\d{4})/i,
			handler: function(strDate, matched) {
				var m, d = new Date();
				if (matched.length == 7) {
					if (matched[6]) {
						d.setFullYear(matched[6]);
					}
				}
				if ((m=getMonth(matched[1])) >= 0) {
					d.setMonth(m);
					d.setDate(parseInt(matched[2], 10));
					d.setHours(parseInt(matched[3], 10));
					d.setMinutes(parseInt(matched[4], 10));
					d.setSeconds(parseInt(matched[5], 10));
				} else {
					d = null;
				}
				return d;
			}
		},
		// Month dd[st,h,rd,nd], yyyy or Mon dd[st,h,rd,nd], yyyy
		// September 11, 2001 or Jan 4th, 2009
		{	regex: /(\w+) (\d{1,2})(?:st|nd|rd|th)?,? ?(\d{4})?/i,
			handler: function(strDate, matched) {
				var m, d = new Date();
				if (matched.length == 4) {
					if (matched[3]) {
						d.setFullYear(matched[3]);
					}
				}
				if ((m=getMonth(matched[1])) >= 0) {
					d.setMonth(m);
					d.setDate(parseInt(matched[2], 10));
				} else {
					d = null;
				}
				if (d) return checktime(d,strDate);
			}
		},
		// yyyyMMdd.hhmmss
		// 20080412.153421
		{	regex: /(\d{4})(\d{1,2})(\d{1,2})(?:.|T)(\d{1,2})(\d{1,2})(?:\.?)(\d{1,2})/,
			handler: function(strDate, matched) {
				var d = new Date();
				d.setFullYear(matched[1]);
				d.setMonth(parseInt(matched[2], 10) - 1);
				d.setDate(parseInt(matched[3], 10));
				d.setHours(parseInt(matched[4], 10));
				d.setMinutes(parseInt(matched[5], 10));
				d.setSeconds(parseInt(matched[6], 10));
				return d;
			}
		},
		// dd[st,h,rd,nd]
		// 27th
		{	regex: /^(\d{1,2})(st|nd|rd|th)?$/i, 
			handler: function(strDate, matched) {
				var d = new Date();
				d.setDate(parseInt(matched[1], 10));
				return checktime(d,strDate);
			}
		},
		// Today or Now
		{	regex: /^tod|^now/i,
			handler: function(strDate, matched) { 
				var d = new Date();
				return checktime(d,strDate);
			} 
		},
		// Tomorrow
		{	regex: /^tom/i,
			handler: function(strDate, matched) {
				var d = new Date(); 
				d.setDate(d.getDate() + 1); 
				return checktime(d,strDate);
			}
		},
		// Yesterday
		{	regex: /^yes/i,
			handler: function(strDate, matched) {
				var d = new Date();
				d.setDate(d.getDate() - 1);
				return checktime(d,strDate);
			}
		}
	];      
	function getMonth(monthName) {
		var intCount = monthNames.length;
		var matches, rv = -1;
		for (var idx = 0; idx < intCount; idx++) {
			matches = new RegExp("^" + monthName, "i").test(monthNames[idx]);
			if (matches) {
				rv = idx;
				idx=intCount;
			}
		}
		return rv;
	};
	function zeroDate(date) {
    var adjusted = new Date(date.getTime());
    adjusted.setHours(0);
    adjusted.setMinutes(0);
    adjusted.setSeconds(0);
    adjusted.setMilliseconds(0);
    return adjusted;
  };
	function checktime(dateObj, strDate) {
		var timeRE = /(\d{1,2}):(\d{1,2}):?(\d{1,2})? ?(am|pm)?/i;
		var matched = timeRE.exec(strDate);
		if (matched && matched.length > 1 && matched[1]) {
			var intHours = parseInt(matched[1]);
			if (matched.length > 4 && matched[4]) {
				if (matched[4].toLowerCase().indexOf("am")==0) {
					if (intHours == 12) {
						intHours = 0;
					} else if (intHours > 12) {
						return (null);
					}
				} else if (intHours < 12 && matched[4].toLowerCase().indexOf("pm")==0) {
					intHours += 12;
				}
			}
			dateObj.setHours(intHours);
			dateObj.setMinutes(0);
			dateObj.setSeconds(0);
			if (matched.length > 2 && matched[2]) {
				dateObj.setMinutes(matched[2]);
				dateObj.setSeconds(0);
				if (matched.length > 3 && matched[3]) {
					dateObj.setSeconds(matched[3]);
				}
			}
		}
		return dateObj;
	};
	function checkDate(oDate) {
		if (!oDate) {
			oDate = new Date();
		}
		return(oDate);
	};
	////////////////////////////////////////////////////////////////////////////
	// This function uses the same 'format' strings as the 
	// java.text.SimpleDateFormat class, with minor exceptions.
	// The format string consists of the following abbreviations:
	// 
	// Field        | Full Form          | Short Form
	// -------------+--------------------+-----------------------
	// Year         | yyyy (4 digits)    | yy (2 digits), y (2 or 4 digits)
	// Month        | MMM (name)         | MM (2 digits), M (1 or 2 digits)
	//              | NNN (abbr.)        |
	// Day of Month | dd (2 digits)      | d (1 or 2 digits)
	// Day of Week  | EE (name)          | E (abbr)
	// Hour (1-12)  | hh (2 digits)      | h (1 or 2 digits)
	// Hour (0-23)  | HH (2 digits)      | H (1 or 2 digits)
	// Hour (0-11)  | KK (2 digits)      | K (1 or 2 digits)
	// Hour (1-24)  | kk (2 digits)      | k (1 or 2 digits)
	// Minute       | mm (2 digits)      | m (1 or 2 digits)
	// Second       | ss (2 digits)      | s (1 or 2 digits)
	// AM/PM        | A  (AM or PM)      | a ( a or p)
	//
	// NOTE THE DIFFERENCE BETWEEN MM and mm! Month=MM, not mm!
	// Examples:
	//  "MMM d, y" matches: January 01, 2000
	//                      Dec 1, 1900
	//                      Nov 20, 00
	//  "M/d/yy"   matches: 01/20/00
	//                      9/2/00
	//  "MMM dd, yyyy hh:mm:ssa" matches: "January 01, 2000 12:30:45AM"
	// ------------------------------------------------------------------
	// formatDate (date_object, format)
	// Returns a date in the output format specified.
	/////////////////////////////////////////////////////////////////////////////
	function formatDate(date,format) {
		function LZ(x) {return(x<0||x>9?"":"0")+x}
		format=format+"";
		var result="";
		var i_format=0;
		var c="";
		var token="";
		var y=date.getYear();
		var M=date.getMonth()+1;
		var d=date.getDate();
		var E=date.getDay();
		var H=date.getHours();
		var m=date.getMinutes();
		var s=date.getSeconds();
	
		var yyyy,yy,MMM,MM,dd,hh,h,mm,ss,ampm,HH,H,KK,K,kk,k;
		// Convert real date parts into formatted versions
		var value=new Object();
		if (y <= 50) { y = "" + (y + 2000); }
		else if (y <=99) { y = "" + (y + 1900);	}
		else if (y < 999) y=""+(y-0+1900);
		else y=""+y;
		value["y"]=""+y;
		value["yyyy"]=y;
		value["yy"]=y.substring(2,4);
		value["M"]=M;
		value["MM"]=LZ(M);
		value["MMM"]=monthNames[M-1];
		value["NNN"]=monthNames[M+11];
		value["d"]=d;
		value["dd"]=LZ(d);
		value["E"]=dayNames[E].substring(0,3);
		value["EE"]=dayNames[E];
		value["H"]=H;
		value["HH"]=LZ(H);
		if (H==0){value["h"]=12;}
		else if (H>12){value["h"]=H-12;}
		else {value["h"]=H;}
		value["hh"]=LZ(value["h"]);
		if (H>11) {value["K"]=H-12;} else {value["K"]=H;}
		value["k"]=H+1;
		value["KK"]=LZ(value["K"]);
		value["kk"]=LZ(value["k"]);
		if (H > 11) { value["A"]="PM"; value["a"]= "p"; }
		else { value["A"]="AM"; value["a"] = "a"; }
		value["m"]=m;
		value["mm"]=LZ(m);
		value["s"]=s;
		value["ss"]=LZ(s);
		while (i_format < format.length) {
			c=format.charAt(i_format);
			token="";
			while ((format.charAt(i_format)==c) && (i_format < format.length)) {
				token += format.charAt(i_format++);
			}
			if (value[token] != null) {	result=result + value[token]; }
			else { result=result + token; }
		}
		return result;
	};
	var datesHandler = {
		parse: function(strDate) {
			var intCount = datePatterns.length;
			var regex, handler, matched;
			for (var idx = 0; idx < intCount; idx++) {
				matched = null;
				regex = datePatterns[idx].regex;
				handler = datePatterns[idx].handler;
				matched = regex.exec(strDate);
				if (matched) {
					return handler(strDate,matched);
				}
			}
			return null;
		},
    relative: function(date, to) {
      to || (to = new Date());
      var diff = (to - date) / 60000;
      if (diff < 1) {
        return "a few seconds ago";
      }
      else if (diff < 2) {
        return "about a minute ago";
      }
      else if (diff < 60) {
        return parseInt(diff, 10) + " minutes ago";
      }
      else {
        diff = diff / 60;
        if (diff < 2) {
          return "an hour ago";
        }
        else if (diff < 24) {
          return parseInt(diff, 10) + " hours ago";
        }
        else {
          diff = diff / 24;
          if (diff < 2) {
            return "a day ago";
          }
          else if (diff < 30) {
            return parseInt(diff, 10) + " days ago";
          }
          else {
            diff = diff / 30;
            if (diff < 2) {
              return "about a month ago";
            }
            else if (diff < 12) {
              return parseInt(diff, 10) + " months ago";
            }
            else {
              diff = diff / 12;
              if (diff < 2) {
                return "about a year ago";
              }
              return parseInt(diff, 10) + " years ago";
            }
          }
        }
      }
      return s;        
    },
		longdate: function(oDate) {
			return formatDate(checkDate(oDate), "EE, MMM dd, yyyy hh:mm:ss A");
		},
		shortdate: function(oDate) {
			return formatDate(checkDate(oDate), "M/d/yyyy");
		},
		iso8601date: function(oDate) {
			return formatDate(checkDate(oDate), "yyyy-MM-ddTHH:mm:ss");
		},
		querydate: function(oDate) {
			return formatDate(checkDate(oDate), "M/d/yyyy hh:mm:ss A");
		},
		format: function(oDate, strFormat) {
			return formatDate(checkDate(oDate), strFormat);
		}
	};
	return datesHandler;
}(), true);

// ***
// jquery.dom.js
// Simple DOM manipulation

// creates an element by specified name, appends it to current node and returns
// new element's jQuery object
plugin('dom', function(name) {
  var result;
  this.each(function() {
    var el = this.appendChild(document.createElement(name));
    (result && $(result).add(el)) || (result = $(el));
  });
  return result;
});


// creates a div, appends it to current node and returns div's jQuery object
plugin('div', function() {
  return this.dom('div');
});

plugin('domBefore', function(name) {
	var result;
	this.each(function() {
		var el = this.parentNode.insertBefore(document.createElement(name), this);
		(result && $(result).add(el)) || (result = $(el));
	});
	return result;
});

plugin('domAfter', function(name) {
	var result;
	this.each(function() {
		var el = this.parentNode.insertBefore(document.createElement(name), this.nextSibling);
		(result && $(result).add(el)) || (result = $(el));
	});
	return result;
});

// adding form element requires that attributes be set before it is added
// to dom.  
plugin('form', function(name, attrs, label) {
	var result;
	var el;
	if (label && (label.position == 'before') && this[0]) {
		$(this).dom('span').addClass('label').html(label.text);
	}
	try {
		var elstr = '<' + name;
		for (var attr in attrs) {
			elstr += ' ' + attr + '="' + attrs[attr] + '"';
		}
		el = document.createElement(elstr);
	}
	catch(e) {
		el = document.createElement(name);
		for (var attr in attrs) {
			$(el).attr(attr, attrs[attr]);
		}
	}
	if (this[0]) {
		this.each(function() {
			this.appendChild(el);
			(result && $(result).add(el)) || (result = $(el));
		});
	}
	else {
		result = $(el);
	}
	if (label && (label.position == 'after') && this[0]) {
		$(this).dom('span').addClass('label').html(label.text);
	}
	return result;
});
// ***
// jquery.error-console.js

var global_error_console = {
	ERROR_CONSOLE_KEY: 'error_console',
	defaults: {
		title: "Errors"
	}
};

plugin('errorConsole', function(settings) {
	var g = global_error_console;
	var err = this.data(g.ERROR_CONSOLE_KEY);
	if (!err) {
		settings = $.extend({}, g.defaults, settings);
		err = new ErrorConsole(this[0]);
		this.data(g.ERROR_CONSOLE_KEY, err);
	}
	return err;
	
	function ErrorConsole(srcNode) {
	
		var errors = 0;
		var console;
	
		this.Add = function(errormsg) {
			++errors;
			if (!console) {
				console = $(srcNode).div().addClass("error-console").dom("ul");
			}
			console.dom("li").text(errormsg);
		}
		
		this.hasErrors = function() {
			return (errors > 0) ? true : false;
		}
		
		this.clear = function() {
			if (console) {
				$(console.parent()).remove();
				console = null;
			}
			errors = 0;
		}
	}
});

// ***
// jquery.info-box.js
// Provides ability to create popup areas to display information
// on mouse over of elements

/* 
Currently only implements the 'hover' event (mouseover).

The calling program is responsible for the creation of the info box via
the create callback.  The create callback is passed the event that triggered
the hover.  This plugin tracks all created info boxes and is mapped via the 
element id on which the event is set.  The calling program is responsible for
making sure that elements have an id attribute.

*/

var info_box_globals = {
	itemInfo: {},
	itemTimeoutId: 0,
	curInfo: null,
	default_settings: {
		// callback for creating the info box 
		// passes the element that the event is triggered on
		create: nop,
		useParentPosition: false,
		useParentClass: ""
	},
	infoBox: null
};
 
plugin("infoBox", function(id, settings) {
	var g = info_box_globals;
	var complete_settings = $.extend({}, g.default_settings, settings);
	g.infoBox = new infoBox(id, this, complete_settings);

	function infoBox(id, nodes, settings) {
		g.itemInfo[id] = {};
		nodes.hover(
			function() {
				var me = this;
				g.itemTimeoutId = window.setTimeout(function() {
					itemInfoBox(me, "open");
				}, 500);
			},
			function() {
				clearTimeout(g.itemTimeoutId);
				itemInfoBox(this, "close");
			}
		);

		
		function itemInfoBox(el, mode) {
			switch(mode) {
				case "open":
					if (!(g.itemInfo[id])[el.id]) {
						var box = settings.create(el);
						$(box).shadowInner('info-popup');
						(g.itemInfo[id])[el.id] = box;
					}
					g.curInfo = (g.itemInfo[id])[el.id];
					$(g.curInfo).show();
					adjustPosition(g.curInfo, el);
					break;
				case "close":
					if (g.curInfo) {
						$(g.curInfo).hide();
					}
					break;
			}
		};
		
		function adjustPosition(node, relatedNode) {
			//dimensions(node);
			var posNode = settings.useParentPosition
				? $(relatedNode).parents(settings.useParentClass+':first') : relatedNode;
			var rnPos = $(posNode).offset();
			$(node).css(
				{
					left: rnPos.left + 'px', 
					top: (rnPos.top+$(posNode).outerHeight()) + 'px'
				});

			var pos = $(node).offset(); 
			var nodeWidth = $(node).outerWidth(); 
			var nodeHeight = $(node).outerHeight();
	
			var windowRight = $(window).right();
			var windowBottom = $(window).bottom();
			
			//console.info('windowRight=' + windowRight + ' windowBottom=' + windowBottom);
			var right = pos.left + nodeWidth;
			if (right >= windowRight) {
				$(node).css({left: (windowRight - nodeWidth) + "px"});
			}
			
			var bottom = pos.top + nodeHeight;
			if (bottom >= windowBottom) {
				$(node).css({top: (windowBottom - nodeHeight) + "px"});
			}
			//console.info('right=' + right + ' bottom=' + bottom);
		};
		
		function dimensions(node) {
			console.info("node");
			var pos = $(node).position();
			console.info("position: left=" + pos.left + " top=" + pos.top);
			pos = $(node).offset();
			console.info("offset: left=" + pos.left + " top=" + pos.top);
			console.info("height=" + $(node).height() + " width=" + $(node).width());
			console.info("outerHeight=" + $(node).outerHeight() + " outerWidth=" + $(node).outerWidth());
			
			console.info("window");
			console.info("height=" + $(window).height() + " width=" + $(window).width());
			console.info("scrollLeft=" + $(window).scrollLeft() + " scrollTop=" + $(window).scrollTop());
		};
	};
});


// ***
// jquery.list-grid.js
// presents information as a list of items
// uses table for grid

// TODO(kwallace): add documentation

var list_grid_globals = {
  // global storage for all list grid instances
  all: {},
  load: nop,
  default_settings: {
	// callback for creating rendering for each item.  The rendering callback
	// is pagged a GOM object that has the methods needed for rendering.
    render: nop,
    showMonthName: false,
    className: 'list-grid'
  },
  CELL: ['<div class="cell">', '', '</div>']
}

plugin('listGrid', function(id, settings, params) {

	// abbreviate global scope
	var g = list_grid_globals;
	var g_cal = calendar_grid_globals;
	// assign unique id, if not specified
	var instance_id = guid(id);
	var instance = g.all[instance_id];
	if (!instance) {
	    
		// merge provided args with the defaults to complete missing (if any)
		// members
		var complete_settings = $.extend({}, g.default_settings, settings);
		var complete_params = $.extend({}, new DefaultParams(), params);
	    
		return instance = g.all[instance_id] = new ListGrid(instance_id, this, 
			complete_settings, complete_params);
	}
	else {
		instance.renderItems();
	}
	return instance;
	
	function DefaultParams() {
    
		// default range is:
		// * start == first day of the current month,
		// * end == last day of the current month
		g_cal.createMonthRange(new Date(), 1, this);
	}
	
	// list grid instance
	function ListGrid(id, node, settings, params) {
		var me = this;
		var rangeincrement = {mode: -1, monthSpan: 1, daySpan: 0}
		// generate initial calendar grid:
		var table = node.table('class="' + settings.className + '"');
		renderItems(params);
		
		this.renderItems = function() {
			renderItems(params);
		}
		
		function renderItems(params) {
			setRangeIncrement(params);
			rangeincrement.daySpan =  g_cal.getDaysSpan(params.start, params.end);
			// invoke load handler
			params.rangeincrement = rangeincrement;
			settings.load(params);
			// invoke render handler
			settings.render.call(new GOM(), params);
			// render initial view of the table
			table.render();
		}
		
		// Grid Object Model (GOM)
		// exposes usable structure of the list grid without revealing too much
		function GOM() {
			this.row = function() {
				table.row();
			}
			// provides access to the inside of a list grid cell
			this.cell = function(data, className) {
				g.CELL[1] = data;
				var c = table.cell(g.CELL.join(''), 
					className ? 'class="' + className + '"' : '');
			}
			
			this.render = function() {
				table.render();
			}
			
			this.clear = function(keepHead) {
				table.clear(keepHead);
			}
		}
		
		function setRangeIncrement(params) {
			if (!params.rangeincrement || (params.rangeincrement.mode == -1)) {
				if ((rangeincrement.monthSpan = g_cal.rangeCoversMonth(params)) > 0) {
					rangeincrement.mode = 0;
				}
				else { 
					rangeincrement.mode = 1;
				}
			}
			else {
				rangeincrement.mode = params.rangeincrement.mode;
			}
		}
		
		this.change = function(new_params) {
			if (new_params != null) {
				if (!isNaN(new_params)) {
					if (new_params != 0) {
						$.extend(params, slide(new_params));
					}
				}
				else {
					$.extend(params, new_params);
					setRangeIncrement(params);
				}
			}
			else {
				g_cal.createMonthRange(g_cal.zeroDate(new Date()), 1, params);
			}
      table.clear(true);
			renderItems(params);
			// move relative to the current range
			function slide(n) {
				if (rangeincrement.mode == 0) {
					// change by months
					var start = new Date(params.start.getTime());
					start.setMonth(start.getMonth() + (n*rangeincrement.monthSpan));
					return g_cal.createMonthRange(start, rangeincrement.monthSpan, {});
				} 
				// change by explicit range
				var start;
				var end;
				if (n > 0) {
					start = g_cal.zeroDate(params.end);
					start.setDate(start.getDate() + 1);
					end = new Date(start.getTime());
					end.setDate(end.getDate() + (rangeincrement.daySpan*n));
				}
				else {
					end = g_cal.zeroDate(params.start);
					end.setDate(end.getDate() - 1);
					start = new Date(end.getTime());
					start.setDate(start.getDate() + (rangeincrement.daySpan*n));
				}
				return { start: start, end: end };
			}
		}
		 
		// removes the table grid
		this.remove = function() {
			 $('table:first', node).remove();
			g.all[instance_id] = null;
		}
	}
});


// ***
// jquery.phrase.js
// provides inline sentence-based UI with dialog triggers

var global_phrase = {
  // default options
  defaults: {
    // called when the phrase is ready to load
    // * this -- builder
    load: nop,
    className: 'phrase',
    name: 'div'
  }
};

// TODO(dglazkov): consider refactoring to build HTML as a string, then
//                 using .append(html)
// TODO(dglazkov): add documentation
plugin('phrase', function(options) {
  
  var g = global_phrase;
  var opt = $.extend({}, g.defaults, options || {});
  
  var me = this;
  // where the node will be stored
  var n;

  // true when the sentence just started
  var first = true;
  
  opt.load.call({
    text: function(text, c) {
      space();
      var span = node().dom('span').addClass('text').html(text);
      if (c) span.addClass(c);
      return this;
    },
    spot: function(text, c, click) {
      space();
      init(node().dom('button').addClass('spot').html(text), c, click);
      return this;
    },
    link: function(text, url, c, click) {
      space();
      init(node().dom('a').attr('href', url).addClass('link').html(text), c,
           click);
      return this;
    },
    button: function(text, c, click) {
      space();
      init(node().dom('button').addClass('button')
           .html('<span><b></b>' + text + '</span>'), c, click);
      return this;
    },
    period: function() {
      node().dom('span').html('.');
      return this;
    },
    comma: function() {
      node().dom('span').html(',');
      return this;
    },
    semicolon: function() {
      node().dom('span').html(';');
      return this;
    },
    phrase: function(options) {
			space();
			var opt = $.extend({}, g.defaults, options || {});
			var area = $(node()).phrase(options);
			return area;
    },
    node: function(n) {
			node().append(n);
    }
	});
  
	return this;

  function node() {
    return n || (n = $(me).dom(opt.name).addClass(opt.className));
  };

  function space() {
    if (first) {
      first = false;
      return;
    }
    node().dom('span').addClass('space').html(' ');
  };

  function init(n, c, click) {
    click || typeof(c) == "function" && (click = c, c = null);
    c && n.addClass(c);
    click && n.click(function(e) {
      this.blur();
      click.apply(this, arguments);
      e.preventDefault();
    });
  };

});

// ***
// jquery.query-form-phrase.js
// provides UI for query form

var query_form_phrase = {
  // default options
  defaults: {
    // called when the phrase is ready to load
    load: nop,
    config: null,
    node: null,
    mode: false,
    range: true,
    reset: true,
    search: true,
    filter: true,
    gridId: 0
  }
};

// TODO(kew): add documentation
plugin('queryFormPhrase', function(options) {
  
  var g = query_form_phrase;
  var opt = $.extend({}, g.defaults, options || {});
  
  var defaults = {
		name: "div",
		className: 'phrase',
		phrase: null,
		node: null
	};
	
	var queryPhrase = {
		phrase: null,
		config: opt.config,
		reload: nop,
		gridId: opt.gridId
	};
	
	queryPhrase.state = new stateManager(queryPhrase, {
		node: opt.node,
		className: 'head'
	});
	queryPhrase.phrase = queryPhrase.state.Phrase;
	
	if (opt.mode) {
		queryPhrase.mode = new modeManager(queryPhrase, {
			phrase: queryPhrase.phrase,
			name: 'span'
		});
		queryPhrase.state.mode = queryPhrase.mode.state;
	}
	
	if (opt.range) {
		if (queryPhrase.config.range) {
			queryPhrase.range = new rangeManager(queryPhrase, {
				phrase: queryPhrase.phrase,
				name: 'span'
			});
			queryPhrase.state.range = queryPhrase.range.state;
			queryPhrase.state.increment = queryPhrase.range.increment.state;
		}
	}
	
	if (opt.reset) {
		queryPhrase.reset = new resetManager(queryPhrase, {
			phrase: queryPhrase.phrase,
			name: 'div'
		});
	}
	
	if (opt.search) {
		if (queryPhrase.config.search) {
			queryPhrase.search = new searchManager(queryPhrase, {
				phrase: queryPhrase.phrase,
				name: 'span'
			});
		}
	}
	
	if (opt.filter) {
		if (queryPhrase.config.filters) {
			queryPhrase.filters = new filterManager(queryPhrase, {
				phrase: queryPhrase.phrase,
				name: 'span'
			});
		}
	}
	
	return queryPhrase;
			
	function stateManager(view, options) {
		var me = this;
		options = $.extend({}, defaults, options);
		var holder = options.phrase ? options.phrase : options.node;
		var state;
		this.range = null;
		this.mode = null;
		this.increment = null;
		this.Phrase;
		
		var phrase = holder.phrase({
			name: options.name,
			className: options.className,
			load: function() {
				me.Phrase = this;
				this.phrase({
					load: function() {
						this.text('');
						state = this;
					}
				});
			}
		});
		
		this.render = function() {
			state.text('Show ' + view.config.renderer.title);
			if (me.range) {
				state.node(me.range);
			}
			if (me.increment) {
				state.node(me.increment);
			}
			if (me.mode) {
				state.text(' ');
				state.node(me.mode);
			}
			state.period();
		}
	}
	
	function resetManager(view, options) {
		me = this;
		this.Phrase;
		options = $.extend({}, defaults, options);
		var holder = options.phrase ? options.phrase : options.node;	
		var phrase = holder.phrase({
			name: options.name,
			load: function() {
				me.Phrase = this;
				this.text('Reset range to this');
				this.link('month', '#today', 'navigation', function() {
					view.gridChange();
				});
				var area = this.phrase({
					name: 'span',
					className: 'area-change-increment',
					load: function() {
						this.phrase({
							name: 'span',
							load: function() {
								this.text(', ');
								this.link('Multiples', '#increments1', 'm-change-increment',
									function() {
										view.range.increment.toggle(true);
									}
								);
							}
						});
						this.text(" or ");
						this.link('Day', '#increments', 'change-increment', 
							function() {
								view.range.increment.toggle(false);
							}
						).semicolon();
						
						view.range.increment.titleChange = $('.change-increment', phrase);
						view.range.increment.titleMonthsChange = $('.m-change-increment', phrase);
					}
				});
			}
		});
	}
	
	function modeManager(view, options) {
		var me = this;
		options = $.extend({}, defaults, options);
		var holder = options.phrase ? options.phrase : options.node;
		this.current = null;
		this.range = null
		this.titleChange = null;
		this.state = $('<span class="state-mode" />');
		this.Phrase;
		
		var phrase = holder.phrase({
			name: options.name,
			load: function() {
				me.Phrase = this;
				this.text('Change view to');
				this.link('View', '#view', 'change-view', function() {
					$.superGrid(opt.gridId).changemode({mode: view.config.mode});
				}).semicolon();
				me.titleChange = $('.change-view', phrase);
			}
		});
		
		this.update = function(mode) {
			if (mode == "calendar") {
				if (!view.viewedInGrid) {
					view.viewedInGrid = true;
					view.config.loadRequired = true;
				}
			}
			view.config.mode = mode;
			switch(view.config.mode) {
				case "calendar":
					me.state.html(' grid');
					me.titleChange.html('list');
					break;
				case "list":
					me.state.html(' list');
					me.titleChange.html('grid');
					break;
			}
		}
	}
	
	function searchManager(view, options) {
		var me = this;
		options = $.extend({}, defaults, options);
		var holder = options.phrase ? options.phrase : options.node;				
		var phrase = holder.phrase({
			name: options.name,
			load: function() {
				this.link('Search ' + view.config.renderer.title + '.', '#search', 'search');
				$(".search", phrase).searchPicker(
					{
						title: view.config.search.label,
						config: view.config,
						renderer: view.config.renderer,
						range: view.config.range,
						getParams: function() {
							return {config: view.config};
						}
					});
			}
		});
	}
	
	function filterManager(view, options) {
		var me = this;
		options = $.extend({}, defaults, options);
		var holder = options.phrase ? options.phrase : options.node;				
		var phrase = holder.phrase({
			name: options.name,
			load: function() {
				this.link('Filter Events.', '#filter', 'filter', 
					function() {
						$(".filter", phrase).categoryPicker(
							{
								title: 'Select Categories',
								inheritUp: false,
								inheritDown: true,
								categories: view.config.filters.selects,
								select: function(selected) {
									for (var key in selected) {
										view.config.filters.set(key, selected[key]);
									}
									view.reload();
								}
							});
					});
			}
		});
	}
	
	function rangeManager(view, options) {
		var me = this;
		options = $.extend({}, defaults, options);
		var holder = options.phrase ? options.phrase : options.node;
		this.current = {};
		this.picker = null;
		this.state = $('<span class="state-range" />');
		this.monthSpot = null;
		this.Phrase;
		
		this.increment = {
			toggle: function(multiples) {
				var r = {rangeincrement: {}};
				for (var key in me.current.rangeincrement) {
					r.rangeincrement[key] = me.current.rangeincrement[key];
				}
				for (var key in me.current) {
					if (!me.current[key].mode) {
						r[key] = me.current[key];
					}
				}
				r.rangeincrement.mode = 
					(r.rangeincrement.mode == 0) 
						? (multiples 
							? (r.rangeincrement.monthSpan > 1 
								? 1
								: -1)
							: (r.rangeincrement.monthSpan > 1 
								? -1
								: 1))
						: -1;
				if (r.rangeincrement.mode == -1) {
					r = calendar_grid_globals.createMonthRange(
						r.start, 
						(multiples ? r.rangeincrement.monthSpan : 1), 
						r)
				}
				view.gridChange(r);
			},
			state: $('<span class="state-increment" />'),
			titleChange: null,
			titleMonthsChange: null,
			updateTitle: function() {
				var monthspan = me.current.rangeincrement.monthSpan;
				if (monthspan == 0) monthspan = 1;
				var month = monthspan + " Month";
				var months = month + ((monthspan > 1) ? "s" : "");
				var dayspan = me.current.rangeincrement.daySpan + 1;
				var day = dayspan + " Day";
				var days = day + ((dayspan > 1) ? "s" : "");
				var cur = " as a ";
				if (me.current.rangeincrement.mode == 0) {
					cur += month;
					chg = days;
					this.titleMonthsChange.parent().hide();
				}
				else {
					cur += day;
					chg = months;
				}
				this.state.html(cur);
				if (monthspan > 1) {
					this.titleMonthsChange.html(chg);
					this.titleMonthsChange.parent().show();
					chg = "1 month";
				}	
				this.titleChange.html(chg);
			}
		}
		var phrase = holder.phrase({
			name: options.name,
			load: function() {
				me.Phrase = this;
				this.text('Change ');
				this.link('range', '#range', 'range', 
					function() {
						$(this).rangePicker('rangepicker', 
							{
								type: 'range',
								mode: 'dialog',
								refresh: function() {
									var range = {
											start: view.config.range.start.value.get(),
											end: view.config.range.end.value.get()
										};
									return range;
								},
								select: function(range) {
									view.gridChange(range);
								}
							});
						});
				this.text(' to ');
				this.link('previous', '#previous', 'navigation', function() {
					view.gridChange(-1);
				});
				this.text(' or ');
				this.link('next', '#next', 'navigation', function() {
					view.gridChange(1);
				});
				this.semicolon();
			}
		});
		
		function getTitle(start, end) {
			var title = "";
			if (start && !start.getYear) {
				start = new Date(Date.parse(start));
			}
			if (end && !end.getYear) {
				end = new Date(Date.parse(end));
			}
			if (!start && !end) {
				title = " across all dates ";
			}
			else if (!start) {
				title = " before " + 
					$.dates.format(end, "M/d/yyyy");
			}
			else if (!end) {
				title = " after " + 
					$.dates.format(start, "M/d/yyyy");
			}
			else {
				title = " between " + 
					$.dates.format(start, "M/d/yyyy") +
					" and " +
					$.dates.format(end, "M/d/yyyy");
			}
			return title;
		}
		this.update = function(rangeincrement, start, end, complete_start, complete_end) {
			if (((me.current.start != start) || (me.current.end != end)) 
						|| ((view.mode == 'calendar') && !view.config.range.extended )){
				view.config.loadRequired = true;
			}
			me.current.start = start;
			me.current.end = end;
			me.current.rangeincrement = rangeincrement;
			view.config.range.set(start, end, complete_start, complete_end);
			me.state.html(getTitle(start, end));
			me.increment.updateTitle();
		}
	}
});

// ***
// jquery.range-picker.js
/*
rangePicker plugin
Can be configured to be a range picker, date picker, or date/time picker
Settings:
load - function to call 
*/

var range_picker_globals = {
	range_default_settings: {
		change: nop,
		validation: nop,
		select: nop,
		refresh: nop,
		time: false,
		label: 'Select Date Range',
		type: 'range', // range | date 
		mode: 'dialog' // dialog | inline
		
	},
	picker_default_settings: {
		label: 'Select Date',
		time: false
	},
	pickertime_default_settings: {
		label: 'Select Date and Time',
		time: true
	}
}
 
plugin("rangePicker", function(id, settings) {
	var dp = range_picker_globals;
	var el_id = guid(id);
  var type = settings.type ? settings.type : dp.range_default_settings.type;
  var mode = settings.mode ? settings.mode : dp.range_default_settings.mode;
	return new Picker(this, type, mode, settings);
	
	function Picker(jnode, type, mode, settings) {
		var me = this;
		var picker;
		var width;
		switch (type) {
			case 'range':
				width =  (settings.time) ? '515px' : '385px';
				settings = $.extend({}, dp.range_default_settings, settings);
				break;
			case 'date':
				width = '200px';
				settings = $.extend({}, dp.picker_default_settings, settings);
				break;
			case 'datetime':
				width = '270px';
				settings = $.extend({}, dp.picker_default_settings, 
					dp.pickertime_default_settings, settings);
				break;
		}
		var id = guid();

		switch (mode) {
			case 'dialog':
					jnode.sheet({
						width: width,
						title: settings.title,
						load: function(done, manager) {
							var holder = this.div().addClass('range-picker-holder');
							switch (type) {
								case "range":
									picker = new rangePicker(holder, id, settings);
									break;
								default:
									picker = new datePicker(
										holder.div().addClass('date-picker'), id, settings);
									break;
							}
							holder.phrase({
								load: function() {
									this.button('CANCEL', function() {
										manager.close();
									});
									this.button('OK', 'ok', function(done) {
										manager.enabled(false);
										if (picker.validate()) {
											manager.close();
											settings.select(picker.data());
										}
									});
								}
							});
						},
            close: function() {
              settings.close && settings.close();
            },
            volatile: true,
						open: function() {
							if (settings.refresh) {
								var data = settings.refresh();
								if (data) {
									picker.refresh(data);
								}
							}
						}
				});
				break;
			default:
				var holder = jnode.div().addClass('range-picker-holder');
				switch (type) {
					case "range":
						picker = new rangePicker(holder, id, settings);
            if (settings.refresh) {
              var data = settings.refresh();
              if (data) {
                picker.refresh(data);
              }
            }
						break;
					default:
						picker = new datePicker(
							holder.div().addClass('date-picker'), id, settings);
						break;
				}
				break;
		}
	}

	function rangePicker(jnode, id, settings) {
		var me = this;
		var table = jnode.table('class="table-range-picker"');
		table.row();
		var startId = "range-start-" + id;
		var endId = "range-end-" + id;
		table.cell('<div id="' + startId + '" class="date-picker date-area" />');
		table.cell('<div id="' + endId + '" class="date-picker date-area" />');
		table.render();
		picker_settings = dp.picker_default_settings;
		picker_settings.time = settings.time;
		picker_settings.label = "Start";
		var startPicker = new datePicker($("#"+startId), 
			startId, picker_settings,
			null
			/*
			function(date) {
				console.info('startpicker-callback', date, endPicker.date.value);
				if (endPicker.date && endPicker.date.value) {
					if (date.getTime() > endPicker.date.value.getTime()) {
						endPicker.date.update(date, true, true, false);
					}
				}
			}
			*/
			);
		picker_settings.label = "End";
		var endPicker = new datePicker($("#"+endId), 
			endId, picker_settings,
			null
			/*
			function(date) {
				console.info('endpicker-callback', date, startPicker.date.value);
				if (startPicker.date && startPicker.date.value) {
					if (date.getTime() < startPicker.date.value.getTime()) {
						startPicker.date.update(date, true, true, false);
					}
				}
			}
			*/
			);
		this.data = function() {
			var r = {rangeincrement: {}};
			r.start = me.range.start();
			r.end = me.range.end();
			r.rangeincrement.mode = -1;
			return r;
		}
		this.range = { 
			start: function() { return startPicker.date.value; }, 
			end: function() { return endPicker.date.value; }
		};
		
		this.change = function(start, end) {
			startPicker.date.update(start, true, true);
			endPicker.date.update(end, true, true);
		}
		this.refresh = function(range) {
			me.change(range.start, range.end);
		}
		var errorConsole = jnode.errorConsole();
		
		this.validate = function() {
			errorConsole.clear();
			validateRange();
			if (errorConsole.hasErrors()) {
				return false;
			}
			return true;
		}
		
		function validateRange() {
			var startDate = startPicker.date.value;
			var endDate = endPicker.date.value;
			if (startDate > endDate) {
				errorConsole.Add("Start date > end date");
			}
		}
	}
	
	/*
	******************************************************************************
	* Date Picker
	******************************************************************************
	*/
	function datePicker(srcNode, id, settings, callback) {
		var me = this;
		var dateHolder = srcNode;
		var time;
		var label;
		
		if (settings.label) {
			srcNode.div().addClass("label").html(settings.label);
		}
		if (settings.time) {
			srcNode.addClass("date-time-area");
			label = {
				position: "before",
				text: "Date"
			};
		}
		else {
			srcNode.addClass("date-area");
		}
		
		var inputArea = dateHolder.div().addClass("inputs");
		var hintArea = dateHolder.div().addClass("hints");
		var buttonArea = dateHolder.div().addClass("buttons");
		
		var input = inputArea.form("input", 
			{
				type: "text", 
				name: "specificDate", 
				className: "date"
			},
			label);
		var hint = hintArea.div().addClass("hint");
		input.focus(function() {
			hintArea.show();
			validateDate(false);
		});
		input.blur(function() {
			validateDate(true);
			hintArea.hide();
		});
		input.keyup(function() {
			validateDate(false);
		});
		this.validate = function() {
			return true;
		}
		this.data = function() {
			return this.date.value;
		}
		this.refresh = function(date) {
			if (!date.getTime) {
				date = $.dates.parse(date);
			}
			me.date.update(date, true, true, false);
		}
		this.date = {
			value: null,
			update: function(date, changeGrid, selected, noTimeUpdate) {
				me.date.value = date;
				input.val($.dates.shortdate(me.date.value));
				title.text($.dates.format(date, 'MMM yyyy'));
				if (changeGrid) {
					var params = {};
					calendar_grid_globals.createMonthRange(date, 1, params);
					grid.change(params);
				}
				if (time && !noTimeUpdate) {
					time.update(me.date.value);
				}
				if (selected) {
					current(me.date.value);
				}
				//if (callback) callback(date);
			},
			updateTime: function(hours, minutes) {
				var updateGrid = false;
				cur = me.date.value;
				if (!cur) {
					cur = new Date();
					updateGrid = true;
				}
				cur.setHours(hours);
				cur.setMinutes(minutes);
				cur.setSeconds(0);
				me.date.update(cur, updateGrid, true, true);
			}
		};
		
		if (!settings.time) {
			buttonArea.pushButton("today", 
				{
					container: "none",
					className: "",
					mode: "unconnected",
					buttons: [
						{
							title: "Today",
							className: "today",
							selected: function() {
								me.date.update(calendar_grid_globals.zeroDate(new Date()), true, true);
							}
						}
					]
				});
		}
		
		hintArea.hide();
		dateHolder.append(inputArea).append(hintArea).append(buttonArea);
		
		if (settings.time) {
			time = new timePicker(srcNode, 
				{
					inputArea: inputArea,
					hintArea: hintArea,
					buttonArea: buttonArea,
					dateUpdate: me.date.update,
					timeUpdate: me.date.updateTime
				});
		}
		
		var title = $('<button type="button" class="title" />');
		title.click(function() {
			var yearPicker = dateHolder.data("YearMonthPicker");
			if (yearPicker) {
				yearPicker.show();
			}
			else {
				yearPicker = new yearMonthPicker(dateHolder, 
					pickerGrid,
					function(date) {
						change(date);
					}); 
				dateHolder.data("YearMonthPicker", yearPicker);
			}
		});
		
		var pickerGrid = dateHolder.div().addClass("date-picker-grid");
		
		pickerGrid.click(function() {
			title.focus();
			hintArea.hide();
		});
	
		pickerGrid.pushButton("navigation", 
			{
				container: "table",
				className: "navigation",
				mode: "unconnected",
				buttons: [
					{
						title: "",
						className: "previous",
						selected: function() {
							grid.change(-1);
						}
					},
					{
						title: title,
						name: "div",
						className: "title"
					},
					{
						title: "",
						className: "next",
						selected: function() {
							grid.change(1);
						}
					}
				]
			});
		
		var grid = pickerGrid.calendarGrid('calendar-'+id, {
			load: function(params) {
				//current(params.start, this);
				gridCells = this;
				title.text($.dates.format(params.start, 'MMM yyyy'));
				$(".calendar-grid .day", dateHolder).each(function() {
					$(this).hover(
						function() {
							$(this).parent().addClass("mouse-over");
						},
						function() {
							$(this).parent().removeClass("mouse-over");
						}
					);
				});
			},
			select: function(date) {
				//if (settings.validation) settings.validation(date);
				me.date.update(date, false, true);
			},
			labels: 'abbr'
		});
		
		var gridCells;
		var curCell;
		function current(date, gridObj) {
			if (gridObj) {
				gridCells = gridObj;
				date = me.date.value ? me.date.value : date;
			}
			if (curCell) {
				curCell.removeClass("current");
			}
			curCell = gridCells.cell(date);
			if (curCell) {
				curCell = curCell.parent().addClass("current");
			}
		}
		
		function change(date) {
			var params = {};
			calendar_grid_globals.createMonthRange(date, 1, params);
			grid.change(params);
		}
		
		function setInput(date) {
			if (date != null) {
				me.date.update(date, true, true, false);
			}
			else {
				input.val("");
			}
		}
		function setHint(value) {
			hint.html(value);
		}
		
		function validateDate(updateInput) {
			var result = parse(input.val());
			if (updateInput) {
				if (result) {
					setInput(result);
				}
				else {
					setInput();
				}
			}
			else {
					setHint(result 
						? gets(result)
						: ((input.val().length > 0) ? "is this a valid date?" : ""));
			}
			return true;
		}
		
		function gets(date) {
			var value = "";
			value = (date.getMonth() + 1)
				+ "/"
				+ date.getDate()
				+ "/"
				+ date.getFullYear();
			return value;
		}
		
		function parse(value) {
			var newDate = null;
			if (value.length == 0) {
				return null;
			}
			var match = value.match(/^\s*(\d+)\s*((\/)?\s*(\d+))?\s*((\/)?\s*(\d+)?)?\s*$/i);
			if (match) {
				newDate = new Date();
				var month = parseInt(match[1])-1;
				var day = match[4] ? parseInt(match[4]) : newDate.getDate();
				var year = match[7] ? parseInt(match[7]) : newDate.getFullYear();
				try {
					if (year > 99) {
						newDate.setFullYear(year, month, day);
					}
					else {
						newDate.setYear(year, month, day);
					}
					newDate.setHours(0);
					newDate.setMinutes(0);
					newDate.setSeconds(0);
				}
				catch (e) {
					newDate = null
				}
			}
			return newDate;
		}
	}
	
	/*
	******************************************************************************
	* Year/Month Picker
	******************************************************************************
	*/
	function yearMonthPicker(srcNode, posNode, select) {
		
		var offset = posNode.position();
		var holder = srcNode.parent().div().addClass('year-picker-holder');
		holder.css({
			position: "absolute",
			left: offset.left+"px",
			top: offset.top+"px"
			});
		holder.height(posNode.height());
		holder.width(posNode.width());
		var cells = {};
		var date = new Date();
		var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
		holder.pushButton("navigation", 
			{
				container: "table",
				className: "navigation",
				mode: "unconnected",
				buttons: [
					{
						title: "",
						className: "previous",
						selected: function() {
							date.setFullYear(date.getFullYear()-1);
							title.text(yearTitle(date));
						}
					},
					{
						title: yearTitle(date),
						//name: "div",
						className: "title",
						selected: function() {
							select(new Date(date.getFullYear(), 0, 1));
							close();
						}
					},
					{
						title: "",
						className: "next",
						selected: function() {
							date.setFullYear(date.getFullYear()+1);
							title.text(yearTitle(date));
						}
					}
				]
			});
		var title = $(".navigation .title", holder);
		var CELL = ['<div id="', 1, '" class="cell">', 3, '</div>'];
		var table = holder.table('class="year-picker-grid"');
		table.row();
		for (var i = 0; i < months.length; i++) {
			var cell_id = CELL[1] = guid();
			cells[cell_id] = i;

			CELL[3] = months[i];
			table.cell(CELL.join(''));
			if ((i % 3) == 2) {
				table.row();
			}
		}
		table.render();
		
		$(".year-picker-grid .cell", holder).each(function() {
			$(this).click(function(ev) {
				var month = cells[this.id];
				select(new Date(date.getFullYear(), month, 1));
				close();
			});
			$(this).hover(
				function() {
					$(this).parent().addClass("mouse-over");
				},
				function() {
					$(this).parent().removeClass("mouse-over");
				}
			);
		});
		this.show = function() {
			holder.fadeIn(200);
		}
		
		function close() {
			holder.fadeOut(200);
		}
		
		function yearTitle(date) {
			return $.dates.format(date, "yyyy");
		}
	}
	
	/*
	******************************************************************************
	* Time Picker
	******************************************************************************
	*/
	function timePicker(holder, options) {
		var me = this;
		var hint = null;
		var input = null;
		var specific = null;
		
		this.update = function(date) {
			set(date, true);
		}
			
		var input = options.inputArea.form("input", 
			{
				type: "text", 
				name: "specificTime", 
				className: "time"
			},
			{
				position: "after",
				text: "Time"
			});
		var hint = $('div:first', options.hintArea);
		input.focus(function() {
			options.hintArea.show();
			validateTime(false);
		});
		input.blur(function() {
			validateTime(true);
			options.hintArea.hide();
		});
		input.keyup(function() {
			validateTime(false);
		});
				
		var choices = options.buttonArea.pushButton("choices", 
			{
				container: "none",
				mode: "disconnected",
				buttons: [
					{
						title: "Now",
						className: "now",
						selected: function() {
							var now = new Date();
							if (options.dateUpdate) {
								options.dateUpdate(now, true, true);
							}
							set(now, true);
						}
					},
					{
						title: "Start of day",
						id: "start",
						className: "button start",
						selected: function() {
							setInput(0, 0, true);
						}
					},
					{
						title: "End of day",
						id: "end",
						className: "button end",
						selected: function() {
							setInput(23, 59, true);
						}
					}
				]
			});
		
		function setInput(hours, minutes, update) {
			if (hours != null) {
				if (options.timeUpdate && update) {
					options.timeUpdate(hours, minutes);
				}
				input.val(gets(hours, minutes));
			}
			else {
				input.val("");
			}
		}
		
		function setHint(value) {
			hint.html(value);
		}

		function validateTime(updateInput) {
			var result = parse(input.val());
			if (updateInput) {
				if (result) {
					setInput(result.hours, result.minutes, true);
				}
				else {
					setInput();
				}
			}
			else {
					setHint(result 
						? gets(result.hours, result.minutes)
						: ((input.val().length > 0) ? "is this a valid time?" : ""));
			}
			return true;
		}
			
		function set(time, update) {
			if (update || (input.val().length == 0)) {
				var sod = new Date(time.getFullYear(), time.getMonth(), time.getDate());
				var eod = (new Date(sod.getTime() + 86340000)).valueOf();
				var sod = sod.valueOf();
				var value = time.valueOf();
				var hours = time.getHours();
				var minutes = time.getMinutes();
				var seconds = time.getSeconds();
				setInput(hours, minutes, update);
			}
		}
		
		
		function gets(hours, minutes) {
			var value = "";
			var ampm = " AM";
			if (hours == 0) {
				value += "12"
			}
			else if (hours < 12) {
				value += hours;
			}
			else if (hours == 12) {
				value += hours;
				ampm = " PM";
			}
			else if (hours > 12) {
				value += (hours - 12);
				ampm = " PM"
			}
			value += ":";
			if (minutes < 9) {
				value += "0"
			}
			value += minutes + "";
			return value + ampm;
		}
		function parse(value) {
			if (value.length == 0) {
				return null;
			}
			var match = value.match(/^\s*(\d+)\s*(\:\s*(\d+))?\s*([a|p]m?)?\s*$/i);
			if (match) {
				var hours = parseInt(match[1]);
				var minutes = match[3] ? parseInt(match[3]) : 0;
				var ampm = match[4];
				if (!isNaN(hours) && hours < 25) {
					if (!isNaN(minutes)) {
						if (minutes < 60) {
							if (ampm) {
								var ispm = ampm.toUpperCase().charAt(0) == "P";
								if (ispm) {
									if (hours != 12) hours += 12;
								}
								else if (hours == 12) hours = 0;
								if (hours < 25) {
									return { hours: hours, minutes: minutes }
								}
							}
							else {
								return { hours: hours, minutes: minutes }
							}
						}
					}
				}
			}
			return null;
		}
	}
});

// ***
// jquery.renderer-article.js
// Publication renderer

var global_renderer_article = {
	DATA_KEY: 'renderer_article_data',
	
	renderer: {
		titleCapitalized: 'Article',
		title: 'article',
		className: 'article',
		selector: '.content ul:first',
		
		noFormRenderer: function(config) {
			$('#l-content').addClass(this.className);
			config.queryJNode.remove();
			$('.article .f-abstract:first').each(function() {
				$(this).addClass('float-abstract');
				$(this).shadow('f-abstract');
				/*
				$('.abstract', this).each(function() {
					
				});
				*/
			});
		}
	}
};

$.contentQuery('article', global_renderer_article.renderer);

// ***
// jquery.renderer-calendar.js
// Calendar renderer

var global_renderer_calendar = {
	DATA_KEY: 'renderer_calendar_data',
	
	renderer: {
		titleCapitalized: 'Events',
		title: 'events',
		className: 'calendar',
		selector: '.content ul:first',
		
		itemClearer: function(config) {
			config.items = [];
		},
		itemLoader: function(config, node) {
			var items = [];
			var itemMap = {};
			$('li', node).each(function() {
				var item = config.renderer.itemCreate.call($(this));
				items.push(item);
				itemMap[item.id] = item;
			});
			config.items = items;
			config.itemMap = itemMap;
		},
		// Called to render each item.
		// Parameters:
		// * this -- jQuery object of where the item should be renderered
		// * view -- either "calendar" or "list"
		// * item -- an opaque object, representing one item in the query
		item: function(view, item) {
			switch(view) {
				case "calendar":
					this.dom("span").addClass("time").html($.dates.format(item.date, "h:mma"));
					this.dom("span").addClass("title").html(item.title.innerHTML);
					break;
				case "list":
				default:
					this.cell(item.title.innerHTML, 'title ' 
						+ (item.className ? ' ' + item.className : ''));
					this.cell($.dates.format(item.date, "MMM dd, yyyy hh:mma"), 'start-date');
					this.cell($.dates.format(item.ends, "MMM dd, yyyy hh:mma"), 'end-date');
					this.cell(item.abstract, 'abstract');
					break;
			}
		},
		// Called when the content query config and renderer have been determined
		// by default, takes the items from markup.
		// Parameters:
		// * this -- jQuery object of the main ".content-query" node
		// * config -- the configuration object
		// * done -- the callback to call when loading is complete
		// return -- true, if the loading is asynchronous. If the loading is
		// asynchronous, the content query processing will suspend until the "done"
		// call back is called
		load: function(config, done) {
			if (!config.loadDisabled && config.loadRequired) {
				var me = this;
				config.loadRequired = false;
				$.ajah(config.postUrl(), null,
					function() {
						var eventList = $(me.selector, this);
						if (eventList) {
							config.renderer.itemLoader(config, eventList);
						}
						done();
					},
					function() {
						
					}
				);
			}
			else {
				done();
			}
		},
		// Called to create each item.
		// Parameters:
		// * this -- jQuery object of the corresponding item in the content query
		// return -- an opaque object, representing on item in the query
		itemCreate: function() {
			
			var node = $(this);
			var id = guid();
			var requiresEdit = false;
			// title
			var fieldNode = $("a.title:first", node);
			var title = document.createElement("div");
			fieldNode.clone().prependTo(title);
			// start date
			var date;
			fieldNode = $("span abbr.start-date", node);
			if (fieldNode.length == 0) {
					requiresEdit = true;
					date = new Date();
			}
			else {
				date = $.dates.parse($(fieldNode).attr("title"));
			}
			// ends date
			var ends;
			fieldNode = $("span abbr.end-date", node);
			if (fieldNode.length == 0) {
					requiresEdit = true;
					ends = new Date();
			}
			else {
				ends = $.dates.parse($(fieldNode).attr("title"));
			}
			// abstract
			fieldNode = $("p.abstract:first", node);
			var abstract = (fieldNode.length > 0) ? fieldNode.html() : "";
			
			var className;
			if (requiresEdit) {
				className = 'e-requires-edit';
			}
			
			return {
				id: id,
				className: className,
				title: title,
				date: date,
				ends: ends,
				abstract: abstract
			};
		},
		noFormRenderer: function(config) {
			config.loadDisabled = true;
			config.range.init();
			global_renderer_calendar.renderer.render.call(config.queryJNode, config);
		},
		// Called to create the overall presentation.
		// Parameters:
		// * this -- jQuery object of where the item should be rendered
		// * config -- the configuration object
		// * renderer -- the current renderer object
		// * items -- the initial set of items to render (this set is what was
		//            delivered with markup)
		//render: function(config, renderer, items) {
		render: function(config) {
			var g = global_renderer_calendar;
			var renderer = config.renderer;
			var node = this;
			//var gridId = config.key + guid();
			config.queryJNode.addClass(config.renderer.className);
			
			var view = $.queryFormPhrase(
				{
					config: config,
					node: node,
					mode: true,
					gridId: config.key + guid()
				});
			view.viewedInGrid = false;
			view.gridChange = function(range) {
					g.renderer.itemClearer(config);
					$.superGrid(view.gridId).change(range);
				}
			view.reload = function() {
				config.loadRequired = true;
				view.gridChange(0);
			}
			
			view.state.render();
			
			$(config.queryJNode).superGrid(view.gridId, {
				mode: "calendar",
				showMonthName: true,
				modeChange: view.mode.update,
				calLoad: function(params) {
					view.range.update(params.rangeincrement, params.start, params.end, params.completeRange.start, params.completeRange.end);
					var sgrid = this;
					renderer.load(config, function() {
						if (config.items) {
							$.each(config.items, function() {
								var diff = daysdiff(this.ends, this.date);
								if (diff > 0) {
									this.className += " e-multiple";
								}
								sgrid.push(this);
								if (diff > 0) {
									var saved = new Date();
									saved.setTime(this.date.getTime());
									for (var i = 1; i <= diff; i++) {
										if (i == diff) {
											this.date.setTime(this.ends.getTime());
										}
										else {
											this.date.setDate(this.date.getDate()+1);
										}
										sgrid.push(this);
									}
									this.date.setTime(saved.getTime());
								}
							});
						}
						sgrid.end();
						$(".e-item").infoBox('event',
							{
								//useParentPosition: true,
								//useParentClass: '.e-item',
								create: function(el) {
									var item = config.itemMap[el.id];
									if (item) {
										var box = document.createElement("div");
										box.className = "info-popup";
										$(el).append(box);
										$(box).append(
											'<div class="title">' + $(item.title).text() + '</div>'
											+ '<div class="times"><span class="label">Starts:</span>'
											+ '<span class="starts">'
											+ $.dates.format(item.date, "M/d/yyyy h:mma")
											+ '</span><span class="label">Ends:</span>'
											+ '<span class="ends">'
											+ $.dates.format(item.ends, "M/d/yyyy h:mma")
											+ '</span></div><div class="abstract">'
											+ item.abstract + '</div>'
										);
										return box;
									}
									return null;
								}
							}
						);
					});
				},
				calRender: function(data) {
					renderer.item.call(this, "calendar", data);
				},
				listLoad: function(params) {
					view.range.update(params.rangeincrement, params.start, params.end);
				},
				listRender: function(data, end) {
					var listgrid = this;
					config.mode = "list";
					renderer.load(config, function() {
						listgrid.row();
						$.each(config.items, function() {
							renderer.item.call(listgrid, "list", this);
							listgrid.row();
						});
						listgrid.render();
						if (end) {
						end();
					}
					});
					
				}
			});
			
			function daysdiff(date1, date2) {
				// The number of milliseconds in one day
				var ONE_DAY = 1000 * 60 * 60 * 24;
				// Convert both dates to milliseconds
				var date1_ms = date1.getTime();
				var date2_ms = date2.getTime();
				// Calculate the difference in milliseconds
				var difference_ms = Math.abs(date1_ms - date2_ms);
				// Convert back to days and return
				return Math.floor(difference_ms/ONE_DAY);
			}	
		}
	}
};

$.contentQuery('events', global_renderer_calendar.renderer);

// ***
// jquery.renderer-event.js
// event renderer

var global_renderer_event = {
	DATA_KEY: 'renderer_event_data',
	
	renderer: {
		titleCapitalized: 'Event',
		title: 'event',
		className: 'event',
				
		noFormRenderer: function(config) {
			var gcal = calendar_grid_globals;
			$('#l-content').addClass(this.className);
			config.queryJNode.remove();
			$('#l-content .range').each(function() {
				var ids = $('[id]', this);
				$(this).empty();
				$(this).append(ids);
				var from, to;
				var jFrom = $('abbr.from', this);
				if (jFrom.length > 0) {
					from = $.dates.parse(jFrom.attr('title'));
				}
				var jTo = $('abbr.to', this);
				if (jTo.length > 0) {
					to = $.dates.parse(jTo.attr('title'));
				}
				$('abbr', this).remove();
				var datestr;
				if (from && to) {
					if (from.getTime() == to.getTime()) {
						datestr = $.dates.format(from, "EE, MMM dd, yyyy")
						+ " @ "
						+ $.dates.format(from, "h:mm A");
					}
					else {
						var f = gcal.zeroDate(from);
						var t = gcal.zeroDate(to);
						if (f.getTime() == t.getTime()) {
							datestr = $.dates.format(f, "EE, MMM dd, yyyy")
								+ " @ "
								+ $.dates.format(from, "h:mm A")
								+ " - "
								+ $.dates.format(to, "h:mm A");
						}
						else {
							datestr = $.dates.format(from, "EE, MMM dd, yyyy h:mm A")
								+ " - "
								+ $.dates.format(to, "EE, MMM dd, yyyy h:mm A");
						}
					}
					$('.from', this).addClass('date-formatted').text(datestr);
				}
				//$.authoring.content.position();
				
				// if main content, put abstract right above it
				$('#l-content .main').each(function() {
					var ab = $('#l-content .abstract');
					$(this).before(ab);
					ab.shadow('abstract')
				});
			});
		}
	}
};

$.contentQuery('event', global_renderer_event.renderer);

// ***
// jquery.renderer-publication.js
// Publication renderer

var global_renderer_publication = {
	DATA_KEY: 'renderer_publication_data',
	
	renderer: {
		titleCapitalized: 'Articles',
		title: 'articles',
		className: 'publication',
		selector: '.content ul:first',
		
		itemClearer: function(config) {
			config.items = [];
		},
		itemLoader: function(config, node) {
			var items = [];
			$('li', node).each(function() {
				var item = $(this);
				items.push(item);
			});
			config.items = items;
		},
		item: function(view, item) {
			switch(view) {
				case "list":
				default:
					this.cell(item.title.innerHTML, 'title ' 
						+ (item.className ? ' ' + item.className : ''));
					this.cell(item.abstract, 'abstract');
					this.cell($.dates.format(item.releasedate, "MMM dd, yyyy hh:mma"), 'releasedate');
					break;
			}
		},
		itemCreate: function() {
			var node = $(this);
			var id = guid();
			var requiresEdit = false;
			// title
			var fieldNode = $("a.title:first", node);
			var title = document.createElement("div");
			fieldNode.clone().prependTo(title);
			// abstract
			fieldNode = $("p.abstract:first", node);
			var abstract = (fieldNode.length > 0) ? fieldNode.html() : "";
			// release date
			var releasedate;
			fieldNode = $("span abbr.releasedate", node);
			if (fieldNode.length == 0) {
					requiresEdit = true;
					releasedate = new Date();
			}
			else {
				releasedate = $.dates.parse($(fieldNode).attr("title"));
			}
			
			return {
				title: title,
				abstract: abstract,
				releasedate: releasedate
			};
		},
		// Called when the content query config and renderer have been determined
		// by default, takes the items from markup.
		// Parameters:
		// * this -- jQuery object of the main ".content-query" node
		// * config -- the configuration object
		// * done -- the callback to call when loading is complete
		// return -- true, if the loading is asynchronous. If the loading is
		// asynchronous, the content query processing will suspend until the "done"
		// call back is called
		load: function(config, done) {
			if (!config.loadDisabled && config.loadRequired) {
				var me = this;
				config.loadRequired = false;
				$.ajah(config.postUrl(), null,
					function() {
						var list = $(me.selector, this);
						if (list) {
							config.renderer.itemLoader(config, list);
						}
						done();
					},
					function() {
						
					}
				);
			}
			else {
				done();
			}
		},
		// Called to create the overall presentation.
		// Parameters:
		// * config -- the configuration object
		render: function(config) {
			var g = global_renderer_calendar;
			var renderer = config.renderer;
			var node = this;
			var grid;
			var gridId = config.key + guid();
			config.queryJNode.addClass(config.renderer.className);
			
			var view = $.queryFormPhrase(
				{
					config: config,
					node: node
				});
				
			view.state.mode = 'list';
				
			view.gridChange = function(range) {
					g.renderer.itemClearer(config);
					grid.change(range);
				}
			
			view.state.render();
			grid = config.queryJNode.listGrid(gridId, 
				{
					load: function(params) {
						view.range.update(params.rangeincrement, params.start, params.end);
					},
					render: function(params) {
						var tbl = this;
						spinner = config.queryJNode.spinner({text: "Loading..."});
						renderer.load(config, function() {
							tbl.row();
							$.each(config.items, function() {
								tbl.cell(this.html());
								tbl.row();
							});
							tbl.render();
							spinner.remove();
						});
					}
				});
		},
		noFormRenderer: function(config) {
			config.loadDisabled = true;
		}
	}
};

$.contentQuery('articles', global_renderer_publication.renderer);

// ***
// jquery.search-picker.js
// based on sheet, listGrid and spinner, provides a search dialog box

var global_search = {
	SEARCH_KEY: 'search_sheet',
	defaults: {
		title: "Search",
		renderer: null,
		range: null,
		getParams: null
	},
	params: {
		config: null
	}
};

// TODO(dglazkov): add documentation
plugin('searchPicker', function(settings) {
	var g = global_search;
	settings = $.extend({}, g.defaults, settings);
	var search = $(this).data(g.SEARCH_KEY);
	if (!search) {
		search = new searchSheet($(this), settings); 
	}
	
	var rangeButtons;
	var searchResults;
	var searchGom;
	var errorConsole;
	var searchText;
	var eventList;
	
	function searchSheet(trigger, settings) {
		$(trigger).click(function() {
			$(trigger).sheet({
				width: "650px",
				title: settings.title,
				load: function() {
					g.params.config = settings.config;
					searchHandler(this, settings);
				},
				open: function() {
					if (settings.getParams) {
						g.params = settings.getParams();
						if (rangeButtons) {
							rangeButtons.updateButtonText('userange', rangeButtonTitle());
						}
					}
				},
				ok: function() {
				},
				close: function() {
					searchClear(true);
				}
			});
		});
	};
	
	function rangeButtonTitle() {
		return "between " + g.params.config.range.start.value.toString("M/d/yyyy")
				+ " and " + g.params.config.range.end.value.toString("M/d/yyyy");
	};
	
	function searchHandler(searchArea, settings) {
		searchArea.addClass('search-picker');
		var getter = searchArea.div().addClass("searchGetter");
		getter.div().addClass("words").text("Search for ");
		searchText = getter.form("input", {type: "text", name: "searchText"});
		getter.div().addClass("words").text(" in ");
		var useRange = true;
		if (g.params.config.range) {
			rangeButtons = $(getter).pushButton("range", 
				{
					buttons: [
						{
							on: true,
							id: 'userange',
							title: rangeButtonTitle(),
							selected: function() {
								useRange = true;
							}
						},
						{	
							id: 'useall',
							title: "all " + settings.renderer.title,
							selected: function() {
								useRange = false;
							}
						}
					]
				});
		}
		else {
			getter.div().addClass("all").text("all " + settings.renderer.title);
		}
		errorConsole = $(getter).errorConsole();
		var submit = getter.dom("button")
			.attr("name", "submit")
			.text("Search");
		$(submit).click(function() {
			submitSearch(searchArea, settings, useRange);
		});
		
		getter.div().addClass("float-clear");
		searchResults = searchArea.div().addClass("searchResults");
	};
	
	function searchClear(clearInput) {
		if (searchGom) {
			searchGom.clear(true);
			errorConsole.clear();
			if (clearInput) searchText.val('');
		}
	}
	
	function submitSearch(searchArea, settings, useRange) {
		searchClear(false);
		var searchVal = searchText.val();
		if (!searchVal || searchVal.length == 0) {
			errorConsole.Add("Enter search value");
		}
		if (errorConsole.hasErrors()) {
			return;
		}
		var config = g.params.config;
		var renderer = settings.renderer;
		config.search.value.set(searchVal);
		searchResults.spinner({text: "Searching..."});
		$.ajah(config.postUrl({range: useRange, search: true}), null,
			function() {
				eventList = $(renderer.selector, this);
				if (eventList && eventList.length > 0) {
					searchResults.listGrid('searchresults', 
						{
							load: function(params) {
							},
							render: function(params) {
								if (!searchGom) {
									searchGom = this;
									searchGom.row();
								}
								$('li', eventList).each(function() {
									renderer.item.call(searchGom, "list", renderer.itemCreate.call($(this)));
									searchGom.row();
								});
								
								searchGom.render();
								searchResults.spinner().remove();
							}
						});
				}
				else {
					searchResults.spinner().remove();
					errorConsole.Add("No events found matching search criteria");
				}
			}
		);
	};
});
// ***
// jquery.sheet.js
// represents a modal, draggable dialog box. Only one instance of the box exists
// the content in the box is placed into slots, and each slot is activated by
// a trigger: a DOM node that calls sheet.

// only one slot can be visible at a time. On each opening of the sheet, the
// inactive sheets are hidden, and the active sheet is made visible.

    
// TODO(dglazkov): hook up to the resize event to make sure overlay always
//                 covers the entire page
// TODO(dglazkov): implement resizeable dialog box
// TODO(dglazkov): make title shared across sheets, and then make it the
//                 handle for dragging (eliminates the problem with the
//                 scrollbars)
// TODO(dglazkov): finish implementing manager logic: loading, open


// node.sheet(options) is the only valid call for this plugin. Each call does
// the following:
// * identifies the trigger node, or the node at which the 'sheet' method was
//   called
// * if this is a first-time call, remembers the trigger and allocates a slot
//   for it. Then, calls 'load' hook. The method is be used to populate the
//   sheet.
// * otherwise, opens the slot for the trigger.
// * calls 'open' hook
// * if the user closes the sheet by clicking on 'ok' button, calls 'ok' hook
// * if the user closes the sheet by clicking on 'close' button, calls 'cancel'
//   hook


var global_sheet = {
  animate: 100,
  SHEET_KEY: 'sheet',
  defaults: {
    // default width of the dialog box
    width: 300,
    // estimated height of the dialog box
    height: 300,
    // called when the slot is loading
    // * this -- slot jQuery node
    // * end -- load completion callback.
    // * manager -- a manager object, which has the following methods:
    //    * close() -- closes the sheet
    //    * loading(on) -- if on=true, sets the sheet into "loading"
    //                         state, otherwise, returns back to normal state
    //    * enable(on) - if on=true, enables the bay area of the sheet,
    //                   otherwise disables the bay are of the sheet. The sheet
    //                   is always re-enabled on open.
    //    * open() -- opens the sheet
    // * returns 'false' (or nothing), if the loading is synchronous. If the
    //   loading is asynchronous, the plugin does not consider it being complete
    //   until the 'end' is called
    load: nop,
    // called when dialog box is opened
    open: nop,
    // called when dialog box is closed
    // * this -- slot jQuery node
    // * waiting -- true, if the slot was still loading when the dialog box
    //   closed
    close: nop,
    // called when dialog box is enabled or disabled
    // * this -- slot jQuery node
    // * on -- true, if it's being disabled, false otherwise
    enable: nop,
    // called when the dialog box is closed
    cancel: nop,
    // called when the 'Ok' button is clicked
    ok: nop,
    // specifies whether to hide the "ok" button
    passive: false,
    // if true, the load hook is called every time the sheet opens
    // for this trigger. In other words, the slot is reloaded on each open
    volatile: false
  }
};

// * options -- sheet options. See 'global_sheet.defaults' for detailed
//   description of all options
plugin('sheet', function(options) {
  var g = global_sheet;
  
  if (options == 'close') {
    return;
  }
  
  options = $.extend({}, g.defaults, options || {});
  
  // retrieve UI primitives or create, if don't exist
  // the semi-transparent overlay that covers the entire page to
  // make the dialog modal
  var overlay = g.overlay || (g.overlay = $(document.body).div()
    .css({ top: '0', left: '0', position: 'absolute', display: 'none' })
    .attr('id', 'sheet-overlay-ui'));

  // the actual dialog box
  var dialog = g.dialog || (g.dialog = $(document.body).div()
    .css({ position: 'absolute', display: 'none', top: 0, left: 0})
    .attr('id', 'sheet-ui').draggable());  

  // set width (always)
  dialog.css({ width: options.width || 300 });

  var trigger = $(this);
  
  // immediately remove focus from trigger
  trigger.blur();
  
  // sheet object -- all information needed for using the sheet
  var sheet = trigger.data(g.SHEET_KEY)
    || (sheet = allocate(dialog), trigger.data(g.SHEET_KEY, sheet), sheet);
  
  // initalize sheet
  if (!sheet.initialized) {
    // perform one-time initialization things
    sheet.title().html(options.title || trigger.text());
    sheet.close().click(function(evt) {
      this.blur();
      close();
      evt.preventDefault();
    });
    sheet.initialized = true;
  }
  
  if (!sheet.loaded) {
    load() && open();
    options.volatile || (sheet.loaded = true);
  } else {
    options.enable.call(sheet.slot(), true);
    open();
  }
  
  // attempts to load (synchronously or asynchronously) the contents of the
  // requested slot
  function load() {
    var slot = sheet.slot();
    // Empty slot right away. This is relevant when the slot is volatile
    slot.empty();
    // invoke load hook with the completion callback
    if (options.load.call(slot, function() {
      var w = waiting();
      // clear wait key
      waiting(0);
      // hide spinner
      sheet.spinner().stop().hide();
      if (!w) {
        // this is actually a synchronous execution
        sheet.loaded = true;
      }
      w < 2 && open();
    }, {
      open: nop,
      close: function() {
        close();
      },
      loading: nop,
      enabled: function(on) {
        options.enable.call(slot, on);
      }
    })) {
      if (sheet.loaded) {
        // synchronous loading with callback
        return true;
      }
      // asynchronous loading, will wait for "complete" callback
      waiting(1);
      open();
      // not loaded, advise waiting for callback to complete
      return false;
    }
    // synchronous loading, done
    return true;
  };
  
  // reads or sets the WAIT_KEY. Valid values:
  // * 0 -- no waiting, everything has loaded
  // * 1 -- waiting for loading to complete, the dialog is open
  // * 2 -- waiting for loading to complete, the dialog is closed
  function waiting(value) {
    if (!arguments.length) {
      return sheet.waiting || 0;
    }
    sheet.waiting = value;
    return value;
  };

  // actually opens the dialog box, makes requested slot visible
  // also sets up overlay
  function open() {
    var w = waiting();
    // step back to waiting=1 so that "open" is called by the async callback
    w > 1 && waiting(1);
    var subject = w ? sheet.spinner() : sheet.bay();
    // cover the entire page
    // open dialog box and requested slot
    overlay.css({ 'z-index': max_z(), width: width(), height: height()})
      .add(dialog.css(position())).add(sheet.holder).show();
    subject.show(g.animate, function() {
      // when done animating, invoke open hook
      w || options.open.call(sheet.slot());
      w = 1;
    });
  };
  
  // creates dialog position CSS object
  function position() {
    var pos = trigger.offset();
    var maxw = document.documentElement.scrollWidth;
    if (pos.left + options.width > maxw) {
      pos.left = Math.max(0, maxw - options.width - 20);
    }
    var maxh = document.documentElement.scrollHeight;
    if (pos.top + options.height > maxh) {
      pos.top = Math.max(0, maxh - options.height - 20);
    }
    return { 'z-index': max_z(), top: pos.top, left: pos.left };
  };
  
  // closes the dialog
  function close() {
    var w = waiting();
    // console.debug('close', trigger, w);
    if (w) {
      w = waiting(2);
    }
    dialog.fadeOut(g.animate, function() {
      sheet.holder.add(overlay).hide();
    });
    options.close.call(trigger, w);
  };
  
  // width and height taken directly from jquery/ui/ui.dialog.js
  // why work when you can steal?
  function height() {
    if ($.browser.msie && $.browser.version < 7) {
      var scrollHeight = Math.max(document.documentElement.scrollHeight,
                                  document.body.scrollHeight);
      var offsetHeight = Math.max(document.documentElement.offsetHeight,
                                  document.body.offsetHeight);
      
      if (scrollHeight < offsetHeight) {
        return $(window).height() + 'px';
      } else {
        return scrollHeight + 'px';
      }
    } else {
      return $(document).height() + 'px';
    }
  };
  
  function width() {
    if ($.browser.msie && $.browser.version < 7) {
      var scrollWidth = Math.max(
        document.documentElement.scrollWidth, document.body.scrollWidth);
      var offsetWidth = Math.max(
        document.documentElement.offsetWidth, document.body.offsetWidth);
      
      if (scrollWidth < offsetWidth) {
        return $(window).width() + 'px';
      } else {
        return scrollWidth + 'px';
      }
    } else {
      return $(document).width() + 'px';
    }
  };

  // allocates a new holder and supporting chrome
  function allocate(ui, trigger) {
    var holder = $(ui).div().hide().addClass('holder').html('<div class="top"><div class="close"><a href="#close">Close</a></div><div class="title"></div><div class="sill"></div></div><div class="middle"><div class="spinner" style="display:none;"></div><div class="bay" style="display:none"><div class="slot"></div></div></div><div class="bottom"><div class="handle"></div><div class="spacer"></div><div class="sill"></div></div>');
    return {
      holder: holder,
      trigger: trigger,
      close: function() {
        var close = holder.find('.top .close a');
        this.close = function() { return close; };
        return close;
      },
      slot: function() {
        var slot = holder.find('.slot');
        this.slot = function() { return slot; };
        return slot;
      },
      bay: function() {
        var bay = holder.find('.bay');
        this.bay = function() { return bay; };
        return bay;
      },
      title: function() {
        var title = holder.find('.title');
        this.title = function() { return title; };
        return title;
      },
      spinner: function() {
        var spinner = holder.find('.spinner');
        this.spinner = function() { return spinner; };
        return spinner;
      },
      handle: function() {
        var hanlde = holder.find('.handle');
        this.handle = function() { return handle; };
        return handle;
      }
    };
  };
});
// ***
// jquery.spinner.js
/*
================================================================================
jquery.spinner.js
Provides "in progress" indicators for instances where a page is waiting on
some other operation.  Good for ajax operations.  

================================================================================
*/
var spinner_globals = {
	id: 'spinner_data'
};

plugin('spinner', function(settings) {
	var g = spinner_globals;
	var settings_defaults = {
		cache: false,
		callbacks: null,
		text: ""
	};
	// set defaults for non-specified options
	var settings = $.extend(settings_defaults, settings);
	var instance = this.data(g.id);
	if (!instance) {
		instance = new Spinner($(this.get(0)), settings.text);
		this.data(g.id, instance);
	}
	return instance;
	
	function Spinner(node, text) {
		var spinnerHolder = node.domBefore('div')
			.addClass('e-loading')
			.css({width: node.css('innerWidth'), height: node.css('innerHeight')});
		spinnerHolder.div().html(text);
		node.hide();
		spinnerHolder.fadeIn(200);
		
		this.remove = function() {
			node.removeData(g.id);
			spinnerHolder.remove();
			node.fadeIn(200);
		};
	};
});




// ***
// jquery.storage.js
// handles persistent storage
// has two members:
// * session -- represents data, persisted throughout the browser session
// * user -- data, persisted for this user across sessions
// both members have the same three methods:
// * save(name, value) -- associates supplied 'value' and 'name' and stores
//                        them
// * load(name) -- returns a value (or nothing, if doesn't exist), associated
//                 with the supplied 'name'
// * remove(name) -- removes a name/value pair from the storage


var persistence_globals = {
  // this is where Persistence instance is stored
  instance: null,
  user_key: '~~eu',
	session_key: '~~es',
  expires: function() {
		var now = new Date();
		now.setDate(now.getDate() + 30);
		return '; expires=' + now.toUTCString() + ';';
	}(),
  delete_s: '=; expires=Thu, 13 Jul 1972 02:11:00 GMT',
	path: ';path=/;' 
};

plugin('storage', function() {
  
  var g = persistence_globals;
  return (g.instance = new Persistence());
  
	function Persistence() {
		var u = new Storage(g.user_key);
		var s = new Storage(g.session_key);
		var c = 0;
    
    this.user = function(name, value) {
      if (has_value(value)) {
        return save_value(u, name, value);
      }
      return load_value(u, name);
    };
    
    this.session = function(name, value) {
      if (has_value(value)) {
        return save_value(s, name, value);
      }
      return load_value(s, name);
    };

		parse(document.cookie, ';', '=', function(k, v) {
      // parse either and increment success counter
      ((k == g.user_key && load(k, v, u))
      || (k == g.session_key && load(k, v, s))) && c++;
      // make sure to stop parsing after both 
      return c > 1;
		});
    
    function has_value(value) {
      return value || typeof(value) == 'boolean';
    };
    
    function load_value(where, name) {
      return where[name];
    };
    
    function save_value(where, name, value) {
      where[name] = value;
      where.save();
      return value;
    };
    
		function parse(d, d1, d2, action) {
			var pairs = d.split(d1);
			for(var i = 0; i < pairs.length; i++) {
				var pair = pairs[i].split(d2);
				if (pair.length == 2) {
					if (action(trim(pair[0]), trim(pair[1]))) break;
				}
			}
			return true;
		};
		
		function load(k, v, o) {
			return parse(v, '|', '~', function(k, v) { o[k] = v;});
		};
	
		function trim(s) {
			return s.replace(/^\s+|\s+$/g, '');
		};
  }
	
	
	function remove(key) {
		if (!key) {
			remove(g.user_key);
			remove(g.session_key);
			return;
		}
		document.cookie = key + g.delete_s;
	};
		
	function Storage(key) {
    
    this.save = function() {
      var a = [];
      for(var k in this) {
        var v = this[k];
        var t = typeof(v);
        if (t == 'string'
            || t == 'number'
            || t == 'boolean'
            || t == 'datetime') {
          a.push(k + '~' + v);
        }
      }
      document.cookie = key + '=' + a.join('|') +
        (key == g.user_key ? g.expires : '') + g.path;
    };
	};
	
}(), true);
// ***
// jquery.super-grid.js
// Combines calendarGrid, listGrid, and spinner into a switchable presentation
// of data, making it viewable as a calendar grid or a list grid

// TODO(dglazkov): add documentation

var super_grid_globals = {
	all: {},
	default_settings: {
		// valid modes: calendar|list
		mode: "calendar",
		// called when the grid loads (initially or as a result of setting
		// changing)
		// * this -- Grid Object Model (GOM), an object that encapsulates
		//   manipulation of the grid
		// * params -- parameters of the grid (either defaults or passed via change
		//   invocation)
		calLoad: nop,
		calRender: nop,
		listLoad: nop,
		listRender: nop,
		showMonthName: true,
		modeChange: nop
	}
 }
 
plugin("superGrid", function(id, settings) {
	var g = super_grid_globals;
	// assign unique id, if not specified
	id = guid(id);
	var instance = g.all[id];
	if (!instance) {
		 var complete_settings = $.extend({}, g.default_settings, settings);
		instance = g.all[id] = new superGrid(this, complete_settings);
	}
	return instance;

	function superGrid(jnode, complete_settings) {
		var me = this;
		this.settings = complete_settings;
		var paramsInUse;
		var spinner;
		var cal;
		var list;
		var cur;
		var gom;
		
		createGrid(false, false);
				
		function createGrid(loadNeeded, modeChanged) {
			switch (me.settings.mode) {
				case "list":
					if (!list) {
						list = new grid(jnode);
						list.grid = list.container.listGrid('calendar', 
							{
								load: function(params) {
									modeChanged = true;
									paramsInUse = params;
									settings.listLoad.call(this, params);
								},
								render: function(params) {
									spinner = list.container.spinner({text: "Loading..."});
									settings.listRender.call(this, params, 
										function() {
											spinner.remove();
										}
									);
									
								}
							},
							paramsInUse);
						list.node = list.container.find("table");
					}
					cur = list;
					break;
				case "calendar":
				default:
					if (!cal) {
						cal = new grid(jnode);
						
						cal.grid = cal.container.calendarGrid('calendar', 
							{
								// load is called in context of the calenderGrid GOM
								// so 'this' is the calendarGrid GOM
								load: function(params) {
									modeChanged = true;
									paramsInUse = params;
									cal.node = cal.container.find("table");
									spinner = cal.node.spinner({text: "Loading..."});
									gom = new GOM(me, this);
									settings.calLoad.call(gom, params);
								},
								select: function(date, invalid) {
								},
								labels: "full",
								showMonthName: me.settings.showMonthName
							},
							paramsInUse);
					}
					else if (loadNeeded) {
						cal.grid.change(paramsInUse);
					}
					cur = cal;
					break;
			}
			cur.container.show();
			if (modeChanged) {
				me.settings.modeChange(me.settings.mode);
			}
		}
		
		function grid(jnode) {
			this.grid;
			this.node;
			this.container = $('<div class="gridcontainer" />').appendTo(jnode);
		}
		
		this.change = function(params) {
			cur.grid.change(params);
		}
		
		this.changemode = function(params) {
			cur.container.hide();
			switch(me.settings.mode) {
				case "calendar":
					this.settings.mode = "list";
					break;
				case "list":
					this.settings.mode = "calendar";
					break;
			}
			createGrid((params.mode == "list") && (this.settings.mode == "calendar"), true);
		}
		
		function GOM(sgrid, cgom) {
			var supergrid = sgrid;
			var calgom = cgom;
			
			this.push = function(data) {
				var cell = calgom.cell(data.date);
				if (cell) {
					var eh = $('<div></div>');
					if (data.id) {
						eh.attr('id', data.id);
					}
					eh.addClass('e-item');
					if (data.className) {
						eh.addClass(data.className);
					}
					cell.append(eh);
					supergrid.settings.calRender.call($(eh), data);
				}
			}
			
			this.end = function() {
				spinner.remove();
			}
		}
	}
});

// ***
// jquery.table.js
// a handy table creator. Generates HTML markup for a table
// When instantiated, automatically creates a thead row, assuming that all
// cells are going to be th. If you want to skip the head row, call .row()
// immediately after creating an instance

// TODO(dglazkov): add remove() method, which allows to remove top or bottom X
// rows

// table templates. HTML construction is done by joining arrays, which is
// faster than concatenating strings.
var table_templates = {
  //TABLE : [ '<table ', 1, '>', 3,'</table>' ],
  TABLE : [ '<table ', 1, '></table>' ],
  TR: [ '<tr>', 1, '</tr>'],
  TH: [ '<th ', 1, '>', 3, '</th>'],
  TD: [ '<td ', 1, '>', 3, '</td>']
};

// creates a table object at the specified node. The object can then be
// manipulated to add cells, rows, and render. Nothing is rendered initially.
// * attrs -- string of table attributes
plugin('table', function(attrs) {
  
  var table = this.data('table_maker');
  if (!table) {
    table = new Table(this, attrs);
    this.data('table_maker', table);
  }
  return table;
  
  // Table object is at the heart of the table creator
  // * attrs -- string of table attributes
  function Table(node, attrs) {
    
    // the table node (created on first render)
    var table;
    // template for current row. Starts with TH, all following rows with TD
    var row_template = table_templates.TH;
    // true if rendering head row, false otherwise
    var head = true;
    // collection of row HTML strings
    var rows = [];
    // collection of cell HTML strings
    var cells = [];
    
    // starts a new row
    // if a row has no cells, this call has no effect
    // returns self
    this.row = function() {
      if (cells.length) {
        table_templates.TR[1] = cells.join('');
        rows.push(table_templates.TR.join(''));
        cells = [];
      }
      if (head) {
        row_template = table_templates.TD;
        head = false;
      } 
      return this;
    };
    
    // creates a cell in the table in the current row
    // * content -- content of the cell (optional)
    // * attrs -- attribute value for the cell (optional)
    // returns self
    this.cell = function(content, attrs) {
      row_template[1] = attrs || '';
      row_template[3] = content || '';
      cells.push(row_template.join(''));
      return this;
    };
    
    // renders the table based on accumulated cell/row info and purges the
    // buffer. If the table already existed, appends to the table
    // returns self
    this.render = function() {
      if (!rows.length) {
        if (cells.length) {
          this.row();
        } else {
          return this;
        }
      }
      var html = rows.join('');
      if (!table) {
				table_templates.TABLE[1] = attrs || '';
				table = $(table_templates.TABLE.join(''));
				table.append(html);
				node.append(table);
				/*
        table_templates.TABLE[1] = attrs || '';
        table_templates.TABLE[3] = html;
        node.append(table_templates.TABLE.join(''));
        table = $('>table:first', node);
        */
      } else {
        table.append(html);
      }
      rows = [];
      return this;
    };
    
    // clears table contents. This method does not remove the actual table
    // element. Use standard jQuery methods if you want to completely zap the
    // table.
    // * keepHead -- if true, the operation keeps the head of the table.
    this.clear = function(keepHead) {
      if (table) {
        if (keepHead) {
          $('tr:has(td)', table).remove();
        } else {
          row_template = table_templates.TH;
          head = true;
          table.empty();
        }
      }
      return this;
    };
  };
});


// ***
// jquery.tiny-mce.js
// TinyMCE dynamic loader. Provides ability to add rich text editing
// capabilities without having to specify TinyMCE in the HEAD element


// TODO(dglazkov): add support for editor options.
// TODO(dglazkov): provide more elegant disable/enable presentation. Currently,
//                 the editor is simply hidden with visibility: hidden
// TODO(dglazkov): add support for removing editor

var global_tinymce = {
  // at this point, the editor DLL must be on the same domain
  BASE_URL: '/assets/TinyMce.axd',
  EDITOR_KEY: 'tinyMce',
  defaults: {
		theme : "advanced",
		skin : "o2k7",
    gecko_spellcheck: true,
		skin_variant : "black",
    // Add "-" to custom plugins to load them remotely
    //plugins : "table,advlink,insertdatetime,preview,searchreplace,print,contextmenu,paste,fullscreen,xhtmlxtras,-imagepicker",
    plugins : "table,advlink,insertdatetime,preview,searchreplace,print,contextmenu,paste,fullscreen,xhtmlxtras,imagepicker",
		height: "300px", width: "600px",
    safari_warning : false,
    cleanup_on_startup : false,
    relative_urls: true,
    strict_loading_mode: true,
    theme_advanced_buttons1: "cut,copy,paste,pastetext,pasteword,cleanup,removeformat,separator,search,replace,separator,undo,redo,separator,styleselect,formatselect",
    theme_advanced_buttons2: "bold,italic,underline,strikethrough,sub,sup,separator,justifyleft,justifycenter,justifyright,justifyfull,separator,bullist,numlist,outdent,indent,separator,link,unlink,anchor,imagepicker,charmap",
    theme_advanced_buttons3 : "tablecontrols,separator,hr,visualaid,code,separator,fullscreen,separator,print",
		theme_advanced_toolbar_location : "top",
		theme_advanced_toolbar_align : "left",
    theme_advanced_path_location : "bottom",
    plugin_insertdate_dateFormat : "%Y-%m-%d",
    plugin_insertdate_timeFormat : "%H:%M:%S",
		theme_advanced_resize_horizontal : false,
    theme_advanced_resizing : false
  },
  default_options: {
    // called when the editor initialized and rendered for the first time
    ready: nop,
    // called when the content is submitted (triggered via 'submit' command)
    submit: nop
  },
  // true if tinyMCE was already loaded
  loaded: false
};

// tinyMce plugin:
// * cmd -- optional command. Available commands:
//    * load -- initializes the editor at the given node (optional)
//    * disable -- temporarily disables the editor
//    * enable -- temporarily enables the editor
//    * submit -- trigger submit event in the editor
//    * reset -- resets content of the editor to the original input value
plugin('tinyMce', function(cmd, options, config) {
  var node = this;
  
  if (node.length == 0) return;
  
  var g = global_tinymce;
  
  if (typeof(cmd) != 'string') {
		config = options;
    options = cmd;
    cmd = 'load'; // load event
    config = $.extend({}, g.defaults, config || {});
  }
  
  if (g.loaded) {
    init();
    return;
  }
  
  // this sequence in part is from:
  // http://tinymce.moxiecode.com/punbb/viewtopic.php?id=10419
  $.extend(window, {
    // specify tinyMCE base 
    tinyMCEPreInit: {
      suffix: '',
      // has to be just http URI. It sets the base of tinymce library, 
      // later is used by tinyMCE in XHR loading other scipts, for example
      // plugins
      base: g.BASE_URL
    },
    // today (v3.0.8), there's a bug that incorrectly sets up (and fails)
    // content ready event for IE during loading
    // setting this to true bypasses this branch
    tinyMCE_GZ: { loaded: true }
  });
  
  $.getScript(g.BASE_URL + '/tiny_mce.js', function() {
    // Add image picker plugin
    // This is the debug version, loads image picker from a special directory
    // You need to set it up to point to tinymce plugin in Org.Web.TinyMce
    // project
    //tinymce.PluginManager.load('imagepicker',
    //  '/tinymce/plugins/imagepicker/editor_plugin.js?'
    //  + new Date().getTime().toString());

    // tell tinymce that the DOM has been already loaded
    // and then it will use XHR to load other scipts (by ex. plugins). 
    tinymce.dom.Event._pageInit();
    init();
    g.loaded = true;
  });

  function init() {
    $.each(node, function() {
      var tmce;
      if (!(tmce = node.data(g.EDITOR_KEY))) {
        node.data(g.EDITOR_KEY, 1);
        // create editor instance
        editor = new tinymce.Editor(this.id = guid(this.id), config);
        editor.onPostRender.add(function() {
          var o = $.extend({}, g.default_options, options || {});
          o.ready();
          node.data(g.EDITOR_KEY, {
            editor: editor,
            options: o
          });
          exec(tmce, node);
        });
        editor.render();
        return;
      }
      if (tmce == 1) return;
      exec(tmce, node);
    });
  };
  
  function exec(tmce, node) {
    switch(cmd) {
      case 'disable':
        $(tmce.editor.getContainer()).css({
          visibility: 'hidden'
        });
        break;
      case 'submit':
        tmce.options.submit(tmce.editor.getContent());
        break;
      case 'enable':
        $(tmce.editor.getContainer()).css({
          visibility: 'visible'
        });
        break;
      case 'reset':
        var e = tmce.editor;
        e.execCommand('mceAddUndoLevel');
        e.setContent(node.val());
        break;
    }
  };
});
// ***
// jquery.z-authoring.js
// Note: "z" prefix is to ensure that this file is linked last into core.js
// provides authoring UI bootstrap and login handler
// This is not a typical plugin. All of the functionality is
// handled at startup on every load. However, you can use $.authoring to
// get current authoring settings

// Glossary:
// * Bootstrap Criteria -- determine if authoring needs to be loaded
// * Authoring Criteria -- determine user role

// since this script is only processed when HEAD had already loaded, start
// processing meta and link elements.


// TODO(glazkov): see if we could load authoring CSS dynamically
// TODO(glazkov): add keyboard shortcuts: Ctrl+Shift+E to show login window,
//                Esc to hide it, Enter to submit dialog

// workaround for a jQuery bug, where load events are not fired
// if there are no ready events in queue
$(nop);

var authoring_globals = function() {
  
  if (!/enable-authoring=yes/.test(document.cookie)) {
    // authoring not enabled, exit
    return;
  }
  
  var role_matrix = {
    Anonymous: {},
    Authenticated: { authenticated: true },
    Author: { authenticated: true, author: true },
    Administrator: { authenticated: true, author: true, admin: true },
    SystemAdministrator: { authenticated: true, author: true, admin: true }
  };
  
  // true if authoring should be engaged
  var engage_authoring;
  
  var draftPreviewRegex = /(\?)?(draft-preview=true)(&|$)/;
  
  var g = {
    // velocity of animation
    animation: 100,
    // stores all relevant URLs
    // * draft
    // * published
    // * comments
    // * form -- authoring URL
    url: function() {
      // initialize with standard Engine URLs
      var result = {
				host: location.protocol + '//' + location.host + '/',
        // base URL for the script to load
        base: ($('script[src$=/scripts/core.js]').attr('src') || '')
          .replace(/scripts\/core.js$/, ''),
        login: '/engine/login/',
        user: '/engine/user/ .results',
        logout: '/engine/logout/',
        structure: '/engine/structure/',
        comment: '/engine/comment/',
        history: '/engine/history/',
        workgroup: '/engine/workgroup/',
        images: '/engine/images/'
      };
      $('link').each(function() {
          var rel = this.rel;
          if (rel != 'stylesheet' && rel != 'alternate') {
            result[rel] = this.href;
        }
      });
      return result;
    }(),
    // stores all meta tags
    // * identifier -- page Id
    // * hidden -- true, if page is hidden
    // * offline -- true, if page is offline
    meta: function() {
      var result = {};
      $('meta').each(function() {
        var name = this.name;
        if (name != 'robots') {
          result[this.name] = this.content;
        }
      });
      return result;
    }(),
    // current user information
    user: { login: '', role: 'Anonymous', name: 'Anonymous',
      is: function(role) { return !!role_matrix[this.role][role]; }
    },
    // true, if the page is a draft preview mode
    draft_preview: draftPreviewRegex.test(document.location.search),
    // true, if the page should display the "work" tab (either draft page,
    // or draft preview)
    work_view: false,
    max_z: function() {
      return max_z();
    },
    err: {
      login: 'Invalid login or password.',
      server: 'Server error has occurred.'
    }
  };
  
  if (g.meta.identifier && (g.url.form || g.url.draft)) {
    // determine if this is work view
    if (g.url.published) {
      g.work_view = true;
      g.draft_preview = false;
    } else {
      g.work_view = g.draft_preview;
    }
    var loc = window.location;
    // set published URL
    g.url.published || (g.url.published = loc.href.replace(
      draftPreviewRegex, function(match) {
        return arguments[2].length ? '' : '?';
      }));
    // set draft URL
    g.url.draft || (g.url.draft = loc.href + (loc.search ? '' : '?')
      + 'draft-preview=true');
    // see if we've been logged in during this session
    g.logged_in = $.storage.session('logged_in') == 'true';
    // Verify Bootstrap Criteria
    // this criteria has one false negative -- if the user logged in on
    // some other page without setting "logged_in" cookie and then
    // viewed this page in "live" mode. This is not a big deal, however --
    // they'll just have to re-login.
    if (g.work_view || g.logged_in) {
			$.getScript(g.url.base + 'scripts/custom/editor.js')
      // load authoring assets
      $.getScript(g.url.base + 'scripts/authoring.js', function() {
        $(engage);
      });
      // get user role, defer loading login window/handle
      $.ajah2(g.url.user, { page: g.meta.identifier }, function() {
        $(this).find('.self .vcard').each(function() {
          var self = $(this);
          $.extend(g.user, {
            name: self.find('.fn').text(),
            login: self.find('.nickname').text(),
            role: self.find('.role').text()
          });
          var caps = {};
          self.find('.capabilities li').each(function() {
            caps[$(this).text()] = true;
          });
          g.user.caps = caps;
        });
        if (g.logged_in =
            $.storage.session('logged_in', g.user.is('authenticated'))) {
          // engage authoring
          engage_authoring = true;
          engage();
        } else {
          loginHandle();
        }
      });
    } else {
      // wait for the page to finish loading
      $(function() {
        loginHandle();
      });
    }
  }
  // otherwise, this is probably not an Estrada page, do not engage
  return g;

  function engage() {
    if (!engage_authoring || !$.authoring.engage) {
      return;
    }
    engage_authoring = false;
    authoringNode();
    ($.authoring.engage || nop)(g);    
  }

  // create or get the authoring node
  function authoringNode() {
    return g.node || (g.node = $(document.body).div()
      .css({ position: 'absolute', top: '0', left: '0', width: '100%',
             marginTop: '0' }).div().attr('id', 'authoring-ui'));
  }

  // render login handle
  function loginHandle() {
    // error timer
    var err_timer;
    var n = authoringNode();
    // element cache
    var els = {};
    
    var open;
    
    $.authoring.login = {
      // attempt login
      attempt: function(button) {
        el('error').slideUp(g.animation);
        var data = { username: $('#e-username').val(),
          password: $('#e-password').val(),
          page: g.meta.identifier
        };
        if (data.username.length && data.password.length) {
          els.button = $(button).hide();
          el('loading').show();
          $.ajah2(g.url.login, data, function(status, location) {
              // reset shelf to open (default)
              $.storage.user('shelf', 'o');
              // redirect to work view
              window.location = location;
            }, function(status) {
              err_timer && window.clearTimeout(err_timer);
              el('error').html(g.err[ status == 422 ? 'login' : 'server'])
                .slideDown(g.animation, function() {
                  err_timer = window.setTimeout(function() {
                    err_timer = 0;
                    el('error').slideUp(g.animation);
                  }, 2000);
                });
              el('loading').hide();
              els.button.show();
            });
        }
      },
      keycheck: function(evt, obj) {
		if (evt.keyCode == 13) {
			if (obj.id == "e-username") {
				$("input#e-password").focus();
			} else if (obj.id == 'e-password') {
				$.authoring.login.attempt($('button#e-login'));
	      	}
		}
      },
      // open login dialog
      open: function(n) {
        if (g.meta['auth-mode'] == 'http') {
          // HTTP/Windows authentication
          // attempt authentication
          $.ajah2(g.url.login, { page: g.meta.identifier },
            function(status, location) {
              // reset shelf to open (default)
              $.storage.user('shelf', 'o');
              // redirect to work view
              window.location = location;
            }, function(status) {
              // TODO(dglazkov): Handle HTTP login failure error
            });
          el('teaser', n).hide();
          return;
        } else {
          // forms authentication
          open = true;
          el('teaser', n).hide();
          el('dialog', n).slideDown(g.animation);
        }
      },
      // close login dialog
      close: function(close) {
        el('dialog', n).slideUp(g.animation, function() {
          el('handle', n).show();
        });
        open = false;
      },
      // reveal teaser
      tease: function(n) {
        if (!open) {
          el('handle', n).hide();
          el('teaser', n).slideDown(g.animation);
        }
      },
      // hide teaser
      untease: function(n) {
        if (!open) {
          el('teaser', n).slideUp(g.animation, function() {
            el('handle', n).show();
          });
        }
      }
    };
    // append login HTML (left as one long string to avoid unnecessary string
    // concatenation operations)
    n.append('<div class="login"><div class="handle" onmouseover="$.authoring.login.tease(this)"></div><div style="display:none;" class="teaser" onmouseout="$.authoring.login.untease(this)" onclick="$.authoring.login.open(this)"></div><div style="display:none;" class="dialog"><form><div class="content"><label for="e-username">Username:</label><input type="text" class="text" id="e-username" name="username" onkeyup="$.authoring.login.keycheck(event, this)"/><label for="e-password">Password:</label><input type="password" class="password" id="e-password" name="password" onkeyup="$.authoring.login.keycheck(event,this)"/><button id="e-login" class="button" type="button" onclick="$.authoring.login.attempt(this)"><span>LOGIN</span></button><div class="loading" style="display:none;"></div></div><div class="error" style="display:none;"></div></form><div class="close" onclick="$.authoring.login.close(this)">Close</div><div class="sill"></div></div></div>');
    
    // add document click handler to close the login window when
    // clicked outside
    $(document).click(function(e) {
      if (open) {
        if ($(e.target).parents('#authoring-ui').length == 0) {
          $.authoring.login.close();
        }
      }
    });
    
    function el(name, node) {
      return (els[name] || (els[name] = node ?
        (node.className == name ?
          $(node) : $(node).parent().find('.' + name).eq(0))
          : $('#authoring-ui').find('.' + name).eq(0)));
    };
  };
  
}();

plugin('authoring', function() {
  return authoring_globals;
}, true);
// ***
// ~common.js
// common functions/objects

// register a plugin with jQuery
// * name -- name of the plugin as it would be used, i.e. $(x).name
// * plugin -- the plugin function
// * global -- if true, the plugin will only be registered at jQuery root
//     object, not jQuery.fn
function plugin(name, plugin, global) {
  $[name] = plugin;
  if (!global) {
    $.fn[name] = plugin;
  }
};

// Utilities
// functions/variables that are frequently used across plugins
  
// empty handler/function
function nop() {};

// empty array
var ear = [];

// globally uique ID generator
// * value -- a proposed unique value. If specified, generator just returns it.
//   Otherwise, generator returns a newly generated value.
// * returns a unique id string
function guid(value) {
  return value || ('uid-' + guid.count++);
};

guid.count = 0;

// always returns the top-most value for z-index
function max_z() {
    return ++max_z.count || (max_z.count = 2008);
};

plugin('replaceClass', function(toReplace, replaceWith) {
	//var node = $(this).get(0);
	$(this).each(function() {
		this.className = this.className.replace(toReplace, replaceWith);
	});
});

plugin('right', function() {
	if (!this[0]) return;
	return $(this).scrollLeft() + $(this).width();
});

plugin('bottom', function() {
	if (!this[0]) return;
	return $(this).scrollTop() + $(this).height();
});

plugin('cols', function(settings) {
	var prefix = "e-";
	var innerClass = prefix+"cols-inner";
	var defaults = {
		holder: {
			id: "cols-"+guid(),
			className: prefix+"cols"
		},
		one: {
			className: prefix+"left"
		},
		two: {
			className: prefix+"right"
		}
	};
	settings = $.extend({}, defaults, settings);
	var holder = $(document.createElement("div"));
	$(holder).attr("id", settings.holder.id);
	holder.div().addClass(innerClass).addClass(settings.one.className);
	holder.div().addClass(innerClass).addClass(settings.two.className);
	holder.div().addClass(prefix+"cols-bottom");
	if (this[0]) {
		$(this).append(holder);
	}
	return holder;
});

plugin('shadow', function(className) {
	if (className) {
		className = ' ' +  className + '-shadow';
	}
	else {
		className = '';
	}
	var node = $('<div class="p-shadow' + className + '"><div class="p-shadow2"></div></div>');
	this.wrap(node);
	return node;
});

plugin('shadowInner', function(className) {
	if (className) {
		className = ' ' +  className + '-shadow';
	}
	else {
		className = '';
	}
	var node = $('<div class="p-shadow' + className + '"><div class="p-shadow2"></div></div>');
	this.wrapInner(node);
	return node;
});

}
