UNPKG

infusion

Version:

Infusion is an application framework for developing flexible stuff with JavaScript

388 lines (345 loc) 13.6 kB
/* Copyright The Infusion copyright holders See the AUTHORS.md file at the top-level directory of this distribution and at https://github.com/fluid-project/infusion/raw/main/AUTHORS.md. Licensed under the Educational Community License (ECL), Version 2.0 or the New BSD license. You may not use this file except in compliance with one these Licenses. You may obtain a copy of the ECL 2.0 License and BSD License at https://github.com/fluid-project/infusion/raw/main/Infusion-LICENSE.txt */ "use strict"; fluid.defaults("fluid.uploader.html5", { gradeNames: "fluid.uploader.multiFileUploader", components: { strategy: { type: "fluid.uploader.html5Strategy" } } }); fluid.defaults("fluid.uploader.html5Strategy", { gradeNames: ["fluid.uploader.strategy"], components: { local: { // TODO: Would be nice to have some way to express that this is a "natural covariant refinement" type: "fluid.uploader.html5Strategy.local" }, remote: { type: "fluid.uploader.html5Strategy.remote" } } }); // FLUID-6056 ( https://issues.fluidproject.org/browse/FLUID-6056 ) // Using `navigator.msLaunchUri` to browser detect IE10+ and MS Edge, because // it is exclusive to Microsoft browsers for IE 10 and later. fluid.registerNamespace("fluid.uploader.html5.browser"); fluid.uploader.html5.browser.isMS = !!navigator.msLaunchUri; // TODO: The following two or three functions probably ultimately belong on a that responsible for // coordinating with the XHR. A fileConnection object or something similar. fluid.uploader.html5Strategy.fileSuccessHandler = function (file, events, xhr) { events.onFileSuccess.fire(file, xhr.responseText, xhr); events.onFileComplete.fire(file); }; fluid.uploader.html5Strategy.fileErrorHandler = function (file, events, xhr) { events.onFileError.fire(file, fluid.uploader.errorConstants.UPLOAD_FAILED, xhr.status, xhr); events.onFileComplete.fire(file); }; fluid.uploader.html5Strategy.fileStopHandler = function (file, events, xhr) { events.onFileError.fire(file, fluid.uploader.errorConstants.UPLOAD_STOPPED, xhr.status, xhr); events.onFileComplete.fire(file); }; fluid.uploader.html5Strategy.monitorFileUploadXHR = function (file, events, xhr) { xhr.onreadystatechange = function () { if (xhr.readyState === 4) { var status = xhr.status; if (status >= 200 && status <= 204) { fluid.uploader.html5Strategy.fileSuccessHandler(file, events, xhr); } else if (status === 0) { fluid.uploader.html5Strategy.fileStopHandler(file, events, xhr); } else { fluid.uploader.html5Strategy.fileErrorHandler(file, events, xhr); } } }; xhr.upload.onprogress = function (pe) { events.onFileProgress.fire(file, pe.loaded, pe.total); }; }; fluid.uploader.html5Strategy.uploadNextFile = function (queue, uploadFile) { var batch = queue.currentBatch; var file = batch.files[batch.fileIdx]; uploadFile(file); }; fluid.uploader.html5Strategy.uploadFile = function (that, file) { that.events.onFileStart.fire(file); that.currentXHR = that.createXHR(); fluid.uploader.html5Strategy.monitorFileUploadXHR(file, that.events, that.currentXHR); that.fileSender.send(file, that.queueSettings, that.currentXHR); }; fluid.uploader.html5Strategy.stop = function (that) { that.queue.isUploading = false; that.currentXHR.abort(); that.events.onUploadStop.fire(); }; fluid.defaults("fluid.uploader.html5Strategy.remote", { gradeNames: ["fluid.uploader.remote"], components: { fileSender: { type: "fluid.uploader.html5Strategy.fileSender" } }, invokers: { createXHR: "fluid.uploader.html5Strategy.createXHR", // Upload files in the current batch without exceeding the fileUploadLimit uploadNextFile: { funcName: "fluid.uploader.html5Strategy.uploadNextFile", args: ["{that}.queue", "{that}.uploadFile"] }, uploadFile: { funcName: "fluid.uploader.html5Strategy.uploadFile", args: ["{that}", "{arguments}.0"] }, stop: { funcName: "fluid.uploader.html5Strategy.stop", args: ["{that}"] } } }); fluid.uploader.html5Strategy.createXHR = function () { return new XMLHttpRequest(); }; fluid.uploader.html5Strategy.createFormData = function () { return new FormData(); }; // Set additional POST parameters for xhr fluid.uploader.html5Strategy.setPostParams = function (formData, postParams) { $.each(postParams, function (key, value) { formData.append(key, value); }); }; fluid.defaults("fluid.uploader.html5Strategy.fileSender", { gradeNames: ["fluid.component", "fluid.contextAware"], invokers: { send: { funcName: "fluid.fail", args: "Error instantiating HTML5 Uploader - browser does not support FormData feature. Please try version 1.4 or earlier of Uploader which has Firefox 3.x support" } }, contextAwareness: { technology: { checks: { formData: { contextValue: "{fluid.browser.supportsFormData}", gradeNames: "fluid.uploader.html5Strategy.formDataSender" } } } } }); /******************************************************* * HTML5 FormData Sender, used by most modern browsers * *******************************************************/ fluid.defaults("fluid.uploader.html5Strategy.formDataSender", { gradeNames: ["fluid.component"], invokers: { createFormData: "fluid.uploader.html5Strategy.createFormData", send: { funcName: "fluid.uploader.html5Strategy.sendFormData", args: ["{that}.createFormData", "{arguments}.0", "{arguments}.1", "{arguments}.2"] } } }); /* * Uploads the file using the HTML5 FormData object. */ fluid.uploader.html5Strategy.sendFormData = function (formCreator, file, queueSettings, xhr) { var formData = formCreator(); formData.append("file", file); fluid.uploader.html5Strategy.setPostParams(formData, queueSettings.postParams); xhr.open("POST", queueSettings.uploadURL, true); xhr.send(formData); return formData; }; /************************************ * HTML5 Strategy's Local Behaviour * ************************************/ fluid.defaults("fluid.uploader.html5Strategy.local", { gradeNames: ["fluid.uploader.local"], invokers: { addFiles: { funcName: "fluid.uploader.html5Strategy.local.addFiles", args: ["{that}", "{arguments}.0"] // files }, removeFile: "fluid.identity", // it appears this was never implemented enableBrowseButton: "{that}.browseButtonView.enable", disableBrowseButton: "{that}.browseButtonView.disable" }, components: { browseButtonView: { type: "fluid.uploader.html5Strategy.browseButtonView", container: "{uploader}.container", options: { strings: "{uploader}.options.strings.buttons", queueSettings: "{uploader}.options.queueSettings", selectors: { browseButton: "{uploader}.options.selectors.browseButton" }, events: { onBrowse: "{local}.events.onFileDialog" }, listeners: { onFilesQueued: "{local}.addFiles" } } } } }); fluid.uploader.html5Strategy.local.addFiles = function (that, files) { // Add files to the file queue without exceeding the fileUploadLimit and the fileSizeLimit // NOTE: fileSizeLimit set to bytes for HTML5 Uploader. // TODO: These look like they should be part of a real model. var queueSettings = that.options.queueSettings; var sizeLimit = queueSettings.fileSizeLimit * 1024; var fileLimit = queueSettings.fileUploadLimit; var uploaded = that.queue.getUploadedFiles().length; var queued = that.queue.getReadyFiles().length; var remainingUploadLimit = fileLimit - uploaded - queued; that.events.onFilesSelected.fire(files.length); // Provide feedback to the user if the file size is too large and isn't added to the file queue var numFilesAdded = 0; for (var i = 0; i < files.length; i++) { var file = files[i]; if (fileLimit && remainingUploadLimit === 0) { that.events.onQueueError.fire(file, fluid.uploader.queueErrorConstants.QUEUE_LIMIT_EXCEEDED); } else if (file.size > sizeLimit) { file.filestatus = fluid.uploader.fileStatusConstants.ERROR; that.events.onQueueError.fire(file, fluid.uploader.queueErrorConstants.FILE_EXCEEDS_SIZE_LIMIT); } else if (!fileLimit || remainingUploadLimit > 0) { file.id = "file-" + fluid.allocateGuid(); file.filestatus = fluid.uploader.fileStatusConstants.QUEUED; that.events.afterFileQueued.fire(file); remainingUploadLimit--; numFilesAdded++; } } that.events.afterFileDialog.fire(numFilesAdded); }; /******************** * browseButtonView * ********************/ fluid.uploader.bindEventsToFileInput = function (that, fileInput) { fileInput.on("click", function () { that.events.onBrowse.fire(); }); // FLUID-6056 ( https://issues.fluidproject.org/browse/FLUID-6056 ) // In IE 11 and MS Edge, < input type="file" > creates an element with // two keyboard focusable parts (textfield and button). When the textfield // is focused ( this happens first when tabbing through elements ), pressing // the "Enter" key triggers a form submission. The workaround implemented // here is to translate the input from the "Enter" key into a click event // on the fileInput so that it will open the OS's file dialog. // This hack is only needed for IE 11 and MS Edge. If the it is executed on // Firefox because Firefox treats the file dialog as a popup, which is // caught by the popup blocker. if (fluid.uploader.html5.browser.isMS) { fileInput.on("keydown", function (event) { if (event.keyCode === $.ui.keyCode.ENTER) { event.preventDefault(); fileInput.trigger("click"); } }); } fileInput.on("change", function () { var files = fileInput[0].files; that.renderFreshMultiFileInput(); that.events.onFilesQueued.fire(files); }); fileInput.on("focus", function () { that.browseButton.addClass("focus"); that.events.onFocusFileInput.fire(that, fileInput, true); }); fileInput.on("blur", function () { that.browseButton.removeClass("focus"); that.events.onFocusFileInput.fire(that, fileInput, false); }); }; fluid.uploader.renderMultiFileInput = function (that) { var multiFileInput = $(that.options.multiFileInputMarkup); var fileTypes = that.options.queueSettings.fileTypes; if (fluid.isArrayable(fileTypes)) { fileTypes = fileTypes.join(); multiFileInput.attr("accept", fileTypes); } return multiFileInput; }; fluid.uploader.renderFreshMultiFileInput = function (that) { var previousInput = that.locate("fileInputs").last(); previousInput.hide(); previousInput.prop("tabindex", -1); var newInput = fluid.uploader.renderMultiFileInput(that); newInput.attr("aria-label", that.options.strings.addMore); previousInput.after(newInput); fluid.uploader.bindEventsToFileInput(that, newInput); }; fluid.uploader.setupBrowseButtonView = function (that) { var multiFileInput = fluid.uploader.renderMultiFileInput(that); multiFileInput.attr("aria-label", that.options.strings.browse); that.browseButton.append(multiFileInput); fluid.uploader.bindEventsToFileInput(that, multiFileInput); that.browseButton.prop("tabindex", -1); }; fluid.uploader.isEnabled = function (element) { return !element.prop("disabled"); }; fluid.defaults("fluid.uploader.html5Strategy.browseButtonView", { gradeNames: ["fluid.viewComponent"], strings: { browse: "Browse files", addMore: "Add more" }, multiFileInputMarkup: "<input type='file' multiple='' class='flc-uploader-html5-input' />", queueSettings: {}, members: { browseButton: "{that}.dom.browseButton" }, invokers: { enable: { // TODO: FLUID-4928 "this": "{that}.dom.fileInputs", method: "prop", args: ["disabled", false] }, disable: { "this": "{that}.dom.fileInputs", method: "prop", args: ["disabled", true] }, isEnabled: { funcName: "fluid.uploader.isEnabled", args: "{that}.dom.fileInputs" }, renderFreshMultiFileInput: { funcName: "fluid.uploader.renderFreshMultiFileInput", args: "{that}" } }, selectors: { browseButton: ".flc-uploader-button-browse", fileInputs: ".flc-uploader-html5-input" }, events: { onFocusFileInput: null, onBrowse: null, onFilesQueued: null }, listeners: { onCreate: { funcName: "fluid.uploader.setupBrowseButtonView", args: "{that}" } } });