
//
//      LOGGING UTILITIES
//

var NONE = 0;
var ERROR = 1;
var WARN = 3;
var TRACE = 4;
var WARN = 5;
var INFO = 6;
var ALL = 7;

//var TRACING = false;

var LOGLEVEL = ALL;

// Logging utilities

if (!window['console']){
    window.console = {log: function(x){ /*status = x*/}};
}

function deprecated(msg){
    console.log('Deprecated: ' + msg);
}

function warning(msg){
    if (LOGLEVEL < WARN) return;
    console.log('Warning: ' + msg);
}

function error(msg){
    if (LOGLEVEL < ERROR) return;
    console.log('Error: ' + msg);
}

function info(msg){
    if (LOGLEVEL < INFO) return;
    console.log('Info: ' + msg);
}

function trace(msg){
    if (LOGLEVEL < TRACE) return;
    console.log('Trace: ' + msg);
}

//
// Bring these forward from Prototype 1.5.1
//

if (! String.prototype['startsWith']){
    Object.extend(String.prototype, {
        startsWith: function(pattern){
            return this.indexOf(pattern) == 0;
        },
        endsWith: function(pattern){
            return this.indexOf(pattern) == (this.length - pattern.length);
        },
        evalJSON: function(sanitize) {
          try {
            if (!sanitize || (/^("(\\.|[^"\\\n\r])*?"|[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t])+?$/.test(this))){
              return eval('(' + this + ')');
            }
            return {};
          } catch (e) {}
          throw new SyntaxError('Badly formated JSON string: ' + this.inspect());
        }
    });
}

// Add enumerate method to array.  Pass in to each function the element and index.
if (! Array.prototype['enumerate']){
    Object.extend(Array.prototype, {
        enumerate: function(fn){
            for (var i; i < this.length; i++){
                fn(this[i], i);
            }
        }
    });
}

// Useful characters for strings

var NBSP = String.fromCharCode(0xA0); // this is Javascript for &nbsp; or non-breaking space

// Utility for submitting forms without submitting page

function do_submit(evt){
    var form = Event.findElement(evt, 'form');
    if (form.fireEvent){ // IE
        form.fireEvent('onsubmit');
        form.submit();
    }else{
        var evt = document.createEvent('HTMLEvents');
        evt.initEvent('submit', true, true);
        form.dispatchEvent(evt);
        form.submit();
    }
}

// Simple Array Iterator

function ArrayIterator(arr){
    this._array = arr;
    this.index = 0;
    this.next = function(){
        this.index += 1;
        if (this.index >= this._array.length){
            this.index = 0;
        }
        return this._array[this.index];
    };
    this.prev = function(){
        this.index -= 1;
        if (this.index < 0){
            this.index = this._array.length - 1;
        }
        return this._array[this.index];
    };
    this.current = function(){
        return this._array[this.index];
    };
}

//
// DOM HELPERS modelled after MochiKit
//

function ELEM(name, attrs){
    var elem = document.createElement(name);
    if (attrs){
        for (name in attrs){
            if (name == 'class' || name == 'className'){
                elem.className = attrs[name].toString();
            }else if (name == 'id'){
                elem.id = attrs[name].toString();
            }else if (name.startsWith('on')){
                Event.observe(elem, name.slice(2).toLowerCase(), attrs[name].bindAsEventListener(elem));
            }else if (name == 'styles'){
                var styles = attrs[name];
                for (stylename in styles){
                    elem.style[stylename] = styles[stylename];
                }
            }else{
                elem.setAttribute(name, attrs[name].toString());
            }
        }
    }
    if (arguments.length > 2){
        for (var i = 2; i < arguments.length; i++){
            elem.appendChild(arguments[i]);
        }
    }
    return elem;
}

function partial(func, arg){
    return function(){
        var args = $A(arguments);
        args.unshift(arg);
        return func.apply(this, args);
    };
}

function TEXT(str){
    return document.createTextNode(str.toString());
}

var A = partial(ELEM, 'a');
var BUTTON = partial(ELEM, 'button');
var BR = partial(ELEM, 'br');
var DIV = partial(ELEM, 'div');
var EM = partial(ELEM, 'em');
var FORM = partial(ELEM, 'form');
var H1 = partial(ELEM, 'h1');
var H2 = partial(ELEM, 'h2');
var H3 = partial(ELEM, 'h3');
var HR = partial(ELEM, 'hr');
var IMG = partial(ELEM, 'img');
var INPUT = partial(ELEM, 'input');
var LABEL = partial(ELEM, 'label');
var LEGEND = partial(ELEM, 'legend');
var LI = partial(ELEM, 'li');
var OL = partial(ELEM, 'ol');
var OPTGROUP = partial(ELEM, 'optgroup');
var OPTION = partial(ELEM, 'option');
var P = partial(ELEM, 'p');
var PRE = partial(ELEM, 'pre');
var SELECT = partial(ELEM, 'select');
var SPAN = partial(ELEM, 'span');
var STRONG = partial(ELEM, 'strong');
var TABLE = partial(ELEM, 'table');
var TBODY = partial(ELEM, 'tbody');
var TD = partial(ELEM, 'td');
var TEXTAREA = partial(ELEM, 'textarea');
var TFOOT = partial(ELEM, 'tfoot');
var TH = partial(ELEM, 'th');
var THEAD = partial(ELEM, 'thead');
var TR = partial(ELEM, 'tr');
var TT = partial(ELEM, 'tt');
var UL = partial(ELEM, 'ul');

var uid = 0;
function UniqId(){
    uid += 1;
    return 'id_' + uid;
}

//
//      Rich Text Editor Initialization
//

// Asbru's Web Content Editor uses HTML entities even in UTF-8 for many things
// that aren't needed. Replace with something that sticks to what's necessary.
// The only change to this function is near the end (see 'CHANGED' )
function webeditor_custom_contenteditable_formatContentNodeXHTML(node, rootnode) {
	var content = '';
	if (node.tagName) {
		if ((node.firstChild) || (node.tagName.match(new RegExp("^(TEXTAREA|TABLE|THEAD|TBODY|TFOOT|TR|TD|DIR|MENU|DL|OL|UL|FORM|SELECT|H1|H2|H3|H4|H5|H6|A|OBJECT|EMBED|NOEMBED|MAP|IFRAME|DIV)$")))) {
			content += '<' + node.tagName.toLowerCase() + contenteditable_formatContentNodeAttributes(node) + '>';
			if (node.tagName.match(new RegExp("^(TABLE|THEAD|TBODY|TFOOT|TR|DIR|MENU|DL|OL|UL|FORM|SELECT|OBJECT|EMBED|NOEMBED)$"))) {
				if ((! node.nextSibling) || (node.nextSibling.nodeName != "#text") || ((node.nextSibling.nodeValue[0] != "\r") && (node.nextSibling.nodeValue[0] != "\n"))) {
					content += '\r\n';
				}
			}
			var childnode = node;
			for (var childnode = node.firstChild; childnode; childnode=childnode.nextSibling) {
				content += contenteditable_formatContentNodeXHTML(childnode, rootnode);
			}
			content += '</' + node.tagName.toLowerCase() + '>';
			if (node.tagName.match(new RegExp("^(TABLE|THEAD|TBODY|TFOOT|TR|DIR|MENU|DL|OL|UL|FORM|OPTION|TD|P|DIV|LI|DD|DT|H1|H2|H3|H4|H5|H6|IFRAME|OBJECT|EMBED|NOEMBED|MAP|IFRAME)$"))) {
				if ((! node.nextSibling) || (node.nextSibling.nodeName != "#text") || ((node.nextSibling.nodeValue[0] != "\r") && (node.nextSibling.nodeValue[0] != "\n"))) {
					content += '\r\n';
				}
			}
		} else {
			// MSIE may not handle custom start/end tags and child nodes
			if (node.tagName.charAt(0) == "/") {
				// MSIE: "/foo" custom end tag tagName = custom end tag
				content += '<' + node.tagName.toLowerCase() + contenteditable_formatContentNodeAttributes(node) + '>';
			} else {
				// MSIE: check for "/foo" custom end tag tagNames
				var endtags = rootnode.getElementsByTagName("/"+node.tagName);
				if (endtags.length) {
					// MSIE: "/foo" custom end tag tagNames = custom start tag
					content += '<' + node.tagName.toLowerCase() + contenteditable_formatContentNodeAttributes(node) + '>';
				} else {
					// empty tag
					content += '<' + node.tagName.toLowerCase() + contenteditable_formatContentNodeAttributes(node) + ' />';
				}
			}
			if (node.tagName.match(new RegExp("^(P|BR|HR|LI|DD|DT)$"))) {
				if ((! node.nextSibling) || (node.nextSibling.nodeName != "#text") || ((node.nextSibling.nodeValue[0] != "\r") && (node.nextSibling.nodeValue[0] != "\n"))) {
					content += '\r\n';
				}
			}
		}
	} else if (node.nodeValue) {
		var value = node.nodeValue;
		if (value.match(new RegExp("^<script", "i")) && value.match(new RegExp("<\/script>", "i"))) {
			// ok - script
		} else if (value.match(new RegExp("^<!--")) && value.match(new RegExp("-->"))) {
			// ok - comment
		} else {
			value = value.escapeHTML();  // CHANGED. Uses something from Prototype.
			value = value.replace(/(&(?![#a-zA-Z0-9]+;))/gi, "&amp;");
		}
		content += value;
	}
	return content;
}

function webeditor_custom_contenteditable_formatContentNodeHTML(node, rootnode) {
	var content = '';
	if (node.tagName) {
		if ((node.firstChild) || (node.tagName.match(new RegExp("^(TEXTAREA|TABLE|THEAD|TBODY|TFOOT|TR|TD|DIR|MENU|DL|OL|UL|FORM|SELECT|H1|H2|H3|H4|H5|H6|A|OBJECT|EMBED|NOEMBED|MAP|IFRAME|DIV)$")))) {
			content += '<' + node.tagName.toLowerCase() + contenteditable_formatContentNodeAttributes(node) + '>';
			if (node.tagName.match(new RegExp("^(TABLE|THEAD|TBODY|TFOOT|TR|DIR|MENU|DL|OL|UL|FORM|SELECT|OBJECT|EMBED|NOEMBED)$"))) {
				if ((! node.nextSibling) || (node.nextSibling.nodeName != "#text") || ((node.nextSibling.nodeValue[0] != "\r") && (node.nextSibling.nodeValue[0] != "\n"))) {
					content += '\r\n';
				}
			}
			var childnode = node;
			for (var childnode = node.firstChild; childnode; childnode=childnode.nextSibling) {
				content += contenteditable_formatContentNodeHTML(childnode, rootnode);
			}
			content += '</' + node.tagName.toLowerCase() + '>';
			if (node.tagName.match(new RegExp("^(TABLE|THEAD|TBODY|TFOOT|TR|DIR|MENU|DL|OL|UL|FORM|OPTION|TD|P|DIV|LI|DD|DT|H1|H2|H3|H4|H5|H6|IFRAME|OBJECT|EMBED|NOEMBED|MAP|IFRAME)$"))) {
				if ((! node.nextSibling) || (node.nextSibling.nodeName != "#text") || ((node.nextSibling.nodeValue[0] != "\r") && (node.nextSibling.nodeValue[0] != "\n"))) {
					content += '\r\n';
				}
			}
		} else {
			content += '<' + node.tagName.toLowerCase() + contenteditable_formatContentNodeAttributes(node) + '>';
			if (node.tagName.match(new RegExp("^(P|BR|HR|LI|DD|DT)$"))) {
				if ((! node.nextSibling) || (node.nextSibling.nodeName != "#text") || ((node.nextSibling.nodeValue[0] != "\r") && (node.nextSibling.nodeValue[0] != "\n"))) {
					content += '\r\n';
				}
			}
		}
	} else if (node.nodeValue) {
		var value = node.nodeValue;
		if (value.match(new RegExp("^<script", "i")) && value.match(new RegExp("<\/script>", "i"))) {
			// ok - script
		} else if (value.match(new RegExp("^<!--")) && value.match(new RegExp("-->"))) {
			// ok - comment
		} else {
			value = value.escapeHTML();  // CHANGED. Uses something from Prototype.
			value = value.replace(/(&(?![#a-zA-Z0-9]+;))/gi, "&amp;");
		}
		content += value;
	}
	return content;
}


function webeditor_custom_createlink(){
    var url = prompt('Enter the URL');
    var sel = WebEditorGetContentSelection();
    WebEditorPasteContent('<a href="' + url + '">' + sel + '</a>');
}

function webeditor_custom_insertmedia(){
    showDialogLazy('editor_choose_photo_popup', '/users/' + UserId + ';choose_photos_popup', {height: '435px'});
}

function initializeWebeditors(){
      if (!window['webeditor']) return;
      if (webeditor.type == "textarea") return;
	webeditor.textareas = [];
    $$('textarea').each(function(textarea){
        if (textarea.hasClassName('mceNoEditor')){
            return;
	    }
		var name = textarea.name;
		var value = (navigator.appVersion.indexOf("Safari") < 0) ? textarea.innerHTML.unescapeHTML() : textarea.innerHTML; // only unescape if not Safari
		var width = textarea.clientWidth;
		var height = textarea.clientHeight;
        webeditor.textareas.push(name);
		var div = DIV({styles:{border: '1px solid #CCCCCC', minWidth: width + 'px', minHeight: height + 'px'}},
		    DIV({id: name + '_toolbar_container', 'class': 'webeditor_toolbar_container'}),
		    DIV({id: name + '_webeditor_container', 'class': 'webeditor_container'})
		);
		textarea.parentNode.replaceChild(div, textarea);
		WebEditorToolbar(name,   { container: name+'_toolbar_container',
		    fontname: {Verdana: 'Verdana', Times: 'Times New Roman, Times, serif', Courier: 'Courier New, Courier, monospace', Comic: 'Comic Sans MS'},
		    fontsize: {Small:1, Medium:2, Large: 3, Larger: 4},
            toolbar1: 'undo redo fontname fontsize forecolor bold italic underline justifyleft justifycenter justifyright createlink unlink insertmedia'}   );
		WebEditor(name, value, {container: name+'_webeditor_container', stylesheet:  '/javascripts/webeditor/example.css', format: 'html', width: width, height: height, onEnter: '<br>', onShiftEnter: '<p>', onCtrlEnter: '<div>', onAltEnter: '<hr>' });
    });
    if (webeditor.textareas.length){
        webeditor_init();
    }
    // replace webeditor's default contenteditable_formatContentNode* methods
    contenteditable_formatContentNodeXHTML = webeditor_custom_contenteditable_formatContentNodeXHTML
    contenteditable_formatContentNodeHTML  = webeditor_custom_contenteditable_formatContentNodeHTML
}
Event.observe(window, 'load', initializeWebeditors);



//
//      INVITE BUILDER for adding more invitees to a form
//

var INVITEE_TEMPLATE = new Template("invitees[user#{count}][#{type}]");

function INVITEE_INPUT(type, count){
    // Substitute the type and count into template for id and name
    var idname = INVITEE_TEMPLATE.evaluate({type:type, count:count});
    return INPUT({id: idname, name: idname, type: 'text', value: ""});
}

// Add five more fields to invite users, limit to 25 total
function add_invites(evt){
    Event.stop(evt);
    var members = $('invite_these_family_members');
    var count = members.immediateDescendants().length;
    $R(count + 1, count + 5).each(function(index){members.appendChild(new_invite(index));});
    if (count + 5 > 24){
        $('add_more_invites_button').hide();
    }
}

function new_invite(index){
    return TR({}, TD({}, INVITEE_INPUT('first_name', index)),
                  TD({}, INVITEE_INPUT('last_name', index)),
                  TD({}, INVITEE_INPUT('email', index)));
}

//
//      PHOTO ALBUM IMAGE PICKER WIDGETS
//

function basicFilmstrip(id, rows, columns, pattern){
    return new Filmstrip(id, rows, columns, null, pattern);
}

// Used via lazy initialization from rich text editor
function availablePhotosPickerInitializer(id, rows, columns, pattern){
    return new Filmstrip(id, rows, columns, addToEditorFilmstripInitHandler, pattern);
}

function addToEditorFilmstripInitHandler(anchor){
    Event.observe(anchor, 'click', addToEditorFilmstripItemClickHandler.bindAsEventListener(anchor));
}

var addToEditorImage = null;
var currentEditor = null;

function addToEditorFilmstripItemClickHandler(evt){
    Event.stop(evt);
    var img = addToEditorImage = this.down('img');
    WebEditorPasteContent('<img src="' + this.href + '" width="200px" height="auto" alt="' + img.alt + '">');
    closeDialog('editor_choose_photo_popup');
}

function availablePhotosInitializer(id, rows, columns, pattern){
    return new Filmstrip(id, rows, columns, addFilmstripItemInitHandler, pattern);
}

function albumPickerPhotosInitializer(id, rows, columns, pattern){
    return new Filmstrip(id, rows, columns, removeFilmstripItemInitHandler, pattern);
}

function removeFilmstripItemInitHandler(anchor){
    var image = anchor.down('img');
    Event.observe(anchor, 'click', removeFilmstripItemClickHandler.bindAsEventListener(anchor));
    anchor.appendChild(IMG({src: '/icons/remove_popover_16.gif', 'class': 'filmstrip_overlay'}));
}

function removeFilmstripItemClickHandler(evt){
    Event.stop(evt);
    Event.stopObserving(this, 'click', removeFilmstripItemClickHandler);
    var self = this;
    if (this.parentNode.nodeName == '#document-fragment'){
        return error('there should not be a document fragment here');
    }
    if(/MSIE/.test(navigator.userAgent) && !window.opera){
        var async_flag = false;
    }else{
        var async_flag = true;
    }
    new Ajax.Request(
        this.href,
        {
            asynchronous: async_flag,
            onSuccess: function(transport){
                try{
                    var response = transport.responseText.evalJSON(true);
                    var old_id = extractId(this);
                    this.id = this.id.replace(old_id, response.asset_id);
                    photos_available_widget.add(this);
                }catch(e){
                    error('removeFilmstripItemClickHandler: problem with Ajax request.');
                }}.bindAsEventListener(self),
            onFailure: function(response){
                error('failed to remove ' + this.href);}.bindAsEventListener(self),
            method: 'delete'
        });
    var parent = $(this.parentNode);
    var index = parent.immediateDescendants().indexOf(this);    
    existing_photo_album_widget.removeByIndex(index);
    photos_in_album_widget.removeItem(this);
}


function addFilmstripItemInitHandler(anchor){
    anchor._observer = addFilmstripItemClickHandler.bindAsEventListener(anchor);
    Event.observe(anchor, 'click', anchor._observer);
    anchor.appendChild(IMG({src: '/icons/add_popover_16.gif', 'class': 'filmstrip_overlay'}));
}

function addFilmstripItemClickHandler(evt){
    Event.stop(evt);
    Event.stopObserving(this, 'click', this._observer);
    var self = this;
    if (this.parentNode.nodeName == '#document-fragment'){
        return error('there should not be a document fragment here');
    }
    if(/MSIE/.test(navigator.userAgent) && !window.opera){
        var async_flag = false;
    }else{
        var async_flag = true;
    }
    new Ajax.Request(
        this.href, 
        {
            asynchronous: async_flag,
            onSuccess: function(transport){
//                trace('added ' + this.href + ' successfully: ' + transport.responseText);
                var response = transport.responseText.evalJSON(true);
                var old_id = extractId(this);
//                trace('old_id: ' + old_id + ', new_id: ' + response.new_link);
                existing_photo_album_widget.add(this);
                this.id = this.id.replace(old_id, response.new_link);
                photos_in_album_widget.add(this);}.bindAsEventListener(self),
            onFailure: function(response){
                error('failed to add ' + this.href);}.bindAsEventListener(self)
        });
    photos_available_widget.removeItem(this);
}

function clickDisabledFilmstripItemClickHandler(evt){
    Event.stop(evt);
}

function clickDisabledFilmstripItemInitHandler(anchor){
    Event.observe(anchor, 'click', clickDisabledFilmstripItemClickHandler);
}

//
//      Utilities for Filmstrip
//

function getAttrs(elem){
    var e = $(elem);
    var attrs = {};
    $A(['id', 'class', 'future_src', 'cached', 'alt', 'src']).each(function(attr_name){
        if (e.readAttribute(attr_name)){
            attrs[attr_name] = e.readAttribute(attr_name);
        }
    });
    return attrs;
}

// Clone only specific parts of a node
// Clone variation specifically for use with filmstrip items
function cloneShallow(anchor, uri){
    if (anchor._observer){
        Event.stopObserving(anchor, 'click', anchor._observer);
    }
    var new_a = anchor.cloneNode(false);
    if (uri){
        new_a.setAttribute('href', uri);
    }
    new_a.appendChild(anchor.down().cloneNode(false));
    return new_a;
}

function cloneShallow2(anchor, uri){
    var attrs = getAttrs(anchor);
    var new_a = Elem(anchor.tagName.toLowerCase(), getAttrs(anchor));
    $(anchor).immediateDescendants().each(function(node){
        if (node.className == 'filmstrip_overlay'){
            //pass
        }else{
            new_li.appendChild(cloneShallow(node));
        }
    });
    $(anchor).down().setAttribute('href', uri);
    return anchor;
}


// function specifically to extract a Rails image id from a filmstrip item
function extractId(node){
    return node.id.split('_').last();
}

//
//      Constant(s) for filmstrip
//

MAX_VISIBLE_PAGES = 10;

//
//      Classes for filmstrip
//      

//      Pager
//          id
//          owner
//          length
//          currentPage()
//          page_list()
//          add_page()
//          remove_page()
//          setVisiblePages() // internal method
//          setPage(page_number)
//          hide()
//          show()
//          _unlinkPage(page_number) // internal method
//          _linkPage() // internal method
//          initialize() // internal method

function _page_from_anchor(anchor){
    var url = anchor.getAttribute('href');
    var idx = url.lastIndexOf('#page');
    return url.slice(idx + 5); // remove #page prefix
}

function Pager(id, owner){
    this.container = $(id);
    this.id = id;
    this.owner = owner;
    this.prev_button = $(id + '_prev_button');
    this.next_button = $(id + '_next_button');
    this.current_page = 0;
    this.currentPage = function(){
        return this.current_page;
    }.bind(this);
    this.page_list = function(){
        return $(this.id + '_list').getElementsBySelector('a');
    };
    this.add_page = function(){
        var owner = this.owner;
        var page_list = $(this.id + '_list');
        var anchor = A({'class': 'page', href: '#page' + this.length}, TEXT('' + (this.length + 1)));
        var click_handler = function(evt){
            Event.stop(evt);
            owner.setPage(_page_from_anchor(anchor));
        };
        anchor._observer = click_handler.bindAsEventListener(anchor);
        Event.observe(anchor, 'click', anchor._observer);
        if (page_list.down('a')){ 
            // if this isn't the first anchor, add a pipe separator
            page_list.appendChild(SPAN({}, TEXT(' | ')));
        }
        page_list.appendChild(anchor);
        var click_handler = function(evt){
            Event.stop(evt);
            owner.setPage(_page_from_anchor(anchor));
        };
        anchor._observer = click_handler;
        this.length += 1;
        if (this.page_list().length > 1){
            this.show();
        }
    };
    this.remove_page = function(){
        if (this.currentPage == this.length - 1){
            this.owner.setPage(this.length);
        }
        var page_list = this.page_list();
        var anchor = page_list[page_list.length - 1];
        Event.stopObserving(anchor, 'click', anchor._observer);
        var list_element = anchor.parentNode;
        var separator = anchor.previous('span');
        if (separator){
            list_element.removeChild(separator);  // remove the pipe separator, if it exists
        }
        list_element.removeChild(anchor);
        this.length -= 1;
        if (this.page_list().length < 2){
            this.hide();
        }
    };
    this.setVisiblePages = function(){
        var pages = this.page_list();
        var first_visible_page = Math.max(Math.min(this.current_page, pages.length - MAX_VISIBLE_PAGES), 0);
        var last_visible_page = first_visible_page + MAX_VISIBLE_PAGES;
        pages.each(function(page, index){
            if (index >= first_visible_page && index < last_visible_page){ 
                page.show();
                var separator = page.next('span');
                if (index < (last_visible_page - 1)){
                    if (separator) separator.show();
                }else{
                    if (separator) separator.hide();
                }
            }else{ 
                page.hide();
                var separator = page.next('span');
                if (separator) separator.hide();
            }
        });
    };
    this.setPage = function(page_number){
        this._unlink_page(page_number);
        // if there are more page numbers than will fit, may need to rotate the list here
        if (page_number < 1){
            this.prev_button.hide();
        }else{
            this.prev_button.show();
        }
        if (page_number < this.page_list().length - 1){
            this.next_button.show();
        }else{
            this.next_button.hide();
        }
        this.setVisiblePages();
    };
    this.hide = function(){
        this.container.hide();
    };
    this.show = function(){
        this.container.show();
    };
    this._unlink_page = function(page_number){
        this._link_page();
        this.current_page = page_number;
        // make sure we are on a valid page
        if (this.current_page < 0) this.current_page = 0;
        if (this.current_page > (this.page_list().length - 1)) this.current_page = this.page_list().length - 1;
        if (this.page_list().length < 1) return warning('Pager._unlink_list: empty page_list');
        // get the page link
        var anchor = this.page_list()[page_number];
        if (!anchor) return error('Pager._unlink_page(' + page_number + '): Page not found');
        anchor.addClassName('link_hidden');
        // remove the pipe character
        
    };
    this._link_page = function(){
        if (this.page_list().length < 1) return;
        if (this.current_page < 0) this.current_page = 0;
        if (this.current_page > (this.page_list().length - 1)) this.current_page = this.page_list().length - 1;
        var anchor = this.page_list()[this.current_page];
        if (!anchor) return error('Pager._link_page(' + this.current_page + '): Page not found');
        anchor.removeClassName('link_hidden');
    };
    this.initialize = function(){
        var owner = this.owner;
        var self = this;
        var currentPage = this.currentPage;
        var page_list = this.page_list();
        this.length = page_list.length;
        page_list.each(function(anchor, index){
            var click_handler = function(evt){
                Event.stop(evt);
                owner.setPage(_page_from_anchor(anchor));
            };
            anchor._observer = click_handler.bindAsEventListener(anchor);
            Event.observe(anchor, 'click', anchor._observer);
        });
        Event.observe(this.prev_button, 'click', function(evt){
            Event.stop(evt);
            if (currentPage() - 1 < 0){
                error('Pager trying to move past start of list');
                return;
            }
            owner.setPage(currentPage() - 1);
        });
        Event.observe(this.next_button, 'click', function(evt){
            Event.stop(evt);
            if (self.currentPage() + 2 > self.page_list().length){
                error('Pager trying to move past end of list');
                return;
            }
            owner.setPage(currentPage() + 1);
        });
        if (this.page_list().length < 2){
            this.hide();
        }
        this.prev_button.hide();
        this._unlink_page(0);
    };
    this.initialize();
}

//
//      Filmstrip
//          id
//          rows
//          columns
//          setPage(page_number)
//          add(item)
//          removeItem(item)
//          removeByIndex(index)
//          contains(item)
//          setLength() // internal method
//          repaginate() // internal method
//          image_list()
//          initialize() // internal method
//          toString()

function Filmstrip(id, rows, columns, initHandler, pattern){
    this.id = id;
    this.initHandler = initHandler;
    if(pattern){
        info('Filmstrip pattern: ' + unescape(pattern).unescapeHTML());
        this.template = new Template(unescape(pattern).unescapeHTML());
    }else{
        this.template = null;
    }
    this.rows = rows;
    this.columns = columns;
    this.setPage = function(page){
        var pageNumber = parseInt(page);
        if (pageNumber === NaN || pageNumber >= this.foot_pager.length){
            pageNumber = this.foot_pager.length - 1;
        }
        this.current_page = pageNumber;
        this.foot_pager.setPage(pageNumber);
        if (this.head_pager) this.head_pager.setPage(pageNumber);
        var start_shown = this.page_size * pageNumber;
        var end_shown = start_shown + this.page_size;
        this.image_list().each(function(anchor, index){
            if ((index >= start_shown) && (index < end_shown)){
                anchor.style.display = 'inline';
                var image = anchor.down('img');
                if(image.getAttribute('src').endsWith('/images/blank.gif')){
                    image.src = image.getAttribute('longdesc');
                }
            }else{
                anchor.style.display = 'none';
            }
        });
    };
    this.add = function(item){
        var uri = null;
        if (this.template){
            //trace('setting the uri: ' + this.template.template + ', ' +  extractId(item));
            uri = this.template.evaluate({photo_id: extractId(item)});
        }
        var newNode = cloneShallow(item, uri);
        this.image_list_element.appendChild(newNode);
        if (this.initHandler){
            initHandler(newNode);
        }
        this.setLength();
        this.repaginate();
        this.setPage(this.foot_pager.length - 1);
        return item;
    };
    this.contains = function(item){
        return this.image_list_element.immediateDescendants().indexOf(item) > -1;
    };
    this.removeItem = function(item){
        if(! this.contains(item)){
            return error(extractId(item) + ' is not in ' + this.id);
        }
            this.image_list_element.removeChild(item);
        this.setLength();
        this.repaginate();
        return item;
    };
    this.removeByIndex = function(index){
        //trace('removeByIndex(' + index + ')');
        if (index < 0 || index > (this.image_list().length - 1)) return error('removeChildByIndex: index ' + index + ' outside of range: ' + this.image_list().length);
        var oldElem = this.image_list()[index];
        //trace('removing ' + oldElem + ' from ' + this.image_list_element);
        this.image_list_element.removeChild(oldElem);
    };
    this.setLength = function(){
        this.length = this.image_list().length;
    };
    this.repaginate = function(){
        var pages = this.foot_pager.length;
        var pages_needed = Math.ceil(this.length / this.page_size);
        if (pages < pages_needed){
            this.foot_pager.add_page();
            if (this.head_pager) this.head_pager.add_page();
            //trace('added page');
        }else if(pages > pages_needed){
            this.foot_pager.remove_page();
            if (this.head_pager) this.head_pager.remove_page();
            //trace('removed page');
        }
        this.setPage(this.current_page);
    };
    this.image_list = function(){
        return this.image_list_element.immediateDescendants();
    };
    this.initialize = function(){
        window[id + '_widget'] = this;
        this.container = $(id);
        this.page_size = this.rows * this.columns;
        this.foot_pager = new Pager(id + '_foot_pager', this);
        if (rows > 3){
            this.head_pager = new Pager(id + '_head_pager', this);
        }else{
            var temp = $(id + '_head_pager');
            if(temp){
                temp.parentNode.removeChild(temp);
            }
        }
        this.image_list_element = $(id + '_image_list');
        this.setLength();
        this.setPage(0);
        if (this.initHandler){
            this.image_list().each(this.initHandler);
        }
    };
    this.toString = function(){
        return 'Filmstrip(' + this.id + ', [image_data ...], ' + this.rows + ', ' + this.columns + ')';
    };
    this.initialize();
}

//      TAB MENU

function initializeTabMenu(){
    if ($('tabmenu')){
        window.tabmenu = new YAHOO.widget.Menu('tabmenu', {visible: false, clicktohide: true});
        tabmenu.render();
    }
}

function toggleTabMenu(evt){
    Event.stop(evt);
    if ($('tabmenu').style.visibility == 'visible'){
        tabmenu.hide();
    }else{
        tabmenu.show();
    }
}


//
//      COLOR PICKER WIDGET
//

function toggle_colour(){
    if ($('colour_palette_table').hasClassName('hidden')){
        $('colour_palette_table').removeClassName('hidden');
    }else{
        $('colour_palette_table').addClassName('hidden');
    }
}

function pick_colour(event){
    var colour = Event.element(event).className;
    $('colour_swatch').className = colour;
    $('family_colour').value = colour.slice(3); // remove "cp-" prefix
    toggle_colour();
}

function initializeColorPicker(){
    if ($('family_colour')){
        $('colour_swatch').addClassName('cp-' + $('family_colour').value); // add "cp-" prefix
        $('colour_palette_table').observe('click', pick_colour);
    }
}

//
//      DIALOG UTILITIES
//

function showDialog(id, optionsHash){

    if (! $(id)) return error('Failed to show dialog, no id matches ' + id );

    var options = $H({submit: false, width: '600px', height: '600px', modal: true, close: true});
    if (optionsHash){
        options.merge(optionsHash);
    }
    var overlay_id = 'overlay_' + id;
    var dialog = window[overlay_id];
    if (! dialog){
        document.body.appendChild(document.getElementById(id));
        window[overlay_id] = dialog = new YAHOO.widget.Dialog(id);
        dialog.cfg.queueProperty('modal', options.modal);
        dialog.cfg.queueProperty('close', options.close);
        dialog.cfg.queueProperty('width', options.width);
        dialog.cfg.queueProperty('height', options.height);
        if (! options.submit){
            dialog.cfg.queueProperty('postmethod', 'none');
        }
        dialog.cfg.queueProperty('zIndex', 9999);
        dialog.render();
    }
    dialog.center();
    dialog.element.firstChild.style.display = 'block';
    dialog.show();
}

// TODO Replace this with more generic version that will take an options hash for the
// params we want to be able to specify when calling.
function showBusyDialog(id, submit){
    var overlay_id = 'overlay_' + id;
    var dialog = window[overlay_id];
    if (! dialog){
        document.body.appendChild($(id));
        window[overlay_id] = dialog = new YAHOO.widget.Dialog(id );
        dialog.cfg.queueProperty('modal', true);
        dialog.cfg.queueProperty('close', false);
        dialog.cfg.queueProperty('width', '450px');
        dialog.cfg.queueProperty('height', '225px');
        dialog.cfg.queueProperty('constraintoviewport', true);
        dialog.cfg.queueProperty('fixedcenter', true);
        if (! submit){
            dialog.cfg.queueProperty('postmethod', 'none');
        }
        dialog.cfg.queueProperty('zIndex', 1000);
        dialog.render();
    }
    dialog.element.firstChild.style.display = 'block';
    dialog.center();
    dialog.show();
}

function showDialogLazy(id, uri, options){
    if (! $(id)){
        document.body.style.cursor = 'wait';
        new Ajax.Updater('popup_container', uri, {
            method: 'get',
            evalScripts: true,
            onComplete: function(req){
                document.body.style.cursor = 'default';
                showDialog(id, options);
            },
            onFailure: function(req){
                error('showDialogLazy failed');
            }});
    }else{
        showDialog(id, options);
    }
}

function showAnswerDialog(questionId, questionTitle, writtenBy) {
  $('answer_popup_title').innerHTML = questionTitle;
  $('answer_question_id').value = questionId;
  $('answer_title').value = questionTitle;
  $('answer_written_by').value = writtenBy;
  showDialog('answer_holder', 'answer_close');
}

function closeDialog(id){
    var dialog = window['overlay_' + id];
    if (dialog){
        dialog.hide();
    }
}

//
//      YUI CALENDAR PICKER
//

    function pad2(number){
    var value = '' + number;
    if (value.length < 2){
        return '0' + value;
    }
    return value;
}


function initializeDatePickers(){
    document.getElementsByClassName('datepicker').each(function(el){
        var base_id = el.id;
        var dateinput = $(base_id + '_dateinput');
        var datebutton = $(base_id + '_datebutton');
        var datecalendar = $(base_id + '_datecalendar');
        var yui_calendar = new YAHOO.widget.Calendar(base_id + '_yui_calendar', base_id + '_datecalendar', {title: 'Choose a date', close: true});
        yui_calendar.render();
        window[base_id + 'yui_calendar'] = yui_calendar;
        function handleSelect(type, args, obj){
            var date_arr = args[0][0];
            var year = date_arr[0];
            var month = date_arr[1];
            var day = date_arr[2];
            $(base_id + '_dateinput').value = this.Locale.MONTHS_LONG[month - 1] + ' ' + day + ', ' + year;
            $(base_id + '_datehidden').value = year + '-' + pad2(month) + '-' + pad2(day);
            yui_calendar.hide();
        }
        function handleButton(type, args, obj){
            if ($(base_id + '_datecalendar').style.display == 'block'){
                yui_calendar.hide();
            }else{
                yui_calendar.show();
            }
        }
    	function updateCal() {
    	    var updateFailed = false;
            if (dateinput.value != "") {
                try{
    	            yui_calendar.select(parseDateString(dateinput.value));
    	        }catch(e){
    	            updateFailed = true;
	            }
                var firstDate = yui_calendar.getSelectedDates()[0];
                if (!firstDate){
                    firstDate = yui_calendar.today;
                }
                var month = firstDate.getMonth() + 1;
                var year = firstDate.getFullYear();
                var day = firstDate.getDate();
    	        yui_calendar.cfg.setProperty("pagedate", month + "/" + year);
    	        yui_calendar.render();
    	        if (updateFailed){
                    $(base_id + '_dateinput').value = this.Locale.MONTHS_LONG[month -1] + ' ' + day + ', ' + year;
                }
    	    }
    	}
    	
        YAHOO.util.Event.addListener(base_id + '_datebutton', 'click', handleButton, yui_calendar, true);
        YAHOO.util.Event.addListener(base_id + '_dateinput', 'change', updateCal, yui_calendar, true);
        
        yui_calendar.selectEvent.subscribe(handleSelect, yui_calendar, true);
    });
}

//
//      MULTISELECT and AUTO-COMPLETION
//

function autocompleteUserSummary(name, email){
    return name + NBSP + '(' + email + ')';
}

function autocompleteHardenSpaces(text){
    return text.replace(/\x/g, NBSP);
}

function autocompleteCreateQueryHandler(select){
    var autocompleteGetData = function(query){
        query = decodeURI(query);
        var lcquery = query.toLowerCase();
        var data = []; 
        $(select).getElementsBySelector('option').each(function(opt){
            if (opt.selected) return;
            var name = opt.innerHTML;
            var lastfirst = opt.getAttribute('title');
            var email = opt.getAttribute('label');
            if(name.toLowerCase().startsWith(lcquery)){
                data.push([name, name, autocompleteUserSummary(name, email)]);
            }else if(lastfirst.toLowerCase().startsWith(lcquery)){
                data.push([lastfirst, name, autocompleteUserSummary(name, email)]);
            }else if(email.toLowerCase().startsWith(lcquery)){
                data.push([email, name, autocompleteUserSummary(name, email)]);
            }
        });
        if (!data.length){ // No one found, insert error message
            data.push([query, null, '<span style="color:red;">Nothing matched "' + query + '". Do you need to <a href="/accounts;invite">invite them</a>?</span>']);
        }
        return data;
    };
    return autocompleteGetData;
}

function autocompleteSelectUser(select, yuiName, flag){    
    // Select item in hidden form to send to server
    var options = $(select).getElementsBySelector('option');
    options.each(function(option){
        var name2 = option.innerHTML;
        var email2 = option.label;
        var kinName = autocompleteUserSummary(name2, email2);
        if (yuiName == kinName){
            option.selected = flag;
        }
    });
}

function autocompleteAddFeedback(name, yuiName, context){
    if (name === null) return;
    autocompleteSelectUser(context.select, yuiName, true);
    // Build the feedback text and button
    var remove_button = BUTTON({type: 'button', alt: 'Remove ' + NBSP + name, title: 'Remove' + NBSP + name}, IMG({src: '/images/icon_remove.gif'}));
    var name_span = $('people_view').appendChild(SPAN({}, SPAN({title: yuiName}, TEXT(autocompleteHardenSpaces(name) + NBSP)), remove_button, TEXT(', ')));
    YAHOO.util.Event.addListener(remove_button, 'click', function(evt){
            autocompleteSelectUser(context.select, yuiName, false);
            name_span.parentNode.removeChild(name_span);
        return false;
    });
}

function autocompleteOnSelect(self, item, context){
    // clear input field
    info('autocompleteOnSelect');
    $(context['input']).value = '';
    // extract values to work with
    var selection = item[2];
    var name = selection[1];
    var yuiName = selection[2];
    autocompleteAddFeedback(name, yuiName, context);
    return false;
};

function buildMultiSelect(select, input, output, initially_selected){
    // select is id of select element that we pass back to server
    // input is id of the input element the user types into
    // output is the id of the div that YUI uses for autocomplete suggestions
    // initially_selected is a list of users already selected, from the server
    var datasource = new YAHOO.widget.DS_JSFunction(autocompleteCreateQueryHandler(select));
    datasource.maxCacheEntries = 0;
    var autocomplete = new YAHOO.widget.AutoComplete(input, output, datasource);
    autocomplete.queryDelay = 0;
    autocomplete.prehighlightClassName = "yui-ac-prehighlight"; 
    autocomplete.typeAhead = true; 
    autocomplete.useShadow = false; 
//    autocomplete.forceSelection = true;
//    autocomplete.maxResultsDisplayed = 10;
    autocomplete.allowBrowserAutocomplete = false;
    autocomplete.animVert = true; 
    autocomplete.formatResult = function(result_item, query) { 
          return result_item[2]; // name (email)
    };
    var context = {input: input, autocomplete: autocomplete, select: select, output: output};
    initially_selected.each(function(user){
        autocompleteAddFeedback(user.name, autocompleteUserSummary(user.name, user.email), context);
    });
    autocomplete.itemSelectEvent.subscribe(autocompleteOnSelect, context);
}


////////////////////////////////////////////////////////////
//
//      Save and check values before leaving page without saving
//
////////////////////////////////////////////////////////////

var initialSelects = new Array(new Array());
var initialInputs = new Array(new Array());
var initialTextareas = new Array(new Array());
var initialForms = new Array();
function saveInitialElements() {
  // only save forms with the "formCheck" classname
  initialForms = Element.getElementsByClassName(document.body, "formCheck");
  	
	for (var i = 0; i < initialForms.length; i++) {	
	  var selects = initialForms[i].getElementsByTagName("select");
	  var inputs = new Array();
	  var all_inputs = initialForms[i].getElementsByTagName("input");
	  var textareas = initialForms[i].getElementsByTagName("textarea");
	  
	  // take out inputs that deal with webeditor fonts
	  for (var j = 0; j < all_inputs.length; j++) {
	    if (all_inputs[j].hasClassName("webeditor_select") == false) {
	      inputs.push(all_inputs[j]);
	    }
	  }
	
	  // save select values
  	for (var j = 0; j < selects.length; j++) {
  		initialSelects[i][j] = selects[j].selectedIndex;
  	}
  	
  	// save input values
  	for (var j = 0; j < inputs.length; j++) {
  		// save checked status for checkboxes
  		if(inputs[j].type == "checkbox") {
  			initialInputs[i][j] = inputs[j].checked;
  		}
  		// save value for other inputs
  		else {
  			initialInputs[i][j] = inputs[j].value;
  		}
  	}
  	
  	// save textarea values
  	for (var j = 0; j < textareas.length; j++) {
      initialTextareas[i][j] = textareas[j].value;
      //alert("TEXTAREA: " + textareas[j].value);
  	}
	}
	
	
}

var checkPage = true;
function initializeSafeForms() {
	// save initial form values into initialValues array
	saveInitialElements();
	// get forms that need exit confirmation
	forms = Element.getElementsByClassName(document.body, "formCheck");
	if (forms.length > 0) {
		// when exiting page, get confirmation
		window.onbeforeunload = check;
	}
	// set forms to not check after submitting
	for (i = 0; i < forms.length; i++) {
		Event.observe(forms[i], 'submit', stopCheck);
	}
}

function stopCheck() {
	checkPage = false;
}

function check() {
  hasChanged = false;
  
  // only check forms with the "formCheck" classname
  var forms = Element.getElementsByClassName(document.body, "formCheck");
	
	for (var i = 0; i < forms.length; i++) {
	  var selects = forms[i].getElementsByTagName("select");
	  var inputs = new Array();
	  var all_inputs = forms[i].getElementsByTagName("input");
	  var textareas = forms[i].getElementsByTagName("textarea");
	  
	  // take out inputs that deal with webeditor fonts
	  for (var j = 0; j < all_inputs.length; j++) {
	    if (all_inputs[j].hasClassName("webeditor_select") == false) {
	      inputs.push(all_inputs[j]);
	    }
	  }

    for (var j = 0; j < selects.length; j++) {
      if (initialSelects[i][j] != selects[j].selectedIndex) {
        hasChanged = true;
      }
    }
  		
  	// see if inputs have changed
  	for (var j = 0; j < inputs.length; j++) {
  	  if (inputs[j].type != "hidden") {
    		if (inputs[j].type == "checkbox" && initialInputs[i][j] != inputs[j].checked) {
    			hasChanged = true;
    		}
    		else if (inputs[j].type != "checkbox" && initialInputs[i][j] != inputs[j].value) {
    			hasChanged = true;
    		}
  		}
  	}
  	
  	// see if textareas have changed
    for (var j = 0; j < textareas.length; j++){
      var textarea = textareas[j];
      
  		if (initialTextareas[i][j] != textarea.value) {
  			hasChanged = true;
  		}
  	}
	}
	
	// if we should check pages and form has changed, prompt user for confirmation
	if (checkPage && hasChanged) {
		return "Unsaved changes will be lost.";
	}
}

//
// Google Analytics
//
function initializeAnalytics() {
	var scriptLoadFunction = function(){
		_uacct = "UA-978255-1";
		urchinTracker();
	};
	var e = loadScript("https://ssl.google-analytics.com/urchin.js", scriptLoadFunction);
}

function loadScript(url, aFunction){
	var e = document.createElement("script");
	Event.observe(e, "load", aFunction);
	e.type = "text/javascript";
	e.src = url;
	document.getElementsByTagName("head")[0].appendChild(e);
	return e;
}

//
//      WIDGET INITIALIZATION
//

Event.observe(window, 'load', function(){
    //annotate_for_tracking();
    initializeColorPicker();
    initializeDatePickers();
	initializeSafeForms();
	initializeAnalytics();
});
//
//		COMMENT DELETION
//

function delete_comment(comment_id) {
	$('delete_confirmation_' + comment_id).className = "delete_comment_confirmation";
	Effect.Appear($('delete_confirmation_' + comment_id));
}
     
function hide_delete_confirmation(comment_id) {
	checkPage = true;
	Effect.Fade($('delete_confirmation_' + comment_id));
}
     
function really_delete_comment(comment_id) {
	checkPage = false;
	Effect.Fade($('comment_text_' + comment_id));
	new Ajax.Request('/comments/' + comment_id, {asynchronous:true, evalScripts:true, method:'delete'});
}

function fixSafariURLBug(){
    $$('a').each(function(anchor){
        anchor.href = anchor.href.replace(/;/g, '%3B');        
    });
    $$('form').each(function(frm){
        frm.action = frm.action.replace(/;/g, '%3B');
    });
}
Event.observe(window, 'load', function(){
    if (/Safari/.test(navigator.userAgent)) fixSafariURLBug();
});

function typeOf(value) {
    var s = typeof value;
    if (s === 'object') {
        if (value) {
            if (value instanceof Array) {
                s = 'array';
            }
        } else {
            s = 'null';
        }
    }
    return s;
}

/*
        Single-file uploader scripts
*/

function setDisplayMessage() {
    if (totalFiles() > 0) {
       Element.hide('empty_file_list');
       $('upload_photos_button').disabled = false;
    }
    else {
       Element.show('empty_file_list');
       $('upload_photos_button').disabled = true;
    }
}

function startUpload(evt) {
    if (evt){
        YAHOO.util.Event.stopEvent(evt);            
    }
    showBusyDialog('busy');
    $('total_files').innerHTML = '' + totalFiles();
    sendNextFile();
}

function hideProgressDialog() {
    closeDialog('busy');
}

function ajaxSubmitOneFile(form){
    // the second argument is true to indicate file upload.
    YAHOO.util.Connect.setForm(form, true);
//    trace('Sending file ' + form.immediateDescendants()[0].value);
    YAHOO.util.Connect.asyncRequest('POST', UPLOAD_PATH, {upload: sendNextFile} );
}

var currentlyUploading = -1;
var MAX_FILES_TO_UPLOAD = 30;
var numberOfFeedbackRows = 0;

function totalFiles(){
    return  $('form_list').immediateDescendants().length - 1; // subtract one because we keep a blank form for the next entry
}

function sendNextFile(){
    currentlyUploading += 1;
    if (currentlyUploading >= totalFiles()){ 
        hideProgressDialog();
        window.location = describePhotosUrl;
        $('uploader').hide();
        $('upload_finished').show();
//        trace('we should be closing the progress dialog now and redirecting');
        return;
    }
    showUploadStatus(currentlyUploading + 1);
    ajaxSubmitOneFile($('form_list').immediateDescendants()[currentlyUploading]);
}

function showUploadStatus(index){
    $('file_no').innerHTML = '' + index;
}

function getIndex(node){
    if (!node) return -1;
    if (!node.id){alert("No id for node: " + node.nodeName);};
    return parseInt(node.id.split('_').last()); //extract numeric id from node
}

function safe_hide(node){
    node.style.position = 'absolute';
    node.style.left = '-1000px';
}

function safe_show(node){
    node.style.position = '';
    node.style.left = '';
}

function lastChild(node){
    return node.immediateDescendants().last();
}

function addFeedbackRow(evt){
    YAHOO.util.Event.stopEvent(evt);
    var index = getIndex(this);
    var button = BUTTON({id: 'remove_' + index}, TEXT('Remove'));
    YAHOO.util.Event.addListener(button, 'click', removeFeedbackRow);
    $('files_list').appendChild(DIV({id: 'filename_' + index}, button, TEXT(this.value)));
    var old_form = lastChild($('form_list'));
    numberOfFeedbackRows += 1;
    var new_idx = numberOfFeedbackRows;
    var input = INPUT({id: 'image_' + new_idx, name: 'Filedata', type: 'file'});
    YAHOO.util.Event.addListener(input, 'change', addFeedbackRow);        
    $('form_list').appendChild(FORM({action: old_form.action, enctype: old_form.enctype, id: 'upload_' + new_idx, method: 'POST'}, input));
    safe_hide(old_form);
    if (totalFiles() >= MAX_FILES_TO_UPLOAD){
        safe_hide(new_form); // we still need the new form because of the way we count files to upload
    }
    setDisplayMessage();
}

function removeFeedbackRow(evt){
    YAHOO.util.Event.stopEvent(evt);
    var index = getIndex(this);
    $('upload_' + index).remove();
    $('filename_' + index).remove();
    safe_show($('form_list').lastChild);
    setDisplayMessage();
}

/*
 * Family membership
 */

// move the index'th family up or down
 function swap(index, direction) {
 	node1_id = document.getElementsByClassName("familylinks")[index].id;

 	if (direction == "up") {
		siblings = $(node1_id).up().previousSiblings();
	}
	else {
		siblings = $(node1_id).up().nextSiblings();
	}
	
	found = false;
	for (i = 0; i < siblings.length && found == false; i++) {
		// look for the next family that isn't hidden (i.e. deleted)
		if (siblings[i].getStyle("display") != "none") {
			node2_id = siblings[i].getElementsByClassName("familylinks")[0].id;
			found = true;
		}
	}
 	node1 = $(node1_id);
	node2 = $(node2_id);
	node1_html = node1.innerHTML;
	node1.hide();
	node2.hide();
	
	// switch contents of families
	node1.update(node2.innerHTML);
	node2.update(node1_html);
	node1.id = node2_id;
	node2.id = node1_id;
	
	Effect.Appear(node1);
	Effect.Appear(node2);
 }
 
 // page clean up after deleting a family
 function after_delete(del_node) {
 	// hides the family
	del_node.hide();
	
 	families = document.getElementsByClassName("family");
 	
	// look for the first family that isn't hidden (i.e. deleted)
	found = false;
	for (i = 0; i < families.length && found == false; i++) {
		if (families[i].getStyle("display") != "none") {
			// delete the "Move up" link for first visible family if it exists
			up_link = families[i].getElementsByClassName("familymoveup");
			if (up_link.length > 0) {
				up_link[0].remove();
			}
			found = true;
		}
	}
	
	// look for the last family that isn't hidden (i.e. deleted)
	found = false;
	for (i = (families.length - 1); i >= 0 && found == false; i--) {
		if (families[i].getStyle("display") != "none") {
			// delete the "Move down" link for last visible family if it exists
			down_link = families[i].getElementsByClassName("familymovedown");
			if (down_link.length > 0) {
				down_link[0].remove();
			}
			found = true;
		}
	}
}

function show_more_families() {
	var families = $("morefamilies");
	document.body.appendChild(families);
	families.show();
	$("morefamiliesimg").src = "/images/family-tabs/triangle_up.png";
	$("morefamiliesimg").alt = "show";
	
	// find absolute position of menu
	curtop = 26 + $("morefamilieslink").offsetTop;
	curleft = 63 + $("morefamilieslink").offsetLeft;
	obj = $("morefamilieslink");
	while (obj = obj.offsetParent) {
		curtop += obj.offsetTop;
		curleft += obj.offsetLeft;
	}
	families.style.top = curtop + "px";
	families.style.left = curleft + "px";
}

function hide_more_families() {
	$("morefamilies").hide();
	$("morefamiliesimg").src = "/images/family-tabs/triangle_down.png";
	$("morefamiliesimg").alt = "hide";
}

function toggle_more_families() {
	if ($("morefamiliesimg").alt == "show") {
		hide_more_families();
	}
	else {
		show_more_families();
	}
}

/* hides the More families menu if you click off the menu */
Event.observe(document, 'click', function(e) {
	if (($("morefamilieslink") != null) && (!Event.element(e).descendantOf($("morefamiliestab")))) {
		hide_more_families();
	}
});

/* hides feedback lightbox after sending message */

function endFeedback() {
	closeDialog('feedback_holder');
	notice = document.getElementsByClassName('notice');
	notice[0].innerHTML = "Thank you for your feedback!  We'll reply back soon.";
	Element.show(notice[0]);
}

function after_rotate() {
  // refresh the rotated images to show the new photo
	var images = document.getElementsByClassName("croppable");
	for (var i = 0; i < images.length; i++)
	{
	  var new_img = images[i].cloneNode(false);
	  new_img.setAttribute("src", images[i].getAttribute("src") + "?new"); // appended param to force redrawing the image
	  images[i].parentNode.replaceChild(new_img, images[i]);
	}
	closeDialog('busy');
}
