vendor/assets/javascripts/jquery.iframe-transport.js in remotipart-1.0.5 vs vendor/assets/javascripts/jquery.iframe-transport.js in remotipart-1.1.0

- old
+ new

@@ -1,18 +1,18 @@ // This [jQuery](http://jquery.com/) plugin implements an `<iframe>` // [transport](http://api.jquery.com/extending-ajax/#Transports) so that // `$.ajax()` calls support the uploading of files using standard HTML file -// input fields. This is done by switching the exchange from `XMLHttpRequest` to -// a hidden `iframe` element containing a form that is submitted. +// input fields. This is done by switching the exchange from `XMLHttpRequest` +// to a hidden `iframe` element containing a form that is submitted. // The [source for the plugin](http://github.com/cmlenz/jquery-iframe-transport) // is available on [Github](http://github.com/) and dual licensed under the MIT // or GPL Version 2 licenses. // ## Usage -// To use this plugin, you simply add a `iframe` option with the value `true` +// To use this plugin, you simply add an `iframe` option with the value `true` // to the Ajax settings an `$.ajax()` call, and specify the file fields to // include in the submssion using the `files` option, which can be a selector, // jQuery object, or a list of DOM elements containing one or more // `<input type="file">` elements: @@ -23,16 +23,15 @@ // }).complete(function(data) { // console.log(data); // }); // }); -// The plugin will construct a hidden `<iframe>` element containing a copy of -// the form the file field belongs to, will disable any form fields not -// explicitly included, submit that form, and process the response. +// The plugin will construct hidden `<iframe>` and `<form>` elements, add the +// file field(s) to that form, submit the form, and process the response. -// If you want to include other form fields in the form submission, include them -// in the `data` option, and set the `processData` option to `false`: +// If you want to include other form fields in the form submission, include +// them in the `data` option, and set the `processData` option to `false`: // $("#myform").submit(function() { // $.ajax(this.action, { // data: $(":text", this).serializeArray(), // files: $(":file", this), @@ -41,181 +40,181 @@ // }).complete(function(data) { // console.log(data); // }); // }); -// ### The Server Side +// ### Response Data Types -// If the response is not HTML or XML, you (unfortunately) need to apply some -// trickery on the server side. To send back a JSON payload, send back an HTML -// `<textarea>` element with a `data-type` attribute that contains the MIME +// As the transport does not have access to the HTTP headers of the server +// response, it is not as simple to make use of the automatic content type +// detection provided by jQuery as with regular XHR. If you can't set the +// expected response data type (for example because it may vary depending on +// the outcome of processing by the server), you will need to employ a +// workaround on the server side: Send back an HTML document containing just a +// `<textarea>` element with a `data-type` attribute that specifies the MIME // type, and put the actual payload in the textarea: // <textarea data-type="application/json"> // {"ok": true, "message": "Thanks so much"} // </textarea> -// The iframe transport plugin will detect this and attempt to apply the same -// conversions that jQuery applies to regular responses. That means for the -// example above you should get a Javascript object as the `data` parameter of -// the `complete` callback, with the properties `ok: true` and -// `message: "Thanks so much"`. +// The iframe transport plugin will detect this and pass the value of the +// `data-type` attribute on to jQuery as if it was the "Content-Type" response +// header, thereby enabling the same kind of conversions that jQuery applies +// to regular responses. For the example above you should get a Javascript +// object as the `data` parameter of the `complete` callback, with the +// properties `ok: true` and `message: "Thanks so much"`. +// ### Handling Server Errors + +// Another problem with using an `iframe` for file uploads is that it is +// impossible for the javascript code to determine the HTTP status code of the +// servers response. Effectively, all of the calls you make will look like they +// are getting successful responses, and thus invoke the `done()` or +// `complete()` callbacks. You can only determine communicate problems using +// the content of the response payload. For example, consider using a JSON +// response such as the following to indicate a problem with an uploaded file: + +// <textarea data-type="application/json"> +// {"ok": false, "message": "Please only upload reasonably sized files."} +// </textarea> + // ### Compatibility -// This plugin has primarily been tested on Safari 5, Firefox 4, and Internet -// Explorer all the way back to version 6. While I haven't found any issues with -// it so far, I'm fairly sure it still doesn't work around all the quirks in all -// different browsers. But the code is still pretty simple overall, so you -// should be able to fix it and contribute a patch :) +// This plugin has primarily been tested on Safari 5 (or later), Firefox 4 (or +// later), and Internet Explorer (all the way back to version 6). While I +// haven't found any issues with it so far, I'm fairly sure it still doesn't +// work around all the quirks in all different browsers. But the code is still +// pretty simple overall, so you should be able to fix it and contribute a +// patch :) // ## Annotated Source (function($, undefined) { + "use strict"; // Register a prefilter that checks whether the `iframe` option is set, and - // switches to the iframe transport if it is `true`. + // switches to the "iframe" data type if it is `true`. $.ajaxPrefilter(function(options, origOptions, jqXHR) { if (options.iframe) { return "iframe"; } }); - // Register an iframe transport, independent of requested data type. It will - // only activate when the "files" option has been set to a non-empty list of - // enabled file inputs. + // Register a transport for the "iframe" data type. It will only activate + // when the "files" option has been set to a non-empty list of enabled file + // inputs. $.ajaxTransport("iframe", function(options, origOptions, jqXHR) { var form = null, iframe = null, - origAction = null, - origTarget = null, - origEnctype = null, - addedFields = [], - disabledFields = [], - files = $(options.files).filter(":file:enabled"); + name = "iframe-" + $.now(), + files = $(options.files).filter(":file:enabled"), + markers = null, + accepts; // This function gets called after a successful submission or an abortion // and should revert all changes made to the page to enable the // submission via this transport. function cleanUp() { - $(addedFields).each(function() { - this.remove(); - }); - $(disabledFields).each(function() { - this.disabled = false; - }); - form.attr("action", origAction || "") - .attr("target", origTarget || "") - .attr("enctype", origEnctype || ""); - iframe.attr("src", "javascript:false;").remove(); + markers.prop('disabled', false); + form.remove(); + iframe.bind("load", function() { iframe.remove(); }); + iframe.attr("src", "javascript:false;"); } // Remove "iframe" from the data types list so that further processing is // based on the content type returned by the server, without attempting an // (unsupported) conversion from "iframe" to the actual type. options.dataTypes.shift(); if (files.length) { - // Determine the form the file fields belong to, and make sure they all - // actually belong to the same form. - files.each(function() { - if (form !== null && this.form !== form) { - jQuery.error("All file fields must belong to the same form"); - } - form = this.form; - }); - form = $(form); + form = $("<form enctype='multipart/form-data' method='post'></form>"). + hide().attr({action: options.url, target: name}); - // Store the original form attributes that we'll be replacing temporarily. - origAction = form.attr("action"); - origTarget = form.attr("target"); - origEnctype = form.attr("enctype"); - - // We need to disable all other inputs in the form so that they don't get - // included in the submitted data unexpectedly. - form.find(":input:not(:submit)").each(function() { - if (!this.disabled && (this.type != "file" || files.index(this) < 0)) { - this.disabled = true; - disabledFields.push(this); - } - }); - // If there is any additional data specified via the `data` option, // we add it as hidden fields to the form. This (currently) requires // the `processData` option to be set to false so that the data doesn't // get serialized to a string. if (typeof(options.data) === "string" && options.data.length > 0) { - jQuery.error("data must not be serialized"); + $.error("data must not be serialized"); } $.each(options.data || {}, function(name, value) { if ($.isPlainObject(value)) { name = value.name; value = value.value; } - addedFields.push($("<input type='hidden'>").attr("name", name) - .attr("value", value).appendTo(form)); + $("<input type='hidden' />").attr({name: name, value: value}). + appendTo(form); }); // Add a hidden `X-Requested-With` field with the value `IFrame` to the // field, to help server-side code to determine that the upload happened // through this transport. - addedFields.push($("<input type='hidden' name='X-Requested-With'>") - .attr("value", "IFrame").appendTo(form)); + $("<input type='hidden' value='IFrame' name='X-Requested-With' />"). + appendTo(form); // Borrowed straight from the JQuery source // Provides a way of specifying the accepted data type similar to HTTP_ACCEPTS accepts = options.dataTypes[ 0 ] && options.accepts[ options.dataTypes[0] ] ? options.accepts[ options.dataTypes[0] ] + ( options.dataTypes[ 0 ] !== "*" ? ", */*; q=0.01" : "" ) : options.accepts[ "*" ] - addedFields.push($("<input type='hidden' name='X-Http-Accept'>") - .attr("value", accepts).appendTo(form)); + $("<input type='hidden' name='X-Http-Accept'>") + .attr("value", accepts).appendTo(form); + // Move the file fields into the hidden form, but first remember their + // original locations in the document by replacing them with disabled + // clones. This should also avoid introducing unwanted changes to the + // page layout during submission. + markers = files.after(function(idx) { + return $(this).clone().prop("disabled", true); + }).next(); + files.appendTo(form); + return { // The `send` function is called by jQuery when the request should be // sent. send: function(headers, completeCallback) { - iframe = $("<iframe src='javascript:false;' name='iframe-" + $.now() - + "' style='display:none'></iframe>"); + iframe = $("<iframe src='javascript:false;' name='" + name + + "' id='" + name + "' style='display:none'></iframe>"); // The first load event gets fired after the iframe has been injected // into the DOM, and is used to prepare the actual submission. iframe.bind("load", function() { // The second load event gets fired when the response to the form // submission is received. The implementation detects whether the // actual payload is embedded in a `<textarea>` element, and // prepares the required conversions to be made in that case. iframe.unbind("load").bind("load", function() { - var doc = this.contentWindow ? this.contentWindow.document : (this.contentDocument ? this.contentDocument : this.document), root = doc.documentElement ? doc.documentElement : doc.body, textarea = root.getElementsByTagName("textarea")[0], - type = textarea ? textarea.getAttribute("data-type") : null; - - var status = textarea ? parseInt(textarea.getAttribute("response-code")) : 200, - statusText = "OK", - responses = { text: type ? textarea.value : root ? root.innerHTML : null }, - headers = "Content-Type: " + (type || "text/html") - - completeCallback(status, statusText, responses, headers); - - setTimeout(cleanUp, 50); + type = textarea && textarea.getAttribute("data-type") || null, + status = textarea && textarea.getAttribute("data-status") || 200, + statusText = textarea && textarea.getAttribute("data-statusText") || "OK", + content = { + html: root.innerHTML, + text: type ? + textarea.value : + root ? (root.textContent || root.innerText) : null + }; + cleanUp(); + completeCallback(status, statusText, content, type ? + ("Content-Type: " + type) : + null); }); - // Now that the load handler has been set up, reconfigure and - // submit the form. - form.attr("action", options.url) - .attr("target", iframe.attr("name")) - .attr("enctype", "multipart/form-data") - .get(0).submit(); + // Now that the load handler has been set up, submit the form. + form[0].submit(); }); - // After everything has been set up correctly, the iframe gets - // injected into the DOM so that the submission can be initiated. - iframe.insertAfter(form); + // After everything has been set up correctly, the form and iframe + // get injected into the DOM so that the submission can be + // initiated. + $("body").append(form, iframe); }, // The `abort` function is called by jQuery when the request should be // aborted. abort: function() {