/*
 * jQuery Form Plugin
 * version: 2.21 (08-FEB-2009)
 * @requires jQuery v1.2.2 or later
 *
 * Examples and documentation at: http://malsup.com/jquery/form/
 * Dual licensed under the MIT and GPL licenses:
 *   http://www.opensource.org/licenses/mit-license.php
 *   http://www.gnu.org/licenses/gpl.html
 */
;(function($) {

/*
    Usage Note:  
    -----------
    Do not use both ajaxSubmit and ajaxForm on the same form.  These
    functions are intended to be exclusive.  Use ajaxSubmit if you want
    to bind your own submit handler to the form.  For example,

    $(document).ready(function() {
        $('#myForm').bind('submit', function() {
            $(this).ajaxSubmit({
                target: '#output'
            });
            return false; // <-- important!
        });
    });

    Use ajaxForm when you want the plugin to manage all the event binding
    for you.  For example,

    $(document).ready(function() {
        $('#myForm').ajaxForm({
            target: '#output'
        });
    });
        
    When using ajaxForm, the ajaxSubmit function will be invoked for you
    at the appropriate time.  
*/

/**
 * ajaxSubmit() provides a mechanism for immediately submitting 
 * an HTML form using AJAX.
 */
$.fn.ajaxSubmit = function(options) {
    // fast fail if nothing selected (http://dev.jquery.com/ticket/2752)
    if (!this.length) {
        log('ajaxSubmit: skipping submit process - no element selected');
        return this;
    }

    if (typeof options == 'function')
        options = { success: options };

    options = $.extend({
        url:  this.attr('action') || window.location.toString(),
        type: this.attr('method') || 'GET'
    }, options || {});

    // hook for manipulating the form data before it is extracted;
    // convenient for use with rich editors like tinyMCE or FCKEditor
    var veto = {};
    this.trigger('form-pre-serialize', [this, options, veto]);
    if (veto.veto) {
        log('ajaxSubmit: submit vetoed via form-pre-serialize trigger');
        return this;
    }

    // provide opportunity to alter form data before it is serialized
    if (options.beforeSerialize && options.beforeSerialize(this, options) === false) {
        log('ajaxSubmit: submit aborted via beforeSerialize callback');
        return this;
    }    
   
    var a = this.formToArray(options.semantic);
    if (options.data) {
        options.extraData = options.data;
        for (var n in options.data) {
          if(options.data[n] instanceof Array) {
            for (var k in options.data[n])
              a.push( { name: n, value: options.data[n][k] } )
          }  
          else
             a.push( { name: n, value: options.data[n] } );
        }
    }

    // give pre-submit callback an opportunity to abort the submit
    if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) {
        log('ajaxSubmit: submit aborted via beforeSubmit callback');
        return this;
    }    

    // fire vetoable 'validate' event
    this.trigger('form-submit-validate', [a, this, options, veto]);
    if (veto.veto) {
        log('ajaxSubmit: submit vetoed via form-submit-validate trigger');
        return this;
    }    

    var q = $.param(a);

    if (options.type.toUpperCase() == 'GET') {
        options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q;
        options.data = null;  // data is null for 'get'
    }
    else
        options.data = q; // data is the query string for 'post'

    var $form = this, callbacks = [];
    if (options.resetForm) callbacks.push(function() { $form.resetForm(); });
    if (options.clearForm) callbacks.push(function() { $form.clearForm(); });

    // perform a load on the target only if dataType is not provided
    if (!options.dataType && options.target) {
        var oldSuccess = options.success || function(){};
        callbacks.push(function(data) {
            $(options.target).html(data).each(oldSuccess, arguments);
        });
    }
    else if (options.success)
        callbacks.push(options.success);

    options.success = function(data, status) {
        for (var i=0, max=callbacks.length; i < max; i++)
            callbacks[i].apply(options, [data, status, $form]);
    };

    // are there files to upload?
    var files = $('input:file', this).fieldValue();
    var found = false;
    for (var j=0; j < files.length; j++)
        if (files[j])
            found = true;

    // options.iframe allows user to force iframe mode
   if (options.iframe || found) { 
       // hack to fix Safari hang (thanks to Tim Molendijk for this)
       // see:  http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d
       if (options.closeKeepAlive)
           $.get(options.closeKeepAlive, fileUpload);
       else
           fileUpload();
       }
   else
       $.ajax(options);

    // fire 'notify' event
    this.trigger('form-submit-notify', [this, options]);
    return this;


    // private function for handling file uploads (hat tip to YAHOO!)
    function fileUpload() {
        var form = $form[0];
        
        if ($(':input[name=submit]', form).length) {
            alert('Error: Form elements must not be named "submit".');
            return;
        }
        
        var opts = $.extend({}, $.ajaxSettings, options);
		var s = jQuery.extend(true, {}, $.extend(true, {}, $.ajaxSettings), opts);

        var id = 'jqFormIO' + (new Date().getTime());
        var $io = $('<iframe id="' + id + '" name="' + id + '" src="about:blank" />');
        var io = $io[0];

        $io.css({ position: 'absolute', top: '-1000px', left: '-1000px' });

        var xhr = { // mock object
            aborted: 0,
            responseText: null,
            responseXML: null,
            status: 0,
            statusText: 'n/a',
            getAllResponseHeaders: function() {},
            getResponseHeader: function() {},
            setRequestHeader: function() {},
            abort: function() { 
                this.aborted = 1; 
                $io.attr('src','about:blank'); // abort op in progress
            }
        };

        var g = opts.global;
        // trigger ajax global events so that activity/block indicators work like normal
        if (g && ! $.active++) $.event.trigger("ajaxStart");
        if (g) $.event.trigger("ajaxSend", [xhr, opts]);

		if (s.beforeSend && s.beforeSend(xhr, s) === false) {
			s.global && jQuery.active--;
			return;
        }
        if (xhr.aborted)
            return;
        
        var cbInvoked = 0;
        var timedOut = 0;

        // add submitting element to data if we know it
        var sub = form.clk;
        if (sub) {
            var n = sub.name;
            if (n && !sub.disabled) {
                options.extraData = options.extraData || {};
                options.extraData[n] = sub.value;
                if (sub.type == "image") {
                    options.extraData[name+'.x'] = form.clk_x;
                    options.extraData[name+'.y'] = form.clk_y;
                }
            }
        }

        // take a breath so that pending repaints get some cpu time before the upload starts
        setTimeout(function() {
            // make sure form attrs are set
            var t = $form.attr('target'), a = $form.attr('action');

			// update form attrs in IE friendly way
			form.setAttribute('target',id);
			if (form.getAttribute('method') != 'POST')
				form.setAttribute('method', 'POST');
			if (form.getAttribute('action') != opts.url)
				form.setAttribute('action', opts.url);
							
            // ie borks in some cases when setting encoding
            if (! options.skipEncodingOverride) {
                $form.attr({
                    encoding: 'multipart/form-data',
                    enctype:  'multipart/form-data'
                });
            }

            // support timout
            if (opts.timeout)
                setTimeout(function() { timedOut = true; cb(); }, opts.timeout);

            // add "extra" data to form if provided in options
            var extraInputs = [];
            try {
                if (options.extraData)
                    for (var n in options.extraData)
                        extraInputs.push(
                            $('<input type="hidden" name="'+n+'" value="'+options.extraData[n]+'" />')
                                .appendTo(form)[0]);
            
                // add iframe to doc and submit the form
                $io.appendTo('body');
                io.attachEvent ? io.attachEvent('onload', cb) : io.addEventListener('load', cb, false);
                form.submit();
            }
            finally {
                // reset attrs and remove "extra" input elements
				form.setAttribute('action',a);
                t ? form.setAttribute('target', t) : $form.removeAttr('target');
                $(extraInputs).remove();
            }
        }, 10);

        var nullCheckFlag = 0;
		
        function cb() {
            if (cbInvoked++) return;
            
            io.detachEvent ? io.detachEvent('onload', cb) : io.removeEventListener('load', cb, false);

            var ok = true;
            try {
                if (timedOut) throw 'timeout';
                // extract the server response from the iframe
                var data, doc;

                doc = io.contentWindow ? io.contentWindow.document : io.contentDocument ? io.contentDocument : io.document;
                
                if ((doc.body == null || doc.body.innerHTML == '') && !nullCheckFlag) {
                    // in some browsers (cough, Opera 9.2.x) the iframe DOM is not always traversable when
                    // the onload callback fires, so we give them a 2nd chance
                    nullCheckFlag = 1;
                    cbInvoked--;
                    setTimeout(cb, 100);
                    return;
                }
                
                xhr.responseText = doc.body ? doc.body.innerHTML : null;
                xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;
                xhr.getResponseHeader = function(header){
                    var headers = {'content-type': opts.dataType};
                    return headers[header];
                };

                if (opts.dataType == 'json' || opts.dataType == 'script') {
                    var ta = doc.getElementsByTagName('textarea')[0];
                    xhr.responseText = ta ? ta.value : xhr.responseText;
                }
                else if (opts.dataType == 'xml' && !xhr.responseXML && xhr.responseText != null) {
                    xhr.responseXML = toXml(xhr.responseText);
                }
                data = $.httpData(xhr, opts.dataType);
            }
            catch(e){
                ok = false;
                $.handleError(opts, xhr, 'error', e);
            }

            // ordering of these callbacks/triggers is odd, but that's how $.ajax does it
            if (ok) {
                opts.success(data, 'success');
                if (g) $.event.trigger("ajaxSuccess", [xhr, opts]);
            }
            if (g) $.event.trigger("ajaxComplete", [xhr, opts]);
            if (g && ! --$.active) $.event.trigger("ajaxStop");
            if (opts.complete) opts.complete(xhr, ok ? 'success' : 'error');

            // clean up
            setTimeout(function() {
                $io.remove();
                xhr.responseXML = null;
            }, 100);
        };

        function toXml(s, doc) {
            if (window.ActiveXObject) {
                doc = new ActiveXObject('Microsoft.XMLDOM');
                doc.async = 'false';
                doc.loadXML(s);
            }
            else
                doc = (new DOMParser()).parseFromString(s, 'text/xml');
            return (doc && doc.documentElement && doc.documentElement.tagName != 'parsererror') ? doc : null;
        };
    };
};

/**
 * ajaxForm() provides a mechanism for fully automating form submission.
 *
 * The advantages of using this method instead of ajaxSubmit() are:
 *
 * 1: This method will include coordinates for <input type="image" /> elements (if the element
 *    is used to submit the form).
 * 2. This method will include the submit element's name/value data (for the element that was
 *    used to submit the form).
 * 3. This method binds the submit() method to the form for you.
 *
 * The options argument for ajaxForm works exactly as it does for ajaxSubmit.  ajaxForm merely
 * passes the options argument along after properly binding events for submit elements and
 * the form itself.
 */ 
$.fn.ajaxForm = function(options) {
    return this.ajaxFormUnbind().bind('submit.form-plugin',function() {
        $(this).ajaxSubmit(options);
        return false;
    }).each(function() {
        // store options in hash
        $(":submit,input:image", this).bind('click.form-plugin',function(e) {
            var form = this.form;
            form.clk = this;
            if (this.type == 'image') {
                if (e.offsetX != undefined) {
                    form.clk_x = e.offsetX;
                    form.clk_y = e.offsetY;
                } else if (typeof $.fn.offset == 'function') { // try to use dimensions plugin
                    var offset = $(this).offset();
                    form.clk_x = e.pageX - offset.left;
                    form.clk_y = e.pageY - offset.top;
                } else {
                    form.clk_x = e.pageX - this.offsetLeft;
                    form.clk_y = e.pageY - this.offsetTop;
                }
            }
            // clear form vars
            setTimeout(function() { form.clk = form.clk_x = form.clk_y = null; }, 10);
        });
    });
};

// ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm
$.fn.ajaxFormUnbind = function() {
    this.unbind('submit.form-plugin');
    return this.each(function() {
        $(":submit,input:image", this).unbind('click.form-plugin');
    });

};

/**
 * formToArray() gathers form element data into an array of objects that can
 * be passed to any of the following ajax functions: $.get, $.post, or load.
 * Each object in the array has both a 'name' and 'value' property.  An example of
 * an array for a simple login form might be:
 *
 * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
 *
 * It is this array that is passed to pre-submit callback functions provided to the
 * ajaxSubmit() and ajaxForm() methods.
 */
$.fn.formToArray = function(semantic) {
    var a = [];
    if (this.length == 0) return a;

    var form = this[0];
    var els = semantic ? form.getElementsByTagName('*') : form.elements;
    if (!els) return a;
    for(var i=0, max=els.length; i < max; i++) {
        var el = els[i];
        var n = el.name;
        if (!n) continue;

        if (semantic && form.clk && el.type == "image") {
            // handle image inputs on the fly when semantic == true
            if(!el.disabled && form.clk == el)
                a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
            continue;
        }

        var v = $.fieldValue(el, true);
        if (v && v.constructor == Array) {
            for(var j=0, jmax=v.length; j < jmax; j++)
                a.push({name: n, value: v[j]});
        }
        else if (v !== null && typeof v != 'undefined')
            a.push({name: n, value: v});
    }

    if (!semantic && form.clk) {
        // input type=='image' are not found in elements array! handle them here
        var inputs = form.getElementsByTagName("input");
        for(var i=0, max=inputs.length; i < max; i++) {
            var input = inputs[i];
            var n = input.name;
            if(n && !input.disabled && input.type == "image" && form.clk == input)
                a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
        }
    }
    return a;
};

/**
 * Serializes form data into a 'submittable' string. This method will return a string
 * in the format: name1=value1&amp;name2=value2
 */
$.fn.formSerialize = function(semantic) {
    //hand off to jQuery.param for proper encoding
    return $.param(this.formToArray(semantic));
};

/**
 * Serializes all field elements in the jQuery object into a query string.
 * This method will return a string in the format: name1=value1&amp;name2=value2
 */
$.fn.fieldSerialize = function(successful) {
    var a = [];
    this.each(function() {
        var n = this.name;
        if (!n) return;
        var v = $.fieldValue(this, successful);
        if (v && v.constructor == Array) {
            for (var i=0,max=v.length; i < max; i++)
                a.push({name: n, value: v[i]});
        }
        else if (v !== null && typeof v != 'undefined')
            a.push({name: this.name, value: v});
    });
    //hand off to jQuery.param for proper encoding
    return $.param(a);
};

/**
 * Returns the value(s) of the element in the matched set.  For example, consider the following form:
 *
 *  <form><fieldset>
 *      <input name="A" type="text" />
 *      <input name="A" type="text" />
 *      <input name="B" type="checkbox" value="B1" />
 *      <input name="B" type="checkbox" value="B2"/>
 *      <input name="C" type="radio" value="C1" />
 *      <input name="C" type="radio" value="C2" />
 *  </fieldset></form>
 *
 *  var v = $(':text').fieldValue();
 *  // if no values are entered into the text inputs
 *  v == ['','']
 *  // if values entered into the text inputs are 'foo' and 'bar'
 *  v == ['foo','bar']
 *
 *  var v = $(':checkbox').fieldValue();
 *  // if neither checkbox is checked
 *  v === undefined
 *  // if both checkboxes are checked
 *  v == ['B1', 'B2']
 *
 *  var v = $(':radio').fieldValue();
 *  // if neither radio is checked
 *  v === undefined
 *  // if first radio is checked
 *  v == ['C1']
 *
 * The successful argument controls whether or not the field element must be 'successful'
 * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
 * The default value of the successful argument is true.  If this value is false the value(s)
 * for each element is returned.
 *
 * Note: This method *always* returns an array.  If no valid value can be determined the
 *       array will be empty, otherwise it will contain one or more values.
 */
$.fn.fieldValue = function(successful) {
    for (var val=[], i=0, max=this.length; i < max; i++) {
        var el = this[i];
        var v = $.fieldValue(el, successful);
        if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length))
            continue;
        v.constructor == Array ? $.merge(val, v) : val.push(v);
    }
    return val;
};

/**
 * Returns the value of the field element.
 */
$.fieldValue = function(el, successful) {
    var n = el.name, t = el.type, tag = el.tagName.toLowerCase();
    if (typeof successful == 'undefined') successful = true;

    if (successful && (!n || el.disabled || t == 'reset' || t == 'button' ||
        (t == 'checkbox' || t == 'radio') && !el.checked ||
        (t == 'submit' || t == 'image') && el.form && el.form.clk != el ||
        tag == 'select' && el.selectedIndex == -1))
            return null;

    if (tag == 'select') {
        var index = el.selectedIndex;
        if (index < 0) return null;
        var a = [], ops = el.options;
        var one = (t == 'select-one');
        var max = (one ? index+1 : ops.length);
        for(var i=(one ? index : 0); i < max; i++) {
            var op = ops[i];
            if (op.selected) {
				var v = op.value;
				if (!v) // extra pain for IE...
                	v = (op.attributes && op.attributes['value'] && !(op.attributes['value'].specified)) ? op.text : op.value;
                if (one) return v;
                a.push(v);
            }
        }
        return a;
    }
    return el.value;
};

/**
 * Clears the form data.  Takes the following actions on the form's input fields:
 *  - input text fields will have their 'value' property set to the empty string
 *  - select elements will have their 'selectedIndex' property set to -1
 *  - checkbox and radio inputs will have their 'checked' property set to false
 *  - inputs of type submit, button, reset, and hidden will *not* be effected
 *  - button elements will *not* be effected
 */
$.fn.clearForm = function() {
    return this.each(function() {
        $('input,select,textarea', this).clearFields();
    });
};

/**
 * Clears the selected form elements.
 */
$.fn.clearFields = $.fn.clearInputs = function() {
    return this.each(function() {
        var t = this.type, tag = this.tagName.toLowerCase();
        if (t == 'text' || t == 'password' || tag == 'textarea')
            this.value = '';
        else if (t == 'checkbox' || t == 'radio')
            this.checked = false;
        else if (tag == 'select')
            this.selectedIndex = -1;
    });
};

/**
 * Resets the form data.  Causes all form elements to be reset to their original value.
 */
$.fn.resetForm = function() {
    return this.each(function() {
        // guard against an input with the name of 'reset'
        // note that IE reports the reset function as an 'object'
        if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType))
            this.reset();
    });
};

/**
 * Enables or disables any matching elements.
 */
$.fn.enable = function(b) { 
    if (b == undefined) b = true;
    return this.each(function() { 
        this.disabled = !b 
    });
};

/**
 * Checks/unchecks any matching checkboxes or radio buttons and
 * selects/deselects and matching option elements.
 */
$.fn.selected = function(select) {
    if (select == undefined) select = true;
    return this.each(function() { 
        var t = this.type;
        if (t == 'checkbox' || t == 'radio')
            this.checked = select;
        else if (this.tagName.toLowerCase() == 'option') {
            var $sel = $(this).parent('select');
            if (select && $sel[0] && $sel[0].type == 'select-one') {
                // deselect all other options
                $sel.find('option').selected(false);
            }
            this.selected = select;
        }
    });
};

// helper fn for console logging
// set $.fn.ajaxSubmit.debug to true to enable debug logging
function log() {
    if ($.fn.ajaxSubmit.debug && window.console && window.console.log)
        window.console.log('[jquery.form] ' + Array.prototype.join.call(arguments,''));
};

/*
 * Style File - jQuery plugin for styling file input elements
 *
 * Copyright (c) 2007-2008 Mika Tuupola
 *
 * Licensed under the MIT license:
 *   http://www.opensource.org/licenses/mit-license.php
 *
 * Based on work by Shaun Inman
 *   http://www.shauninman.com/archive/2007/09/10/styling_file_inputs_with_css_and_the_dom
 *
 * Revision: $Id: jquery.filestyle.js 303 2008-01-30 13:53:24Z tuupola $
 *
 */

$.fn.filestyle = function(options) {

    /* TODO: This should not override CSS. */
    var settings = {
        width : 250
    };

    if(options) {
        $.extend(settings, options);
    };

    return this.each(function() {

        var self = this;
        var wrapper = $("<div>").css({
          "width": settings.imagewidth + "px",
          "height": settings.imageheight + "px",
          "background": "url(" + settings.image + ") 0 0 no-repeat",
          "background-position": "right",
          "display": "inline",
          "position": "relative",
          "float": "left",
          "overflow": "hidden"
        }).addClass('fileStyle');

				var _once = false;
        $(self)
					.wrap(wrapper)
					.css({
            "position": "absolute",
            "left": -124,
            "height": settings.imageheight + "px",
            "width": settings.width + "px",
            "display": "inline",
            "opacity": 0
          }).click(function(eve) {
						//eve.stopPropagation();
					});


				$(self).parent('div.fileStyle')
					.click(function() {
						//$(self).trigger('click');
					});

        if ($.browser.mozilla) {
            if (/Win/.test(navigator.platform)) {
                $(self).css("margin-left", "-142px");
            } else {
                $(self).css("margin-left", "-168px");
            }
        } else {
            $(self).css("margin-left", settings.imagewidth - settings.width + "px");
        }
    });
};

})(jQuery);

/** {doc}
 * Software License Agreement (BSD License)
 * 
 * Copyright (c) 2008, Dave Hauenstein
 * All rights reserved.
 * 
 * Redistribution and use of this software in source and binary forms, with or
 * without modification, are permitted provided that the following conditions
 * are met:
 * 
 * - Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 * 
 * - Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 * 
 * - Neither the name of Dave Hauenstein nor the names of its contributors may be
 *   used to endorse or promote products derived from this software without
 *   specific prior written permission of Dave Hauenstein
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

/*
* @param String   options[url]                  url that post request will hit
* @param String   options[params]               paramters sent via the post request to the server; string; ex: name=dave&last_name=hauenstein
* @param String   options[field_type]           can be: text, textarea, select; default: text
* @param String   options[select_options]       this is a string seperated by commas for the dropdown options, if field_type is dropdown
* @param String   options[textarea_cols]        number of columns textarea will have, if field_type is textarea; default: 25
* @param String   options[textarea_rows]        number of rows textarea will have, if field_type is textarea; default: 10
* @param String   options[bg_over]              background color of editable elements on HOVER
* @param String   options[bg_out]               background color of editable elements on RESTORE from hover
* @param String   options[saving_text]          text to be used when server is saving information; default: 'Saving...'
* @param String   options[saving_image]         specify an image location instead of text while server is saving; default: uses saving text
* @param String   options[default_text]         text to show up if the element that has this functionality is empty
* @param String   options[select_text]          default text to show up in select box
* @param String   options[value_required]       if set to true, the element will not be saved unless a value is entered
* @param String   options[element_id]           name of parameter holding element_id; default: element_id
* @param String   options[update_value]         name of parameter holding update_value; default: update_value
* @param String   options[original_html]        name of parameter holding original_html; default: original_html
* @param String   options[save_button]          image button tag to use as "Save" button
* @param String   options[cancel_button]        image button tag to use as "Cancel" button
* @param String   options[show_buttons]         will show the buttons: cancel or save; will automatically cancel out the onBlur functionality
* @param String   options[on_blur]              what to do on blur: "save" or null; default: "save"; will be overridden if $param show_buttons is true
* @param Function options[callback]             call function instead of submitting to url
* @param Function options[success]              this function gets called if server responds with a success
* @param Function options[error]                this function gets called if server responds with an error
*/
jQuery.fn.editInPlace = function(options) {

    /* DEFINE THE DEFAULT SETTINGS, SWITCH THEM WITH THE OPTIONS USER PROVIDES */
  var settings = {

           url : "",
        params : "",
    field_type : "text",
select_options : "",
 textarea_cols : "25",
 textarea_rows : "10",
       bg_over : "#ffc",
        bg_out : "transparent",
   saving_text : "Saving...",
  saving_image : "",
  default_text : "(Click here to add text)",
   select_text : "Choose new value",
value_required : null,
    element_id : "element_id",
  update_value : "update_value",
 original_html : "original_html",
   save_button : '<input type="submit" class="inplace_save" value="Save"/>',
 cancel_button : '<input type="submit" class="inplace_cancel" value="Cancel"/>',
  show_buttons : false,
       on_blur : "save",
      callback : null,
       success : null,
         error : function(request){
                   alert("Failed to save value: " + request.responseText || 'Unspecified Error');
                 }
  };

    if(options) {
        jQuery.extend(settings, options);
    }

    /* preload the loading icon if it exists */
    if(settings.saving_image != ""){
        var loading_image = new Image();
        loading_image.src = settings.saving_image;
    }

    /* THIS FUNCTION WILL TRIM WHITESPACE FROM BEFORE/AFTER A STRING */
    String.prototype.trim = function() {
        return this.replace(/^\s+/, '')
                             .replace(/\s+$/, '');
    };

    /* THIS FUNCTION WILL ESCAPE ANY HTML ENTITIES SO "Quoted Values" work */
    String.prototype.escape_html = function() {
        return this.replace(/&/g, "&amp;")
                             .replace(/</g, "&lt;")
                             .replace(/>/g, "&gt;")
                             .replace(/"/g, "&quot;");
  };

    /* CREATE THE INPLACE EDITOR */
    return this.each(function(){

        if(jQuery(this).html() == "") jQuery(this).html(settings.default_text);

        var editing = false;

        //save the original element - for change of scope
        var original_element = jQuery(this);

        var click_count = 0;

        jQuery(this)

        .mouseover(function(){
            jQuery(this).css("background", settings.bg_over);
        })

        .mouseout(function(){
            jQuery(this).css("background", settings.bg_out);
        })

        .click(function(){
            click_count++;

            if(!editing)
            {
                editing = true;

                //save original text - for cancellation functionality
                var original_html = jQuery(this).html();
                var buttons_code  = (settings.show_buttons) ? settings.save_button + ' ' + settings.cancel_button : '';

                //if html is our default text, clear it out to prevent saving accidentally
                if (original_html == settings.default_text) jQuery(this).html('');

                if (settings.field_type == "textarea")
                {
                    var use_field_type = '<textarea name="inplace_value" class="inplace_field" rows="' + settings.textarea_rows + '" cols="' + settings.textarea_cols + '">' + jQuery(this).text().trim().escape_html() + '</textarea>';
                }
                else if(settings.field_type == "text")
                {
                    var use_field_type = '<input type="text" name="inplace_value" class="inplace_field" value="' +
                                            jQuery(this).text().trim().escape_html() + '" />';
                }
                else if(settings.field_type == "select")
                {
                    var optionsArray = settings.select_options.split(',');
                    var use_field_type = '<select name="inplace_value" class="inplace_field"><option value="">' + settings.select_text + '</option>';
                        for(var i=0; i<optionsArray.length; i++){
                            var optionsValuesArray = optionsArray[i].split(':');
                            var use_value = optionsValuesArray[1] || optionsValuesArray[0];
                            var selected = use_value == original_html ? 'selected="selected" ' : '';
                            use_field_type += '<option ' + selected + 'value="' + use_value.trim().escape_html() + '">' + optionsValuesArray[0].trim().escape_html() + '</option>';
                        }
                        use_field_type += '</select>';
                }

                /* insert the new in place form after the element they click, then empty out the original element */
                jQuery(this).html('<form class="inplace_form" style="display: inline; margin: 0; padding: 0;">' + use_field_type + ' ' + buttons_code + '</form>');

            }/* END- if(!editing) -END */

            if(click_count == 1)
            {
                function cancelAction()
                {
                    editing = false;
                    click_count = 0;

                    /* put the original background color in */
                    original_element.css("background", settings.bg_out);

                    /* put back the original text */
                    original_element.html(original_html);

                    return false;
                }

                function saveAction()
                {
                    /* put the original background color in */
                    original_element.css("background", settings.bg_out);

                    var this_elem = jQuery(this);

                    var new_html = (this_elem.is('form')) ? this_elem.children(0).val() : this_elem.parent().children(0).val();

                    /* set saving message */
                    if(settings.saving_image != ""){
                        var saving_message = '<img src="' + settings.saving_image + '" alt="Saving..." />';
                    } else {
                        var saving_message = settings.saving_text;
                    }

                    /* place the saving text/image in the original element */
                    original_element.html(saving_message);

                    if(settings.params != ""){
                        settings.params = "&" + settings.params;
                    }

                    if(settings.callback) {
                        html = settings.callback(original_element.attr("id"), new_html, original_html, settings.params);
                        editing = false;
                        click_count = 0;
                        if (html) {
                            /* put the newly updated info into the original element */
                            original_element.html(html || new_html);
                        } else {
                            /* failure; put original back */
                            alert("Failed to save value: " + new_html);
                            original_element.html(original_html);
                        }
                    } else if (settings.value_required && (new_html == "" || new_html == undefined)) {
                        editing = false;
                        click_count = 0;
                        original_element.html(original_html);
                        alert("Error: You must enter a value to save this field");
                    } else {
                        jQuery.ajax({
                            url: settings.url,
                            type: "POST",
                            data: settings.update_value + '=' + new_html + '&' + settings.element_id + '=' + original_element.attr("id") + settings.params + '&' + settings.original_html + '=' + original_html,
                            dataType: "html",
                            complete: function(request){
                                editing = false;
                                click_count = 0;
                            },
                            success: function(html){
                                /* if the text returned by the server is empty, */
                                /* put a marker as text in the original element */
                                var new_text = html || settings.default_text;

                                /* put the newly updated info into the original element */
                                original_element.html(new_text);
                                if (settings.success) settings.success(html, original_element);
                            },
                            error: function(request) {
                                original_element.html(original_html);
                                if (settings.error) settings.error(request, original_element);
                            }
                        });
                    }

                    return false;
                }

                /* set the focus to the new input element */
                original_element.children("form").children(".inplace_field").focus().select();

                /* CLICK CANCEL BUTTON functionality */
                original_element.children("form").children(".inplace_cancel").click(cancelAction);

                /* CLICK SAVE BUTTON functionality */
                original_element.children("form").children(".inplace_save").click(saveAction);

                /* if cancel/save buttons should be shown, cancel blur functionality */
                if(!settings.show_buttons){
                    /* if on_blur is set to save, set the save funcion */
                    if(settings.on_blur == "save")
                        original_element.children("form").children(".inplace_field").blur(saveAction);
                    /* if on_blur is set to cancel, set the cancel funcion */
                    else
                        original_element.children("form").children(".inplace_field").blur(cancelAction);
                }

                /* hit esc key */
                $(document).keyup(function(event){
                    if (event.keyCode == 27) {
                        cancelAction();
                    }
                });

                original_element.children("form").submit(saveAction);

            }/* END- if(click_count == 1) -END */
        });
    });
};


/*!
 * jQuery corner plugin: simple corner rounding
 * Examples and documentation at: http://jquery.malsup.com/corner/
 * version 2.03 (05-DEC-2009)
 * Dual licensed under the MIT and GPL licenses:
 * http://www.opensource.org/licenses/mit-license.php
 * http://www.gnu.org/licenses/gpl.html
 */

/**
 *  corner() takes a single string argument:  $('#myDiv').corner("effect corners width")
 *
 *  effect:  name of the effect to apply, such as round, bevel, notch, bite, etc (default is round). 
 *  corners: one or more of: top, bottom, tr, tl, br, or bl. 
 *           by default, all four corners are adorned. 
 *  width:   width of the effect; in the case of rounded corners this is the radius. 
 *           specify this value using the px suffix such as 10px (and yes, it must be pixels).
 *
 * @author Dave Methvin (http://methvin.com/jquery/jq-corner.html)
 * @author Mike Alsup   (http://jquery.malsup.com/corner/)
 */
;(function($) { 

var ua = navigator.userAgent;
var moz = $.browser.mozilla && /gecko/i.test(ua);
var webkit = $.browser.safari && /Safari\/[5-9]/.test(ua);

var expr = $.browser.msie && (function() {
    var div = document.createElement('div');
    try { div.style.setExpression('width','0+0'); div.style.removeExpression('width'); }
    catch(e) { return false; }
    return true;
})();
    
function sz(el, p) { 
    return parseInt($.css(el,p))||0; 
};
function hex2(s) {
    var s = parseInt(s).toString(16);
    return ( s.length < 2 ) ? '0'+s : s;
};
function gpc(node) {
    for ( ; node && node.nodeName.toLowerCase() != 'html'; node = node.parentNode ) {
        var v = $.css(node,'backgroundColor');
        if (v == 'rgba(0, 0, 0, 0)')
            continue; // webkit
        if (v.indexOf('rgb') >= 0) { 
            var rgb = v.match(/\d+/g); 
            return '#'+ hex2(rgb[0]) + hex2(rgb[1]) + hex2(rgb[2]);
        }
        if ( v && v != 'transparent' )
            return v;
    }
    return '#ffffff';
};

function getWidth(fx, i, width) {
    switch(fx) {
    case 'round':  return Math.round(width*(1-Math.cos(Math.asin(i/width))));
    case 'cool':   return Math.round(width*(1+Math.cos(Math.asin(i/width))));
    case 'sharp':  return Math.round(width*(1-Math.cos(Math.acos(i/width))));
    case 'bite':   return Math.round(width*(Math.cos(Math.asin((width-i-1)/width))));
    case 'slide':  return Math.round(width*(Math.atan2(i,width/i)));
    case 'jut':    return Math.round(width*(Math.atan2(width,(width-i-1))));
    case 'curl':   return Math.round(width*(Math.atan(i)));
    case 'tear':   return Math.round(width*(Math.cos(i)));
    case 'wicked': return Math.round(width*(Math.tan(i)));
    case 'long':   return Math.round(width*(Math.sqrt(i)));
    case 'sculpt': return Math.round(width*(Math.log((width-i-1),width)));
    case 'dog':    return (i&1) ? (i+1) : width;
    case 'dog2':   return (i&2) ? (i+1) : width;
    case 'dog3':   return (i&3) ? (i+1) : width;
    case 'fray':   return (i%2)*width;
    case 'notch':  return width; 
    case 'bevel':  return i+1;
    }
};

$.fn.corner = function(options) {
    // in 1.3+ we can fix mistakes with the ready state
	if (this.length == 0) {
        if (!$.isReady && this.selector) {
            var s = this.selector, c = this.context;
            $(function() {
                $(s,c).corner(options);
            });
        }
        return this;
	}

    return this.each(function(index){
		var $this = $(this);
		var o = [ options || '', $this.attr($.fn.corner.defaults.metaAttr) || ''].join(' ').toLowerCase();
		//var o = (options || $this.attr($.fn.corner.defaults.metaAttr) || '').toLowerCase();
		var keep = /keep/.test(o);                       // keep borders?
		var cc = ((o.match(/cc:(#[0-9a-f]+)/)||[])[1]);  // corner color
		var sc = ((o.match(/sc:(#[0-9a-f]+)/)||[])[1]);  // strip color
		var width = parseInt((o.match(/(\d+)px/)||[])[1]) || 10; // corner width
		var re = /round|bevel|notch|bite|cool|sharp|slide|jut|curl|tear|fray|wicked|sculpt|long|dog3|dog2|dog/;
		var fx = ((o.match(re)||['round'])[0]);
		var edges = { T:0, B:1 };
		var opts = {
			TL:  /top|tl|left/.test(o),       TR:  /top|tr|right/.test(o),
			BL:  /bottom|bl|left/.test(o),    BR:  /bottom|br|right/.test(o)
		};
		if ( !opts.TL && !opts.TR && !opts.BL && !opts.BR )
			opts = { TL:1, TR:1, BL:1, BR:1 };
			
		// support native rounding
		if ($.fn.corner.defaults.useNative && fx == 'round' && (moz || webkit) && !cc && !sc) {
			if (opts.TL)
				$this.css(moz ? '-moz-border-radius-topleft' : '-webkit-border-top-left-radius', width + 'px');
			if (opts.TR)
				$this.css(moz ? '-moz-border-radius-topright' : '-webkit-border-top-right-radius', width + 'px');
			if (opts.BL)
				$this.css(moz ? '-moz-border-radius-bottomleft' : '-webkit-border-bottom-left-radius', width + 'px');
			if (opts.BR)
				$this.css(moz ? '-moz-border-radius-bottomright' : '-webkit-border-bottom-right-radius', width + 'px');
			return;
		}
			
		var strip = document.createElement('div');
		strip.style.overflow = 'hidden';
		strip.style.height = '1px';
		strip.style.backgroundColor = sc || 'transparent';
		strip.style.borderStyle = 'solid';
	
        var pad = {
            T: parseInt($.css(this,'paddingTop'))||0,     R: parseInt($.css(this,'paddingRight'))||0,
            B: parseInt($.css(this,'paddingBottom'))||0,  L: parseInt($.css(this,'paddingLeft'))||0
        };

        if (typeof this.style.zoom != undefined) this.style.zoom = 1; // force 'hasLayout' in IE
        if (!keep) this.style.border = 'none';
        strip.style.borderColor = cc || gpc(this.parentNode);
        var cssHeight = $.curCSS(this, 'height');

        for (var j in edges) {
            var bot = edges[j];
            // only add stips if needed
            if ((bot && (opts.BL || opts.BR)) || (!bot && (opts.TL || opts.TR))) {
                strip.style.borderStyle = 'none '+(opts[j+'R']?'solid':'none')+' none '+(opts[j+'L']?'solid':'none');
                var d = document.createElement('div');
                $(d).addClass('jquery-corner');
                var ds = d.style;

                bot ? this.appendChild(d) : this.insertBefore(d, this.firstChild);

                if (bot && cssHeight != 'auto') {
                    if ($.css(this,'position') == 'static')
                        this.style.position = 'relative';
                    ds.position = 'absolute';
                    ds.bottom = ds.left = ds.padding = ds.margin = '0';
                    if (expr)
                        ds.setExpression('width', 'this.parentNode.offsetWidth');
                    else
                        ds.width = '100%';
                }
                else if (!bot && $.browser.msie) {
                    if ($.css(this,'position') == 'static')
                        this.style.position = 'relative';
                    ds.position = 'absolute';
                    ds.top = ds.left = ds.right = ds.padding = ds.margin = '0';
                    
                    // fix ie6 problem when blocked element has a border width
                    if (expr) {
                        var bw = sz(this,'borderLeftWidth') + sz(this,'borderRightWidth');
                        ds.setExpression('width', 'this.parentNode.offsetWidth - '+bw+'+ "px"');
                    }
                    else
                        ds.width = '100%';
                }
                else {
                	ds.position = 'relative';
                    ds.margin = !bot ? '-'+pad.T+'px -'+pad.R+'px '+(pad.T-width)+'px -'+pad.L+'px' : 
                                        (pad.B-width)+'px -'+pad.R+'px -'+pad.B+'px -'+pad.L+'px';                
                }

                for (var i=0; i < width; i++) {
                    var w = Math.max(0,getWidth(fx,i, width));
                    var e = strip.cloneNode(false);
                    e.style.borderWidth = '0 '+(opts[j+'R']?w:0)+'px 0 '+(opts[j+'L']?w:0)+'px';
                    bot ? d.appendChild(e) : d.insertBefore(e, d.firstChild);
                }
            }
        }
    });
};

$.fn.uncorner = function() { 
	if (moz || webkit)
		this.css(moz ? '-moz-border-radius' : '-webkit-border-radius', 0);
	$('div.jquery-corner', this).remove();
	return this;
};

// expose options
$.fn.corner.defaults = {
	useNative: true, // true if plugin should attempt to use native browser support for border radius rounding
	metaAttr:  'data-corner' // name of meta attribute to use for options
};
    
})(jQuery);

/*
 ### jQuery Multiple File Upload Plugin v1.31 - 2009-01-17 ###
 * Home: http://www.fyneworks.com/jquery/multiple-file-upload/
 * Code: http://code.google.com/p/jquery-multifile-plugin/
 *
 * Dual licensed under the MIT and GPL licenses:
 *   http://www.opensource.org/licenses/mit-license.php
 *   http://www.gnu.org/licenses/gpl.html
 ###
*/
 
 // extend jQuery - $.MultiFile hook
 $.extend($, {
  MultiFile: function( o /* Object */ ){
   //return $("INPUT[type='file'].multi").MultiFile(o);
   return $("input:file.multi").MultiFile(o);
  }
 });
 
 //===
 
 // extend $.MultiFile - default options
 $.extend($.MultiFile, {
  options: {
   accept: '', max: -1,
   // error handling function
   error: function(s){
    if($.blockUI){
     $.blockUI({
      message: s.replace(/\n/gi,'<br/>'),
      css: { 
       border:'none', padding:'15px', size:'12.0pt',
       backgroundColor:'#900', color:'#fff',
       opacity:'.8','-webkit-border-radius': '10px','-moz-border-radius': '10px'
      }
     });
     window.setTimeout($.unblockUI, 2000);
    }
    else{
     alert(s);
    }
   },
   // namePattern: $name/$id (from master element), $i (slave count), $g (group count)
   namePattern: '$name',
   // STRING: collection lets you show messages in different languages
   STRING: {
    remove:'x',
    denied:'You cannot select a $ext file.\nTry again...',
    file:'$file',
    selected:'File selected: $file',
    duplicate:'This file has already been selected:\n$file'
   }
  }
 });
 
 //===
 
 // extend $.MultiFile - global methods
 $.extend($.MultiFile, {
  
  
  /**
   * This utility makes it easy to disable all 'empty' file elements in the document before submitting a form.
   * It marks the affected elements so they can be easily re-enabled after the form submission or validation.
   *
   * Returns a jQuery collection of all affected elements.
   *
   * @name disableEmpty
   * @type jQuery
   * @cat Plugins/Multifile
   * @author Diego A. (http://www.fyneworks.com/)
   *
   * @example $.MultiFile.disableEmpty();
   * @param String class (optional) A string specifying a class to be applied to all affected elements - Default: 'mfD'.
   */
  disableEmpty: function(klass){
   var o = [];
   $('input:file').each(function(){ if($(this).val()=='') o[o.length] = this; });
   return $(o).each(function(){ this.disabled = true }).addClass(klass || 'mfD');
  },
  
  
 /**
  * This method re-enables 'empty' file elements that were disabled (and marked) with the $.MultiFile.disableEmpty method.
  *
  * Returns a jQuery collection of all affected elements.
  *
  * @name reEnableEmpty
  * @type jQuery
  * @cat Plugins/Multifile
  * @author Diego A. (http://www.fyneworks.com/)
  *
  * @example $.MultiFile.reEnableEmpty();
  * @param String klass (optional) A string specifying the class that was used to mark affected elements - Default: 'mfD'.
  */
  reEnableEmpty: function(klass){
   klass = klass || 'mfD';
   return $('input:file.'+klass).removeClass(klass).each(function(){ this.disabled = false });
  },
  
  
 /**
  * This method will intercept other jQuery plugins and disable empty file input elements prior to form submission
  *
  * @name intercept
  * @cat Plugins/Multifile
  * @author Diego A. (http://www.fyneworks.com/)
  *
  * @example $.MultiFile.intercept();
  * @param Array methods (optional) Array of method names to be intercepted
  */
  autoIntercept: [ 'submit', 'ajaxSubmit', 'validate' /* array of methods to intercept */ ],
  intercepted: {},
  intercept: function(methods, context, args){
   var method, value; args = args || [];
   if(args.constructor.toString().indexOf("Array")<0) args = [ args ];
   if(typeof(methods)=='function'){
    $.MultiFile.disableEmpty();
    value = methods.apply(context || window, args);
    $.MultiFile.reEnableEmpty();
    return value;
   };
   if(methods.constructor.toString().indexOf("Array")<0) methods = [methods];
   for(var i=0;i<methods.length;i++){
    method = methods[i]+''; // make sure that we have a STRING
    if(method) (function(method){ // make sure that method is ISOLATED for the interception
     $.MultiFile.intercepted[method] = $.fn[method] || function(){};
     $.fn[method] = function(){
      $.MultiFile.disableEmpty();
      value = $.MultiFile.intercepted[method].apply(this, arguments);
      $.MultiFile.reEnableEmpty();
      return value;
     }; // interception
    })(method); // MAKE SURE THAT method IS ISOLATED for the interception
   };// for each method
  }
 });
 
 //===
 
 // extend jQuery function library
 $.extend($.fn, {
   
			// Use this function to clear values of file inputs
			// But this doesn't always work: $(element).val('').attr('value', '')[0].value = '';
			reset: function(){ return this.each(function(){ try{ this.reset(); }catch(e){} }); },
    
   // MultiFile function
   MultiFile: function( options /* Object */ ){
    
    //### http://plugins.jquery.com/node/1363
    // utility method to integrate this plugin with others...
    if($.MultiFile.autoIntercept){
     $.MultiFile.intercept( $.MultiFile.autoIntercept /* array of methods to intercept */ );
     $.MultiFile.autoIntercept = null; /* only run this once */
    };
    
    //===
    
    // Bind to each element in current jQuery object
    return $(this).each(function(group_count){
     if(this._MultiFile) return; this._MultiFile = true;
     
       // BUG 1251 FIX: http://plugins.jquery.com/project/comments/add/1251
       // variable group_count would repeat itself on multiple calls to the plugin.
       // this would cause a conflict with multiple elements
       // changes scope of variable to global so id will be unique over n calls
       window.MultiFile = (window.MultiFile || 0) + 1;
       group_count = window.MultiFile;
       
       // Copy parent attributes - Thanks to Jonas Wagner
       // we will use this one to create new input elements
       var MF = {e:this, E:$(this), clone:$(this).clone()};
       
       //===
       
       //# USE CONFIGURATION
       if(typeof options=='number') options = {max:options};
       if(typeof options=='string') options = {accept:options};
       var o = $.extend({},
        $.MultiFile.options,
        options || {},
        ($.meta ? MF.E.data()/*NEW metadata plugin*/ :
        ($.metadata ? MF.E.metadata()/*OLD metadata plugin*/ : 
        null/*metadata plugin not available*/)) || {}
       );
       // limit number of files that can be selected?
       if(!(o.max>0) /*IsNull(MF.max)*/){
        o.max = MF.E.attr('maxlength');
        if(!(o.max>0) /*IsNull(MF.max)*/){
         o.max = (String(MF.e.className.match(/\b(max|limit)\-([0-9]+)\b/gi) || ['']).match(/[0-9]+/gi) || [''])[0];
         if(!(o.max>0)) o.max = -1;
         else           o.max = String(o.max).match(/[0-9]+/gi)[0];
        }
       };
       o.max = new Number(o.max);
       // limit extensions?
       o.accept = o.accept || MF.E.attr('accept') || '';
       if(!o.accept){
        o.accept = (MF.e.className.match(/\b(accept\-[\w\|]+)\b/gi)) || '';
        o.accept = new String(o.accept).replace(/^(accept|ext)\-/i,'');
       };
       
       //===
       
       // APPLY CONFIGURATION
       $.extend(MF, o || {});
       MF.STRING = $.extend({},$.MultiFile.options.STRING,MF.STRING);
       
       //===
       
       //#########################################
       // PRIVATE PROPERTIES/METHODS
       $.extend(MF, {
        n: 0, // How many elements are currently selected?
        slaves: [], files: [],
        instanceKey: MF.e.id || 'MultiFile'+String(group_count), // Instance Key?
        generateID: function(z){ return MF.instanceKey + (z>0 ?'_F'+String(z):''); },
        trigger: function(event, element){
         var handler = MF[event], value = $(element).attr('value');
         if(handler){
          var returnValue = handler(element, value, MF);
          if( returnValue!=null ) return returnValue;
         }
         return true;
        }
       });
       
       //===
       
       // Setup dynamic regular expression for extension validation
       // - thanks to John-Paul Bader: http://smyck.de/2006/08/11/javascript-dynamic-regular-expresions/
       if(String(MF.accept).length>1){
        MF.rxAccept = new RegExp('\\.('+(MF.accept?MF.accept:'')+')$','gi');
       };
       
       //===
       
       // Create wrapper to hold our file list
       MF.wrapID = MF.instanceKey+'_wrap'; // Wrapper ID?
       MF.E.wrap('<div id="'+MF.wrapID+'"></div>');
       MF.wrapper = $('#'+MF.wrapID+'');
       
       //===
       
       // MF MUST have a name - default: file1[], file2[], file3[]
       MF.e.name = MF.e.name || 'file'+ group_count +'[]';
       
       //===
       
							if(!MF.list){
								// Create a wrapper for the list
								// * OPERA BUG: NO_MODIFICATION_ALLOWED_ERR ('list' is a read-only property)
								// this change allows us to keep the files in the order they were selected
								MF.wrapper.append( '<span id="'+MF.wrapID+'_list"></span>' );
								MF.list = $('#'+MF.wrapID+'_list');
							};
       MF.list = $(MF.list);
							
       //===
       
       // Bind a new element
       MF.addSlave = function( slave, slave_count ){
        // Keep track of how many elements have been displayed
        MF.n++;
        // Add reference to master element
        slave.MF = MF;
        // Count slaves
        slave.i = slave_count;
        
        // BUG FIX: http://plugins.jquery.com/node/1495
        // Clear identifying properties from clones
        if(slave.i>0) slave.id = slave.name = null;
        
        // Define element's ID and name (upload components need this!)
        slave.id = slave.id || MF.generateID(slave.i);
        
        //slave.name = (slave.name || MF.E.attr('name') || 'file');// + (slave.i>0?slave.i:''); // same name as master element
        // 2008-Apr-29: New customizable naming convention (see url below)
        // http://groups.google.com/group/jquery-dev/browse_frm/thread/765c73e41b34f924#
        slave.name = String(MF.namePattern
         /*master name*/.replace(/\$name/gi,MF.E.attr('name'))
         /*master id  */.replace(/\$id/gi,  MF.E.attr('id'))
         /*group count*/.replace(/\$g/gi,   (group_count>0?group_count:''))
         /*slave count*/.replace(/\$i/gi,   (slave_count>0?slave_count:''))
        );
        
        // Clear value
        $(slave).val('').attr('value','')[0].value = '';
        
        // If we've reached maximum number, disable input slave
        if( (MF.max > 0) && ((MF.n-1) > (MF.max)) )//{ // MF.n Starts at 1, so subtract 1 to find true count
         slave.disabled = true;
        //};
        
        // Remember most recent slave
        MF.current = MF.slaves[slave.i] = slave;
        
        // now let's use jQuery
        slave = $(slave);
        
        // Triggered when a file is selected
        $(slave).change(function(){
          
          // Lose focus to stop IE7 firing onchange again
          $(this).blur();
          
          //# Trigger Event! onFileSelect
          if(!MF.trigger('onFileSelect', this, MF)) return false;
          //# End Event!
          
          //# Retrive value of selected file from element
          var ERROR = '', v = String(this.value || ''/*.attr('value)*/);
          
          // check extension
          if(MF.accept && v && !v.match(MF.rxAccept))//{
            ERROR = MF.STRING.denied.replace('$ext', String(v.match(/\.\w{1,4}$/gi)));
           //}
          //};
          
          // Disallow duplicates
										for(var f in MF.slaves)//{
           if(MF.slaves[f] && MF.slaves[f]!=this)//{
  										//console.log(MF.slaves[f],MF.slaves[f].value);
            if(MF.slaves[f].value==v)//{
             ERROR = MF.STRING.duplicate.replace('$file', v.match(/[^\/\\]+$/gi));
            //};
           //};
          //};
          
          // Create a new file input element
          //var newEle = $('<input name="'+(MF.E.attr('name') || '')+'" type="file"/>');
          var newEle = $(MF.clone).clone();// Copy parent attributes - Thanks to Jonas Wagner
          //# Let's remember which input we've generated so
          // we can disable the empty ones before submission
          // See: http://plugins.jquery.com/node/1495
          newEle.addClass('MultiFile');
          
          // Handle error
          if(ERROR!=''){
            // Handle error
            MF.error(ERROR);
            
            // Clear element value (DOES NOT WORK in some browsers)
            //slave.reset().val('').attr('value', '')[0].value = '';
            
            // 2007-06-24: BUG FIX - Thanks to Adrian Wr?bel <adrian [dot] wrobel [at] gmail.com>
            // Ditch the trouble maker and add a fresh new element
            MF.n--;
            MF.addSlave(newEle[0], this.i);
            slave.parent().prepend(newEle);
            slave.remove();
            return false;
          };
          
          // Hide this element (NB: display:none is evil!)
          $(this).css({ position:'absolute', top: '-3000px' });
          
          // Add new element to the form
          //MF.list.before(newEle);//.append(newEle);
          MF.wrapper.prepend(newEle);//.append(newEle);
          
          // Update list
          MF.addToList( this );
          
          // Bind functionality
          MF.addSlave( newEle[0], this.i+1 );
          
          //# Trigger Event! afterFileSelect
          if(!MF.trigger('afterFileSelect', this, MF)) return false;
          //# End Event!
          
        }); // slave.change()
        
       };// MF.addSlave
       // Bind a new element
       
       
       
       // Add a new file to the list
       MF.addToList = function( slave ){
        
        //# Trigger Event! onFileAppend
        if(!MF.trigger('onFileAppend', slave, MF)) return false;
        //# End Event!
        
        // Create label elements
        var
         r = $('<div class="in_queue"></div>'),
         v = String(slave.value || ''/*.attr('value)*/),
         a = $('<span class="file" title="'+MF.STRING.selected.replace('$file', v)+'">'+MF.STRING.file.replace('$file', v.match(/[^\/\\]+$/gi)[0])+'</span>'),
         b = $('<a href="#'+MF.wrapID+'">'+MF.STRING.remove+'</a>'),
				 c = $('<div class="clearer"></div>');
        
        // Insert label
        MF.list.append(
         r.append(a, ' ',b, c)//.prepend(slave.i+': ')
        );
        
        b.click(function(){
         
          //# Trigger Event! onFileRemove
          if(!MF.trigger('onFileRemove', slave, MF)) return false;
          //# End Event!
          
          MF.n--;
          MF.current.disabled = false;
          
          // Remove element, remove label, point to current
										MF.slaves[slave.i] = null;
										$(slave).remove();
										$(this).parent().remove();
          
          // Show most current element again (move into view) and clear selection
          $(MF.current).css({ position:'', top: '' });
										$(MF.current).reset().val('').attr('value', '')[0].value = '';
          
          //# Trigger Event! afterFileRemove
          if(!MF.trigger('afterFileRemove', slave, MF)) return false;
          //# End Event!
										
          return false;
        });
        
        //# Trigger Event! afterFileAppend
        if(!MF.trigger('afterFileAppend', slave, MF)) return false;
        //# End Event!
        
       }; // MF.addToList
       // Add element to selected files list
       
       
       
       // Bind functionality to the first element
       if(!MF.MF) MF.addSlave(MF.e, 0);
       
       // Increment control count
       //MF.I++; // using window.MultiFile
       MF.n++;
       
    });
    // each element
   
   }
   // MultiFile function
 
 });
 // extend jQuery function library
 
 
 
 /*
  ### Default implementation ###
  The plugin will attach itself to file inputs
  with the class 'multi' when the page loads
 */
// $(function(){ $.MultiFile() });

