diff --git a/modules/.eslintignore b/modules/.eslintignore
index 41fbb14996d15c..e076becc5aebcf 100644
--- a/modules/.eslintignore
+++ b/modules/.eslintignore
@@ -48,4 +48,5 @@
# These are generated third-party files, not intended for editing or
# inspection.
#
+ apps/frontend-js/frontend-js-jquery-web/src/main/resources/META-INF/resources/jquery/form.js
apps/frontend-js/frontend-js-svg4everybody-web/src/main/resources/META-INF/resources/svg4everybody.js
\ No newline at end of file
diff --git a/modules/.prettierignore b/modules/.prettierignore
index 21082801946d7e..019669676cb4ec 100644
--- a/modules/.prettierignore
+++ b/modules/.prettierignore
@@ -47,5 +47,6 @@
# These are generated third-party files, not intended for editing or
# inspection.
#
+ /apps/frontend-js/frontend-js-jquery-web/src/main/resources/META-INF/resources/jquery/form.js
/apps/frontend-js/frontend-js-svg4everybody-web/src/main/resources/META-INF/resources/svg4everybody.js
/yarn-*.js
\ No newline at end of file
diff --git a/modules/apps/frontend-js/frontend-js-jquery-web/build.gradle b/modules/apps/frontend-js/frontend-js-jquery-web/build.gradle
index e8dbac94b496e6..209f6593b49e26 100644
--- a/modules/apps/frontend-js/frontend-js-jquery-web/build.gradle
+++ b/modules/apps/frontend-js/frontend-js-jquery-web/build.gradle
@@ -1,11 +1,21 @@
+import com.liferay.gradle.plugins.defaults.internal.util.GitUtil
+import com.liferay.gradle.util.FileUtil
import com.liferay.gradle.util.copy.StripPathSegmentsAction
+ext {
+ jQueryFormVersion = "3.51"
+
+ jQueryFormURL = "https://raw.githubusercontent.com/jquery-form/form/${jQueryFormVersion}/jquery.form.js"
+}
+
task buildBootstrapJS(type: Copy)
task buildJQuery(type: Copy)
+task buildJQueryForm(type: Copy)
task buildPopperJS(type: Copy)
+task patchJQueryForm(type: Copy)
File jsDestinationDir = file("tmp/META-INF/resources")
-File jqueryDestinationDir = new File(jsDestinationDir, "jquery")
+File jQueryDestinationDir = new File(jsDestinationDir, "jquery")
buildBootstrapJS {
dependsOn buildJQuery
@@ -18,14 +28,14 @@ buildBootstrapJS {
include "bootstrap/dist/js/bootstrap.bundle.min.js.map"
includeEmptyDirs = false
- into jqueryDestinationDir
+ into jQueryDestinationDir
}
buildJQuery {
dependsOn npmInstall
doFirst {
- delete jqueryDestinationDir
+ delete jQueryDestinationDir
}
eachFile new StripPathSegmentsAction(2)
@@ -36,7 +46,26 @@ buildJQuery {
include "jquery/dist/jquery.min.js.map"
includeEmptyDirs = false
- into jqueryDestinationDir
+ into jQueryDestinationDir
+}
+
+buildJQueryForm {
+ File jQueryFormURLFile = file("src/main/resources/META-INF/resources/jquery/form.js")
+
+ ext {
+ autoClean = false
+ }
+
+ from {
+ FileUtil.get(project, project.jQueryFormURL, jQueryFormURLFile)
+ }
+
+ into jQueryFormURLFile.parentFile
+
+ eachFile {
+ GitUtil.getGitResult(project, "apply", "-v",
+ "src/main/resources/META-INF/resources/_diffs/jquery.form.patch")
+ }
}
buildPopperJS {
@@ -50,12 +79,13 @@ buildPopperJS {
include "popper.js/dist/umd/popper.min.js.map"
includeEmptyDirs = false
- into jqueryDestinationDir
+ into jQueryDestinationDir
}
classes {
dependsOn buildBootstrapJS
dependsOn buildJQuery
+ dependsOn buildJQueryForm
dependsOn buildPopperJS
}
diff --git a/modules/apps/frontend-js/frontend-js-jquery-web/src/main/resources/META-INF/resources/_diffs/jquery.form.patch b/modules/apps/frontend-js/frontend-js-jquery-web/src/main/resources/META-INF/resources/_diffs/jquery.form.patch
new file mode 100644
index 00000000000000..d54f3394d0cb67
--- /dev/null
+++ b/modules/apps/frontend-js/frontend-js-jquery-web/src/main/resources/META-INF/resources/_diffs/jquery.form.patch
@@ -0,0 +1,39 @@
+diff --git a/modules/apps/frontend-js/frontend-js-jquery-web/src/main/resources/META-INF/resources/jquery/form.js b/modules/apps/frontend-js/frontend-js-jquery-web/src/main/resources/META-INF/resources/jquery/form.js
+index 591ad6f1fe7b..04cf9498c9a2 100644
+--- a/modules/apps/frontend-js/frontend-js-jquery-web/src/main/resources/META-INF/resources/jquery/form.js
++++ b/modules/apps/frontend-js/frontend-js-jquery-web/src/main/resources/META-INF/resources/jquery/form.js
+@@ -13,7 +13,7 @@
+ // AMD support
+ (function (factory) {
+ "use strict";
+- if (typeof define === 'function' && define.amd) {
++ if (false && typeof define === 'function' && define.amd) {
+ // using AMD; register as anon module
+ define(['jquery'], factory);
+ } else {
+@@ -190,6 +190,11 @@ $.fn.ajaxSubmit = function(options) {
+ var oldSuccess = options.success || function(){};
+ callbacks.push(function(data) {
+ var fn = options.replaceTarget ? 'replaceWith' : 'html';
++
++ data = options.replaceTarget
++ ? data
++ : $.parseHTML($('
').text(data).html());
++
+ $(options.target)[fn](data).each(oldSuccess, arguments);
+ });
+ }
+@@ -800,9 +805,10 @@ $.fn.ajaxSubmit = function(options) {
+ }
+ return (doc && doc.documentElement && doc.documentElement.nodeName != 'parsererror') ? doc : null;
+ };
+- var parseJSON = $.parseJSON || function(s) {
+- /*jslint evil:true */
+- return window['eval']('(' + s + ')');
++ var parseJSON = $.parseJSON || function() {
++ window.console.error('jQuery.paseJSON is undefined');
++
++ return null;
+ };
+
+ var httpData = function( xhr, type, s ) { // mostly lifted from jq1.4.4
diff --git a/modules/apps/frontend-js/frontend-js-jquery-web/src/main/resources/META-INF/resources/jquery/form.js b/modules/apps/frontend-js/frontend-js-jquery-web/src/main/resources/META-INF/resources/jquery/form.js
index bb6c3dd3ab38f8..04cf9498c9a22b 100644
--- a/modules/apps/frontend-js/frontend-js-jquery-web/src/main/resources/META-INF/resources/jquery/form.js
+++ b/modules/apps/frontend-js/frontend-js-jquery-web/src/main/resources/META-INF/resources/jquery/form.js
@@ -1,51 +1,31 @@
-/**
- * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
- *
- * This library is free software; you can redistribute it and/or modify it under
- * the terms of the GNU Lesser General Public License as published by the Free
- * Software Foundation; either version 2.1 of the License, or (at your option)
- * any later version.
- *
- * This library is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
- * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
- * details.
+/*!
+ * jQuery Form Plugin
+ * version: 3.51.0-2014.06.20
+ * Requires jQuery v1.5 or later
+ * Copyright (c) 2014 M. Alsup
+ * Examples and documentation at: http://malsup.com/jquery/form/
+ * Project repository: https://github.com/malsup/form
+ * Dual licensed under the MIT and GPL licenses.
+ * https://github.com/malsup/form#copyright-and-license
*/
-
-(function (jQuery) {
-
- /*!
- * jQuery Form Plugin
- * version: 3.51.0-2014.06.20
- * Requires jQuery v1.5 or later
- * Copyright (c) 2014 M. Alsup
- * Examples and documentation at: http://malsup.com/jquery/form/
- * Project repository: https://github.com/malsup/form
- * Dual licensed under the MIT and GPL licenses.
- * https://github.com/malsup/form#copyright-and-license
- */
- /*global ActiveXObject */
-
- // AMD support
-
- (function (factory) {
- 'use strict';
- if (false && typeof define === 'function' && define.amd) {
-
- // using AMD; register as anon module
-
- define(['jquery'], factory);
- }
- else {
-
- // no AMD; invoke directly
-
- factory(typeof jQuery != 'undefined' ? jQuery : window.Zepto);
- }
- })(function ($) {
- 'use strict';
-
- /*
+/*global ActiveXObject */
+
+// AMD support
+(function (factory) {
+ "use strict";
+ if (false && typeof define === 'function' && define.amd) {
+ // using AMD; register as anon module
+ define(['jquery'], factory);
+ } else {
+ // no AMD; invoke directly
+ factory( (typeof(jQuery) != 'undefined') ? jQuery : window.Zepto );
+ }
+}
+
+(function($) {
+"use strict";
+
+/*
Usage Note:
-----------
Do not use both ajaxSubmit and ajaxForm on the same form. These
@@ -82,1581 +62,1222 @@
at the appropriate time.
*/
- /**
- * Feature detection
- */
- var feature = {};
- feature.fileapi = $("
").get(0).files !== undefined;
- feature.formdata = window.FormData !== undefined;
-
- var hasProp = !!$.fn.prop;
-
- // attr2 uses prop when it can but checks the return type for
- // an expected string. this accounts for the case where a form
- // contains inputs with names like "action" or "method"; in those
- // cases "prop" returns the element
-
- $.fn.attr2 = function () {
- if (!hasProp) {
- return this.attr.apply(this, arguments);
- }
- var val = this.prop.apply(this, arguments);
- if ((val && val.jquery) || typeof val === 'string') {
- return val;
- }
- return this.attr.apply(this, arguments);
- };
-
- /**
- * ajaxSubmit() provides a mechanism for immediately submitting
- * an HTML form using AJAX.
- */
- $.fn.ajaxSubmit = function (options) {
- /*jshint scripturl:true */
-
- // fast fail if nothing selected (http://dev.jquery.com/ticket/2752)
-
- if (!this.length) {
- log(
- 'ajaxSubmit: skipping submit process - no element selected'
- );
- return this;
- }
-
- var method,
- action,
- url,
- $form = this;
-
- if (typeof options == 'function') {
- options = {success: options};
- }
- else if (options === undefined) {
- options = {};
- }
-
- method = options.type || this.attr2('method');
- action = options.url || this.attr2('action');
-
- url = typeof action === 'string' ? $.trim(action) : '';
- url = url || window.location.href || '';
- if (url) {
-
- // clean url (don't include hash vaue)
-
- url = (url.match(/^([^#]+)/) || [])[1];
- }
-
- options = $.extend(
- true,
- {
- url,
- success: $.ajaxSettings.success,
- type: method || $.ajaxSettings.type,
- iframeSrc: /^https/i.test(window.location.href || '')
- ? 'javascript:false'
- : 'about:blank',
- },
- options
- );
-
- // hook for manipulating the form data before it is extracted;
- // convenient for use with rich editors like tinyMCE or FCKEditor
-
- var veto = {};
- this.trigger('form-pre-serialize', [this, options, veto]);
- if (veto.veto) {
- log('ajaxSubmit: submit vetoed via form-pre-serialize trigger');
- return this;
- }
-
- // provide opportunity to alter form data before it is serialized
-
- if (
- options.beforeSerialize &&
- options.beforeSerialize(this, options) === false
- ) {
- log('ajaxSubmit: submit aborted via beforeSerialize callback');
- return this;
- }
-
- var traditional = options.traditional;
- if (traditional === undefined) {
- traditional = $.ajaxSettings.traditional;
- }
-
- var elements = [];
- var qx,
- a = this.formToArray(options.semantic, elements);
- if (options.data) {
- options.extraData = options.data;
- qx = $.param(options.data, traditional);
- }
-
- // give pre-submit callback an opportunity to abort the submit
-
- if (
- options.beforeSubmit &&
- options.beforeSubmit(a, this, options) === false
- ) {
- log('ajaxSubmit: submit aborted via beforeSubmit callback');
- return this;
- }
-
- // fire vetoable 'validate' event
-
- this.trigger('form-submit-validate', [a, this, options, veto]);
- if (veto.veto) {
- log(
- 'ajaxSubmit: submit vetoed via form-submit-validate trigger'
- );
- return this;
- }
-
- var q = $.param(a, traditional);
- if (qx) {
- q = q ? q + '&' + qx : qx;
- }
- 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 callbacks = [];
- if (options.resetForm) {
- callbacks.push(function () {
- $form.resetForm();
- });
- }
- if (options.clearForm) {
- callbacks.push(function () {
- $form.clearForm(options.includeHidden);
- });
- }
-
- // 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) {
- var fn = options.replaceTarget ? 'replaceWith' : 'html';
- $(options.target)[fn](data).each(oldSuccess, arguments);
- });
- }
- else if (options.success) {
- callbacks.push(options.success);
- }
-
- options.success = function (data, status, xhr) {
-
- // jQuery 1.4+ passes xhr as 3rd arg
- var context = options.context || this; // jQuery 1.4+ supports scope context
- for (var i = 0, max = callbacks.length; i < max; i++) {
- callbacks[i].apply(context, [
- data,
- status,
- xhr || $form,
- $form,
- ]);
- }
- };
-
- if (options.error) {
- var oldError = options.error;
- options.error = function (xhr, status, error) {
- var context = options.context || this;
- oldError.apply(context, [xhr, status, error, $form]);
- };
- }
-
- if (options.complete) {
- var oldComplete = options.complete;
- options.complete = function (xhr, status) {
- var context = options.context || this;
- oldComplete.apply(context, [xhr, status, $form]);
- };
- }
-
- // are there files to upload?
-
- // [value] (issue #113), also see comment:
- // https://github.com/malsup/form/commit/588306aedba1de01388032d5f42a60159eea9228#commitcomment-2180219
-
- var fileInputs = $('input[type=file]:enabled', this).filter(
- function () {
- return $(this).val() !== '';
- }
- );
-
- var hasFileInputs = fileInputs.length > 0;
- var mp = 'multipart/form-data';
- var multipart =
- $form.attr('enctype') == mp || $form.attr('encoding') == mp;
-
- var fileAPI = feature.fileapi && feature.formdata;
- log('fileAPI :' + fileAPI);
- var shouldUseFrame = (hasFileInputs || multipart) && !fileAPI;
-
- var jqxhr;
-
- // options.iframe allows user to force iframe mode
- // 06-NOV-09: now defaulting to iframe mode if file input is detected
-
- if (
- options.iframe !== false &&
- (options.iframe || shouldUseFrame)
- ) {
-
- // hack to fix Safari hang (thanks to Tim Molendijk for this)
- // see: http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d
-
- if (options.closeKeepAlive) {
- $.get(options.closeKeepAlive, function () {
- jqxhr = fileUploadIframe(a);
- });
- }
- else {
- jqxhr = fileUploadIframe(a);
- }
- }
- else if ((hasFileInputs || multipart) && fileAPI) {
- jqxhr = fileUploadXhr(a);
- }
- else {
- jqxhr = $.ajax(options);
- }
-
- $form.removeData('jqxhr').data('jqxhr', jqxhr);
-
- // clear element array
-
- for (var k = 0; k < elements.length; k++) {
- elements[k] = null;
- }
-
- // fire 'notify' event
-
- this.trigger('form-submit-notify', [this, options]);
- return this;
-
- // utility fn for deep serialization
-
- function deepSerialize(extraData) {
- var serialized = $.param(extraData, options.traditional).split(
- '&'
- );
- var len = serialized.length;
- var result = [];
- var i, part;
- for (i = 0; i < len; i++) {
-
- // #252; undo param space replacement
-
- serialized[i] = serialized[i].replace(/\+/g, ' ');
- part = serialized[i].split('=');
-
- // #278; use array instead of object storage, favoring array serializations
-
- result.push([
- decodeURIComponent(part[0]),
- decodeURIComponent(part[1]),
- ]);
- }
- return result;
- }
-
- // XMLHttpRequest Level 2 file uploads (big hat tip to francois2metz)
-
- function fileUploadXhr(a) {
- var formdata = new FormData();
-
- for (var i = 0; i < a.length; i++) {
- formdata.append(a[i].name, a[i].value);
- }
-
- if (options.extraData) {
- var serializedData = deepSerialize(options.extraData);
- for (i = 0; i < serializedData.length; i++) {
- if (serializedData[i]) {
- formdata.append(
- serializedData[i][0],
- serializedData[i][1]
- );
- }
- }
- }
-
- options.data = null;
-
- var s = $.extend(true, {}, $.ajaxSettings, options, {
- contentType: false,
- processData: false,
- cache: false,
- type: method || 'POST',
- });
-
- if (options.uploadProgress) {
-
- // workaround because jqXHR does not expose upload property
-
- s.xhr = function () {
- var xhr = $.ajaxSettings.xhr();
- if (xhr.upload) {
- xhr.upload.addEventListener(
- 'progress',
- function (event) {
- var percent = 0;
- var position =
- event.loaded ||
- event.position; /*event.position is deprecated*/
- var total = event.total;
- if (event.lengthComputable) {
- percent = Math.ceil(
- (position / total) * 100
- );
- }
- options.uploadProgress(
- event,
- position,
- total,
- percent
- );
- },
- false
- );
- }
- return xhr;
- };
- }
-
- s.data = null;
- var beforeSend = s.beforeSend;
- s.beforeSend = function (xhr, o) {
-
- //Send FormData() provided by user
-
- if (options.formData) {
- o.data = options.formData;
- }
- else {
- o.data = formdata;
- }
- if (beforeSend) {
- beforeSend.call(this, xhr, o);
- }
- };
- return $.ajax(s);
- }
-
- // private function for handling file uploads (hat tip to YAHOO!)
-
- function fileUploadIframe(a) {
- var form = $form[0],
- el,
- i,
- s,
- g,
- id,
- $io,
- io,
- xhr,
- sub,
- n,
- timedOut,
- timeoutHandle;
- var deferred = $.Deferred();
-
- // #341
-
- deferred.abort = function (status) {
- xhr.abort(status);
- };
-
- if (a) {
-
- // ensure that every serialized input is still enabled
-
- for (i = 0; i < elements.length; i++) {
- el = $(elements[i]);
- if (hasProp) {
- el.prop('disabled', false);
- }
- else {
- el.removeAttr('disabled');
- }
- }
- }
-
- s = $.extend(true, {}, $.ajaxSettings, options);
- s.context = s.context || s;
- id = 'jqFormIO' + new Date().getTime();
- if (s.iframeTarget) {
- $io = $(s.iframeTarget);
- n = $io.attr2('name');
- if (!n) {
- $io.attr2('name', id);
- }
- else {
- id = n;
- }
- }
- else {
- $io = $(
- '
'
- );
- $io.css({
- position: 'absolute',
- top: '-1000px',
- left: '-1000px',
- });
- }
- io = $io[0];
-
- xhr = {
-
- // mock object
-
- aborted: 0,
- responseText: null,
- responseXML: null,
- status: 0,
- statusText: 'n/a',
- getAllResponseHeaders() {},
- getResponseHeader() {},
- setRequestHeader() {},
- abort(status) {
- var e = status === 'timeout' ? 'timeout' : 'aborted';
- log('aborting upload... ' + e);
- this.aborted = 1;
-
- try {
-
- // #214, #257
-
- if (io.contentWindow.document.execCommand) {
- io.contentWindow.document.execCommand('Stop');
- }
- }
- catch (ignore) {}
-
- $io.attr('src', s.iframeSrc); // abort op in progress
- xhr.error = e;
- if (s.error) {
- s.error.call(s.context, xhr, e, status);
- }
- if (g) {
- $.event.trigger('ajaxError', [xhr, s, e]);
- }
- if (s.complete) {
- s.complete.call(s.context, xhr, e);
- }
- },
- };
-
- g = s.global;
-
- // trigger ajax global events so that activity/block indicators work like normal
-
- if (g && 0 === $.active++) {
- $.event.trigger('ajaxStart');
- }
- if (g) {
- $.event.trigger('ajaxSend', [xhr, s]);
- }
-
- if (
- s.beforeSend &&
- s.beforeSend.call(s.context, xhr, s) === false
- ) {
- if (s.global) {
- $.active--;
- }
- deferred.reject();
- return deferred;
- }
- if (xhr.aborted) {
- deferred.reject();
- return deferred;
- }
-
- // add submitting element to data if we know it
-
- sub = form.clk;
- if (sub) {
- n = sub.name;
- if (n && !sub.disabled) {
- s.extraData = s.extraData || {};
- s.extraData[n] = sub.value;
- if (sub.type == 'image') {
- s.extraData[n + '.x'] = form.clk_x;
- s.extraData[n + '.y'] = form.clk_y;
- }
- }
- }
-
- var CLIENT_TIMEOUT_ABORT = 1;
- var SERVER_ABORT = 2;
-
- function getDoc(frame) {
-
- /* it looks like contentWindow or contentDocument do not
- * carry the protocol property in ie8, when running under ssl
- * frame.document is the only valid response document, since
- * the protocol is know but not on the other two objects. strange?
- * "Same origin policy" http://en.wikipedia.org/wiki/Same_origin_policy
- */
-
- var doc = null;
-
- // IE8 cascading access check
-
- try {
- if (frame.contentWindow) {
- doc = frame.contentWindow.document;
- }
- }
- catch (err) {
-
- // IE8 access denied under ssl & missing protocol
-
- log('cannot get iframe.contentWindow document: ' + err);
- }
-
- if (doc) {
-
- // successful getting content
-
- return doc;
- }
-
- try {
-
- // simply checking may throw in ie8 under ssl or mismatched protocol
-
- doc = frame.contentDocument
- ? frame.contentDocument
- : frame.document;
- }
- catch (err) {
-
- // last attempt
-
- log('cannot get iframe.contentDocument: ' + err);
- doc = frame.document;
- }
- return doc;
- }
-
- // Rails CSRF hack (thanks to Yvan Barthelemy)
-
- var csrf_token = $('meta[name=csrf-token]').attr('content');
- var csrf_param = $('meta[name=csrf-param]').attr('content');
- if (csrf_param && csrf_token) {
- s.extraData = s.extraData || {};
- s.extraData[csrf_param] = csrf_token;
- }
-
- // take a breath so that pending repaints get some cpu time before the upload starts
-
- function doSubmit() {
-
- // make sure form attrs are set
-
- var t = $form.attr2('target'),
- a = $form.attr2('action'),
- mp = 'multipart/form-data',
- et =
- $form.attr('enctype') ||
- $form.attr('encoding') ||
- mp;
-
- // update form attrs in IE friendly way
-
- form.setAttribute('target', id);
- if (!method || /post/i.test(method)) {
- form.setAttribute('method', 'POST');
- }
- if (a != s.url) {
- form.setAttribute('action', s.url);
- }
-
- // ie borks in some cases when setting encoding
-
- if (
- !s.skipEncodingOverride &&
- (!method || /post/i.test(method))
- ) {
- $form.attr({
- encoding: 'multipart/form-data',
- enctype: 'multipart/form-data',
- });
- }
-
- // support timout
-
- if (s.timeout) {
- timeoutHandle = setTimeout(function () {
- timedOut = true;
- cb(CLIENT_TIMEOUT_ABORT);
- }, s.timeout);
- }
-
- // look for server aborts
-
- function checkState() {
- try {
- var state = getDoc(io).readyState;
- log('state = ' + state);
- if (
- state &&
- state.toLowerCase() == 'uninitialized'
- ) {
- setTimeout(checkState, 50);
- }
- }
- catch (e) {
- log('Server abort: ', e, ' (', e.name, ')');
- cb(SERVER_ABORT);
- if (timeoutHandle) {
- clearTimeout(timeoutHandle);
- }
- timeoutHandle = undefined;
- }
- }
-
- // add "extra" data to form if provided in options
-
- var extraInputs = [];
- try {
- if (s.extraData) {
- for (var n in s.extraData) {
- if (s.extraData.hasOwnProperty(n)) {
-
- // if using the $.param format that allows for multiple values with the same name
-
- if (
- $.isPlainObject(s.extraData[n]) &&
- s.extraData[n].hasOwnProperty('name') &&
- s.extraData[n].hasOwnProperty('value')
- ) {
- extraInputs.push(
- $(
- '
'
- )
- .val(s.extraData[n].value)
- .appendTo(form)[0]
- );
- }
- else {
- extraInputs.push(
- $(
- '
'
- )
- .val(s.extraData[n])
- .appendTo(form)[0]
- );
- }
- }
- }
- }
-
- if (!s.iframeTarget) {
-
- // add iframe to doc and submit the form
-
- $io.appendTo('body');
- }
- if (io.attachEvent) {
- io.attachEvent('onload', cb);
- }
- else {
- io.addEventListener('load', cb, false);
- }
- setTimeout(checkState, 15);
-
- try {
- form.submit();
- }
- catch (err) {
-
- // just in case form has element with name/id of 'submit'
-
- var submitFn = document.createElement('form')
- .submit;
- submitFn.apply(form);
- }
- }
- finally {
-
- // reset attrs and remove "extra" input elements
-
- form.setAttribute('action', a);
- form.setAttribute('enctype', et); // #380
- if (t) {
- form.setAttribute('target', t);
- }
- else {
- $form.removeAttr('target');
- }
- $(extraInputs).remove();
- }
- }
-
- if (s.forceSync) {
- doSubmit();
- }
- else {
- setTimeout(doSubmit, 10); // this lets dom updates render
- }
-
- var data,
- doc,
- domCheckCount = 50,
- callbackProcessed;
-
- function cb(e) {
- if (xhr.aborted || callbackProcessed) {
- return;
- }
-
- doc = getDoc(io);
- if (!doc) {
- log('cannot access response document');
- e = SERVER_ABORT;
- }
- if (e === CLIENT_TIMEOUT_ABORT && xhr) {
- xhr.abort('timeout');
- deferred.reject(xhr, 'timeout');
- return;
- }
- else if (e == SERVER_ABORT && xhr) {
- xhr.abort('server abort');
- deferred.reject(xhr, 'error', 'server abort');
- return;
- }
-
- if (!doc || doc.location.href == s.iframeSrc) {
-
- // response not received yet
-
- if (!timedOut) {
- return;
- }
- }
- if (io.detachEvent) {
- io.detachEvent('onload', cb);
- }
- else {
- io.removeEventListener('load', cb, false);
- }
-
- var status = 'success',
- errMsg;
- try {
- if (timedOut) {
- throw 'timeout';
- }
-
- var isXml =
- s.dataType == 'xml' ||
- doc.XMLDocument ||
- $.isXMLDoc(doc);
- log('isXml=' + isXml);
- if (
- !isXml &&
- window.opera &&
- (doc.body === null || !doc.body.innerHTML)
- ) {
- if (--domCheckCount) {
-
- // in some browsers (Opera) the iframe DOM is not always traversable when
- // the onload callback fires, so we loop a bit to accommodate
-
- log(
- 'requeing onLoad callback, DOM not available'
- );
- setTimeout(cb, 250);
- return;
- }
-
- // let this fall through because server response could be an empty document
- //log('Could not access iframe DOM after mutiple tries.');
- //throw 'DOMException: not available';
-
- }
-
- //log('response detected');
-
- var docRoot = doc.body ? doc.body : doc.documentElement;
- xhr.responseText = docRoot ? docRoot.innerHTML : null;
- xhr.responseXML = doc.XMLDocument
- ? doc.XMLDocument
- : doc;
- if (isXml) {
- s.dataType = 'xml';
- }
- xhr.getResponseHeader = function (header) {
- var headers = {'content-type': s.dataType};
- return headers[header.toLowerCase()];
- };
-
- // support for XHR 'status' & 'statusText' emulation :
-
- if (docRoot) {
- xhr.status =
- Number(docRoot.getAttribute('status')) ||
- xhr.status;
- xhr.statusText =
- docRoot.getAttribute('statusText') ||
- xhr.statusText;
- }
-
- var dt = (s.dataType || '').toLowerCase();
- var scr = /(json|script|text)/.test(dt);
- if (scr || s.textarea) {
-
- // see if user embedded response in textarea
-
- var ta = doc.getElementsByTagName('textarea')[0];
- if (ta) {
- xhr.responseText = ta.value;
-
- // support for XHR 'status' & 'statusText' emulation :
-
- xhr.status =
- Number(ta.getAttribute('status')) ||
- xhr.status;
- xhr.statusText =
- ta.getAttribute('statusText') ||
- xhr.statusText;
- }
- else if (scr) {
-
- // account for browsers injecting pre around json response
-
- var pre = doc.getElementsByTagName('pre')[0];
- var b = doc.getElementsByTagName('body')[0];
- if (pre) {
- xhr.responseText = pre.textContent
- ? pre.textContent
- : pre.innerText;
- }
- else if (b) {
- xhr.responseText = b.textContent
- ? b.textContent
- : b.innerText;
- }
- }
- }
- else if (
- dt == 'xml' &&
- !xhr.responseXML &&
- xhr.responseText
- ) {
- xhr.responseXML = toXml(xhr.responseText);
- }
-
- try {
- data = httpData(xhr, dt, s);
- }
- catch (err) {
- status = 'parsererror';
- xhr.error = errMsg = err || status;
- }
- }
- catch (err) {
- log('error caught: ', err);
- status = 'error';
- xhr.error = errMsg = err || status;
- }
-
- if (xhr.aborted) {
- log('upload aborted');
- status = null;
- }
-
- if (xhr.status) {
-
- // we've set xhr.status
-
- status =
- (xhr.status >= 200 && xhr.status < 300) ||
- xhr.status === 304
- ? 'success'
- : 'error';
- }
-
- // ordering of these callbacks/triggers is odd, but that's how $.ajax does it
-
- if (status === 'success') {
- if (s.success) {
- s.success.call(s.context, data, 'success', xhr);
- }
- deferred.resolve(xhr.responseText, 'success', xhr);
- if (g) {
- $.event.trigger('ajaxSuccess', [xhr, s]);
- }
- }
- else if (status) {
- if (errMsg === undefined) {
- errMsg = xhr.statusText;
- }
- if (s.error) {
- s.error.call(s.context, xhr, status, errMsg);
- }
- deferred.reject(xhr, 'error', errMsg);
- if (g) {
- $.event.trigger('ajaxError', [xhr, s, errMsg]);
- }
- }
-
- if (g) {
- $.event.trigger('ajaxComplete', [xhr, s]);
- }
-
- if (g && !--$.active) {
- $.event.trigger('ajaxStop');
- }
-
- if (s.complete) {
- s.complete.call(s.context, xhr, status);
- }
-
- callbackProcessed = true;
- if (s.timeout) {
- clearTimeout(timeoutHandle);
- }
-
- // clean up
-
- setTimeout(function () {
- if (!s.iframeTarget) {
- $io.remove();
- }
- else {
-
- //adding else to clean up existing iframe response.
-
- $io.attr('src', s.iframeSrc);
- }
- xhr.responseXML = null;
- }, 100);
- }
-
- var toXml =
- $.parseXML ||
- function (s, doc) {
-
- // use parseXML if available (jQuery 1.5+)
-
- 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.nodeName != 'parsererror'
- ? doc
- : null;
- };
- var parseJSON =
- $.parseJSON ||
- function (s) {
- /*jslint evil:true */
- return window['eval']('(' + s + ')');
- };
-
- var httpData = function (xhr, type, s) {
-
- // mostly lifted from jq1.4.4
-
- var ct = xhr.getResponseHeader('content-type') || '',
- xml =
- type === 'xml' || (!type && ct.indexOf('xml') >= 0),
- data = xml ? xhr.responseXML : xhr.responseText;
-
- if (
- xml &&
- data.documentElement.nodeName === 'parsererror'
- ) {
- if ($.error) {
- $.error('parsererror');
- }
- }
- if (s && s.dataFilter) {
- data = s.dataFilter(data, type);
- }
- if (typeof data === 'string') {
- if (
- type === 'json' ||
- (!type && ct.indexOf('json') >= 0)
- ) {
- data = parseJSON(data);
- }
- else if (
- type === 'script' ||
- (!type && ct.indexOf('javascript') >= 0)
- ) {
- $.globalEval(data);
- }
- }
- return data;
- };
-
- return deferred;
- }
- };
-
- /**
- * 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
elements (if the element
- * is used to submit the form).
- * 2. This method will include the submit element's name/value data (for the element that was
- * used to submit the form).
- * 3. This method binds the submit() method to the form for you.
- *
- * The options argument for ajaxForm works exactly as it does for ajaxSubmit. ajaxForm merely
- * passes the options argument along after properly binding events for submit elements and
- * the form itself.
- */
- $.fn.ajaxForm = function (options) {
- options = options || {};
- options.delegation = options.delegation && $.isFunction($.fn.on);
-
- // in jQuery 1.3+ we can fix mistakes with the ready state
-
- if (!options.delegation && this.length === 0) {
- var o = {s: this.selector, c: this.context};
- if (!$.isReady && o.s) {
- log('DOM not ready, queuing ajaxForm');
- $(function () {
- $(o.s, o.c).ajaxForm(options);
- });
- return this;
- }
-
- // is your DOM ready? http://docs.jquery.com/Tutorials:Introducing_$(document).ready()
-
- log(
- 'terminating; zero elements found by selector' +
- ($.isReady ? '' : ' (DOM not ready)')
- );
- return this;
- }
-
- if (options.delegation) {
- $(document)
- .off('submit.form-plugin', this.selector, doAjaxSubmit)
- .off(
- 'click.form-plugin',
- this.selector,
- captureSubmittingElement
- )
- .on(
- 'submit.form-plugin',
- this.selector,
- options,
- doAjaxSubmit
- )
- .on(
- 'click.form-plugin',
- this.selector,
- options,
- captureSubmittingElement
- );
- return this;
- }
-
- return this.ajaxFormUnbind()
- .bind('submit.form-plugin', options, doAjaxSubmit)
- .bind('click.form-plugin', options, captureSubmittingElement);
- };
-
- // private event handlers
-
- function doAjaxSubmit(e) {
- /*jshint validthis:true */
- var options = e.data;
- if (!e.isDefaultPrevented()) {
-
- // if event has been canceled, don't proceed
-
- e.preventDefault();
- $(e.target).ajaxSubmit(options); // #365
- }
- }
-
- function captureSubmittingElement(e) {
- /*jshint validthis:true */
- var target = e.target;
- var $el = $(target);
- if (!$el.is('[type=submit],[type=image]')) {
-
- // is this a child element of the submit el? (ex: a span within a button)
-
- var t = $el.closest('[type=submit]');
- if (t.length === 0) {
- return;
- }
- target = t[0];
- }
- var form = this;
- form.clk = target;
- if (target.type == 'image') {
- if (e.offsetX !== undefined) {
- form.clk_x = e.offsetX;
- form.clk_y = e.offsetY;
- }
- else if (typeof $.fn.offset == 'function') {
- var offset = $el.offset();
- form.clk_x = e.pageX - offset.left;
- form.clk_y = e.pageY - offset.top;
- }
- else {
- form.clk_x = e.pageX - target.offsetLeft;
- form.clk_y = e.pageY - target.offsetTop;
- }
- }
-
- // clear form vars
-
- setTimeout(function () {
- form.clk = form.clk_x = form.clk_y = null;
- }, 100);
- }
-
- // ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm
-
- $.fn.ajaxFormUnbind = function () {
- return this.unbind('submit.form-plugin click.form-plugin');
- };
-
- /**
- * formToArray() gathers form element data into an array of objects that can
- * be passed to any of the following ajax functions: $.get, $.post, or load.
- * Each object in the array has both a 'name' and 'value' property. An example of
- * an array for a simple login form might be:
- *
- * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
- *
- * It is this array that is passed to pre-submit callback functions provided to the
- * ajaxSubmit() and ajaxForm() methods.
- */
- $.fn.formToArray = function (semantic, elements) {
- var a = [];
- if (this.length === 0) {
- return a;
- }
-
- var form = this[0];
- var formId = this.attr('id');
- var els = semantic ? form.getElementsByTagName('*') : form.elements;
- var els2;
-
- if (els && !/MSIE [678]/.test(navigator.userAgent)) {
-
- // #390
- els = $(els).get(); // convert to standard array
- }
-
- // #386; account for inputs outside the form which use the 'form' attribute
-
- if (formId) {
- els2 = $(':input[form="' + formId + '"]').get(); // hat tip @thet
- if (els2.length) {
- els = (els || []).concat(els2);
- }
- }
-
- if (!els || !els.length) {
- return a;
- }
-
- var i, j, n, v, el, max, jmax;
- for (i = 0, max = els.length; i < max; i++) {
- el = els[i];
- n = el.name;
- if (!n || el.disabled) {
- continue;
- }
-
- if (semantic && form.clk && el.type == 'image') {
-
- // handle image inputs on the fly when semantic == true
-
- if (form.clk == el) {
- a.push({name: n, value: $(el).val(), type: el.type});
- a.push(
- {name: n + '.x', value: form.clk_x},
- {name: n + '.y', value: form.clk_y}
- );
- }
- continue;
- }
-
- v = $.fieldValue(el, true);
- if (v && v.constructor == Array) {
- if (elements) {
- elements.push(el);
- }
- for (j = 0, jmax = v.length; j < jmax; j++) {
- a.push({name: n, value: v[j]});
- }
- }
- else if (feature.fileapi && el.type == 'file') {
- if (elements) {
- elements.push(el);
- }
- var files = el.files;
- if (files.length) {
- for (j = 0; j < files.length; j++) {
- a.push({name: n, value: files[j], type: el.type});
- }
- }
- else {
-
- // #180
-
- a.push({name: n, value: '', type: el.type});
- }
- }
- else if (v !== null && typeof v != 'undefined') {
- if (elements) {
- elements.push(el);
- }
- a.push({
- name: n,
- value: v,
- type: el.type,
- required: el.required,
- });
- }
- }
-
- if (!semantic && form.clk) {
-
- // input type=='image' are not found in elements array! handle it here
-
- var $input = $(form.clk),
- input = $input[0];
- n = input.name;
- if (n && !input.disabled && input.type == 'image') {
- a.push({name: n, value: $input.val()});
- 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&name2=value2
- */
- $.fn.formSerialize = function (semantic) {
-
- //hand off to jQuery.param for proper encoding
-
- return $.param(this.formToArray(semantic));
- };
-
- /**
- * Serializes all field elements in the jQuery object into a query string.
- * This method will return a string in the format: name1=value1&name2=value2
- */
- $.fn.fieldSerialize = function (successful) {
- var a = [];
- this.each(function () {
- var n = this.name;
- if (!n) {
- return;
- }
- var v = $.fieldValue(this, successful);
- if (v && v.constructor == Array) {
- for (var i = 0, max = v.length; i < max; i++) {
- a.push({name: n, value: v[i]});
- }
- }
- else if (v !== null && typeof v != 'undefined') {
- a.push({name: this.name, value: v});
- }
- });
-
- //hand off to jQuery.param for proper encoding
-
- return $.param(a);
- };
-
- /**
- * Returns the value(s) of the element in the matched set. For example, consider the following form:
- *
- *
- *
- * var v = $('input[type=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 = $('input[type=checkbox]').fieldValue();
- * // if neither checkbox is checked
- * v === undefined
- * // if both checkboxes are checked
- * v == ['B1', 'B2']
- *
- * var v = $('input[type=radio]').fieldValue();
- * // if neither radio is checked
- * v === undefined
- * // if first radio is checked
- * v == ['C1']
- *
- * The successful argument controls whether or not the field element must be 'successful'
- * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
- * The default value of the successful argument is true. If this value is false the value(s)
- * for each element is returned.
- *
- * Note: This method *always* returns an array. If no valid value can be determined the
- * array will be empty, otherwise it will contain one or more values.
- */
- $.fn.fieldValue = function (successful) {
- for (var val = [], i = 0, max = this.length; i < max; i++) {
- var el = this[i];
- var v = $.fieldValue(el, successful);
- if (
- v === null ||
- typeof v == 'undefined' ||
- (v.constructor == Array && !v.length)
- ) {
- continue;
- }
- if (v.constructor == Array) {
- $.merge(val, v);
- }
- else {
- val.push(v);
- }
- }
- return val;
- };
-
- /**
- * Returns the value of the field element.
- */
- $.fieldValue = function (el, successful) {
- var n = el.name,
- t = el.type,
- tag = el.tagName.toLowerCase();
- if (successful === undefined) {
- successful = true;
- }
-
- if (
- successful &&
- (!n ||
- el.disabled ||
- t == 'reset' ||
- t == 'button' ||
- ((t == 'checkbox' || t == 'radio') && !el.checked) ||
- ((t == 'submit' || t == 'image') &&
- el.form &&
- el.form.clk != el) ||
- (tag == 'select' && el.selectedIndex == -1))
- ) {
- return null;
- }
-
- if (tag == 'select') {
- var index = el.selectedIndex;
- if (index < 0) {
- return null;
- }
- var a = [],
- ops = el.options;
- var one = t == 'select-one';
- var max = one ? index + 1 : ops.length;
- for (var i = one ? index : 0; i < max; i++) {
- var op = ops[i];
- if (op.selected) {
- var v = op.value;
- if (!v) {
-
- // extra pain for IE...
-
- v =
- op.attributes &&
- op.attributes.value &&
- !op.attributes.value.specified
- ? op.text
- : op.value;
- }
- if (one) {
- return v;
- }
- a.push(v);
- }
- }
- return a;
- }
- return $(el).val();
- };
-
- /**
- * Clears the form data. Takes the following actions on the form's input fields:
- * - input text fields will have their 'value' property set to the empty string
- * - select elements will have their 'selectedIndex' property set to -1
- * - checkbox and radio inputs will have their 'checked' property set to false
- * - inputs of type submit, button, reset, and hidden will *not* be effected
- * - button elements will *not* be effected
- */
- $.fn.clearForm = function (includeHidden) {
- return this.each(function () {
- $('input,select,textarea', this).clearFields(includeHidden);
- });
- };
-
- /**
- * Clears the selected form elements.
- */
- $.fn.clearFields = $.fn.clearInputs = function (includeHidden) {
- var re = /^(?:color|date|datetime|email|month|number|password|range|search|tel|text|time|url|week)$/i; // 'hidden' is not in this list
- return this.each(function () {
- var t = this.type,
- tag = this.tagName.toLowerCase();
- if (re.test(t) || tag == 'textarea') {
- this.value = '';
- }
- else if (t == 'checkbox' || t == 'radio') {
- this.checked = false;
- }
- else if (tag == 'select') {
- this.selectedIndex = -1;
- }
- else if (t == 'file') {
- if (/MSIE/.test(navigator.userAgent)) {
- $(this).replaceWith($(this).clone(true));
- }
- else {
- $(this).val('');
- }
- }
- else if (includeHidden) {
-
- // includeHidden can be the value true, or it can be a selector string
- // indicating a special test; for example:
- // $('#myForm').clearForm('.special:hidden')
- // the above would clean hidden inputs that have the class of 'special'
-
- if (
- (includeHidden === true && /hidden/.test(t)) ||
- (typeof includeHidden == 'string' &&
- $(this).is(includeHidden))
- ) {
- this.value = '';
- }
- }
- });
- };
-
- /**
- * Resets the form data. Causes all form elements to be reset to their original value.
- */
- $.fn.resetForm = function () {
- return this.each(function () {
-
- // guard against an input with the name of 'reset'
- // note that IE reports the reset function as an 'object'
-
- if (
- typeof this.reset == 'function' ||
- (typeof this.reset == 'object' && !this.reset.nodeType)
- ) {
- this.reset();
- }
- });
- };
-
- /**
- * Enables or disables any matching elements.
- */
- $.fn.enable = function (b) {
- if (b === undefined) {
- b = true;
- }
- return this.each(function () {
- this.disabled = !b;
- });
- };
-
- /**
- * Checks/unchecks any matching checkboxes or radio buttons and
- * selects/deselects and matching option elements.
- */
- $.fn.selected = function (select) {
- if (select === undefined) {
- select = true;
- }
- return this.each(function () {
- var t = this.type;
- if (t == 'checkbox' || t == 'radio') {
- this.checked = select;
- }
- else if (this.tagName.toLowerCase() == 'option') {
- var $sel = $(this).parent('select');
- if (select && $sel[0] && $sel[0].type == 'select-one') {
-
- // deselect all other options
-
- $sel.find('option').selected(false);
- }
- this.selected = select;
- }
- });
- };
-
- // expose debug var
-
- $.fn.ajaxSubmit.debug = false;
-
- // helper fn for console logging
-
- function log() {
- if (!$.fn.ajaxSubmit.debug) {
- return;
- }
- var msg =
- '[jquery.form] ' + Array.prototype.join.call(arguments, '');
- if (window.console && window.console.log) {
- window.console.log(msg);
- }
- else if (window.opera && window.opera.postError) {
- window.opera.postError(msg);
- }
- }
- });
-})(window.$);
+/**
+ * Feature detection
+ */
+var feature = {};
+feature.fileapi = $("
").get(0).files !== undefined;
+feature.formdata = window.FormData !== undefined;
+
+var hasProp = !!$.fn.prop;
+
+// attr2 uses prop when it can but checks the return type for
+// an expected string. this accounts for the case where a form
+// contains inputs with names like "action" or "method"; in those
+// cases "prop" returns the element
+$.fn.attr2 = function() {
+ if ( ! hasProp ) {
+ return this.attr.apply(this, arguments);
+ }
+ var val = this.prop.apply(this, arguments);
+ if ( ( val && val.jquery ) || typeof val === 'string' ) {
+ return val;
+ }
+ return this.attr.apply(this, arguments);
+};
+
+/**
+ * ajaxSubmit() provides a mechanism for immediately submitting
+ * an HTML form using AJAX.
+ */
+$.fn.ajaxSubmit = function(options) {
+ /*jshint scripturl:true */
+
+ // fast fail if nothing selected (http://dev.jquery.com/ticket/2752)
+ if (!this.length) {
+ log('ajaxSubmit: skipping submit process - no element selected');
+ return this;
+ }
+
+ var method, action, url, $form = this;
+
+ if (typeof options == 'function') {
+ options = { success: options };
+ }
+ else if ( options === undefined ) {
+ options = {};
+ }
+
+ method = options.type || this.attr2('method');
+ action = options.url || this.attr2('action');
+
+ url = (typeof action === 'string') ? $.trim(action) : '';
+ url = url || window.location.href || '';
+ if (url) {
+ // clean url (don't include hash vaue)
+ url = (url.match(/^([^#]+)/)||[])[1];
+ }
+
+ options = $.extend(true, {
+ url: url,
+ success: $.ajaxSettings.success,
+ type: method || $.ajaxSettings.type,
+ iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank'
+ }, options);
+
+ // hook for manipulating the form data before it is extracted;
+ // convenient for use with rich editors like tinyMCE or FCKEditor
+ var veto = {};
+ this.trigger('form-pre-serialize', [this, options, veto]);
+ if (veto.veto) {
+ log('ajaxSubmit: submit vetoed via form-pre-serialize trigger');
+ return this;
+ }
+
+ // provide opportunity to alter form data before it is serialized
+ if (options.beforeSerialize && options.beforeSerialize(this, options) === false) {
+ log('ajaxSubmit: submit aborted via beforeSerialize callback');
+ return this;
+ }
+
+ var traditional = options.traditional;
+ if ( traditional === undefined ) {
+ traditional = $.ajaxSettings.traditional;
+ }
+
+ var elements = [];
+ var qx, a = this.formToArray(options.semantic, elements);
+ if (options.data) {
+ options.extraData = options.data;
+ qx = $.param(options.data, traditional);
+ }
+
+ // give pre-submit callback an opportunity to abort the submit
+ if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) {
+ log('ajaxSubmit: submit aborted via beforeSubmit callback');
+ return this;
+ }
+
+ // fire vetoable 'validate' event
+ this.trigger('form-submit-validate', [a, this, options, veto]);
+ if (veto.veto) {
+ log('ajaxSubmit: submit vetoed via form-submit-validate trigger');
+ return this;
+ }
+
+ var q = $.param(a, traditional);
+ if (qx) {
+ q = ( q ? (q + '&' + qx) : qx );
+ }
+ 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 callbacks = [];
+ if (options.resetForm) {
+ callbacks.push(function() { $form.resetForm(); });
+ }
+ if (options.clearForm) {
+ callbacks.push(function() { $form.clearForm(options.includeHidden); });
+ }
+
+ // 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) {
+ var fn = options.replaceTarget ? 'replaceWith' : 'html';
+
+ data = options.replaceTarget
+ ? data
+ : $.parseHTML($('
').text(data).html());
+
+ $(options.target)[fn](data).each(oldSuccess, arguments);
+ });
+ }
+ else if (options.success) {
+ callbacks.push(options.success);
+ }
+
+ options.success = function(data, status, xhr) { // jQuery 1.4+ passes xhr as 3rd arg
+ var context = options.context || this ; // jQuery 1.4+ supports scope context
+ for (var i=0, max=callbacks.length; i < max; i++) {
+ callbacks[i].apply(context, [data, status, xhr || $form, $form]);
+ }
+ };
+
+ if (options.error) {
+ var oldError = options.error;
+ options.error = function(xhr, status, error) {
+ var context = options.context || this;
+ oldError.apply(context, [xhr, status, error, $form]);
+ };
+ }
+
+ if (options.complete) {
+ var oldComplete = options.complete;
+ options.complete = function(xhr, status) {
+ var context = options.context || this;
+ oldComplete.apply(context, [xhr, status, $form]);
+ };
+ }
+
+ // are there files to upload?
+
+ // [value] (issue #113), also see comment:
+ // https://github.com/malsup/form/commit/588306aedba1de01388032d5f42a60159eea9228#commitcomment-2180219
+ var fileInputs = $('input[type=file]:enabled', this).filter(function() { return $(this).val() !== ''; });
+
+ var hasFileInputs = fileInputs.length > 0;
+ var mp = 'multipart/form-data';
+ var multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp);
+
+ var fileAPI = feature.fileapi && feature.formdata;
+ log("fileAPI :" + fileAPI);
+ var shouldUseFrame = (hasFileInputs || multipart) && !fileAPI;
+
+ var jqxhr;
+
+ // options.iframe allows user to force iframe mode
+ // 06-NOV-09: now defaulting to iframe mode if file input is detected
+ if (options.iframe !== false && (options.iframe || shouldUseFrame)) {
+ // hack to fix Safari hang (thanks to Tim Molendijk for this)
+ // see: http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d
+ if (options.closeKeepAlive) {
+ $.get(options.closeKeepAlive, function() {
+ jqxhr = fileUploadIframe(a);
+ });
+ }
+ else {
+ jqxhr = fileUploadIframe(a);
+ }
+ }
+ else if ((hasFileInputs || multipart) && fileAPI) {
+ jqxhr = fileUploadXhr(a);
+ }
+ else {
+ jqxhr = $.ajax(options);
+ }
+
+ $form.removeData('jqxhr').data('jqxhr', jqxhr);
+
+ // clear element array
+ for (var k=0; k < elements.length; k++) {
+ elements[k] = null;
+ }
+
+ // fire 'notify' event
+ this.trigger('form-submit-notify', [this, options]);
+ return this;
+
+ // utility fn for deep serialization
+ function deepSerialize(extraData){
+ var serialized = $.param(extraData, options.traditional).split('&');
+ var len = serialized.length;
+ var result = [];
+ var i, part;
+ for (i=0; i < len; i++) {
+ // #252; undo param space replacement
+ serialized[i] = serialized[i].replace(/\+/g,' ');
+ part = serialized[i].split('=');
+ // #278; use array instead of object storage, favoring array serializations
+ result.push([decodeURIComponent(part[0]), decodeURIComponent(part[1])]);
+ }
+ return result;
+ }
+
+ // XMLHttpRequest Level 2 file uploads (big hat tip to francois2metz)
+ function fileUploadXhr(a) {
+ var formdata = new FormData();
+
+ for (var i=0; i < a.length; i++) {
+ formdata.append(a[i].name, a[i].value);
+ }
+
+ if (options.extraData) {
+ var serializedData = deepSerialize(options.extraData);
+ for (i=0; i < serializedData.length; i++) {
+ if (serializedData[i]) {
+ formdata.append(serializedData[i][0], serializedData[i][1]);
+ }
+ }
+ }
+
+ options.data = null;
+
+ var s = $.extend(true, {}, $.ajaxSettings, options, {
+ contentType: false,
+ processData: false,
+ cache: false,
+ type: method || 'POST'
+ });
+
+ if (options.uploadProgress) {
+ // workaround because jqXHR does not expose upload property
+ s.xhr = function() {
+ var xhr = $.ajaxSettings.xhr();
+ if (xhr.upload) {
+ xhr.upload.addEventListener('progress', function(event) {
+ var percent = 0;
+ var position = event.loaded || event.position; /*event.position is deprecated*/
+ var total = event.total;
+ if (event.lengthComputable) {
+ percent = Math.ceil(position / total * 100);
+ }
+ options.uploadProgress(event, position, total, percent);
+ }, false);
+ }
+ return xhr;
+ };
+ }
+
+ s.data = null;
+ var beforeSend = s.beforeSend;
+ s.beforeSend = function(xhr, o) {
+ //Send FormData() provided by user
+ if (options.formData) {
+ o.data = options.formData;
+ }
+ else {
+ o.data = formdata;
+ }
+ if(beforeSend) {
+ beforeSend.call(this, xhr, o);
+ }
+ };
+ return $.ajax(s);
+ }
+
+ // private function for handling file uploads (hat tip to YAHOO!)
+ function fileUploadIframe(a) {
+ var form = $form[0], el, i, s, g, id, $io, io, xhr, sub, n, timedOut, timeoutHandle;
+ var deferred = $.Deferred();
+
+ // #341
+ deferred.abort = function(status) {
+ xhr.abort(status);
+ };
+
+ if (a) {
+ // ensure that every serialized input is still enabled
+ for (i=0; i < elements.length; i++) {
+ el = $(elements[i]);
+ if ( hasProp ) {
+ el.prop('disabled', false);
+ }
+ else {
+ el.removeAttr('disabled');
+ }
+ }
+ }
+
+ s = $.extend(true, {}, $.ajaxSettings, options);
+ s.context = s.context || s;
+ id = 'jqFormIO' + (new Date().getTime());
+ if (s.iframeTarget) {
+ $io = $(s.iframeTarget);
+ n = $io.attr2('name');
+ if (!n) {
+ $io.attr2('name', id);
+ }
+ else {
+ id = n;
+ }
+ }
+ else {
+ $io = $('');
+ $io.css({ position: 'absolute', top: '-1000px', left: '-1000px' });
+ }
+ io = $io[0];
+
+
+ xhr = { // mock object
+ aborted: 0,
+ responseText: null,
+ responseXML: null,
+ status: 0,
+ statusText: 'n/a',
+ getAllResponseHeaders: function() {},
+ getResponseHeader: function() {},
+ setRequestHeader: function() {},
+ abort: function(status) {
+ var e = (status === 'timeout' ? 'timeout' : 'aborted');
+ log('aborting upload... ' + e);
+ this.aborted = 1;
+
+ try { // #214, #257
+ if (io.contentWindow.document.execCommand) {
+ io.contentWindow.document.execCommand('Stop');
+ }
+ }
+ catch(ignore) {}
+
+ $io.attr('src', s.iframeSrc); // abort op in progress
+ xhr.error = e;
+ if (s.error) {
+ s.error.call(s.context, xhr, e, status);
+ }
+ if (g) {
+ $.event.trigger("ajaxError", [xhr, s, e]);
+ }
+ if (s.complete) {
+ s.complete.call(s.context, xhr, e);
+ }
+ }
+ };
+
+ g = s.global;
+ // trigger ajax global events so that activity/block indicators work like normal
+ if (g && 0 === $.active++) {
+ $.event.trigger("ajaxStart");
+ }
+ if (g) {
+ $.event.trigger("ajaxSend", [xhr, s]);
+ }
+
+ if (s.beforeSend && s.beforeSend.call(s.context, xhr, s) === false) {
+ if (s.global) {
+ $.active--;
+ }
+ deferred.reject();
+ return deferred;
+ }
+ if (xhr.aborted) {
+ deferred.reject();
+ return deferred;
+ }
+
+ // add submitting element to data if we know it
+ sub = form.clk;
+ if (sub) {
+ n = sub.name;
+ if (n && !sub.disabled) {
+ s.extraData = s.extraData || {};
+ s.extraData[n] = sub.value;
+ if (sub.type == "image") {
+ s.extraData[n+'.x'] = form.clk_x;
+ s.extraData[n+'.y'] = form.clk_y;
+ }
+ }
+ }
+
+ var CLIENT_TIMEOUT_ABORT = 1;
+ var SERVER_ABORT = 2;
+
+ function getDoc(frame) {
+ /* it looks like contentWindow or contentDocument do not
+ * carry the protocol property in ie8, when running under ssl
+ * frame.document is the only valid response document, since
+ * the protocol is know but not on the other two objects. strange?
+ * "Same origin policy" http://en.wikipedia.org/wiki/Same_origin_policy
+ */
+
+ var doc = null;
+
+ // IE8 cascading access check
+ try {
+ if (frame.contentWindow) {
+ doc = frame.contentWindow.document;
+ }
+ } catch(err) {
+ // IE8 access denied under ssl & missing protocol
+ log('cannot get iframe.contentWindow document: ' + err);
+ }
+
+ if (doc) { // successful getting content
+ return doc;
+ }
+
+ try { // simply checking may throw in ie8 under ssl or mismatched protocol
+ doc = frame.contentDocument ? frame.contentDocument : frame.document;
+ } catch(err) {
+ // last attempt
+ log('cannot get iframe.contentDocument: ' + err);
+ doc = frame.document;
+ }
+ return doc;
+ }
+
+ // Rails CSRF hack (thanks to Yvan Barthelemy)
+ var csrf_token = $('meta[name=csrf-token]').attr('content');
+ var csrf_param = $('meta[name=csrf-param]').attr('content');
+ if (csrf_param && csrf_token) {
+ s.extraData = s.extraData || {};
+ s.extraData[csrf_param] = csrf_token;
+ }
+
+ // take a breath so that pending repaints get some cpu time before the upload starts
+ function doSubmit() {
+ // make sure form attrs are set
+ var t = $form.attr2('target'),
+ a = $form.attr2('action'),
+ mp = 'multipart/form-data',
+ et = $form.attr('enctype') || $form.attr('encoding') || mp;
+
+ // update form attrs in IE friendly way
+ form.setAttribute('target',id);
+ if (!method || /post/i.test(method) ) {
+ form.setAttribute('method', 'POST');
+ }
+ if (a != s.url) {
+ form.setAttribute('action', s.url);
+ }
+
+ // ie borks in some cases when setting encoding
+ if (! s.skipEncodingOverride && (!method || /post/i.test(method))) {
+ $form.attr({
+ encoding: 'multipart/form-data',
+ enctype: 'multipart/form-data'
+ });
+ }
+
+ // support timout
+ if (s.timeout) {
+ timeoutHandle = setTimeout(function() { timedOut = true; cb(CLIENT_TIMEOUT_ABORT); }, s.timeout);
+ }
+
+ // look for server aborts
+ function checkState() {
+ try {
+ var state = getDoc(io).readyState;
+ log('state = ' + state);
+ if (state && state.toLowerCase() == 'uninitialized') {
+ setTimeout(checkState,50);
+ }
+ }
+ catch(e) {
+ log('Server abort: ' , e, ' (', e.name, ')');
+ cb(SERVER_ABORT);
+ if (timeoutHandle) {
+ clearTimeout(timeoutHandle);
+ }
+ timeoutHandle = undefined;
+ }
+ }
+
+ // add "extra" data to form if provided in options
+ var extraInputs = [];
+ try {
+ if (s.extraData) {
+ for (var n in s.extraData) {
+ if (s.extraData.hasOwnProperty(n)) {
+ // if using the $.param format that allows for multiple values with the same name
+ if($.isPlainObject(s.extraData[n]) && s.extraData[n].hasOwnProperty('name') && s.extraData[n].hasOwnProperty('value')) {
+ extraInputs.push(
+ $('').val(s.extraData[n].value)
+ .appendTo(form)[0]);
+ } else {
+ extraInputs.push(
+ $('').val(s.extraData[n])
+ .appendTo(form)[0]);
+ }
+ }
+ }
+ }
+
+ if (!s.iframeTarget) {
+ // add iframe to doc and submit the form
+ $io.appendTo('body');
+ }
+ if (io.attachEvent) {
+ io.attachEvent('onload', cb);
+ }
+ else {
+ io.addEventListener('load', cb, false);
+ }
+ setTimeout(checkState,15);
+
+ try {
+ form.submit();
+ } catch(err) {
+ // just in case form has element with name/id of 'submit'
+ var submitFn = document.createElement('form').submit;
+ submitFn.apply(form);
+ }
+ }
+ finally {
+ // reset attrs and remove "extra" input elements
+ form.setAttribute('action',a);
+ form.setAttribute('enctype', et); // #380
+ if(t) {
+ form.setAttribute('target', t);
+ } else {
+ $form.removeAttr('target');
+ }
+ $(extraInputs).remove();
+ }
+ }
+
+ if (s.forceSync) {
+ doSubmit();
+ }
+ else {
+ setTimeout(doSubmit, 10); // this lets dom updates render
+ }
+
+ var data, doc, domCheckCount = 50, callbackProcessed;
+
+ function cb(e) {
+ if (xhr.aborted || callbackProcessed) {
+ return;
+ }
+
+ doc = getDoc(io);
+ if(!doc) {
+ log('cannot access response document');
+ e = SERVER_ABORT;
+ }
+ if (e === CLIENT_TIMEOUT_ABORT && xhr) {
+ xhr.abort('timeout');
+ deferred.reject(xhr, 'timeout');
+ return;
+ }
+ else if (e == SERVER_ABORT && xhr) {
+ xhr.abort('server abort');
+ deferred.reject(xhr, 'error', 'server abort');
+ return;
+ }
+
+ if (!doc || doc.location.href == s.iframeSrc) {
+ // response not received yet
+ if (!timedOut) {
+ return;
+ }
+ }
+ if (io.detachEvent) {
+ io.detachEvent('onload', cb);
+ }
+ else {
+ io.removeEventListener('load', cb, false);
+ }
+
+ var status = 'success', errMsg;
+ try {
+ if (timedOut) {
+ throw 'timeout';
+ }
+
+ var isXml = s.dataType == 'xml' || doc.XMLDocument || $.isXMLDoc(doc);
+ log('isXml='+isXml);
+ if (!isXml && window.opera && (doc.body === null || !doc.body.innerHTML)) {
+ if (--domCheckCount) {
+ // in some browsers (Opera) the iframe DOM is not always traversable when
+ // the onload callback fires, so we loop a bit to accommodate
+ log('requeing onLoad callback, DOM not available');
+ setTimeout(cb, 250);
+ return;
+ }
+ // let this fall through because server response could be an empty document
+ //log('Could not access iframe DOM after mutiple tries.');
+ //throw 'DOMException: not available';
+ }
+
+ //log('response detected');
+ var docRoot = doc.body ? doc.body : doc.documentElement;
+ xhr.responseText = docRoot ? docRoot.innerHTML : null;
+ xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;
+ if (isXml) {
+ s.dataType = 'xml';
+ }
+ xhr.getResponseHeader = function(header){
+ var headers = {'content-type': s.dataType};
+ return headers[header.toLowerCase()];
+ };
+ // support for XHR 'status' & 'statusText' emulation :
+ if (docRoot) {
+ xhr.status = Number( docRoot.getAttribute('status') ) || xhr.status;
+ xhr.statusText = docRoot.getAttribute('statusText') || xhr.statusText;
+ }
+
+ var dt = (s.dataType || '').toLowerCase();
+ var scr = /(json|script|text)/.test(dt);
+ if (scr || s.textarea) {
+ // see if user embedded response in textarea
+ var ta = doc.getElementsByTagName('textarea')[0];
+ if (ta) {
+ xhr.responseText = ta.value;
+ // support for XHR 'status' & 'statusText' emulation :
+ xhr.status = Number( ta.getAttribute('status') ) || xhr.status;
+ xhr.statusText = ta.getAttribute('statusText') || xhr.statusText;
+ }
+ else if (scr) {
+ // account for browsers injecting pre around json response
+ var pre = doc.getElementsByTagName('pre')[0];
+ var b = doc.getElementsByTagName('body')[0];
+ if (pre) {
+ xhr.responseText = pre.textContent ? pre.textContent : pre.innerText;
+ }
+ else if (b) {
+ xhr.responseText = b.textContent ? b.textContent : b.innerText;
+ }
+ }
+ }
+ else if (dt == 'xml' && !xhr.responseXML && xhr.responseText) {
+ xhr.responseXML = toXml(xhr.responseText);
+ }
+
+ try {
+ data = httpData(xhr, dt, s);
+ }
+ catch (err) {
+ status = 'parsererror';
+ xhr.error = errMsg = (err || status);
+ }
+ }
+ catch (err) {
+ log('error caught: ',err);
+ status = 'error';
+ xhr.error = errMsg = (err || status);
+ }
+
+ if (xhr.aborted) {
+ log('upload aborted');
+ status = null;
+ }
+
+ if (xhr.status) { // we've set xhr.status
+ status = (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) ? 'success' : 'error';
+ }
+
+ // ordering of these callbacks/triggers is odd, but that's how $.ajax does it
+ if (status === 'success') {
+ if (s.success) {
+ s.success.call(s.context, data, 'success', xhr);
+ }
+ deferred.resolve(xhr.responseText, 'success', xhr);
+ if (g) {
+ $.event.trigger("ajaxSuccess", [xhr, s]);
+ }
+ }
+ else if (status) {
+ if (errMsg === undefined) {
+ errMsg = xhr.statusText;
+ }
+ if (s.error) {
+ s.error.call(s.context, xhr, status, errMsg);
+ }
+ deferred.reject(xhr, 'error', errMsg);
+ if (g) {
+ $.event.trigger("ajaxError", [xhr, s, errMsg]);
+ }
+ }
+
+ if (g) {
+ $.event.trigger("ajaxComplete", [xhr, s]);
+ }
+
+ if (g && ! --$.active) {
+ $.event.trigger("ajaxStop");
+ }
+
+ if (s.complete) {
+ s.complete.call(s.context, xhr, status);
+ }
+
+ callbackProcessed = true;
+ if (s.timeout) {
+ clearTimeout(timeoutHandle);
+ }
+
+ // clean up
+ setTimeout(function() {
+ if (!s.iframeTarget) {
+ $io.remove();
+ }
+ else { //adding else to clean up existing iframe response.
+ $io.attr('src', s.iframeSrc);
+ }
+ xhr.responseXML = null;
+ }, 100);
+ }
+
+ var toXml = $.parseXML || function(s, doc) { // use parseXML if available (jQuery 1.5+)
+ 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.nodeName != 'parsererror') ? doc : null;
+ };
+ var parseJSON = $.parseJSON || function() {
+ window.console.error('jQuery.paseJSON is undefined');
+
+ return null;
+ };
+
+ var httpData = function( xhr, type, s ) { // mostly lifted from jq1.4.4
+
+ var ct = xhr.getResponseHeader('content-type') || '',
+ xml = type === 'xml' || !type && ct.indexOf('xml') >= 0,
+ data = xml ? xhr.responseXML : xhr.responseText;
+
+ if (xml && data.documentElement.nodeName === 'parsererror') {
+ if ($.error) {
+ $.error('parsererror');
+ }
+ }
+ if (s && s.dataFilter) {
+ data = s.dataFilter(data, type);
+ }
+ if (typeof data === 'string') {
+ if (type === 'json' || !type && ct.indexOf('json') >= 0) {
+ data = parseJSON(data);
+ } else if (type === "script" || !type && ct.indexOf("javascript") >= 0) {
+ $.globalEval(data);
+ }
+ }
+ return data;
+ };
+
+ return deferred;
+ }
+};
+
+/**
+ * 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 elements (if the element
+ * is used to submit the form).
+ * 2. This method will include the submit element's name/value data (for the element that was
+ * used to submit the form).
+ * 3. This method binds the submit() method to the form for you.
+ *
+ * The options argument for ajaxForm works exactly as it does for ajaxSubmit. ajaxForm merely
+ * passes the options argument along after properly binding events for submit elements and
+ * the form itself.
+ */
+$.fn.ajaxForm = function(options) {
+ options = options || {};
+ options.delegation = options.delegation && $.isFunction($.fn.on);
+
+ // in jQuery 1.3+ we can fix mistakes with the ready state
+ if (!options.delegation && this.length === 0) {
+ var o = { s: this.selector, c: this.context };
+ if (!$.isReady && o.s) {
+ log('DOM not ready, queuing ajaxForm');
+ $(function() {
+ $(o.s,o.c).ajaxForm(options);
+ });
+ return this;
+ }
+ // is your DOM ready? http://docs.jquery.com/Tutorials:Introducing_$(document).ready()
+ log('terminating; zero elements found by selector' + ($.isReady ? '' : ' (DOM not ready)'));
+ return this;
+ }
+
+ if ( options.delegation ) {
+ $(document)
+ .off('submit.form-plugin', this.selector, doAjaxSubmit)
+ .off('click.form-plugin', this.selector, captureSubmittingElement)
+ .on('submit.form-plugin', this.selector, options, doAjaxSubmit)
+ .on('click.form-plugin', this.selector, options, captureSubmittingElement);
+ return this;
+ }
+
+ return this.ajaxFormUnbind()
+ .bind('submit.form-plugin', options, doAjaxSubmit)
+ .bind('click.form-plugin', options, captureSubmittingElement);
+};
+
+// private event handlers
+function doAjaxSubmit(e) {
+ /*jshint validthis:true */
+ var options = e.data;
+ if (!e.isDefaultPrevented()) { // if event has been canceled, don't proceed
+ e.preventDefault();
+ $(e.target).ajaxSubmit(options); // #365
+ }
+}
+
+function captureSubmittingElement(e) {
+ /*jshint validthis:true */
+ var target = e.target;
+ var $el = $(target);
+ if (!($el.is("[type=submit],[type=image]"))) {
+ // is this a child element of the submit el? (ex: a span within a button)
+ var t = $el.closest('[type=submit]');
+ if (t.length === 0) {
+ return;
+ }
+ target = t[0];
+ }
+ var form = this;
+ form.clk = target;
+ if (target.type == 'image') {
+ if (e.offsetX !== undefined) {
+ form.clk_x = e.offsetX;
+ form.clk_y = e.offsetY;
+ } else if (typeof $.fn.offset == 'function') {
+ var offset = $el.offset();
+ form.clk_x = e.pageX - offset.left;
+ form.clk_y = e.pageY - offset.top;
+ } else {
+ form.clk_x = e.pageX - target.offsetLeft;
+ form.clk_y = e.pageY - target.offsetTop;
+ }
+ }
+ // clear form vars
+ setTimeout(function() { form.clk = form.clk_x = form.clk_y = null; }, 100);
+}
+
+
+// ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm
+$.fn.ajaxFormUnbind = function() {
+ return this.unbind('submit.form-plugin click.form-plugin');
+};
+
+/**
+ * formToArray() gathers form element data into an array of objects that can
+ * be passed to any of the following ajax functions: $.get, $.post, or load.
+ * Each object in the array has both a 'name' and 'value' property. An example of
+ * an array for a simple login form might be:
+ *
+ * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
+ *
+ * It is this array that is passed to pre-submit callback functions provided to the
+ * ajaxSubmit() and ajaxForm() methods.
+ */
+$.fn.formToArray = function(semantic, elements) {
+ var a = [];
+ if (this.length === 0) {
+ return a;
+ }
+
+ var form = this[0];
+ var formId = this.attr('id');
+ var els = semantic ? form.getElementsByTagName('*') : form.elements;
+ var els2;
+
+ if (els && !/MSIE [678]/.test(navigator.userAgent)) { // #390
+ els = $(els).get(); // convert to standard array
+ }
+
+ // #386; account for inputs outside the form which use the 'form' attribute
+ if ( formId ) {
+ els2 = $(':input[form="' + formId + '"]').get(); // hat tip @thet
+ if ( els2.length ) {
+ els = (els || []).concat(els2);
+ }
+ }
+
+ if (!els || !els.length) {
+ return a;
+ }
+
+ var i,j,n,v,el,max,jmax;
+ for(i=0, max=els.length; i < max; i++) {
+ el = els[i];
+ n = el.name;
+ if (!n || el.disabled) {
+ continue;
+ }
+
+ if (semantic && form.clk && el.type == "image") {
+ // handle image inputs on the fly when semantic == true
+ if(form.clk == el) {
+ a.push({name: n, value: $(el).val(), type: el.type });
+ a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
+ }
+ continue;
+ }
+
+ v = $.fieldValue(el, true);
+ if (v && v.constructor == Array) {
+ if (elements) {
+ elements.push(el);
+ }
+ for(j=0, jmax=v.length; j < jmax; j++) {
+ a.push({name: n, value: v[j]});
+ }
+ }
+ else if (feature.fileapi && el.type == 'file') {
+ if (elements) {
+ elements.push(el);
+ }
+ var files = el.files;
+ if (files.length) {
+ for (j=0; j < files.length; j++) {
+ a.push({name: n, value: files[j], type: el.type});
+ }
+ }
+ else {
+ // #180
+ a.push({ name: n, value: '', type: el.type });
+ }
+ }
+ else if (v !== null && typeof v != 'undefined') {
+ if (elements) {
+ elements.push(el);
+ }
+ a.push({name: n, value: v, type: el.type, required: el.required});
+ }
+ }
+
+ if (!semantic && form.clk) {
+ // input type=='image' are not found in elements array! handle it here
+ var $input = $(form.clk), input = $input[0];
+ n = input.name;
+ if (n && !input.disabled && input.type == 'image') {
+ a.push({name: n, value: $input.val()});
+ 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&name2=value2
+ */
+$.fn.formSerialize = function(semantic) {
+ //hand off to jQuery.param for proper encoding
+ return $.param(this.formToArray(semantic));
+};
+
+/**
+ * Serializes all field elements in the jQuery object into a query string.
+ * This method will return a string in the format: name1=value1&name2=value2
+ */
+$.fn.fieldSerialize = function(successful) {
+ var a = [];
+ this.each(function() {
+ var n = this.name;
+ if (!n) {
+ return;
+ }
+ var v = $.fieldValue(this, successful);
+ if (v && v.constructor == Array) {
+ for (var i=0,max=v.length; i < max; i++) {
+ a.push({name: n, value: v[i]});
+ }
+ }
+ else if (v !== null && typeof v != 'undefined') {
+ a.push({name: this.name, value: v});
+ }
+ });
+ //hand off to jQuery.param for proper encoding
+ return $.param(a);
+};
+
+/**
+ * Returns the value(s) of the element in the matched set. For example, consider the following form:
+ *
+ *
+ *
+ * var v = $('input[type=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 = $('input[type=checkbox]').fieldValue();
+ * // if neither checkbox is checked
+ * v === undefined
+ * // if both checkboxes are checked
+ * v == ['B1', 'B2']
+ *
+ * var v = $('input[type=radio]').fieldValue();
+ * // if neither radio is checked
+ * v === undefined
+ * // if first radio is checked
+ * v == ['C1']
+ *
+ * The successful argument controls whether or not the field element must be 'successful'
+ * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
+ * The default value of the successful argument is true. If this value is false the value(s)
+ * for each element is returned.
+ *
+ * Note: This method *always* returns an array. If no valid value can be determined the
+ * array will be empty, otherwise it will contain one or more values.
+ */
+$.fn.fieldValue = function(successful) {
+ for (var val=[], i=0, max=this.length; i < max; i++) {
+ var el = this[i];
+ var v = $.fieldValue(el, successful);
+ if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length)) {
+ continue;
+ }
+ if (v.constructor == Array) {
+ $.merge(val, v);
+ }
+ else {
+ val.push(v);
+ }
+ }
+ return val;
+};
+
+/**
+ * Returns the value of the field element.
+ */
+$.fieldValue = function(el, successful) {
+ var n = el.name, t = el.type, tag = el.tagName.toLowerCase();
+ if (successful === undefined) {
+ successful = true;
+ }
+
+ if (successful && (!n || el.disabled || t == 'reset' || t == 'button' ||
+ (t == 'checkbox' || t == 'radio') && !el.checked ||
+ (t == 'submit' || t == 'image') && el.form && el.form.clk != el ||
+ tag == 'select' && el.selectedIndex == -1)) {
+ return null;
+ }
+
+ if (tag == 'select') {
+ var index = el.selectedIndex;
+ if (index < 0) {
+ return null;
+ }
+ var a = [], ops = el.options;
+ var one = (t == 'select-one');
+ var max = (one ? index+1 : ops.length);
+ for(var i=(one ? index : 0); i < max; i++) {
+ var op = ops[i];
+ if (op.selected) {
+ var v = op.value;
+ if (!v) { // extra pain for IE...
+ v = (op.attributes && op.attributes.value && !(op.attributes.value.specified)) ? op.text : op.value;
+ }
+ if (one) {
+ return v;
+ }
+ a.push(v);
+ }
+ }
+ return a;
+ }
+ return $(el).val();
+};
+
+/**
+ * Clears the form data. Takes the following actions on the form's input fields:
+ * - input text fields will have their 'value' property set to the empty string
+ * - select elements will have their 'selectedIndex' property set to -1
+ * - checkbox and radio inputs will have their 'checked' property set to false
+ * - inputs of type submit, button, reset, and hidden will *not* be effected
+ * - button elements will *not* be effected
+ */
+$.fn.clearForm = function(includeHidden) {
+ return this.each(function() {
+ $('input,select,textarea', this).clearFields(includeHidden);
+ });
+};
+
+/**
+ * Clears the selected form elements.
+ */
+$.fn.clearFields = $.fn.clearInputs = function(includeHidden) {
+ var re = /^(?:color|date|datetime|email|month|number|password|range|search|tel|text|time|url|week)$/i; // 'hidden' is not in this list
+ return this.each(function() {
+ var t = this.type, tag = this.tagName.toLowerCase();
+ if (re.test(t) || tag == 'textarea') {
+ this.value = '';
+ }
+ else if (t == 'checkbox' || t == 'radio') {
+ this.checked = false;
+ }
+ else if (tag == 'select') {
+ this.selectedIndex = -1;
+ }
+ else if (t == "file") {
+ if (/MSIE/.test(navigator.userAgent)) {
+ $(this).replaceWith($(this).clone(true));
+ } else {
+ $(this).val('');
+ }
+ }
+ else if (includeHidden) {
+ // includeHidden can be the value true, or it can be a selector string
+ // indicating a special test; for example:
+ // $('#myForm').clearForm('.special:hidden')
+ // the above would clean hidden inputs that have the class of 'special'
+ if ( (includeHidden === true && /hidden/.test(t)) ||
+ (typeof includeHidden == 'string' && $(this).is(includeHidden)) ) {
+ this.value = '';
+ }
+ }
+ });
+};
+
+/**
+ * Resets the form data. Causes all form elements to be reset to their original value.
+ */
+$.fn.resetForm = function() {
+ return this.each(function() {
+ // guard against an input with the name of 'reset'
+ // note that IE reports the reset function as an 'object'
+ if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType)) {
+ this.reset();
+ }
+ });
+};
+
+/**
+ * Enables or disables any matching elements.
+ */
+$.fn.enable = function(b) {
+ if (b === undefined) {
+ b = true;
+ }
+ return this.each(function() {
+ this.disabled = !b;
+ });
+};
+
+/**
+ * Checks/unchecks any matching checkboxes or radio buttons and
+ * selects/deselects and matching option elements.
+ */
+$.fn.selected = function(select) {
+ if (select === undefined) {
+ select = true;
+ }
+ return this.each(function() {
+ var t = this.type;
+ if (t == 'checkbox' || t == 'radio') {
+ this.checked = select;
+ }
+ else if (this.tagName.toLowerCase() == 'option') {
+ var $sel = $(this).parent('select');
+ if (select && $sel[0] && $sel[0].type == 'select-one') {
+ // deselect all other options
+ $sel.find('option').selected(false);
+ }
+ this.selected = select;
+ }
+ });
+};
+
+// expose debug var
+$.fn.ajaxSubmit.debug = false;
+
+// helper fn for console logging
+function log() {
+ if (!$.fn.ajaxSubmit.debug) {
+ return;
+ }
+ var msg = '[jquery.form] ' + Array.prototype.join.call(arguments,'');
+ if (window.console && window.console.log) {
+ window.console.log(msg);
+ }
+ else if (window.opera && window.opera.postError) {
+ window.opera.postError(msg);
+ }
+}
+
+}));