diff --git a/app/assets/javascripts/app/controllers/_application_controller_form.js.coffee b/app/assets/javascripts/app/controllers/_application_controller_form.js.coffee index a58bfef21..4885ebae0 100644 --- a/app/assets/javascripts/app/controllers/_application_controller_form.js.coffee +++ b/app/assets/javascripts/app/controllers/_application_controller_form.js.coffee @@ -290,14 +290,24 @@ class App.ControllerForm extends App.Controller # add file uploader u = => - uploader = new qq.FileUploader( - element: document.getElementById(fileUploaderId) - action: '/api/ticket_attachment_new' - debug: false - params: - form_id: @form_id + @el.find('#' + fileUploaderId ).fineUploader( + request: + endpoint: '/api/ticket_attachment_new' + params: + form_id: @form_id + text: + uploadButton: '' + template: '
{dragZoneText}
' +
+ ' ' +
+ 'tag + if (innerHTML && innerHTML.match(/^'); + // src="javascript:false;" removes ie6 prompt on https + + iframe.setAttribute('id', id); + + iframe.style.display = 'none'; + document.body.appendChild(iframe); + + return iframe; + }, + /** + * Creates form, that will be submitted to iframe + */ + _createForm: function(iframe, params){ + // We can't use the following code in IE6 + // var form = document.createElement('form'); + // form.setAttribute('method', 'post'); + // form.setAttribute('enctype', 'multipart/form-data'); + // Because in this case file won't be attached to request + var protocol = this._options.demoMode ? "GET" : "POST" + var form = qq.toElement(''); + + var queryString = qq.obj2url(params, this._options.endpoint); + + form.setAttribute('action', queryString); + form.setAttribute('target', iframe.name); + form.style.display = 'none'; + document.body.appendChild(form); + + return form; + } +}); +/** + * Class for uploading files using xhr + * @inherits qq.UploadHandlerAbstract + */ +qq.UploadHandlerXhr = function(o){ + qq.UploadHandlerAbstract.apply(this, arguments); + + this._files = []; + this._xhrs = []; + + // current loaded size in bytes for each file + this._loaded = []; +}; + +// static method +qq.UploadHandlerXhr.isSupported = function(){ + var input = document.createElement('input'); + input.type = 'file'; + + return ( + 'multiple' in input && + typeof File != "undefined" && + typeof FormData != "undefined" && + typeof (new XMLHttpRequest()).upload != "undefined" ); +}; + +// @inherits qq.UploadHandlerAbstract +qq.extend(qq.UploadHandlerXhr.prototype, qq.UploadHandlerAbstract.prototype) + +qq.extend(qq.UploadHandlerXhr.prototype, { + /** + * Adds file to the queue + * Returns id to use with upload, cancel + **/ + add: function(file){ + if (!(file instanceof File)){ + throw new Error('Passed obj in not a File (in qq.UploadHandlerXhr)'); + } + + return this._files.push(file) - 1; + }, + getName: function(id){ + var file = this._files[id]; + // fix missing name in Safari 4 + //NOTE: fixed missing name firefox 11.0a2 file.fileName is actually undefined + return (file.fileName !== null && file.fileName !== undefined) ? file.fileName : file.name; + }, + getSize: function(id){ + var file = this._files[id]; + return file.fileSize != null ? file.fileSize : file.size; + }, + /** + * Returns uploaded bytes for file identified by id + */ + getLoaded: function(id){ + return this._loaded[id] || 0; + }, + isValid: function(id) { + return this._files[id] !== undefined; + }, + reset: function() { + qq.UploadHandlerAbstract.prototype.reset.apply(this, arguments); + this._files = []; + this._xhrs = []; + this._loaded = []; + }, + /** + * Sends the file identified by id and additional query params to the server + * @param {Object} params name-value string pairs + */ + _upload: function(id, params){ + this._options.onUpload(id, this.getName(id), true); + + var file = this._files[id], + name = this.getName(id), + size = this.getSize(id); + + this._loaded[id] = 0; + + var xhr = this._xhrs[id] = new XMLHttpRequest(); + var self = this; + + xhr.upload.onprogress = function(e){ + if (e.lengthComputable){ + self._loaded[id] = e.loaded; + self._options.onProgress(id, name, e.loaded, e.total); + } + }; + + xhr.onreadystatechange = function(){ + if (xhr.readyState == 4){ + self._onComplete(id, xhr); + } + }; + + // build query string + params = params || {}; + params[this._options.inputName] = name; + var queryString = qq.obj2url(params, this._options.endpoint); + + var protocol = this._options.demoMode ? "GET" : "POST"; + xhr.open(protocol, queryString, true); + xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest"); + xhr.setRequestHeader("X-File-Name", encodeURIComponent(name)); + xhr.setRequestHeader("Cache-Control", "no-cache"); + if (this._options.forceMultipart) { + var formData = new FormData(); + formData.append(this._options.inputName, file); + file = formData; + } else { + xhr.setRequestHeader("Content-Type", "application/octet-stream"); + //NOTE: return mime type in xhr works on chrome 16.0.9 firefox 11.0a2 + xhr.setRequestHeader("X-Mime-Type",file.type ); + } + for (key in this._options.customHeaders){ + xhr.setRequestHeader(key, this._options.customHeaders[key]); + }; + + this.log('Sending upload request for ' + id); + xhr.send(file); + }, + _onComplete: function(id, xhr){ + "use strict"; + // the request was aborted/cancelled + if (!this._files[id]) { return; } + + var name = this.getName(id); + var size = this.getSize(id); + var response; //the parsed JSON response from the server, or the empty object if parsing failed. + + this._options.onProgress(id, name, size, size); + + this.log("xhr - server response received for " + id); + this.log("responseText = " + xhr.responseText); + + try { + if (typeof JSON.parse === "function") { + response = JSON.parse(xhr.responseText); + } else { + response = eval("(" + xhr.responseText + ")"); + } + } catch(error){ + this.log('Error when attempting to parse xhr response text (' + error + ')', 'error'); + response = {}; + } + + if (xhr.status !== 200 || !response.success){ + if (this._options.onAutoRetry(id, name, response, xhr)) { + return; + } + } + + this._options.onComplete(id, name, response, xhr); + + this._xhrs[id] = null; + this._dequeue(id); + }, + _cancel: function(id){ + this._options.onCancel(id, this.getName(id)); + + this._files[id] = null; + + if (this._xhrs[id]){ + this._xhrs[id].abort(); + this._xhrs[id] = null; + } + } +}); +(function($) { + "use strict"; + var uploader, $el, init, dataStore, pluginOption, pluginOptions, addCallbacks, transformOptions, isValidCommand, + delegateCommand; + + pluginOptions = ['uploaderType']; + + init = function (options) { + if (options) { + var xformedOpts = transformOptions(options); + addCallbacks(xformedOpts); + + if (pluginOption('uploaderType') === 'basic') { + uploader(new qq.FineUploaderBasic(xformedOpts)); + } + else { + uploader(new qq.FineUploader(xformedOpts)); + } + } + + return $el; + }; + + dataStore = function(key, val) { + var data = $el.data('fineuploader'); + + if (val) { + if (data === undefined) { + data = {}; + } + data[key] = val; + $el.data('fineuploader', data); + } + else { + if (data === undefined) { + return null; + } + return data[key]; + } + }; + + //the underlying Fine Uploader instance is stored in jQuery's data stored, associated with the element + // tied to this instance of the plug-in + uploader = function(instanceToStore) { + return dataStore('uploader', instanceToStore); + }; + + pluginOption = function(option, optionVal) { + return dataStore(option, optionVal); + }; + + //implement all callbacks defined in Fine Uploader as functions that trigger appropriately names events and + // return the result of executing the bound handler back to Fine Uploader + addCallbacks = function(transformedOpts) { + var callbacks = transformedOpts.callbacks = {}; + + $.each(new qq.FineUploaderBasic()._options.callbacks, function(prop, func) { + var name, $callbackEl; + + name = /^on(\w+)/.exec(prop)[1]; + name = name.substring(0, 1).toLowerCase() + name.substring(1); + $callbackEl = $el; + + callbacks[prop] = function() { + var args = Array.prototype.slice.call(arguments); + return $callbackEl.triggerHandler(name, args); + }; + }); + }; + + //transform jQuery objects into HTMLElements, and pass along all other option properties + transformOptions = function(source, dest) { + var xformed, arrayVals; + + if (dest === undefined) { + if (source.uploaderType !== 'basic') { + xformed = { element : $el[0] }; + } + else { + xformed = {}; + } + } + else { + xformed = dest; + } + + $.each(source, function(prop, val) { + if ($.inArray(prop, pluginOptions) >= 0) { + pluginOption(prop, val); + } + else if (val instanceof $) { + xformed[prop] = val[0]; + } + else if ($.isPlainObject(val)) { + xformed[prop] = {}; + transformOptions(val, xformed[prop]); + } + else if ($.isArray(val)) { + arrayVals = []; + $.each(val, function(idx, arrayVal) { + if (arrayVal instanceof $) { + $.merge(arrayVals, arrayVal); + } + else { + arrayVals.push(arrayVal); + } + }); + xformed[prop] = arrayVals; + } + else { + xformed[prop] = val; + } + }); + + if (dest === undefined) { + return xformed; + } + }; + + isValidCommand = function(command) { + return $.type(command) === "string" && + !command.match(/^_/) && //enforce private methods convention + uploader()[command] !== undefined; + }; + + //assuming we have already verified that this is a valid command, call the associated function in the underlying + // Fine Uploader instance (passing along the arguments from the caller) and return the result of the call back to the caller + delegateCommand = function(command) { + return uploader()[command].apply(uploader(), Array.prototype.slice.call(arguments, 1)); + }; + + $.fn.fineUploader = function(optionsOrCommand) { + $el = this; + + if (uploader() && isValidCommand(optionsOrCommand)) { + return delegateCommand.apply(this, arguments); + } + else if (typeof optionsOrCommand === 'object' || !optionsOrCommand) { + return init.apply(this, arguments); + } + else { + $.error('Method ' + optionsOrCommand + ' does not exist on jQuery.fineUploader'); + } + + return this; + }; + +}(jQuery)); diff --git a/app/assets/javascripts/app/views/agent_ticket_create.jst.eco b/app/assets/javascripts/app/views/agent_ticket_create.jst.eco index 8d8c2977b..cd7bac5f5 100644 --- a/app/assets/javascripts/app/views/agent_ticket_create.jst.eco +++ b/app/assets/javascripts/app/views/agent_ticket_create.jst.eco @@ -5,13 +5,14 @@