/** * @class ep * */ /** * Perform an asynchronous file upload with HTTP (Ajax). * * The `ep.ajax()` function is a modified alias of `jQuery.ajax()` and supports all options of `jQuery.ajax()`. * * This method provides file upload with AJAX. File uploads are always asynchrony the `async` options isn't editable. * To upload files with `ep.ajax()` us the additional options `file`, `fileSizeMax`, `fileMimeAccept` and `progress`. * * ====New API standard in 6.14.0==== * For AJAX use `deferred object` methods instead of setting `success`, `error`, `complete` and `progress` methods as arguments. * * Properties to methods: * * + `success` => `.done()` * + `error` => `.fail()` * + `complete` => `.always()` * + `progress` => `.progress()` * * ### Examples * Upload files with ajax on change. * * JavaScript: * * $('#upload').on( 'change', function(event){ * ep.ajax({ * file: this, * fileSizeMax: 2097152, * fileMimeAccept: 'image/*', * dataType: 'json' * }) * .progress(function( event, statusText, jqXHR ){ * // added in verison 6.14.0 * }) * .done(function( jsonData, statusText, jqXHR ){ * * }) * .fail(function( jqXHR, statusText, errorThrown ){ * * }) * }); * * HTML: * * <input id="upload" type="file" multiple="true" name="NewFile" /> * * * ### Dependencies * * + `jQuery.mime` * * @param {Object} options A set of key/value pairs that configure the Ajax request. All settings are optional. * @param {Object} [options.file] An input element of type file, which contains the file(s) to upload (only in connection with dataType 'json'). * @param {Number} [options.fileSizeMax] A number of file size limit (bytes). * @param {String} [options.fileMimeAccept] A string containing one or more mime types / mime type groups. * @param {String} [options.progress] An event method which called on file upload progress. Receives event, statusText and jqXHR as arguments. * * @method ajax * @static * @member ep * * @since 6.12.0 */ /* * @copyright © Copyright 2006-2010, epages GmbH, All Rights Reserved. * * @module de_epages.presentation.ajaxTransport * * @revision $Revision: 1.7 $ */ define("de_epages/presentation/ajaxtransport", [ "jquery", "de_epages", "util/mime", "util/support", "jquery/uid", "ep/ajax" ], function ($, de_epages, mime, support) { // extend the ajax settings if( !$.ajaxSettings.fileSizeMax ){ $.ajaxSettings.fileSizeMax = 2097152; // set max size of files default: 2MB } // use html5 as default var html5 = support.ajaxUpload, // url query params rquery = /\?/, // to string method toString = Object.prototype.toString, // map of advanced errors statusMap = { 102: { status:102, errorThrown:'Processing' }, 200: { status:200, errorThrown:'Ok' }, 290: { status:290, errorThrown:'Ok' }, // Item 408: { status:408, errorThrown:'Request Timeout' }, 489: { status:489, errorThrown:'Invalid Image Size' }, // Item 490: { status:490, errorThrown:'Bad Item Request' }, 491: { status:491, errorThrown:'Items of Request Failed' }, 492: { status:492, errorThrown:'Empty Request' }, // Item 493: { status:493, errorThrown:'Mimetype Not Accept Error' }, // Item 494: { status:494, errorThrown:'File Size Limit Error' }, // Item 495: { status:495, errorThrown:'Feature Limit Exceeded' }, // Item 496: { status:496, errorThrown:'Invalid Image File' }, // Item 497: { status:497, errorThrown:'Image Exceeds Megapixels' }, // Item 498: { status:498, errorThrown:'Invalid MP3 File' }, // Item 499: { status:499, errorThrown:'Virus Found Error' }, // Item 500: { status:500, errorThrown:'Internal Server Error' }, 590: { status:590, errorThrown:'Internal Server Error' }, // Item 599: { status:599, errorThrown:'Unknow Error' } }, // local Error handle errorGet = function( extended, code, errorThrown ){ if( extended!==true ){ errorThrown = code; code = extended; extended = false; } var data = statusMap[ code ] || { status:code, errorThrown: errorThrown || 'Unknow Error' }, text; if (errorThrown) { data.errorThrown = errorThrown.replace(/(^|_)([A-Z])([A-Z]+)/g, function (all, $1, $2, $3) { return ($1 ? ' ' : '') + $2 + $3.toLowerCase(); }); } text = '{"status":'+data.code+',"errorThrown":"'+data.errorThrown+'"}}'; return extended ? {text:'{"ERROR":'+text+'}',data:{ERROR: data}} : {text:text,data: data}; }, // upload single file with ajax singleAJAX = function( o, originalOptions, jqXHR, progressCallback ){ var xhr = o.xhr(), send = xhr.send, fileObj = o.file; // modify options o.data = null; o.type = 'post'; o.async = true; o.xhr = function(){ return xhr; }; // add progress handle if( xhr.upload ){ xhr.upload.onprogress = function(event){ progressCallback(event); }; } // modify send xhr xhr.send = function(){ xhr.setRequestHeader( 'Content-Type', 'application/octet-stream' ); xhr.setRequestHeader( 'If-Modified-Since', 'Mon, 26 Jul 1997 05:00:00 GMT' ); xhr.setRequestHeader( 'Cache-Control', 'no-cache' ); xhr.setRequestHeader( 'content-disposition', 'attachment; filename="'+encodeURIComponent(fileObj.fileName||fileObj.name)+'"; name="'+o['_inputName']+'"' ); if(xhr.sendAsBinary && typeof fileObj.getAsBinary == "function") { xhr.sendAsBinary(fileObj.getAsBinary()); } else{ send.call(xhr,fileObj); } }; }, // handle multiple files to upload with ajax multiAJAX = function( o, originalOptions, jqXHR, progressCallback ){ // state whether xhr abort var isAbort = false, // state whether error-caused isError = false; // results send and abort handle return { send: function( headers, completeCallback ){ var inputName = o['_inputName'], fileStack = $.merge([],o.file.files), fileStackLength = fileStack.length, // response data store responseText = [], responseData = [], headerStack = [], // completeCallback nextItem = function( i ){ if( i<fileStackLength ){ var fileObj = fileStack[i], fileItem = { name: inputName, fileName: fileObj.name || fileObj.fileName, fileSize: fileObj.size || fileObj.fileSize, fileType: fileObj.type }, xhrItemProgress = function( event ){ if( isAbort ){ // abort item if main xhr is abort xhrItem.abort(); } else{ // set status of main xhr jqXHR.status = 102; jqXHR.statusText = 'progress-item'; // call progress handle progressCallback( // modify progress event object $.extend(event,{ item: i+1, itemTotal: fileStackLength, itemLoaded: i, data: {item:fileItem} }), jqXHR.statusText, jqXHR ); } }, xhrItemDone = function( data, textStatus, xhrItem ){ // overwrite original status textStatus = 'success-item'; var status = 290; if( isAbort ){ // abort item if main xhr is abort xhrItem.abort(); } else{ if( data.ERROR ){ data = { item: fileItem, ERROR: errorGet(data.ERROR.status || 590, data.ERROR.errorThrown).data }; // set error status isError = true; status = data.ERROR.status; textStatus = 'error-item'; } else{ // store data responseText.push(xhrItem.responseText); responseData.push(data); data = {success:data, item:fileItem}; } // set status of main xhr jqXHR.status = status; jqXHR.statusText = textStatus; // call progress handle progressCallback( // simulate progress event object $.extend($.Event(),{ type: 'progress', lengthComputable: true, position: fileItem.fileSize, total: fileItem.fileSize, loaded: fileItem.fileSize, item: i+1, itemTotal: fileStackLength, itemLoaded: i+1, data: data }), textStatus, jqXHR ); } }, xhrItemFail = function( xhrItem, textStatus, errorThrown ){ // use the advanced error handling from success callback var error = errorGet( true, xhrItem.status, errorThrown ); xhrItem.responseText = error.text; xhrItemDone.call(xhrItem, error.data, textStatus, xhrItem ); }, xhrItemAlways = function(){ if( isAbort ){ xhrItem.abort(); } else{ // store responseHeaders headerStack.push( xhrItem.getAllResponseHeaders() ); // start xhr for next item nextItem(i+1); } }, // setup options for AJAX item xhrItemOptions = $.extend( {}, originalOptions, { _inputName: inputName, file: fileObj }), // xhr object for current item xhrItem; // check accept mimetype of file if( !mime.mime(fileItem.fileType,o.fileMimeAccept) ){ xhrItemOptions.dataType = 'ERROR'; xhrItemOptions.ERROR = errorGet(493).data; } else if( (fileItem.fileSize) > o.fileSizeMax ){ xhrItemOptions.dataType = 'ERROR'; xhrItemOptions.ERROR = errorGet(494).data; } // call AJAX xhrItem = $.ajax(xhrItemOptions); xhrItem .progress(xhrItemProgress) .done(xhrItemDone) .fail(xhrItemFail) .always(xhrItemAlways); } else{ var status = 200, statusText = 'success'; if( isError ){ status = 491; statusText = errorGet(491).data.errorThrown; } else if( !responseData.length ){ status = 492; statusText = errorGet(492).data.errorThrown; } // build responseText from all responses jqXHR.responseText = '['+responseText.join(',')+']'; jqXHR.responseXML = null; // execute callback completeCallback( status, statusText, { json: responseData, text: jqXHR.responseText }, headerStack.join('') ); } }; // start first xhr for first item nextItem(0); }, abort: function( statusText ){ // set abort state isAbort = true; } }; }; //#JSCOVERAGE_IF false // container for iframes var container, // handle single file upload with iframe singleIFRAME = function( o, originalOptions, jqXHR, progressCallback ){ o.url = originalOptions.url + (rquery.test(originalOptions.url) ? '&' : '?') + $.param( originalOptions.data ); // state whether xhr abort var isAbort = false, isTimeout = false, isComplete = false, isSuccess = false; // results send and abort handle return { send: function( headers, completeCallback ){ var id = $.uid('upload'), // iframe io, ioNode, // form form, formNode, // file input file = $(o.file), fileNode = o.file, fileNodeName = file.attr('name'), filePlaceholder, // simulate fileObj fileItem = { fileName: file.val(), fileSize: 204800, name: fileNodeName || 'NewFile' }; // get mime type of file var temp = /\.([^.]+)$/.exec(fileItem.fileName); fileItem.fileType = mime.mime(temp ? temp[1] : ''); // complete handle var complete = function( event, responseText ){ clearInterval(interval); if( !isAbort && !isComplete ){ isComplete = true; var error, status, statusText, responseData; // get response text if( !responseText ){ if( isTimeout ){ responseText = errorGet(true,408).text; } else if( ioNode.contentWindow && ioNode.contentWindow.document ){ responseText = ioNode.contentWindow.document.body ? ioNode.contentWindow.document.body.innerHTML : ''; } else if( ioNode.contentDocument && ioNode.contentDocument.document ){ responseText = ioNode.contentDocument.document.body ? ioNode.contentDocument.document.body.innerHTML : ''; } if( !responseText ){ responseText = errorGet(true,408).text; } } // try to parse JSON from response text try{ responseText = responseText.replace(/^(<pre[^>]*>)([^]*?)(<[^<]*pre>)$/,"$2"); responseData = $.parseJSON(responseText || "null"); if (responseData.ERROR) { error = errorGet(true, responseData.ERROR.status, responseData.ERROR.errorThrown); responseText = error.text; responseData = error.data; } } catch(error){ error = errorGet( true, 490 ); responseText = error.text; responseData = error.data; } // set status if (responseData.ERROR) { status = responseData.ERROR.status; statusText = responseData.ERROR.errorThrown; } else { status = 200; statusText = 'success'; } // set success state isSuccess = status===200; // handle progress item success/error // set date for progress event var progressEventData = {item:fileItem}; if( responseData.ERROR ){ progressEventData.ERROR = responseData.ERROR; } else{ progressEventData.success = responseData; } // set status of main xhr jqXHR.status = isSuccess ? 290 : status; jqXHR.statusText = isSuccess ? 'success-item' : 'error-item'; // call progress handle progressCallback( // simulate progress event object $.extend($.Event(),{ type: 'progress', lengthComputable: true, position: 204800, total: 204800, loaded: 204800, item: 1, itemTotal: 1, itemLoaded: 1, data: progressEventData }), jqXHR.statusText, jqXHR ); // set status of main xhr if( isSuccess ){ jqXHR.status = 290; jqXHR.statusText = 'success'; } else{ jqXHR.status = 491; jqXHR.statusText = errorGet(491).data.errorThrown; } // build responseText from all responses jqXHR.responseText = '['+responseText+']'; jqXHR.responseXML = null; // execute callback completeCallback( jqXHR.status, jqXHR.statusText, { json: responseData ? [responseData] : [], text: jqXHR.responseText }, '' ); } }, // progress handle interval, progressLoaded = 0, progress = function(){ if( !isAbort ){ jqXHR.status = 102; var textStatus = jqXHR.statusText = 'progress-item'; progressLoaded += 2048; if( progressLoaded > 203776 ){ clearInterval(interval); progressLoaded = 203776; } progressCallback( // simulate progress event object $.extend($.Event(),{ type: 'progress', lengthComputable: true, position: progressLoaded, total: 204800, loaded: progressLoaded, item: 1, itemTotal: 1, itemLoaded: 0, data: {item:fileItem} }), textStatus, jqXHR ); } }; // check accept mimetype of file if( mime.mime(fileItem.fileName,o.fileMimeAccept) ){ complete( {}, errorGet(true,493).text ); } else{ // iframe container container = container || $('<div>') .css('display','none') .appendTo( document.body ); // iframe to send form io = $('<iframe name="'+id+'"></iframe>').appendTo( container ); ioNode = io[0]; // bind success/error callback event if( window.attachEvent ){ ioNode.attachEvent( 'onload', complete); } else{ ioNode.addEventListener( 'load', function(){ var self = this, args = arguments, look = function(){ var body = (ioNode.contentDocument || ioNode.contentWindow && ioNode.contentWindow.document || {}).body; if( (body && body.innerHTML) || loop >= 100 ){ clearInterval(interval); complete.apply(self,args); } }, loop = 0, // fix bug in load event, use own check of load after load is triggered interval = setInterval(look,10); look(); }, false ); } // form form = $('<form>') .attr({ target: id, method: 'POST', action: o.url + '&handleashtml=1', enctype: 'multipart/form-data', encoding: 'multipart/form-data' }) .appendTo( container ); formNode = form[0]; if( o.timeout > 0 ){ // Timeout checker setTimeout(function(){ // Check to see if the request is still happening if( !isAbort ){ isTimeout = true; complete(); } }, o.timeout); } // run progress if defined if( o.progress ){ // simulate xhr progress interval interval = setInterval(progress,25); } // send file filePlaceholder = file.clone().insertAfter(file).attr('disabled',true); if( !fileNodeName ){ file.attr('name','NewFile'); } form.append(file); formNode.submit(); if( !fileNodeName ){ file.removeAttr('name'); } file.insertBefore(filePlaceholder); filePlaceholder.remove(); form.remove(); } }, abort: function( statusText ){ // set abort state isAbort = true; } }; }; //#JSCOVERAGE_ENDIF $.ajaxTransport( 'json', function( o, originalOptions, jqXHR ){ var progressHook = function(){ var context = o.context || jqXHR, progress = jqXHR.progress, deferred = $.Deferred(); jqXHR.progress = function(){ deferred.progress.apply( deferred, arguments ); return jqXHR; } if( o.progress ){ jqXHR.progress(o.progress); } return (function(){ deferred.notifyWith( context, arguments ); }); }; // transport file if( o.file ){ // be sure o.file is not modified var fileData = o.file = originalOptions.file, fileObj = toString.call(fileData)==='[object File]', fileElem = fileObj ? $() : $(fileData); if( fileElem.is(':file') ){ // prepare options o['_inputName'] = fileElem.attr('name') || 'NewFile'; o.fileMimeAccept = o.fileMimeAccept || fileElem.attr('accept') || '*/*'; originalOptions.data = $.extend( true, {ErrorAction:'JSONAjaxTransportFileError'}, originalOptions.data ); if( html5 ){ return multiAJAX( o, originalOptions, jqXHR, progressHook() ); } else{ return singleIFRAME( o, originalOptions, jqXHR, progressHook() ); } } else if( fileObj && o['_inputName'] ){ return singleAJAX( o, originalOptions, jqXHR, progressHook() ); } } }); // handle client error to prevent call a real xhr $.ajaxTransport( 'ERROR', function( o, originalOptions, jqXHR ){ return { send: function( headers, completeCallback ){ var error = o.ERROR || errorGet(490).data; completeCallback( error.status, error.errorThrown, {}, '' ); } }; }); return de_epages; });