/*
 * jQuery Form Plugin
 * version: 2.07 (03/04/2008)
 * @requires jQuery v1.2.2 or later
 *
 * Examples 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
 *
 * Revision: $Id$
 */
 (function($) {
/**
 * ajaxSubmit() provides a mechanism for submitting an HTML form using AJAX.
 *
 * ajaxSubmit accepts a single argument which can be either a success callback function
 * or an options Object.  If a function is provided it will be invoked upon successful
 * completion of the submit and will be passed the response from the server.
 * If an options Object is provided, the following attributes are supported:
 *
 *  target:   Identifies the element(s) in the page to be updated with the server response.
 *            This value may be specified as a jQuery selection string, a jQuery object,
 *            or a DOM element.
 *            default value: null
 *
 *  url:      URL to which the form data will be submitted.
 *            default value: value of form's 'action' attribute
 *
 *  type:     The method in which the form data should be submitted, 'GET' or 'POST'.
 *            default value: value of form's 'method' attribute (or 'GET' if none found)
 *
 *  data:     Additional data to add to the request, specified as key/value pairs (see $.ajax).
 *
 *  beforeSubmit:  Callback method to be invoked before the form is submitted.
 *            default value: null
 *
 *  success:  Callback method to be invoked after the form has been successfully submitted
 *            and the response has been returned from the server
 *            default value: null
 *
 *  dataType: Expected dataType of the response.  One of: null, 'xml', 'script', or 'json'
 *            default value: null
 *
 *  semantic: Boolean flag indicating whether data must be submitted in semantic order (slower).
 *            default value: false
 *
 *  resetForm: Boolean flag indicating whether the form should be reset if the submit is successful
 *
 *  clearForm: Boolean flag indicating whether the form should be cleared if the submit is successful
 *
 *
 * The 'beforeSubmit' callback can be provided as a hook for running pre-submit logic or for
 * validating the form data.  If the 'beforeSubmit' callback returns false then the form will
 * not be submitted. The 'beforeSubmit' callback is invoked with three arguments: the form data
 * in array format, the jQuery object, and the options object passed into ajaxSubmit.
 * The form data array takes the following form:
 *
 *     [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
 *
 * If a 'success' callback method is provided it is invoked after the response has been returned
 * from the server.  It is passed the responseText or responseXML value (depending on dataType).
 * See jQuery.ajax for further details.
 *
 *
 * The dataType option provides a means for specifying how the server response should be handled.
 * This maps directly to the jQuery.httpData method.  The following values are supported:
 *
 *      'xml':    if dataType == 'xml' the server response is treated as XML and the 'success'
 *                   callback method, if specified, will be passed the responseXML value
 *      'json':   if dataType == 'json' the server response will be evaluted and passed to
 *                   the 'success' callback, if specified
 *      'script': if dataType == 'script' the server response is evaluated in the global context
 *
 *
 * Note that it does not make sense to use both the 'target' and 'dataType' options.  If both
 * are provided the target will be ignored.
 *
 * The semantic argument can be used to force form serialization in semantic order.
 * This is normally true anyway, unless the form contains input elements of type='image'.
 * If your form must be submitted with name/value pairs in semantic order and your form
 * contains an input of type='image" then pass true for this arg, otherwise pass false
 * (or nothing) to avoid the overhead for this logic.
 *
 *
 * When used on its own, ajaxSubmit() is typically bound to a form's submit event like this:
 *
 * $("#form-id").submit(function() {
 *     $(this).ajaxSubmit(options);
 *     return false; // cancel conventional submit
 * });
 *
 * When using ajaxForm(), however, this is done for you.
 *
 * @example
 * $('#myForm').ajaxSubmit(function(data) {
 *     alert('Form submit succeeded! Server returned: ' + data);
 * });
 * @desc Submit form and alert server response
 *
 *
 * @example
 * var options = {
 *     target: '#myTargetDiv'
 * };
 * $('#myForm').ajaxSubmit(options);
 * @desc Submit form and update page element with server response
 *
 *
 * @example
 * var options = {
 *     success: function(responseText) {
 *         alert(responseText);
 *     }
 * };
 * $('#myForm').ajaxSubmit(options);
 * @desc Submit form and alert the server response
 *
 *
 * @example
 * var options = {
 *     beforeSubmit: function(formArray, jqForm) {
 *         if (formArray.length == 0) {
 *             alert('Please enter data.');
 *             return false;
 *         }
 *     }
 * };
 * $('#myForm').ajaxSubmit(options);
 * @desc Pre-submit validation which aborts the submit operation if form data is empty
 *
 *
 * @example
 * var options = {
 *     url: myJsonUrl.php,
 *     dataType: 'json',
 *     success: function(data) {
 *        // 'data' is an object representing the the evaluated json data
 *     }
 * };
 * $('#myForm').ajaxSubmit(options);
 * @desc json data returned and evaluated
 *
 *
 * @example
 * var options = {
 *     url: myXmlUrl.php,
 *     dataType: 'xml',
 *     success: function(responseXML) {
 *        // responseXML is XML document object
 *        var data = $('myElement', responseXML).text();
 *     }
 * };
 * $('#myForm').ajaxSubmit(options);
 * @desc XML data returned from server
 *
 *
 * @example
 * var options = {
 *     resetForm: true
 * };
 * $('#myForm').ajaxSubmit(options);
 * @desc submit form and reset it if successful
 *
 * @example
 * $('#myForm).submit(function() {
 *    $(this).ajaxSubmit();
 *    return false;
 * });
 * @desc Bind form's submit event to use ajaxSubmit
 *
 *
 * @name ajaxSubmit
 * @type jQuery
 * @param options  object literal containing options which control the form submission process
 * @cat Plugins/Form
 * @return jQuery
 */
$.fn.ajaxSubmit = function(options) {
    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) return this;

    var a = this.formToArray(options.semantic);
    if (options.data) {
        options.extraData = options.data;
        for (var n in options.data)
            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) return this;

    // fire vetoable 'validate' event
    this.trigger('form-submit-validate', [a, this, options, veto]);
    if (veto.veto) 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](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 ($.browser.safari && 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];
        var opts = $.extend({}, $.ajaxSettings, options);

        var id = 'jqFormIO' + (new Date().getTime());
        var $io = $('<iframe id="' + id + '" name="' + id + '" />');
        var io = $io[0];
        var op8 = $.browser.opera && window.opera.version() < 9;
        if ($.browser.msie || op8) io.src = 'javascript:false;document.write("");';
        $io.css({ position: 'absolute', top: '-1000px', left: '-1000px' });

        var xhr = { // mock object
            responseText: null,
            responseXML: null,
            status: 0,
            statusText: 'n/a',
            getAllResponseHeaders: function() {},
            getResponseHeader: function() {},
            setRequestHeader: function() {}
        };

        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]);

        var cbInvoked = 0;
        var timedOut = 0;

        // 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');
            $form.attr({
                target:   id,
                encoding: 'multipart/form-data',
                enctype:  'multipart/form-data',
                method:   'POST',
                action:   opts.url
            });

            // 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.attr('action', a);
                t ? $form.attr('target', t) : $form.removeAttr('target');
                $(extraInputs).remove();
            }
        }, 10);

        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;
                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.
 *
 * Note that for accurate x/y coordinates of image submit elements in all browsers
 * you need to also use the "dimensions" plugin (this method will auto-detect its presence).
 *
 * 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.  See ajaxSubmit for a full description of the options argument.
 *
 *
 * @example
 * var options = {
 *     target: '#myTargetDiv'
 * };
 * $('#myForm').ajaxSForm(options);
 * @desc Bind form's submit event so that 'myTargetDiv' is updated with the server response
 *       when the form is submitted.
 *
 *
 * @example
 * var options = {
 *     success: function(responseText) {
 *         alert(responseText);
 *     }
 * };
 * $('#myForm').ajaxSubmit(options);
 * @desc Bind form's submit event so that server response is alerted after the form is submitted.
 *
 *
 * @example
 * var options = {
 *     beforeSubmit: function(formArray, jqForm) {
 *         if (formArray.length == 0) {
 *             alert('Please enter data.');
 *             return false;
 *         }
 *     }
 * };
 * $('#myForm').ajaxSubmit(options);
 * @desc Bind form's submit event so that pre-submit callback is invoked before the form
 *       is submitted.
 *
 *
 * @name   ajaxForm
 * @param  options  object literal containing options which control the form submission process
 * @return jQuery
 * @cat    Plugins/Form
 * @type   jQuery
 */
$.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
 *
 * @name   ajaxFormUnbind
 * @return jQuery
 * @cat    Plugins/Form
 * @type   jQuery
 */
$.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.
 *
 * The semantic argument can be used to force form serialization in semantic order.
 * This is normally true anyway, unless the form contains input elements of type='image'.
 * If your form must be submitted with name/value pairs in semantic order and your form
 * contains an input of type='image" then pass true for this arg, otherwise pass false
 * (or nothing) to avoid the overhead for this logic.
 *
 * @example var data = $("#myForm").formToArray();
 * $.post( "myscript.cgi", data );
 * @desc Collect all the data from a form and submit it to the server.
 *
 * @name formToArray
 * @param semantic true if serialization must maintain strict semantic ordering of elements (slower)
 * @type Array<Object>
 * @cat Plugins/Form
 */
$.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
 *
 * The semantic argument can be used to force form serialization in semantic order.
 * If your form must be submitted with name/value pairs in semantic order then pass
 * true for this arg, otherwise pass false (or nothing) to avoid the overhead for
 * this logic (which can be significant for very large forms).
 *
 * @example var data = $("#myForm").formSerialize();
 * $.ajax('POST', "myscript.cgi", data);
 * @desc Collect all the data from a form into a single string
 *
 * @name formSerialize
 * @param semantic true if serialization must maintain strict semantic ordering of elements (slower)
 * @type String
 * @cat Plugins/Form
 */
$.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
 *
 * The successful argument controls whether or not serialization is limited to
 * 'successful' controls (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
 * The default value of the successful argument is true.
 *
 * @example var data = $("input").fieldSerialize();
 * @desc Collect the data from all successful input elements into a query string
 *
 * @example var data = $(":radio").fieldSerialize();
 * @desc Collect the data from all successful radio input elements into a query string
 *
 * @example var data = $("#myForm :checkbox").fieldSerialize();
 * @desc Collect the data from all successful checkbox input elements in myForm into a query string
 *
 * @example var data = $("#myForm :checkbox").fieldSerialize(false);
 * @desc Collect the data from all checkbox elements in myForm (even the unchecked ones) into a query string
 *
 * @example var data = $(":input").fieldSerialize();
 * @desc Collect the data from all successful input, select, textarea and button elements into a query string
 *
 * @name fieldSerialize
 * @param successful true if only successful controls should be serialized (default is true)
 * @type String
 * @cat Plugins/Form
 */
$.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.
 *
 * @example var data = $("#myPasswordElement").fieldValue();
 * alert(data[0]);
 * @desc Alerts the current value of the myPasswordElement element
 *
 * @example var data = $("#myForm :input").fieldValue();
 * @desc Get the value(s) of the form elements in myForm
 *
 * @example var data = $("#myForm :checkbox").fieldValue();
 * @desc Get the value(s) for the successful checkbox element(s) in the jQuery object.
 *
 * @example var data = $("#mySingleSelect").fieldValue();
 * @desc Get the value(s) of the select control
 *
 * @example var data = $(':text').fieldValue();
 * @desc Get the value(s) of the text input or textarea elements
 *
 * @example var data = $("#myMultiSelect").fieldValue();
 * @desc Get the values for the select-multiple control
 *
 * @name fieldValue
 * @param Boolean successful true if only the values for successful controls should be returned (default is true)
 * @type Array<String>
 * @cat Plugins/Form
 */
$.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.
 *
 * 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 the given element is not
 * successful and the successful arg is not false then the returned value will be null.
 *
 * Note: If the successful flag is true (default) but the element is not successful, the return will be null
 * Note: The value returned for a successful select-multiple element will always be an array.
 * Note: If the element has no value the return value will be undefined.
 *
 * @example var data = jQuery.fieldValue($("#myPasswordElement")[0]);
 * @desc Gets the current value of the myPasswordElement element
 *
 * @name fieldValue
 * @param Element el The DOM element for which the value will be returned
 * @param Boolean successful true if value returned must be for a successful controls (default is true)
 * @type String or Array<String> or null or undefined
 * @cat Plugins/Form
 */
$.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) {
                // extra pain for IE...
                var v = $.browser.msie && !(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
 *
 * @example $('form').clearForm();
 * @desc Clears all forms on the page.
 *
 * @name clearForm
 * @type jQuery
 * @cat Plugins/Form
 */
$.fn.clearForm = function() {
    return this.each(function() {
        $('input,select,textarea', this).clearFields();
    });
};

/**
 * Clears the selected form elements.  Takes the following actions on the matched elements:
 *  - 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
 *
 * @example $('.myInputs').clearFields();
 * @desc Clears all inputs with class myInputs
 *
 * @name clearFields
 * @type jQuery
 * @cat Plugins/Form
 */
$.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.
 *
 * @example $('form').resetForm();
 * @desc Resets all forms on the page.
 *
 * @name resetForm
 * @type jQuery
 * @cat Plugins/Form
 */
$.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.
 *
 * @example $(':radio').enabled(false);
 * @desc Disables all radio buttons
 *
 * @name select
 * @type jQuery
 * @cat Plugins/Form
 */
$.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.
 *
 * @example $(':checkbox').select();
 * @desc Checks all checkboxes
 *
 * @name select
 * @type jQuery
 * @cat Plugins/Form
 */
$.fn.select = 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').select(false);
            }
            this.selected = select;
        }
    });
};

})(jQuery);

/*
+-----------------------------------------------------------------------+
| Copyright (c) 2006-2007 Mika Tuupola, Dylan Verheul                   |
| All rights reserved.                                                  |
|                                                                       |
| Redistribution and use in source and binary forms, with or without    |
| modification, are permitted provided that the following conditions    |
| are met:                                                              |
|                                                                       |
| o Redistributions of source code must retain the above copyright      |
|   notice, this list of conditions and the following disclaimer.       |
| o 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.|
|                                                                       |
| 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.  |
|                                                                       |
+-----------------------------------------------------------------------+
*/

/* $Id$ */

/**
  * jQuery inplace editor plugin (version 1.3.x)
  *
  * Based on editable by Dylan Verheul <dylan@dyve.net>
  * http://www.dyve.net/jquery/?editable
  *
  * @name  jEditable
  * @type  jQuery
  * @param String  target             POST URL or function name to send edited content
  * @param Hash    options            additional options 
  * @param String  options[name]      POST parameter name of edited content
  * @param String  options[id]        POST parameter name of edited div id
  * @param String  options[type]      text, textarea or select
  * @param Integer options[rows]      number of rows if using textarea
  * @param Integer options[cols]      number of columns if using textarea
  * @param Mixed   options[height]    'auto' or height in pixels
  * @param Mixed   options[width]     'auto' or width in pixels 
  * @param String  options[loadurl]   URL to fetch content before editing
  * @param String  options[loadtype]  Request type for load url. Should be GET or POST.
  * @param String  options[data]      Or content given as paramameter.
  * @param String  options[indicator] indicator html to show when saving
  * @param String  options[tooltip]   optional tooltip text via title attribute
  * @param String  options[event]     jQuery event such as 'click' of 'dblclick'
  * @param String  options[onblur]    'cancel', 'submit' or 'ignore'
  * @param String  options[submit]    submit button value, empty means no button
  * @param String  options[cancel]    cancel button value, empty means no button
  * @param String  options[cssclass]  CSS class to apply to input form. 'inherit' to copy from parent.
  * @param String  options[style]     Style to apply to input form 'inherit' to copy from parent.
  * @param String  options[select]    true or false, when true text is highlighted
  *             
  */

jQuery.fn.editable = function(target, options, callback) {

    /* prevent elem has no properties error */
    if (this.length == 0) { 
        return(this); 
    };
    
    var settings = {
        target   : target,
        name     : 'value',
        id       : 'id',
        type     : 'text',
        width    : 'auto',
        height   : 'auto',
        event    : 'click',
        onblur   : 'cancel',
        loadtype : 'GET'
    };
        
    if(options) {
        jQuery.extend(settings, options);
    };
    
    var callback = callback || function() { };
      
    jQuery(this).attr('title', settings.tooltip);

    jQuery(this)[settings.event](function(e) {

        /* save this to self because this changes when scope changes */
        var self = this;

        /* prevent throwing an exeption if edit field is clicked again */
        if (self.editing) {
            return;
        }

        /* figure out how wide and tall we are */
        var width = 
            ('auto' == settings.width)  ? jQuery(self).width()  : settings.width;
        var height = 
            ('auto' == settings.height) ? jQuery(self).height() : settings.height;

        self.editing    = true;
        self.revert     = jQuery(self).html();
        self.innerHTML  = '';

        /* create the form object */
        var f = document.createElement('form');
        
        /* apply css or style or both */
        if (settings.cssclass) {
            if ('inherit' == settings.cssclass) {
                jQuery(f).attr('class', jQuery(self).attr('class'));
            } else {
                jQuery(f).attr('class', settings.cssclass);
            }
        }
        
        if (settings.style) {
            if ('inherit' == settings.style) {
                jQuery(f).attr('style', jQuery(self).attr('style'));
                /* IE needs the second line or display wont be inherited */
                jQuery(f).css('display', jQuery(self).css('display'));                
            } else {
                jQuery(f).attr('style', settings.style);
            }
        }
        
        /*  main input element */
        var i;
        switch (settings.type) {
            case 'textarea':
                i = document.createElement('textarea');
                if (settings.rows) {
                    i.rows = settings.rows;
                } else {
                    jQuery(i).height(height);
                }
                if (settings.cols) {
                    i.cols = settings.cols;
                } else {
                    jQuery(i).width(width);
                }   
                break;
            case 'select':
                i = document.createElement('select');
                break;
            default:
                i = document.createElement('input');
                i.type  = settings.type;
                jQuery(i).width(width);
                jQuery(i).height(height);
                /* https://bugzilla.mozilla.org/show_bug.cgi?id=236791 */
                i.setAttribute('autocomplete','off');
        }

        /* maintain bc with 1.1.1 and earlier versions */        
        if (settings.getload) {
            settings.loadurl    = settings.getload;
            settings.loadtype = 'GET';
        } else if (settings.postload) {
            settings.loadurl    = settings.postload;
            settings.loadtype = 'POST';
        }

        /* set input content via POST, GET, given data or existing value */
        if (settings.loadurl) {
            var data = {};
            data[settings.id] = self.id;
            jQuery.ajax({
               type : settings.loadtype,
               url  : settings.loadurl,
               data : data,
               success: function(str) {
                  setInputContent(str);
               }
            });
        } else if (settings.data) {
            setInputContent(settings.data);
        } else { 
            setInputContent(self.revert);
        }

        i.name  = settings.name;
        f.appendChild(i);

        if (settings.submit) {
            var b = document.createElement('input');
            b.type = 'submit';
            b.value = settings.submit;
            f.appendChild(b);
        }

        if (settings.cancel) {
            var b = document.createElement('input');
            b.type = 'button';
            b.value = settings.cancel;
            jQuery(b).click(function() {
                reset();
            });
            f.appendChild(b);
        }

        /* add created form to self */
        self.appendChild(f);

        i.focus();
        
        /* highlight input contents when requested */
        if (settings.select) {
            i.select();
        }
         
        /* discard changes if pressing esc */
        jQuery(i).keydown(function(e) {
            if (e.keyCode == 27) {
                e.preventDefault();
                reset();
            }
        });

        /* discard, submit or nothing with changes when clicking outside */
        /* do nothing is usable when navigating with tab */
        var t;
        if ('cancel' == settings.onblur) {
            jQuery(i).blur(function(e) {
                t = setTimeout(reset, 500)
            });
        } else if ('submit' == settings.onblur) {
            jQuery(i).blur(function(e) {
                jQuery(f).submit();
            });
        } else {
            jQuery(i).blur(function(e) {
              /* TODO: maybe something here */
            });
        }

        jQuery(f).submit(function(e) {

            if (t) { 
                clearTimeout(t);
            }

            /* do no submit */
            e.preventDefault(); 

            /* check if given target is function */
            if (jQuery.isFunction(settings.target)) {
                var str = settings.target.apply(self, [jQuery(i).val(), settings]);
                self.innerHTML = str;
                self.editing = false;
                callback.apply(self, [self.innerHTML, settings]);
            } else {
                /* add edited content and id of edited element to POST */           
                var p = {};
                p[i.name] = jQuery(i).val();
                p[settings.id] = self.id;

                /* show the saving indicator */
                jQuery(self).html(settings.indicator);
                jQuery.post(settings.target, p, function(str) {
                    self.innerHTML = str;
                    self.editing = false;
                    callback.apply(self, [self.innerHTML, settings]);
                });
            }
                        
            return false;
        });

        function reset() {
            self.innerHTML = self.revert;
            self.editing   = false;
        };
        
        function setInputContent(str) {
            if (jQuery.isFunction(str)) {
                var str = str.apply(self, [self.revert, settings]);
            }
            switch (settings.type) { 	 
                case 'select': 	 
                    if (String == str.constructor) { 	 
                        eval ("var json = " + str);
                        for (var key in json) {
                            if ('selected' == key) {
                                continue;
                            } 
                            o = document.createElement('option'); 	 
                            o.value = key;
                            var text = document.createTextNode(json[key]);
                            o.appendChild(text);
                            if (key == json['selected']) {
                                o.selected = true;
                            }
                            i.appendChild(o); 	 
                        }
                    } 	 
                    break; 	 
                default: 	 
                    i.value = str; 	 
                    break; 	 
            } 	 
        }

    });

    return(this);
};
(function ($) {
    var m = {
            '\b': '\\b',
            '\t': '\\t',
            '\n': '\\n',
            '\f': '\\f',
            '\r': '\\r',
            '"' : '\\"',
            '\\': '\\\\'
        },
        s = {
            'array': function (x) {
                var a = ['['], b, f, i, l = x.length, v;
                for (i = 0; i < l; i += 1) {
                    v = x[i];
                    f = s[typeof v];
                    if (f) {
                        v = f(v);
                        if (typeof v == 'string') {
                            if (b) {
                                a[a.length] = ',';
                            }
                            a[a.length] = v;
                            b = true;
                        }
                    }
                }
                a[a.length] = ']';
                return a.join('');
            },
            'boolean': function (x) {
                return String(x);
            },
            'null': function (x) {
                return "null";
            },
            'number': function (x) {
                return isFinite(x) ? String(x) : 'null';
            },
            'object': function (x) {
                if (x) {
                    if (x instanceof Array) {
                        return s.array(x);
                    }
                    var a = ['{'], b, f, i, v;
                    for (i in x) {
                        v = x[i];
                        f = s[typeof v];
                        if (f) {
                            v = f(v);
                            if (typeof v == 'string') {
                                if (b) {
                                    a[a.length] = ',';
                                }
                                a.push(s.string(i), ':', v);
                                b = true;
                            }
                        }
                    }
                    a[a.length] = '}';
                    return a.join('');
                }
                return 'null';
            },
            'string': function (x) {
                if (/["\\\x00-\x1f]/.test(x)) {
                    x = x.replace(/([\x00-\x1f\\"])/g, function(a, b) {
                        var c = m[b];
                        if (c) {
                            return c;
                        }
                        c = b.charCodeAt();
                        return '\\u00' +
                            Math.floor(c / 16).toString(16) +
                            (c % 16).toString(16);
                    });
                }
                return '"' + x + '"';
            }
        };

	$.toJSON = function(v) {
		var f = isNaN(v) ? s[typeof v] : s['number'];
		if (f) return f(v);
	};
	
	$.parseJSON = function(v, safe) {
		if (safe === undefined) safe = $.parseJSON.safe;
		if (safe && !/^("(\\.|[^"\\\n\r])*?"|[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t])+?$/.test(v))
			return undefined;
		return eval('('+v+')');
	};
	
	$.parseJSON.safe = false;

})(jQuery);

/*
 * jQuery Live Search Plugin
 * Version: 0.1
 * Date: 2008-08-04
 * Author: Eduardo Lundgren
 * Copyright: Copyright (c) 2008 Eduardo Lundgren under dual MIT/GPL license.
*/

; (function($) {

    $.fn.extend({
        liveSearch: function(options) {
            return this.each(function() {
                var opt = options = $.extend({},
                $.liveSearch.defaults, options || {});
                new $.liveSearch(this, opt);
            });
        }
    });


    $.liveSearch = function(element, options) {
        this.init(element, options);
    };

    $.extend($.liveSearch.prototype, {
        init: function(element, options) {
            var instance = this;

            this.options = options;
            this.timer = null;
            this.cache = null;
            this.element = jQuery(element);
            this.list = jQuery(this.options.list);
            this.delay = this.options.delay;
            this.filterList = jQuery(this.options.filter || this.list);

            if (this.filterList.length) {
                this.cache = this.filterList.map(this.options.data);

                this.element
                .keyup(function() {
                    var self = this;

                    if (instance.timer) {
                        clearTimeout(instance.timer);
                    }

                    instance.timer = setTimeout(function() {
                        instance.options.before.apply(instance);
                        instance.filter();
                        instance.options.after.apply(instance);
                    },
                    instance.delay);
                })
                .parents('form').submit(function() {
                    return false;
                });
            }

            return this;
        },

        filter: function() {
            var instance = this,
            results = [],
            rows = this.list;

            this.term = $.trim(this.element.val().toLowerCase()).match(/(\w|\s|[*])*/g).join("");

            if (!this.term) {
                rows.each(function() {
                    instance.options.show.apply(this);
                });
            } else {
                rows.each(function() {
                    instance.options.hide.apply(this);
                });

                this.cache.each(function(i, v) {
                    var regex = new RegExp(instance.term.replace("*", ""), "g");
                    if (regex.test(v) || instance.options.exclude.apply(rows[i])) {
                        results.push(i);
                    }
                });

                $.each(results,
                function() {
                    instance.options.show.apply(jQuery(rows[this]));
                });
            }
        }
    });

    $.extend($.liveSearch, {
        defaults: {
            delay: 250,
            show: function() {
                $(this).show();
            },
            hide: function() {
                $(this).hide();
            },
            data: function() {
                return $(this)[0].innerHTML.toLowerCase();
            },
            exclude: function() {
            	return false;
            },
            before: function() {},
            after: function() {}
        }
    });

})(jQuery);
/*
 * jQuery Media Plugin for converting elements into rich media content.
 *
 * Examples and documentation at: http://malsup.com/jquery/media/
 * Copyright (c) 2007 M. Alsup
 * Dual licensed under the MIT and GPL licenses:
 * http://www.opensource.org/licenses/mit-license.php
 * http://www.gnu.org/licenses/gpl.html
 *
 * @author: M. Alsup
 * @version: 0.66 (6/08/2007)
 * @requires jQuery v1.1.2 or later
 *
 * Supported Media Players:
 *    - Flash
 *    - Quicktime
 *    - Real Player
 *    - Silverlight
 *    - Windows Media Player
 *    - iframe
 *
 * Supported Media Formats:
 *   Any types supported by the above players, such as:
 *     Video: asf, avi, flv, mov, mpg, mpeg, mp4, qt, smil, swf, wmv, 3g2, 3gp
 *     Audio: aif, aac, au, gsm, mid, midi, mov, mp3, m4a, snd, rm, wav, wma
 *     Other: bmp, html, pdf, psd, qif, qtif, qti, tif, tiff, xaml
 *
 * Thanks to Mark Hicken for helping me debug this on Safari!
 */
(function($) {

/**
 * Chainable method for converting elements into rich media.
 *
 * @name media
 * @param Object options Options object
 * @param Function callback fn invoked for each matched element before conversion
 * @param Function callback fn invoked for each matched element after conversion
 * @cat Plugins/media
 */
$.fn.media = function(options, f1, f2) {
    return this.each(function() {
        if (typeof options == 'function') {
            f2 = f1;
            f1 = options;
            options = {};
        }
        var o = getSettings(this, options);
        // pre-conversion callback, passes original element and fully populated options
        if (typeof f1 == 'function') f1(this, o);
        
        var r = getTypesRegExp();
        var m = r.exec(o.src) || [''];
        o.type ? m[0] = o.type : m.shift();
        for (var i=0; i < m.length; i++) {
            fn = m[i].toLowerCase();
            if (isDigit(fn[0])) fn = 'fn' + fn; // fns can't begin with numbers
            if (!$.fn.media[fn]) 
                continue;  // unrecognized media type
            // normalize autoplay settings
            var player = $.fn.media[fn+'_player'];
            if (!o.params) o.params = {};
            if (player) {
                var num = player.autoplayAttr == 'autostart';
                o.params[player.autoplayAttr || 'autoplay'] = num ? (o.autoplay ? 1 : 0) : o.autoplay ? true : false;
            }
            var $div = $.fn.media[fn](this, o);

            $div.css('backgroundColor', o.bgColor).width(o.width);
            
            // post-conversion callback, passes original element, new div element and fully populated options
            if (typeof f2 == 'function') f2(this, $div[0], o);
            break;
        }
    });
};

/**
 * Chainable method for preparing elements to display rich media with
 * a page overlay.
 *
 * @name mediabox
 * @param Object options Options object
 * @param Object css values for the media div
 * @cat Plugins/media
 */
$.fn.mediabox = function(options, css) {
    return this.click(function() {
        if (typeof $.blockUI == 'undefined' || typeof $.fn.displayBox == 'undefined') {
            if (typeof $.fn.mediabox.warning != 'undefined') return this; // one warning is enough
            $.fn.mediabox.warning = 1;
            alert('The mediabox method requires blockUI v1.20 or later.');
            return false;
        }
        var o, div=0, $e = $(this).clone();
        $e.appendTo('body').hide().css({margin: 0});
        options = $.extend({}, options, { autoplay: 1 }); // force autoplay in box mode
        $e.media(options, function(){}, function(origEl, newEl, opts) {
            div = newEl;
            o = opts;
        });
        if (!div) return false;
        // don't pull element from the dom on Safari
        var $div = $.browser.safari ? $(div).hide() : $(div).remove();

        if (o.loadingImage)
            $div.css({
                backgroundImage:    'url('+o.loadingImage+')',
                backgroundPosition: 'center center',
                backgroundRepeat:   'no-repeat'
            });
        if (o.boxTitle)
            $div.prepend('<div style="margin:0;padding:0">' + o.boxTitle + '</div>');
        
        if (css) $div.css(css);

        $div.displayBox( { width: o.width, height: o.height }, function(el) {
            // quirkiness; sometimes media doesn't stop when removed from the DOM (especially in IE)
            $(el).find('object,embed').each(function() {
                try { this.Stop();   } catch(e) {}  // quicktime
                try { this.DoStop(); } catch(e) {}  // real
                try { this.controls.stop(); } catch(e) {} // windows media player
            });
        });
        return false;
    });
};

  
/**
 * Non-chainable method for adding or changing file format / player mapping
 * @name mapFormat
 * @param String format File format extension (ie: mov, wav, mp3)
 * @param String player Player name to use for the format (one of: flash, quicktime, realplayer, winmedia, silverlight or iframe
 */
$.fn.media.mapFormat = function(format, player) {
    if (!format || !player || !$.fn.media.defaults.players[player]) return; // invalid
    format = format.toLowerCase();
    if (isDigit(format[0])) format = 'fn' + format;
    $.fn.media[format] = $.fn.media[player];
};


// global defautls; override as needed
$.fn.media.defaults = {
    width:         400,
    height:        400,
    preferMeta:    1,         // true if markup metadata takes precedence over options object
    autoplay:      0,         // normalized cross-player setting
    bgColor:       '#ffffff', // background color
    params:        {},        // added to object element as param elements; added to embed element as attrs
    attrs:         {},        // added to object and embed elements as attrs
    flashvars:     {},        // added to flash content as flashvars param/attr
    flashVersion:  '7',       // required flash version
    
    // MediaBox options
    boxTitle:      null,      // MediaBox titlebar
    loadingImage:  null,      // MediaBox loading indicator
    
    // default flash video and mp3 player (@see: http://jeroenwijering.com/?item=Flash_Media_Player)
    flvPlayer:     'mediaplayer.swf',
    mp3Player:     'mediaplayer.swf',
    
    // @see http://msdn2.microsoft.com/en-us/library/bb412401.aspx
    silverlight: {
        inplaceInstallPrompt: 'true', // display in-place install prompt?
        isWindowless:         'true', // windowless mode (false for wrapping markup)
        framerate:            '24',   // maximum framerate
        version:              '0.9',  // Silverlight version
        onError:              null,   // onError callback
        onLoad:               null,   // onLoad callback
        initParams:           null,   // object init params
        userContext:          null    // callback arg passed to the load callback
    }
};

// Media Players; think twice before overriding
$.fn.media.defaults.players = {
    flash: {
        types:        'flv,mp3,swf',
        oAttrs:   {
            classid:  'clsid:d27cdb6e-ae6d-11cf-96b8-444553540000',
            type:     'application/x-oleobject',
            codebase: 'http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=' + $.fn.media.defaults.flashVersion
        },
        eAttrs: {
            type:         'application/x-shockwave-flash',
            pluginspage:  'http://www.adobe.com/go/getflashplayer'
        }        
    },
    quicktime: {
        types:        'aif,aiff,aac,au,bmp,gsm,mov,mid,midi,mpg,mpeg,mp4,m4a,psd,qt,qtif,qif,qti,snd,tif,tiff,wav,3g2,3gp',
        oAttrs:   {
            classid:  'clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B',
            codebase: 'http://www.apple.com/qtactivex/qtplugin.cab'
        },
        eAttrs: {
            pluginspage:  'http://www.apple.com/quicktime/download/'
        }
    },
    realplayer: {
        types:        'ra,ram,rm,rpm,rv,smi,smil',
        autoplayAttr: 'autostart',
        oAttrs:   {
            classid:  'clsid:CFCDAA03-8BE4-11cf-B84B-0020AFBBCCFA'
        },
        eAttrs: {
            type:         'audio/x-pn-realaudio-plugin',
            pluginspage:  'http://www.real.com/player/'
        }
    },
    winmedia: {
        types:        'asf,avi,wma,wmv',
        autoplayAttr: 'autostart',
        oUrl:         'url',
        oAttrs:   {
            classid:  'clsid:6BF52A52-394A-11d3-B153-00C04F79FAA6',
            type:     'application/x-oleobject'
        },
        eAttrs: {
            type:         'application/x-mplayer2',
            pluginspage:  'http://www.microsoft.com/Windows/MediaPlayer/'
        }        
    },
    // special cases
    iframe: {
        types: 'html,pdf'
    },
    silverlight: {
        types: 'xaml'
    }
};

//
//  everything below here is private
//


var counter = 1;

for (var player in $.fn.media.defaults.players) {
    var types = $.fn.media.defaults.players[player].types;
    $.each(types.split(','), function(i,o) {
        if (isDigit(o[0])) o = 'fn' + o;
        $.fn.media[o] = $.fn.media[player] = getGenerator(player);
        $.fn.media[o+'_player'] = $.fn.media.defaults.players[player];
    });
};

function getTypesRegExp() {
    var types = '';
    for (var player in $.fn.media.defaults.players) {
        if (types.length) types += ',';
        types += $.fn.media.defaults.players[player].types;
    };
    return new RegExp('\\.(' + types.replace(/,/g,'|') + ')\\b');
};

function getGenerator(player) {
    return function(el, options) {
        return generate(el, options, player);
    };
};

function isDigit(c) {
    return '0123456789'.indexOf(c) > -1;
};

// flatten all possible options: global defaults, meta, option obj
function getSettings(el, options) {
    options = options || {};
    var $el = $(el);
    
    var cls = el.className || '';
    var meta = $.meta ? $el.data() : {};
    var w = meta.width  || parseInt(((cls.match(/w:(\d+)/)||[])[1]||0));
    var h = meta.height || parseInt(((cls.match(/h:(\d+)/)||[])[1]||0));
    if (w) meta.width  = w;
    if (h) meta.height = h;
    if (cls) meta.cls = cls;

    var a = $.fn.media.defaults;
    var b = $.meta && $.fn.media.defaults.preferMeta ? options : meta;
    var c = b == options ? meta : options;

    var p = { params: { bgColor: options.bgColor || $.fn.media.defaults.bgColor } };
    var opts = $.extend({}, a, b, c);
    $.each(['attrs','params','flashvars','silverlight'], function(i,o) {
        opts[o] = $.extend({}, p[o] || {}, a[o] || {}, b[o] || {}, c[o] || {});
    });

    if (typeof opts.caption == 'undefined') opts.caption = $el.text();

    // make sure we have a source!
    opts.src = opts.src || $el.attr('href') || $el.attr('src') || 'unknown';
    return opts;
};

//
//  Flash Player
//

// generate flash using SWFObject if possible
$.fn.media.swf = function(el, opts) {
    if (typeof SWFObject == 'undefined') {
        // roll our own
        if (opts.flashvars) {
            var a = [];
            for (var f in opts.flashvars)
                a.push(f + '=' + opts.flashvars[f]);
            if (!opts.params) opts.params = {};
            opts.params.flashvars = a.join('&');
        }
        return generate(el, opts, 'flash');
    }

    var id = el.id ? (' id="'+el.id+'"') : '';
    var cls = opts.cls ? (' class="' + opts.cls + '"') : '';
    var $div = $('<div' + id + cls + '>');
    $(el).after($div).remove();

    var so = new SWFObject(opts.src, 'movie_player_' + counter++, opts.width, opts.height, opts.flashVersion, opts.bgColor);
    for (var p in opts.params)
        if (p != 'bgColor') so.addParam(p, opts.params[p]);
    for (var f in opts.flashvars)
        so.addVariable(f, opts.flashvars[f]);
    so.write($div[0]);

    if (opts.caption) $('<div>').appendTo($div).html(opts.caption);
    return $div;
};

// map flv and mp3 files to the swf player by default
$.fn.media.flv = $.fn.media.mp3 = function(el, opts) {
    var src = opts.src;
    var player = /\.mp3\b/i.test(src) ? $.fn.media.defaults.mp3Player : $.fn.media.defaults.flvPlayer;
    opts.src = player;
    opts.src = opts.src + '?file=' + src;
    opts.flashvars = $.extend({}, { file: src }, opts.flashvars );
    return $.fn.media.swf(el, opts);
};

//
//  Silverlight
//
$.fn.media.xaml = function(el, opts) {
    if (!window.Sys || !window.Sys.Silverlight) {
        if ($.fn.media.xaml.warning) return;
        $.fn.media.xaml.warning = 1;
        alert('You must include the Silverlight.js script.');
        return;
    }

    var props = {
        width: opts.width,
        height: opts.height,
        background: opts.bgColor,
        inplaceInstallPrompt: opts.silverlight.inplaceInstallPrompt,
        isWindowless: opts.silverlight.isWindowless,
        framerate: opts.silverlight.framerate,
        version: opts.silverlight.version
    };
    var events = {
        onError: opts.silverlight.onError,
        onLoad: opts.silverlight.onLoad
    };

    var id1 = el.id ? (' id="'+el.id+'"') : '';
    var id2 = opts.id || 'AG' + counter++;
    // convert element to div
    var cls = opts.cls ? (' class="' + opts.cls + '"') : '';
    var $div = $('<div' + id1 + cls + '>');
    $(el).after($div).remove();
    
    Sys.Silverlight.createObjectEx({
        source: opts.src,
        initParams: opts.silverlight.initParams,
        userContext: opts.silverlight.userContext,
        id: id2,
        parentElement: $div[0],
        properties: props,
        events: events
    });

    if (opts.caption) $('<div>').appendTo($div).html(opts.caption);
    return $div;
};

//
// generate object/embed markup
//
function generate(el, opts, player) {
    var $el = $(el);
    var o = $.fn.media.defaults.players[player];
    
    if (player == 'iframe') {
        var o = $('<iframe' + ' width="' + opts.width + '" height="' + opts.height + '" >');
        o.attr('src', opts.src);
        o.css('backgroundColor', o.bgColor);
    }
    else if ($.browser.msie) {
        var a = ['<object width="' + opts.width + '" height="' + opts.height + '" '];
        for (var key in opts.attrs)
            a.push(key + '="'+opts.attrs[key]+'" ');
        for (var key in o.oAttrs || {})
            a.push(key + '="'+o.oAttrs[key]+'" ');
        a.push('></ob'+'ject'+'>');
        var p = ['<param name="' + (o.oUrl || 'src') +'" value="' + opts.src + '">'];
        for (var key in opts.params)
            p.push('<param name="'+ key +'" value="' + opts.params[key] + '">');
        var o = document.createElement(a.join(''));
        for (var i=0; i < p.length; i++)
            o.appendChild(document.createElement(p[i]));
    }
    else {
        var a = ['<embed width="' + opts.width + '" height="' + opts.height + '" style="display:block"'];
        if (opts.src) a.push(' src="' + opts.src + '" ');
        for (var key in opts.attrs)
            a.push(key + '="'+opts.attrs[key]+'" ');
        for (var key in o.eAttrs || {})
            a.push(key + '="'+o.eAttrs[key]+'" ');
        for (var key in opts.params)
            a.push(key + '="'+opts.params[key]+'" ');
        a.push('></em'+'bed'+'>');
    }
    // convert element to div
    var id = el.id ? (' id="'+el.id+'"') : '';
    var cls = opts.cls ? (' class="' + opts.cls + '"') : '';
    var $div = $('<div' + id + cls + '>');
    $el.after($div).remove();
    ($.browser.msie || player == 'iframe') ? $div.append(o) : $div.html(a.join(''));
    if (opts.caption) $('<div>').appendTo($div).html(opts.caption);
    return $div;
};


})(jQuery);

jQuery.fn.alignTo = function(referrer, options) {
	var defaults = {
		positionX: 'left',
		positionY: 'bottom',
		offsetX: 0,
		offsetY: 0,
		directionH: 'right',
		directionV: 'down',
		detectH: true,
		detectV: true,
		linkToFront: false
	};

	options = jQuery.extend(defaults, options);

	var el = jQuery(this);

	if (referrer && !referrer.jquery) {
		referrer = jQuery(referrer);
	}

	var referrerOffset = referrer.offset();

	var dimensions = {
		elementWidth: el.width(),
		elementHeight: el.height(),
		referrerX: referrerOffset.left,
		referrerY: referrerOffset.top,
		referrerWidth: referrer.outerWidth(),
		referrerHeight: referrer.outerHeight()
	};

	var x = 0;
	var y = 0;

	var positionX = options.positionX;
	var positionY = options.positionY;

	el.attachPositionHelper(dimensions);

	if (positionX != 'left') {
		x = dimensions.referrerWidth;

		if (positionX == 'center') {
			x /= 2;
		}
	}

	if (positionY != 'top') {
		y = dimensions.referrerHeight;

		if (positionX == 'center') {
			y /= 2;
		}
	}

	x += options.offsetX;
	y += options.offsetY;

	var elementOffset = el.parent().offset();

	var positionOptions = {
		bottom: 'auto',
		left: 'auto',
		right: 'auto',
		top: 'auto'
	};

	var fitsHorizontally = true;
	var fitsVertically = true;

	if (options.detectH || options.detectV) {
		var win = jQuery(window);

		var windowHeight = win.height();
		var windowWidth = win.width();

		var windowScrollLeft = win.scrollLeft();
		var windowScrollTop = win.scrollTop();

		var leftValue = (elementOffset.left + dimensions.elementWidth);
		var topValue = (elementOffset.top + dimensions.elementHeight);

		if (leftValue > (windowWidth + windowScrollLeft) || (elementOffset.left - windowScrollLeft) < 0 &&
			dimensions.elementWidth <= dimensions.referrerX) {
			fitsHorizontally = false;
		}

		if ((topValue > (windowHeight + windowScrollTop) || (elementOffset.top - windowScrollTop) < 0) &&
			dimensions.elementHeight <= dimensions.referrerY) {

			fitsVertically = false;
		}
	}

	if (options.directionH == 'left' ||
	(options.directionH != 'left' && !fitsHorizontally)) {

		positionOptions.right = x;
	}
	else {
		positionOptions.left = x;
	}

	if (options.directionV == 'up' ||
	(options.directionV != 'up' && !fitsVertically)) {

		positionOptions.bottom = y;
	}
	else {
		positionOptions.top = y;
	}

	el.css(positionOptions);

	return this;
}

jQuery.fn.attachPositionHelper = function(dimensions) {
	if (!this.data('position-helper')) {
		var el = jQuery(this);
		var helper = jQuery('<div class="lfr-position-helper"></div>');

		helper.css(
			{
				height: dimensions.referrerHeight,
				left: dimensions.referrerX,
				top: dimensions.referrerY,
				width: dimensions.referrerWidth
			}
		);

		helper.append(el);

		jQuery(document.body).append(helper);

		this.data('position-helper', helper);
	}

	return this;
};

jQuery.fn.detachPositionHelper = function(appendTo) {
	var helper = this.data('position-helper');

	this.hide().appendTo(appendTo || document.body);

	if (helper) {
		this.data('position-helper', null);

		helper.remove();
	}
}

;(function( $ ){

	var $scrollTo = $.scrollTo = function( target, duration, settings ){
		$scrollTo.window().scrollTo( target, duration, settings );
	};

	$scrollTo.defaults = {
		axis:'y',
		duration:1
	};

	//returns the element that needs to be animated to scroll the window
	$scrollTo.window = function(){
		return $( $.browser.safari ? 'body' : 'html' );
	};

	$.fn.scrollTo = function( target, duration, settings ){
		if( typeof duration == 'object' ){
			settings = duration;
			duration = 0;
		}
		settings = $.extend( {}, $scrollTo.defaults, settings );
		duration = duration || settings.speed || settings.duration;//speed is still recognized for backwards compatibility
		settings.queue = settings.queue && settings.axis.length > 1;//make sure the settings are given right
		if( settings.queue )
			duration /= 2;//let's keep the overall speed, the same.
		settings.offset = both( settings.offset );
		settings.over = both( settings.over );

		return this.each(function(){
			var elem = this, $elem = $(elem),
				t = target, toff, attr = {},
				win = $elem.is('html,body');
			switch( typeof t ){
				case 'number'://will pass the regex
				case 'string':
					if( /^([+-]=)?\d+(px)?$/.test(t) ){
						t = both( t );
						break;//we are done
					}
					t = $(t,this);// relative selector, no break!
				case 'object':
					if( t.is || t.style )//DOM/jQuery
						toff = (t = $(t)).offset();//get the real position of the target 
			}
			$.each( settings.axis.split(''), function( i, axis ){
				var Pos	= axis == 'x' ? 'Left' : 'Top',
					pos = Pos.toLowerCase(),
					key = 'scroll' + Pos,
					act = elem[key],
					Dim = axis == 'x' ? 'Width' : 'Height',
					dim = Dim.toLowerCase();

				if( toff ){//jQuery/DOM
					attr[key] = toff[pos] + ( win ? 0 : act - $elem.offset()[pos] );

					if( settings.margin ){//if it's a dom element, reduce the margin
						attr[key] -= parseInt(t.css('margin'+Pos)) || 0;
						attr[key] -= parseInt(t.css('border'+Pos+'Width')) || 0;
					}
					
					attr[key] += settings.offset[pos] || 0;//add/deduct the offset
					
					if( settings.over[pos] )//scroll to a fraction of its width/height
						attr[key] += t[dim]() * settings.over[pos];
				}else
					attr[key] = t[pos];//remove the unnecesary 'px'

				if( /^\d+$/.test(attr[key]) )//number or 'number'
					attr[key] = attr[key] <= 0 ? 0 : Math.min( attr[key], max(Dim) );//check the limits

				if( !i && settings.queue ){//queueing each axis is required					
					if( act != attr[key] )//don't waste time animating, if there's no need.
						animate( settings.onAfterFirst );//intermediate animation
					delete attr[key];//don't animate this axis again in the next iteration.
				}
			});			
			animate( settings.onAfter );			

			function animate( callback ){
				$elem.animate( attr, duration, settings.easing, callback && function(){
					callback.call(this, target);
				});
			};
			function max( Dim ){
				var el = win ? $.browser.opera ? document.body : document.documentElement : elem;
				return el['scroll'+Dim] - el['client'+Dim];
			};
		});
	};

	function both( val ){
		return typeof val == 'object' ? val : { top:val, left:val };
	};

})( jQuery );
jQuery.fn.disableSelection = function() {
	return this.attr('unselectable', 'on').css('MozUserSelect', 'none').bind('selectstart.ui',
		function() {
			return false;
		}
	);
};

jQuery.fn.enableSelection = function() {
	return this.attr('unselectable', 'off').css('MozUserSelect', '').unbind('selectstart.ui');
};
/*
 * Treeview pre-1.4.1 - jQuery plugin to hide and show branches of a tree
 * 
 * http://bassistance.de/jquery-plugins/jquery-plugin-treeview/
 * http://docs.jquery.com/Plugins/Treeview
 *
 * Copyright (c) 2007 Jörn Zaefferer
 *
 * Dual licensed under the MIT and GPL licenses:
 *   http://www.opensource.org/licenses/mit-license.php
 *   http://www.gnu.org/licenses/gpl.html
 *
 * Revision: $Id: jquery.treeview.js 5759 2008-07-01 07:50:28Z joern.zaefferer $
 *
 */

;(function($) {

	$.extend($.fn, {
		swapClass: function(c1, c2) {
			var c1Elements = this.filter('.' + c1);
			this.filter('.' + c2).removeClass(c2).addClass(c1);
			c1Elements.removeClass(c1).addClass(c2);
			return this;
		},
		replaceClass: function(c1, c2) {
			return this.filter('.' + c1).removeClass(c1).addClass(c2).end();
		},
		hoverClass: function(className) {
			className = className || "hover";
			return this.hover(function() {
				$(this).addClass(className);
			}, function() {
				$(this).removeClass(className);
			});
		},
		heightToggle: function(animated, callback) {
			animated ?
				this.animate({ height: "toggle" }, animated, callback) :
				this.each(function(){
					jQuery(this)[ jQuery(this).is(":hidden") ? "show" : "hide" ]();
					if(callback)
						callback.apply(this, arguments);
				});
		},
		heightHide: function(animated, callback) {
			if (animated) {
				this.animate({ height: "hide" }, animated, callback);
			} else {
				this.hide();
				if (callback)
					this.each(callback);				
			}
		},
		prepareBranches: function(settings) {
			if (!settings.prerendered) {
				// mark last tree items
				this.filter(":last-child:not(ul)").addClass(CLASSES.last);
				// collapse whole tree, or only those marked as closed, anyway except those marked as open
				this.filter((settings.collapsed ? "" : "." + CLASSES.closed) + ":not(." + CLASSES.open + ")").find(">ul").hide();
			}
			// return all items with sublists
			return this.filter(":has(>ul)");
		},
		applyClasses: function(settings, toggler) {
			this.filter(":has(>ul):not(:has(>a))").find(">span").unbind("click.treeview").bind("click.treeview", function(event) {
				// don't handle click events on children, eg. checkboxes
				if ( this == event.target )
					toggler.apply($(this).next());
			}).add( $("a", this) ).hoverClass();
			
			if (!settings.prerendered) {
				// handle closed ones first
				this.filter(":has(>ul:hidden)")
						.addClass(CLASSES.expandable)
						.replaceClass(CLASSES.last, CLASSES.lastExpandable);
						
				// handle open ones
				this.not(":has(>ul:hidden)")
						.addClass(CLASSES.collapsable)
						.replaceClass(CLASSES.last, CLASSES.lastCollapsable);
						
	            // create hitarea if not present
				var hitarea = this.find("div." + CLASSES.hitarea);
				if (!hitarea.length)
					hitarea = this.prepend("<div class=\"" + CLASSES.hitarea + "\"/>").find("div." + CLASSES.hitarea);
				hitarea.removeClass().addClass(CLASSES.hitarea).each(function() {
					var classes = "";
					$.each($(this).parent().attr("class").split(" "), function() {
						classes += this + "-hitarea ";
					});
					$(this).addClass( classes );
				})
			}
			
			// apply event to hitarea
			this.find("div." + CLASSES.hitarea).click( toggler );
		},
		treeview: function(settings) {
			
			settings = $.extend({
				cookieId: "treeview"
			}, settings);
			
			if ( settings.toggle ) {
				var callback = settings.toggle;
				settings.toggle = function() {
					return callback.apply($(this).parent()[0], arguments);
				};
			}
		
			// factory for treecontroller
			function treeController(tree, control) {
				// factory for click handlers
				function handler(filter) {
					return function() {
						// reuse toggle event handler, applying the elements to toggle
						// start searching for all hitareas
						toggler.apply( $("div." + CLASSES.hitarea, tree).filter(function() {
							// for plain toggle, no filter is provided, otherwise we need to check the parent element
							return filter ? $(this).parent("." + filter).length : true;
						}) );
						return false;
					};
				}
				// click on first element to collapse tree
				$("a:eq(0)", control).click( handler(CLASSES.collapsable) );
				// click on second to expand tree
				$("a:eq(1)", control).click( handler(CLASSES.expandable) );
				// click on third to toggle tree
				$("a:eq(2)", control).click( handler() ); 
			}
		
			// handle toggle event
			function toggler() {
				$(this)
					.parent()
					// swap classes for hitarea
					.find(">.hitarea")
						.swapClass( CLASSES.collapsableHitarea, CLASSES.expandableHitarea )
						.swapClass( CLASSES.lastCollapsableHitarea, CLASSES.lastExpandableHitarea )
					.end()
					// swap classes for parent li
					.swapClass( CLASSES.collapsable, CLASSES.expandable )
					.swapClass( CLASSES.lastCollapsable, CLASSES.lastExpandable )
					// find child lists
					.find( ">ul" )
					// toggle them
					.heightToggle( settings.animated, settings.toggle );
				if ( settings.unique ) {
					$(this).parent()
						.siblings()
						// swap classes for hitarea
						.find(">.hitarea")
							.replaceClass( CLASSES.collapsableHitarea, CLASSES.expandableHitarea )
							.replaceClass( CLASSES.lastCollapsableHitarea, CLASSES.lastExpandableHitarea )
						.end()
						.replaceClass( CLASSES.collapsable, CLASSES.expandable )
						.replaceClass( CLASSES.lastCollapsable, CLASSES.lastExpandable )
						.find( ">ul" )
						.heightHide( settings.animated, settings.toggle );
				}
			}
			this.data("toggler", toggler);
			
			function serialize() {
				function binary(arg) {
					return arg ? 1 : 0;
				}
				var data = [];
				branches.each(function(i, e) {
					data[i] = $(e).is(":has(>ul:visible)") ? 1 : 0;
				});
				$.cookie(settings.cookieId, data.join(""), settings.cookieOptions );
			}
			
			function deserialize() {
				var stored = $.cookie(settings.cookieId);
				if ( stored ) {
					var data = stored.split("");
					branches.each(function(i, e) {
						$(e).find(">ul")[ parseInt(data[i]) ? "show" : "hide" ]();
					});
				}
			}
			
			// add treeview class to activate styles
			this.addClass("treeview");
			
			// prepare branches and find all tree items with child lists
			var branches = this.find("li").prepareBranches(settings);
			
			switch(settings.persist) {
			case "cookie":
				var toggleCallback = settings.toggle;
				settings.toggle = function() {
					serialize();
					if (toggleCallback) {
						toggleCallback.apply(this, arguments);
					}
				};
				deserialize();
				break;
			case "location":
				var current = this.find("a").filter(function() { return this.href.toLowerCase() == location.href.toLowerCase(); });
				if ( current.length ) {
					current.addClass("selected").parents("ul, li").add( current.next() ).show();
				}
				break;
			}
			
			branches.applyClasses(settings, toggler);
				
			// if control option is set, create the treecontroller and show it
			if ( settings.control ) {
				treeController(this, settings.control);
				$(settings.control).show();
			}
			
			return this;
		}
	});
	
	// classes used by the plugin
	// need to be styled via external stylesheet, see first example
	$.treeview = {};
	var CLASSES = ($.treeview.classes = {
		open: "open",
		closed: "closed",
		expandable: "expandable",
		expandableHitarea: "expandable-hitarea",
		lastExpandableHitarea: "lastExpandable-hitarea",
		collapsable: "collapsable",
		collapsableHitarea: "collapsable-hitarea",
		lastCollapsableHitarea: "lastCollapsable-hitarea",
		lastCollapsable: "lastCollapsable",
		lastExpandable: "lastExpandable",
		last: "last",
		hitarea: "hitarea"
	});
	
	// provide backwards compability
	$.fn.Treeview = $.fn.treeview;
	
})(jQuery);
/*
 * jQuery UI Accordion
 * 
 * Copyright (c) 2007, 2008 Jörn Zaefferer
 * Dual licensed under the MIT (MIT-LICENSE.txt)
 * and GPL (GPL-LICENSE.txt) licenses.
 *
 * http://docs.jquery.com/UI/Accordion
 *
 * Depends:
 *	ui.core.js
 */
(function($) {

$.widget("ui.accordion", {
	init: function() {
		var options = this.options;
		
		if ( options.navigation ) {
			var current = this.element.find("a").filter(options.navigationFilter);
			if ( current.length ) {
				if ( current.filter(options.header).length ) {
					options.active = current;
				} else {
					options.active = current.parent().parent().prev();
					current.addClass("current");
				}
			}
		}
		
		// calculate active if not specified, using the first header
		options.headers = this.element.find(options.header);
		options.active = findActive(options.headers, options.active);
		
		// IE7-/Win - Extra vertical space in Lists fixed
		if ($.browser.msie) {
			this.element.find('a').css('zoom', '1');
		}
		
		if (!this.element.hasClass("ui-accordion")) {
			this.element.addClass("ui-accordion");
			$("<span class='ui-accordion-left'/>").insertBefore(options.headers);
			$("<span class='ui-accordion-right'/>").appendTo(options.headers);
			options.headers.addClass("ui-accordion-header").attr("tabindex", "0");
		}
		
		var maxHeight;
		if ( options.fillSpace ) {
			maxHeight = this.element.parent().height();
			options.headers.each(function() {
				maxHeight -= $(this).outerHeight();
			});
			var maxPadding = 0;
			options.headers.next().each(function() {
				maxPadding = Math.max(maxPadding, $(this).innerHeight() - $(this).height());
			}).height(maxHeight - maxPadding);
		} else if ( options.autoHeight ) {
			maxHeight = 0;
			options.headers.next().each(function() {
				maxHeight = Math.max(maxHeight, $(this).outerHeight());
			}).height(maxHeight);
		}
	
		options.headers
			.not(options.active || "")
			.next()
			.hide();
		options.active.parent().andSelf().addClass(options.selectedClass);
		
		if (options.event) {
			this.element.bind((options.event) + ".accordion", clickHandler);
		}
	},
	activate: function(index) {
		// call clickHandler with custom event
		clickHandler.call(this.element[0], {
			target: findActive( this.options.headers, index )[0]
		});
	},
	destroy: function() {
		this.options.headers.next().css("display", "");
		if ( this.options.fillSpace || this.options.autoHeight ) {
			this.options.headers.next().css("height", "");
		}
		$.removeData(this.element[0], "accordion");
		this.element.removeClass("ui-accordion").unbind(".accordion");
	}
});

function scopeCallback(callback, scope) {
	return function() {
		return callback.apply(scope, arguments);
	};
};

function completed(cancel) {
	// if removed while animated data can be empty
	if (!$.data(this, "accordion")) {
		return;
	}
	
	var instance = $.data(this, "accordion");
	var options = instance.options;
	options.running = cancel ? 0 : --options.running;
	if ( options.running ) {
		return;
	}
	if ( options.clearStyle ) {
		options.toShow.add(options.toHide).css({
			height: "",
			overflow: ""
		});
	}
	instance.trigger('change', null, options.data);
}

function toggle(toShow, toHide, data, clickedActive, down) {
	var options = $.data(this, "accordion").options;
	options.toShow = toShow;
	options.toHide = toHide;
	options.data = data;
	var complete = scopeCallback(completed, this);
	
	// count elements to animate
	options.running = toHide.size() === 0 ? toShow.size() : toHide.size();
	
	if ( options.animated ) {
		if ( !options.alwaysOpen && clickedActive ) {
			$.ui.accordion.animations[options.animated]({
				toShow: jQuery([]),
				toHide: toHide,
				complete: complete,
				down: down,
				autoHeight: options.autoHeight
			});
		} else {
			$.ui.accordion.animations[options.animated]({
				toShow: toShow,
				toHide: toHide,
				complete: complete,
				down: down,
				autoHeight: options.autoHeight
			});
		}
	} else {
		if ( !options.alwaysOpen && clickedActive ) {
			toShow.toggle();
		} else {
			toHide.hide();
			toShow.show();
		}
		complete(true);
	}
}

function clickHandler(event) {
	var options = $.data(this, "accordion").options;
	if (options.disabled) {
		return false;
	}
	
	// called only when using activate(false) to close all parts programmatically
	if ( !event.target && !options.alwaysOpen ) {
		options.active.parent().andSelf().toggleClass(options.selectedClass);
		var toHide = options.active.next(),
			data = {
				options: options,
				newHeader: jQuery([]),
				oldHeader: options.active,
				newContent: jQuery([]),
				oldContent: toHide
			},
			toShow = (options.active = $([]));
		toggle.call(this, toShow, toHide, data );
		return false;
	}
	// get the click target
	var clicked = $(event.target);
	
	// due to the event delegation model, we have to check if one
	// of the parent elements is our actual header, and find that
	// otherwise stick with the initial target
	clicked = $( clicked.parents(options.header)[0] || clicked );
	
	var clickedActive = clicked[0] == options.active[0];
	
	// if animations are still active, or the active header is the target, ignore click
	if (options.running || (options.alwaysOpen && clickedActive)) {
		return false;
	}
	if (!clicked.is(options.header)) {
		return;
	}
	
	// switch classes
	options.active.parent().andSelf().toggleClass(options.selectedClass);
	if ( !clickedActive ) {
		clicked.parent().andSelf().addClass(options.selectedClass);
	}
	
	// find elements to show and hide
	var toShow = clicked.next(),
		toHide = options.active.next(),
		data = {
			options: options,
			newHeader: clickedActive && !options.alwaysOpen ? $([]) : clicked,
			oldHeader: options.active,
			newContent: clickedActive && !options.alwaysOpen ? $([]) : toShow,
			oldContent: toHide
		},
		down = options.headers.index( options.active[0] ) > options.headers.index( clicked[0] );
	
	options.active = clickedActive ? $([]) : clicked;
	toggle.call(this, toShow, toHide, data, clickedActive, down );

	return false;
};

function findActive(headers, selector) {
	return selector
		? typeof selector == "number"
			? headers.filter(":eq(" + selector + ")")
			: headers.not(headers.not(selector))
		: selector === false
			? $([])
			: headers.filter(":eq(0)");
}

$.extend($.ui.accordion, {
	defaults: {
		selectedClass: "selected",
		alwaysOpen: true,
		animated: 'slide',
		event: "click",
		header: "a",
		autoHeight: true,
		running: 0,
		navigationFilter: function() {
			return this.href.toLowerCase() == location.href.toLowerCase();
		}
	},
	animations: {
		slide: function(options, additions) {
			options = $.extend({
				easing: "swing",
				duration: 300
			}, options, additions);
			if ( !options.toHide.size() ) {
				options.toShow.animate({height: "show"}, options);
				return;
			}
			var hideHeight = options.toHide.height(),
				showHeight = options.toShow.height(),
				difference = showHeight / hideHeight;
			options.toShow.css({ height: 0, overflow: 'hidden' }).show();
			options.toHide.filter(":hidden").each(options.complete).end().filter(":visible").animate({height:"hide"},{
				step: function(now) {
					var current = (hideHeight - now) * difference;
					if ($.browser.msie || $.browser.opera) {
						current = Math.ceil(current);
					}
					options.toShow.height( current );
				},
				duration: options.duration,
				easing: options.easing,
				complete: function() {
					if ( !options.autoHeight ) {
						options.toShow.css("height", "auto");
					}
					options.complete();
				}
			});
		},
		bounceslide: function(options) {
			this.slide(options, {
				easing: options.down ? "bounceout" : "swing",
				duration: options.down ? 1000 : 200
			});
		},
		easeslide: function(options) {
			this.slide(options, {
				easing: "easeinout",
				duration: 700
			});
		}
	}
});

// deprecated, use accordion("activate", index) instead
$.fn.activate = function(index) {
	return this.accordion("activate", index);
};

})(jQuery);

/*
 * jQuery UI Droppable
 *
 * Copyright (c) 2008 Paul Bakaus
 * Dual licensed under the MIT (MIT-LICENSE.txt)
 * and GPL (GPL-LICENSE.txt) licenses.
 * 
 * http://docs.jquery.com/UI/Droppables
 *
 * Depends:
 *	ui.core.js
 *	ui.draggable.js
 */
(function($) {

$.widget("ui.droppable", {
	init: function() {
		
		var o = this.options, accept = o.accept;
		this.isover = 0; this.isout = 1;

		this.options.accept = this.options.accept && this.options.accept.constructor == Function ? this.options.accept : function(d) {
			return d.is(accept);
		};

		//Store the droppable's proportions
		this.proportions = { width: this.element[0].offsetWidth, height: this.element[0].offsetHeight };
		
		// Add the reference and positions to the manager
		$.ui.ddmanager.droppables[this.options.scope] = $.ui.ddmanager.droppables[this.options.scope] || [];
		$.ui.ddmanager.droppables[this.options.scope].push(this);
		
		(this.options.cssNamespace && this.element.addClass(this.options.cssNamespace+"-droppable"));
		
	},
	plugins: {},
	ui: function(c) {
		return {
			draggable: (c.currentItem || c.element),
			helper: c.helper,
			position: c.position,
			absolutePosition: c.positionAbs,
			options: this.options,
			element: this.element
		};
	},
	destroy: function() {
		var drop = $.ui.ddmanager.droppables[this.options.scope];
		for ( var i = 0; i < drop.length; i++ )
			if ( drop[i] == this )
				drop.splice(i, 1);
		
		this.element
			.removeClass("ui-droppable-disabled")
			.removeData("droppable")
			.unbind(".droppable");
	},
	over: function(e) {
		
		var draggable = $.ui.ddmanager.current;
		if (!draggable || (draggable.currentItem || draggable.element)[0] == this.element[0]) return; // Bail if draggable and droppable are same element
		
		if (this.options.accept.call(this.element,(draggable.currentItem || draggable.element))) {
			$.ui.plugin.call(this, 'over', [e, this.ui(draggable)]);
			this.element.triggerHandler("dropover", [e, this.ui(draggable)], this.options.over);
		}
		
	},
	out: function(e) {
		
		var draggable = $.ui.ddmanager.current;
		if (!draggable || (draggable.currentItem || draggable.element)[0] == this.element[0]) return; // Bail if draggable and droppable are same element
		
		if (this.options.accept.call(this.element,(draggable.currentItem || draggable.element))) {
			$.ui.plugin.call(this, 'out', [e, this.ui(draggable)]);
			this.element.triggerHandler("dropout", [e, this.ui(draggable)], this.options.out);
		}
		
	},
	drop: function(e,custom) {
		
		var draggable = custom || $.ui.ddmanager.current;
		if (!draggable || (draggable.currentItem || draggable.element)[0] == this.element[0]) return false; // Bail if draggable and droppable are same element
		
		var childrenIntersection = false;
		this.element.find(":data(droppable)").not(".ui-draggable-dragging").each(function() {
			var inst = $.data(this, 'droppable');
			if(inst.options.greedy && $.ui.intersect(draggable, $.extend(inst, { offset: inst.element.offset() }), inst.options.tolerance)) {
				childrenIntersection = true; return false;
			}
		});
		if(childrenIntersection) return false;
		
		if(this.options.accept.call(this.element,(draggable.currentItem || draggable.element))) {
			$.ui.plugin.call(this, 'drop', [e, this.ui(draggable)]);
			this.element.triggerHandler("drop", [e, this.ui(draggable)], this.options.drop);
			return true;
		}
		
		return false;
		
	},
	activate: function(e) {
		
		var draggable = $.ui.ddmanager.current;
		$.ui.plugin.call(this, 'activate', [e, this.ui(draggable)]);
		if(draggable) this.element.triggerHandler("dropactivate", [e, this.ui(draggable)], this.options.activate);
		
	},
	deactivate: function(e) {
		
		var draggable = $.ui.ddmanager.current;
		$.ui.plugin.call(this, 'deactivate', [e, this.ui(draggable)]);
		if(draggable) this.element.triggerHandler("dropdeactivate", [e, this.ui(draggable)], this.options.deactivate);
		
	}
});

$.extend($.ui.droppable, {
	defaults: {
		disabled: false,
		tolerance: 'intersect',
		scope: 'default',
		cssNamespace: 'ui'
	}
});

$.ui.intersect = function(draggable, droppable, toleranceMode) {
	
	if (!droppable.offset) return false;
	
	var x1 = (draggable.positionAbs || draggable.position.absolute).left, x2 = x1 + draggable.helperProportions.width,
		y1 = (draggable.positionAbs || draggable.position.absolute).top, y2 = y1 + draggable.helperProportions.height;
	var l = droppable.offset.left, r = l + droppable.proportions.width,
		t = droppable.offset.top, b = t + droppable.proportions.height;
	
	switch (toleranceMode) {
		case 'fit':
			return (l < x1 && x2 < r
				&& t < y1 && y2 < b);
			break;
		case 'intersect':
			return (l < x1 + (draggable.helperProportions.width / 2) // Right Half
				&& x2 - (draggable.helperProportions.width / 2) < r // Left Half
				&& t < y1 + (draggable.helperProportions.height / 2) // Bottom Half
				&& y2 - (draggable.helperProportions.height / 2) < b ); // Top Half
			break;
		case 'pointer':
			return (l < ((draggable.positionAbs || draggable.position.absolute).left + (draggable.clickOffset || draggable.offset.click).left) && ((draggable.positionAbs || draggable.position.absolute).left + (draggable.clickOffset || draggable.offset.click).left) < r
				&& t < ((draggable.positionAbs || draggable.position.absolute).top + (draggable.clickOffset || draggable.offset.click).top) && ((draggable.positionAbs || draggable.position.absolute).top + (draggable.clickOffset || draggable.offset.click).top) < b);
			break;
		case 'touch':
			return (
					(y1 >= t && y1 <= b) ||	// Top edge touching
					(y2 >= t && y2 <= b) ||	// Bottom edge touching
					(y1 < t && y2 > b)		// Surrounded vertically
				) && (
					(x1 >= l && x1 <= r) ||	// Left edge touching
					(x2 >= l && x2 <= r) ||	// Right edge touching
					(x1 < l && x2 > r)		// Surrounded horizontally
				);
			break;
		default:
			return false;
			break;
		}
	
};

/*
	This manager tracks offsets of draggables and droppables
*/
$.ui.ddmanager = {
	current: null,
	droppables: { 'default': [] },
	prepareOffsets: function(t, e) {
		
		var m = $.ui.ddmanager.droppables[t.options.scope];
		var type = e ? e.type : null; // workaround for #2317
		var list = (t.currentItem || t.element).find(":data(droppable)").andSelf();	

		droppablesLoop: for (var i = 0; i < m.length; i++) {
			
			if(m[i].options.disabled || (t && !m[i].options.accept.call(m[i].element,(t.currentItem || t.element)))) continue;	//No disabled and non-accepted
			for (var j=0; j < list.length; j++) { if(list[j] == m[i].element[0]) { m[i].proportions.height = 0; continue droppablesLoop; } }; //Filter out elements in the current dragged item
			m[i].visible = m[i].element.css("display") != "none"; if(!m[i].visible) continue; 									//If the element is not visible, continue
			
			m[i].offset = m[i].element.offset();
			m[i].proportions = { width: m[i].element[0].offsetWidth, height: m[i].element[0].offsetHeight };
			
			if(type == "dragstart" || type == "sortactivate") m[i].activate.call(m[i], e); 										//Activate the droppable if used directly from draggables
			
		}
		
	},
	drop: function(draggable, e) {
		
		var dropped = false;
		$.each($.ui.ddmanager.droppables[draggable.options.scope], function() {
			
			if(!this.options) return;
			if (!this.options.disabled && this.visible && $.ui.intersect(draggable, this, this.options.tolerance))
				dropped = this.drop.call(this, e);
			
			if (!this.options.disabled && this.visible && this.options.accept.call(this.element,(draggable.currentItem || draggable.element))) {
				this.isout = 1; this.isover = 0;
				this.deactivate.call(this, e);
			}
			
		});
		return dropped;
		
	},
	drag: function(draggable, e) {
		
		//If you have a highly dynamic page, you might try this option. It renders positions every time you move the mouse.
		if(draggable.options.refreshPositions) $.ui.ddmanager.prepareOffsets(draggable, e);
		
		//Run through all droppables and check their positions based on specific tolerance options

		$.each($.ui.ddmanager.droppables[draggable.options.scope], function() {
			
			if(this.options.disabled || this.greedyChild || !this.visible) return;
			var intersects = $.ui.intersect(draggable, this, this.options.tolerance);
			
			var c = !intersects && this.isover == 1 ? 'isout' : (intersects && this.isover == 0 ? 'isover' : null);
			if(!c) return;
			
			var parentInstance;
			if (this.options.greedy) {
				var parent = this.element.parents(':data(droppable):eq(0)');
				if (parent.length) {
					parentInstance = $.data(parent[0], 'droppable');
					parentInstance.greedyChild = (c == 'isover' ? 1 : 0);
				}
			}
			
			// we just moved into a greedy child
			if (parentInstance && c == 'isover') {
				parentInstance['isover'] = 0;
				parentInstance['isout'] = 1;
				parentInstance.out.call(parentInstance, e);
			}
			
			this[c] = 1; this[c == 'isout' ? 'isover' : 'isout'] = 0;
			this[c == "isover" ? "over" : "out"].call(this, e);
			
			// we just moved out of a greedy child
			if (parentInstance && c == 'isout') {
				parentInstance['isout'] = 0;
				parentInstance['isover'] = 1;
				parentInstance.over.call(parentInstance, e);
			}
		});
		
	}
};

/*
 * Droppable Extensions
 */

$.ui.plugin.add("droppable", "activeClass", {
	activate: function(e, ui) {
		$(this).addClass(ui.options.activeClass);
	},
	deactivate: function(e, ui) {
		$(this).removeClass(ui.options.activeClass);
	},
	drop: function(e, ui) {
		$(this).removeClass(ui.options.activeClass);
	}
});

$.ui.plugin.add("droppable", "hoverClass", {
	over: function(e, ui) {
		$(this).addClass(ui.options.hoverClass);
	},
	out: function(e, ui) {
		$(this).removeClass(ui.options.hoverClass);
	},
	drop: function(e, ui) {
		$(this).removeClass(ui.options.hoverClass);
	}
});

})(jQuery);

/*
 * jQuery UI Resizable
 *
 * Copyright (c) 2008 Paul Bakaus
 * Dual licensed under the MIT (MIT-LICENSE.txt)
 * and GPL (GPL-LICENSE.txt) licenses.
 * 
 * http://docs.jquery.com/UI/Resizables
 *
 * Depends:
 *	ui.core.js
 */
(function($) {

$.widget("ui.resizable", $.extend({}, $.ui.mouse, {
	init: function() {

		var self = this, o = this.options;

		var elpos = this.element.css('position');
		
		this.originalElement = this.element;
		
		// simulate .ui-resizable { position: relative; }
		this.element.addClass("ui-resizable").css({ position: /static/.test(elpos) ? 'relative' : elpos });
		
		$.extend(o, {
			_aspectRatio: !!(o.aspectRatio),
			helper: o.helper || o.ghost || o.animate ? o.helper || 'proxy' : null,
			knobHandles: o.knobHandles === true ? 'ui-resizable-knob-handle' : o.knobHandles
		});
		
		//Default Theme
		var aBorder = '1px solid #DEDEDE';
		
		o.defaultTheme = {
			'ui-resizable': { display: 'block' },
			'ui-resizable-handle': { position: 'absolute', background: '#F2F2F2', fontSize: '0.1px' },
			'ui-resizable-n': { cursor: 'n-resize', height: '4px', left: '0px', right: '0px', borderTop: aBorder },
			'ui-resizable-s': { cursor: 's-resize', height: '4px', left: '0px', right: '0px', borderBottom: aBorder },
			'ui-resizable-e': { cursor: 'e-resize', width: '4px', top: '0px', bottom: '0px', borderRight: aBorder },
			'ui-resizable-w': { cursor: 'w-resize', width: '4px', top: '0px', bottom: '0px', borderLeft: aBorder },
			'ui-resizable-se': { cursor: 'se-resize', width: '4px', height: '4px', borderRight: aBorder, borderBottom: aBorder },
			'ui-resizable-sw': { cursor: 'sw-resize', width: '4px', height: '4px', borderBottom: aBorder, borderLeft: aBorder },
			'ui-resizable-ne': { cursor: 'ne-resize', width: '4px', height: '4px', borderRight: aBorder, borderTop: aBorder },
			'ui-resizable-nw': { cursor: 'nw-resize', width: '4px', height: '4px', borderLeft: aBorder, borderTop: aBorder }
		};
		
		o.knobTheme = {
			'ui-resizable-handle': { background: '#F2F2F2', border: '1px solid #808080', height: '8px', width: '8px' },
			'ui-resizable-n': { cursor: 'n-resize', top: '0px', left: '45%' },
			'ui-resizable-s': { cursor: 's-resize', bottom: '0px', left: '45%' },
			'ui-resizable-e': { cursor: 'e-resize', right: '0px', top: '45%' },
			'ui-resizable-w': { cursor: 'w-resize', left: '0px', top: '45%' },
			'ui-resizable-se': { cursor: 'se-resize', right: '0px', bottom: '0px' },
			'ui-resizable-sw': { cursor: 'sw-resize', left: '0px', bottom: '0px' },
			'ui-resizable-nw': { cursor: 'nw-resize', left: '0px', top: '0px' },
			'ui-resizable-ne': { cursor: 'ne-resize', right: '0px', top: '0px' }
		};
		
		o._nodeName = this.element[0].nodeName;
		
		//Wrap the element if it cannot hold child nodes
		if(o._nodeName.match(/canvas|textarea|input|select|button|img/i)) {
			var el = this.element;
			
			//Opera fixing relative position
			if (/relative/.test(el.css('position')) && $.browser.opera)
				el.css({ position: 'relative', top: 'auto', left: 'auto' });
			
			//Create a wrapper element and set the wrapper to the new current internal element
			el.wrap(
				$('<div class="ui-wrapper"	style="overflow: hidden;"></div>').css( {
					position: el.css('position'),
					width: el.outerWidth(),
					height: el.outerHeight(),
					top: el.css('top'),
					left: el.css('left')
				})
			);
			
			var oel = this.element; this.element = this.element.parent();
			
			// store instance on wrapper
			this.element.data('resizable', this); 
			
			//Move margins to the wrapper
			this.element.css({ marginLeft: oel.css("marginLeft"), marginTop: oel.css("marginTop"),
				marginRight: oel.css("marginRight"), marginBottom: oel.css("marginBottom")
			});
			
			oel.css({ marginLeft: 0, marginTop: 0, marginRight: 0, marginBottom: 0});
			
			//Prevent Safari textarea resize
			if ($.browser.safari && o.preventDefault) oel.css('resize', 'none');
			
			o.proportionallyResize = oel.css({ position: 'static', zoom: 1, display: 'block' });
			
			// avoid IE jump
			this.element.css({ margin: oel.css('margin') });
			
			// fix handlers offset
			this._proportionallyResize();
		}
		
		if(!o.handles) o.handles = !$('.ui-resizable-handle', this.element).length ? "e,s,se" : { n: '.ui-resizable-n', e: '.ui-resizable-e', s: '.ui-resizable-s', w: '.ui-resizable-w', se: '.ui-resizable-se', sw: '.ui-resizable-sw', ne: '.ui-resizable-ne', nw: '.ui-resizable-nw' };
		if(o.handles.constructor == String) {
			
			o.zIndex = o.zIndex || 1000;
			
			if(o.handles == 'all') o.handles = 'n,e,s,w,se,sw,ne,nw';
			
			var n = o.handles.split(","); o.handles = {};
			
			// insertions are applied when don't have theme loaded
			var insertionsDefault = {
				handle: 'position: absolute; display: none; overflow:hidden;',
				n: 'top: 0pt; width:100%;',
				e: 'right: 0pt; height:100%;',
				s: 'bottom: 0pt; width:100%;',
				w: 'left: 0pt; height:100%;',
				se: 'bottom: 0pt; right: 0px;',
				sw: 'bottom: 0pt; left: 0px;',
				ne: 'top: 0pt; right: 0px;',
				nw: 'top: 0pt; left: 0px;'
			};
			
			for(var i = 0; i < n.length; i++) {
				var handle = $.trim(n[i]), dt = o.defaultTheme, hname = 'ui-resizable-'+handle, loadDefault = !$.ui.css(hname) && !o.knobHandles, userKnobClass = $.ui.css('ui-resizable-knob-handle'), 
							allDefTheme = $.extend(dt[hname], dt['ui-resizable-handle']), allKnobTheme = $.extend(o.knobTheme[hname], !userKnobClass ? o.knobTheme['ui-resizable-handle'] : {});
				
				// increase zIndex of sw, se, ne, nw axis
				var applyZIndex = /sw|se|ne|nw/.test(handle) ? { zIndex: ++o.zIndex } : {};
				
				var defCss = (loadDefault ? insertionsDefault[handle] : ''), 
					axis = $(['<div class="ui-resizable-handle ', hname, '" style="', defCss, insertionsDefault.handle, '"></div>'].join('')).css( applyZIndex );
				o.handles[handle] = '.ui-resizable-'+handle;
				
				this.element.append(
					//Theme detection, if not loaded, load o.defaultTheme
					axis.css( loadDefault ? allDefTheme : {} )
						// Load the knobHandle css, fix width, height, top, left...
						.css( o.knobHandles ? allKnobTheme : {} ).addClass(o.knobHandles ? 'ui-resizable-knob-handle' : '').addClass(o.knobHandles)
				);
			}
			
			if (o.knobHandles) this.element.addClass('ui-resizable-knob').css( !$.ui.css('ui-resizable-knob') ? { /*border: '1px #fff dashed'*/ } : {} );
		}
		
		this._renderAxis = function(target) {
			target = target || this.element;
			
			for(var i in o.handles) {
				if(o.handles[i].constructor == String) 
					o.handles[i] = $(o.handles[i], this.element).show();
				
				if (o.transparent)
					o.handles[i].css({opacity:0});
				
				//Apply pad to wrapper element, needed to fix axis position (textarea, inputs, scrolls)
				if (this.element.is('.ui-wrapper') && 
					o._nodeName.match(/textarea|input|select|button/i)) {
					
					var axis = $(o.handles[i], this.element), padWrapper = 0;
					
					//Checking the correct pad and border
					padWrapper = /sw|ne|nw|se|n|s/.test(i) ? axis.outerHeight() : axis.outerWidth();
					
					//The padding type i have to apply...
					var padPos = [ 'padding', 
						/ne|nw|n/.test(i) ? 'Top' :
						/se|sw|s/.test(i) ? 'Bottom' : 
						/^e$/.test(i) ? 'Right' : 'Left' ].join(""); 
					
					if (!o.transparent)
						target.css(padPos, padWrapper);
					
					this._proportionallyResize();
				}
				if(!$(o.handles[i]).length) continue;
			}
		};
		
		this._renderAxis(this.element);
		o._handles = $('.ui-resizable-handle', self.element);
		
		if (o.disableSelection)
			o._handles.each(function(i, e) { $.ui.disableSelection(e); });
		
		//Matching axis name
		o._handles.mouseover(function() {
			if (!o.resizing) {
				if (this.className) 
					var axis = this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i);
				//Axis, default = se
				self.axis = o.axis = axis && axis[1] ? axis[1] : 'se';
			}
		});
		
		//If we want to auto hide the elements
		if (o.autoHide) {
			o._handles.hide();
			$(self.element).addClass("ui-resizable-autohide").hover(function() {
				$(this).removeClass("ui-resizable-autohide");
				o._handles.show();
			},
			function(){
				if (!o.resizing) {
					$(this).addClass("ui-resizable-autohide");
					o._handles.hide();
				}
			});
		}
		
		this.mouseInit();
	},
	plugins: {},
	ui: function() {
		return {
			originalElement: this.originalElement,
			element: this.element,
			helper: this.helper,
			position: this.position,
			size: this.size,
			options: this.options,
			originalSize: this.originalSize,
			originalPosition: this.originalPosition
		};
	},
	propagate: function(n,e) {
		$.ui.plugin.call(this, n, [e, this.ui()]);
		if (n != "resize") this.element.triggerHandler(["resize", n].join(""), [e, this.ui()], this.options[n]);
	},
	destroy: function() {
		var el = this.element, wrapped = el.children(".ui-resizable").get(0);
		
		this.mouseDestroy();
		
		var _destroy = function(exp) {
			$(exp).removeClass("ui-resizable ui-resizable-disabled")
				.removeData("resizable").unbind(".resizable").find('.ui-resizable-handle').remove();
		};
		
		_destroy(el);
		
		if (el.is('.ui-wrapper') && wrapped) {
			el.parent().append(
				$(wrapped).css({
					position: el.css('position'),
					width: el.outerWidth(),
					height: el.outerHeight(),
					top: el.css('top'),
					left: el.css('left')
				})
			).end().remove();
			
			_destroy(wrapped);
		}
	},
	mouseStart: function(e) {
		if(this.options.disabled) return false;
		
		var handle = false;
		for(var i in this.options.handles) {
			if($(this.options.handles[i])[0] == e.target) handle = true;
		}
		if (!handle) return false;
		
		var o = this.options, iniPos = this.element.position(), el = this.element, 
			num = function(v) { return parseInt(v, 10) || 0; }, ie6 = $.browser.msie && $.browser.version < 7;
		o.resizing = true;
		o.documentScroll = { top: $(document).scrollTop(), left: $(document).scrollLeft() };
		
		// bugfix #1749
		if (el.is('.ui-draggable') || (/absolute/).test(el.css('position'))) {
			
			// sOffset decides if document scrollOffset will be added to the top/left of the resizable element
			var sOffset = $.browser.msie && !o.containment && (/absolute/).test(el.css('position')) && !(/relative/).test(el.parent().css('position'));
			var dscrollt = sOffset ? o.documentScroll.top : 0, dscrolll = sOffset ? o.documentScroll.left : 0;
			
			el.css({ position: 'absolute', top: (iniPos.top + dscrollt), left: (iniPos.left + dscrolll) });
		}
		
		//Opera fixing relative position
		if ($.browser.opera && /relative/.test(el.css('position')))
			el.css({ position: 'relative', top: 'auto', left: 'auto' });
		
		this._renderProxy();
		
		var curleft = num(this.helper.css('left')), curtop = num(this.helper.css('top'));
		
		if (o.containment) {
			curleft += $(o.containment).scrollLeft()||0;
			curtop += $(o.containment).scrollTop()||0;
		}
		
		//Store needed variables
		this.offset = this.helper.offset();
		this.position = { left: curleft, top: curtop };
		this.size = o.helper || ie6 ? { width: el.outerWidth(), height: el.outerHeight() } : { width: el.width(), height: el.height() };
		this.originalSize = o.helper || ie6 ? { width: el.outerWidth(), height: el.outerHeight() } : { width: el.width(), height: el.height() };
		this.originalPosition = { left: curleft, top: curtop };
		this.sizeDiff = { width: el.outerWidth() - el.width(), height: el.outerHeight() - el.height() };
		this.originalMousePosition = { left: e.pageX, top: e.pageY };
		
		//Aspect Ratio
		o.aspectRatio = (typeof o.aspectRatio == 'number') ? o.aspectRatio : ((this.originalSize.height / this.originalSize.width)||1);
		
		if (o.preserveCursor)
			$('body').css('cursor', this.axis + '-resize');
			
		this.propagate("start", e);
		return true;
	},
	mouseDrag: function(e) {
		
		//Increase performance, avoid regex
		var el = this.helper, o = this.options, props = {},
			self = this, smp = this.originalMousePosition, a = this.axis;
		
		var dx = (e.pageX-smp.left)||0, dy = (e.pageY-smp.top)||0;
		var trigger = this._change[a];
		if (!trigger) return false;
		
		// Calculate the attrs that will be change
		var data = trigger.apply(this, [e, dx, dy]), ie6 = $.browser.msie && $.browser.version < 7, csdif = this.sizeDiff;
		
		if (o._aspectRatio || e.shiftKey)
			data = this._updateRatio(data, e);
		
		data = this._respectSize(data, e);
		
		// plugins callbacks need to be called first
		this.propagate("resize", e);
		
		el.css({
			top: this.position.top + "px", left: this.position.left + "px", 
			width: this.size.width + "px", height: this.size.height + "px"
		});
		
		if (!o.helper && o.proportionallyResize)
			this._proportionallyResize();
		
		this._updateCache(data);
		
		// calling the user callback at the end
		this.element.triggerHandler("resize", [e, this.ui()], this.options["resize"]);
		
		return false;
	},
	mouseStop: function(e) {
		
		this.options.resizing = false;
		var o = this.options, num = function(v) { return parseInt(v, 10) || 0; }, self = this;
		
		if(o.helper) {
			var pr = o.proportionallyResize, ista = pr && (/textarea/i).test(pr.get(0).nodeName), 
						soffseth = ista && $.ui.hasScroll(pr.get(0), 'left') /* TODO - jump height */ ? 0 : self.sizeDiff.height,
							soffsetw = ista ? 0 : self.sizeDiff.width;
			
			var s = { width: (self.size.width - soffsetw), height: (self.size.height - soffseth) },
				left = (parseInt(self.element.css('left'), 10) + (self.position.left - self.originalPosition.left)) || null, 
				top = (parseInt(self.element.css('top'), 10) + (self.position.top - self.originalPosition.top)) || null;
			
			if (!o.animate)
				this.element.css($.extend(s, { top: top, left: left }));
			
			if (o.helper && !o.animate) this._proportionallyResize();
		}
		
		if (o.preserveCursor)
			$('body').css('cursor', 'auto');
		
		this.propagate("stop", e);
		
		if (o.helper) this.helper.remove();
		
		return false;
	},
	_updateCache: function(data) {
		var o = this.options;
		this.offset = this.helper.offset();
		if (data.left) this.position.left = data.left;
		if (data.top) this.position.top = data.top;
		if (data.height) this.size.height = data.height;
		if (data.width) this.size.width = data.width;
	},
	_updateRatio: function(data, e) {
		var o = this.options, cpos = this.position, csize = this.size, a = this.axis;
		
		if (data.height) data.width = (csize.height / o.aspectRatio);
		else if (data.width) data.height = (csize.width * o.aspectRatio);
		
		if (a == 'sw') {
			data.left = cpos.left + (csize.width - data.width);
			data.top = null;
		}
		if (a == 'nw') { 
			data.top = cpos.top + (csize.height - data.height);
			data.left = cpos.left + (csize.width - data.width);
		}
		
		return data;
	},
	_respectSize: function(data, e) {
		
		var el = this.helper, o = this.options, pRatio = o._aspectRatio || e.shiftKey, a = this.axis, 
				ismaxw = data.width && o.maxWidth && o.maxWidth < data.width, ismaxh = data.height && o.maxHeight && o.maxHeight < data.height,
					isminw = data.width && o.minWidth && o.minWidth > data.width, isminh = data.height && o.minHeight && o.minHeight > data.height;
		
		if (isminw) data.width = o.minWidth;
		if (isminh) data.height = o.minHeight;
		if (ismaxw) data.width = o.maxWidth;
		if (ismaxh) data.height = o.maxHeight;
		
		var dw = this.originalPosition.left + this.originalSize.width, dh = this.position.top + this.size.height;
		var cw = /sw|nw|w/.test(a), ch = /nw|ne|n/.test(a);
		
		if (isminw && cw) data.left = dw - o.minWidth;
		if (ismaxw && cw) data.left = dw - o.maxWidth;
		if (isminh && ch)	data.top = dh - o.minHeight;
		if (ismaxh && ch)	data.top = dh - o.maxHeight;
		
		// fixing jump error on top/left - bug #2330
		var isNotwh = !data.width && !data.height;
		if (isNotwh && !data.left && data.top) data.top = null;
		else if (isNotwh && !data.top && data.left) data.left = null;
		
		return data;
	},
	_proportionallyResize: function() {
		var o = this.options;
		if (!o.proportionallyResize) return;
		var prel = o.proportionallyResize, el = this.helper || this.element;
		
		if (!o.borderDif) {
			var b = [prel.css('borderTopWidth'), prel.css('borderRightWidth'), prel.css('borderBottomWidth'), prel.css('borderLeftWidth')],
				p = [prel.css('paddingTop'), prel.css('paddingRight'), prel.css('paddingBottom'), prel.css('paddingLeft')];
			
			o.borderDif = $.map(b, function(v, i) {
				var border = parseInt(v,10)||0, padding = parseInt(p[i],10)||0;
				return border + padding; 
			});
		}
		prel.css({
			height: (el.height() - o.borderDif[0] - o.borderDif[2]) + "px",
			width: (el.width() - o.borderDif[1] - o.borderDif[3]) + "px"
		});
	},
	_renderProxy: function() {
		var el = this.element, o = this.options;
		this.elementOffset = el.offset();
		
		if(o.helper) {
			this.helper = this.helper || $('<div style="overflow:hidden;"></div>');
			
			// fix ie6 offset
			var ie6 = $.browser.msie && $.browser.version < 7, ie6offset = (ie6 ? 1 : 0),
			pxyoffset = ( ie6 ? 2 : -1 );
			
			this.helper.addClass(o.helper).css({
				width: el.outerWidth() + pxyoffset,
				height: el.outerHeight() + pxyoffset,
				position: 'absolute',
				left: this.elementOffset.left - ie6offset +'px',
				top: this.elementOffset.top - ie6offset +'px',
				zIndex: ++o.zIndex
			});
			
			this.helper.appendTo("body");
			
			if (o.disableSelection)
				$.ui.disableSelection(this.helper.get(0));
			
		} else {
			this.helper = el; 
		}
	},
	_change: {
		e: function(e, dx, dy) {
			return { width: this.originalSize.width + dx };
		},
		w: function(e, dx, dy) {
			var o = this.options, cs = this.originalSize, sp = this.originalPosition;
			return { left: sp.left + dx, width: cs.width - dx };
		},
		n: function(e, dx, dy) {
			var o = this.options, cs = this.originalSize, sp = this.originalPosition;
			return { top: sp.top + dy, height: cs.height - dy };
		},
		s: function(e, dx, dy) {
			return { height: this.originalSize.height + dy };
		},
		se: function(e, dx, dy) {
			return $.extend(this._change.s.apply(this, arguments), this._change.e.apply(this, [e, dx, dy]));
		},
		sw: function(e, dx, dy) {
			return $.extend(this._change.s.apply(this, arguments), this._change.w.apply(this, [e, dx, dy]));
		},
		ne: function(e, dx, dy) {
			return $.extend(this._change.n.apply(this, arguments), this._change.e.apply(this, [e, dx, dy]));
		},
		nw: function(e, dx, dy) {
			return $.extend(this._change.n.apply(this, arguments), this._change.w.apply(this, [e, dx, dy]));
		}
	}
}));

$.extend($.ui.resizable, {
	defaults: {
		cancel: ":input",
		distance: 1,
		delay: 0,
		preventDefault: true,
		transparent: false,
		minWidth: 10,
		minHeight: 10,
		aspectRatio: false,
		disableSelection: true,
		preserveCursor: true,
		autoHide: false,
		knobHandles: false
	}
});

/*
 * Resizable Extensions
 */

$.ui.plugin.add("resizable", "containment", {
	
	start: function(e, ui) {
		var o = ui.options, self = $(this).data("resizable"), el = self.element;
		var oc = o.containment,	ce = (oc instanceof $) ? oc.get(0) : (/parent/.test(oc)) ? el.parent().get(0) : oc;
		if (!ce) return;
		
		self.containerElement = $(ce);
		
		if (/document/.test(oc) || oc == document) {
			self.containerOffset = { left: 0, top: 0 };
			self.containerPosition = { left: 0, top: 0 };
			
			self.parentData = { 
				element: $(document), left: 0, top: 0, 
				width: $(document).width(), height: $(document).height() || document.body.parentNode.scrollHeight
			};
		}
		
				
		// i'm a node, so compute top, left, right, bottom
		else{
			self.containerOffset = $(ce).offset();
			self.containerPosition = $(ce).position();
			self.containerSize = { height: $(ce).innerHeight(), width: $(ce).innerWidth() };
		
			var co = self.containerOffset, ch = self.containerSize.height,	cw = self.containerSize.width, 
						width = ($.ui.hasScroll(ce, "left") ? ce.scrollWidth : cw ), height = ($.ui.hasScroll(ce) ? ce.scrollHeight : ch);
		
			self.parentData = { 
				element: ce, left: co.left, top: co.top, width: width, height: height
			};
		}
	},
	
	resize: function(e, ui) {
		var o = ui.options, self = $(this).data("resizable"), 
				ps = self.containerSize, co = self.containerOffset, cs = self.size, cp = self.position,
				pRatio = o._aspectRatio || e.shiftKey, cop = { top:0, left:0 }, ce = self.containerElement;
		
		if (ce[0] != document && /static/.test(ce.css('position')))
			cop = self.containerPosition;
		
		if (cp.left < (o.helper ? co.left : cop.left)) {
			self.size.width = self.size.width + (o.helper ? (self.position.left - co.left) : (self.position.left - cop.left));
			if (pRatio) self.size.height = self.size.width * o.aspectRatio;
			self.position.left = o.helper ? co.left : cop.left;
		}
		
		if (cp.top < (o.helper ? co.top : 0)) {
			self.size.height = self.size.height + (o.helper ? (self.position.top - co.top) : self.position.top);
			if (pRatio) self.size.width = self.size.height / o.aspectRatio;
			self.position.top = o.helper ? co.top : 0;
		}
		
		var woset = (o.helper ? self.offset.left - co.left : (self.position.left - cop.left)) + self.sizeDiff.width, 
					hoset = (o.helper ? self.offset.top - co.top : self.position.top) + self.sizeDiff.height;
		
		if (woset + self.size.width >= self.parentData.width) {
			self.size.width = self.parentData.width - woset;
			if (pRatio) self.size.height = self.size.width * o.aspectRatio;
		}
		
		if (hoset + self.size.height >= self.parentData.height) {
			self.size.height = self.parentData.height - hoset;
			if (pRatio) self.size.width = self.size.height / o.aspectRatio;
		}
	},
	
	stop: function(e, ui){
		var o = ui.options, self = $(this).data("resizable"), cp = self.position,
				co = self.containerOffset, cop = self.containerPosition, ce = self.containerElement;
		
		var helper = $(self.helper), ho = helper.offset(), w = helper.innerWidth(), h = helper.innerHeight();
		
		
		if (o.helper && !o.animate && /relative/.test(ce.css('position')))
			$(this).css({ left: (ho.left - co.left), top: (ho.top - co.top), width: w, height: h });
		
		if (o.helper && !o.animate && /static/.test(ce.css('position')))
			$(this).css({ left: cop.left + (ho.left - co.left), top: cop.top + (ho.top - co.top), width: w, height: h });
		
	}
});

$.ui.plugin.add("resizable", "grid", {
	
	resize: function(e, ui) {
		var o = ui.options, self = $(this).data("resizable"), cs = self.size, os = self.originalSize, op = self.originalPosition, a = self.axis, ratio = o._aspectRatio || e.shiftKey;
		o.grid = typeof o.grid == "number" ? [o.grid, o.grid] : o.grid;
		var ox = Math.round((cs.width - os.width) / (o.grid[0]||1)) * (o.grid[0]||1), oy = Math.round((cs.height - os.height) / (o.grid[1]||1)) * (o.grid[1]||1);
		
		if (/^(se|s|e)$/.test(a)) {
			self.size.width = os.width + ox;
			self.size.height = os.height + oy;
		}
		else if (/^(ne)$/.test(a)) {
			self.size.width = os.width + ox;
			self.size.height = os.height + oy;
			self.position.top = op.top - oy;
		}
		else if (/^(sw)$/.test(a)) {
			self.size.width = os.width + ox;
			self.size.height = os.height + oy;
			self.position.left = op.left - ox;
		}
		else {
			self.size.width = os.width + ox;
			self.size.height = os.height + oy;
			self.position.top = op.top - oy;
			self.position.left = op.left - ox;
		}
	}
	
});

$.ui.plugin.add("resizable", "animate", {
	
	stop: function(e, ui) {
		var o = ui.options, self = $(this).data("resizable");
		
		var pr = o.proportionallyResize, ista = pr && (/textarea/i).test(pr.get(0).nodeName), 
						soffseth = ista && $.ui.hasScroll(pr.get(0), 'left') /* TODO - jump height */ ? 0 : self.sizeDiff.height,
							soffsetw = ista ? 0 : self.sizeDiff.width;
		
		var style = { width: (self.size.width - soffsetw), height: (self.size.height - soffseth) },
					left = (parseInt(self.element.css('left'), 10) + (self.position.left - self.originalPosition.left)) || null, 
						top = (parseInt(self.element.css('top'), 10) + (self.position.top - self.originalPosition.top)) || null; 
		
		self.element.animate(
			$.extend(style, top && left ? { top: top, left: left } : {}), { 
				duration: o.animateDuration || "slow", easing: o.animateEasing || "swing", 
				step: function() {
					
					var data = {
						width: parseInt(self.element.css('width'), 10),
						height: parseInt(self.element.css('height'), 10),
						top: parseInt(self.element.css('top'), 10),
						left: parseInt(self.element.css('left'), 10)
					};
					
					if (pr) pr.css({ width: data.width, height: data.height });
					
					// propagating resize, and updating values for each animation step
					self._updateCache(data);
					self.propagate("animate", e);
					
				}
			}
		);
	}
	
});

$.ui.plugin.add("resizable", "ghost", {
	
	start: function(e, ui) {
		var o = ui.options, self = $(this).data("resizable"), pr = o.proportionallyResize, cs = self.size;
		
		if (!pr) self.ghost = self.element.clone();
		else self.ghost = pr.clone();
		
		self.ghost.css(
			{ opacity: .25, display: 'block', position: 'relative', height: cs.height, width: cs.width, margin: 0, left: 0, top: 0 }
		)
		.addClass('ui-resizable-ghost').addClass(typeof o.ghost == 'string' ? o.ghost : '');
		
		self.ghost.appendTo(self.helper);
		
	},
	
	resize: function(e, ui){
		var o = ui.options, self = $(this).data("resizable"), pr = o.proportionallyResize;
		
		if (self.ghost) self.ghost.css({ position: 'relative', height: self.size.height, width: self.size.width });
		
	},
	
	stop: function(e, ui){
		var o = ui.options, self = $(this).data("resizable"), pr = o.proportionallyResize;
		if (self.ghost && self.helper) self.helper.get(0).removeChild(self.ghost.get(0));
	}
	
});

$.ui.plugin.add("resizable", "alsoResize", {
	
	start: function(e, ui) {
		var o = ui.options, self = $(this).data("resizable"), 
		
		_store = function(exp) {
			$(exp).each(function() {
				$(this).data("resizable-alsoresize", {
					width: parseInt($(this).width(), 10), height: parseInt($(this).height(), 10),
					left: parseInt($(this).css('left'), 10), top: parseInt($(this).css('top'), 10)
				});
			});
		};
		
		if (typeof(o.alsoResize) == 'object') {
			if (o.alsoResize.length) { o.alsoResize = o.alsoResize[0];	_store(o.alsoResize); }
			else { $.each(o.alsoResize, function(exp, c) { _store(exp); }); }
		}else{
			_store(o.alsoResize);
		} 
	},
	
	resize: function(e, ui){
		var o = ui.options, self = $(this).data("resizable"), os = self.originalSize, op = self.originalPosition;
		
		var delta = { 
			height: (self.size.height - os.height) || 0, width: (self.size.width - os.width) || 0,
			top: (self.position.top - op.top) || 0, left: (self.position.left - op.left) || 0
		},
		
		_alsoResize = function(exp, c) {
			$(exp).each(function() {
				var start = $(this).data("resizable-alsoresize"), style = {}, css = c && c.length ? c : ['width', 'height', 'top', 'left'];
				
				$.each(css || ['width', 'height', 'top', 'left'], function(i, prop) {
					var sum = (start[prop]||0) + (delta[prop]||0);
					if (sum && sum >= 0)
						style[prop] = sum || null;
				});
				$(this).css(style);
			});
		};
		
		if (typeof(o.alsoResize) == 'object') {
			$.each(o.alsoResize, function(exp, c) { _alsoResize(exp, c); });
		}else{
			_alsoResize(o.alsoResize);
		}
	},
	
	stop: function(e, ui){
		$(this).removeData("resizable-alsoresize-start");
	}
});

})(jQuery);

/*
 * jQuery UI Sortable
 *
 * Copyright (c) 2008 Paul Bakaus
 * Dual licensed under the MIT (MIT-LICENSE.txt)
 * and GPL (GPL-LICENSE.txt) licenses.
 *
 * http://docs.jquery.com/UI/Sortables
 *
 * Depends:
 *	ui.core.js
 */
(function($) {

function contains(a, b) {
    var safari2 = $.browser.safari && $.browser.version < 522;
    if (a.contains && !safari2) {
        return a.contains(b);
    }
    if (a.compareDocumentPosition)
        return !!(a.compareDocumentPosition(b) & 16);
    while (b = b.parentNode)
          if (b == a) return true;
    return false;
};

$.widget("ui.sortable", $.extend({}, $.ui.mouse, {
	init: function() {

		var o = this.options;
		this.containerCache = {};
		this.element.addClass("ui-sortable");

		//Get the items
		this.refresh();

		//Let's determine if the items are floating
		this.floating = this.items.length ? (/left|right/).test(this.items[0].item.css('float')) : false;

		//Let's determine the parent's offset
		//if(!(/(relative|absolute|fixed)/).test(this.element.css('position'))) this.element.css('position', 'relative');
		this.offset = this.element.offset();

		//Initialize mouse events for interaction
		this.mouseInit();

	},
	plugins: {},
	ui: function(inst) {
		return {
			helper: (inst || this)["helper"],
			placeholder: (inst || this)["placeholder"] || $([]),
			position: (inst || this)["position"],
			absolutePosition: (inst || this)["positionAbs"],
			options: this.options,
			element: this.element,
			item: (inst || this)["currentItem"],
			sender: inst ? inst.element : null
		};
	},

	propagate: function(n,e,inst, noPropagation) {
		$.ui.plugin.call(this, n, [e, this.ui(inst)]);
		if(!noPropagation) this.element.triggerHandler(n == "sort" ? n : "sort"+n, [e, this.ui(inst)], this.options[n]);
	},

	serialize: function(o) {

		var items = this.getItemsAsjQuery(o && o.connected);
		var str = []; o = o || {};

		$(items).each(function() {
			var res = ($(this.item || this).attr(o.attribute || 'id') || '').match(o.expression || (/(.+)[-=_](.+)/));
			if(res) str.push((o.key || res[1])+'[]='+(o.key && o.expression ? res[1] : res[2]));
		});

		return str.join('&');

	},

	toArray: function(attr) {

		var items = this.getItemsAsjQuery(o && o.connected);
		var ret = [];

		items.each(function() { ret.push($(this).attr(attr || 'id')); });
		return ret;

	},

	/* Be careful with the following core functions */
	intersectsWith: function(item) {
		var x1 = this.positionAbs.left, x2 = x1 + this.helperProportions.width,
		y1 = this.positionAbs.top, y2 = y1 + this.helperProportions.height;
		var l = item.left, r = l + item.width,
		t = item.top, b = t + item.height;

		var dyClick = this.offset.click.top, dxClick = this.offset.click.left;
		var isOverElement = (y1 + dyClick) > t && (y1 + dyClick) < b && (x1 + dxClick) > l && (x1 + dxClick) < r;

		if(this.options.tolerance == "pointer" || this.options.forcePointerForContainers || (this.options.tolerance == "guess" && this.helperProportions[this.floating ? 'width' : 'height'] > item[this.floating ? 'width' : 'height'])) {
			return isOverElement;
		} else {

			return (l < x1 + (this.helperProportions.width / 2) // Right Half
				&& x2 - (this.helperProportions.width / 2) < r // Left Half
				&& t < y1 + (this.helperProportions.height / 2) // Bottom Half
				&& y2 - (this.helperProportions.height / 2) < b ); // Top Half

		}
	},

	intersectsWithEdge: function(item) {
		var dyClick = this.offset.click.top, dxClick = this.offset.click.left;
		var helperHeight = this.helperProportions.height, helperWidth = this.helperProportions.width;
		var helperTop = this.positionAbs.top, helperLeft = this.positionAbs.left;
		var itemHeight = item.height, itemWidth = item.width;
		var itemTop = item.top, itemLeft = item.left;

		var isOverElementHeight = ((helperTop + dyClick) > itemTop) &&
								((helperTop + dyClick) < (itemTop + itemHeight));

		var isOverElementWidth = ((helperLeft + dxClick) > itemLeft) &&
								((helperLeft + dxClick) < (itemLeft + itemWidth));

		var isOverElement = isOverElementHeight && isOverElementWidth;
		var verticalDirection = this._getDragVerticalDirection();
		var horizontalDirection = this._getDragHorizontalDirection();

		if (!isOverElement) return false;

		if (this.floating) {

			if (!horizontalDirection) {
				return verticalDirection == "down" ? 2 : 1;
			}

			return horizontalDirection == "right" ? 2 : 1;
		}
		else {

			if (!verticalDirection) {
				return false;
			}

			return verticalDirection == "down" ? 2 : 1;
		}

		return false;
	},

	_getDragVerticalDirection: function() {
		var helperTop = this.positionAbs.top;
		var lastTop = this.lastPositionAbs.top;
		var delta = helperTop - lastTop;

		if (delta == 0) {
			return false;
		}

		var direction = delta > 0 ? "down" : "up";
		return direction;
	},

	_getDragHorizontalDirection: function() {
		var helperLeft = this.positionAbs.left;
		var lastLeft = this.lastPositionAbs.left;
		var delta = helperLeft - lastLeft;

		if (delta == 0) {
			return false;
		}

		var direction = delta > 0 ? "right" : "left";
		return direction;
	},

	refresh: function() {
		this.refreshItems();
		this.refreshPositions();
	},

	getItemsAsjQuery: function(connected) {

		var self = this;
		var items = [];
		var queries = [];

		if(this.options.connectWith && connected) {
			for (var i = this.options.connectWith.length - 1; i >= 0; i--){
				var cur = $(this.options.connectWith[i]);
				for (var j = cur.length - 1; j >= 0; j--){
					var inst = $.data(cur[j], 'sortable');
					if(inst && inst != this && !inst.options.disabled) {
						queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element) : $(inst.options.items, inst.element).not(".ui-sortable-helper"), inst]);
					}
				};
			};
		}

		queries.push([$.isFunction(this.options.items) ? this.options.items.call(this.element, null, { options: this.options, item: this.currentItem }) : $(this.options.items, this.element).not(".ui-sortable-helper"), this]);

		for (var i = queries.length - 1; i >= 0; i--){
			queries[i][0].each(function() {
				items.push(this);
			});
		};

		return $(items);

	},

	removeCurrentsFromItems: function() {

		var list = this.currentItem.find(":data(sortable-item)");

		for (var i=0; i < this.items.length; i++) {

			for (var j=0; j < list.length; j++) {
				if(list[j] == this.items[i].item[0])
					this.items.splice(i,1);
			};

		};

	},

	refreshItems: function() {

		this.items = [];
		this.containers = [this];
		var items = this.items;
		var self = this;
		var queries = [[$.isFunction(this.options.items) ? this.options.items.call(this.element, null, { options: this.options, item: this.currentItem }) : $(this.options.items, this.element), this]];

		if(this.options.connectWith) {
			for (var i = this.options.connectWith.length - 1; i >= 0; i--){
				var cur = $(this.options.connectWith[i]);
				for (var j = cur.length - 1; j >= 0; j--){
					var inst = $.data(cur[j], 'sortable');
					if(inst && inst != this && !inst.options.disabled) {
						queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element) : $(inst.options.items, inst.element), inst]);
						this.containers.push(inst);
					}
				};
			};
		}

		for (var i = queries.length - 1; i >= 0; i--){
			var targetData = queries[i][1];
			var _queries = queries[i][0];

			for (var j=0, queriesLength = _queries.length; j < queriesLength; j++) {
				var item = $(_queries[j]);

				item.data('sortable-item', targetData); // Data for target checking (mouse manager)

				items.push({
					item: item,
					instance: targetData,
					width: 0, height: 0,
					left: 0, top: 0
				});
			};
		};

	},

	refreshPositions: function(fast) {

		//This has to be redone because due to the item being moved out/into the offsetParent, the offsetParent's position will change
		if(this.offsetParent) {
			var po = this.offsetParent.offset();
			this.offset.parent = { top: po.top + this.offsetParentBorders.top, left: po.left + this.offsetParentBorders.left };
		}

		for (var i = this.items.length - 1; i >= 0; i--){

			var item = this.items[i];

			//We ignore calculating positions of all connected containers when we're not over them
			if(item.instance != this.currentContainer && this.currentContainer && item.item[0] != this.currentItem[0])
				continue;

			var t = this.options.toleranceElement ? $(this.options.toleranceElement, item.item) : item.item;

			if (!fast) {
				item.width = t[0].offsetWidth;
				item.height = t[0].offsetHeight;
			}

			var p = t.offset();
			item.left = p.left;
			item.top = p.top;

		};

		if(this.options.custom && this.options.custom.refreshContainers) {
			this.options.custom.refreshContainers.call(this);
		} else {
			for (var i = this.containers.length - 1; i >= 0; i--){
				var p = this.containers[i].element.offset();
				this.containers[i].containerCache.left = p.left;
				this.containers[i].containerCache.top = p.top;
				this.containers[i].containerCache.width	= this.containers[i].element.outerWidth();
				this.containers[i].containerCache.height = this.containers[i].element.outerHeight();
			};
		}

	},

	destroy: function() {
		this.element
			.removeClass("ui-sortable ui-sortable-disabled")
			.removeData("sortable")
			.unbind(".sortable");
		this.mouseDestroy();

		for ( var i = this.items.length - 1; i >= 0; i-- )
			this.items[i].item.removeData("sortable-item");
	},

	createPlaceholder: function(that) {

		var self = that || this, o = self.options;

		if(!o.placeholder || o.placeholder.constructor == String) {
			var className = o.placeholder;
			o.placeholder = {
				element: function() {
					var el = $(document.createElement(self.currentItem[0].nodeName)).addClass(className || "ui-sortable-placeholder")[0];

					if(!className) {
						el.style.visibility = "hidden";
						document.body.appendChild(el);
						el.innerHTML = self.currentItem[0].innerHTML;
						document.body.removeChild(el);
					};

					return el;
				},
				update: function(container, p) {
					if(className) return;
					if(!p.height()) { p.height(self.currentItem.innerHeight()); };
					if(!p.width()) { p.width(self.currentItem.innerWidth()); };
				}
			};
		}

		self.placeholder = $(o.placeholder.element.call(self.element, self.currentItem))
		self.currentItem.parent()[0].appendChild(self.placeholder[0]);
		self.placeholder[0].parentNode.insertBefore(self.placeholder[0], self.currentItem[0]);
		o.placeholder.update(self, self.placeholder);

	},

	contactContainers: function(e) {
		for (var i = this.containers.length - 1; i >= 0; i--){

			if(this.intersectsWith(this.containers[i].containerCache)) {
				if(!this.containers[i].containerCache.over) {


					if(this.currentContainer != this.containers[i]) {

						//When entering a new container, we will find the item with the least distance and append our item near it
						var dist = 10000; var itemWithLeastDistance = null; var base = this.positionAbs[this.containers[i].floating ? 'left' : 'top'];
						for (var j = this.items.length - 1; j >= 0; j--) {
							if(!contains(this.containers[i].element[0], this.items[j].item[0])) continue;
							var cur = this.items[j][this.containers[i].floating ? 'left' : 'top'];
							if(Math.abs(cur - base) < dist) {
								dist = Math.abs(cur - base); itemWithLeastDistance = this.items[j];
							}
						}

						if(!itemWithLeastDistance && !this.options.dropOnEmpty) //Check if dropOnEmpty is enabled
							continue;

						this.currentContainer = this.containers[i];
						itemWithLeastDistance ? this.options.sortIndicator.call(this, e, itemWithLeastDistance, null, true) : this.options.sortIndicator.call(this, e, null, this.containers[i].element, true);
						this.propagate("change", e); //Call plugins and callbacks
						this.containers[i].propagate("change", e, this); //Call plugins and callbacks

						//Update the placeholder
						this.options.placeholder.update(this.currentContainer, this.placeholder);

					}

					this.containers[i].propagate("over", e, this);
					this.containers[i].containerCache.over = 1;
				}
			} else {
				if(this.containers[i].containerCache.over) {
					this.containers[i].propagate("out", e, this);
					this.containers[i].containerCache.over = 0;
				}
			}

		};
	},

	mouseCapture: function(e, overrideHandle) {

		if(this.options.disabled || this.options.type == 'static') return false;

		//We have to refresh the items data once first
		this.refreshItems();

		//Find out if the clicked node (or one of its parents) is a actual item in this.items
		var currentItem = null, self = this, nodes = $(e.target).parents().each(function() {
			if($.data(this, 'sortable-item') == self) {
				currentItem = $(this);
				return false;
			}
		});
		if($.data(e.target, 'sortable-item') == self) currentItem = $(e.target);

		if(!currentItem) return false;
		if(this.options.handle && !overrideHandle) {
			var validHandle = false;

			$(this.options.handle, currentItem).find("*").andSelf().each(function() { if(this == e.target) validHandle = true; });
			if(!validHandle) return false;
		}

		this.currentItem = currentItem;
		this.removeCurrentsFromItems();
		return true;

	},

	mouseStart: function(e, overrideHandle, noActivation) {

		var o = this.options;
		this.currentContainer = this;

		//We only need to call refreshPositions, because the refreshItems call has been moved to mouseCapture
		this.refreshPositions();

		//Create and append the visible helper
		this.helper = typeof o.helper == 'function' ? $(o.helper.apply(this.element[0], [e, this.currentItem])) : (o.helper == "original" ? this.currentItem :  this.currentItem.clone());
		if (!this.helper.parents('body').length) $(o.appendTo != 'parent' ? o.appendTo : this.currentItem[0].parentNode)[0].appendChild(this.helper[0]); //Add the helper to the DOM if that didn't happen already

		/*
		 * - Position generation -
		 * This block generates everything position related - it's the core of draggables.
		 */

		this.margins = {																				//Cache the margins
			left: (parseInt(this.currentItem.css("marginLeft"),10) || 0),
			top: (parseInt(this.currentItem.css("marginTop"),10) || 0)
		};

		this.offset = this.currentItem.offset();														//The element's absolute position on the page
		this.offset = {																					//Substract the margins from the element's absolute offset
			top: this.offset.top - this.margins.top,
			left: this.offset.left - this.margins.left
		};

		this.offset.click = {																			//Where the click happened, relative to the element
			left: e.pageX - this.offset.left,
			top: e.pageY - this.offset.top
		};

		this.offsetParent = this.helper.offsetParent();													//Get the offsetParent and cache its position
		var po = this.offsetParent.offset();

		this.offsetParentBorders = {
			top: (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0),
			left: (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0)
		};
		this.offset.parent = {																			//Store its position plus border
			top: po.top + this.offsetParentBorders.top,
			left: po.left + this.offsetParentBorders.left
		};

		this.updateOriginalPosition = this.originalPosition = this.generatePosition(e);				//Generate the original position
		this.domPosition = { prev: this.currentItem.prev()[0], parent: this.currentItem.parent()[0] };  //Cache the former DOM position

		//If o.placeholder is used, create a new element at the given position with the class
		this.helperProportions = { width: this.helper.outerWidth(), height: this.helper.outerHeight() };//Cache the helper size


		if(o.helper == "original") {
			this._storedCSS = { position: this.currentItem.css("position"), top: this.currentItem.css("top"), left: this.currentItem.css("left"), clear: this.currentItem.css("clear") };
		}

		if(o.helper != "original") this.currentItem.hide(); //Hide the original, won't cause anything bad this way
		this.helper.css({ position: 'absolute', clear: 'both' }).addClass('ui-sortable-helper'); //Position it absolutely and add a helper class
		this.createPlaceholder();

		//Call plugins and callbacks
		this.propagate("start", e);
		if(!this._preserveHelperProportions) this.helperProportions = { width: this.helper.outerWidth(), height: this.helper.outerHeight() };//Recache the helper size

		if(o.cursorAt) {
			if(o.cursorAt.left != undefined) this.offset.click.left = o.cursorAt.left;
			if(o.cursorAt.right != undefined) this.offset.click.left = this.helperProportions.width - o.cursorAt.right;
			if(o.cursorAt.top != undefined) this.offset.click.top = o.cursorAt.top;
			if(o.cursorAt.bottom != undefined) this.offset.click.top = this.helperProportions.height - o.cursorAt.bottom;
		}

		/*
		 * - Position constraining -
		 * Here we prepare position constraining like grid and containment.
		 */

		if(o.containment) {
			if(o.containment == 'parent') o.containment = this.helper[0].parentNode;
			if(o.containment == 'document' || o.containment == 'window') this.containment = [
				0 - this.offset.parent.left,
				0 - this.offset.parent.top,
				$(o.containment == 'document' ? document : window).width() - this.offset.parent.left - this.helperProportions.width - this.margins.left - (parseInt(this.element.css("marginRight"),10) || 0),
				($(o.containment == 'document' ? document : window).height() || document.body.parentNode.scrollHeight) - this.offset.parent.top - this.helperProportions.height - this.margins.top - (parseInt(this.element.css("marginBottom"),10) || 0)
			];

			if(!(/^(document|window|parent)$/).test(o.containment)) {
				var ce = $(o.containment)[0];
				var co = $(o.containment).offset();

				this.containment = [
					co.left + (parseInt($(ce).css("borderLeftWidth"),10) || 0) - this.offset.parent.left,
					co.top + (parseInt($(ce).css("borderTopWidth"),10) || 0) - this.offset.parent.top,
					co.left+Math.max(ce.scrollWidth,ce.offsetWidth) - (parseInt($(ce).css("borderLeftWidth"),10) || 0) - this.offset.parent.left - this.helperProportions.width - this.margins.left - (parseInt(this.currentItem.css("marginRight"),10) || 0),
					co.top+Math.max(ce.scrollHeight,ce.offsetHeight) - (parseInt($(ce).css("borderTopWidth"),10) || 0) - this.offset.parent.top - this.helperProportions.height - this.margins.top - (parseInt(this.currentItem.css("marginBottom"),10) || 0)
				];
			}
		}

		//Post 'activate' events to possible containers
		if(!noActivation) {
			 for (var i = this.containers.length - 1; i >= 0; i--) { this.containers[i].propagate("activate", e, this); }
		}

		//Prepare possible droppables
		if($.ui.ddmanager) $.ui.ddmanager.current = this;
		if ($.ui.ddmanager && !o.dropBehaviour) $.ui.ddmanager.prepareOffsets(this, e);

		this.dragging = true;

		this.mouseDrag(e); //Execute the drag once - this causes the helper not to be visible before getting its correct position
		return true;


	},

	convertPositionTo: function(d, pos) {
		if(!pos) pos = this.position;
		var mod = d == "absolute" ? 1 : -1;
		return {
			top: (
				pos.top																	// the calculated relative position
				+ this.offset.parent.top * mod											// The offsetParent's offset without borders (offset + border)
				- (this.offsetParent[0] == document.body ? 0 : this.offsetParent[0].scrollTop) * mod	// The offsetParent's scroll position
				+ this.margins.top * mod												//Add the margin (you don't want the margin counting in intersection methods)
			),
			left: (
				pos.left																// the calculated relative position
				+ this.offset.parent.left * mod											// The offsetParent's offset without borders (offset + border)
				- (this.offsetParent[0] == document.body ? 0 : this.offsetParent[0].scrollLeft) * mod	// The offsetParent's scroll position
				+ this.margins.left * mod												//Add the margin (you don't want the margin counting in intersection methods)
			)
		};
	},

	generatePosition: function(e) {

		var o = this.options;
		var position = {
			top: (
				e.pageY																	// The absolute mouse position
				- this.offset.click.top													// Click offset (relative to the element)
				- this.offset.parent.top												// The offsetParent's offset without borders (offset + border)
				+ (this.offsetParent[0] == document.body ? 0 : this.offsetParent[0].scrollTop)	// The offsetParent's scroll position, not if the element is fixed
			),
			left: (
				e.pageX																	// The absolute mouse position
				- this.offset.click.left												// Click offset (relative to the element)
				- this.offset.parent.left												// The offsetParent's offset without borders (offset + border)
				+ (this.offsetParent[0] == document.body ? 0 : this.offsetParent[0].scrollLeft)	// The offsetParent's scroll position, not if the element is fixed
			)
		};

		if(!this.originalPosition) return position;										//If we are not dragging yet, we won't check for options

		/*
		 * - Position constraining -
		 * Constrain the position to a mix of grid, containment.
		 */
		if(this.containment) {
			if(position.left < this.containment[0]) position.left = this.containment[0];
			if(position.top < this.containment[1]) position.top = this.containment[1];
			if(position.left > this.containment[2]) position.left = this.containment[2];
			if(position.top > this.containment[3]) position.top = this.containment[3];
		}

		if(o.grid) {
			var top = this.originalPosition.top + Math.round((position.top - this.originalPosition.top) / o.grid[1]) * o.grid[1];
			position.top = this.containment ? (!(top < this.containment[1] || top > this.containment[3]) ? top : (!(top < this.containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top;

			var left = this.originalPosition.left + Math.round((position.left - this.originalPosition.left) / o.grid[0]) * o.grid[0];
			position.left = this.containment ? (!(left < this.containment[0] || left > this.containment[2]) ? left : (!(left < this.containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left;
		}

		return position;
	},

	mouseDrag: function(e) {

		//Compute the helpers position
		this.position = this.generatePosition(e);
		this.positionAbs = this.convertPositionTo("absolute");

		if (!this.lastPositionAbs) {
			this.lastPositionAbs = this.positionAbs;
		}

		//Call the internal plugins
		$.ui.plugin.call(this, "sort", [e, this.ui()]);

		//Regenerate the absolute position used for position checks
		this.positionAbs = this.convertPositionTo("absolute");

		//Set the helper's position
		this.helper[0].style.left = this.position.left+'px';
		this.helper[0].style.top = this.position.top+'px';

		//Rearrange
		for (var i = this.items.length - 1; i >= 0; i--) {
			var intersection = this.intersectsWithEdge(this.items[i]);
			if(!intersection) continue;

			if(this.items[i].item[0] != this.currentItem[0] //cannot intersect with itself
				&&	this.placeholder[intersection == 1 ? "next" : "prev"]()[0] != this.items[i].item[0] //no useless actions that have been done before
				&&	!contains(this.placeholder[0], this.items[i].item[0]) //no action if the item moved is the parent of the item checked
				&& (this.options.type == 'semi-dynamic' ? !contains(this.element[0], this.items[i].item[0]) : true)
			) {

				this.updateOriginalPosition = this.generatePosition(e);

				this.direction = intersection == 1 ? "down" : "up";
				this.options.sortIndicator.call(this, e, this.items[i]);
				this.propagate("change", e); //Call plugins and callbacks
				break;
			}
		}

		//Post events to containers
		this.contactContainers(e);

		//Interconnect with droppables
		if($.ui.ddmanager) $.ui.ddmanager.drag(this, e);

		//Call callbacks
		this.element.triggerHandler("sort", [e, this.ui()], this.options["sort"]);

		this.lastPositionAbs = this.positionAbs;

		return false;

	},

	rearrange: function(e, i, a, hardRefresh) {

		a ? a[0].appendChild(this.placeholder[0]) : i.item[0].parentNode.insertBefore(this.placeholder[0], (this.direction == 'down' ? i.item[0] : i.item[0].nextSibling));

		//Various things done here to improve the performance:
		// 1. we create a setTimeout, that calls refreshPositions
		// 2. on the instance, we have a counter variable, that get's higher after every append
		// 3. on the local scope, we copy the counter variable, and check in the timeout, if it's still the same
		// 4. this lets only the last addition to the timeout stack through
		this.counter = this.counter ? ++this.counter : 1;
		var self = this, counter = this.counter;

		window.setTimeout(function() {
			if(counter == self.counter) self.refreshPositions(!hardRefresh); //Precompute after each DOM insertion, NOT on mousemove
		},0);

	},

	mouseStop: function(e, noPropagation) {

		//If we are using droppables, inform the manager about the drop
		if ($.ui.ddmanager && !this.options.dropBehaviour)
			$.ui.ddmanager.drop(this, e);

		if(this.options.revert) {
			var self = this;
			var cur = self.placeholder.offset();

			$(this.helper).animate({
				left: cur.left - this.offset.parent.left - self.margins.left + (this.offsetParent[0] == document.body ? 0 : this.offsetParent[0].scrollLeft),
				top: cur.top - this.offset.parent.top - self.margins.top + (this.offsetParent[0] == document.body ? 0 : this.offsetParent[0].scrollTop)
			}, parseInt(this.options.revert, 10) || 500, function() {
				self.clear(e);
			});
		} else {
			this.clear(e, noPropagation);
		}

		return false;

	},

	clear: function(e, noPropagation) {

		//We first have to update the dom position of the actual currentItem
		if(!this._noFinalSort) {
			var parentNode = this.placeholder[0].parentNode;
			if (parentNode) {
				parentNode.insertBefore( this.currentItem[0], this.placeholder[0] );
			}
		}
		this._noFinalSort = null;

		if(this.options.helper == "original")
			this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper");
		else
			this.currentItem.show();

		if(this.domPosition.prev != this.currentItem.prev().not(".ui-sortable-helper")[0] || this.domPosition.parent != this.currentItem.parent()[0]) this.propagate("update", e, null, noPropagation); //Trigger update callback if the DOM position has changed
		if(!contains(this.element[0], this.currentItem[0])) { //Node was moved out of the current element
			this.propagate("remove", e, null, noPropagation);
			for (var i = this.containers.length - 1; i >= 0; i--){
				if(contains(this.containers[i].element[0], this.currentItem[0])) {
					this.containers[i].propagate("update", e, this, noPropagation);
					this.containers[i].propagate("receive", e, this, noPropagation);
				}
			};
		};

		//Post events to containers
		for (var i = this.containers.length - 1; i >= 0; i--){
			this.containers[i].propagate("deactivate", e, this, noPropagation);
			if(this.containers[i].containerCache.over) {
				this.containers[i].propagate("out", e, this);
				this.containers[i].containerCache.over = 0;
			}
		}

		this.dragging = false;
		if(this.cancelHelperRemoval) {
			this.propagate("stop", e, null, noPropagation);
			return false;
		}

		this.propagate("beforeStop", e, null, noPropagation);

		this.placeholder.remove();
		if(this.options.helper != "original") this.helper.remove(); this.helper = null;
		this.propagate("stop", e, null, noPropagation);

		return true;

	}
}));

$.extend($.ui.sortable, {
	getter: "serialize toArray",
	defaults: {
		helper: "original",
		tolerance: "guess",
		distance: 1,
		delay: 0,
		scroll: true,
		scrollSensitivity: 20,
		scrollSpeed: 20,
		cancel: ":input",
		items: '> *',
		zIndex: 1000,
		dropOnEmpty: true,
		appendTo: "parent",
		sortIndicator: $.ui.sortable.prototype.rearrange,
		scope: "default"
	}
});

/*
 * Sortable Extensions
 */

$.ui.plugin.add("sortable", "cursor", {
	start: function(e, ui) {
		var t = $('body');
		if (t.css("cursor")) ui.options._cursor = t.css("cursor");
		t.css("cursor", ui.options.cursor);
	},
	beforeStop: function(e, ui) {
		if (ui.options._cursor) $('body').css("cursor", ui.options._cursor);
	}
});

$.ui.plugin.add("sortable", "zIndex", {
	start: function(e, ui) {
		var t = ui.helper;
		if(t.css("zIndex")) ui.options._zIndex = t.css("zIndex");
		t.css('zIndex', ui.options.zIndex);
	},
	beforeStop: function(e, ui) {
		if(ui.options._zIndex) $(ui.helper).css('zIndex', ui.options._zIndex);
	}
});

$.ui.plugin.add("sortable", "opacity", {
	start: function(e, ui) {
		var t = ui.helper;
		if(t.css("opacity")) ui.options._opacity = t.css("opacity");
		t.css('opacity', ui.options.opacity);
	},
	beforeStop: function(e, ui) {
		if(ui.options._opacity) $(ui.helper).css('opacity', ui.options._opacity);
	}
});

$.ui.plugin.add("sortable", "scroll", {
	start: function(e, ui) {
		var o = ui.options;
		var i = $(this).data("sortable");

		i.overflowY = function(el) {
			do { if(/auto|scroll/.test(el.css('overflow')) || (/auto|scroll/).test(el.css('overflow-y'))) return el; el = el.parent(); } while (el[0].parentNode);
			return $(document);
		}(i.currentItem);
		i.overflowX = function(el) {
			do { if(/auto|scroll/.test(el.css('overflow')) || (/auto|scroll/).test(el.css('overflow-x'))) return el; el = el.parent(); } while (el[0].parentNode);
			return $(document);
		}(i.currentItem);

		if(i.overflowY[0] != document && i.overflowY[0].tagName != 'HTML') i.overflowYOffset = i.overflowY.offset();
		if(i.overflowX[0] != document && i.overflowX[0].tagName != 'HTML') i.overflowXOffset = i.overflowX.offset();

	},
	sort: function(e, ui) {

		var o = ui.options;
		var i = $(this).data("sortable");

		if(i.overflowY[0] != document && i.overflowY[0].tagName != 'HTML') {
			if((i.overflowYOffset.top + i.overflowY[0].offsetHeight) - e.pageY < o.scrollSensitivity)
				i.overflowY[0].scrollTop = i.overflowY[0].scrollTop + o.scrollSpeed;
			if(e.pageY - i.overflowYOffset.top < o.scrollSensitivity)
				i.overflowY[0].scrollTop = i.overflowY[0].scrollTop - o.scrollSpeed;
		} else {
			if(e.pageY - $(document).scrollTop() < o.scrollSensitivity)
				$(document).scrollTop($(document).scrollTop() - o.scrollSpeed);
			if($(window).height() - (e.pageY - $(document).scrollTop()) < o.scrollSensitivity)
				$(document).scrollTop($(document).scrollTop() + o.scrollSpeed);
		}

		if(i.overflowX[0] != document && i.overflowX[0].tagName != 'HTML') {
			if((i.overflowXOffset.left + i.overflowX[0].offsetWidth) - e.pageX < o.scrollSensitivity)
				i.overflowX[0].scrollLeft = i.overflowX[0].scrollLeft + o.scrollSpeed;
			if(e.pageX - i.overflowXOffset.left < o.scrollSensitivity)
				i.overflowX[0].scrollLeft = i.overflowX[0].scrollLeft - o.scrollSpeed;
		} else {
			if(e.pageX - $(document).scrollLeft() < o.scrollSensitivity)
				$(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed);
			if($(window).width() - (e.pageX - $(document).scrollLeft()) < o.scrollSensitivity)
				$(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed);
		}

	}
});

$.ui.plugin.add("sortable", "axis", {
	sort: function(e, ui) {

		var i = $(this).data("sortable");

		if(ui.options.axis == "y") i.position.left = i.originalPosition.left;
		if(ui.options.axis == "x") i.position.top = i.originalPosition.top;

	}
});

})(jQuery);
/*
 * jQuery UI Tabs
 *
 * Copyright (c) 2007, 2008 Klaus Hartl (stilbuero.de)
 * Dual licensed under the MIT (MIT-LICENSE.txt)
 * and GPL (GPL-LICENSE.txt) licenses.
 *
 * http://docs.jquery.com/UI/Tabs
 *
 * Depends:
 *	ui.core.js
 */
(function($) {

$.widget("ui.tabs", {
	init: function() {
		this.options.event += '.tabs'; // namespace event
		
		// create tabs
		this.tabify(true);
	},
	setData: function(key, value) {
		if ((/^selected/).test(key))
			this.select(value);
		else {
			this.options[key] = value;
			this.tabify();
		}
	},
	length: function() {
		return this.$tabs.length;
	},
	tabId: function(a) {
		return a.title && a.title.replace(/\s/g, '_').replace(/[^A-Za-z0-9\-_:\.]/g, '')
			|| this.options.idPrefix + $.data(a);
	},
	ui: function(tab, panel) {
		return {
			options: this.options,
			tab: tab,
			panel: panel,
			index: this.$tabs.index(tab)
		};
	},
	tabify: function(init) {

		this.$lis = $('li:has(a[href])', this.element);
		this.$tabs = this.$lis.map(function() { return $('a', this)[0]; });
		this.$panels = $([]);

		var self = this, o = this.options;

		this.$tabs.each(function(i, a) {
			// inline tab
			if (a.hash && a.hash.replace('#', '')) // Safari 2 reports '#' for an empty hash
				self.$panels = self.$panels.add(a.hash);
			// remote tab
			else if ($(a).attr('href') != '#') { // prevent loading the page itself if href is just "#"
				$.data(a, 'href.tabs', a.href); // required for restore on destroy
				$.data(a, 'load.tabs', a.href); // mutable
				var id = self.tabId(a);
				a.href = '#' + id;
				var $panel = $('#' + id);
				if (!$panel.length) {
					$panel = $(o.panelTemplate).attr('id', id).addClass(o.panelClass)
						.insertAfter( self.$panels[i - 1] || self.element );
					$panel.data('destroy.tabs', true);
				}
				self.$panels = self.$panels.add( $panel );
			}
			// invalid tab href
			else
				o.disabled.push(i + 1);
		});

		if (init) {

			// attach necessary classes for styling if not present
			this.element.addClass(o.navClass);
			this.$panels.each(function() {
				var $this = $(this);
				$this.addClass(o.panelClass);
			});

			// Selected tab
			// use "selected" option or try to retrieve:
			// 1. from fragment identifier in url
			// 2. from cookie
			// 3. from selected class attribute on <li>
			if (o.selected === undefined) {
				if (location.hash) {
					this.$tabs.each(function(i, a) {
						if (a.hash == location.hash) {
							o.selected = i;
							// prevent page scroll to fragment
							if ($.browser.msie || $.browser.opera) { // && !o.remote
								var $toShow = $(location.hash), toShowId = $toShow.attr('id');
								$toShow.attr('id', '');
								setTimeout(function() {
									$toShow.attr('id', toShowId); // restore id
								}, 500);
							}
							scrollTo(0, 0);
							return false; // break
						}
					});
				}
				else if (o.cookie) {
					var index = parseInt($.cookie('ui-tabs' + $.data(self.element)),10);
					if (index && self.$tabs[index])
						o.selected = index;
				}
				else if (self.$lis.filter('.' + o.selectedClass).length)
					o.selected = self.$lis.index( self.$lis.filter('.' + o.selectedClass)[0] );
			}
			o.selected = o.selected === null || o.selected !== undefined ? o.selected : 0; // first tab selected by default

			// Take disabling tabs via class attribute from HTML
			// into account and update option properly.
			// A selected tab cannot become disabled.
			o.disabled = $.unique(o.disabled.concat(
				$.map(this.$lis.filter('.' + o.disabledClass),
					function(n, i) { return self.$lis.index(n); } )
			)).sort();
			if ($.inArray(o.selected, o.disabled) != -1)
				o.disabled.splice($.inArray(o.selected, o.disabled), 1);
			
			// highlight selected tab
			this.$panels.addClass(o.hideClass);
			this.$lis.removeClass(o.selectedClass);
			if (o.selected !== null) {
				this.$panels.eq(o.selected).show().removeClass(o.hideClass); // use show and remove class to show in any case no matter how it has been hidden before
				this.$lis.eq(o.selected).addClass(o.selectedClass);
				
				// seems to be expected behavior that the show callback is fired
				var onShow = function() {
					self.trigger('show', null,
						self.ui(self.$tabs[o.selected], self.$panels[o.selected]));
				};

				// load if remote tab
				if ($.data(this.$tabs[o.selected], 'load.tabs'))
					this.load(o.selected, onShow);
				// just trigger show event
				else
					onShow();
			}
			
			// clean up to avoid memory leaks in certain versions of IE 6
			$(window).bind('unload', function() {
				self.$tabs.unbind('.tabs');
				self.$lis = self.$tabs = self.$panels = null;
			});

		}

		// disable tabs
		for (var i = 0, li; li = this.$lis[i]; i++)
			$(li)[$.inArray(i, o.disabled) != -1 && !$(li).hasClass(o.selectedClass) ? 'addClass' : 'removeClass'](o.disabledClass);

		// reset cache if switching from cached to not cached
		if (o.cache === false)
			this.$tabs.removeData('cache.tabs');
		
		// set up animations
		var hideFx, showFx, baseFx = { 'min-width': 0, duration: 1 }, baseDuration = 'normal';
		if (o.fx && o.fx.constructor == Array)
			hideFx = o.fx[0] || baseFx, showFx = o.fx[1] || baseFx;
		else
			hideFx = showFx = o.fx || baseFx;

		// reset some styles to maintain print style sheets etc.
		var resetCSS = { display: '', overflow: '', height: '' };
		if (!$.browser.msie) // not in IE to prevent ClearType font issue
			resetCSS.opacity = '';

		// Hide a tab, animation prevents browser scrolling to fragment,
		// $show is optional.
		function hideTab(clicked, $hide, $show) {
			$hide.animate(hideFx, hideFx.duration || baseDuration, function() { //
				$hide.addClass(o.hideClass).css(resetCSS); // maintain flexible height and accessibility in print etc.
				if ($.browser.msie && hideFx.opacity)
					$hide[0].style.filter = '';
				if ($show)
					showTab(clicked, $show, $hide);
			});
		}

		// Show a tab, animation prevents browser scrolling to fragment,
		// $hide is optional.
		function showTab(clicked, $show, $hide) {
			if (showFx === baseFx)
				$show.css('display', 'block'); // prevent occasionally occuring flicker in Firefox cause by gap between showing and hiding the tab panels
			$show.animate(showFx, showFx.duration || baseDuration, function() {
				$show.removeClass(o.hideClass).css(resetCSS); // maintain flexible height and accessibility in print etc.
				if ($.browser.msie && showFx.opacity)
					$show[0].style.filter = '';

				// callback
				self.trigger('show', null, self.ui(clicked, $show[0]));
			});
		}

		// switch a tab
		function switchTab(clicked, $li, $hide, $show) {
			/*if (o.bookmarkable && trueClick) { // add to history only if true click occured, not a triggered click
				$.ajaxHistory.update(clicked.hash);
			}*/
			$li.addClass(o.selectedClass)
				.siblings().removeClass(o.selectedClass);
			hideTab(clicked, $hide, $show);
		}

		// attach tab event handler, unbind to avoid duplicates from former tabifying...
		this.$tabs.unbind('.tabs').bind(o.event, function() {

			//var trueClick = e.clientX; // add to history only if true click occured, not a triggered click
			var $li = $(this).parents('li:eq(0)'),
				$hide = self.$panels.filter(':visible'),
				$show = $(this.hash);

			// If tab is already selected and not unselectable or tab disabled or 
			// or is already loading or click callback returns false stop here.
			// Check if click handler returns false last so that it is not executed
			// for a disabled or loading tab!
			if (($li.hasClass(o.selectedClass) && !o.unselect)
				|| $li.hasClass(o.disabledClass) 
				|| $(this).hasClass(o.loadingClass)
				|| self.trigger('select', null, self.ui(this, $show[0])) === false
				) {
				this.blur();
				return false;
			}

			self.options.selected = self.$tabs.index(this);

			// if tab may be closed
			if (o.unselect) {
				if ($li.hasClass(o.selectedClass)) {
					self.options.selected = null;
					$li.removeClass(o.selectedClass);
					self.$panels.stop();
					hideTab(this, $hide);
					this.blur();
					return false;
				} else if (!$hide.length) {
					self.$panels.stop();
					var a = this;
					self.load(self.$tabs.index(this), function() {
						$li.addClass(o.selectedClass).addClass(o.unselectClass);
						showTab(a, $show);
					});
					this.blur();
					return false;
				}
			}

			if (o.cookie)
				$.cookie('ui-tabs' + $.data(self.element), self.options.selected, o.cookie);

			// stop possibly running animations
			self.$panels.stop();

			// show new tab
			if ($show.length) {

				// prevent scrollbar scrolling to 0 and than back in IE7, happens only if bookmarking/history is enabled
				/*if ($.browser.msie && o.bookmarkable) {
					var showId = this.hash.replace('#', '');
					$show.attr('id', '');
					setTimeout(function() {
						$show.attr('id', showId); // restore id
					}, 0);
				}*/

				var a = this;
				self.load(self.$tabs.index(this), $hide.length ? 
					function() {
						switchTab(a, $li, $hide, $show);
					} :
					function() {
						$li.addClass(o.selectedClass);
						showTab(a, $show);
					}
				);

				// Set scrollbar to saved position - need to use timeout with 0 to prevent browser scroll to target of hash
				/*var scrollX = window.pageXOffset || document.documentElement && document.documentElement.scrollLeft || document.body.scrollLeft || 0;
				var scrollY = window.pageYOffset || document.documentElement && document.documentElement.scrollTop || document.body.scrollTop || 0;
				setTimeout(function() {
					scrollTo(scrollX, scrollY);
				}, 0);*/

			} else
				throw 'jQuery UI Tabs: Mismatching fragment identifier.';

			// Prevent IE from keeping other link focussed when using the back button
			// and remove dotted border from clicked link. This is controlled in modern
			// browsers via CSS, also blur removes focus from address bar in Firefox
			// which can become a usability and annoying problem with tabsRotate.
			if ($.browser.msie)
				this.blur();

			//return o.bookmarkable && !!trueClick; // convert trueClick == undefined to Boolean required in IE
			return false;

		});

		// disable click if event is configured to something else
		if (!(/^click/).test(o.event))
			this.$tabs.bind('click.tabs', function() { return false; });

	},
	add: function(url, label, index) {
		if (index == undefined) 
			index = this.$tabs.length; // append by default

		var o = this.options;
		var $li = $(o.tabTemplate.replace(/#\{href\}/g, url).replace(/#\{label\}/g, label));
		$li.data('destroy.tabs', true);

		var id = url.indexOf('#') == 0 ? url.replace('#', '') : this.tabId( $('a:first-child', $li)[0] );

		// try to find an existing element before creating a new one
		var $panel = $('#' + id);
		if (!$panel.length) {
			$panel = $(o.panelTemplate).attr('id', id)
				.addClass(o.hideClass)
				.data('destroy.tabs', true);
		}
		$panel.addClass(o.panelClass);
		if (index >= this.$lis.length) {
			$li.appendTo(this.element);
			$panel.appendTo(this.element[0].parentNode);
		} else {
			$li.insertBefore(this.$lis[index]);
			$panel.insertBefore(this.$panels[index]);
		}
		
		o.disabled = $.map(o.disabled,
			function(n, i) { return n >= index ? ++n : n });
			
		this.tabify();

		if (this.$tabs.length == 1) {
			$li.addClass(o.selectedClass);
			$panel.removeClass(o.hideClass);
			var href = $.data(this.$tabs[0], 'load.tabs');
			if (href)
				this.load(index, href);
		}

		// callback
		this.trigger('add', null, this.ui(this.$tabs[index], this.$panels[index]));
	},
	remove: function(index) {
		var o = this.options, $li = this.$lis.eq(index).remove(),
			$panel = this.$panels.eq(index).remove();

		// If selected tab was removed focus tab to the right or
		// in case the last tab was removed the tab to the left.
		if ($li.hasClass(o.selectedClass) && this.$tabs.length > 1)
			this.select(index + (index + 1 < this.$tabs.length ? 1 : -1));

		o.disabled = $.map($.grep(o.disabled, function(n, i) { return n != index; }),
			function(n, i) { return n >= index ? --n : n });

		this.tabify();

		// callback
		this.trigger('remove', null, this.ui($li.find('a')[0], $panel[0]));
	},
	enable: function(index) {
		var o = this.options;
		if ($.inArray(index, o.disabled) == -1)
			return;
			
		var $li = this.$lis.eq(index).removeClass(o.disabledClass);
		if ($.browser.safari) { // fix disappearing tab (that used opacity indicating disabling) after enabling in Safari 2...
			$li.css('display', 'inline-block');
			setTimeout(function() {
				$li.css('display', 'block');
			}, 0);
		}

		o.disabled = $.grep(o.disabled, function(n, i) { return n != index; });

		// callback
		this.trigger('enable', null, this.ui(this.$tabs[index], this.$panels[index]));
	},
	disable: function(index) {
		var self = this, o = this.options;
		if (index != o.selected) { // cannot disable already selected tab
			this.$lis.eq(index).addClass(o.disabledClass);

			o.disabled.push(index);
			o.disabled.sort();

			// callback
			this.trigger('disable', null, this.ui(this.$tabs[index], this.$panels[index]));
		}
	},
	select: function(index) {
		if (typeof index == 'string')
			index = this.$tabs.index( this.$tabs.filter('[href$=' + index + ']')[0] );
		this.$tabs.eq(index).trigger(this.options.event);
	},
	load: function(index, callback) { // callback is for internal usage only
		
		var self = this, o = this.options, $a = this.$tabs.eq(index), a = $a[0],
				bypassCache = callback == undefined || callback === false, url = $a.data('load.tabs');

		callback = callback || function() {};
		
		// no remote or from cache - just finish with callback
		if (!url || !bypassCache && $.data(a, 'cache.tabs')) {
			callback();
			return;
		}

		// load remote from here on
		
		var inner = function(parent) {
			var $parent = $(parent), $inner = $parent.find('*:last');
			return $inner.length && $inner.is(':not(img)') && $inner || $parent;
		};
		var cleanup = function() {
			self.$tabs.filter('.' + o.loadingClass).removeClass(o.loadingClass)
						.each(function() {
							if (o.spinner)
								inner(this).parent().html(inner(this).data('label.tabs'));
						});
			self.xhr = null;
		};
		
		if (o.spinner) {
			var label = inner(a).html();
			inner(a).wrapInner('<em></em>')
				.find('em').data('label.tabs', label).html(o.spinner);
		}

		var ajaxOptions = $.extend({}, o.ajaxOptions, {
			url: url,
			success: function(r, s) {
				$(a.hash).html(r);
				cleanup();
				
				if (o.cache)
					$.data(a, 'cache.tabs', true); // if loaded once do not load them again

				// callbacks
				self.trigger('load', null, self.ui(self.$tabs[index], self.$panels[index]));
				o.ajaxOptions.success && o.ajaxOptions.success(r, s);
				
				// This callback is required because the switch has to take
				// place after loading has completed. Call last in order to 
				// fire load before show callback...
				callback();
			}
		});
		if (this.xhr) {
			// terminate pending requests from other tabs and restore tab label
			this.xhr.abort();
			cleanup();
		}
		$a.addClass(o.loadingClass);
		setTimeout(function() { // timeout is again required in IE, "wait" for id being restored
			self.xhr = $.ajax(ajaxOptions);
		}, 0);

	},
	url: function(index, url) {
		this.$tabs.eq(index).removeData('cache.tabs').data('load.tabs', url);
	},
	destroy: function() {
		var o = this.options;
		this.element.unbind('.tabs')
			.removeClass(o.navClass).removeData('tabs');
		this.$tabs.each(function() {
			var href = $.data(this, 'href.tabs');
			if (href)
				this.href = href;
			var $this = $(this).unbind('.tabs');
			$.each(['href', 'load', 'cache'], function(i, prefix) {
				$this.removeData(prefix + '.tabs');
			});
		});
		this.$lis.add(this.$panels).each(function() {
			if ($.data(this, 'destroy.tabs'))
				$(this).remove();
			else
				$(this).removeClass([o.selectedClass, o.unselectClass,
					o.disabledClass, o.panelClass, o.hideClass].join(' '));
		});
	}
});

$.ui.tabs.defaults = {
	// basic setup
	unselect: false,
	event: 'click',
	disabled: [],
	cookie: null, // e.g. { expires: 7, path: '/', domain: 'jquery.com', secure: true }
	// TODO history: false,

	// Ajax
	spinner: 'Loading&#8230;',
	cache: false,
	idPrefix: 'ui-tabs-',
	ajaxOptions: {},

	// animations
	fx: null, // e.g. { height: 'toggle', opacity: 'toggle', duration: 200 }

	// templates
	tabTemplate: '<li><a href="#{href}"><span>#{label}</span></a></li>',
	panelTemplate: '<div></div>',

	// CSS classes
	navClass: 'ui-tabs-nav',
	selectedClass: 'ui-tabs-selected',
	unselectClass: 'ui-tabs-unselect',
	disabledClass: 'ui-tabs-disabled',
	panelClass: 'ui-tabs-panel',
	hideClass: 'ui-tabs-hide',
	loadingClass: 'ui-tabs-loading'
};

$.ui.tabs.getter = "length";

/*
 * Tabs Extensions
 */

/*
 * Rotate
 */
$.extend($.ui.tabs.prototype, {
	rotation: null,
	rotate: function(ms, continuing) {
		
		continuing = continuing || false;
		
		var self = this, t = this.options.selected;
		
		function start() {
			self.rotation = setInterval(function() {
				t = ++t < self.$tabs.length ? t : 0;
				self.select(t);
			}, ms); 
		}
		
		function stop(e) {
			if (!e || e.clientX) { // only in case of a true click
				clearInterval(self.rotation);
			}
		}
		
		// start interval
		if (ms) {
			start();
			if (!continuing)
				this.$tabs.bind(this.options.event, stop);
			else
				this.$tabs.bind(this.options.event, function() {
					stop();
					t = self.options.selected;
					start();
				});
		}
		// stop interval
		else {
			stop();
			this.$tabs.unbind(this.options.event, stop);
		}
	}
});

})(jQuery);

/*
 * jQuery UI Autocomplete
 *
 * Copyright (c) 2007, 2008 Dylan Verheul, Dan G. Switzer, Anjesh Tuladhar, Jörn Zaefferer
 * Dual licensed under the MIT (MIT-LICENSE.txt)
 * and GPL (GPL-LICENSE.txt) licenses.
 * 
 * http://docs.jquery.com/UI/Autocomplete
 *
 * Depends:
 *	ui.core.js
 */
(function($) {

$.widget("ui.autocomplete", {
	
	init: function() {

		$.extend(this.options, {
			delay: this.options.url ? $.Autocompleter.defaults.delay : 10,
			max: !this.options.scroll ? 10 : 150,
			highlight: this.options.highlight || function(value) { return value; }, // if highlight is set to false, replace it with a do-nothing function
			formatMatch: this.options.formatMatch || this.options.formatItem // if the formatMatch option is not specified, then use formatItem for backwards compatibility
		});
		
		new $.Autocompleter(this.element[0], this.options);
		
	},
	
	result: function(handler) {
		return this.element.bind("result", handler);
	},
	search: function(handler) {
		return this.element.trigger("search", [handler]);
	},
	flushCache: function() {
		return this.element.trigger("flushCache");
	},
	setData: function(key, value){
		return this.element.trigger("setOptions", [{ key: value }]);
	},
	destroy: function() {
		return this.element.trigger("unautocomplete");
	}
	
});

$.Autocompleter = function(input, options) {

	var KEY = {
		UP: 38,
		DOWN: 40,
		DEL: 46,
		TAB: 9,
		RETURN: 13,
		ESC: 27,
		COMMA: 188,
		PAGEUP: 33,
		PAGEDOWN: 34,
		BACKSPACE: 8
	};

	// Create $ object for input element
	var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass);
	if(options.result) $input.bind('result.autocomplete', options.result);

	var timeout;
	var previousValue = "";
	var cache = $.Autocompleter.Cache(options);
	var hasFocus = 0;
	var lastKeyPressCode;
	var config = {
		mouseDownOnSelect: false
	};
	var select = $.Autocompleter.Select(options, input, selectCurrent, config);
	
	var blockSubmit;
	
	// prevent form submit in opera when selecting with return key
	$.browser.opera && $(input.form).bind("submit.autocomplete", function() {
		if (blockSubmit) {
			blockSubmit = false;
			return false;
		}
	});
	
	// only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all
	$input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) {
		// track last key pressed
		lastKeyPressCode = event.keyCode;
		switch(event.keyCode) {
		
			case KEY.UP:
				event.preventDefault();
				if ( select.visible() ) {
					select.prev();
				} else {
					onChange(0, true);
				}
				break;
				
			case KEY.DOWN:
				event.preventDefault();
				if ( select.visible() ) {
					select.next();
				} else {
					onChange(0, true);
				}
				break;
				
			case KEY.PAGEUP:
				event.preventDefault();
				if ( select.visible() ) {
					select.pageUp();
				} else {
					onChange(0, true);
				}
				break;
				
			case KEY.PAGEDOWN:
				event.preventDefault();
				if ( select.visible() ) {
					select.pageDown();
				} else {
					onChange(0, true);
				}
				break;
			
			// matches also semicolon
			case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA:
			case KEY.TAB:
			case KEY.RETURN:
				if( selectCurrent() ) {
					// stop default to prevent a form submit, Opera needs special handling
					event.preventDefault();
					blockSubmit = true;
					return false;
				}
				break;
				
			case KEY.ESC:
				select.hide();
				break;
				
			default:
				clearTimeout(timeout);
				timeout = setTimeout(onChange, options.delay);
				break;
		}
	}).focus(function(){
		// track whether the field has focus, we shouldn't process any
		// results if the field no longer has focus
		hasFocus++;
	}).blur(function() {
		hasFocus = 0;
		if (!config.mouseDownOnSelect) {
			hideResults();
		}
	}).click(function() {
		// show select when clicking in a focused field
		if ( hasFocus++ > 1 && !select.visible() ) {
			onChange(0, true);
		}
	}).bind("search", function() {
		// TODO why not just specifying both arguments?
		var fn = (arguments.length > 1) ? arguments[1] : null;
		function findValueCallback(q, data) {
			var result;
			if( data && data.length ) {
				for (var i=0; i < data.length; i++) {
					if( data[i].result.toLowerCase() == q.toLowerCase() ) {
						result = data[i];
						break;
					}
				}
			}
			if( typeof fn == "function" ) fn(result);
			else $input.trigger("result", result && [result.data, result.value]);
		}
		$.each(trimWords($input.val()), function(i, value) {
			request(value, findValueCallback, findValueCallback);
		});
	}).bind("flushCache", function() {
		cache.flush();
	}).bind("setOptions", function() {
		$.extend(options, arguments[1]);
		// if we've updated the data, repopulate
		if ( "data" in arguments[1] )
			cache.populate();
	}).bind("unautocomplete", function() {
		select.unbind();
		$input.unbind();
		$(input.form).unbind(".autocomplete");
	});
	
	
	function selectCurrent() {
		var selected = select.selected();
		if( !selected )
			return false;
		
		var v = selected.result;
		previousValue = v;
		
		if ( options.multiple ) {
			var words = trimWords($input.val());
			if ( words.length > 1 ) {
				v = words.slice(0, words.length - 1).join( options.multipleSeparator ) + options.multipleSeparator + v;
			}
			v += options.multipleSeparator;
		}
		
		$input.val(v);
		hideResultsNow();
		$input.trigger("result", [selected.data, selected.value]);
		return true;
	}
	
	function onChange(crap, skipPrevCheck) {
		if( lastKeyPressCode == KEY.DEL ) {
			select.hide();
			return;
		}
		
		var currentValue = $input.val();
		
		if ( !skipPrevCheck && currentValue == previousValue )
			return;
		
		previousValue = currentValue;
		
		currentValue = lastWord(currentValue);
		if ( currentValue.length >= options.minChars) {
			$input.addClass(options.loadingClass);
			if (!options.matchCase)
				currentValue = currentValue.toLowerCase();
			request(currentValue, receiveData, hideResultsNow);
		} else {
			stopLoading();
			select.hide();
		}
	};
	
	function trimWords(value) {
		if ( !value ) {
			return [""];
		}
		var words = value.split( options.multipleSeparator );
		var result = [];
		$.each(words, function(i, value) {
			if ( $.trim(value) )
				result[i] = $.trim(value);
		});
		return result;
	}
	
	function lastWord(value) {
		if ( !options.multiple )
			return value;
		var words = trimWords(value);
		return words[words.length - 1];
	}
	
	// fills in the input box w/the first match (assumed to be the best match)
	// q: the term entered
	// sValue: the first matching result
	function autoFill(q, sValue){
		// autofill in the complete box w/the first match as long as the user hasn't entered in more data
		// if the last user key pressed was backspace, don't autofill
		if( options.autoFill && (lastWord($input.val()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != KEY.BACKSPACE ) {
			// fill in the value (keep the case the user has typed)
			$input.val($input.val() + sValue.substring(lastWord(previousValue).length));
			// select the portion of the value not typed by the user (so the next character will erase)
			$.Autocompleter.Selection(input, previousValue.length, previousValue.length + sValue.length);
		}
	};

	function hideResults() {
		clearTimeout(timeout);
		timeout = setTimeout(hideResultsNow, 200);
	};

	function hideResultsNow() {
		var wasVisible = select.visible();
		select.hide();
		clearTimeout(timeout);
		stopLoading();
		if (options.mustMatch) {
			// call search and run callback
			$input.autocomplete("search", function (result){
					// if no value found, clear the input box
					if( !result ) {
						if (options.multiple) {
							var words = trimWords($input.val()).slice(0, -1);
							$input.val( words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : "") );
						}
						else
							$input.val( "" );
					}
				}
			);
		}
		if (wasVisible)
			// position cursor at end of input field
			$.Autocompleter.Selection(input, input.value.length, input.value.length);
	};

	function receiveData(q, data) {
		if ( data && data.length && hasFocus ) {
			stopLoading();
			select.display(data, q);
			autoFill(q, data[0].value);
			select.show();
		} else {
			hideResultsNow();
		}
	};

	function request(term, success, failure) {
		if (!options.matchCase)
			term = term.toLowerCase();
		var data = cache.load(term);
		// recieve the cached data
		if (data && data.length) {
			success(term, data);
		// if an AJAX url has been supplied, try loading the data now
		
		} else if( (typeof options.url == "string") && (options.url.length > 0) ){
			
			var extraParams = {
				timestamp: +new Date()
			};
			$.each(options.extraParams, function(key, param) {
				extraParams[key] = typeof param == "function" ? param() : param;
			});
			
			$.ajax({
				// try to leverage ajaxQueue plugin to abort previous requests
				mode: "abort",
				// limit abortion to this input
				port: "autocomplete" + input.name,
				dataType: options.dataType,
				url: options.url,
				data: $.extend({
					q: lastWord(term),
					limit: options.max
				}, extraParams),
				success: function(data) {
					var parsed = options.parse && options.parse(data) || parse(data);
					cache.add(term, parsed);
					success(term, parsed);
				}
			});
		}

		else if (options.source && typeof options.source == 'function') {
			var resultData = options.source(term);
			var parsed = (options.parse) ? options.parse(resultData) : resultData;

			cache.add(term, parsed);
			success(term, parsed);
		} else {
			// if we have a failure, we need to empty the list -- this prevents the the [TAB] key from selecting the last successful match
			select.emptyList();
			failure(term);
		}
	};
	
	function parse(data) {
		var parsed = [];
		var rows = data.split("\n");
		for (var i=0; i < rows.length; i++) {
			var row = $.trim(rows[i]);
			if (row) {
				row = row.split("|");
				parsed[parsed.length] = {
					data: row,
					value: row[0],
					result: options.formatResult && options.formatResult(row, row[0]) || row[0]
				};
			}
		}
		return parsed;
	};

	function stopLoading() {
		$input.removeClass(options.loadingClass);
	};

};

$.Autocompleter.defaults = {
	inputClass: "ui-autocomplete-input",
	resultsClass: "ui-autocomplete-results",
	loadingClass: "ui-autocomplete-loading",
	minChars: 1,
	delay: 400,
	matchCase: false,
	matchSubset: true,
	matchContains: false,
	cacheLength: 10,
	max: 100,
	mustMatch: false,
	extraParams: {},
	selectFirst: true,
	formatItem: function(row) { return row[0]; },
	formatMatch: null,
	autoFill: false,
	width: 0,
	multiple: false,
	multipleSeparator: ", ",
	highlight: function(value, term) {
		return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<strong>$1</strong>");
	},
    scroll: true,
    scrollHeight: 180
};

$.extend($.ui.autocomplete, {
	defaults: $.Autocompleter.defaults
});

$.Autocompleter.Cache = function(options) {

	var data = {};
	var length = 0;
	
	function matchSubset(s, sub) {
		if (!options.matchCase) 
			s = s.toLowerCase();
		var i = s.indexOf(sub);
		if (i == -1) return false;
		return i == 0 || options.matchContains;
	};
	
	function add(q, value) {
		if (length > options.cacheLength){
			flush();
		}
		if (!data[q]){ 
			length++;
		}
		data[q] = value;
	}
	
	function populate(){
		if( !options.data ) return false;
		// track the matches
		var stMatchSets = {},
			nullData = 0;

		// no url was specified, we need to adjust the cache length to make sure it fits the local data store
		if( !options.url ) options.cacheLength = 1;
		
		// track all options for minChars = 0
		stMatchSets[""] = [];
		
		// loop through the array and create a lookup structure
		for ( var i = 0, ol = options.data.length; i < ol; i++ ) {
			var rawValue = options.data[i];
			// if rawValue is a string, make an array otherwise just reference the array
			rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue;
			
			var value = options.formatMatch(rawValue, i+1, options.data.length);
			if ( value === false )
				continue;
				
			var firstChar = value.charAt(0).toLowerCase();
			// if no lookup array for this character exists, look it up now
			if( !stMatchSets[firstChar] ) 
				stMatchSets[firstChar] = [];

			// if the match is a string
			var row = {
				value: value,
				data: rawValue,
				result: options.formatResult && options.formatResult(rawValue) || value
			};
			
			// push the current match into the set list
			stMatchSets[firstChar].push(row);

			// keep track of minChars zero items
			if ( nullData++ < options.max ) {
				stMatchSets[""].push(row);
			}
		};

		// add the data items to the cache
		$.each(stMatchSets, function(i, value) {
			// increase the cache size
			options.cacheLength++;
			// add to the cache
			add(i, value);
		});
	}
	
	// populate any existing data
	setTimeout(populate, 25);
	
	function flush(){
		data = {};
		length = 0;
	}
	
	return {
		flush: flush,
		add: add,
		populate: populate,
		load: function(q) {
			if (!options.cacheLength || !length)
				return null;
			/* 
			 * if dealing w/local data and matchContains than we must make sure
			 * to loop through all the data collections looking for matches
			 */
			if( !options.url && options.matchContains ){
				// track all matches
				var csub = [];
				// loop through all the data grids for matches
				for( var k in data ){
					// don't search through the stMatchSets[""] (minChars: 0) cache
					// this prevents duplicates
					if( k.length > 0 ){
						var c = data[k];
						$.each(c, function(i, x) {
							// if we've got a match, add it to the array
							if (matchSubset(x.value, q)) {
								csub.push(x);
							}
						});
					}
				}				
				return csub;
			} else 
			// if the exact item exists, use it
			if (data[q]){
				return data[q];
			} else
			if (options.matchSubset) {
				for (var i = q.length - 1; i >= options.minChars; i--) {
					var c = data[q.substr(0, i)];
					if (c) {
						var csub = [];
						$.each(c, function(i, x) {
							if (matchSubset(x.value, q)) {
								csub[csub.length] = x;
							}
						});
						return csub;
					}
				}
			}
			return null;
		}
	};
};

$.Autocompleter.Select = function (options, input, select, config) {
	var CLASSES = {
		ACTIVE: "ui-autocomplete-over"
	};
	
	var listItems,
		active = -1,
		data,
		term = "",
		needsInit = true,
		element,
		list;
	
	// Create results
	function init() {
		if (!needsInit)
			return;
		element = $("<div/>")
		.hide()
		.addClass(options.resultsClass)
		.css("position", "absolute")
		.appendTo(document.body);
	
		list = $("<ul/>").appendTo(element).mouseover( function(event) {
			if(target(event).nodeName && target(event).nodeName.toUpperCase() == 'LI') {
	            active = $("li", list).removeClass(CLASSES.ACTIVE).index(target(event));
			    $(target(event)).addClass(CLASSES.ACTIVE);            
	        }
		}).click(function(event) {
			$(target(event)).addClass(CLASSES.ACTIVE);
			select();
			// TODO provide option to avoid setting focus again after selection? useful for cleanup-on-focus
			input.focus();
			return false;
		}).mousedown(function() {
			config.mouseDownOnSelect = true;
		}).mouseup(function() {
			config.mouseDownOnSelect = false;
		});
		
		if( options.width > 0 )
			element.css("width", options.width);
			
		needsInit = false;
	} 
	
	function target(event) {
		var element = event.target;
		while(element && element.tagName != "LI")
			element = element.parentNode;
		// more fun with IE, sometimes event.target is empty, just ignore it then
		if(!element)
			return [];
		return element;
	}

	function moveSelect(step) {
		listItems.slice(active, active + 1).removeClass(CLASSES.ACTIVE);
		movePosition(step);
        var activeItem = listItems.slice(active, active + 1).addClass(CLASSES.ACTIVE);
        if(options.scroll) {
            var offset = 0;
            listItems.slice(0, active).each(function() {
				offset += this.offsetHeight;
			});
            if((offset + activeItem[0].offsetHeight - list.scrollTop()) > list[0].clientHeight) {
                list.scrollTop(offset + activeItem[0].offsetHeight - list.innerHeight());
            } else if(offset < list.scrollTop()) {
                list.scrollTop(offset);
            }
        }
	};
	
	function movePosition(step) {
		active += step;
		if (active < 0) {
			active = listItems.size() - 1;
		} else if (active >= listItems.size()) {
			active = 0;
		}
	}
	
	function limitNumberOfItems(available) {
		return options.max && options.max < available
			? options.max
			: available;
	}
	
	function fillList() {
		list.empty();
		var max = limitNumberOfItems(data.length);
		for (var i=0; i < max; i++) {
			if (!data[i])
				continue;
			var formatted = options.formatItem(data[i].data, i+1, max, data[i].value, term);
			if ( formatted === false )
				continue;
			var li = $("<li/>").html( options.highlight(formatted, term) ).addClass(i%2 == 0 ? "ui-autocomplete-even" : "ui-autocomplete-odd").appendTo(list)[0];
			$.data(li, "ui-autocomplete-data", data[i]);
		}
		listItems = list.find("li");
		if ( options.selectFirst ) {
			listItems.slice(0, 1).addClass(CLASSES.ACTIVE);
			active = 0;
		}
		// apply bgiframe if available
		if ( $.fn.bgiframe )
			list.bgiframe();
	}
	
	return {
		display: function(d, q) {
			init();
			data = d;
			term = q;
			fillList();
		},
		next: function() {
			moveSelect(1);
		},
		prev: function() {
			moveSelect(-1);
		},
		pageUp: function() {
			if (active != 0 && active - 8 < 0) {
				moveSelect( -active );
			} else {
				moveSelect(-8);
			}
		},
		pageDown: function() {
			if (active != listItems.size() - 1 && active + 8 > listItems.size()) {
				moveSelect( listItems.size() - 1 - active );
			} else {
				moveSelect(8);
			}
		},
		hide: function() {
			element && element.hide();
			listItems && listItems.removeClass(CLASSES.ACTIVE)
			active = -1;
			$(input).triggerHandler("autocompletehide", [{}, { options: options }], options["hide"]);
		},
		visible : function() {
			return element && element.is(":visible");
		},
		current: function() {
			return this.visible() && (listItems.filter("." + CLASSES.ACTIVE)[0] || options.selectFirst && listItems[0]);
		},
		show: function() {
			var offset = $(input).offset();
			element.css({
				width: typeof options.width == "string" || options.width > 0 ? options.width : $(input).width(),
				top: offset.top + input.offsetHeight,
				left: offset.left
			}).show();
			
            if(options.scroll) {
                list.scrollTop(0);
                list.css({
					maxHeight: options.scrollHeight,
					overflow: 'auto'
				});
				
                if($.browser.msie && typeof document.body.style.maxHeight === "undefined") {
					var listHeight = 0;
					listItems.each(function() {
						listHeight += this.offsetHeight;
					});
					var scrollbarsVisible = listHeight > options.scrollHeight;
                    list.css('height', scrollbarsVisible ? options.scrollHeight : listHeight );
					if (!scrollbarsVisible) {
						// IE doesn't recalculate width when scrollbar disappears
						listItems.width( list.width() - parseInt(listItems.css("padding-left")) - parseInt(listItems.css("padding-right")) );
					}
                }
                
            }
            
            $(input).triggerHandler("autocompleteshow", [{}, { options: options }], options["show"]);
            
		},
		selected: function() {
			var selected = listItems && listItems.filter("." + CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE);
			return selected && selected.length && $.data(selected[0], "ui-autocomplete-data");
		},
		emptyList: function (){
			list && list.empty();
		},
		unbind: function() {
			element && element.remove();
		}
	};
};

$.Autocompleter.Selection = function(field, start, end) {
	if( field.createTextRange ){
		var selRange = field.createTextRange();
		selRange.collapse(true);
		selRange.moveStart("character", start);
		selRange.moveEnd("character", end);
		selRange.select();
	} else if( field.setSelectionRange ){
		field.setSelectionRange(start, end);
	} else {
		if( field.selectionStart ){
			field.selectionStart = start;
			field.selectionEnd = end;
		}
	}
	field.focus();
};

})(jQuery);

(function($) {

$.widget("ui.tree", {
	init: function() {

		var self = this;
		this.identifier = (new Date()).getTime()+Math.random();

		this.element.sortable({
			items: this.options.sortOn,
			scope: this.identifier,
			distance: this.options.distance,
			placeholder: "ui-tree-placeholder",
			helper: this.options.helper,
			handle: this.options.handle,
			scroll: this.options.scroll,
			appendTo: this.options.appendTo,
			start: function(e, ui) {
				var inst = $(this).data("sortable");
				inst.placeholder.hide();
				inst.helperProportions.height = inst.currentItem.find(self.options.dropOn).length ? inst.currentItem.find(self.options.dropOn).outerHeight() : inst.currentItem.outerHeight();
				inst._preserveHelperProportions = true;
				inst.refreshPositions(true);

				self.originalParent = ui.item.parent();

				(self.options.start && self.options.start.apply(this, [e, ui]));
			},
			stop: function(e, ui) {
				var sortable = $(this).data("sortable");
				$(sortable.options.items, sortable.element).removeClass(self.options.sortIndicatorDown).removeClass(self.options.sortIndicatorUp);

				if ( self.originalParent.is(':empty') )
					self.originalParent.remove();

				(self.options.stop && self.options.stop.apply(this, [e, ui]));
			},
			sortIndicator: function(e, item, append, hardRefresh) {

				append ? append[0].appendChild(this.placeholder[0]) : item.item[0].parentNode.insertBefore(this.placeholder[0], (this.direction == 'down' ? item.item[0] : item.item[0].nextSibling));

				$(this.options.items, this.element).removeClass(self.options.sortIndicatorDown).removeClass(self.options.sortIndicatorUp);
				item.item.addClass(this.direction == "down" ? self.options.sortIndicatorDown : self.options.sortIndicatorUp);

			}
		});

		//Make certain nodes droppable
		$(this.options.dropOn, this.element).droppable({
			accept: this.options.sortOn,
			hoverClass: this.options.dropHoverClass,
			tolerance: "pointer",
			scope: this.identifier,
			over: function(e, ui) {
				$(self.options.sortOn, self.element).removeClass(self.options.sortIndicatorDown).removeClass(self.options.sortIndicatorUp);
				self.overDroppable = true;
				self.trigger('over', e, ui);
			},
			out: function(e, ui) {
				self.overDroppable = false;
				(self.options.out && self.options.out.apply(this, [e, ui]));
			},
			drop: function(e, ui) {

				var ul = $(this).parent().find("ul");
				if(!ul.length) var ul = $("<ul></ul>").appendTo($(this).parent());

				ui.draggable.appendTo( $(this).parent().find("> ul") );

				self.element.data("sortable")._noFinalSort = true;

				(self.options.drop && self.options.drop.apply(this, [e, ui]));
			}
		});

	}
});

$.extend($.ui.tree, {
	defaults: {
		sortOn: "*",
		dropOn: "div",
		dropHoverClass: "ui-tree-hover",
		sortIndicatorDown: "hover-down",
		sortIndicatorUp: "hover-up"
	}
});

})(jQuery);
Liferay.Layout = {
	init: function(options) {
		var instance = this;

		instance.isFreeForm = options.freeForm;

		var layoutHandler;

		if (!options.freeForm) {
			layoutHandler = instance.Columns;
		}
		else {
			layoutHandler = instance.FreeForm;
		}

		instance._useCloneProxy = options.clonePortlet;

		layoutHandler.init(options);

		instance.layoutHandler = layoutHandler;
	},

	refresh: function(portletBound) {
		var instance = this;

		instance.layoutHandler.refresh(portletBound);
	},

	showTemplates: function() {
		var instance = this;

		var url = themeDisplay.getPathMain() + '/layout_configuration/templates';

		Liferay.Popup(
			{
				modal: true,
				position: ['center', 100],
				title: Liferay.Language.get('layout'),
				url: url,
				urlData: {
					p_l_id: themeDisplay.getPlid(),
					doAsUserId: themeDisplay.getDoAsUserIdEncoded(),
					redirect: Liferay.currentURL
				},
				width: 700
			}
		);
	},

	_findIndex: function(portlet, parentNode) {
		var instance = this;

		parentNode = parentNode || portlet.parentNode;

		return jQuery('> .portlet-boundary', parentNode).index(portlet);
	},

	_saveLayout: function(options) {
		var instance = this;

		var data = {
			doAsUserId: themeDisplay.getDoAsUserIdEncoded(),
			p_l_id: themeDisplay.getPlid()
		};

		jQuery.extend(data, options);

		jQuery.ajax(
			{
				url: themeDisplay.getPathMain() + '/portal/update_layout',
				data: data
			}
		);
	}
};

Liferay.Layout.Columns = {
	init: function(options) {
		var instance = this;

		instance._columns = options.columnSelector;
		instance._portlets = options.boxSelector;
		instance._grid = jQuery(options.grid);
		instance._handleSelector = options.handleSelector;
		instance._boxSelector = options.boxSelector;
		instance._placeHolderClass = options.placeHolderClass;
		instance._onCompleteCallback = options.onComplete;

		instance._activeAreaClass = 'active-area';
		instance._dropAreaClass = 'drop-area';

		instance._gridColumns = '.lfr-column';

		instance._counter = 0;

		instance._placeholderCachedObject = jQuery('<div class="' + instance._placeHolderClass + '"></div>');

		var options = {
			appendTo: 'body',
			connectWith: [instance._columns],
			dropOnEmpty: true,
			forcePointerForContainers: true,
			handle: instance._handleSelector,
			items: instance._boxSelector,
			helper: instance._createHelper,
			placeholder: {
				element: function() {
					return instance._placeholderCachedObject;
				},
				update: function(container, p) {
				}
			},
			tolerance: 'guess',
			revert:	false,
			distance: 2,
			scroll: true,
			scrollSensitivity: 50,
			scrollSpeed: 30,
			custom: {
				refreshContainers: function() {
					for (var i = this.containers.length - 1; i >= 0; i--){
						var container = this.containers[i];
						var cell = container.element.parent();
						var offset = cell.offset();

						container.containerCache.left = offset.left;
						container.containerCache.top = offset.top;
						container.containerCache.width	= cell.outerWidth();
						container.containerCache.height = cell.outerHeight();
					};
				}
			},

			// Callbacks

			start: function(event, ui) {
				instance._onStart(event, ui);
			},
			stop: function(event, ui) {
				instance._onStop(event, ui);
			},
			update: function(event, ui) {
				instance._onUpdate(event, ui);
			},
			receive: function(event, ui) {
				instance._onReceive(event, ui);
			},
			remove: function(event, ui) {
				instance._onRemove(event, ui);
			},

			// These methods are sensitive to performance, so we don't add to
			// the callstack and instead just do the work inline.

			over: function(event, ui) {
				instance._counter++;
				jQuery(this).parent(instance._gridColumns).addClass(instance._activeAreaClass);
				ui.helper.removeClass('not-intersecting');
			},
			out: function(event, ui) {
				instance._counter++;
				jQuery(this).parent(instance._gridColumns).removeClass(instance._activeAreaClass);

				// We need to make sure that the active class and the intersection
				// logic don't fall out of sync

				if (!(instance._counter % 2)) {
					ui.helper.addClass('not-intersecting');
					instance._counter = 0;
				}
			},
			activate: function(event, ui) {
				instance._grid.addClass('dragging');
				jQuery(this).parent(instance._gridColumns).addClass(instance._dropAreaClass);
			},
			deactivate: function(event, ui) {
				jQuery(this).parent(instance._gridColumns).removeClass(instance._dropAreaClass);
			}
		};

		instance.sortColumns = jQuery(instance._columns);

		instance.sortColumns.sortable(options);

		jQuery(instance._boxSelector).find(instance._handleSelector).css('cursor', 'move');
	},

	refresh: function(portletBound) {
		var instance = this;

		if (portletBound) {
			jQuery(instance._handleSelector, portletBound).css('cursor', 'move');
		}

		instance.sortColumns.sortable('refresh');
	},

	startDragging: function() {
		var instance = this;

		instance._grid.addClass('dragging');
	},

	stopDragging: function() {
		var instance = this;

		instance._grid.removeClass('dragging');
	},

	_createHelper: function(event, obj) {
		var instance = this;

		var width = obj[0].offsetWidth;
		var height = obj[0].offsetHeight;
		var div = [];

		if (instance._useCloneProxy) {
			div = obj.clone();
		}
		else {
			div = jQuery(Liferay.Template.PORTLET);
			div.addClass('ui-proxy');

			var titleHtml = obj.find('.portlet-title, .portlet-title-default').html();

			div.find('.portlet-title').html(titleHtml);
		}

		div.css(
			{
				width: width,
				height: height,
				zIndex: Liferay.zIndex.DRAG_ITEM
			}
		);

		return div[0];
	},

	_onOut: function(event, ui) {
		var instance = this;
	},

	_onReceive: function(event, ui) {
		var instance = this;

		if (ui.element[0].className.indexOf('empty') > -1) {
			ui.element.removeClass('empty');
		}
	},

	_onRemove: function(event, ui) {
		var instance = this;

		var oCol = ui.element;
		var foundPortlets = oCol.find('.portlet-boundary');
		var minPortlets = 1;
		if (foundPortlets.length < minPortlets) {
			oCol.addClass('empty');
		}
	},

	_onStart: function(event, ui) {
		var instance = this;

		var helperHeight = ui.helper.outerHeight();
		var cachedPlaceholder = instance._placeholderCachedObject;

		instance.startDragging();

		cachedPlaceholder.height(helperHeight);
	},

	_onStop: function(event, ui) {
		var instance = this;

		instance.stopDragging();
	},

	_onUpdate: function(event, ui) {
		var instance = this;

		var currentCol = ui.element[0];
		var portlet = (ui.item || [false])[0];

		if (portlet && portlet.parentNode == currentCol) {
			var position = Liferay.Layout._findIndex(portlet, currentCol);
			var currentColumnId = Liferay.Util.getColumnId(currentCol.id);
			var portletId = Liferay.Util.getPortletId(portlet.id);

			var viewport = Liferay.Util.viewport.scroll();
			var portletOffset = ui.item.offset();

			Liferay.Layout._saveLayout(
				{
					cmd: 'move',
					p_p_col_id: currentColumnId,
					p_p_col_pos: position,
					p_p_id: portletId
				}
			);

			if (instance._onCompleteCallback) {
				instance._onCompleteCallback(event, ui);
			}

			if (viewport.y > portletOffset.top) {
				window.scrollTo(portletOffset.left, portletOffset.top - 10);
			}
		}
	}
};

Liferay.Layout.FreeForm = {
	init: function(options) {
		var instance = this;

		// Set private variables

		instance._columns = options.columnSelector;
		instance._portlets = options.boxSelector;

		jQuery(instance._columns).find(instance._portlets).each(
			function() {
				instance.add(this);
			}
		);
	},

	add: function(portlet) {
		var instance = this;

		var handle = jQuery('.portlet-header-bar, .portlet-title-default, .portlet-topper', portlet);

		handle.css('cursor', 'move');

		var jPortlet = jQuery(portlet);

		if (!jPortlet.find('.ui-resizable-handle').length) {
			jPortlet.append('<div class="ui-resizable-handle ui-resizable-se"></div>');
		}

		jPortlet.css('position', 'absolute');

		instance._createHelperCache(portlet);

		var helperZIndex = instance._maxZIndex + 10;

		jPortlet.draggable(
			{
				handle: '.portlet-header-bar, .portlet-title-default, .portlet-topper, .portlet-topper *',
				helper: function(event) {
					var portlet = jQuery(this);
					var helper = instance._createHelperCache(this);

					var height = portlet.height();
					var width = portlet.width();

					helper.css(
						{
							height: height,
							width: width,
							zIndex: helperZIndex
						}
					);

					var titleHtml = portlet.find('.portlet-title, .portlet-title-default').html();

					helper.find('.portlet-title').html(titleHtml);

					return helper[0];
				},
				start: function(event, ui) {
					instance._moveToTop(this);
				},
				distance: 2,
				stop: function(event, ui) {
					var portlet = this;

					var left = parseInt(ui.position.left);
					var top = parseInt(ui.position.top);

					left = Math.round(left/10) * 10;
					top = Math.round(top/10) * 10;

					portlet.style.left = left + 'px';
					portlet.style.top = top + 'px';

					instance._savePosition(portlet);
				}
			}
		);

		jPortlet.mousedown(
			function(event) {
				if (instance._current != this) {
					instance._moveToTop(this, true);
					instance._savePosition(this, true);
					instance._current = this;
					this.style.zIndex = instance._maxZIndex;
				}
			}
		);

		var resizeBox = jQuery('.portlet-content-container, .portlet-borderless-container', portlet);
		var oldPortletHeight = parseInt(jPortlet[0].style.height) || jPortlet.height();

		jPortlet.resizable(
			{
				helper: 'ui-resizable-proxy',
				start: function(event, ui) {
					ui.helper.css('z-index', helperZIndex);
					instance._moveToTop(this);
				},
				stop: function(event, ui) {
					var portlet = this;
					var rBoxHeight = parseInt(resizeBox[0].style.height);
					var portletHeight = ui.size.height;
					var newHeight = Math.round((portletHeight / oldPortletHeight) * rBoxHeight);

					resizeBox.css('height', newHeight);
					jPortlet.css('height', 'auto');

					oldPortletHeight = portletHeight;
					instance._savePosition(portlet);
				}
			}
		);

		if ((parseInt(portlet.style.top) + parseInt(portlet.style.left)) == 0) {
			if (portlet.columnPos == undefined) {
				portlet.columnPos = 0;
			}

			portlet.style.top = (20 * portlet.columnPos) + 'px';
			portlet.style.left = (20 * portlet.columnPos) + 'px';
		}

		instance._current = portlet;
	},

	refresh: function(portletBound) {
		var instance = this;

		if (portletBound) {
			instance.add(portletBound);
		}
	},

	_createHelperCache: function(obj) {
		var instance = this;

		if (!obj.jquery) {
			obj = jQuery(obj);
		}

		var cache = obj.data('ui-helper-drag');

		if (!cache) {
			var cachedObj = jQuery(Liferay.Template.PORTLET);

			cachedObj.addClass('ui-proxy');

			cache = obj.data('ui-helper-drag', cachedObj);
		}

		return cache;
	},

	_moveToTop: function(portlet, temporary) {
		var instance = this;

		var container = portlet.parentNode;
		portlet.oldPosition = Liferay.Layout._findIndex(portlet);

		if (!temporary) {
			container.appendChild(portlet);
		}
		else {
			portlet.style.zIndex = instance._maxZIndex + 5;

			jQuery(portlet).one(
				'click',
				function(event) {
					instance._moveToTop(this);
				}
			);
		}
	},

	_savePosition: function(portlet, wasClicked) {
		var instance = this;
		var resizeBox = jQuery(portlet).find('.portlet-content-container, .portlet-borderless-container')[0];
		var position = Liferay.Layout._findIndex(portlet);
		var portletId = Liferay.Util.getPortletId(portlet.id);
		var changedIndex = (position != portlet.oldPosition);
		var changedPosition = (resizeBox && !wasClicked);

		if (changedIndex || changedPosition) {
			if (changedIndex) {
				var currentColumnId = Liferay.Util.getColumnId(portlet.parentNode.id);

				Liferay.Layout._saveLayout(
					{
						cmd: 'move',
						p_p_col_id: currentColumnId,
						p_p_col_pos: position,
						p_p_id: portletId
					}
				);
			}

			if (changedPosition) {
				Liferay.Layout._saveLayout(
					{
						cmd: 'drag',
						height: resizeBox.style.height,
						left: portlet.style.left,
						p_p_id: portletId,
						top: portlet.style.top,
						width: portlet.style.width
					}
				);
			}
		}
	},

	_maxZIndex: 99
};
Liferay.Observable = new Class({
	initialize: function() {
		var instance = this;

		instance._eventObj = jQuery(instance);
	},

	bind: function(event, handler, scope) {
		var instance = this;

		if (handler && event) {
			instance._createEventObj();

			var method = handler;

			if (scope) {
				method = function(event) {
					handler.apply(scope || instance, arguments);
				};
			}

			instance._eventObj.bind(event, method);
		}

	},

	get: function(key, defaultValue) {
		var instance = this;

		var prop = '__' + key;
		var value = defaultValue;

		if (prop in instance) {
			value = instance[prop];
		}

		return value;
	},

	set: function(key, value) {
		var instance = this;

		var prop = '__' + key;

		var oldValue = instance[prop];

		if (value != oldValue) {
			instance[prop] = value;

			instance.trigger('update', [instance, {value: value}]);
		}
	},

	trigger: function(event, data){
		var instance = this;

		if (instance._eventsSuspended == false) {
			instance._createEventObj();

			instance._eventObj.triggerHandler(event, data);
		}
	},

	resumeEvents: function(){
		var instance = this;

		instance._eventsSuspended = false;
	},

	suspendEvents: function(){
		var instance = this;

		instance._eventsSuspended = true;
	},

	_createEventObj: function() {
		var instance = this;

		if (!instance._eventObj) {
			instance._eventObj = jQuery(instance);
		}
	},

	_eventsSuspended: false
});
Liferay.AutoFields = Liferay.Observable.extend({

	/**
	 * OPTIONS
	 *
	 * Required
	 * container {string|object}: A jQuery selector that contains the rows you wish to duplicate.
	 * baseRows {string|object}: A jQuery selector that defines which fields are duplicated.
	 *
	 * Optional
	 * fieldIndexes {string}: The name of the POST parameter that will contain a list of the order for the fields.
	 * sortable{boolean}: Whether or not the rows should be sortable
	 * sortableHandle{string}: A jQuery selector that defines a handle for the sortables
	 *
	 */

	initialize: function(options) {
		var instance = this;

		var container = jQuery(options.container);
		var baseRows = jQuery(options.baseRows);

		var fullContainer = jQuery('<div class="row-container"></div>');
		var baseContainer = jQuery('<div class="lfr-form-row"></div>');

		var rowControls = jQuery('<span class="row-controls"><a href="javascript: ;" class="add-row">' + Liferay.Language.get('add-row') + '</a><a href="javascript: ;" class="delete-row modify-link">' + Liferay.Language.get('delete-row') + '</a></span>');

		instance._baseContainer = fullContainer;
		instance._idSeed = baseRows.length;

		instance._undoManager = new Liferay.UndoManager(
			{
				container: container
			}
		);

		if (options.fieldIndexes) {
			instance._fieldIndexes = jQuery('[name=' + options.fieldIndexes + ']');

			if (!instance._fieldIndexes.length) {
				instance._fieldIndexes = jQuery('<input name="' + options.fieldIndexes + '" type="hidden" />')
				instance._baseContainer.append(instance._fieldIndexes);
			}
		}
		else {
			instance._fieldIndexes = jQuery([]);
		}

		fullContainer.click(
			function(event) {
				if (event.target.parentNode.className.indexOf('row-controls') > -1) {
					var target = jQuery(event.target);
					var currentRow = target.parents('.lfr-form-row:first')[0];

					if (target.is('.add-row')) {
						instance.addRow(currentRow);
					}

					if (target.is('.delete-row')) {
						target.trigger('change');

						instance.deleteRow(currentRow);
					}
				}
			}
		);

		instance._container = container;
		instance._rowContainer = fullContainer;

		baseRows.each(
			function(i) {
				var formRow;
				var controls = rowControls.clone();
				var currentRow = jQuery(this);

				if (currentRow.is('.lfr-form-row')) {
					formRow = currentRow;
				}
				else {
					formRow = baseContainer.clone();
					formRow.append(this);
				}

				formRow.append(controls);
				fullContainer.append(formRow);

				if (i == 0) {
					instance._rowTemplate = formRow.clone();
					instance._rowTemplate.clearForm();
				}
			}
		);

		var rows = fullContainer.find('.lfr-form-row');

		container.append(fullContainer);

		if (options.sortable){
			instance._makeSortable(options.sortableHandle);
		}

		Liferay.bind(
			'submitForm',
			function(event, data) {
				var form = jQuery(data.form);

				form.find('.lfr-form-row:hidden').remove();

				var fieldOrder = instance.serialize();

				instance._fieldIndexes.val(fieldOrder);
			}
		);

		instance._undoManager.bind(
			'clearList',
			function(event) {
				var hiddenRows = instance._rowContainer.find('.lfr-form-row:hidden');

				hiddenRows.remove();
			}
		);
	},

	addRow: function(el) {
		var instance = this;

		var currentRow = jQuery(el);
		var clone = currentRow.clone(true);

		var newSeed = (++instance._idSeed);

		clone.find('input, select, textarea').each(
			function() {
				var el = jQuery(this);
				var oldName = el.attr('name');
				var originalName = oldName.replace(/([0-9]+)$/, '');
				var newName = originalName + newSeed;

				if (!el.is(':radio')) {
					el.attr('name', newName);
				}
				else {
					oldName = el.attr('id');

					el.attr('checked', '');
					el.attr('value', newSeed);
				}

				el.attr('id', newName);
				clone.find('label[for=' + oldName + ']').attr('for', newName);
			}
		);

		clone.clearForm();

		clone.find("input[type=hidden]").each(
			function() {
				this.value = '';
			}
		);

		currentRow.after(clone);

		clone.find('input:text:first').trigger('focus');

		instance.trigger('addRow', {row: clone, originalRow: currentRow, idSeed: newSeed});
	},

	deleteRow: function(el) {
		var instance = this;

		var visibleRows = instance._rowContainer.find('.lfr-form-row:visible');

		if (visibleRows.length == 1) {
			instance.addRow(el);
		}

		var deletedElement = jQuery(el);

		deletedElement.hide();

		instance._undoManager.add(
			function(stateData) {
				deletedElement.show();
			}
		);

		instance.trigger('deleteRow', {row: deletedElement});
	},

	serialize: function(filter) {
		var instance = this;

		var rows = instance._baseContainer.find('.lfr-form-row:visible');
		var serializedData = [];

		if (filter) {
			serializedData = filter.apply(instance, [rows]) || [];
		}
		else {
			rows.each(
				function(i) {
					var formField = jQuery(this).find(':input:first');
					var fieldId = formField.attr('id');

					if (!fieldId) {
						fieldId = formField.attr('name');
					}
					fieldId = (fieldId || '').match(/([0-9]+)$/);

					if (fieldId && fieldId[0]) {
						serializedData.push(fieldId[0]);
					}
				}
			)
		}

		return serializedData.join(',');
	},

	_moveDown: function(target) {
		var element = jQuery(target);

		while (!element.is('.lfr-form-row')) {
			element = element.parent();
		}

		element.next().after(element);
	},

	_moveUp: function(target) {
		var element = jQuery(target);

		while(!element.is('.lfr-form-row')) {
			element = element.parent();
		}

		element.prev().before(element);
	},

	_makeSortable: function(sortableHandle) {
		var instance = this;

		var rows = instance._rowContainer.find('.lfr-form-row');

		if (sortableHandle) {
			rows.find(sortableHandle).addClass('handle-sort-vertical');
		}

		instance._rowContainer.sortable(
			{
				axis: 'y',
				helper: function(event, obj) {
					var height = obj.height();
					var width = obj.width();

					var helper = obj.clone();

					helper.css(
						{
							height: height,
							width: width
						}
					);

					return helper[0];
				},
				items: '.lfr-form-row',
				handle: sortableHandle
			}
		);
	},

	_idSeed: 0
});
Liferay.DynamicSelect = new Class({

	/**
	 * OPTIONS
	 *
	 * Required
	 * array {array}: An array of options.
	 * array[i].select {string}: An id of a select box.
	 * array[i].selectId {string}: A JSON object field name for an option value.
	 * array[i].selectDesc {string}: A JSON object field name for an option description.
	 * array[i].selectVal {string}: The value that is displayed in an option field.
	 *
	 * Callbacks
	 * array[i].selectData {function}: Returns a JSON array to populate the next select box.
	 */

	initialize: function(array) {
		var instance = this;

		instance.array = array;

		jQuery.each(
			array,
			function(i, options) {
				var id = options.select;
				var select = jQuery('#' + id);
				var selectData = options.selectData;

				select.attr('data-componentType', 'dynamic_select');

				var prevSelectVal = null;

				if (i > 0) {
					prevSelectVal = array[i - 1].selectVal;
				}

				selectData(
					function(list) {
						instance._updateSelect(i, list);
					},
					prevSelectVal
				);

				select.attr('name', id);

				select.bind(
					'change',
					function() {
						instance._callSelectData(i);
					}
				);
			}
		);
	},

	_callSelectData: function(i) {
		var instance = this;

		var array = instance.array;

		if ((i + 1) < array.length) {
			var curSelect = jQuery('#' + array[i].select);
			var nextSelectData = array[i + 1].selectData;

			nextSelectData(
				function(list) {
					instance._updateSelect(i + 1, list);
				},
				curSelect.val()
			);
		}
	},

	_updateSelect: function(i, list) {
		var instance = this;

		var options = instance.array[i];

		var select = jQuery('#' + options.select);
		var selectId = options.selectId;
		var selectDesc = options.selectDesc;
		var selectVal = options.selectVal;
		var selectNullable = options.selectNullable || true;

		var selectOptions = [];

		if (selectNullable) {
			selectOptions.push('<option value="0"></option>');
		}

		jQuery.each(
			list,
			function(i, obj) {
				var key = obj[selectId];
				var value = obj[selectDesc];

				selectOptions.push('<option value="' + key + '">' + value + '</option>');
			}
		);

		selectOptions = selectOptions.join('');

		select.html(selectOptions);
		select.find('option[value=' + selectVal + ']').attr('selected', 'selected');

		if (Liferay.Browser.isIe()) {
			select.css('width', 'auto');
		}
	}
});
var LayoutConfiguration = {
	categories: [],
	portlets: [],
	showTimer: 0,

	init: function() {
		var instance = this;

		var menu = jQuery('#portal_add_content');

		instance.menu = menu;

		if (menu.length) {
			instance.portlets = menu.find('.lfr-portlet-item');
			instance.categories = menu.find('.lfr-content-category');
			instance.categoryContainers = menu.find('.lfr-add-content');

			var data = function() {
				var value = jQuery(this).attr('id');

				return Liferay.Util.uncamelize(value).toLowerCase();
			};

			var searchField = jQuery('#layout_configuration_content');

			searchField.liveSearch(
				{
					list: instance.portlets,
					data: data,
					show: function() {
						var portlet = jQuery(this);

						portlet.show();
						portlet.parents('.lfr-content-category').addClass('visible').removeClass('hidden').show();
						portlet.parents('.lfr-add-content').addClass('expanded').removeClass('collapsed').show();
					},
					hide: function() {
						var portlet = jQuery(this);

						portlet.hide();
					}
				}
			);

			searchField.liveSearch(
				{
					list: instance.categoryContainers,
					data: data,
					after: function() {
						if (!this.term) {
							instance.categories.addClass('hidden').removeClass('visible').css('display', '');
							instance.categoryContainers.addClass('collapsed').removeClass('expanded').css('display', '');
							instance.portlets.css('display', '');
						}

						if (this.term == "*") {
							instance.categories.addClass('visible').removeClass('hidden');
							instance.categoryContainers.addClass('expanded').removeClass('collapsed');
							instance.portlets.show();
						}
					},
					exclude: function() {
						var categoryContent = jQuery('.lfr-content-category', this);

						var totalVisibleChildren = categoryContent.find('> div:visible').length;

						return totalVisibleChildren > 0;
					}
				}
			);
		}
	},

	toggle: function(ppid) {
		var instance = this;

		var plid = themeDisplay.getPlid();
		var doAsUserId = themeDisplay.getDoAsUserIdEncoded();

		if (!instance.menu) {
			var url = themeDisplay.getPathMain() + '/portal/render_portlet';

			var popupWidth = 250;
			var body = jQuery('body');

			body.addClass('lfr-has-sidebar');

			instance._dialog = Liferay.Popup(
				{
					width: popupWidth,
					message: '<div class="loading-animation" />',
					position: [5,5],
					resizable: false,
					title: Liferay.Language.get("add-application"),
					onClose: function() {
						instance.menu = null;
						body.removeClass('lfr-has-sidebar');
					}
				}
			);

			jQuery.ajax(
				{
					url: url,
					data: {
						p_l_id: plid,
						p_p_id: ppid,
						p_p_state: 'exclusive',
						doAsUserId: doAsUserId
					},
					success: function(message) {
						instance._dialog.html(message);
						instance._loadContent();
					}
				}
			);
		}
	},

	_addPortlet: function(portlet, options) {
		var instance = this;

		var portletMetaData = instance._getPortletMetaData(portlet);

		if (!portletMetaData.portletUsed) {
			var plid = portletMetaData.plid;
			var portletId = portletMetaData.portletId;
			var isInstanceable = portletMetaData.instanceable;

			if (!isInstanceable) {
				portlet.addClass('lfr-portlet-used');
				portlet.draggable('disable');
			}

			var placeHolder = jQuery('<div class="loading-animation" />');
			var onComplete = null;
			var beforePortletLoaded = null;

			if (options) {
				var item = options.item;

				options.placeHolder = placeHolder[0];
				onComplete = options.onComplete;
				beforePortletLoaded = options.beforePortletLoaded;

				item.after(placeHolder);
				item.remove();
			}
			else {
				if (instance._sortColumns) {
					instance._sortColumns.filter(':first').prepend(placeHolder);
				}
			}

			var portletOptions = {
				beforePortletLoaded: beforePortletLoaded,
				onComplete: onComplete,
				plid: plid,
				portletId: portletId,
				placeHolder: placeHolder
			}

			var portletPosition = Liferay.Portlet.add(portletOptions);

			instance._loadPortletFiles(portletMetaData);
		}
	},

	_getPortletMetaData: function(portlet) {
		var instance = this;

		var portletMetaData = portlet._LFR_portletMetaData;

		if (!portletMetaData) {
			var instanceable = (portlet.attr('instanceable') == 'true');
			var plid = portlet.attr('plid');
			var portletId = portlet.attr('portletId');
			var portletUsed = portlet.is('.lfr-portlet-used');
			var headerPortalCssPaths = (portlet.attr('headerPortalCssPaths') || '').split(',');
            var headerPortletCssPaths = (portlet.attr('headerPortletCssPaths') || '').split(',');
			var footerPortalCssPaths = (portlet.attr('footerPortalCssPaths') || '').split(',');
			var footerPortletCssPaths = (portlet.attr('footerPortletCssPaths') || '').split(',');

			portletMetaData = {
				instanceable: instanceable,
				plid: plid,
				portletId: portletId,
				portletPaths: {
					footer: footerPortletCssPaths,
					header: headerPortletCssPaths
				},
				portalPaths: {
					footer: footerPortalCssPaths,
					header: headerPortalCssPaths
				},
				portletUsed: portletUsed
			}

			portlet._LFR_portletMetaData = portletMetaData;
		}

		return portletMetaData;
	},

	_loadContent: function() {
		var instance = this;

		instance.init();

		Liferay.Util.addInputType();

		Liferay.bind('closePortlet', instance._onPortletClose, instance);

		instance._portletItems = instance._dialog.find('div.lfr-portlet-item');
		var portlets = instance._portletItems;

		portlets.find('a').click(
			function(event) {
				var link = jQuery(this);
				var portlet = link.parents('.lfr-portlet-item:first');

				instance._addPortlet(portlet);
			}
		);

		var zIndex = instance._dialog.parents('.ui-dialog').css('z-index');

		instance._helper = jQuery(Liferay.Template.PORTLET).css('z-index', zIndex + 10);
		instance._helper.addClass('ui-proxy generic-portlet not-intersecting');

		var type = 'Column';
		var appendTo = 'body';

		if (Liferay.Layout.isFreeForm) {
			appendTo = '#column-1';
			type = 'FreeForm';
		}
		else {

			// Let's make sure we have all the columns ready

			if (!instance._sortColumns || !instance._sortableInstance) {
				instance._sortColumns = Liferay.Layout.Columns.sortColumns;
				instance._sortableInstance = instance._sortColumns.data('sortable');
			}

			var sortColumns = instance._sortColumns;
			var sortableInstance = instance._sortableInstance;

			sortableInstance.refresh();

			if (!instance._eventsBound) {
				sortColumns.bind('sortreceive.sortable',
					function(event, ui) {
						if (ui.item.is('.lfr-portlet-item') && ui.sender.is('.lfr-portlet-item') && !sortableInstance.dragging) {
							var placeholder = ui.item;
							var portlet = ui.sender;

							var options = {
								item: placeholder
							};

							instance._addPortlet(portlet, options);

							placeholder.hide();
						}
					}
				);

				sortColumns.bind('sortactivate.sortable',
					function(event) {
						Liferay.Layout.Columns.startDragging();
						sortableInstance.refreshPositions(true);
					}
				);

				sortColumns.bind(
					'sortstart.sortable',
					function(event, ui) {
						if (ui.item.is('.lfr-portlet-item')) {
							ui.placeholder.css(
								{
									height: 200,
									width: 300
								}
							);
						}
					}
				);

				instance._eventsBound = true;
			}
		}

		instance._dragOptions = {
			appendTo: appendTo,
			connectToSortable: '.lfr-portlet-column',
			distance: 2,
			helper: function(event) {
				var helper = instance._helper.clone();
				var title = this.getAttribute('title');

				helper.find('.portlet-title').text(title);

				return helper[0];
			},
			start: function(event, ui) {
				if (instance['_on'+ type +'DragStart']) {
					instance['_on'+ type +'DragStart'](event, ui, this);
				}
			},
			drag: function(event, ui) {
				if (instance['_on'+ type +'Drag']) {
					instance['_on'+ type +'Drag'](event, ui, this);
				}
			},
			stop: function(event, ui) {
				if (instance['_on'+ type +'DragStop']) {
					instance['_on'+ type +'DragStop'](event, ui, this);
				}
			}
		};

		portlets.draggable(instance._dragOptions);

		portlets.filter('.lfr-portlet-used').draggable('disable');

		if (Liferay.Browser.isIe()) {
			portlets.hover(
				function() {
					this.className += ' over';
				},
				function() {
					this.className = this.className.replace('over', '');
				}
			);
		}

		jQuery('.lfr-add-content > h2').click(
			function() {
				var heading = jQuery(this).parent();
				var category = heading.find('> .lfr-content-category');

				category.toggleClass('hidden').toggleClass('visible');
				heading.toggleClass('collapsed').toggleClass('expanded');
			}
		);
	},

	_loadPortletFiles: function(portletMetaData) {
		var instance = this;

		var headerPortalCssPaths = portletMetaData.portalPaths.header;
		var footerPortalCssPaths = portletMetaData.portalPaths.footer;
		var headerPortletCssPaths = portletMetaData.portletPaths.header;
		var footerPortletCssPaths = portletMetaData.portletPaths.footer;

		var head = jQuery('head');
		var docBody = jQuery(document.body);

		var headerCSS = headerPortalCssPaths.concat(headerPortletCssPaths);
		var footerCSS = footerPortalCssPaths.concat(footerPortletCssPaths);

		jQuery.each(
			headerCSS,
			function(i, n) {
				head.prepend('<link href="' + this + '" rel="stylesheet" type="text/css" />');
			}
		);

		if (Liferay.Browser.isIe()) {
			jQuery('body link').appendTo('head');

			jQuery('link.lfr-css-file').each(
				function(i) {
					document.createStyleSheet(this.href);
				}
			);
		}

		jQuery.each(
			footerCSS,
			function(i, n) {
				docBody.append('<link href="' + this + '" rel="stylesheet" type="text/css" />');
			}
		);
	},

	_onColumnDragStop: function(event, ui, obj) {
		var instance = this;

		Liferay.Layout.Columns.stopDragging();
	},

	_onFreeFormDragStart: function(event, ui, obj) {
		var instance = this;

		ui.helper.removeClass('not-intersecting');
	},

	_onFreeFormDragStop: function(event, ui, obj) {
		var instance = this;

		var portlet = jQuery(obj);
		var helper = ui.helper;
		var position = ui.position;

		var dimensions = {
			height: ui.helper.height(),
			position: 'absolute',
			width: ui.helper.width()
		};

		var options = {
			beforePortletLoaded: function(placeHolder) {
				placeHolder = jQuery(placeHolder);
				placeHolder.css(position);
				placeHolder.css(dimensions);
			},
			item: helper,
			onComplete: function(portlet, portletId) {
				jQuery(portlet).css(position);
				Liferay.Layout.FreeForm._moveToTop(portlet);
				Liferay.Layout.FreeForm._savePosition(portlet);
			}
		};

		instance._addPortlet(portlet, options);
	},

	_onPortletClose: function(event, portletData) {
		var instance = this;

		var popup = jQuery('#portal_add_content');
		var item = popup.find('.lfr-portlet-item[plid=' + portletData.plid + '][portletId=' + portletData.portletId + '][instanceable=false]');

		if (item.is('.lfr-portlet-used')) {
			item.removeClass('lfr-portlet-used');
			item.draggable('enable');
		}
	}
};
Liferay.LayoutExporter = {
	all: function(options) {
		options = options || {};

		var pane = options.pane;
		var obj = options.obj;
		var publish = options.publish;

		if (obj.checked) {
			jQuery(pane).hide();

			if (!publish) {
				jQuery('#publishBtn').show();
				jQuery('#selectBtn').hide();
			}
			else {
				jQuery('#changeBtn').hide();
			}
		}
	},

	details: function(options) {
		options = options || {};

		var toggle = options.toggle;
		var detail = options.detail;

		var img = jQuery(toggle)[0];

		if (jQuery(detail).css('display') == 'none') {
			jQuery(detail).slideDown('normal');
			img.src = Liferay.LayoutExporter.icons.minus;
		}
		else {
			jQuery(detail).slideUp('normal');
			img.src = Liferay.LayoutExporter.icons.plus;
		}
	},

	icons: {
		minus: themeDisplay.getPathThemeImages() + '/arrows/01_minus.png',
		plus: themeDisplay.getPathThemeImages() + '/arrows/01_plus.png'
	},

	proposeLayout: function(options) {
		options = options || {};

		var url = options.url;
		var namespace = options.namespace;
		var reviewers = options.reviewers;
		var title = options.title;

		var contents =
			"<div>" +
				"<form action='" + url + "' method='post'>" +
					"<textarea name='" + namespace + "description' style='height: 100px; width: 284px;'></textarea><br /><br />";

		if (reviewers.length > 0) {
			contents += Liferay.Language.get('reviewer') + " <select name='" + namespace + "reviewUserId'>";

			for (var i = 0; i < reviewers.length; i++) {
				contents += "<option value='" + reviewers[i].userId + "'>" + reviewers[i].fullName + "</option>";
			}

			contents += "</select><br /><br />";
		}

		contents +=
					"<input type='submit' value='" + Liferay.Language.get('proceed') + "' />" +
					"<input type='button' value='" + Liferay.Language.get('cancel') + "' onClick='Liferay.Popup.close(this);' />" +
				"</form>" +
			"</div>";

		Liferay.Popup({
			'title': title,
			message: contents,
			noCenter: false,
			modal: true,
			width: 300
		});
	},

	publishToLive: function(options) {
		options = options || {};

		var messageId = options.messageId;
		var url = options.url;
		var title = options.title;

		if (!title) {
			title = Liferay.Language.get(messageId);
		}

		var exportLayoutsPopup = Liferay.Popup(
			{
				title: title,
				modal: true,
				width: 600,
				overflow: 'auto',
				messageId: messageId
			}
		);

		jQuery.ajax(
			{
				url: url,
				success: function(response) {
					jQuery(exportLayoutsPopup).html(response);
				}
			}
		);
	},

	selected: function(options) {
		options = options || {};

		var pane = options.pane;
		var obj = options.obj;
		var publish = options.publish;

		if (obj.checked) {
			jQuery(pane).show();

			if (!publish) {
				jQuery('#publishBtn').hide();
				jQuery('#selectBtn').show();
			}
			else {
				jQuery('#changeBtn').show();
			}
		}
	}
};
Liferay.Notice = new Class({

	/**
	 * OPTIONS
	 *
	 * Required
	 * content {string}: The content of the toolbar.
	 *
	 * Optional
	 * closeText {string}: Use for the "close" button. Set to false to not have a close button.
	 * toggleText {object}: The text to use for the "hide" and "show" button. Set to false to not have a hide button.
	 * noticeClass {string}: A class to add to the notice toolbar.
	 * type {string}: Either 'notice' or 'warning', depending on the type of the toolbar. Defaults to notice.
	 *
	 * Callbacks
	 * onClose {function}: Called when the toolbar is closed.
	 */

	initialize: function(options) {
		var instance = this;
		options = options || {};
		instance._noticeType = options.type || 'notice';
		instance._noticeClass = 'popup-alert-notice';
		instance._useCloseButton = true;
		instance._onClose = options.onClose;
		instance._closeText = options.closeText;
		instance._body = jQuery('body');

		instance._useToggleButton = false;
		instance._hideText = '';
		instance._showText = '';

		if (options.toggleText !== false) {
			instance.toggleText = jQuery.extend(
				{
					hide: null,
					show: null
				},
			options.toggleText);

			instance._useToggleButton = true;
		}

		if (instance._noticeType == 'warning') {
			instance._noticeClass = 'popup-alert-warning';
		}

		if (options.noticeClass) {
			instance._noticeClass += ' ' + options.noticeClass;
		}

		instance._content = options.content || '';

		instance._createHTML();

		return instance._notice;
	},

	setClosing: function() {
		var instance = this;

		var staticAlerts = jQuery('.popup-alert-notice, .popup-alert-warning').not('[dynamic=true]');

		if (staticAlerts.length) {
			instance._useCloseButton = true;
			instance._addCloseButton(staticAlerts);

			if (!instance._body) {
				instance._body = jQuery('body');
			}

			instance._body.addClass('has-alerts')
		}
	},

	_createHTML: function() {
		var instance = this;

		var notice = jQuery('<div class="' + instance._noticeClass + '" dynamic="true"><div class="popup-alert-content"></div></div>');

		notice.html(instance._content);

		instance._addCloseButton(notice);
		instance._addToggleButton(notice);

		instance._body.append(notice);
		instance._body.addClass('has-alerts');

		instance._notice = notice;
	},

	_addCloseButton: function(notice) {
		var instance = this;

		if (instance._closeText !== false) {
			instance._closeText = instance._closeText || Liferay.Language.get('close');
		}
		else {
			instance._useCloseButton = false;
			instance._closeText = '';
		}

		if (instance._useCloseButton) {
			var html = '<input class="submit popup-alert-close" type="submit" value="' + instance._closeText + '" />';

			notice.append(html);

			var closeButton = notice.find('.popup-alert-close');
			closeButton.click(
				function() {
					notice.slideUp('normal',
						function() {
							notice.remove();
							instance._body.removeClass('has-alerts');
						}
					);

					if (instance._onClose) {
						instance._onClose();
					}
				}
			);
		}
	},

	_addToggleButton: function(notice) {
		var instance = this;

		if (instance._useToggleButton) {
			instance._hideText = instance._toggleText.hide || Liferay.Language.get('hide');
			instance._showText = instance._toggleText.show || Liferay.Language.get('show');

			var toggleButton = jQuery('<a class="toggle-button" href="javascript:;"><span>' + instance._hideText + '</span></a>');
			var toggleSpan = toggleButton.find('span');
			var height = 0;

			toggleButton.toggle(
				function() {
					notice.slideUp();
					toggleSpan.text(instance._showText);
				},
				function() {
					notice.slideDown();
					toggleSpan.text(instance._hideText);
				}
			);

			notice.append(toggleButton);
		}
	}
});
Liferay.Navigation = new Class({

	/**
	 * OPTIONS
	 *
	 * Required
	 * hasPermission {boolean}: Whether the current user has permission to modify the navigation
	 * layoutIds {array}: The displayable layout ids.
	 * navBlock {string|object}: A jQuery selector or DOM element of the navigation.
	 */

	initialize: function(options) {
		var instance = this;

		instance.options = options;

		instance._navBlock = jQuery(instance.options.navBlock);

		instance._hasPermission = instance.options.hasPermission;
		instance._isModifiable = instance._navBlock.is('.modify-pages');
		instance._isSortable = instance._navBlock.is('.sort-pages') && instance._hasPermission;
		instance._isUseHandle = instance._navBlock.is('.use-handle');

		instance._updateURL = themeDisplay.getPathMain() + '/layout_management/update_page';

		var items = instance._navBlock.find('> ul > li');

		items.each(
			function(i) {
				this._LFR_layoutId = instance.options.layoutIds[i];
			}
		);

		instance._makeAddable();
		instance._makeDeletable();
		instance._makeSortable();
		instance._makeEditable();

		Liferay.bind('tree', instance._treeCallback, instance);
	},

	_addPage: function(event, obj) {
		var instance = this;

		var navItem = instance._navBlock;
		var addBlock = jQuery('<li>' + instance._enterPage + '</li>');

		var blockInput = addBlock.find('input');

		navItem.find('ul:first').append(addBlock);

		var savePage = addBlock.find('.save-page');
		var cancelPage = addBlock.find('.cancel-page');
		var currentInput = addBlock.find('.enter-page input');

		var pageParents = jQuery(document);

		var pageBlur = function(internalEvent) {
			var currentEl = jQuery(internalEvent.target);
			var liParent = currentEl.parents('ul:eq(0)');

			if ((liParent.length == 0) && !currentEl.is('li') && !currentEl.parents('#add-page').length) {
				cancelPage.trigger('click');
			}
		};

		pageParents.bind('click.liferay', pageBlur);

		cancelPage.click(
			function(event) {
				instance._cancelAddingPage(event, addBlock);
				pageParents.unbind('click.liferay', pageBlur);
			}
		);

		savePage.click(
			function(event) {
				instance._savePage(event, this);
				pageParents.unbind('click.liferay', pageBlur);
			}
		);

		currentInput.keyup(
			function(event) {
				if (event.keyCode == 13) {
					savePage.trigger('click');
				}
				else if (event.keyCode == 27) {
					cancelPage.trigger('click');
				}
				else {
					return;
				}

				pageParents.unbind('click.liferay', pageBlur);
			}
		);
	},

	_cancelAddingPage: function(event, obj) {
		var instance = this;
		obj.remove();
	},

	_cancelPage: function(event, obj, oldName) {
		var navItem = null;

		if (oldName) {
			navItem = jQuery(obj).parents('li');

			var enterPage = navItem.find('.enter-page');

			enterPage.prev().show();
			enterPage.remove();
		}
		else {
			navItem = jQuery(this).parents('li');

			navItem.remove();
		}
	},

	_deleteButton: function(obj) {
		var instance = this;

		obj.append('<span class="delete-tab">X</span>');

		var deleteTab = obj.find('.delete-tab');

		deleteTab.click(
			function(event) {
				instance._removePage(this);
			}
		);

		deleteTab.hide();

		obj.hover(
			function() {
				jQuery(this).find('.delete-tab').fadeIn('fast');
			},
			function() {
				jQuery(this).find('.delete-tab').fadeOut('fast');
			}
		);
	},

	_makeAddable: function() {
		var instance = this;

		if (instance._isModifiable) {
			var navList = instance._navBlock.find('ul:first');

			instance._enterPage =
				'<div class="enter-page">' +
				'<input class="lfr-auto-focus" type="text" name="new_page" value="" class="text" />' +
				'<a class="cancel-page" href="javascript: ;"></a>' +
				'<a class="save-page" href="javascript: ;">' + Liferay.Language.get('save') + '</a>' +
				'</div>';

			if (instance._hasPermission) {
				navList.after(
					'<div id="add-page">' +
					'<a href="javascript:;">' +
					'<span>' + Liferay.Language.get('add-page') + '</span>' +
					'</a>' +
					'</div>');

				var addPage = navList.parent().find('#add-page a');

				addPage.click(
					function(event) {
						instance._addPage(event, this);
					}
				);
			}
		}
	},

	_makeDeletable: function() {
		var instance = this;

		if (instance._isModifiable && instance._hasPermission) {
			var navItems = instance._navBlock.find('li').not('.selected');

			instance._deleteButton(navItems);
		}
	},

	_makeEditable: function() {
		var instance = this;

		if (instance._isModifiable) {
			var currentItem = instance._navBlock.find('li.selected');
			var currentLink = currentItem.find('a');
			var currentSpan = currentLink.find('span');

			currentLink.click(
				function(event) {
					if (event.shiftKey) {
						return false;
					}
				}
			);

			var resetCursor = function() {
				currentSpan.css('cursor', 'pointer');
			};

			currentLink.hover(
				function(event) {
					if (!themeDisplay.isStateMaximized() || event.shiftKey) {
						currentSpan.css('cursor', 'text');
					}
				},
				resetCursor
			);

			currentSpan.click(
				function(event) {
					if (themeDisplay.isStateMaximized() && !event.shiftKey) {
						return;
					}

					var span = jQuery(this);
					var text = span.text();

					span.parent().hide();
					span.parent().after(instance._enterPage);

					var enterPage = span.parent().next();

					var pageParents = enterPage.parents();

					var enterPageInput = enterPage.find('input');

					var pageBlur = function(event) {
						event.stopPropagation();

						if (!jQuery(this).is('li')) {
							cancelPage.trigger('click');
						}

						return false;
					};

					enterPageInput.val(text);

					enterPageInput.trigger('select');

					var savePage = enterPage.find('.save-page');

					savePage.click(
						function(event) {
							instance._savePage(event, this, text);
							pageParents.unbind('blur.liferay', pageBlur);
							pageParents.unbind('click.liferay', pageBlur);
						}
					);

					var cancelPage = enterPage.find('.cancel-page');

					cancelPage.hide();

					cancelPage.click(
						function(event) {
							instance._cancelPage(event, this, text);
							pageParents.unbind('blur.liferay', pageBlur);
							pageParents.unbind('click.liferay', pageBlur);
						}
					);

					enterPageInput.keyup(
						function(event) {
							if (event.keyCode == 13) {
								savePage.trigger('click');
								pageParents.unbind('blur.liferay', pageBlur);
								pageParents.unbind('click.liferay', pageBlur);
							}
							else if (event.keyCode == 27) {
								cancelPage.trigger('click');
								pageParents.unbind('blur.liferay', pageBlur);
								pageParents.unbind('click.liferay', pageBlur);
							}
						}
					);

					pageParents.bind('click.liferay', pageBlur);

					resetCursor();

					return false;
				}
			);
		}
	},

	_makeSortable: function() {
		var instance = this;

		var navBlock = instance._navBlock;
		var navList = navBlock.find('ul:first');

		if (instance._isSortable) {
			var items = navList.find('li');
			var anchors = items.find('a');

			if (instance._isUseHandle) {
				items.append('<span class="sort-handle">+</span>');
			}
			else {
				anchors.css('cursor', 'move');
				anchors.find('span').css('cursor', 'pointer');
			}

			items.addClass('sortable-item');

			instance.sortable = navList.sortable(
				{
					items: '.sortable-item',
					placeholder: 'navigation-sort-helper',
					handle: (instance._isUseHandle ? '.sort-handle' : 'a'),
					opacity: 0.8,
					revert:	false,
					tolerance: 'pointer',
					distance: 5,
					stop: function(event, ui) {
						instance._saveSortables(ui.item[0]);

						Liferay.trigger('navigation',
							{
								item: ui.item[0],
								type: 'sort'
							}
						);
					}
				}
			);
		}
	},

	_removePage: function(obj) {
		var instance = this;

		var tab = jQuery(obj).parents('li');
		var tabText = tab.find('a span').html();

		if (confirm(Liferay.Language.get('are-you-sure-you-want-to-delete-this-page'))) {
			var data = {
				doAsUserId: themeDisplay.getDoAsUserIdEncoded(),
				cmd: 'delete',
				groupId: themeDisplay.getScopeGroupId(),
				privateLayout: themeDisplay.isPrivateLayout(),
				layoutId: tab[0]._LFR_layoutId
			};

			jQuery.ajax(
				{
					data: data,
					success: function() {
						Liferay.trigger('navigation',
							{
								item: tab,
								type: 'delete'
							}
						);

						tab.remove();
					},
					url: instance._updateURL
				}
			);
		}
	},

	_savePage: function(event, obj, oldName) {
		var instance = this;

		if ((event.type == 'keyup') && (event.keyCode !== 13)) {
			return;
		}

		var data = null;
		var onSuccess = null;

		var newNavItem = jQuery(obj).parents('li');
		var name = newNavItem.find('input').val();
		var enterPage = newNavItem.find('.enter-page');

		if (oldName) {

			// Updating an existing page

			if (name != oldName) {
				data = {
					doAsUserId: themeDisplay.getDoAsUserIdEncoded(),
					cmd: 'name',
					groupId: themeDisplay.getScopeGroupId(),
					privateLayout: themeDisplay.isPrivateLayout(),
					layoutId: themeDisplay.getLayoutId(),
					name: name,
					languageId: themeDisplay.getLanguageId()
				};

				onSuccess = function(data) {
					var currentTab = enterPage.prev();
					var currentSpan = currentTab.find('span');

					currentSpan.text(name);
					currentTab.show();

					enterPage.remove();

					var oldTitle = jQuery(document).attr('title');

					var regex = new RegExp(oldName, 'g');

					newTitle = oldTitle.replace(regex, name);

					jQuery(document).attr('title', newTitle);
				}
			}
			else {

				// The new name is the same as the old one

				var currentTab = enterPage.prev();

				currentTab.show();
				enterPage.remove();

				return false;
			}
		}
		else {

			// Adding a new page

			data = {
				mainPath: themeDisplay.getPathMain(),
				doAsUserId: themeDisplay.getDoAsUserIdEncoded(),
				cmd: 'add',
				groupId: themeDisplay.getScopeGroupId(),
				privateLayout: themeDisplay.isPrivateLayout(),
				parentLayoutId: themeDisplay.getParentLayoutId(),
				name: name
			};

			onSuccess = function(data) {
				var newTab = jQuery('<a href="' + data.url + '"><span>' + name + '</span></a>');

				if (instance._isUseHandle) {
					enterPage.before('<span class="sort-handle">+</span>');
				}
				else {
					newTab.css('cursor', 'move');
				}

				newNavItem[0]._LFR_layoutId = data.layoutId;

				enterPage.before(newTab);
				enterPage.remove();

				newNavItem.addClass('sortable-item');

				instance.sortable.sortable('refresh');
				instance._deleteButton(newNavItem);

				Liferay.trigger('navigation',
					{
						item: newNavItem,
						type: 'add'
					}
				)
			}
		}

		jQuery.ajax(
			{
				data: data,
				dataType: 'json',
				success: onSuccess,
				url: instance._updateURL
			}
		);
	},

	_saveSortables: function(obj) {
		var instance = this;

		var tabs = jQuery('li', instance._navBlock);

		var data = {
			doAsUserId: themeDisplay.getDoAsUserIdEncoded(),
			cmd: 'priority',
			groupId: themeDisplay.getScopeGroupId(),
			privateLayout: themeDisplay.isPrivateLayout(),
			layoutId: obj._LFR_layoutId,
			priority: tabs.index(obj)
		};

		jQuery.ajax(
			{
				data: data,
				url: instance._updateURL
			}
		);
	},

	_treeCallback: function(event, data) {
		var instance = this;

		var navigation = instance._navBlock.find('> ul');
		var droppedItem = jQuery(data.droppedItem);
		var dropTarget = jQuery(data.dropTarget);

		if (instance._isSortable) {
			var liItems = navigation.find('> li');

			var tree = droppedItem.parent();
			var droppedName = droppedItem.find('span:first').text();
			var newParent = dropTarget.parents('li:first');

			var liChild = liItems.find('span').not('.delete-tab');

			liChild = liChild.filter(
				function() {
					var currentItem = jQuery(this);

					if (currentItem.text() == droppedName) {
						return true;
					}
					else {
						return false;
					}
				}
			);

			var treeItems = tree.find('> li');

			var newIndex = treeItems.index(droppedItem);

			if (liChild.length > 0) {
				var newSibling = liItems.eq(newIndex);
				var parentLi = liChild.parents('li:first');

				if (!newParent.is('.tree-item')) {
					newSibling.after(parentLi);

					if (parentLi.is(':hidden')) {
						parentLi.show();
					}
				}
				else {

					//TODO: add parsing to move child elements around by their layoutId

					parentLi.hide();
				}
			}
			else if (!newParent.is('.tree-item')) {
				var newTab = liItems.slice(0, 1).clone();

				newTab.removeClass('selected');
				newTab.find('.child-menu').remove();

				var newTabLink = newTab.find('a span');

				newTabLink.text(droppedName);
				newTabLink.css('cursor', 'pointer');

				liItems.parent().append(newTab);
			}
		}
	},

	_enterPage: '',
	_updateURL: ''
});
Liferay.Panel = Liferay.Observable.extend({

	/**
	 * OPTIONS
	 *
	 * Optional
	 * container {string|object}: A jQuery selector of the panel container if there are multiple panels handled by this one.
	 * panel {string|object}: A jQuery selector of the panel.
	 * panelContent {string|object}: A jQuery selector of the content section of the panel.
	 * header {string|object}: A jQuery selector of the panel's header area.
	 * titles {string|object}: A jQuery selector of the titles in the panel.
	 * footer {string|object}: A jQuery selector of the panel's footer area.
	 * accordion {boolean}: Whether or not the panels have accordion behavior (meaning only one panel can be open at a time).
	 * collapsible {boolean}: Whether or not the panel can be collapsed by clicking the title.
	 *
	 */

	initialize: function(options) {
		var instance = this;

		var defaults = {
			container: null,
			panel: '.lfr-panel',
			panelContent: '.lfr-panel-content',
			header: '.lfr-panel-header',
			titles: '.lfr-panel-titlebar',
			footer: '.lfr-panel-footer',
			accordion: false,
			collapsible: true,
			persistState: false
		};

		options = jQuery.extend(defaults, options);

		instance._inContainer = false;
		instance._container = jQuery(document.body);

		if (options.container) {
			instance._container = jQuery(options.container);
			instance._inContainer = true;
		}

		instance._panel = instance._container.find(options.panel);

		instance._panelContent = instance._panel.find(options.panelContent);
		instance._header = instance._panel.find(options.header);
		instance._footer = instance._panel.find(options.footer);
		instance._panelTitles = instance._panel.find(options.titles);
		instance._accordion = options.accordion;

		instance._collapsible = options.collapsible;
		instance._persistState = options.persistState;

		if (instance._collapsible) {
			instance.makeCollapsible();

			instance._panelTitles.disableSelection();
			instance._panelTitles.css(
				{
					cursor: 'pointer'
				}
			);

			var collapsedPanels = instance._panel.filter('.lfr-collapsed');

			if (instance._accordion && !collapsedPanels.length) {
				instance._panel.slice(1).addClass('lfr-collapsed');
			}
		}

		instance.set('container', instance._container);
		instance.set('panel', instance._panel);
		instance.set('panelContent', instance._panelContent);
		instance.set('panelTitles', instance._panelTitles);
	},

	makeCollapsible: function() {
		var instance = this;

		instance._panelTitles.each(
			function(i, n) {
				var title = jQuery(this);
				var panel = title.parents('.lfr-panel:first');

				if (panel.hasClass('lfr-extended')) {
					var toggler = title.find('.lfr-panel-button');

					if (!toggler.length) {
						title.append('<a class="lfr-panel-button" href="javascript: ;"></a>');
					}
				}
			}
		);

		instance._panelTitles.mousedown(
			function(event) {
				instance.onTitleClick(this);
			}
		);
	},

	onTitleClick: function(el) {
		var instance = this;

		var currentContainer = jQuery(el).parents('.lfr-panel');

		currentContainer.toggleClass('lfr-collapsed');

		if (instance._accordion) {
			var siblings = currentContainer.siblings('.lfr-panel');

			siblings.each(
				function (i, n) {
					if (this.id) {
						instance._saveState(this.id, 'closed');
					}

					jQuery(this).addClass('lfr-collapsed');
				}
			);
		}

		var panelId = currentContainer.attr('id');
		var state = 'open';

		if (currentContainer.hasClass('lfr-collapsed')) {
			state = 'closed';
		}

		instance._saveState(panelId, state);

		instance.trigger('titleClick');
	},

	_saveState: function (id, state) {
		var instance = this;

		if (instance._persistState) {
			var data = {};

			data[id] = state;

			jQuery.ajax(
				{
					url: themeDisplay.getPathMain() + '/portal/session_click',
					data: data
				}
			);
		}
	}
});

jQuery.extend(
	Liferay.Panel,
	{
		get: function(id) {
			var instance = this;

			return instance[instance._prefix + id];
		},

		register: function(id, panel) {
			var instance = this;

			instance[instance._prefix + id] = panel;
		},

		_prefix: '__'
	}
);
Liferay.PanelFloating = Liferay.Panel.extend({

	/**
	 * OPTIONS
	 *
	 * Also inherits all configuration options from Liferay.Panel
	 *
	 * Optional
	 * trigger {string|object}: A jQuery selector of the element that triggers the opening of the floating panel.
	 * paging {boolean}: Whether or not to add pagination to the panel.
	 * pagingElements {string}: A jQuery selector of the elements that make up each "page".
	 * resultsPerPage {number}: The number of results to show per page.
	 * width {number}: The width of the panel.
	 *
	 */

	initialize: function(options) {
		var instance = this;

		var defaults = {
			trigger: '.lfr-trigger',
			paging: false,
			pagingElements: 'ul',
			resultsPerPage: 1,
			width: 300
		};

		options = jQuery.extend(defaults, options);

		instance._paging = options.paging;
		instance._pagingElements = options.pagingElements;
		instance._trigger = jQuery(options.trigger);
		instance._containerWidth = options.width;

		instance.parent(options);

		if (!instance._inContainer) {
			instance._container = jQuery('<div class="lfr-floating-container"></div>');

			instance._panel.eq(0).before(instance._container);
			instance._container.append(instance._panel);

			instance._inContainer = true;
		}

		instance.paginate(instance._container.find('.lfr-panel-content'));

		instance._trigger.addClass('lfr-floating-trigger');

		instance._trigger.click(
			function(event) {
				instance.onTriggerClick(this);

				jQuery(document).bind(
					'click.liferay-panel',
					function(event) {
						var target = jQuery(event.target);

						if (!target.is('.lfr-panel') && !target.parents('.lfr-position-helper').length) {

							instance.onOuterClick(this);
							jQuery(this).unbind('click.liferay-panel', arguments.callee);
						}
					}
				);

				return false;
			}
		);

		instance.set('trigger', instance._trigger);
	},

	hide: function() {
		var instance = this;

		instance._container.detachPositionHelper();

		instance._trigger.removeClass('lfr-trigger-selected');

		instance.trigger('hide');
	},

	onOuterClick: function() {
		var instance = this;

		instance.hide();

		instance.trigger('outerClick');
	},

	onTitleClick: function(el) {
		var instance = this;

		instance.parent(el);

		var currentContainer = jQuery(el).parents('.lfr-panel');
		var sets = currentContainer.find('ul');

		if (!sets.filter('.current-set').length) {
			sets.filter(':first').addClass('current-set');
		}

		instance.paginate(currentContainer);
	},

	onTriggerClick: function(trigger) {
		var instance = this;

		var panelHidden = instance._container.is(':hidden');

		if (panelHidden) {
			instance.show(trigger);
		}
		else {
			instance.hide(trigger);
		}

		instance.trigger('triggerClick');
	},

	paginate: function(currentPanelContent) {
		var instance = this;

		if (!currentPanelContent) {
			currentPanelContent = instance._container.find('.lfr-panel-open .lfr-panel-content');
		}

		if (instance._paging) {
			instance._container.addClass('lfr-panel-paging');

			currentPanelContent.each(
				function(i, n) {
					var currentPanelContent = jQuery(this);

					if (currentPanelContent.data('paginated') != true) {
						var sets = currentPanelContent.find('>' + instance._pagingElements);
						var totalPages = sets.length;

						var currentSet = sets.filter('.current-set');

						var pageNumber = 1;

						if (!currentSet.length) {
							currentSet = sets.eq(0);
							currentSet.addClass('current-set');
						}
						else {
							pageNumber = sets.index(currentSet[0]) + 1;
						}

						currentPanelContent.data('currentPageSet', currentSet);
						currentPanelContent.data('currentPageNumber', pageNumber);

						if (totalPages > 1) {
							var pagingContainer = jQuery('<div class="lfr-component lfr-paging-container"><ul class="lfr-paging-pages"></ul></div>');

							var pagingList = pagingContainer.find('.lfr-paging-pages');

							var listItems = ['<li class="lfr-page lfr-page-previous"><a href="javascript: ;">&laquo;</a></li>'];

							for (var i=1; i <= totalPages; i++) {
								var cssClass = '';

								if (i == 1) {
									cssClass = 'lfr-page-current';
								}

								listItems.push('<li class="lfr-page ' + cssClass + '" data-page="' + i + '"><a href="javascript: ;">' + i + '</a></li>');
							}

							listItems.push('<li class="lfr-page lfr-page-next"><a href="javascript: ;">&raquo;</a></li>');

							pagingList.append(listItems.join(''));

							var goToPage = function(pageNumber) {
								var currentSet = sets.eq(pageNumber - 1);

								if (currentSet.length) {
									sets.removeClass('current-set');
									currentSet.addClass('current-set');

									pagingList.find('.lfr-page').each(
										function(i) {
											var className = this.className || '';

											if (className.indexOf('lfr-page-current') > -1) {
												jQuery(this).removeClass('lfr-page-current');
											}
											else {
												if (className.indexOf('lfr-page-previous') < 0 &&
													className.indexOf('lfr-page-next') < 0 &&
													i == pageNumber) {

													jQuery(this).addClass('lfr-page-current');
												}
											}
										}
									);

									currentPanelContent.data('currentPageSet', currentSet);
									currentPanelContent.data('currentPageNumber', pageNumber);
								}
							};

							pagingContainer.attr('data-currentPageNumber', 1);

							pagingContainer.click(
								function(event) {
									var container = jQuery(this);
									var target = jQuery(event.target);

									if (target.is('.lfr-page') || (target = target.parents('.lfr-page')).length) {
										var pageNumber = target.attr('data-page');

										if (!pageNumber) {
											var currentPageNumber = currentPanelContent.data('currentPageNumber');
											currentPageNumber = parseInt(currentPageNumber);

											if (isNaN(currentPageNumber) || currentPageNumber == 0) {
												currentPageNumber = 1;
											}

											if (target.is('.lfr-page-next')) {
												currentPageNumber += 1;
											}
											else if (target.is('.lfr-page-previous')) {
												currentPageNumber -= 1;
											}

											pageNumber = currentPageNumber;
										}

										if (!target.is('.lfr-page-current')) {
											goToPage(pageNumber);
										}
									}
								}
							);

							currentPanelContent.append(pagingContainer);

							currentPanelContent.data('paginated', true);
						}
					}
				}
			);
		}
	},

	position: function(trigger) {
		var instance = this;

		instance._container.alignTo(trigger);
	},

	show: function(trigger) {
		var instance = this;

		instance._container.width(instance._containerWidth);
		instance._container.show();

		instance.position(trigger);

		instance._trigger.addClass('lfr-trigger-selected');

		if (instance._paging) {
			instance._setMaxPageHeight();
		}

		instance.trigger('show');
	},

	_setMaxPageHeight: function() {
		var instance = this;

		var sets = instance._container.find('.lfr-panel:not(.lfr-collapsed)');

		var maxHeight = 0;

		var panelContent = sets.find('.lfr-panel-content');
		var pages = panelContent.find('>' + instance._pagingElements);

		pages.each(
			function(i, n) {
				var setHeight = jQuery(this).height();

				if (setHeight > maxHeight) {
					maxHeight = setHeight;
				}
			}
		);

		pages.height(maxHeight);
	}
});
Liferay.SearchContainer = new Class({
	initialize: function(options) {
		var instance = this;

		instance._id = options.id || '';
		instance._container = jQuery('#' + instance._id + 'SearchContainer');
		instance._dataStore = jQuery('#' + instance._id + 'PrimaryKeys');

		instance._table = instance._container.find('table');

		instance._table.attr('data-searchContainerId', instance._id);

		Liferay.SearchContainer.register(instance._id, instance);

		var initialIds = instance._dataStore.val();

		if (initialIds) {
			initialIds = initialIds.split(',');

			instance.updateDataStore(initialIds);
		}
	},

	addRow: function(arr, id) {
		var instance = this;

		if (id) {
			var row = instance._table.find('.lfr-template').clone();
			var cells = row.find('> td');

			cells.empty();

			jQuery.each(
				arr,
				function(i, n) {
					if (cells[i]) {
						cells.eq(i).html(n);
					}
				}
			);

			instance._table.append(row);

			row.removeClass('lfr-template');

			instance._ids.push(id);
		}

		instance.updateDataStore();

		instance.trigger('addRow', {ids: instance._ids, rowData: arr});
	},

	bind: function(event, func) {
		var instance = this;

		instance._container.bind(event, func);
	},

	deleteRow: function(obj, id) {
		var instance = this;

		if (typeof obj == 'number' || typeof obj == 'string') {
			obj = instance._table.find('tr').not('.lfr-template').eq(obj);
		}
		else if (obj.nodeName) {
			obj = jQuery(obj);
		}
		else if (obj.jquery) {
			obj = obj;
		}

		if (id) {
			var pos = instance._ids.indexOf(id.toString());

			if (pos > -1) {
				instance._ids.splice(pos, 1);
				instance.updateDataStore();
			}
		}

		instance.trigger('deleteRow', {ids: instance._ids, row: obj});

		if (!obj.is('tr')) {
			obj = obj.parents('tr:first');
		}

		obj.remove();
	},

	getData: function(toArray) {
		var instance = this;

		var ids = instance._ids;

		if (!toArray) {
			ids = ids.join(',');
		}

		return ids;
	},

	updateDataStore: function(ids) {
		var instance = this;

		if (ids) {
			if (typeof ids == 'string') {
				ids = ids.split(',');
			}

			instance._ids = ids;
		}

		instance._dataStore.val(instance._ids.join(','));
	},

	trigger: function(event, data) {
		var instance = this;

		instance._container.trigger(event, data);
	},

	_ids: []
});

jQuery.extend(
	Liferay.SearchContainer,
	{
		get: function(id) {
			var instance = this;

			var searchContainer = null;

			if (instance._cache[id]) {
				searchContainer = instance._cache[id];
			}
			else {
				searchContainer = new Liferay.SearchContainer(
					{
						id: id
					}
				);
			}

			return searchContainer;
		},

		register: function(id, obj) {
			var instance = this;

			instance._cache[id] = obj;
		},

		_cache: {}
	}
);
Liferay.Session = {
	autoExtend: false,
	sessionTimeout: 0,
	sessionTimeoutWarning: 0,
	redirectOnExpire: false,

	init: function(params) {
		var instance = this;

		params = params || {};
		instance.autoExtend = params.autoExtend || instance.autoExtend;

		instance._timeout = params.timeout || instance.sessionTimeout;
		instance._warning = params.timeoutWarning || instance.sessionTimeoutWarning;

		instance.sessionTimeout = instance._timeout * 60000;
		instance.sessionTimeoutWarning = instance._warning * 60000;
		instance._timeoutDiff = instance.sessionTimeout - instance.sessionTimeoutWarning;

		instance._currentTime = instance.sessionTimeoutWarning;

		instance.redirectOnExpire = params.redirectOnExpire || instance.redirectOnExpire;

		instance._cookieKey = 'LFR_SESSION_STATE_' + themeDisplay.getUserId();

		instance.banner = new jQuery;

		var urlBase = themeDisplay.getPathMain() + '/portal/';

		instance._sessionUrls = {
			expire: urlBase + 'expire_session',
			extend: urlBase + 'extend_session'
		};

		instance._stateCheck = setTimeout(
			function() {
				instance.checkState();
			},
			instance._timeoutDiff);

		var timeoutMinutes = instance._timeout;
		var timeLeft = instance._warning;

		instance._warningText = Liferay.Language.get('warning-your-session-will-expire', ['[$SPAN$]', timeoutMinutes]);
		instance._warningText = instance._warningText.replace(/\[\$SPAN\$\]/, '<span class="countdown-timer"></span>');

		instance._toggleText = {
			hide: Liferay.Language.get('hide'),
			show: Liferay.Language.get('show')
		};

		instance._expiredText = Liferay.Language.get('warning-your-session-has-expired');
		instance._extendText = Liferay.Language.get('extend');

		instance.setCookie();
	},

	checkState: function() {
		var instance = this;

		var currentTime = new Date().getTime();
		var sessionState = instance.getCookie();
		var newWaitTime = instance.sessionTimeoutWarning;
		var timeDiff = 0;

		clearTimeout(instance._stateCheck);

		if (sessionState == 'expired') {
			instance.expire();
		}
		else {
			timeDiff = currentTime - sessionState;

			if (!instance.autoExtend) {
				if ((timeDiff + 100) >= instance.sessionTimeoutWarning) {
						instance.warn();
				}
				else {
					newWaitTime = (instance.sessionTimeoutWarning - timeDiff) + 10000;

					instance._stateCheck = setTimeout(
						function() {
							instance.checkState();
						},
					newWaitTime);
				}
			}
			else {
				instance.extend();
			}
		}
	},

	getCookie: function() {
		var instance = this;

		return jQuery.cookie(instance._cookieKey) || 0;
	},

	expire: function() {
		var instance = this;

		document.title = instance._originalTitle;

		jQuery.ajax(
			{
				url: instance._sessionUrls.expire,
				success: function() {
					if (instance.redirectOnExpire) {
						location.href = themeDisplay.getURLHome();
					}
				}
			}
		);

		instance.setCookie('expired');
	},

	extend: function() {
		var instance = this;

		if (instance._countdownTimer) {
			clearInterval(instance._countdownTimer);
		}

		jQuery.ajax(
			{
				url: instance._sessionUrls.extend
			}
		);

		document.title = instance._originalTitle;

		instance._currentTime = instance.sessionTimeoutWarning;

		clearTimeout(instance._sessionExpired);

		if (instance._sessionWarning) {
			clearTimeout(instance._sessionWarning);
		}

		instance._sessionWarning = setTimeout(
			function() {
				if (!instance.autoExtend) {
					instance.warn();
				}
				else {
					instance.extend();
				}
			},
			instance._timeoutDiff
		);

		instance.setCookie();
	},

	setCookie: function(status) {
		var instance = this;

		var currentTime = new Date().getTime();

		var options = {
			secure : (window.location.protocol.indexOf('https') > -1)
		};

		jQuery.cookie(instance._cookieKey, status || currentTime, options);
	},

	warn: function() {
		var instance = this;

		instance.banner = new Liferay.Notice({
			content: instance._warningText,
			closeText: instance._extendText,
			onClose: function() {
				instance.extend();
			},
			toggleText: false
		});

		instance._counter();

		instance._sessionExpired = setTimeout(
			function() {
				instance.expire();
			},
			instance.sessionTimeoutWarning);
	},

	_counter: function() {
		var instance = this;

		var banner = instance.banner;
		if (banner.length) {
			instance._counterText = banner.find('.countdown-timer');
			instance._originalTitle = document.title;
			var interval = 1000;

			instance._counterText.text(instance._setTime());
			document.title = instance.banner.text();

			instance._countdownTimer = setInterval(
				function() {
					var time = instance._setTime();

					instance._currentTime = instance._currentTime - interval;

					if (instance._currentTime > 0) {
						instance._counterText.text(time);
						document.title = instance.banner.text();
					}
					else {
						instance.banner.html(instance._expiredText);
						instance.banner.toggleClass('popup-alert-notice').toggleClass('popup-alert-warning');

						if (instance._countdownTimer) {
							clearInterval(instance._countdownTimer);
						}
					}
				},
			interval
			);
		}
	},

	_formatNumber: function(num) {
		var instance = this;

		if (!Liferay.Util.isArray(num)) {
			if (num <= 9) {
				num = '0' + num;
			}
		}
		else {
			num = jQuery.map(num, instance._formatNumber);
		}
		return num;
	},

	_setTime: function() {
		var instance = this;

		var amount = instance._currentTime;

		if (amount <= 0) {

		}
		else {
			var days=0, hours=0, minutes=0, seconds=0, output='';

			// Remove the milliseconds
			amount = Math.floor(amount/1000);

			hours = Math.floor(amount/3600);
			amount = amount%3600;

			minutes = Math.floor(amount/60);
			amount = amount%60;

			seconds = Math.floor(amount);

			return instance._formatNumber([hours, minutes, seconds]).join(':');
		}
	},

	_banner: [],
	_currentTime: 0,
	_originalTitle: '',
	_sessionUrls: {},
	_timeout: 0,
	_timeoutDiff: 0,
	_warning: 0
};
Liferay.TagsCategoriesSelector = new Class({

	/**
	 * OPTIONS
	 *
	 * Required
	 * curTagsCategories (string): The current categories.
	 * instanceVar {string}: The instance variable for this class.
	 * hiddenInput {string}: The hidden input used to pass in the current categories.
	 * summarySpan {string}: The summary span to show the current categories.
	 */

	initialize: function(options) {
		var instance = this;

		instance._curTagsCategories = [];

		instance.options = options;
		instance._ns = instance.options.instanceVar || '';
		instance._mainContainer = jQuery('<div class="lfr-tag-select-container"></div>');
		instance._container = jQuery('<div class="lfr-tag-container"></div>');
		instance._searchContainer = jQuery('<div class="lfr-tag-search-container"><input class="lfr-tag-search-input" type="text"/></div>');

		var hiddenInput = jQuery('#' + options.hiddenInput);

		hiddenInput.attr('name', hiddenInput.attr('id'));

		instance._popupVisible = false;

		instance._setupSelectTagsCategories();

		if (options.curTagsCategories != '') {
			instance._curTagsCategories = options.curTagsCategories.split(',');

			instance._update();
		}
	},

	deleteTagCategory: function(id) {
		var instance = this;

		var options = instance.options;
		var curTagsCategories = instance._curTagsCategories;

		jQuery('#' + instance._ns + 'CurTags' + id).remove();

		var value = curTagsCategories.splice(id, 1);

		if (instance._popupVisible) {
			jQuery('input[type=checkbox][value$=' + value + ']', instance.selectTagCategoryPopup).attr('checked', false);
		}

		instance._update();
	},

	_tagCategoryIterator: function(entries, parent, buffer, counter) {
		var instance = this;

		jQuery.each(
			entries,
			function(i) {
				var entry = this;
				var entryName = entry.name;
				var entryId = entry.entryId;
				var checked = (instance._curTagsCategories.indexOf(entryName) > -1) ? ' checked="checked" ' : '';

				buffer.push('<label title="');
				buffer.push(entryName);
				buffer.push('" style="padding: 1px 0 1px ');
				buffer.push(counter * 20);
				buffer.push('px;">');
				buffer.push('<input type="checkbox" value="');
				buffer.push(entryName);
				buffer.push('" ');
				buffer.push(checked);
				buffer.push('> ');
				buffer.push(entryName);
				buffer.push('</label>');

				var childEntries = Liferay.Service.Tags.TagsEntry.getGroupVocabularyEntries(
					{
						groupId: themeDisplay.getScopeGroupId(),
						parentEntryName: entryName,
						vocabularyName: parent
					},
					false
				);

				if (childEntries.length > 0) {
					instance._tagCategoryIterator(childEntries, parent, buffer, counter + 1);
				}
			}
		);

		counter = counter - 1;
	},

	_createPopup: function() {
		var instance = this;

		var ns = instance._ns;
		var container = instance._container;
		var mainContainer = instance._mainContainer;
		var searchContainer = instance._searchContainer;

		var saveBtn = jQuery('<input class="submit lfr-save-button" id="' + ns + 'saveButton" type="submit" value="' + Liferay.Language.get('save') + '" />');

		saveBtn.click(
			function() {
				instance._curTagsCategories = instance._curTagsCategories.length ? instance._curTagsCategories : [];

				container.find('input[type=checkbox]').each(
					function() {
						var currentIndex = instance._curTagsCategories.indexOf(this.value);
						if (this.checked) {
							if (currentIndex == -1) {
								instance._curTagsCategories.push(this.value);
							}
						}
						else {
							if (currentIndex > -1) {
								instance._curTagsCategories.splice(currentIndex, 1);
							}
						}
					}
				);

				instance._update();
				Liferay.Popup.close(instance.selectTagCategoryPopup);
			}
		);

		mainContainer.append(searchContainer).append(container).append(saveBtn);

		if (!instance.selectTagCategoryPopup) {
			var popup = Liferay.Popup(
				{
					className: 'lfr-tag-selector',
					message: mainContainer[0],
					modal: false,
					position: 'center',
					resizable: false,
					title: Liferay.Language.get('categories'),
					width: 400,
					open: function() {
 						var inputSearch = jQuery('.lfr-tag-search-input');

						Liferay.Util.defaultValue(inputSearch, Liferay.Language.get('search'));
					},
					onClose: function() {
						instance._popupVisible = false;
						instance.selectTagCategoryPopup = null;
					}
				}
			);
			instance.selectTagCategoryPopup = popup;
		}
		instance._popupVisible = true;

		if (Liferay.Browser.isIe()) {
			jQuery('.lfr-label-text', popup).click(
				function() {
					var input = jQuery(this.previousSibling);
					var checkedState = !input.is(':checked');
					input.attr('checked', checkedState);
				}
			);
		}
	},

	_initializeSearch: function(container) {
		var data = function() {
			var value = jQuery(this).attr('title');

			return value.toLowerCase();
		};

		var inputSearch = jQuery('.lfr-tag-search-input');

		var options = {
			data: data,
			list: '.lfr-tag-container label',
			after: function() {
				jQuery('fieldset', container).each(
					function() {
						var fieldset = jQuery(this);

						var visibleEntries = fieldset.find('label:visible');

						if (visibleEntries.length == 0) {
							fieldset.addClass('no-matches');
						}
						else {
							fieldset.removeClass('no-matches');
						}
					}
				);
			}
		};

		inputSearch.liveSearch(options);
	},

	_setupSelectTagsCategories: function() {
		var instance = this;

		var options = instance.options;
		var ns = instance._ns;

		var input = jQuery('#' + ns + 'selectTagsCategories');

		input.click(
			function() {
				instance._showSelectPopup();
			}
		);
	},

	_showSelectPopup: function() {
		var instance = this;

		var options = instance.options;
		var ns = instance._ns;
		var mainContainer = instance._mainContainer;
		var container = instance._container;
		var searchMessage = Liferay.Language.get('no-categories-found');

		mainContainer.empty();

		container.empty().html('<div class="loading-animation" />');

		Liferay.Service.Tags.TagsVocabulary.getGroupVocabularies(
			{
				groupId: themeDisplay.getScopeGroupId(),
				folksonomy: false
			},
			function(vocabularies) {
				var buffer = [];

				if (vocabularies.length == 0) {
					buffer.push('<fieldset class="no-matches"><legend>' + Liferay.Language.get('category-sets') + '</legend>');
					buffer.push('<div class="lfr-tag-message">' + searchMessage + '</div>');
					buffer.push('</fieldset>');

					container.html(buffer.join(''));
				}
				else {
					jQuery.each(
						vocabularies,
						function(i) {
							var tagCategorySet = this;
							var tagCategorySetName = tagCategorySet.name;
							var tagCategorySetId = tagCategorySet.groupId;

							Liferay.Service.Tags.TagsEntry.getGroupVocabularyRootEntries(
								{
									groupId: tagCategorySetId,
									name: tagCategorySetName
								},
								function(entries) {
									buffer.push('<fieldset>');
									buffer.push('<legend class="lfr-tag-set-title">');
									buffer.push(tagCategorySetName);
									buffer.push('</legend>');

									instance._tagCategoryIterator(entries, tagCategorySetName, buffer, 0);

									buffer.push('<div class="lfr-tag-message">' + searchMessage + '</div>');
									buffer.push('</fieldset>');

									container.html(buffer.join(''));

									instance._initializeSearch(container);
								}
							);
						}
					);
				}
			}
		);

		instance._createPopup();
	},

	_update: function() {
		var instance = this;

		instance._updateHiddenInput();
		instance._updateSummarySpan();
	},

	_updateHiddenInput: function() {
		var instance = this;

		var options = instance.options;
		var curTagsCategories = instance._curTagsCategories;

		var hiddenInput = jQuery('#' + options.hiddenInput);

		hiddenInput.val(curTagsCategories.join(','));
	},

	_updateSummarySpan: function() {
		var instance = this;

		var options = instance.options;
		var curTagsCategories = instance._curTagsCategories;

		var html = '';

		jQuery(curTagsCategories).each(
			function(i, curTagCategory) {
				html += '<span class="ui-tag" id="' + instance._ns + 'CurTags' + i + '">';
				html += curTagCategory;
				html += '<a class="ui-tag-delete" href="javascript: ' + instance._ns + '.deleteTagCategory(' + i + ');"><span>x</span></a>';
				html += '</span>';
			}
		);

		var tagsCategoriesSummary = jQuery('#' + options.summarySpan);

		if (curTagsCategories.length) {
			tagsCategoriesSummary.removeClass('empty');
		}
		else {
			tagsCategoriesSummary.addClass('empty');
		}

		tagsCategoriesSummary.html(html);
	}
});
Liferay.TagsEntriesSelector = new Class({

	/**
	 * OPTIONS
	 *
	 * Required
	 * curTagsEntries (string): The current tag entries.
	 * instanceVar {string}: The instance variable for this class.
	 * hiddenInput {string}: The hidden input used to pass in the current tag entries.
	 * textInput {string}: The text input for users to add tag entries.
	 * summarySpan {string}: The summary span to show the current tag entries.
	 *
	 * Optional
	 * focus {boolean}: Whether the text input should be focused.
	 *
	 * Callbacks
	 * contentCallback {function}: Called to get suggested tag entries.
	 */

	initialize: function(options) {
		var instance = this;

		instance._curTagsEntries = [];

		instance.options = options;
		instance._ns = instance.options.instanceVar || '';
		instance._mainContainer = jQuery('<div class="lfr-tag-select-container"></div>');
		instance._container = jQuery('<div class="lfr-tag-container"></div>');
		instance._searchContainer = jQuery('<div class="lfr-tag-search-container"><input class="lfr-tag-search-input" type="text"/></div>');

		var hiddenInput = jQuery('#' + options.hiddenInput);

		hiddenInput.attr('name', hiddenInput.attr('id'));

		var textInput = jQuery('#' + options.textInput);

		textInput.autocomplete(
			{
				source: instance._getTagsEntries,
				width: textInput.width() + 20,
				formatItem: function(row, i, max, term) {
					return row;
				},
				dataType: 'json',
				delay: 0,
				multiple: true,
				mutipleSeparator: ',',
				minChars: 1,
				hide: function(event, ui) {
					jQuery(this).removeClass('showing-list');
				},
				show: function(event, ui) {
					jQuery(this).addClass('showing-list');
					this._LFR_listShowing = true;
				},
				result: function(event, ui) {
					var caretPos = this.value.length;

					if (this.createTextRange) {
						var textRange = this.createTextRange();

						textRange.moveStart('character', caretPos);
						textRange.select();
					}
					else if (this.selectionStart) {
						this.selectionStart = caretPos;
						this.selectionEnd = caretPos;
					}
				}
			}
		);

		instance._popupVisible = false;

		instance._setupSelectTagsEntries();
		instance._setupSuggestions();

		var addTagEntryButton = jQuery('#' + options.instanceVar + 'addTag');

		addTagEntryButton.click(
			function() {
					var curTagsEntries = instance._curTagsEntries;
					var newTagsEntries = textInput.val().split(',');

					jQuery.each(
						newTagsEntries,
						function(i, n) {
							n = jQuery.trim(n);

							if (curTagsEntries.indexOf(n) == -1) {
								if (n != '') {
									curTagsEntries.push(n);

									if (instance._popupVisible) {
										jQuery('input[type=checkbox][value$=' + n + ']', instance.selectTagEntryPopup).attr('checked', true);
									}
								}
							}
						}
					);

					curTagsEntries = curTagsEntries.sort();
					textInput.val('');

					instance._update();
				}
		);

		textInput.keypress(
			function(event) {
				if (event.keyCode == 13) {
					if (!this._LFR_listShowing) {
						addTagEntryButton.trigger('click');
					}

					this._LFR_listShowing = null;

					return false;
				}
			}
		);

		if (options.focus) {
			textInput.focus();
		}

		if (options.curTagsEntries != '') {
			instance._curTagsEntries = options.curTagsEntries.split(',');

			instance._update();
		}

		Liferay.Util.actsAsAspect(window);

		window.before(
			'submitForm',
			function() {
				var val = jQuery.trim(textInput.val());

				if (val.length) {
					addTagEntryButton.trigger('click');
				}
			}
		);
	},

	deleteTagEntry: function(id) {
		var instance = this;

		var options = instance.options;
		var curTagsEntries = instance._curTagsEntries;

		jQuery('#' + instance._ns + 'CurTags' + id).remove();

		var value = curTagsEntries.splice(id, 1);

		if (instance._popupVisible) {
			jQuery('input[type=checkbox][value$=' + value + ']', instance.selectTagEntryPopup).attr('checked', false);
		}

		instance._update();
	},

	_createPopup: function() {
		var instance = this;

		var ns = instance._ns;
		var container = instance._container;
		var mainContainer = instance._mainContainer;
		var searchContainer = instance._searchContainer;

		var saveBtn = jQuery('<input class="submit lfr-save-button" id="' + ns + 'saveButton" type="submit" value="' + Liferay.Language.get('save') + '" />');

		saveBtn.click(
			function() {
				instance._curTagsEntries = instance._curTagsEntries.length ? instance._curTagsEntries : [];

				container.find('input[type=checkbox]').each(
					function() {
						var currentIndex = instance._curTagsEntries.indexOf(this.value);
						if (this.checked) {
							if (currentIndex == -1) {
								instance._curTagsEntries.push(this.value);
							}
						}
						else {
							if (currentIndex > -1) {
								instance._curTagsEntries.splice(currentIndex, 1);
							}
						}
					}
				);

				instance._update();
				Liferay.Popup.close(instance.selectTagEntryPopup);
			}
		);

		mainContainer.append(searchContainer).append(container).append(saveBtn);

		if (!instance.selectTagEntryPopup) {
			var popup = Liferay.Popup(
				{
					className: 'lfr-tag-selector',
					message: mainContainer[0],
					modal: false,
					position: 'center',
					resizable: false,
					title: Liferay.Language.get('tags'),
					width: 400,
					open: function() {
 						var inputSearch = jQuery('.lfr-tag-search-input');

						Liferay.Util.defaultValue(inputSearch, Liferay.Language.get('search'));
					},
					onClose: function() {
						instance._popupVisible = false;
						instance.selectTagEntryPopup = null;
					}
				}
			);
			instance.selectTagEntryPopup = popup;
		}
		instance._popupVisible = true;

		if (Liferay.Browser.isIe()) {
			jQuery('.lfr-label-text', popup).click(
				function() {
					var input = jQuery(this.previousSibling);
					var checkedState = !input.is(':checked');
					input.attr('checked', checkedState);
				}
			);
		}
	},

	_getTagsEntries: function(term) {
		var beginning = 0;
		var end = 20;

		var data = Liferay.Service.Tags.TagsEntry.search(
			{
				groupId: themeDisplay.getScopeGroupId(),
				name: "%" + term + "%",
				properties: "",
				begin: beginning,
				end: end
			}
		);

		return jQuery.map(
			data,
			function(row) {
				return {
					data: row.text,
					value: row.value,
					result: row.text
				}
			}
		);
	},

	_getVocabularies: function(folksonomy, callback) {
		var instance = this;

		Liferay.Service.Tags.TagsVocabulary.getGroupVocabularies(
			{
				groupId: themeDisplay.getScopeGroupId(),
				folksonomy: folksonomy
			},
			callback
		);
	},

	_getVocabularyEntries: function(vocabulary, callback) {
		var instance = this;

		Liferay.Service.Tags.TagsEntry.getGroupVocabularyEntries(
			{
				groupId: themeDisplay.getScopeGroupId(),
				name: vocabulary
			},
			callback
		);
	},

	_initializeSearch: function(container) {
		var data = function() {
			var value = jQuery(this).attr('title');

			return value.toLowerCase();
		};

		var inputSearch = jQuery('.lfr-tag-search-input');

		var options = {
			data: data,
			list: '.lfr-tag-container label',
			after: function() {
				jQuery('fieldset', container).each(
					function() {
						var fieldset = jQuery(this);

						var visibleEntries = fieldset.find('label:visible');

						if (visibleEntries.length == 0) {
							fieldset.addClass('no-matches');
						}
						else {
							fieldset.removeClass('no-matches');
						}
					}
				);
			}
		};

		inputSearch.liveSearch(options);
	},

	_setupSelectTagsEntries: function() {
		var instance = this;

		var options = instance.options;
		var ns = instance._ns;

		var input = jQuery('#' + ns + 'selectTag');

		input.click(
			function() {
				instance._showSelectPopup();
			}
		);
	},

	_setupSuggestions: function() {
		var instance = this;

		var options = instance.options;
		var ns = instance._ns;

		var input = jQuery('#' + ns + 'suggestions');

		input.click(
			function() {
				instance._showSuggestionsPopup();
			}
		);
	},

	_showSelectPopup: function() {
		var instance = this;

		var options = instance.options;
		var ns = instance._ns;
		var mainContainer = instance._mainContainer;
		var container = instance._container;
		var searchMessage = Liferay.Language.get('no-tags-found');

		mainContainer.empty();

		container.empty().html('<div class="loading-animation" />');

		instance._getVocabularies(true,
			function(vocabularies) {
				var buffer = [];

				if (vocabularies.length == 0) {
					buffer.push('<fieldset class="no-matches"><legend>' + Liferay.Language.get('tag-sets') + '</legend>');
					buffer.push('<div class="lfr-tag-message">' + searchMessage + '</div>');
					buffer.push('</fieldset>');

					container.html(buffer.join(''));
				}
				else {
					jQuery.each(
						vocabularies,
						function(i) {
							var tagEntrySet = this;
							var tagEntrySetName = tagEntrySet.name;

							instance._getVocabularyEntries(
								tagEntrySetName,
								function(entries) {
									buffer.push('<fieldset>');
									buffer.push('<legend class="lfr-tag-set-title">');
									buffer.push(tagEntrySetName);
									buffer.push('</legend>');

									jQuery.each(
										entries,
										function(i) {
											var entry = this;
											var entryName = entry.name;
											var entryId = entry.entryId;
											var checked = (instance._curTagsEntries.indexOf(entryName) > -1) ? ' checked="checked" ' : '';
											buffer.push('<label title="');
											buffer.push(entryName);
											buffer.push('">');
											buffer.push('<input type="checkbox" value="');
											buffer.push(entryName);
											buffer.push('" ');
											buffer.push(checked);
											buffer.push('> ');
											buffer.push(entryName);
											buffer.push('</label>');
										}
									);

									buffer.push('<div class="lfr-tag-message">' + searchMessage + '</div>');
									buffer.push('</fieldset>');

									container.html(buffer.join(''));

									instance._initializeSearch(container);
								}
							);
						}
					);
				}
			}
		);

		instance._createPopup();
	},

	_showSuggestionsPopup: function() {
		var instance = this;

		var options = instance.options;
		var ns = instance._ns;
		var mainContainer = instance._mainContainer;
		var container = instance._container;
		var searchMessage = Liferay.Language.get('no-tags-found');

		mainContainer.empty();

		container.empty().html('<div class="loading-animation" />');

		var context = '';

		if (options.contentCallback) {
			context = options.contentCallback();
		}

		var url =  "http://search.yahooapis.com/ContentAnalysisService/V1/termExtraction?appid=YahooDemo&output=json&context=" + escape(context);

		var buffer = [];

		jQuery.ajax(
			{
				url: themeDisplay.getPathMain() + "/portal/rest_proxy",
				data: {
					url: url
				},
				dataType: "json",
				success: function(obj) {
					buffer.push('<fieldset><legend>' + Liferay.Language.get('suggestions') + '</legend>');

					jQuery.each(
						obj.ResultSet.Result,
						function(i, tagEntry) {
 							var checked = (instance._curTagsEntries.indexOf(tagEntry) > -1) ? ' checked="checked" ' : '';
							var name = ns + 'input' + i;

							buffer.push('<label title="');
							buffer.push(tagEntry);
							buffer.push('"><input');
							buffer.push(checked);
							buffer.push(' type="checkbox" name="');
							buffer.push(name);
							buffer.push('" id="');
							buffer.push(name);
							buffer.push('" value="');
							buffer.push(tagEntry);
							buffer.push('" /> ');
							buffer.push(tagEntry);
							buffer.push('</label>');
						}
					);

					buffer.push('<div class="lfr-tag-message">' + searchMessage + '</div>');
					buffer.push('</fieldset>');

					container.html(buffer.join(''));

					if (!obj.ResultSet.Result.length) {
						container.find('fieldset:first').addClass('no-matches')
					}

					instance._initializeSearch(container);
				}
			}
		);

		instance._createPopup();
	},

	_update: function() {
		var instance = this;

		instance._updateHiddenInput();
		instance._updateSummarySpan();
	},

	_updateHiddenInput: function() {
		var instance = this;

		var options = instance.options;
		var curTagsEntries = instance._curTagsEntries;

		var hiddenInput = jQuery('#' + options.hiddenInput);

		hiddenInput.val(curTagsEntries.join(','));
	},

	_updateSummarySpan: function() {
		var instance = this;

		var options = instance.options;
		var curTagsEntries = instance._curTagsEntries;

		var html = '';

		jQuery(curTagsEntries).each(
			function(i, curTagEntry) {
				html += '<span class="ui-tag" id="' + instance._ns + 'CurTags' + i + '">';
				html += curTagEntry;
				html += '<a class="ui-tag-delete" href="javascript: ' + instance._ns + '.deleteTagEntry(' + i + ');"><span>x</span></a>';
				html += '</span>';
			}
		);

		var tagsEntriesSummary = jQuery('#' + options.summarySpan);

		if (curTagsEntries.length) {
			tagsEntriesSummary.removeClass('empty');
		}
		else {
			tagsEntriesSummary.addClass('empty');
		}

		tagsEntriesSummary.html(html);
	}
});
Liferay.UndoManager = Liferay.Observable.extend({

	/**
	 * OPTIONS
	 *
	 * Required
	 * container {string|object}: A jQuery selector that contains the rows you wish to duplicate.
	 *
	 * Optional
	 * location {string}: The location in the container (top or bottom) where the manager will be added
	 *
	 */

	initialize: function(options) {
		var instance = this;

		var defaults = {
			container: null,
			location: 'top'
		};

		options = jQuery.extend(defaults, options);

		if (options.container) {
			var undoText = Liferay.Language.get('undo-x', ['[$SPAN$]']);
			undoText = undoText.replace(/\[\$SPAN\$\]/, '<span class="items-left">(0)</span>');

			instance._container = jQuery(options.container);
			instance._manager = jQuery('<div class="portlet-msg-info undo-queue queue-empty"><a class="undo-action" href="javascript: ;">' + undoText + '</a><a class="clear-undos" href="javascript: ;">' + Liferay.Language.get('clear-history') + '</a></div>');

			instance._undoItemsLeft = instance._manager.find('.items-left');
			instance._undoButton = instance._manager.find('.undo-action');
			instance._clearUndos = instance._manager.find('.clear-undos');

			instance.bind('update', instance._updateList);

			instance._clearUndos.click(
				function(event) {
					instance._undoCache = [];

					instance.trigger('update');
					instance.trigger('clearList');
				}
			);

			instance._undoButton.click(
				function(event) {
					instance.undo(1);
				}
			);

			var attachMethod = 'prepend';

			if (options.location != 'top') {
				attachMethod = 'append';
			}

			instance._container[attachMethod](instance._manager);

			instance.set('container', instance._container);

			jQuery(window).unload(
				function(event) {
					instance._undoCache = [];
				}
			);
		}
	},

	add: function(handler, stateData) {
		var instance = this;

		if (handler && typeof handler == 'function') {
			var undo = {
				handler: handler,
				stateData: stateData
			};

			instance._undoCache.push(undo);

			instance.trigger('update');
			instance.trigger('add');
		}
	},

	undo: function(limit) {
		var instance = this;

		limit = limit || 1;

		var i = instance._undoCache.length - 1;

		while (limit > 0 && i >= 0) {
			var undoAction = instance._undoCache.pop();

			undoAction.handler.call(instance, undoAction.stateData);

			limit--;
			i--;
		}

		instance.trigger('update');
		instance.trigger('undo');
	},

	_updateList: function() {
		var instance = this;

		var itemsLeft = instance._undoCache.length;
		var manager = instance._manager;

		if (itemsLeft == 1) {
			manager.addClass('queue-single');
		}
		else {
			manager.removeClass('queue-single');
		}

		if (itemsLeft > 0) {
			manager.removeClass('queue-empty');
		}
		else {
			manager.addClass('queue-empty');
		}

		instance._undoItemsLeft.text('(' + itemsLeft + ')');
	},

	_undoCache: []
});
Liferay.Upload = new Class({

	/**
	 * OPTIONS
	 *
	 * Required
	 * allowedFileTypes {string}: A comma-seperated list of allowable filetypes.
	 * container {string|object}: The container where the uploader will be placed.
	 * maxFileSize {number}: The maximum file size that can be uploaded.
	 * uploadFile {string}: The URL to where the file will be uploaded.
	 *
	 * Optional
	 * buttonHeight {number}: The buttons height.
	 * buttonText {string}: The text to be displayed on the upload button.
	 * buttonUrl {string}: A relative (to the flash) file that will be used as the background image of the button.
	 * buttonWidth {number}: The buttons width.
	 * fallbackContainer {string|object}: A jQuery selector or DOM element of the container holding a fallback (in case flash is not supported).
	 * fileDescription {string}: A string describing what files can be uploaded.
	 * namespace {string}: A unique string so that the global callback methods don't collide.
	 * overlayButton {boolean}: Whether the button is overlayed upon the HTML link.
	 *
	 * Callbacks
	 * onFileComplete {function}: Called whenever a file is completely uploaded.
	 * onUploadsComplete {function}: Called when all files are finished being uploaded, and is passed no arguments.
	 * onUploadProgress {function}: Called during upload, and is also passed in the number of bytes loaded as it's second argument.
	 * onUploadError {function}: Called when an error in the upload occurs. Gets passed the error number as it's only argument.
	 */

	initialize: function(options) {
		var instance = this;

		options = options || {};

		instance._container = jQuery(options.container);
		instance._fallbackContainer = jQuery(options.fallbackContainer || []);
		instance._namespaceId = options.namespace || '_liferay_pns_' + Liferay.Util.randomInt() + '_';
		instance._maxFileSize = options.maxFileSize || 0;
		instance._allowedFileTypes = options.allowedFileTypes;
		instance._uploadFile = options.uploadFile;

		instance._buttonUrl = options.buttonUrl || '';
		instance._buttonWidth = options.buttonWidth || 500;
		instance._buttonHeight = options.buttonHeight || 30;
		instance._buttonText = options.buttonText || '';

		instance._buttonPlaceHolderId = instance._namespace('buttonHolder');
		instance._overlayButton = options.overlayButton || true;

		instance._onFileComplete = options.onFileComplete;
		instance._onUploadsComplete = options.onUploadsComplete;
		instance._onUploadProgress = options.onUploadProgress;
		instance._onUploadError = options.onUploadError;

		instance._classicUploaderParam = 'uploader=classic';
		instance._newUploaderParam = 'uploader=new';

		instance._queueCancelled = false;

		instance._flashVersion = deconcept.SWFObjectUtil.getPlayerVersion().major;

		// Check for an override via the query string

		var loc = location.href;

		if (loc.indexOf(instance._classicUploaderParam) > -1 && instance._fallbackContainer.length) {
			instance._fallbackContainer.show();

			instance._setupIframe();

			return;
		}

		// Language keys

		instance._browseText = Liferay.Language.get('browse-you-can-select-multiple-files');
		instance._cancelUploadsText = Liferay.Language.get('cancel-all-uploads');
		instance._cancelFileText = Liferay.Language.get('cancel-upload');
		instance._clearRecentUploadsText = Liferay.Language.get('clear-recent-uploads');
		instance._fileListPendingText = Liferay.Language.get('x-files-ready-to-be-uploaded', '0');
		instance._fileListText = Liferay.Language.get('file-list');
		instance._fileTypesDescriptionText = options.fileDescription || instance._allowedFileTypes;
		instance._uploadsCompleteText = Liferay.Language.get('all-uploads-complete');
		instance._uploadStatusText = Liferay.Language.get('uploading-file-x-of-x', ['[$POS$]','[$TOTAL$]']);
		instance._uploadFilesText = Liferay.Language.get('upload-files');

		if (instance._fallbackContainer.length) {
			instance._useFallbackText = Liferay.Language.get('use-the-classic-uploader');
			instance._useNewUploaderText = Liferay.Language.get('use-the-new-uploader');
		}

		if (instance._flashVersion < 9 && instance._fallbackContainer.length) {
			instance._fallbackContainer.show();

			instance._setupIframe();

			return;
		}

		instance._setupCallbacks();
		instance._setupUploader();
	},

	cancelUploads: function() {
		var instance = this;

		var stats = instance._getStats();

		while (stats.files_queued > 0) {
			instance._uploader.cancelUpload();

			stats = instance._getStats();
		}

		if (stats.in_progress === 0) {
			instance._queueCancelled = false;
		}

		instance._uploadButton.hide();
		instance._cancelButton.hide();
	},

	fileAdded: function(file) {
		var instance = this;

		var listingFiles = instance._fileList;
		var listingUl = listingFiles.find('ul');

		if (!listingUl.length) {
			instance._listInfo.append('<h4>' + instance._fileListText + '</h4>');

			listingFiles.append('<ul class="lfr-component"></ul>');

			instance._uploadTarget.append(instance._clearUploadsButton);
			instance._clearUploadsButton.hide();

			instance._cancelButton.click(
				function() {
					instance.cancelUploads();
					instance._clearUploadsButton.hide();
				}
			);
		}

		instance._cancelButton.show();
		instance._uploadButton.show();

		listingFiles = listingFiles.find('ul');

		var fileId = instance._namespace(file.id);
		var fileName = file.name;

		var li = jQuery(
			'<li class="upload-file" id="' + fileId + '">' +
				'<span class="file-title">' + fileName + '</span>' +
				'<span class="progress-bar">' +
					'<span class="progress" id="' + fileId + 'progress"></span>' +
				'</span>' +
				'<a class="lfr-button cancel-button" href="javascript: ;" id="' + fileId+ 'cancelButton">' + instance._cancelFileText + '</a>' +
			'</li>');

		li.find('.cancel-button').click(
			function() {
				instance._uploader.cancelUpload(file.id);
			}
		);

		var uploadedFiles = listingFiles.find('.upload-complete');

		uploadedFiles = uploadedFiles.filter(':first');

		if (uploadedFiles.length) {
			uploadedFiles.before(li);
		}
		else {
			listingFiles.append(li);
		}

		var stats = instance._getStats();
		var listLength = stats.files_queued;

		instance._updateList(listLength);
	},

	fileCancelled: function(file, error_code, msg) {
		var instance = this;

		var stats = instance._getStats();

		var fileId = instance._namespace(file.id);
		var fileName = file.name;
		var li = jQuery('#' + fileId);

		instance._updateList(stats.files_queued);

		li.fadeOut('slow');
	},

	fileUploadComplete: function(file) {
		var instance = this;

		var fileId = instance._namespace(file.id);
		var li = jQuery('#' + fileId);

		li.removeClass('file-uploading').addClass('upload-complete');

		var uploader = instance._uploader;
		var stats = instance._getStats();

		if (stats.files_queued > 0 && !instance._queueCancelled) {

			// Automatically start the next upload if the queue wasn't cancelled

			uploader.startUpload();
		}
		else if (stats.files_queued === 0 && !instance._queueCancelled) {

			// Call Queue Complete if there are no more files queued and the queue wasn't cancelled

			instance.uploadsComplete(file);
		}
		else {

			// Don't do anything. Remove the queue cancelled flag (if the queue was cancelled it will be set again)

			instance._queueCancelled = false;
		}

		if (instance._onFileComplete) {
			instance._onFileComplete(file);
		}
	},

	flashLoaded: function() {
		var instance = this;

		instance._setupControls();
	},

	uploadError: function(file, error_code, msg) {
		var instance = this;

		/*
		Error codes:
			-10 HTTP error
			-20 No upload script specified
			-30 IOError
			-40 Security error
			-50 Filesize too big
		*/

		if (error_code == SWFUpload.UPLOAD_ERROR.FILE_CANCELLED) {
			instance.fileCancelled(file, error_code, msg);
		}

		if (instance._onUploadError) {
			instance._onUploadError(arguments);
		}
	},

	uploadProgress: function(file, bytesLoaded) {
		var instance = this;
		var fileId = instance._namespace(file.id);
		var progress = document.getElementById(fileId + 'progress');
		var percent = Math.ceil((bytesLoaded / file.size) * 100);

		progress.style.width = percent + '%';

		if (instance._onUploadProgress) {
			instance._onUploadProgress(file, bytesLoaded);
		}
	},

	uploadsComplete: function(file) {
		var instance = this;

		instance._cancelButton.hide();
		instance._updateList(0, instance._uploadsCompleteText);
		instance._uploadButton.hide();

		if (instance._clearUploadsButton.is(':hidden')) {
			instance._clearUploadsButton.show();
		}

		if (instance._onUploadsComplete) {
			instance._onUploadsComplete();
		}

		var uploader = instance._uploader;

		uploader.setStats(
			{
				successful_uploads: 0
			}
		);
	},

	uploadStart: function(file) {
		var instance = this;

		var stats = instance._getStats();
		var listLength = (stats.successful_uploads + stats.upload_errors + stats.files_queued);
		var position = (stats.successful_uploads + stats.upload_errors + 1);

		var currentListText = instance._uploadStatusText.replace('[$POS$]', position).replace('[$TOTAL$]', listLength);
		var fileId = instance._namespace(file.id);

		instance._updateList(listLength, currentListText);

		var li = jQuery('#' + fileId);

		li.addClass('file-uploading');

		return true;
	},

	uploadSuccess: function(file, data) {
		var instance = this;

		instance.fileUploadComplete(file, data);
	},

	_clearUploads: function() {
		var instance = this;

		var completeUploads = instance._fileList.find('.upload-complete');

		completeUploads.fadeOut('slow',
			function() {
				jQuery(this).remove();
			}
		);

		instance._clearUploadsButton.hide();
	},

	_getStats: function() {
		var instance = this;

		return instance._uploader.getStats();
	},

	_namespace: function(txt) {
		var instance = this;

		txt = txt || '';

		return instance._namespaceId + txt;

	},

	_setupCallbacks: function() {
		var instance = this;

		// Global callback references

		instance._cancelUploads = instance._namespace('cancelUploads');
		instance._fileAdded = instance._namespace('fileAdded');
		instance._fileCancelled = instance._namespace('fileCancelled');
		instance._flashLoaded = instance._namespace('flashLoaded');
		instance._uploadStart = instance._namespace('uploadStart');
		instance._uploadProgress = instance._namespace('uploadProgress');
		instance._uploadError = instance._namespace('uploadError');
		instance._uploadSuccess = instance._namespace('uploadSuccess');
		instance._fileUploadComplete = instance._namespace('fileUploadComplete');
		instance._uploadsComplete = instance._namespace('uploadsComplete');
		instance._uploadsCancelled = instance._namespace('uploadsCancelled');

		// Global swfUpload var

		instance._swfUpload = instance._namespace('cancelUploads');

		window[instance._cancelUploads] = function() {
			instance.cancelUploads.apply(instance, arguments);
		};

		window[instance._fileAdded] = function() {
			instance.fileAdded.apply(instance, arguments);
		};

		window[instance._fileCancelled] = function() {
			instance.fileCancelled.apply(instance, arguments);
		};

		window[instance._uploadStart] = function() {
			instance.uploadStart.apply(instance, arguments);
		};

		window[instance._uploadProgress] = function() {
			instance.uploadProgress.apply(instance, arguments);
		};

		window[instance._uploadError] = function() {
			instance.uploadError.apply(instance, arguments);
		};

		window[instance._fileUploadComplete] = function() {
			instance.fileUploadComplete.apply(instance, arguments);
		};

		window[instance._uploadSuccess] = function() {
			instance.uploadSuccess.apply(instance, arguments);
		};

		window[instance._uploadsComplete] = function() {
			instance.uploadsComplete.apply(instance, arguments);
		};

		window[instance._flashLoaded] = function() {
			instance.flashLoaded.apply(instance, arguments);
		};

	},

	_setupControls: function() {
		var instance = this;

		if (!instance._hasControls) {
			instance._uploadTargetId = instance._namespace('uploadTarget');
			instance._listInfoId = instance._namespace('listInfo');
			instance._fileListId = instance._namespace('fileList');

			instance._uploadTarget = jQuery('<div id="' + instance._uploadTargetId + '" class="float-container upload-target"></div>');

			instance._uploadTarget.css('position', 'relative');

			instance._listInfo = jQuery('<div id="' + instance._listInfoId + '" class="upload-list-info"></div>');
			instance._fileList = jQuery('<div id="' + instance._fileListId + '" class="upload-list"></div>');
			instance._cancelButton = jQuery('<a class="lfr-button cancel-uploads" href="javascript: ;">' + instance._cancelUploadsText + '</a>');
			instance._clearUploadsButton = jQuery('<a class="lfr-button clear-uploads" href="javascript: ;">' + instance._clearRecentUploadsText + '</a>');

			instance._browseButton = jQuery('<a class="lfr-button browse-button" href="javascript: ;">' + instance._browseText + '</a>');
			instance._uploadButton = jQuery('<a class="lfr-button upload-button" href="javascript: ;">' + instance._uploadFilesText + '</a>');

			instance._container.prepend([instance._uploadTarget[0], instance._listInfo[0], instance._fileList[0]]);
			instance._uploadTarget.append([instance._browseButton[0], instance._buttonPlaceHolder[0], instance._uploadButton[0], instance._cancelButton[0]]);

			instance._clearUploadsButton.click(
				function() {
					instance._clearUploads();
				}
			);

			if (instance._overlayButton) {
				var buttonWidth = instance._browseButton.outerWidth();
				var buttonHeight = instance._browseButton.outerHeight();
				var buttonOffset = instance._browseButton.offset();

				var flashObj = jQuery('#' + instance._uploader.movieName);

				flashObj.css(
					{
						left: buttonOffset.left,
						position: 'absolute',
						top: buttonOffset.top,
						zIndex: 100000
					}
				);

				instance._uploader.setButtonDimensions(buttonWidth, buttonHeight);
			}
			else {
				instance._browseButton.click(
					function() {
						instance._uploader.selectFiles();
					}
				);
			}

			instance._uploadButton.click(
				function() {
					instance._uploader.startUpload();
				}
			);

			instance._uploadButton.hide();
			instance._cancelButton.hide();

			if (instance._fallbackContainer.length) {
				instance._useFallbackButton = jQuery('<a class="use-fallback using-new-uploader" href="javascript: ;">' + instance._useFallbackText + '</a>');
				instance._fallbackContainer.after(instance._useFallbackButton);

				instance._useFallbackButton.click(
					function() {
						var fallback = jQuery(this);
						var newUploaderClass = 'using-new-uploader';
						var fallbackClass = 'using-classic-uploader';

						if (fallback.is('.' + newUploaderClass)) {
							instance._container.hide();
							instance._fallbackContainer.show();

							fallback.text(instance._useNewUploaderText);
							fallback.removeClass(newUploaderClass).addClass(fallbackClass);

							instance._setupIframe();

							var classicUploaderUrl = '';

							if (location.hash.length) {
								classicUploaderUrl = '&';
							}

							location.hash += classicUploaderUrl + instance._classicUploaderParam;
						}
						else {
							instance._container.show();
							instance._fallbackContainer.hide();
							fallback.text(instance._useFallbackText);
							fallback.removeClass(fallbackClass).addClass(newUploaderClass);

							location.hash = location.hash.replace(instance._classicUploaderParam, instance._newUploaderParam);
						}
					}
				);
			}

			instance._hasControls = true;
		}
	},

	_setupIframe: function() {
		var instance = this;

		if (!instance._fallbackIframe) {
			instance._fallbackIframe = instance._fallbackContainer.find('iframe[id$=-iframe]');

			if (instance._fallbackIframe.length) {
				var frameHeight = jQuery('#content-wrapper', instance._fallbackIframe[0].contentWindow).height() || 250;

				instance._fallbackIframe.height(frameHeight + 150);
			}
		}
	},

	_setupUploader: function() {
		var instance = this;

		if (instance._allowedFileTypes.indexOf('*') == -1) {
			var fileTypes = instance._allowedFileTypes.split(',');

			fileTypes = jQuery.map(
				fileTypes,
				function(value, key) {
					var fileType = value;

					if (value.indexOf('*') == -1) {
						fileType = '*' + value;
					}
					return fileType;
				}
			);

			instance._allowedFileTypes = fileTypes.join(';');
		}

		instance._buttonPlaceHolder = jQuery('<div id="' + instance._buttonPlaceHolderId + '"></div>');

		jQuery(document.body).append(instance._buttonPlaceHolder);

		instance._uploader = new SWFUpload({
			upload_url: instance._uploadFile,
			target: instance._uploadTargetId,
			flash_url: themeDisplay.getPathContext() + '/html/js/misc/swfupload/swfupload_f10.swf',
			file_size_limit: instance._maxFileSize,
			file_types: instance._allowedFileTypes,
			file_types_description: instance._fileTypesDescriptionText,
			browse_link_innerhtml: instance._browseText,
			upload_link_innerhtml: instance._uploadFilesText,
			browse_link_class: 'browse-button liferay-button',
			upload_link_class: 'upload-button liferay-button',
			swfupload_loaded_handler: window[instance._flashLoaded],
			file_queued_handler: window[instance._fileAdded],
			upload_start_handler: window[instance._uploadStart],
			upload_progress_handler: window[instance._uploadProgress],
			upload_complete_handler: window[instance._fileUploadComplete],
			upload_success_handler: window[instance._uploadSuccess],
			upload_file_cancel_callback: window[instance._fileCancelled],
			upload_queue_complete_callback: window[instance._uploadsComplete],
			upload_error_handler: window[instance._uploadError],
			upload_cancel_callback: window[instance._cancelUploads],
			auto_upload : false,
			file_post_name: 'file',
			create_ui: true,
			button_image_url: instance._buttonUrl,
			button_width: instance._buttonWidth,
			button_window_mode: 'transparent',
			button_height: instance._buttonHeight,
			button_placeholder_id: instance._buttonPlaceHolderId,
			button_text: instance._buttonText,
			button_text_style: '',
			button_text_left_padding: 0,
			button_text_top_padding: 0,
			debug: false
		});

		window[instance._swfUpload] = instance._uploader;
	},

	_updateList: function(listLength, message) {
		var instance = this;

		var infoTitle = instance._listInfo.find('h4');
		var listText = '';

		if (!message) {
			listText = instance._fileListPendingText;
			listText = listText.replace(/\d+/g, listLength);
		}
		else {
			listText = message;
		}

		infoTitle.html(listText);
	}
});
