1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843 |
- /**
- * @license MIT
- */
- (function(window, document, undefined) {'use strict';
- // ie10+
- var ie10plus = window.navigator.msPointerEnabled;
- /**
- * Flow.js is a library providing multiple simultaneous, stable and
- * resumable uploads via the HTML5 File API.
- * @param [opts]
- * @param {number} [opts.chunkSize]
- * @param {bool} [opts.forceChunkSize]
- * @param {number} [opts.simultaneousUploads]
- * @param {bool} [opts.singleFile]
- * @param {string} [opts.fileParameterName]
- * @param {number} [opts.progressCallbacksInterval]
- * @param {number} [opts.speedSmoothingFactor]
- * @param {Object|Function} [opts.query]
- * @param {Object|Function} [opts.headers]
- * @param {bool} [opts.withCredentials]
- * @param {Function} [opts.preprocess]
- * @param {string} [opts.method]
- * @param {string|Function} [opts.testMethod]
- * @param {string|Function} [opts.uploadMethod]
- * @param {bool} [opts.prioritizeFirstAndLastChunk]
- * @param {string|Function} [opts.target]
- * @param {number} [opts.maxChunkRetries]
- * @param {number} [opts.chunkRetryInterval]
- * @param {Array.<number>} [opts.permanentErrors]
- * @param {Array.<number>} [opts.successStatuses]
- * @param {Function} [opts.generateUniqueIdentifier]
- * @constructor
- */
- function Flow(opts) {
- /**
- * Supported by browser?
- * @type {boolean}
- */
- this.support = (
- typeof File !== 'undefined' &&
- typeof Blob !== 'undefined' &&
- typeof FileList !== 'undefined' &&
- (
- !!Blob.prototype.slice || !!Blob.prototype.webkitSlice || !!Blob.prototype.mozSlice ||
- false
- ) // slicing files support
- );
- if (!this.support) {
- return ;
- }
- /**
- * Check if directory upload is supported
- * @type {boolean}
- */
- this.supportDirectory = /WebKit/.test(window.navigator.userAgent);
- /**
- * List of FlowFile objects
- * @type {Array.<FlowFile>}
- */
- this.files = [];
- /**
- * Default options for flow.js
- * @type {Object}
- */
- this.defaults = {
- chunkSize: 1024 * 1024,
- forceChunkSize: false,
- simultaneousUploads: 3,
- singleFile: false,
- fileParameterName: 'file',
- progressCallbacksInterval: 500,
- speedSmoothingFactor: 0.1,
- query: {},
- headers: {},
- withCredentials: false,
- preprocess: null,
- method: 'multipart',
- testMethod: 'GET',
- uploadMethod: 'POST',
- prioritizeFirstAndLastChunk: false,
- target: '/',
- testChunks: true,
- generateUniqueIdentifier: null,
- maxChunkRetries: 0,
- chunkRetryInterval: null,
- permanentErrors: [404, 415, 500, 501],
- successStatuses: [200, 201, 202],
- onDropStopPropagation: false
- };
- /**
- * Current options
- * @type {Object}
- */
- this.opts = {};
- /**
- * List of events:
- * key stands for event name
- * value array list of callbacks
- * @type {}
- */
- this.events = {};
- var $ = this;
- /**
- * On drop event
- * @function
- * @param {MouseEvent} event
- */
- this.onDrop = function (event) {
- if ($.opts.onDropStopPropagation) {
- event.stopPropagation();
- }
- event.preventDefault();
- var dataTransfer = event.dataTransfer;
- if (dataTransfer.items && dataTransfer.items[0] &&
- dataTransfer.items[0].webkitGetAsEntry) {
- $.webkitReadDataTransfer(event);
- } else {
- $.addFiles(dataTransfer.files, event);
- }
- };
- /**
- * Prevent default
- * @function
- * @param {MouseEvent} event
- */
- this.preventEvent = function (event) {
- event.preventDefault();
- };
- /**
- * Current options
- * @type {Object}
- */
- this.opts = Flow.extend({}, this.defaults, opts || {});
- }
- Flow.prototype = {
- /**
- * Set a callback for an event, possible events:
- * fileSuccess(file), fileProgress(file), fileAdded(file, event),
- * fileRetry(file), fileError(file, message), complete(),
- * progress(), error(message, file), pause()
- * @function
- * @param {string} event
- * @param {Function} callback
- */
- on: function (event, callback) {
- event = event.toLowerCase();
- if (!this.events.hasOwnProperty(event)) {
- this.events[event] = [];
- }
- this.events[event].push(callback);
- },
- /**
- * Remove event callback
- * @function
- * @param {string} [event] removes all events if not specified
- * @param {Function} [fn] removes all callbacks of event if not specified
- */
- off: function (event, fn) {
- if (event !== undefined) {
- event = event.toLowerCase();
- if (fn !== undefined) {
- if (this.events.hasOwnProperty(event)) {
- arrayRemove(this.events[event], fn);
- }
- } else {
- delete this.events[event];
- }
- } else {
- this.events = {};
- }
- },
- /**
- * Fire an event
- * @function
- * @param {string} event event name
- * @param {...} args arguments of a callback
- * @return {bool} value is false if at least one of the event handlers which handled this event
- * returned false. Otherwise it returns true.
- */
- fire: function (event, args) {
- // `arguments` is an object, not array, in FF, so:
- args = Array.prototype.slice.call(arguments);
- event = event.toLowerCase();
- var preventDefault = false;
- if (this.events.hasOwnProperty(event)) {
- each(this.events[event], function (callback) {
- preventDefault = callback.apply(this, args.slice(1)) === false || preventDefault;
- }, this);
- }
- if (event != 'catchall') {
- args.unshift('catchAll');
- preventDefault = this.fire.apply(this, args) === false || preventDefault;
- }
- return !preventDefault;
- },
- /**
- * Read webkit dataTransfer object
- * @param event
- */
- webkitReadDataTransfer: function (event) {
- var $ = this;
- var queue = event.dataTransfer.items.length;
- var files = [];
- each(event.dataTransfer.items, function (item) {
- var entry = item.webkitGetAsEntry();
- if (!entry) {
- decrement();
- return ;
- }
- if (entry.isFile) {
- // due to a bug in Chrome's File System API impl - #149735
- fileReadSuccess(item.getAsFile(), entry.fullPath);
- } else {
- entry.createReader().readEntries(readSuccess, readError);
- }
- });
- function readSuccess(entries) {
- queue += entries.length;
- each(entries, function(entry) {
- if (entry.isFile) {
- var fullPath = entry.fullPath;
- entry.file(function (file) {
- fileReadSuccess(file, fullPath);
- }, readError);
- } else if (entry.isDirectory) {
- entry.createReader().readEntries(readSuccess, readError);
- }
- });
- decrement();
- }
- function fileReadSuccess(file, fullPath) {
- // relative path should not start with "/"
- file.relativePath = fullPath.substring(1);
- files.push(file);
- decrement();
- }
- function readError(fileError) {
- throw fileError;
- }
- function decrement() {
- if (--queue == 0) {
- $.addFiles(files, event);
- }
- }
- },
- /**
- * Generate unique identifier for a file
- * @function
- * @param {FlowFile} file
- * @returns {string}
- */
- generateUniqueIdentifier: function (file) {
- var custom = this.opts.generateUniqueIdentifier;
- if (typeof custom === 'function') {
- return custom(file);
- }
- // Some confusion in different versions of Firefox
- var relativePath = file.relativePath || file.webkitRelativePath || file.fileName || file.name;
- return file.size + '-' + relativePath.replace(/[^0-9a-zA-Z_-]/img, '');
- },
- /**
- * Upload next chunk from the queue
- * @function
- * @returns {boolean}
- * @private
- */
- uploadNextChunk: function (preventEvents) {
- // In some cases (such as videos) it's really handy to upload the first
- // and last chunk of a file quickly; this let's the server check the file's
- // metadata and determine if there's even a point in continuing.
- var found = false;
- if (this.opts.prioritizeFirstAndLastChunk) {
- each(this.files, function (file) {
- if (!file.paused && file.chunks.length &&
- file.chunks[0].status() === 'pending' &&
- file.chunks[0].preprocessState === 0) {
- file.chunks[0].send();
- found = true;
- return false;
- }
- if (!file.paused && file.chunks.length > 1 &&
- file.chunks[file.chunks.length - 1].status() === 'pending' &&
- file.chunks[0].preprocessState === 0) {
- file.chunks[file.chunks.length - 1].send();
- found = true;
- return false;
- }
- });
- if (found) {
- return found;
- }
- }
- // Now, simply look for the next, best thing to upload
- each(this.files, function (file) {
- if (!file.paused) {
- each(file.chunks, function (chunk) {
- if (chunk.status() === 'pending' && chunk.preprocessState === 0) {
- chunk.send();
- found = true;
- return false;
- }
- });
- }
- if (found) {
- return false;
- }
- });
- if (found) {
- return true;
- }
- // The are no more outstanding chunks to upload, check is everything is done
- var outstanding = false;
- each(this.files, function (file) {
- if (!file.isComplete()) {
- outstanding = true;
- return false;
- }
- });
- if (!outstanding && !preventEvents) {
- // All chunks have been uploaded, complete
- async(function () {
- this.fire('complete');
- }, this);
- }
- return false;
- },
- /**
- * Assign a browse action to one or more DOM nodes.
- * @function
- * @param {Element|Array.<Element>} domNodes
- * @param {boolean} isDirectory Pass in true to allow directories to
- * @param {boolean} singleFile prevent multi file upload
- * @param {Object} attributes set custom attributes:
- * http://www.w3.org/TR/html-markup/input.file.html#input.file-attributes
- * eg: accept: 'image/*'
- * be selected (Chrome only).
- */
- assignBrowse: function (domNodes, isDirectory, singleFile, attributes) {
- if (typeof domNodes.length === 'undefined') {
- domNodes = [domNodes];
- }
- each(domNodes, function (domNode) {
- var input;
- if (domNode.tagName === 'INPUT' && domNode.type === 'file') {
- input = domNode;
- } else {
- input = document.createElement('input');
- input.setAttribute('type', 'file');
- // display:none - not working in opera 12
- extend(input.style, {
- visibility: 'hidden',
- position: 'absolute'
- });
- // for opera 12 browser, input must be assigned to a document
- domNode.appendChild(input);
- // https://developer.mozilla.org/en/using_files_from_web_applications)
- // event listener is executed two times
- // first one - original mouse click event
- // second - input.click(), input is inside domNode
- domNode.addEventListener('click', function() {
- input.click();
- }, false);
- }
- if (!this.opts.singleFile && !singleFile) {
- input.setAttribute('multiple', 'multiple');
- }
- if (isDirectory) {
- input.setAttribute('webkitdirectory', 'webkitdirectory');
- }
- each(attributes, function (value, key) {
- input.setAttribute(key, value);
- });
- // When new files are added, simply append them to the overall list
- var $ = this;
- input.addEventListener('change', function (e) {
- $.addFiles(e.target.files, e);
- e.target.value = '';
- }, false);
- }, this);
- },
- /**
- * Assign one or more DOM nodes as a drop target.
- * @function
- * @param {Element|Array.<Element>} domNodes
- */
- assignDrop: function (domNodes) {
- if (typeof domNodes.length === 'undefined') {
- domNodes = [domNodes];
- }
- each(domNodes, function (domNode) {
- domNode.addEventListener('dragover', this.preventEvent, false);
- domNode.addEventListener('dragenter', this.preventEvent, false);
- domNode.addEventListener('drop', this.onDrop, false);
- }, this);
- },
- /**
- * Un-assign drop event from DOM nodes
- * @function
- * @param domNodes
- */
- unAssignDrop: function (domNodes) {
- if (typeof domNodes.length === 'undefined') {
- domNodes = [domNodes];
- }
- each(domNodes, function (domNode) {
- domNode.removeEventListener('dragover', this.preventEvent);
- domNode.removeEventListener('dragenter', this.preventEvent);
- domNode.removeEventListener('drop', this.onDrop);
- }, this);
- },
- /**
- * Returns a boolean indicating whether or not the instance is currently
- * uploading anything.
- * @function
- * @returns {boolean}
- */
- isUploading: function () {
- var uploading = false;
- each(this.files, function (file) {
- if (file.isUploading()) {
- uploading = true;
- return false;
- }
- });
- return uploading;
- },
- /**
- * should upload next chunk
- * @function
- * @returns {boolean|number}
- */
- _shouldUploadNext: function () {
- var num = 0;
- var should = true;
- var simultaneousUploads = this.opts.simultaneousUploads;
- each(this.files, function (file) {
- each(file.chunks, function(chunk) {
- if (chunk.status() === 'uploading') {
- num++;
- if (num >= simultaneousUploads) {
- should = false;
- return false;
- }
- }
- });
- });
- // if should is true then return uploading chunks's length
- return should && num;
- },
- /**
- * Start or resume uploading.
- * @function
- */
- upload: function () {
- // Make sure we don't start too many uploads at once
- var ret = this._shouldUploadNext();
- if (ret === false) {
- return;
- }
- // Kick off the queue
- this.fire('uploadStart');
- var started = false;
- for (var num = 1; num <= this.opts.simultaneousUploads - ret; num++) {
- started = this.uploadNextChunk(true) || started;
- }
- if (!started) {
- async(function () {
- this.fire('complete');
- }, this);
- }
- },
- /**
- * Resume uploading.
- * @function
- */
- resume: function () {
- each(this.files, function (file) {
- file.resume();
- });
- },
- /**
- * Pause uploading.
- * @function
- */
- pause: function () {
- each(this.files, function (file) {
- file.pause();
- });
- },
- /**
- * Cancel upload of all FlowFile objects and remove them from the list.
- * @function
- */
- cancel: function () {
- for (var i = this.files.length - 1; i >= 0; i--) {
- this.files[i].cancel();
- }
- },
- /**
- * Returns a number between 0 and 1 indicating the current upload progress
- * of all files.
- * @function
- * @returns {number}
- */
- progress: function () {
- var totalDone = 0;
- var totalSize = 0;
- // Resume all chunks currently being uploaded
- each(this.files, function (file) {
- totalDone += file.progress() * file.size;
- totalSize += file.size;
- });
- return totalSize > 0 ? totalDone / totalSize : 0;
- },
- /**
- * Add a HTML5 File object to the list of files.
- * @function
- * @param {File} file
- * @param {Event} [event] event is optional
- */
- addFile: function (file, event) {
- this.addFiles([file], event);
- },
- /**
- * Add a HTML5 File object to the list of files.
- * @function
- * @param {FileList|Array} fileList
- * @param {Event} [event] event is optional
- */
- addFiles: function (fileList, event) {
- var files = [];
- each(fileList, function (file) {
- // Uploading empty file IE10/IE11 hangs indefinitely
- // see https://connect.microsoft.com/IE/feedback/details/813443/uploading-empty-file-ie10-ie11-hangs-indefinitely
- // Directories have size `0` and name `.`
- // Ignore already added files
- if ((!ie10plus || ie10plus && file.size > 0) && !(file.size % 4096 === 0 && (file.name === '.' || file.fileName === '.')) &&
- !this.getFromUniqueIdentifier(this.generateUniqueIdentifier(file))) {
- var f = new FlowFile(this, file);
- if (this.fire('fileAdded', f, event)) {
- files.push(f);
- }
- }
- }, this);
- if (this.fire('filesAdded', files, event)) {
- each(files, function (file) {
- if (this.opts.singleFile && this.files.length > 0) {
- this.removeFile(this.files[0]);
- }
- this.files.push(file);
- }, this);
- }
- this.fire('filesSubmitted', files, event);
- },
- /**
- * Cancel upload of a specific FlowFile object from the list.
- * @function
- * @param {FlowFile} file
- */
- removeFile: function (file) {
- for (var i = this.files.length - 1; i >= 0; i--) {
- if (this.files[i] === file) {
- this.files.splice(i, 1);
- file.abort();
- }
- }
- },
- /**
- * Look up a FlowFile object by its unique identifier.
- * @function
- * @param {string} uniqueIdentifier
- * @returns {boolean|FlowFile} false if file was not found
- */
- getFromUniqueIdentifier: function (uniqueIdentifier) {
- var ret = false;
- each(this.files, function (file) {
- if (file.uniqueIdentifier === uniqueIdentifier) {
- ret = file;
- }
- });
- return ret;
- },
- /**
- * Returns the total size of all files in bytes.
- * @function
- * @returns {number}
- */
- getSize: function () {
- var totalSize = 0;
- each(this.files, function (file) {
- totalSize += file.size;
- });
- return totalSize;
- },
- /**
- * Returns the total size uploaded of all files in bytes.
- * @function
- * @returns {number}
- */
- sizeUploaded: function () {
- var size = 0;
- each(this.files, function (file) {
- size += file.sizeUploaded();
- });
- return size;
- },
- /**
- * Returns remaining time to upload all files in seconds. Accuracy is based on average speed.
- * If speed is zero, time remaining will be equal to positive infinity `Number.POSITIVE_INFINITY`
- * @function
- * @returns {number}
- */
- timeRemaining: function () {
- var sizeDelta = 0;
- var averageSpeed = 0;
- each(this.files, function (file) {
- if (!file.paused && !file.error) {
- sizeDelta += file.size - file.sizeUploaded();
- averageSpeed += file.averageSpeed;
- }
- });
- if (sizeDelta && !averageSpeed) {
- return Number.POSITIVE_INFINITY;
- }
- if (!sizeDelta && !averageSpeed) {
- return 0;
- }
- return Math.floor(sizeDelta / averageSpeed);
- }
- };
- /**
- * FlowFile class
- * @name FlowFile
- * @param {Flow} flowObj
- * @param {File} file
- * @constructor
- */
- function FlowFile(flowObj, file) {
- /**
- * Reference to parent Flow instance
- * @type {Flow}
- */
- this.flowObj = flowObj;
- /**
- * Reference to file
- * @type {File}
- */
- this.file = file;
- /**
- * File name. Some confusion in different versions of Firefox
- * @type {string}
- */
- this.name = file.fileName || file.name;
- /**
- * File size
- * @type {number}
- */
- this.size = file.size;
- /**
- * Relative file path
- * @type {string}
- */
- this.relativePath = file.relativePath || file.webkitRelativePath || this.name;
- /**
- * File unique identifier
- * @type {string}
- */
- this.uniqueIdentifier = flowObj.generateUniqueIdentifier(file);
- /**
- * List of chunks
- * @type {Array.<FlowChunk>}
- */
- this.chunks = [];
- /**
- * Indicated if file is paused
- * @type {boolean}
- */
- this.paused = false;
- /**
- * Indicated if file has encountered an error
- * @type {boolean}
- */
- this.error = false;
- /**
- * Average upload speed
- * @type {number}
- */
- this.averageSpeed = 0;
- /**
- * Current upload speed
- * @type {number}
- */
- this.currentSpeed = 0;
- /**
- * Date then progress was called last time
- * @type {number}
- * @private
- */
- this._lastProgressCallback = Date.now();
- /**
- * Previously uploaded file size
- * @type {number}
- * @private
- */
- this._prevUploadedSize = 0;
- /**
- * Holds previous progress
- * @type {number}
- * @private
- */
- this._prevProgress = 0;
- this.bootstrap();
- }
- FlowFile.prototype = {
- /**
- * Update speed parameters
- * @link http://stackoverflow.com/questions/2779600/how-to-estimate-download-time-remaining-accurately
- * @function
- */
- measureSpeed: function () {
- var timeSpan = Date.now() - this._lastProgressCallback;
- if (!timeSpan) {
- return ;
- }
- var smoothingFactor = this.flowObj.opts.speedSmoothingFactor;
- var uploaded = this.sizeUploaded();
- // Prevent negative upload speed after file upload resume
- this.currentSpeed = Math.max((uploaded - this._prevUploadedSize) / timeSpan * 1000, 0);
- this.averageSpeed = smoothingFactor * this.currentSpeed + (1 - smoothingFactor) * this.averageSpeed;
- this._prevUploadedSize = uploaded;
- },
- /**
- * For internal usage only.
- * Callback when something happens within the chunk.
- * @function
- * @param {FlowChunk} chunk
- * @param {string} event can be 'progress', 'success', 'error' or 'retry'
- * @param {string} [message]
- */
- chunkEvent: function (chunk, event, message) {
- switch (event) {
- case 'progress':
- if (Date.now() - this._lastProgressCallback <
- this.flowObj.opts.progressCallbacksInterval) {
- break;
- }
- this.measureSpeed();
- this.flowObj.fire('fileProgress', this, chunk);
- this.flowObj.fire('progress');
- this._lastProgressCallback = Date.now();
- break;
- case 'error':
- this.error = true;
- this.abort(true);
- this.flowObj.fire('fileError', this, message, chunk);
- this.flowObj.fire('error', message, this, chunk);
- break;
- case 'success':
- if (this.error) {
- return;
- }
- this.measureSpeed();
- this.flowObj.fire('fileProgress', this, chunk);
- this.flowObj.fire('progress');
- this._lastProgressCallback = Date.now();
- if (this.isComplete()) {
- this.currentSpeed = 0;
- this.averageSpeed = 0;
- this.flowObj.fire('fileSuccess', this, message, chunk);
- }
- break;
- case 'retry':
- this.flowObj.fire('fileRetry', this, chunk);
- break;
- }
- },
- /**
- * Pause file upload
- * @function
- */
- pause: function() {
- this.paused = true;
- this.abort();
- },
- /**
- * Resume file upload
- * @function
- */
- resume: function() {
- this.paused = false;
- this.flowObj.upload();
- },
- /**
- * Abort current upload
- * @function
- */
- abort: function (reset) {
- this.currentSpeed = 0;
- this.averageSpeed = 0;
- var chunks = this.chunks;
- if (reset) {
- this.chunks = [];
- }
- each(chunks, function (c) {
- if (c.status() === 'uploading') {
- c.abort();
- this.flowObj.uploadNextChunk();
- }
- }, this);
- },
- /**
- * Cancel current upload and remove from a list
- * @function
- */
- cancel: function () {
- this.flowObj.removeFile(this);
- },
- /**
- * Retry aborted file upload
- * @function
- */
- retry: function () {
- this.bootstrap();
- this.flowObj.upload();
- },
- /**
- * Clear current chunks and slice file again
- * @function
- */
- bootstrap: function () {
- this.abort(true);
- this.error = false;
- // Rebuild stack of chunks from file
- this._prevProgress = 0;
- var round = this.flowObj.opts.forceChunkSize ? Math.ceil : Math.floor;
- var chunks = Math.max(
- round(this.file.size / this.flowObj.opts.chunkSize), 1
- );
- for (var offset = 0; offset < chunks; offset++) {
- this.chunks.push(
- new FlowChunk(this.flowObj, this, offset)
- );
- }
- },
- /**
- * Get current upload progress status
- * @function
- * @returns {number} from 0 to 1
- */
- progress: function () {
- if (this.error) {
- return 1;
- }
- if (this.chunks.length === 1) {
- this._prevProgress = Math.max(this._prevProgress, this.chunks[0].progress());
- return this._prevProgress;
- }
- // Sum up progress across everything
- var bytesLoaded = 0;
- each(this.chunks, function (c) {
- // get chunk progress relative to entire file
- bytesLoaded += c.progress() * (c.endByte - c.startByte);
- });
- var percent = bytesLoaded / this.size;
- // We don't want to lose percentages when an upload is paused
- this._prevProgress = Math.max(this._prevProgress, percent > 0.9999 ? 1 : percent);
- return this._prevProgress;
- },
- /**
- * Indicates if file is being uploaded at the moment
- * @function
- * @returns {boolean}
- */
- isUploading: function () {
- var uploading = false;
- each(this.chunks, function (chunk) {
- if (chunk.status() === 'uploading') {
- uploading = true;
- return false;
- }
- });
- return uploading;
- },
- /**
- * Indicates if file is has finished uploading and received a response
- * @function
- * @returns {boolean}
- */
- isComplete: function () {
- var outstanding = false;
- each(this.chunks, function (chunk) {
- var status = chunk.status();
- if (status === 'pending' || status === 'uploading' || chunk.preprocessState === 1) {
- outstanding = true;
- return false;
- }
- });
- return !outstanding;
- },
- /**
- * Count total size uploaded
- * @function
- * @returns {number}
- */
- sizeUploaded: function () {
- var size = 0;
- each(this.chunks, function (chunk) {
- size += chunk.sizeUploaded();
- });
- return size;
- },
- /**
- * Returns remaining time to finish upload file in seconds. Accuracy is based on average speed.
- * If speed is zero, time remaining will be equal to positive infinity `Number.POSITIVE_INFINITY`
- * @function
- * @returns {number}
- */
- timeRemaining: function () {
- if (this.paused || this.error) {
- return 0;
- }
- var delta = this.size - this.sizeUploaded();
- if (delta && !this.averageSpeed) {
- return Number.POSITIVE_INFINITY;
- }
- if (!delta && !this.averageSpeed) {
- return 0;
- }
- return Math.floor(delta / this.averageSpeed);
- },
- /**
- * Get file type
- * @function
- * @returns {string}
- */
- getType: function () {
- return this.file.type && this.file.type.split('/')[1];
- },
- /**
- * Get file extension
- * @function
- * @returns {string}
- */
- getExtension: function () {
- return this.name.substr((~-this.name.lastIndexOf(".") >>> 0) + 2).toLowerCase();
- }
- };
- /**
- * Class for storing a single chunk
- * @name FlowChunk
- * @param {Flow} flowObj
- * @param {FlowFile} fileObj
- * @param {number} offset
- * @constructor
- */
- function FlowChunk(flowObj, fileObj, offset) {
- /**
- * Reference to parent flow object
- * @type {Flow}
- */
- this.flowObj = flowObj;
- /**
- * Reference to parent FlowFile object
- * @type {FlowFile}
- */
- this.fileObj = fileObj;
- /**
- * File size
- * @type {number}
- */
- this.fileObjSize = fileObj.size;
- /**
- * File offset
- * @type {number}
- */
- this.offset = offset;
- /**
- * Indicates if chunk existence was checked on the server
- * @type {boolean}
- */
- this.tested = false;
- /**
- * Number of retries performed
- * @type {number}
- */
- this.retries = 0;
- /**
- * Pending retry
- * @type {boolean}
- */
- this.pendingRetry = false;
- /**
- * Preprocess state
- * @type {number} 0 = unprocessed, 1 = processing, 2 = finished
- */
- this.preprocessState = 0;
- /**
- * Bytes transferred from total request size
- * @type {number}
- */
- this.loaded = 0;
- /**
- * Total request size
- * @type {number}
- */
- this.total = 0;
- /**
- * Size of a chunk
- * @type {number}
- */
- var chunkSize = this.flowObj.opts.chunkSize;
- /**
- * Chunk start byte in a file
- * @type {number}
- */
- this.startByte = this.offset * chunkSize;
- /**
- * Chunk end byte in a file
- * @type {number}
- */
- this.endByte = Math.min(this.fileObjSize, (this.offset + 1) * chunkSize);
- /**
- * XMLHttpRequest
- * @type {XMLHttpRequest}
- */
- this.xhr = null;
- if (this.fileObjSize - this.endByte < chunkSize &&
- !this.flowObj.opts.forceChunkSize) {
- // The last chunk will be bigger than the chunk size,
- // but less than 2*chunkSize
- this.endByte = this.fileObjSize;
- }
- var $ = this;
- /**
- * Send chunk event
- * @param event
- * @param {...} args arguments of a callback
- */
- this.event = function (event, args) {
- args = Array.prototype.slice.call(arguments);
- args.unshift($);
- $.fileObj.chunkEvent.apply($.fileObj, args);
- };
- /**
- * Catch progress event
- * @param {ProgressEvent} event
- */
- this.progressHandler = function(event) {
- if (event.lengthComputable) {
- $.loaded = event.loaded ;
- $.total = event.total;
- }
- $.event('progress', event);
- };
- /**
- * Catch test event
- * @param {Event} event
- */
- this.testHandler = function(event) {
- var status = $.status(true);
- if (status === 'error') {
- $.event(status, $.message());
- $.flowObj.uploadNextChunk();
- } else if (status === 'success') {
- $.tested = true;
- $.event(status, $.message());
- $.flowObj.uploadNextChunk();
- } else if (!$.fileObj.paused) {
- // Error might be caused by file pause method
- // Chunks does not exist on the server side
- $.tested = true;
- $.send();
- }
- };
- /**
- * Upload has stopped
- * @param {Event} event
- */
- this.doneHandler = function(event) {
- var status = $.status();
- if (status === 'success' || status === 'error') {
- $.event(status, $.message());
- $.flowObj.uploadNextChunk();
- } else {
- $.event('retry', $.message());
- $.pendingRetry = true;
- $.abort();
- $.retries++;
- var retryInterval = $.flowObj.opts.chunkRetryInterval;
- if (retryInterval !== null) {
- setTimeout(function () {
- $.send();
- }, retryInterval);
- } else {
- $.send();
- }
- }
- };
- }
- FlowChunk.prototype = {
- /**
- * Get params for a request
- * @function
- */
- getParams: function () {
- return {
- flowChunkNumber: this.offset + 1,
- flowChunkSize: this.flowObj.opts.chunkSize,
- flowCurrentChunkSize: this.endByte - this.startByte,
- flowTotalSize: this.fileObjSize,
- flowIdentifier: this.fileObj.uniqueIdentifier,
- flowFilename: this.fileObj.name,
- flowRelativePath: this.fileObj.relativePath,
- flowTotalChunks: this.fileObj.chunks.length
- };
- },
- /**
- * Get target option with query params
- * @function
- * @param params
- * @returns {string}
- */
- getTarget: function(target, params){
- if(target.indexOf('?') < 0) {
- target += '?';
- } else {
- target += '&';
- }
- return target + params.join('&');
- },
- /**
- * Makes a GET request without any data to see if the chunk has already
- * been uploaded in a previous session
- * @function
- */
- test: function () {
- // Set up request and listen for event
- this.xhr = new XMLHttpRequest();
- this.xhr.addEventListener("load", this.testHandler, false);
- this.xhr.addEventListener("error", this.testHandler, false);
- var testMethod = evalOpts(this.flowObj.opts.testMethod, this.fileObj, this);
- var data = this.prepareXhrRequest(testMethod, true);
- this.xhr.send(data);
- },
- /**
- * Finish preprocess state
- * @function
- */
- preprocessFinished: function () {
- this.preprocessState = 2;
- this.send();
- },
- /**
- * Uploads the actual data in a POST call
- * @function
- */
- send: function () {
- var preprocess = this.flowObj.opts.preprocess;
- if (typeof preprocess === 'function') {
- switch (this.preprocessState) {
- case 0:
- this.preprocessState = 1;
- preprocess(this);
- return;
- case 1:
- return;
- }
- }
- if (this.flowObj.opts.testChunks && !this.tested) {
- this.test();
- return;
- }
- this.loaded = 0;
- this.total = 0;
- this.pendingRetry = false;
- var func = (this.fileObj.file.slice ? 'slice' :
- (this.fileObj.file.mozSlice ? 'mozSlice' :
- (this.fileObj.file.webkitSlice ? 'webkitSlice' :
- 'slice')));
- var bytes = this.fileObj.file[func](this.startByte, this.endByte, this.fileObj.file.type);
- // Set up request and listen for event
- this.xhr = new XMLHttpRequest();
- this.xhr.upload.addEventListener('progress', this.progressHandler, false);
- this.xhr.addEventListener("load", this.doneHandler, false);
- this.xhr.addEventListener("error", this.doneHandler, false);
- var uploadMethod = evalOpts(this.flowObj.opts.uploadMethod, this.fileObj, this);
- var data = this.prepareXhrRequest(uploadMethod, false, this.flowObj.opts.method, bytes);
- this.xhr.send(data);
- },
- /**
- * Abort current xhr request
- * @function
- */
- abort: function () {
- // Abort and reset
- var xhr = this.xhr;
- this.xhr = null;
- if (xhr) {
- xhr.abort();
- }
- },
- /**
- * Retrieve current chunk upload status
- * @function
- * @returns {string} 'pending', 'uploading', 'success', 'error'
- */
- status: function (isTest) {
- if (this.pendingRetry || this.preprocessState === 1) {
- // if pending retry then that's effectively the same as actively uploading,
- // there might just be a slight delay before the retry starts
- return 'uploading';
- } else if (!this.xhr) {
- return 'pending';
- } else if (this.xhr.readyState < 4) {
- // Status is really 'OPENED', 'HEADERS_RECEIVED'
- // or 'LOADING' - meaning that stuff is happening
- return 'uploading';
- } else {
- if (this.flowObj.opts.successStatuses.indexOf(this.xhr.status) > -1) {
- // HTTP 200, perfect
- // HTTP 202 Accepted - The request has been accepted for processing, but the processing has not been completed.
- return 'success';
- } else if (this.flowObj.opts.permanentErrors.indexOf(this.xhr.status) > -1 ||
- !isTest && this.retries >= this.flowObj.opts.maxChunkRetries) {
- // HTTP 415/500/501, permanent error
- return 'error';
- } else {
- // this should never happen, but we'll reset and queue a retry
- // a likely case for this would be 503 service unavailable
- this.abort();
- return 'pending';
- }
- }
- },
- /**
- * Get response from xhr request
- * @function
- * @returns {String}
- */
- message: function () {
- return this.xhr ? this.xhr.responseText : '';
- },
- /**
- * Get upload progress
- * @function
- * @returns {number}
- */
- progress: function () {
- if (this.pendingRetry) {
- return 0;
- }
- var s = this.status();
- if (s === 'success' || s === 'error') {
- return 1;
- } else if (s === 'pending') {
- return 0;
- } else {
- return this.total > 0 ? this.loaded / this.total : 0;
- }
- },
- /**
- * Count total size uploaded
- * @function
- * @returns {number}
- */
- sizeUploaded: function () {
- var size = this.endByte - this.startByte;
- // can't return only chunk.loaded value, because it is bigger than chunk size
- if (this.status() !== 'success') {
- size = this.progress() * size;
- }
- return size;
- },
- /**
- * Prepare Xhr request. Set query, headers and data
- * @param {string} method GET or POST
- * @param {bool} isTest is this a test request
- * @param {string} [paramsMethod] octet or form
- * @param {Blob} [blob] to send
- * @returns {FormData|Blob|Null} data to send
- */
- prepareXhrRequest: function(method, isTest, paramsMethod, blob) {
- // Add data from the query options
- var query = evalOpts(this.flowObj.opts.query, this.fileObj, this, isTest);
- query = extend(this.getParams(), query);
- var target = evalOpts(this.flowObj.opts.target, this.fileObj, this, isTest);
- var data = null;
- if (method === 'GET' || paramsMethod === 'octet') {
- // Add data from the query options
- var params = [];
- each(query, function (v, k) {
- params.push([encodeURIComponent(k), encodeURIComponent(v)].join('='));
- });
- target = this.getTarget(target, params);
- data = blob || null;
- } else {
- // Add data from the query options
- data = new FormData();
- each(query, function (v, k) {
- data.append(k, v);
- });
- data.append(this.flowObj.opts.fileParameterName, blob, this.fileObj.file.name);
- }
- this.xhr.open(method, target, true);
- this.xhr.withCredentials = this.flowObj.opts.withCredentials;
- // Add data from header options
- each(evalOpts(this.flowObj.opts.headers, this.fileObj, this, isTest), function (v, k) {
- this.xhr.setRequestHeader(k, v);
- }, this);
- return data;
- }
- };
- /**
- * Remove value from array
- * @param array
- * @param value
- */
- function arrayRemove(array, value) {
- var index = array.indexOf(value);
- if (index > -1) {
- array.splice(index, 1);
- }
- }
- /**
- * If option is a function, evaluate it with given params
- * @param {*} data
- * @param {...} args arguments of a callback
- * @returns {*}
- */
- function evalOpts(data, args) {
- if (typeof data === "function") {
- // `arguments` is an object, not array, in FF, so:
- args = Array.prototype.slice.call(arguments);
- data = data.apply(null, args.slice(1));
- }
- return data;
- }
- Flow.evalOpts = evalOpts;
- /**
- * Execute function asynchronously
- * @param fn
- * @param context
- */
- function async(fn, context) {
- setTimeout(fn.bind(context), 0);
- }
- /**
- * Extends the destination object `dst` by copying all of the properties from
- * the `src` object(s) to `dst`. You can specify multiple `src` objects.
- * @function
- * @param {Object} dst Destination object.
- * @param {...Object} src Source object(s).
- * @returns {Object} Reference to `dst`.
- */
- function extend(dst, src) {
- each(arguments, function(obj) {
- if (obj !== dst) {
- each(obj, function(value, key){
- dst[key] = value;
- });
- }
- });
- return dst;
- }
- Flow.extend = extend;
- /**
- * Iterate each element of an object
- * @function
- * @param {Array|Object} obj object or an array to iterate
- * @param {Function} callback first argument is a value and second is a key.
- * @param {Object=} context Object to become context (`this`) for the iterator function.
- */
- function each(obj, callback, context) {
- if (!obj) {
- return ;
- }
- var key;
- // Is Array?
- if (typeof(obj.length) !== 'undefined') {
- for (key = 0; key < obj.length; key++) {
- if (callback.call(context, obj[key], key) === false) {
- return ;
- }
- }
- } else {
- for (key in obj) {
- if (obj.hasOwnProperty(key) && callback.call(context, obj[key], key) === false) {
- return ;
- }
- }
- }
- }
- Flow.each = each;
- /**
- * FlowFile constructor
- * @type {FlowFile}
- */
- Flow.FlowFile = FlowFile;
- /**
- * FlowFile constructor
- * @type {FlowChunk}
- */
- Flow.FlowChunk = FlowChunk;
- /**
- * Library version
- * @type {string}
- */
- Flow.version = '2.9.0';
- if ( typeof module === "object" && module && typeof module.exports === "object" ) {
- // Expose Flow as module.exports in loaders that implement the Node
- // module pattern (including browserify). Do not create the global, since
- // the user will be storing it themselves locally, and globals are frowned
- // upon in the Node module world.
- module.exports = Flow;
- } else {
- // Otherwise expose Flow to the global object as usual
- window.Flow = Flow;
- // Register as a named AMD module, since Flow can be concatenated with other
- // files that may use define, but not via a proper concatenation script that
- // understands anonymous AMD modules. A named AMD is safest and most robust
- // way to register. Lowercase flow is used because AMD module names are
- // derived from file names, and Flow is normally delivered in a lowercase
- // file name. Do this after creating the global so that if an AMD module wants
- // to call noConflict to hide this version of Flow, it will work.
- if ( typeof define === "function" && define.amd ) {
- define( "flow", [], function () { return Flow; } );
- }
- }
- })(window, document);
- /**
- * @description
- * var app = angular.module('App', ['flow.provider'], function(flowFactoryProvider){
- * flowFactoryProvider.defaults = {target: '/'};
- * });
- * @name flowFactoryProvider
- */
- angular.module('flow.provider', [])
- .provider('flowFactory', function() {
- 'use strict';
- /**
- * Define the default properties for flow.js
- * @name flowFactoryProvider.defaults
- * @type {Object}
- */
- this.defaults = {};
- /**
- * Flow, MaybeFlow or NotFlow
- * @name flowFactoryProvider.factory
- * @type {function}
- * @return {Flow}
- */
- this.factory = function (options) {
- return new Flow(options);
- };
- /**
- * Define the default events
- * @name flowFactoryProvider.events
- * @type {Array}
- * @private
- */
- this.events = [];
- /**
- * Add default events
- * @name flowFactoryProvider.on
- * @function
- * @param {string} event
- * @param {Function} callback
- */
- this.on = function (event, callback) {
- this.events.push([event, callback]);
- };
- this.$get = function() {
- var fn = this.factory;
- var defaults = this.defaults;
- var events = this.events;
- return {
- 'create': function(opts) {
- // combine default options with global options and options
- var flow = fn(angular.extend({}, defaults, opts));
- angular.forEach(events, function (event) {
- flow.on(event[0], event[1]);
- });
- return flow;
- }
- };
- };
- });
- angular.module('flow.init', ['flow.provider'])
- .controller('flowCtrl', ['$scope', '$attrs', '$parse', 'flowFactory',
- function ($scope, $attrs, $parse, flowFactory) {
- var options = angular.extend({}, $scope.$eval($attrs.flowInit));
- // use existing flow object or create a new one
- var flow = $scope.$eval($attrs.flowObject) || flowFactory.create(options);
- flow.on('catchAll', function (eventName) {
- var args = Array.prototype.slice.call(arguments);
- args.shift();
- var event = $scope.$broadcast.apply($scope, ['flow::' + eventName, flow].concat(args));
- if ({
- 'progress':1, 'filesSubmitted':1, 'fileSuccess': 1, 'fileError': 1, 'complete': 1
- }[eventName]) {
- $scope.$apply();
- }
- if (event.defaultPrevented) {
- return false;
- }
- });
- $scope.$flow = flow;
- if ($attrs.hasOwnProperty('flowName')) {
- $parse($attrs.flowName).assign($scope, flow);
- $scope.$on('$destroy', function () {
- $parse($attrs.flowName).assign($scope);
- });
- }
- }])
- .directive('flowInit', [function() {
- return {
- scope: true,
- controller: 'flowCtrl'
- };
- }]);
- angular.module('flow.btn', ['flow.init'])
- .directive('flowBtn', [function() {
- return {
- 'restrict': 'EA',
- 'scope': false,
- 'require': '^flowInit',
- 'link': function(scope, element, attrs) {
- var isDirectory = attrs.hasOwnProperty('flowDirectory');
- var isSingleFile = attrs.hasOwnProperty('flowSingleFile');
- var inputAttrs = attrs.hasOwnProperty('flowAttrs') && scope.$eval(attrs.flowAttrs);
- scope.$flow.assignBrowse(element, isDirectory, isSingleFile, inputAttrs);
- }
- };
- }]);
- angular.module('flow.dragEvents', ['flow.init'])
- /**
- * @name flowPreventDrop
- * Prevent loading files then dropped on element
- */
- .directive('flowPreventDrop', function() {
- return {
- 'scope': false,
- 'link': function(scope, element, attrs) {
- element.bind('drop dragover', function (event) {
- event.preventDefault();
- });
- }
- };
- })
- /**
- * @name flowDragEnter
- * executes `flowDragEnter` and `flowDragLeave` events
- */
- .directive('flowDragEnter', ['$timeout', function($timeout) {
- return {
- 'scope': false,
- 'link': function(scope, element, attrs) {
- var promise;
- var enter = false;
- element.bind('dragover', function (event) {
- if (!isFileDrag(event)) {
- return ;
- }
- if (!enter) {
- scope.$apply(attrs.flowDragEnter);
- enter = true;
- }
- $timeout.cancel(promise);
- event.preventDefault();
- });
- element.bind('dragleave drop', function (event) {
- $timeout.cancel(promise);
- promise = $timeout(function () {
- scope.$eval(attrs.flowDragLeave);
- promise = null;
- enter = false;
- }, 100);
- });
- function isFileDrag(dragEvent) {
- var fileDrag = false;
- var dataTransfer = dragEvent.dataTransfer || dragEvent.originalEvent.dataTransfer;
- angular.forEach(dataTransfer && dataTransfer.types, function(val) {
- if (val === 'Files') {
- fileDrag = true;
- }
- });
- return fileDrag;
- }
- }
- };
- }]);
- angular.module('flow.drop', ['flow.init'])
- .directive('flowDrop', function() {
- return {
- 'scope': false,
- 'require': '^flowInit',
- 'link': function(scope, element, attrs) {
- if (attrs.flowDropEnabled) {
- scope.$watch(attrs.flowDropEnabled, function (value) {
- if (value) {
- assignDrop();
- } else {
- unAssignDrop();
- }
- });
- } else {
- assignDrop();
- }
- function assignDrop() {
- scope.$flow.assignDrop(element);
- }
- function unAssignDrop() {
- scope.$flow.unAssignDrop(element);
- }
- }
- };
- });
- !function (angular) {'use strict';
- var module = angular.module('flow.events', ['flow.init']);
- var events = {
- fileSuccess: ['$file', '$message'],
- fileProgress: ['$file'],
- fileAdded: ['$file', '$event'],
- filesAdded: ['$files', '$event'],
- filesSubmitted: ['$files', '$event'],
- fileRetry: ['$file'],
- fileError: ['$file', '$message'],
- uploadStart: [],
- complete: [],
- progress: [],
- error: ['$message', '$file']
- };
- angular.forEach(events, function (eventArgs, eventName) {
- var name = 'flow' + capitaliseFirstLetter(eventName);
- if (name == 'flowUploadStart') {
- name = 'flowUploadStarted';// event alias
- }
- module.directive(name, [function() {
- return {
- require: '^flowInit',
- controller: ['$scope', '$attrs', function ($scope, $attrs) {
- $scope.$on('flow::' + eventName, function () {
- var funcArgs = Array.prototype.slice.call(arguments);
- var event = funcArgs.shift();// remove angular event
- // remove flow object and ignore event if it is from parent directive
- if ($scope.$flow !== funcArgs.shift()) {
- return ;
- }
- var args = {};
- angular.forEach(eventArgs, function(value, key) {
- args[value] = funcArgs[key];
- });
- if ($scope.$eval($attrs[name], args) === false) {
- event.preventDefault();
- }
- });
- }]
- };
- }]);
- });
- function capitaliseFirstLetter(string) {
- return string.charAt(0).toUpperCase() + string.slice(1);
- }
- }(angular);
- angular.module('flow.img', ['flow.init'])
- .directive('flowImg', [function() {
- return {
- 'scope': false,
- 'require': '^flowInit',
- 'link': function(scope, element, attrs) {
- var file = attrs.flowImg;
- scope.$watch(file, function (file) {
- if (!file) {
- return ;
- }
- var fileReader = new FileReader();
- fileReader.readAsDataURL(file.file);
- fileReader.onload = function (event) {
- scope.$apply(function () {
- attrs.$set('src', event.target.result);
- });
- };
- });
- }
- };
- }]);
- angular.module('flow.transfers', ['flow.init'])
- .directive('flowTransfers', [function() {
- return {
- 'scope': true,
- 'require': '^flowInit',
- 'link': function(scope) {
- scope.transfers = scope.$flow.files;
- }
- };
- }]);
- angular.module('flow', ['flow.provider', 'flow.init', 'flow.events', 'flow.btn',
- 'flow.drop', 'flow.transfers', 'flow.img', 'flow.dragEvents']);
|