define('jira/attachment/inline-attach', ['jira/util/urls', 'jira/util/formatter', 'jira/lib/class', 'jira/xsrf', 'jira/util/navigator', 'jquery', 'wrm/context-path'], function (urls, formatter, Class, XSRF, Navigator, jQuery, wrmContextPath) { 'use strict'; var contextPath = wrmContextPath(); /** * Convert a file input element into an inline file upload control. * * If possible it will use the FileApi to perform the uploads. This allows * the user to see progress and cancel individual uploads mid way through. * * If the browser does not have the FileApi it will submit uploads in the background using a form. In this mode * the user will not see progress. * *

Use

* *
Markup:
* *
     *   
     * 
* *
JavaScript
* *
     *
     * new InlineAttach("input:file");
     *
     *   or
     *
     * new InlineAttach(jQuery("#id"));
     *
     * 
* * @class InlineAttach * @extends Class */ var InlineAttach = Class.extend({ /** * Creates an inline file attach control * * @constructs * @param {String | jQuery} element the file input to use for uploading. */ init: function init(element) { var $element = jQuery(element); if (InlineAttach.AjaxPresenter.isSupported($element)) { new InlineAttach.AjaxPresenter($element); } else { new InlineAttach.FormPresenter($element); } } }); jQuery.extend(InlineAttach, /** @lends InlineAttach */{ /** * The maxium number of uploads that can occur concurrently. * @static */ MAX_UPLOADS: 2, /** * The amount of time to wait to see if the upload finishes before displaying the progress. We do this so * because the progress bar is not very useful for small files and infact may introduce a flicker as it is * displayed and quickly removed. * @static */ DISPLAY_WAIT: 600, /** * Wraps the passed function in such a way that ensures it always runs in the passed scope. If no scope is passed * the the function is returned unmodified. If no function is passed, a no-operation function is returned. * * @param fn the function to wrap. * @param scope the scope to run the function under. * @return the wrapped function. */ rescope: function rescope(fn, scope) { if (fn) { if (scope) { return jQuery.proxy(fn, scope); } else { return fn; } } else { return jQuery.noop; } }, /** * Copy the passed array-like object. * * @param array the array like object to copy. */ copyArrayLike: function copyArrayLike(array) { return jQuery.makeArray(array); }, /** * Some global render helpers. */ Renderers: { container: function container() { return jQuery("
"); } } }); /** * A class to helper with the impleation of upload logic. It manages the queing of uploads to ensure that only a * certain number of them are active at one time. * @class InlineAttach.Presenter * @extends Class */ InlineAttach.Presenter = Class.extend({ /** * @constructs */ init: function init() { /** * Has the user cancelled the attach? */ this.cancelled = false; /** * The upload that are currently running. */ this.running = []; /** * The uploads that are currently waiting to run.. */ this.waiting = []; }, /** * Add an upload. The upload will be started if there are not too many currently running uploads or otherwise * it will be queued waiting for a call to _finishUpload. * * @param upload the upload to start or queue. * @return true if there are currently running uploads, false otherwise. */ _addUpload: function _addUpload(upload) { //Only start the current upload if we have not reached the concurrent upload limit. if (!this.cancelled) { if (this.running.length >= InlineAttach.MAX_UPLOADS) { this.waiting.push(upload); } else { this.running.push(upload); upload.upload(); } } return this.running.length > 0; }, /** * Call to indicate that the passed upload has finished running. If there are queued uploads one will be * selected and started to replaced the one just finished. * * @param upload that has finished. * @return true if there are currently running uploads, false otherwise. */ _finishUpload: function _finishUpload(upload) { if (!this.cancelled) { InlineAttach.Presenter.removeFromArray(this.waiting, upload); if (InlineAttach.Presenter.removeFromArray(this.running, upload)) { if (this.waiting.length > 0) { var next = this.waiting.shift(); this.running.push(next); next.upload(); } } } return this.running.length > 0; }, /** * Called when the user 'cancels' all uploading. It aborts all running and queued uploads. */ _cancel: function _cancel() { this.cancelled = true; var i; //Make a copy of waiting (in case an upload finishes while we are aborting) and abort them. var wait = InlineAttach.copyArrayLike(this.waiting); for (i = 0; i < wait.length; i++) { wait[i].abort(); } //Make a copy of running (in case the upload finishes while we are aborting) and abort them. var run = InlineAttach.copyArrayLike(this.running); for (i = 0; i < run.length; i++) { run[i].abort(); } this.waiting = []; this.running = []; } }); jQuery.extend(InlineAttach.Presenter, /** @lends InlineAttach.Presenter */{ /** * Removes the first occurance of the passed element from the passed array. * * @param array the array to process. * @param element the element to remove. * @return the element removed or null if no such element is found. */ removeFromArray: function removeFromArray(array, element) { var index = jQuery.inArray(element, array); if (index >= 0) { return array.splice(index, 1); } else { return null; } } }); /** * The overall control logic when file uploads are to occur using form submission in the background. * @class InlineAttach.FormPresenter * @extends InlineAttach.Presenter */ InlineAttach.FormPresenter = InlineAttach.Presenter.extend({ /** * @constructs * @param $element the file input to used for uploading. */ init: function init($element) { this._super(); /** * The UI form where the user is requesting the upload. */ this.form = new InlineAttach.Form(new InlineAttach.FileInput($element, false)); this.form.fileSelector.onChange(jQuery.proxy(this._attach, this)); this.form.onCancel(jQuery.proxy(this._cancel, this)); }, /** * Called when the file input changes to start uploading to the server. * * @param fileName in the file input. */ _attach: function _attach(fileName) { this.form.clearErrors(); if (this.cancelled) { return; } var form = this.form; var data = this._createSubmitData(); //Add a new "File Input" to the form. We use the old input as part of a hidden form that we can submit to the //server in the background. var $oldInput = form.cloneFileInput(); form.fileSelector.clear(); var progress = form.addStaticProgress(fileName); //We only show progress after we are sure the upload will take longer than InlineAttach.DISPLAY_WAIT. var timer = new InlineAttach.Timer(function () { !this.cancelled && progress.show(); }, this); var upload = new InlineAttach.FormUpload({ $input: $oldInput, url: InlineAttach.FormPresenter.DEFAULT_URL, params: data, scope: this, before: function before() { !this.cancelled && progress.start(); }, success: function success(val) { if (this.cancelled) { return; } if (val.id && val.name) { form.addTemporaryFileCheckbox(val.id, val.name, progress); } else if (val.errorMsg) { form.addErrorWithFileName(val.errorMsg, fileName, progress); } else { form.addError(InlineAttach.Text.tr("upload.error.bad.response", fileName), progress); } }, error: function error(text) { if (this.cancelled) { return; } if (text.indexOf("SecurityTokenMissing") >= 0) { form.addError(InlineAttach.Text.tr("upload.xsrf.timeout", fileName), progress); } else { form.addError(InlineAttach.Text.tr("upload.error.unknown", fileName), progress); } }, after: function after() { timer.cancel(); progress.remove(); if (!this.cancelled && !this._finishUpload(upload)) { form.enable(); } } }); progress.onCancel(function () { upload.abort(); }); if (this._addUpload(upload)) { timer.schedule(InlineAttach.DISPLAY_WAIT); form.disable(); } form.fileSelector.focus(); }, /** * Called when the user 'cancels' the upload form, that is, when the user stops the upload. */ _cancel: function _cancel() { this._super(); this.form.enable(); }, _createSubmitData: function _createSubmitData() { var data = { atl_token: this.form.getAtlToken(), formToken: this.form.getFormToken() }; if (this.form.issueId) { data.id = this.form.issueId; } else if (this.form.projectId) { data.create = true; data.projectId = this.form.projectId; } else { throw "Unable to find either an issueId or projectId to submit the attachment to."; } return data; } }); /** * Default location to add temporary attachments using multi-part request and forms. * @static * @memberof InlineAttach.FormPresenter */ InlineAttach.FormPresenter.DEFAULT_URL = contextPath + "/secure/AttachTemporaryFile.jspa?decorator=none"; /** * The overall control logic when file uploads are to occur using direct AJAX and XHR. * @class InlineAttach.AjaxPresenter * @extends InlineAttach.Presenter */ InlineAttach.AjaxPresenter = InlineAttach.Presenter.extend({ /** * @constructs * @param $element the file input used for uploading. */ init: function init($element) { this._super(); this.form = new InlineAttach.Form(new InlineAttach.FileInput($element, true)); this.form.fileSelector.onChange(jQuery.proxy(this._attach, this)); this.form.onCancel(jQuery.proxy(this._cancel, this)); }, /** * Called to attach the passed File objects to the current issue. * * @param files the files to attach to the issue. */ _attach: function _attach(files) { this.form.clearErrors(); if (this.cancelled) { return; } if (files && files.length > 0) { files = this._checkAndFilterFiles(files); if (files) { this._uploadFiles(files); } } this.form.fileSelector.clear().focus(); }, /** * Called to check the passed files to ensure they can be uploaded. Returns an array of files that can be * uploaded. Null will be returned if no files can be uploaded. * * @param files the files that we want to filter. * @return Returns an array of files that can be uploaded. Null will be returned if no files can be uploaded. */ _checkAndFilterFiles: function _checkAndFilterFiles(files) { if (files.length > InlineAttach.AjaxPresenter.MAX_SELECTED_FILES) { this.form.addError(InlineAttach.Text.tr("upload.error.too.many.files", files.length, InlineAttach.AjaxPresenter.MAX_SELECTED_FILES)); return null; } var maxSize = this.form.maxSize; var newFiles = []; for (var i = 0; i < files.length; i++) { try { var file = files[i]; if (file.size === 0) { this.form.addError(InlineAttach.Text.tr("upload.empty.file", file.name)); } else if (maxSize > 0 && file.size > maxSize) { //Note the order of this call is important. We want the size of the file to be based on max file //(i.e. if maxSize is in MB than file.size should be in MB). var sizes = InlineAttach.Text.fileSize(maxSize, file.size); this.form.addError(InlineAttach.Text.tr("upload.too.big", file.name, sizes[1], sizes[0])); } else { //JRADEV-5679: //Firefox throws exceptions on some I/O edge cases with its implementation of the FileAPI. // For example, reading the File.size can throw an exception if the file no longer exists. // So we don't have to add try...catch statements around everything we copy the attributes we // need. newFiles.push({ name: file.name, size: file.size, file: file }); } } catch (e) { this.form.addError(InlineAttach.AjaxUpload.getClientErrorMessage(e, file)); } } return newFiles.length === 0 ? null : newFiles; }, /** * Create and return the data to be submitted with the upload. * * @return the data to be submitted with the upload. */ _createSubmitData: function _createSubmitData() { var data = { atl_token: this.form.getAtlToken(), formToken: this.form.getFormToken() }; if (this.form.issueId) { data.issueId = this.form.issueId; } else if (this.form.projectId) { data.projectId = this.form.projectId; } else { throw "Unable to find either an issueId or projectId to submit the attachment to."; } return data; }, /** * Actually start uploading the files. The passed files have been checked and are valid. * * @param files the files to upload. */ _uploadFiles: function _uploadFiles(files) { var form = this.form; var data = this._createSubmitData(); var that = this; var running = false; jQuery.each(files, function () { var _progress = form.addProgress(this); var file = this; //We only show progress after we are sure the upload will take longer than InlineAttach.DISPLAY_WAIT. var timer = new InlineAttach.Timer(function () { if (!that.cancelled) { _progress.show(); } }); var upload = new InlineAttach.AjaxUpload({ file: file.file, params: jQuery.extend({ filename: file.name, size: file.size }, data), scope: that, url: InlineAttach.AjaxPresenter.DEFAULT_URL, before: function before() { !this.cancelled && _progress.start(); }, progress: function progress(val) { !this.cancelled && _progress.update(val); }, success: function success(val, status) { if (this.cancelled) { return; } if (status === 201) { if (val.id !== undefined && val.name !== undefined) { form.addTemporaryFileCheckbox(val.id, val.name, _progress, file.file); } else { form.addError(InlineAttach.Text.tr("upload.error.bad.response", file.name), _progress); } } else { if (val.token) { form.setAtlToken(val.token); } if (val.errorMessage) { form.addErrorWithFileName(val.errorMessage, file.name, _progress); } else { form.addError(this._getErrorFromStatus(status, file), _progress); } } }, error: function error(text, status) { if (this.cancelled) { return; } if (status < 0) { //This is a client error so just render it. form.addError(text, _progress); } else { var statusError = this._getErrorFromStatus(status, file); if (statusError) { form.addError(statusError, _progress); } else { form.addError(InlineAttach.Text.tr("upload.error.unknown", file.name), _progress); } } }, after: function after() { timer.cancel(); _progress.finish().remove(); if (!this.cancelled && !this._finishUpload(upload)) { form.enable(); } } }); _progress.onCancel(function () { upload.abort(); }); if (that._addUpload(upload)) { running = true; timer.schedule(InlineAttach.DISPLAY_WAIT); } }); //Disable the form if there are any running uploads. The last running upload will enable the form. if (running) { this.form.disable(); } }, _getErrorFromStatus: function _getErrorFromStatus(status, file) { var error; if (status === 0) { error = InlineAttach.Text.tr("upload.error.server.no.reply", file.name); } else if (status === 400) { error = InlineAttach.Text.tr("upload.error.badrequest", file.name); } else if (status === 401) { error = InlineAttach.Text.tr("upload.error.auth", file.name); } else { error = InlineAttach.Text.tr("upload.error.unknown.status", file.name, status); } return error; }, /** * Called when the user clicks cancel on the from they are using to attach files (i.e. the user does not want * to attach any files). */ _cancel: function _cancel() { this._super(); this.form.enable(); } }); jQuery.extend(InlineAttach.AjaxPresenter, /** @lends InlineAttach.AjaxPresenter */{ /** * The default location to attach temporary files using AJAX. * @static */ DEFAULT_URL: contextPath + "/rest/internal/2/AttachTemporaryFile", /** * The number of files that can be attached at one time. * @static */ MAX_SELECTED_FILES: 100, /** * Check to see if AJAX uploads are supported. * * @param $element the input element that will be used for attachments. */ isSupported: function isSupported($element) { if (!$element || !$element[0] || !$element[0].files) { return false; } else { return InlineAttach.AjaxUpload.isSupported(); } } }); /** * Simple wrapper around a HTML file input. * @class InlineAttach.FileInput * @extends Class */ InlineAttach.FileInput = Class.extend({ /** * @constructs * @param $fileInput the file input to wrap. * @param testMultiple tries to make the wrapped file input accept multiple files when set to true. */ init: function init($fileInput, testMultiple) { this.$element = $fileInput; this.$container = $fileInput.parent(); if (testMultiple && this.$element[0].files !== undefined) { this.$element.attr("multiple", "multiple"); this.multiple = true; } else { this.multiple = false; } }, clear: function clear() { this.$element.val(''); return this; }, getFiles: function getFiles() { return this.$element[0].files; }, hasFiles: function hasFiles() { return this.getFiles().length > 0; }, /** * Call the passed function when the wrapped file input changes. The callback will have "this" assigned to the * FileInput and not the wrapped HTML element. * * @param callback the function to call when the file input changes. It will be run with the FileInput assigned * to "this". If the FileApi is supported, the first argument will be the FileApi files from the HTML input. * If the FileApi is *not* supported it will be the string value from the HTML input. */ onChange: function onChange(callback) { var that = this; this.$element.change(function () { if (that.multiple) { callback.call(that, this.files); } else { callback.call(that, that.getFileName()); } }); return this; }, focus: function focus() { if (this._isIE()) { var $e = this.$element; //IE being the usual pain that it is wont focus unless there's this timeout. setTimeout(function () { $e.focus(); }, 0); } else { this.$element.focus(); } return this; }, /** * Clone the current HTML input and replace it with a new one. * * @return the old file input. */ cloneInput: function cloneInput() { var oldElement = this.$element; oldElement.replaceWith(this.$element = oldElement.clone(true)); oldElement.unbind(); return oldElement; }, /** * Return the filename from the wrapped HTML element. We strip out some common garbage that some * browsers add to the name. * * See: http://dev.w3.org/html5/spec/number-state.html#concept-input-type-file-selected * * @function * @return the filename currently in the wrapped input element. */ getFileName: function () { //Match the "c:\fakepath\" from the start of the string provided its not the entire string. var fakepath = /^c:\\fakepath\\(?!$)/i; return function () { var fileName = this.$element.val(); //Remove "c:\fakepath\" from the string if there is stuff after it. // SEE: http://dev.w3.org/html5/spec/number-state.html#concept-input-type-file-selected fileName = fileName.replace(fakepath, ""); if (this._isIE() && fileName.indexOf("\\") >= 0) { //IE returns an absolute path for the selected file, we however only want to display the //filename. fileName = fileName.substring(fileName.lastIndexOf("\\") + 1); } return fileName; }; }(), _isIE: function _isIE() { return Navigator.isIE() && Navigator.majorVersion() < 11; }, before: function before(el) { if (el) { if (el.$element) { el = el.$element; } this.$container.before(el); } } }); (function () { var options = { showPercentage: false, height: "2px" }; var count = 0; /** * Represents a simple progress bar. * @class InlineAttach.ProgressBar * @extends Class */ InlineAttach.ProgressBar = Class.extend({ /** @constructs */ init: function init() { var $container = this.$element = this._renderers.container(); this.$progress = this._renderers.progress().appendTo($container); this.$progress.progressBar(0, options); this.hidden = true; this.old = 0; }, value: function value(_value) { if (_value > 100) { _value = 100; } else if (_value < 0) { _value = 0; } if (this.hidden) { this.$progress.show(); this.hidden = false; } if (this.old !== _value) { this.$progress.progressBar(_value, options); if (_value >= 100) { this.$progress.fadeOut(); } this.old = _value; } }, _renderers: { container: function container() { return jQuery("
").addClass("file-progress"); }, progress: function progress() { return jQuery("
").attr("id", "upload-progress-" + count++).hide(); } } }); })(); /** * Represents the user's view of a progressing upload whose status (i.e. % complete) is known. This includes: * *
    *
  1. Progress Bar
  2. *
  3. File Name and Stats (upload rate, remaining time, etc)
  4. *
  5. Cancel Link
  6. *
      * * @class InlineAttach.UploadProgress * @extends Class */ InlineAttach.UploadProgress = Class.extend({ /** * @constructs * @param file the file whose upload this object represents. */ init: function init(file) { var $container = this.$element = InlineAttach.Renderers.container().hide(); var progress = this.progress = new InlineAttach.ProgressBar(); var content = this._renders.content(file.name); //The content is the place for the text (filename, size, ...) this.$content = content.$content; this.$cancel = content.$cancel; $container.append(content.$element).append(progress.$element); this.total = file.size; this.current = 0; this.name = file.name; this.timer = new InlineAttach.Timer(this._update, this); this.rateNumerator = 0; this.rateDenominator = 0; this._title(InlineAttach.Text.tr('upload.progress.title.waiting')); }, start: function start() { this.started = this._now(); this.startedSize = 0; return this; }, update: function update(current) { this.timer.cancel(); return this._update(current); }, /** * Called when the current status of the file upload needs to be updated. * * @param current the current amount uploaded in bytes. Can be undefined (i.e. not passed) when asked to do * and update with the last known value. */ _update: function _update(current) { var now = this._now(); //If current is not passed then use the last known value. if (current === undefined) { current = this.current; } else if (current !== this.current) { this.lastUpdate = now; this.current = current; } var text = InlineAttach.Text; var percentage = Math.min(100, Math.round(current / this.total * 100)); var partSize = text.currentOutOfTotalSize(current, this.total); var rateDisplay; var remainingDisplay; this.progress.value(percentage); var timeDiff = (now - this.started) / 1000; //seconds //Calculate the rate every 2 seconds. if (timeDiff >= 2) { //Don't use the first calculation. The OS buffers will still be filling and the //upload will appear to be super quick. if (this.startedSize > 0) { this._addRate((current - this.startedSize) / timeDiff); } this.started = now; this.startedSize = current; } var rate = this._calcRate(); //Only start outputting stats after some data has been sent to stabilise the numbers. if (current >= InlineAttach.UploadProgress.DATA_MIN && rate > 0) { var remaining = Math.max(1, (this.total - current) / rate); //seconds. rateDisplay = text.rate(rate); remainingDisplay = text.time(remaining); } if (now - this.lastUpdate >= InlineAttach.UploadProgress.STALLED_TIMEOUT) { //If we have not seen movement in a while then assume we have stalled. this._content(text.tr("upload.file.stalled", this.name)); if (rateDisplay) { this._title(text.tr("upload.progress.title.known.stalled", rateDisplay, partSize)); } else { this._title(text.tr("upload.progress.title.unknown.stalled", partSize)); } } else { if (rateDisplay) { this._title(text.tr("upload.progress.title.known", rateDisplay, partSize, remainingDisplay)); this._content(text.tr("upload.file.remaining", this.name, remainingDisplay)); } else { this._title(partSize); this._content(this.name); } } if (current < this.total) { //Schedule an update in 1 second if we don't get one from the XHR before than. this.timer.schedule(InlineAttach.UploadProgress.UPLOAD_REFRESH); } return this; }, finish: function finish() { this.progress.value(100); this.timer.cancel(); return this; }, /** * Call the passed function when the user clicks the cancel link associated with an uploads progress. The "this" * variable in the callback is assigned to the current UploadProgress. * * @param callback the function to call when the user clicks the cancel link. */ onCancel: function onCancel(callback) { var that = this; this.$cancel.click(function (e) { e.preventDefault(); callback.call(that); }); return this; }, remove: function remove() { this.$element.remove(); return this; }, hide: function hide() { this.$element.hide(); return this; }, show: function show() { this.$element.fadeIn(); return this; }, _title: function _title(title) { this.$element.attr("title", title); return this; }, _content: function _content(rem) { this.$content.text(rem); return this; }, _addRate: function _addRate(rate) { var weight = InlineAttach.UploadProgress.WEIGHT; this.rateNumerator = this.rateNumerator * weight + rate; this.rateDenominator = this.rateDenominator * weight + 1; }, _calcRate: function _calcRate() { if (this.rateDenominator === 0) { return 0; } var value = this.rateNumerator / this.rateDenominator; if (Math.abs(value) < 0.005) { return 0; } else { return value; } }, _now: function _now() { return new Date().getTime(); }, _renders: { content: function content(fileName) { var text = InlineAttach.Text.tr("upload.file.waiting", fileName); var $container = jQuery("
      "); var $content = jQuery("").text(text); var $cancel = jQuery("").text(InlineAttach.Text.tr("upload.cancel")); $container.append($content).append(" ").append($cancel); return { $element: $container, $content: $content, $cancel: $cancel }; } } }); jQuery.extend(InlineAttach.UploadProgress, /** @lends InlineAttach.UploadProgress */{ /** * If we don't get AJAX progress within this timeout we think things have stalled. * @static */ STALLED_TIMEOUT: 10000, /** * We force the refresh of the upload stats using this interval. * @static */ UPLOAD_REFRESH: 2000, /** * The minimum amount of data to send before we start calculating stats. * @static */ DATA_MIN: 20 * 1024, /** * The decaying factor to use in the weighted average calculation. * @static */ WEIGHT: 0.7 }); /** * Represents the user's view of a progressing upload whose status (i.e. % complete) is unknown. This basically * includes a name and an animated throbber. * @class InlineAttach.UnknownProgress * @extends Class */ InlineAttach.UnknownProgress = Class.extend({ /** * @constructs * @param fileName the name of the file this progress is meant to represent. */ init: function init(fileName) { var content = this._renders.content(fileName); this.$element = content.$element; this.$cancel = content.$cancel; this.$content = content.$content; this.fileName = fileName; this._title(InlineAttach.Text.tr("upload.progress.title.waiting")); }, remove: function remove() { this.$element.remove(); return this; }, hide: function hide() { this.$element.hide(); return this; }, show: function show() { this.$element.fadeIn(); return this; }, start: function start() { this._title(InlineAttach.Text.tr("upload.progress.title.running")); this._content(this.fileName); return this; }, /** * Call the passed function when the user clicks the cancel link associated with an uploads progress. The "this" * variable in the callback is assigned to the current UploadProgress. * * @param callback the function to call when the user clicks the cancel link. */ onCancel: function onCancel(callback) { var that = this; this.$cancel.click(function (e) { e.preventDefault(); callback.call(that); }); return this; }, _title: function _title(title) { this.$element.attr("title", title); return this; }, _content: function _content(text) { this.$content.text(text); return this; }, _renders: { content: function content(fileName) { var text = InlineAttach.Text.tr("upload.file.waiting", fileName); var $cancel = jQuery("").text(InlineAttach.Text.tr("upload.cancel")); var $loading = jQuery("
      "); var $content = jQuery("").text(text); $loading.append($content).append(" ").append($cancel); var $container = InlineAttach.Renderers.container().append($loading); return { $element: $container, $cancel: $cancel, $content: $content }; } } }); /** * Represents the HTML form that contains a FileInput used for file uploads to JIRA. * @class InlineAttach.Form * @extends Class */ InlineAttach.Form = Class.extend({ /** * @constructs * @param fileInput the FileInput whose associated HTML form we are going to wrap. */ init: function init(fileInput) { this.fileSelector = fileInput; this.$form = fileInput.$element.closest("form"); this.maxSize = parseInt(this.$form.find('#attach-max-size').text() || jQuery('#attach-max-size').text()); if (isNaN(this.maxSize)) { throw "Unable to find maximum upload size on form."; } var assigned = false; var val = parseInt(this.$form.find(":input[name=id]").val()); if (!isNaN(val)) { assigned = true; this.issueId = val; } val = parseInt(this.$form.find(":input[name=pid]").val()); if (!isNaN(val)) { assigned = true; this.projectId = val; } if (!assigned) { throw "Unable to find either an issueId or projectId to submit the attachment to."; } }, getAtlToken: function getAtlToken() { var $atlToken = this.$form.find("input[name='atl_token']"); if ($atlToken.length > 0) { return $atlToken.val(); } else { return urls.atl_token(); } }, setAtlToken: function setAtlToken(token) { var $token = this.$form.find("input[name='atl_token']"); if ($token.length > 0) { $token.val(token); } else { XSRF.updateTokenOnPage(token); } return this; }, getFormToken: function getFormToken() { var $formToken = this.$form.find("input[name='formToken']"); if ($formToken.length > 0) { return $formToken.val(); } else { return null; } }, disable: function disable() { this._getFormSubmits().attr("disabled", "disabled"); return this; }, enable: function enable() { this._getFormSubmits().removeAttr("disabled"); return this; }, /** * Creates and adds an UploadProgress to the current form for the passed file. * * @param file the file to create the UploadProgress for. * @return {InlineAttach.UploadProgress} the newly created UploadProgress. */ addProgress: function addProgress(file) { var prog = new InlineAttach.UploadProgress(file); this._addElement(prog.$element); return prog; }, /** * Creates and adds an UnknownProgress to the current form for the passed file name. * * @param fileName the fileName to create the UnknownProgress for. * @return {InlineAttach.UnknownProgress} the newly created UnknownProgress. */ addStaticProgress: function addStaticProgress(fileName) { var prog = new InlineAttach.UnknownProgress(fileName); this._addElement(prog.$element); return prog; }, /** * Adds the checkbox to the form with the passed value and name. * * @param value the submit value for the new checkbox. * @param name the name of the checkbox (i.e. what the user sees) * @param replaceObj if non false, the checkbox will replace the passed object * @param file optionally the FileAPI file object that was uploaded */ addTemporaryFileCheckbox: function addTemporaryFileCheckbox(value, name, replaceObj, file) { var $thumbNail = this.addLocalThumbnailImage(name, file); var $element = InlineAttach.Renderers.container(); var $label = jQuery('