1// This [jQuery](https://jquery.com/) plugin implements an `<iframe>` 2// [transport](https://api.jquery.com/jQuery.ajax/#extending-ajax) so that 3// `$.ajax()` calls support the uploading of files using standard HTML file 4// input fields. This is done by switching the exchange from `XMLHttpRequest` 5// to a hidden `iframe` element containing a form that is submitted. 6 7// The [source for the plugin](https://github.com/cmlenz/jquery-iframe-transport) 8// is available on [Github](https://github.com/) and licensed under the [MIT 9// license](https://github.com/cmlenz/jquery-iframe-transport/blob/master/LICENSE). 10 11// ## Usage 12 13// To use this plugin, you simply add an `iframe` option with the value `true` 14// to the Ajax settings an `$.ajax()` call, and specify the file fields to 15// include in the submssion using the `files` option, which can be a selector, 16// jQuery object, or a list of DOM elements containing one or more 17// `<input type="file">` elements: 18 19// $("#myform").submit(function() { 20// $.ajax(this.action, { 21// files: $(":file", this), 22// iframe: true 23// }).complete(function(data) { 24// console.log(data); 25// }); 26// }); 27 28// The plugin will construct hidden `<iframe>` and `<form>` elements, add the 29// file field(s) to that form, submit the form, and process the response. 30 31// If you want to include other form fields in the form submission, include 32// them in the `data` option, and set the `processData` option to `false`: 33 34// $("#myform").submit(function() { 35// $.ajax(this.action, { 36// data: $(":text", this).serializeArray(), 37// files: $(":file", this), 38// iframe: true, 39// processData: false 40// }).complete(function(data) { 41// console.log(data); 42// }); 43// }); 44 45// ### Response Data Types 46 47// As the transport does not have access to the HTTP headers of the server 48// response, it is not as simple to make use of the automatic content type 49// detection provided by jQuery as with regular XHR. If you can't set the 50// expected response data type (for example because it may vary depending on 51// the outcome of processing by the server), you will need to employ a 52// workaround on the server side: Send back an HTML document containing just a 53// `<textarea>` element with a `data-type` attribute that specifies the MIME 54// type, and put the actual payload in the textarea: 55 56// <textarea data-type="application/json"> 57// {"ok": true, "message": "Thanks so much"} 58// </textarea> 59 60// The iframe transport plugin will detect this and pass the value of the 61// `data-type` attribute on to jQuery as if it was the "Content-Type" response 62// header, thereby enabling the same kind of conversions that jQuery applies 63// to regular responses. For the example above you should get a Javascript 64// object as the `data` parameter of the `complete` callback, with the 65// properties `ok: true` and `message: "Thanks so much"`. 66 67// ### Handling Server Errors 68 69// Another problem with using an `iframe` for file uploads is that it is 70// impossible for the javascript code to determine the HTTP status code of the 71// servers response. Effectively, all of the calls you make will look like they 72// are getting successful responses, and thus invoke the `done()` or 73// `complete()`Â callbacks. You can only communicate problems using the content 74// of the response payload. For example, consider using a JSON response such as 75// the following to indicate a problem with an uploaded file: 76 77// <textarea data-type="application/json"> 78// {"ok": false, "message": "Please only upload reasonably sized files."} 79// </textarea> 80 81// ### Compatibility 82 83// This plugin has primarily been tested on Safari 5 (or later), Firefox 4 (or 84// later), and Internet Explorer (all the way back to version 6). While I 85// haven't found any issues with it so far, I'm fairly sure it still doesn't 86// work around all the quirks in all different browsers. But the code is still 87// pretty simple overall, so you should be able to fix it and contribute a 88// patch :) 89 90// ## Annotated Source 91 92(function($, undefined) { 93 'use strict'; 94 95 // Register a prefilter that checks whether the `iframe` option is set, and 96 // switches to the "iframe" data type if it is `true`. 97 $.ajaxPrefilter(function(options, origOptions, jqXHR) { 98 if (options.iframe) { 99 options.originalURL = options.url; 100 return 'iframe'; 101 } 102 }); 103 104 // Register a transport for the "iframe" data type. It will only activate 105 // when the "files" option has been set to a non-empty list of enabled file 106 // inputs. 107 $.ajaxTransport('iframe', function(options, origOptions, jqXHR) { 108 var form = null, 109 iframe = null, 110 name = 'iframe-' + $.now(), 111 files = $(options.files).filter(':file:enabled'), 112 markers = null, 113 accepts = null; 114 115 // This function gets called after a successful submission or an abortion 116 // and should revert all changes made to the page to enable the 117 // submission via this transport. 118 function cleanUp() { 119 files.each(function(i, file) { 120 var $file = $(file); 121 $file.data('clone').replaceWith($file); 122 }); 123 form.remove(); 124 iframe.one('load', function() { 125 iframe.remove(); 126 }); 127 iframe.attr('src', 'javascript:false;'); 128 } 129 130 // Remove "iframe" from the data types list so that further processing is 131 // based on the content type returned by the server, without attempting an 132 // (unsupported) conversion from "iframe" to the actual type. 133 options.dataTypes.shift(); 134 135 // Use the data from the original AJAX options, as it doesn't seem to be 136 // copied over since jQuery 1.7. 137 // See https://github.com/cmlenz/jquery-iframe-transport/issues/6 138 options.data = origOptions.data; 139 140 if (files.length) { 141 form = $('<form enctype=\'multipart/form-data\' method=\'post\'></form>'). 142 hide().attr({action: options.originalURL, target: name}); 143 144 // If there is any additional data specified via the `data` option, 145 // we add it as hidden fields to the form. This (currently) requires 146 // the `processData` option to be set to false so that the data doesn't 147 // get serialized to a string. 148 if (typeof (options.data) === 'string' && options.data.length > 0) { 149 $.error('data must not be serialized'); 150 } 151 $.each(options.data || {}, function(name, value) { 152 if ($.isPlainObject(value)) { 153 name = value.name; 154 value = value.value; 155 } 156 $('<input type=\'hidden\' />').attr({name: name, value: value}). 157 appendTo(form); 158 }); 159 160 // Add a hidden `X-Requested-With` field with the value `IFrame` to the 161 // field, to help server-side code to determine that the upload happened 162 // through this transport. 163 $('<input type=\'hidden\' value=\'IFrame\' name=\'X-Requested-With\' />'). 164 appendTo(form); 165 166 // Borrowed straight from the JQuery source. 167 // Provides a way of specifying the accepted data type similar to the 168 // HTTP "Accept" header 169 if (options.dataTypes[0] && options.accepts[options.dataTypes[0]]) { 170 accepts = options.accepts[options.dataTypes[0]] + 171 (options.dataTypes[0] !== '*' ? ', */*; q=0.01' : ''); 172 } else { 173 accepts = options.accepts['*']; 174 } 175 $('<input type=\'hidden\' name=\'X-HTTP-Accept\'>'). 176 attr('value', accepts).appendTo(form); 177 178 // Move the file fields into the hidden form, but first remember their 179 // original locations in the document by replacing them with disabled 180 // clones. This should also avoid introducing unwanted changes to the 181 // page layout during submission. 182 markers = files.after(function(idx) { 183 var $this = $(this), 184 $clone = $this.clone().prop('disabled', true); 185 $this.data('clone', $clone); 186 return $clone; 187 }).next(); 188 files.appendTo(form); 189 190 return { 191 192 // The `send` function is called by jQuery when the request should be 193 // sent. 194 send: function(headers, completeCallback) { 195 iframe = $('<iframe src=\'javascript:false;\' name=\'' + name + 196 '\' id=\'' + name + '\' style=\'display:none\'></iframe>'); 197 198 // The first load event gets fired after the iframe has been injected 199 // into the DOM, and is used to prepare the actual submission. 200 iframe.one('load', function() { 201 202 // The second load event gets fired when the response to the form 203 // submission is received. The implementation detects whether the 204 // actual payload is embedded in a `<textarea>` element, and 205 // prepares the required conversions to be made in that case. 206 iframe.one('load', function() { 207 var doc = this.contentWindow ? this.contentWindow.document : 208 (this.contentDocument ? this.contentDocument : this.document), 209 root = doc.documentElement ? doc.documentElement : doc.body, 210 textarea = root.getElementsByTagName('textarea')[0], 211 type = textarea && textarea.getAttribute('data-type') || null, 212 status = textarea && textarea.getAttribute('data-status') || 213 200, 214 statusText = textarea && 215 textarea.getAttribute('data-statusText') || 'OK', 216 content = { 217 html: root.innerHTML, 218 text: type ? 219 textarea.value : 220 root ? (root.textContent || root.innerText) : null, 221 }; 222 cleanUp(); 223 completeCallback(status, statusText, content, type ? 224 ('Content-Type: ' + type) : 225 null); 226 }); 227 228 // Now that the load handler has been set up, submit the form. 229 form[0].submit(); 230 }); 231 232 // After everything has been set up correctly, the form and iframe 233 // get injected into the DOM so that the submission can be 234 // initiated. 235 $('body').append(form, iframe); 236 }, 237 238 // The `abort` function is called by jQuery when the request should be 239 // aborted. 240 abort: function() { 241 if (iframe !== null) { 242 iframe.unbind('load').attr('src', 'javascript:false;'); 243 cleanUp(); 244 } 245 }, 246 247 }; 248 } 249 }); 250 251})(jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.