123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223 |
- // 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.
- // 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 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:
- // $("#myform").submit(function() {
- // $.ajax(this.action, {
- // files: $(":file", this),
- // iframe: true
- // }).complete(function(data) {
- // console.log(data);
- // });
- // });
- // 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`:
- // $("#myform").submit(function() {
- // $.ajax(this.action, {
- // data: $(":text", this).serializeArray(),
- // files: $(":file", this),
- // iframe: true,
- // processData: false
- // }).complete(function(data) {
- // console.log(data);
- // });
- // });
- // ### Response Data Types
- // 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 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 (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" data type if it is `true`.
- $.ajaxPrefilter(function(options, origOptions, jqXHR) {
- if (options.iframe) {
- return "iframe";
- }
- });
- // 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,
- name = "iframe-" + $.now(),
- files = $(options.files).filter(":file:enabled"),
- markers = null;
- // 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() {
- markers.replaceWith(function(idx) {
- return files.get(idx);
- });
- form.remove();
- iframe.attr("src", "javascript:false;").remove();
- }
- // 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) {
- form = $("<form enctype='multipart/form-data' method='post'></form>").
- hide().attr({action: options.url, target: name});
- // 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) {
- $.error("data must not be serialized");
- }
- $.each(options.data || {}, function(name, value) {
- if ($.isPlainObject(value)) {
- name = value.name;
- value = value.value;
- }
- $("<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.
- $("<input type='hidden' value='IFrame' name='X-Requested-With' />").
- 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='" + 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,
- 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, submit the form.
- form[0].submit();
- });
- // 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() {
- if (iframe !== null) {
- iframe.unbind("load").attr("src", "javascript:false;");
- cleanUp();
- }
- }
- };
- }
- });
- })(jQuery);
|