├── .gitignore
├── lib
├── img
│ ├── loading.gif
│ └── progressbar.gif
├── css
│ ├── jquery.fileupload-ui.css
│ └── jquery.fileupload.css
├── jquery.iframe-transport.js
├── vendor
│ └── jquery.ui.widget.js
└── jquery.fileupload.js
├── templates
├── dropzone.html
├── queueItem.html
├── buttons.js
├── queueItem.js
├── buttons.html
├── dropzone.js
├── upload.html
└── upload.js
├── .versions
├── package.js
├── LICENSE.md
├── versions.json
├── README.md
├── main.css
└── uploader.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .build*
2 | .idea
3 |
--------------------------------------------------------------------------------
/lib/img/loading.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomitrescak/meteor-tomi-upload-jquery/HEAD/lib/img/loading.gif
--------------------------------------------------------------------------------
/lib/img/progressbar.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomitrescak/meteor-tomi-upload-jquery/HEAD/lib/img/progressbar.gif
--------------------------------------------------------------------------------
/templates/dropzone.html:
--------------------------------------------------------------------------------
1 |
2 |
12 |
--------------------------------------------------------------------------------
/.versions:
--------------------------------------------------------------------------------
1 | babel-compiler@6.5.2
2 | babel-runtime@0.1.6
3 | base64@1.0.6
4 | blaze@2.1.5
5 | blaze-tools@1.0.6
6 | caching-compiler@1.0.2
7 | caching-html-compiler@1.0.4
8 | check@1.1.2
9 | deps@1.0.10
10 | diff-sequence@1.0.3
11 | ecmascript@0.4.1
12 | ecmascript-runtime@0.2.8
13 | ejson@1.0.9
14 | html-tools@1.0.7
15 | htmljs@1.0.7
16 | id-map@1.0.5
17 | jquery@1.11.6
18 | meteor@1.1.12
19 | minifier-js@1.1.9
20 | modules@0.5.1
21 | modules-runtime@0.6.1
22 | mongo-id@1.0.2
23 | observe-sequence@1.0.9
24 | promise@0.6.5
25 | random@1.0.7
26 | reactive-var@1.0.7
27 | spacebars@1.0.9
28 | spacebars-compiler@1.0.9
29 | templating@1.1.7
30 | templating-tools@1.0.2
31 | tomi:upload-jquery@2.4.0
32 | tracker@1.0.11
33 | underscore@1.0.6
34 |
--------------------------------------------------------------------------------
/templates/queueItem.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 |
11 |
12 | {{{ infoLabel }}}
13 |
14 |
15 |
16 |
17 | {{> buttons state=buttonState type=type uploadContext=uploadContext name=this.item.name}}
18 |
19 |
--------------------------------------------------------------------------------
/templates/buttons.js:
--------------------------------------------------------------------------------
1 | Template['buttons'].helpers({
2 | 'class': function(what) {
3 | return Uploader.UI[this.type][what] ;
4 | },
5 | 'idle': function() {
6 | return this.state.idle();
7 | },
8 | 'cancelled': function() {
9 | return this.state.cancelled();
10 | },
11 | 'waiting': function() {
12 | return this.state.waiting();
13 | },
14 | 'removeFromQueue': function() {
15 | return this.state.removeFromQueue();
16 | }
17 | });
18 |
19 | Template['buttons'].events({
20 | 'click .cancel': function (e) {
21 | Uploader.cancelUpload.call(this.uploadContext, e, this.name);
22 | },
23 | 'click .start': function (e) {
24 | Uploader.startUpload.call(this.uploadContext, e, this.name);
25 | },
26 | 'click .remove': function (e) {
27 | Uploader.removeFromQueue.call(this.uploadContext, e, this.name);
28 | },
29 | 'click .done': function(e) {
30 | Uploader.reset.call(this.uploadContext, e);
31 | }
32 | });
--------------------------------------------------------------------------------
/package.js:
--------------------------------------------------------------------------------
1 | Package.describe({
2 | name: 'tomi:upload-jquery',
3 | summary: 'Client template for uploads using "jquery-file-upload" from blueimp',
4 | version: '2.4.0',
5 | git: 'https://github.com/tomitrescak/meteor-tomi-upload-jquery.git'
6 | });
7 |
8 | Package.onUse(function(api) {
9 | api.versionsFrom('1.0');
10 |
11 | api.use(['reactive-var', 'templating'], 'client');
12 |
13 | api.addFiles([
14 | 'lib/vendor/jquery.ui.widget.js',
15 | 'lib/jquery.iframe-transport.js',
16 | 'lib/jquery.fileupload.js',
17 | ], ['client']);
18 |
19 | api.addFiles([
20 | 'main.css',
21 | 'templates/queueItem.html',
22 | 'templates/queueItem.js',
23 | 'templates/upload.html',
24 | 'templates/upload.js',
25 | 'templates/buttons.html',
26 | 'templates/buttons.js',
27 | 'templates/dropzone.html',
28 | 'templates/dropzone.js',
29 | 'uploader.js'], 'client');
30 |
31 | api.export('Uploader', 'client');
32 | });
33 |
34 | //Package.onTest(function(api) {
35 | // api.use('tinytest');
36 | // api.use('tomi:upload-client');
37 | // api.addFiles('upload-client-tests.js');
38 | //});
39 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Tomas Trescak
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/templates/queueItem.js:
--------------------------------------------------------------------------------
1 | Template['queueItem'].helpers({
2 | 'class': function(what) {
3 | return Uploader.UI[this.type][what] ;
4 | },
5 | 'infoLabel': function() {
6 | var progress = this.uploadContext.queue[this.item.name].get();
7 | return progress.running ?
8 | Uploader.formatProgress(this.item.name, progress.progress, progress.bitrate) :
9 | (this.item.name + ' ' + bytesToSize(this.item.size) + '');
10 | },
11 | buttonState: function() {
12 | var that = this;
13 | return {
14 | 'idle': function () {
15 | return !that.uploadContext.queue[that.item.name].get().running ||
16 | that.uploadContext.queue[that.item.name].get().progress === 100;
17 | },
18 | 'cancelled': function () {
19 | return that.uploadContext.queue[that.item.name].get().cancelled;
20 | },
21 | 'waiting': function () {
22 | return that.uploadContext.queue[that.item.name].get().progress !== 100;
23 | },
24 | 'removeFromQueue': function() {
25 | return true;
26 | }
27 | }
28 | },
29 | 'progress': function() {
30 | return 'width:' + this.uploadContext.queue[this.item.name].get().progress + '%';
31 | }
32 | });
--------------------------------------------------------------------------------
/versions.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": [
3 | [
4 | "base64",
5 | "1.0.1"
6 | ],
7 | [
8 | "blaze",
9 | "2.0.3"
10 | ],
11 | [
12 | "deps",
13 | "1.0.5"
14 | ],
15 | [
16 | "ejson",
17 | "1.0.4"
18 | ],
19 | [
20 | "geojson-utils",
21 | "1.0.1"
22 | ],
23 | [
24 | "htmljs",
25 | "1.0.2"
26 | ],
27 | [
28 | "id-map",
29 | "1.0.1"
30 | ],
31 | [
32 | "jquery",
33 | "1.0.1"
34 | ],
35 | [
36 | "json",
37 | "1.0.1"
38 | ],
39 | [
40 | "meteor",
41 | "1.1.3"
42 | ],
43 | [
44 | "minimongo",
45 | "1.0.5"
46 | ],
47 | [
48 | "observe-sequence",
49 | "1.0.3"
50 | ],
51 | [
52 | "ordered-dict",
53 | "1.0.1"
54 | ],
55 | [
56 | "random",
57 | "1.0.1"
58 | ],
59 | [
60 | "reactive-var",
61 | "1.0.3"
62 | ],
63 | [
64 | "templating",
65 | "1.0.9"
66 | ],
67 | [
68 | "tracker",
69 | "1.0.3"
70 | ],
71 | [
72 | "underscore",
73 | "1.0.1"
74 | ]
75 | ],
76 | "pluginDependencies": [],
77 | "toolVersion": "meteor-tool@1.0.35",
78 | "format": "1.0"
79 | }
--------------------------------------------------------------------------------
/lib/css/jquery.fileupload-ui.css:
--------------------------------------------------------------------------------
1 | @charset "UTF-8";
2 | /*
3 | * jQuery File Upload UI Plugin CSS 9.0.0
4 | * https://github.com/blueimp/jQuery-File-Upload
5 | *
6 | * Copyright 2010, Sebastian Tschan
7 | * https://blueimp.net
8 | *
9 | * Licensed under the MIT license:
10 | * http://www.opensource.org/licenses/MIT
11 | */
12 |
13 | .fileupload-buttonbar .btn,
14 | .fileupload-buttonbar .toggle {
15 | margin-bottom: 5px;
16 | }
17 | .progress-animated .progress-bar,
18 | .progress-animated .bar {
19 | background: url("../img/progressbar.gif") !important;
20 | filter: none;
21 | }
22 | .fileupload-process {
23 | float: right;
24 | display: none;
25 | }
26 | .fileupload-processing .fileupload-process,
27 | .files .processing .preview {
28 | display: block;
29 | width: 32px;
30 | height: 32px;
31 | background: url("../img/loading.gif") center no-repeat;
32 | background-size: contain;
33 | }
34 | .files audio,
35 | .files video {
36 | max-width: 300px;
37 | }
38 |
39 | @media (max-width: 767px) {
40 | .fileupload-buttonbar .toggle,
41 | .files .toggle,
42 | .files .btn span {
43 | display: none;
44 | }
45 | .files .name {
46 | width: 80px;
47 | word-wrap: break-word;
48 | }
49 | .files audio,
50 | .files video {
51 | max-width: 80px;
52 | }
53 | .files img,
54 | .files canvas {
55 | max-width: 100%;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/templates/buttons.html:
--------------------------------------------------------------------------------
1 |
2 | {{#if idle}}
3 | {{#if cancelled}}
4 |
5 |
6 | {{ ut9n 'cancelled'}}
7 |
8 | {{else}}
9 | {{#if waiting}}
10 | {{#if removeFromQueue}}
11 |
12 |
13 | {{ ut9n 'remove'}}
14 |
15 | {{else}}
16 | {{#unless autoStart}}
17 |
18 |
19 | {{ ut9n 'upload'}}
20 |
21 | {{/unless}}
22 | {{/if}}
23 | {{else}}
24 |
25 |
26 | {{ ut9n 'done'}}
27 |
28 | {{/if}}
29 | {{/if}}
30 | {{else}}
31 |
32 |
33 | {{ ut9n 'cancel'}}
34 |
35 | {{/if}}
36 |
37 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This is a client interface package for the [Meteor Uploads](https://github.com/tomitrescak/meteor-uploads)
2 |
3 | Use via {{> upload_bootstrap}}
4 |
5 | If you are seeking for the comfort of this package but with uploads to S3, please go to: [https://github.com/tomitrescak/meteor-tomi-uploads-s3](https://github.com/tomitrescak/meteor-tomi-uploads-s3)
6 |
7 | For the full documentation and instructions and **issues** on use go to [https://github.com/tomitrescak/meteor-uploads](https://github.com/tomitrescak/meteor-uploads).
8 |
9 | [](https://atmospherejs.com/tomi/upload-jquery)
10 |
11 | ####Version Info
12 |
13 | * 2.4.0 - Now fully compatible with Meteor 1.3
14 | * 2.0.0 - Big update
15 | * Full support for Semantic UI
16 | * Breaking change in 'getFileInfo', 'getDirectory' and 'finished' callbacks, when fileInfo is now passed in the callback. See the documentation.
17 | * Update the DEMO application to showcase all current possibilities
18 | * Possibility to automatically create directories on server
19 | * Possibility to delete files on server with UploadServer.delete(path)
20 | * 1.1.1 - Bugfixed 'multiple' parameter
21 | * 1.1.0 - Added the drag and drop zone support
22 | * 1.0.7 - Bugfixes
23 | * 1.0.6 - Added possibility to add extra form elements
24 | * 1.0.5 - Bugfixes
25 | * 1.0.4 - Bugfixes
26 | * 1.0.3 - Complete redesign, Allowing upload of multiple files, supporting only Bootstrap
27 | * 1.0.2 - Added reactive variables for the uploaded file info
28 | * 1.0.1 - Added Semantic.ui support
29 | * 1.0.0 - Initial Version with Bootstrap support
30 |
--------------------------------------------------------------------------------
/lib/css/jquery.fileupload.css:
--------------------------------------------------------------------------------
1 | @charset "UTF-8";
2 | /*
3 | * jQuery File Upload Plugin CSS 1.3.0
4 | * https://github.com/blueimp/jQuery-File-Upload
5 | *
6 | * Copyright 2013, Sebastian Tschan
7 | * https://blueimp.net
8 | *
9 | * Licensed under the MIT license:
10 | * http://www.opensource.org/licenses/MIT
11 | */
12 |
13 | .fileinput-button {
14 | position: relative;
15 | overflow: hidden;
16 | }
17 | .fileinput-button input {
18 | position: absolute;
19 | top: 0;
20 | right: 0;
21 | margin: 0;
22 | opacity: 0;
23 | -ms-filter: 'alpha(opacity=0)';
24 | font-size: 200px;
25 | direction: ltr;
26 | cursor: pointer;
27 | }
28 |
29 | /* Fixes for IE < 8 */
30 | @media screen\9 {
31 | .fileinput-button input {
32 | filter: alpha(opacity=0);
33 | font-size: 100%;
34 | height: 100%;
35 | }
36 | }
37 |
38 | /* SEMANTIC UI */
39 |
40 | .progressOuter {
41 | display: table-cell; width: 100%; vertical-align: top
42 | }
43 |
44 | .progressInner {
45 | height: 33px;
46 | }
47 |
48 | .semantic.progressInner {
49 | background: none repeat scroll 0 0 rgba(0, 0, 0, 0.03);
50 | border: 1px solid rgba(39, 41, 43, 0.15);
51 | box-shadow: none;
52 | display: block;
53 | max-width: 100%;
54 | padding: 0.2857em;
55 | position: relative;
56 | }
57 |
58 | .semantic.progressInner .bar {
59 | animation: 0s ease 0s normal none 1 running none !important;
60 | background-color: #5bbd72 !important;
61 | height: 1.75em;
62 | background: none repeat scroll 0 0 #888;
63 | border-radius: 0.2857rem;
64 | display: block;
65 | line-height: 1;
66 | min-width: 2em;
67 | position: relative;
68 | transition: width 0.3s ease 0s, background-color 0.3s ease 0s;
69 | width: 0;
70 | }
71 |
72 | .rightButton {
73 | float: right;
74 | margin-left: 3px!important;
75 | }
76 |
77 |
78 |
--------------------------------------------------------------------------------
/templates/dropzone.js:
--------------------------------------------------------------------------------
1 | // each upload_multiple template instance holds its own local collection of files list
2 | Template['dropzone'].created = function () {
3 | // start automatically on drop
4 | Template.instance().autoStart = true;
5 |
6 | // init the control
7 | Uploader.init(this);
8 | };
9 |
10 | // each upload_multiple template instance holds its own local collection of files list
11 | Template['dropzone'].helpers({
12 | 'infoLabel': function() {
13 | var progress = Template.instance().globalInfo.get();
14 |
15 | // we may have not yet selected a file
16 | if (progress.progress == 0 || progress.progress == 100) {
17 | return Uploader.localisation.dropFiles;
18 | }
19 | return progress.progress + "%";
20 | },
21 | 'submitData': function() {
22 | if (this.formData) {
23 | this.formData['contentType'] = this.contentType;
24 | } else {
25 | this.formData = {contentType: this.contentType};
26 | }
27 | return typeof this.formData == 'string' ? this.formData : JSON.stringify(this.formData);
28 | }
29 | });
30 |
31 | Template['dropzone'].rendered = function () {
32 | // initialise the uploader area
33 | Uploader.render.call(this);
34 |
35 | // allow visual clues for drag and drop area
36 | $(document).bind('dragover', function (e) {
37 | var dropZone = $('.jqDropZone'),
38 | foundDropzone,
39 | timeout = window.dropZoneTimeout;
40 | if (!timeout) {
41 | dropZone.addClass('in');
42 | }
43 | else {
44 | clearTimeout(timeout);
45 | }
46 | var found = false,
47 | node = e.target;
48 | do {
49 | if ($(node).hasClass('jqDropZone')) {
50 | found = true;
51 | foundDropzone = $(node);
52 | break;
53 | }
54 | node = node.parentNode;
55 | } while (node != null);
56 |
57 | dropZone.removeClass('in hover');
58 | if (found) {
59 | foundDropzone.addClass('hover');
60 | }
61 | window.dropZoneTimeout = setTimeout(function () {
62 | window.dropZoneTimeout = null;
63 | dropZone.removeClass('in hover');
64 | }, 100);
65 | });
66 | };
67 |
--------------------------------------------------------------------------------
/templates/upload.html:
--------------------------------------------------------------------------------
1 |
2 | {{> upload type='bootstrap' formData=formData fileTypes=fileTypes multiple=multiple callbacks=callbacks autoStart=autoStart contentType=contentType }}
3 |
4 |
5 |
6 | {{> upload type='semanticUI' formData=formData fileTypes=fileTypes multiple=multiple callbacks=callbacks autoStart=autoStart contentType=contentType }}
7 |
8 |
9 |
10 |
49 |
50 |
--------------------------------------------------------------------------------
/templates/upload.js:
--------------------------------------------------------------------------------
1 | Template.registerHelper('ut9n', function (key){
2 | return Uploader.localisation[key];
3 | });
4 |
5 | // each upload_multiple template instance holds its own local collection of files list
6 | Template['upload'].created = function () {
7 | Uploader.init(this);
8 |
9 | // copy values to context
10 | if (this.data) {
11 | this.autoStart = this.data.autoStart;
12 | }
13 | };
14 |
15 | Template['upload'].helpers({
16 | 'class': function(where) {
17 | return Uploader.UI[this.type][where];
18 | },
19 | 'uploadContext': function() {
20 | return Template.instance();
21 | },
22 | 'submitData': function() {
23 | if (this.formData) {
24 | this.formData['contentType'] = this.contentType;
25 | } else {
26 | this.formData = {contentType: this.contentType};
27 | }
28 | return typeof this.formData == 'string' ? this.formData : JSON.stringify(this.formData);
29 | },
30 | 'infoLabel': function() {
31 | var instance = Template.instance();
32 |
33 | var progress = instance.globalInfo.get();
34 | var info = instance.info.get()
35 | // we may have not yet selected a file
36 | if (!instance.info.get()) {
37 | return "";
38 | }
39 |
40 | return progress.running ?
41 | Uploader.formatProgress(info.name, progress.progress, progress.bitrate) :
42 | (info.name + ' ' + bytesToSize(info.size) + '');
43 | },
44 | 'progress': function() {
45 | return 'width:' + Template.instance().globalInfo.get().progress + '%';
46 | },
47 | buttonState: function() {
48 | var that = Template.instance();
49 | return {
50 | 'idle': function () {
51 | return !that.globalInfo.get().running;
52 | },
53 | 'cancelled': function () {
54 | return that.globalInfo.get().cancelled;
55 | },
56 | 'waiting': function () {
57 | return that.globalInfo.get().progress !== 100;
58 | },
59 | 'removeFromQueue': function() {
60 | return false;
61 | }
62 | }
63 | },
64 | 'queueItems': function() {
65 | return Template.instance().queueView.get();
66 | },
67 | 'showQueue': function() {
68 | return Template.instance().queueView.get().length > 1;
69 | }
70 | });
71 |
72 | Template['upload'].rendered = function () {
73 | Uploader.render.call(this);
74 | };
75 |
--------------------------------------------------------------------------------
/main.css:
--------------------------------------------------------------------------------
1 | /* SEMANTIC UI */
2 |
3 | .progressOuter {
4 | display: table-cell; width: 100%; vertical-align: top
5 | }
6 |
7 | .progressInner {
8 | height: 33px;
9 | }
10 |
11 | .semantic.progressInner {
12 | background: none repeat scroll 0 0 rgba(0, 0, 0, 0.03);
13 | border: 1px solid rgba(39, 41, 43, 0.15);
14 | box-shadow: none;
15 | display: block;
16 | max-width: 100%;
17 | padding: 0.2857em;
18 | position: relative;
19 | height: 36px;
20 | }
21 |
22 | .semantic.progressInner .bar {
23 | animation: 0s ease 0s normal none 1 running none !important;
24 | background-color: #5bbd72 !important;
25 | height: 1.75em;
26 | background: none repeat scroll 0 0 #888;
27 | border-radius: 0.2857rem;
28 | display: block;
29 | line-height: 1;
30 | position: relative;
31 | transition: width 0.3s ease 0s, background-color 0.3s ease 0s;
32 | width: 0;
33 | }
34 |
35 | .rightButton {
36 | float: right;
37 | margin-left: 3px!important;
38 | }
39 |
40 | /* Bootstrap */
41 |
42 | .btn-file {
43 | position: relative;
44 | overflow: hidden;
45 | }
46 | .btn-file input[type=file] {
47 | position: absolute;
48 | top: 0;
49 | right: 0;
50 | min-width: 100%;
51 | min-height: 100%;
52 | font-size: 100px;
53 | text-align: right;
54 | filter: alpha(opacity=0);
55 | opacity: 0;
56 | outline: none;
57 | background: white;
58 | cursor: inherit;
59 | display: block;
60 | }
61 |
62 | .uploadPanel {
63 | display: table;
64 | }
65 |
66 | .uploadPanel li {
67 | display: table-cell;
68 | }
69 |
70 | .uploadPanel .btn {
71 | display: table-cell; width: 1%;
72 | }
73 |
74 | .uploadPanel .upload-control:first-child {
75 | border-radius: 5px 0 0 5px;
76 | }
77 |
78 | .uploadPanel .upload-control:last-child {
79 | border-radius: 0 5px 5px 0;
80 | }
81 |
82 | .uploadPanel .form-control {
83 | border-radius: 0px;
84 | padding: 0px;
85 | position: relative
86 | }
87 |
88 | .uploadPanel .progress{
89 | border-radius: 0px;
90 | }
91 |
92 | .progress-label {
93 | position: absolute;
94 | width: 100%;
95 | top: 6px;
96 | text-align: center
97 | }
98 |
99 | .leftButton,
100 | .rightButton {
101 | margin: 0px;
102 | display: table-cell;
103 | }
104 |
105 | .rightButton {
106 | width: 130px;
107 | }
108 |
--------------------------------------------------------------------------------
/lib/jquery.iframe-transport.js:
--------------------------------------------------------------------------------
1 | /*
2 | * jQuery Iframe Transport Plugin
3 | * https://github.com/blueimp/jQuery-File-Upload
4 | *
5 | * Copyright 2011, Sebastian Tschan
6 | * https://blueimp.net
7 | *
8 | * Licensed under the MIT license:
9 | * http://www.opensource.org/licenses/MIT
10 | */
11 |
12 | /* global define, require, window, document */
13 |
14 | (function (factory) {
15 | 'use strict';
16 | if (typeof define === 'function' && define.amd) {
17 | // Register as an anonymous AMD module:
18 | define(['jquery'], factory);
19 | } else if (typeof exports === 'object') {
20 | // Node/CommonJS:
21 | factory(require('jquery'));
22 | } else {
23 | // Browser globals:
24 | factory(window.jQuery);
25 | }
26 | }(function ($) {
27 | 'use strict';
28 |
29 | // Helper variable to create unique names for the transport iframes:
30 | var counter = 0;
31 |
32 | // The iframe transport accepts four additional options:
33 | // options.fileInput: a jQuery collection of file input fields
34 | // options.paramName: the parameter name for the file form data,
35 | // overrides the name property of the file input field(s),
36 | // can be a string or an array of strings.
37 | // options.formData: an array of objects with name and value properties,
38 | // equivalent to the return data of .serializeArray(), e.g.:
39 | // [{name: 'a', value: 1}, {name: 'b', value: 2}]
40 | // options.initialIframeSrc: the URL of the initial iframe src,
41 | // by default set to "javascript:false;"
42 | $.ajaxTransport('iframe', function (options) {
43 | if (options.async) {
44 | // javascript:false as initial iframe src
45 | // prevents warning popups on HTTPS in IE6:
46 | /*jshint scripturl: true */
47 | var initialIframeSrc = options.initialIframeSrc || 'javascript:false;',
48 | /*jshint scripturl: false */
49 | form,
50 | iframe,
51 | addParamChar;
52 | return {
53 | send: function (_, completeCallback) {
54 | form = $('
",
259 | options: {
260 | disabled: false,
261 |
262 | // callbacks
263 | create: null
264 | },
265 | _createWidget: function( options, element ) {
266 | element = $( element || this.defaultElement || this )[ 0 ];
267 | this.element = $( element );
268 | this.uuid = widget_uuid++;
269 | this.eventNamespace = "." + this.widgetName + this.uuid;
270 |
271 | this.bindings = $();
272 | this.hoverable = $();
273 | this.focusable = $();
274 |
275 | if ( element !== this ) {
276 | $.data( element, this.widgetFullName, this );
277 | this._on( true, this.element, {
278 | remove: function( event ) {
279 | if ( event.target === element ) {
280 | this.destroy();
281 | }
282 | }
283 | });
284 | this.document = $( element.style ?
285 | // element within the document
286 | element.ownerDocument :
287 | // element is window or document
288 | element.document || element );
289 | this.window = $( this.document[0].defaultView || this.document[0].parentWindow );
290 | }
291 |
292 | this.options = $.widget.extend( {},
293 | this.options,
294 | this._getCreateOptions(),
295 | options );
296 |
297 | this._create();
298 | this._trigger( "create", null, this._getCreateEventData() );
299 | this._init();
300 | },
301 | _getCreateOptions: $.noop,
302 | _getCreateEventData: $.noop,
303 | _create: $.noop,
304 | _init: $.noop,
305 |
306 | destroy: function() {
307 | this._destroy();
308 | // we can probably remove the unbind calls in 2.0
309 | // all event bindings should go through this._on()
310 | this.element
311 | .unbind( this.eventNamespace )
312 | .removeData( this.widgetFullName )
313 | // support: jquery <1.6.3
314 | // http://bugs.jquery.com/ticket/9413
315 | .removeData( $.camelCase( this.widgetFullName ) );
316 | this.widget()
317 | .unbind( this.eventNamespace )
318 | .removeAttr( "aria-disabled" )
319 | .removeClass(
320 | this.widgetFullName + "-disabled " +
321 | "ui-state-disabled" );
322 |
323 | // clean up events and states
324 | this.bindings.unbind( this.eventNamespace );
325 | this.hoverable.removeClass( "ui-state-hover" );
326 | this.focusable.removeClass( "ui-state-focus" );
327 | },
328 | _destroy: $.noop,
329 |
330 | widget: function() {
331 | return this.element;
332 | },
333 |
334 | option: function( key, value ) {
335 | var options = key,
336 | parts,
337 | curOption,
338 | i;
339 |
340 | if ( arguments.length === 0 ) {
341 | // don't return a reference to the internal hash
342 | return $.widget.extend( {}, this.options );
343 | }
344 |
345 | if ( typeof key === "string" ) {
346 | // handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
347 | options = {};
348 | parts = key.split( "." );
349 | key = parts.shift();
350 | if ( parts.length ) {
351 | curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] );
352 | for ( i = 0; i < parts.length - 1; i++ ) {
353 | curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {};
354 | curOption = curOption[ parts[ i ] ];
355 | }
356 | key = parts.pop();
357 | if ( arguments.length === 1 ) {
358 | return curOption[ key ] === undefined ? null : curOption[ key ];
359 | }
360 | curOption[ key ] = value;
361 | } else {
362 | if ( arguments.length === 1 ) {
363 | return this.options[ key ] === undefined ? null : this.options[ key ];
364 | }
365 | options[ key ] = value;
366 | }
367 | }
368 |
369 | this._setOptions( options );
370 |
371 | return this;
372 | },
373 | _setOptions: function( options ) {
374 | var key;
375 |
376 | for ( key in options ) {
377 | this._setOption( key, options[ key ] );
378 | }
379 |
380 | return this;
381 | },
382 | _setOption: function( key, value ) {
383 | this.options[ key ] = value;
384 |
385 | if ( key === "disabled" ) {
386 | this.widget()
387 | .toggleClass( this.widgetFullName + "-disabled", !!value );
388 |
389 | // If the widget is becoming disabled, then nothing is interactive
390 | if ( value ) {
391 | this.hoverable.removeClass( "ui-state-hover" );
392 | this.focusable.removeClass( "ui-state-focus" );
393 | }
394 | }
395 |
396 | return this;
397 | },
398 |
399 | enable: function() {
400 | return this._setOptions({ disabled: false });
401 | },
402 | disable: function() {
403 | return this._setOptions({ disabled: true });
404 | },
405 |
406 | _on: function( suppressDisabledCheck, element, handlers ) {
407 | var delegateElement,
408 | instance = this;
409 |
410 | // no suppressDisabledCheck flag, shuffle arguments
411 | if ( typeof suppressDisabledCheck !== "boolean" ) {
412 | handlers = element;
413 | element = suppressDisabledCheck;
414 | suppressDisabledCheck = false;
415 | }
416 |
417 | // no element argument, shuffle and use this.element
418 | if ( !handlers ) {
419 | handlers = element;
420 | element = this.element;
421 | delegateElement = this.widget();
422 | } else {
423 | element = delegateElement = $( element );
424 | this.bindings = this.bindings.add( element );
425 | }
426 |
427 | $.each( handlers, function( event, handler ) {
428 | function handlerProxy() {
429 | // allow widgets to customize the disabled handling
430 | // - disabled as an array instead of boolean
431 | // - disabled class as method for disabling individual parts
432 | if ( !suppressDisabledCheck &&
433 | ( instance.options.disabled === true ||
434 | $( this ).hasClass( "ui-state-disabled" ) ) ) {
435 | return;
436 | }
437 | return ( typeof handler === "string" ? instance[ handler ] : handler )
438 | .apply( instance, arguments );
439 | }
440 |
441 | // copy the guid so direct unbinding works
442 | if ( typeof handler !== "string" ) {
443 | handlerProxy.guid = handler.guid =
444 | handler.guid || handlerProxy.guid || $.guid++;
445 | }
446 |
447 | var match = event.match( /^([\w:-]*)\s*(.*)$/ ),
448 | eventName = match[1] + instance.eventNamespace,
449 | selector = match[2];
450 | if ( selector ) {
451 | delegateElement.delegate( selector, eventName, handlerProxy );
452 | } else {
453 | element.bind( eventName, handlerProxy );
454 | }
455 | });
456 | },
457 |
458 | _off: function( element, eventName ) {
459 | eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) +
460 | this.eventNamespace;
461 | element.unbind( eventName ).undelegate( eventName );
462 |
463 | // Clear the stack to avoid memory leaks (#10056)
464 | this.bindings = $( this.bindings.not( element ).get() );
465 | this.focusable = $( this.focusable.not( element ).get() );
466 | this.hoverable = $( this.hoverable.not( element ).get() );
467 | },
468 |
469 | _delay: function( handler, delay ) {
470 | function handlerProxy() {
471 | return ( typeof handler === "string" ? instance[ handler ] : handler )
472 | .apply( instance, arguments );
473 | }
474 | var instance = this;
475 | return setTimeout( handlerProxy, delay || 0 );
476 | },
477 |
478 | _hoverable: function( element ) {
479 | this.hoverable = this.hoverable.add( element );
480 | this._on( element, {
481 | mouseenter: function( event ) {
482 | $( event.currentTarget ).addClass( "ui-state-hover" );
483 | },
484 | mouseleave: function( event ) {
485 | $( event.currentTarget ).removeClass( "ui-state-hover" );
486 | }
487 | });
488 | },
489 |
490 | _focusable: function( element ) {
491 | this.focusable = this.focusable.add( element );
492 | this._on( element, {
493 | focusin: function( event ) {
494 | $( event.currentTarget ).addClass( "ui-state-focus" );
495 | },
496 | focusout: function( event ) {
497 | $( event.currentTarget ).removeClass( "ui-state-focus" );
498 | }
499 | });
500 | },
501 |
502 | _trigger: function( type, event, data ) {
503 | var prop, orig,
504 | callback = this.options[ type ];
505 |
506 | data = data || {};
507 | event = $.Event( event );
508 | event.type = ( type === this.widgetEventPrefix ?
509 | type :
510 | this.widgetEventPrefix + type ).toLowerCase();
511 | // the original event may come from any element
512 | // so we need to reset the target on the new event
513 | event.target = this.element[ 0 ];
514 |
515 | // copy original event properties over to the new event
516 | orig = event.originalEvent;
517 | if ( orig ) {
518 | for ( prop in orig ) {
519 | if ( !( prop in event ) ) {
520 | event[ prop ] = orig[ prop ];
521 | }
522 | }
523 | }
524 |
525 | this.element.trigger( event, data );
526 | return !( $.isFunction( callback ) &&
527 | callback.apply( this.element[0], [ event ].concat( data ) ) === false ||
528 | event.isDefaultPrevented() );
529 | }
530 | };
531 |
532 | $.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) {
533 | $.Widget.prototype[ "_" + method ] = function( element, options, callback ) {
534 | if ( typeof options === "string" ) {
535 | options = { effect: options };
536 | }
537 | var hasOptions,
538 | effectName = !options ?
539 | method :
540 | options === true || typeof options === "number" ?
541 | defaultEffect :
542 | options.effect || defaultEffect;
543 | options = options || {};
544 | if ( typeof options === "number" ) {
545 | options = { duration: options };
546 | }
547 | hasOptions = !$.isEmptyObject( options );
548 | options.complete = callback;
549 | if ( options.delay ) {
550 | element.delay( options.delay );
551 | }
552 | if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) {
553 | element[ method ]( options );
554 | } else if ( effectName !== method && element[ effectName ] ) {
555 | element[ effectName ]( options.duration, options.easing, callback );
556 | } else {
557 | element.queue(function( next ) {
558 | $( this )[ method ]();
559 | if ( callback ) {
560 | callback.call( element[ 0 ] );
561 | }
562 | next();
563 | });
564 | }
565 | };
566 | });
567 |
568 | var widget = $.widget;
569 |
570 |
571 |
572 | }));
573 |
--------------------------------------------------------------------------------
/lib/jquery.fileupload.js:
--------------------------------------------------------------------------------
1 | /*
2 | * jQuery File Upload Plugin
3 | * https://github.com/blueimp/jQuery-File-Upload
4 | *
5 | * Copyright 2010, Sebastian Tschan
6 | * https://blueimp.net
7 | *
8 | * Licensed under the MIT license:
9 | * http://www.opensource.org/licenses/MIT
10 | */
11 |
12 | /* jshint nomen:false */
13 | /* global define, require, window, document, location, Blob, FormData */
14 |
15 | (function (factory) {
16 | 'use strict';
17 | if (typeof define === 'function' && define.amd) {
18 | // Register as an anonymous AMD module:
19 | define([
20 | 'jquery',
21 | 'jquery.ui.widget'
22 | ], factory);
23 | } else if (typeof exports === 'object') {
24 | // Node/CommonJS:
25 | factory(
26 | require('jquery'),
27 | require('./vendor/jquery.ui.widget')
28 | );
29 | } else {
30 | // Browser globals:
31 | factory(window.jQuery);
32 | }
33 | }(function ($) {
34 | 'use strict';
35 |
36 | // Detect file input support, based on
37 | // http://viljamis.com/blog/2012/file-upload-support-on-mobile/
38 | $.support.fileInput = !(new RegExp(
39 | // Handle devices which give false positives for the feature detection:
40 | '(Android (1\\.[0156]|2\\.[01]))' +
41 | '|(Windows Phone (OS 7|8\\.0))|(XBLWP)|(ZuneWP)|(WPDesktop)' +
42 | '|(w(eb)?OSBrowser)|(webOS)' +
43 | '|(Kindle/(1\\.0|2\\.[05]|3\\.0))'
44 | ).test(window.navigator.userAgent) ||
45 | // Feature detection for all other devices:
46 | $('
').prop('disabled'));
47 |
48 | // The FileReader API is not actually used, but works as feature detection,
49 | // as some Safari versions (5?) support XHR file uploads via the FormData API,
50 | // but not non-multipart XHR file uploads.
51 | // window.XMLHttpRequestUpload is not available on IE10, so we check for
52 | // window.ProgressEvent instead to detect XHR2 file upload capability:
53 | $.support.xhrFileUpload = !!(window.ProgressEvent && window.FileReader);
54 | $.support.xhrFormDataFileUpload = !!window.FormData;
55 |
56 | // Detect support for Blob slicing (required for chunked uploads):
57 | $.support.blobSlice = window.Blob && (Blob.prototype.slice ||
58 | Blob.prototype.webkitSlice || Blob.prototype.mozSlice);
59 |
60 | // Helper function to create drag handlers for dragover/dragenter/dragleave:
61 | function getDragHandler(type) {
62 | var isDragOver = type === 'dragover';
63 | return function (e) {
64 | e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer;
65 | var dataTransfer = e.dataTransfer;
66 | if (dataTransfer && $.inArray('Files', dataTransfer.types) !== -1 &&
67 | this._trigger(
68 | type,
69 | $.Event(type, {delegatedEvent: e})
70 | ) !== false) {
71 | e.preventDefault();
72 | if (isDragOver) {
73 | dataTransfer.dropEffect = 'copy';
74 | }
75 | }
76 | };
77 | }
78 |
79 | // The fileupload widget listens for change events on file input fields defined
80 | // via fileInput setting and paste or drop events of the given dropZone.
81 | // In addition to the default jQuery Widget methods, the fileupload widget
82 | // exposes the "add" and "send" methods, to add or directly send files using
83 | // the fileupload API.
84 | // By default, files added via file input selection, paste, drag & drop or
85 | // "add" method are uploaded immediately, but it is possible to override
86 | // the "add" callback option to queue file uploads.
87 | $.widget('blueimp.fileupload', {
88 |
89 | options: {
90 | // The drop target element(s), by the default the complete document.
91 | // Set to null to disable drag & drop support:
92 | dropZone: $(document),
93 | // The paste target element(s), by the default undefined.
94 | // Set to a DOM node or jQuery object to enable file pasting:
95 | pasteZone: undefined,
96 | // The file input field(s), that are listened to for change events.
97 | // If undefined, it is set to the file input fields inside
98 | // of the widget element on plugin initialization.
99 | // Set to null to disable the change listener.
100 | fileInput: undefined,
101 | // By default, the file input field is replaced with a clone after
102 | // each input field change event. This is required for iframe transport
103 | // queues and allows change events to be fired for the same file
104 | // selection, but can be disabled by setting the following option to false:
105 | replaceFileInput: true,
106 | // The parameter name for the file form data (the request argument name).
107 | // If undefined or empty, the name property of the file input field is
108 | // used, or "files[]" if the file input name property is also empty,
109 | // can be a string or an array of strings:
110 | paramName: undefined,
111 | // By default, each file of a selection is uploaded using an individual
112 | // request for XHR type uploads. Set to false to upload file
113 | // selections in one request each:
114 | singleFileUploads: true,
115 | // To limit the number of files uploaded with one XHR request,
116 | // set the following option to an integer greater than 0:
117 | limitMultiFileUploads: undefined,
118 | // The following option limits the number of files uploaded with one
119 | // XHR request to keep the request size under or equal to the defined
120 | // limit in bytes:
121 | limitMultiFileUploadSize: undefined,
122 | // Multipart file uploads add a number of bytes to each uploaded file,
123 | // therefore the following option adds an overhead for each file used
124 | // in the limitMultiFileUploadSize configuration:
125 | limitMultiFileUploadSizeOverhead: 512,
126 | // Set the following option to true to issue all file upload requests
127 | // in a sequential order:
128 | sequentialUploads: false,
129 | // To limit the number of concurrent uploads,
130 | // set the following option to an integer greater than 0:
131 | limitConcurrentUploads: undefined,
132 | // Set the following option to true to force iframe transport uploads:
133 | forceIframeTransport: false,
134 | // Set the following option to the location of a redirect url on the
135 | // origin server, for cross-domain iframe transport uploads:
136 | redirect: undefined,
137 | // The parameter name for the redirect url, sent as part of the form
138 | // data and set to 'redirect' if this option is empty:
139 | redirectParamName: undefined,
140 | // Set the following option to the location of a postMessage window,
141 | // to enable postMessage transport uploads:
142 | postMessage: undefined,
143 | // By default, XHR file uploads are sent as multipart/form-data.
144 | // The iframe transport is always using multipart/form-data.
145 | // Set to false to enable non-multipart XHR uploads:
146 | multipart: true,
147 | // To upload large files in smaller chunks, set the following option
148 | // to a preferred maximum chunk size. If set to 0, null or undefined,
149 | // or the browser does not support the required Blob API, files will
150 | // be uploaded as a whole.
151 | maxChunkSize: undefined,
152 | // When a non-multipart upload or a chunked multipart upload has been
153 | // aborted, this option can be used to resume the upload by setting
154 | // it to the size of the already uploaded bytes. This option is most
155 | // useful when modifying the options object inside of the "add" or
156 | // "send" callbacks, as the options are cloned for each file upload.
157 | uploadedBytes: undefined,
158 | // By default, failed (abort or error) file uploads are removed from the
159 | // global progress calculation. Set the following option to false to
160 | // prevent recalculating the global progress data:
161 | recalculateProgress: true,
162 | // Interval in milliseconds to calculate and trigger progress events:
163 | progressInterval: 100,
164 | // Interval in milliseconds to calculate progress bitrate:
165 | bitrateInterval: 500,
166 | // By default, uploads are started automatically when adding files:
167 | autoUpload: true,
168 |
169 | // Error and info messages:
170 | messages: {
171 | uploadedBytes: 'Uploaded bytes exceed file size'
172 | },
173 |
174 | // Translation function, gets the message key to be translated
175 | // and an object with context specific data as arguments:
176 | i18n: function (message, context) {
177 | message = this.messages[message] || message.toString();
178 | if (context) {
179 | $.each(context, function (key, value) {
180 | message = message.replace('{' + key + '}', value);
181 | });
182 | }
183 | return message;
184 | },
185 |
186 | // Additional form data to be sent along with the file uploads can be set
187 | // using this option, which accepts an array of objects with name and
188 | // value properties, a function returning such an array, a FormData
189 | // object (for XHR file uploads), or a simple object.
190 | // The form of the first fileInput is given as parameter to the function:
191 | formData: function (form) {
192 | return form.serializeArray();
193 | },
194 |
195 | // The add callback is invoked as soon as files are added to the fileupload
196 | // widget (via file input selection, drag & drop, paste or add API call).
197 | // If the singleFileUploads option is enabled, this callback will be
198 | // called once for each file in the selection for XHR file uploads, else
199 | // once for each file selection.
200 | //
201 | // The upload starts when the submit method is invoked on the data parameter.
202 | // The data object contains a files property holding the added files
203 | // and allows you to override plugin options as well as define ajax settings.
204 | //
205 | // Listeners for this callback can also be bound the following way:
206 | // .bind('fileuploadadd', func);
207 | //
208 | // data.submit() returns a Promise object and allows to attach additional
209 | // handlers using jQuery's Deferred callbacks:
210 | // data.submit().done(func).fail(func).always(func);
211 | add: function (e, data) {
212 | if (e.isDefaultPrevented()) {
213 | return false;
214 | }
215 | if (data.autoUpload || (data.autoUpload !== false &&
216 | $(this).fileupload('option', 'autoUpload'))) {
217 | data.process().done(function () {
218 | data.submit();
219 | });
220 | }
221 | },
222 |
223 | // Other callbacks:
224 |
225 | // Callback for the submit event of each file upload:
226 | // submit: function (e, data) {}, // .bind('fileuploadsubmit', func);
227 |
228 | // Callback for the start of each file upload request:
229 | // send: function (e, data) {}, // .bind('fileuploadsend', func);
230 |
231 | // Callback for successful uploads:
232 | // done: function (e, data) {}, // .bind('fileuploaddone', func);
233 |
234 | // Callback for failed (abort or error) uploads:
235 | // fail: function (e, data) {}, // .bind('fileuploadfail', func);
236 |
237 | // Callback for completed (success, abort or error) requests:
238 | // always: function (e, data) {}, // .bind('fileuploadalways', func);
239 |
240 | // Callback for upload progress events:
241 | // progress: function (e, data) {}, // .bind('fileuploadprogress', func);
242 |
243 | // Callback for global upload progress events:
244 | // progressall: function (e, data) {}, // .bind('fileuploadprogressall', func);
245 |
246 | // Callback for uploads start, equivalent to the global ajaxStart event:
247 | // start: function (e) {}, // .bind('fileuploadstart', func);
248 |
249 | // Callback for uploads stop, equivalent to the global ajaxStop event:
250 | // stop: function (e) {}, // .bind('fileuploadstop', func);
251 |
252 | // Callback for change events of the fileInput(s):
253 | // change: function (e, data) {}, // .bind('fileuploadchange', func);
254 |
255 | // Callback for paste events to the pasteZone(s):
256 | // paste: function (e, data) {}, // .bind('fileuploadpaste', func);
257 |
258 | // Callback for drop events of the dropZone(s):
259 | // drop: function (e, data) {}, // .bind('fileuploaddrop', func);
260 |
261 | // Callback for dragover events of the dropZone(s):
262 | // dragover: function (e) {}, // .bind('fileuploaddragover', func);
263 |
264 | // Callback for the start of each chunk upload request:
265 | // chunksend: function (e, data) {}, // .bind('fileuploadchunksend', func);
266 |
267 | // Callback for successful chunk uploads:
268 | // chunkdone: function (e, data) {}, // .bind('fileuploadchunkdone', func);
269 |
270 | // Callback for failed (abort or error) chunk uploads:
271 | // chunkfail: function (e, data) {}, // .bind('fileuploadchunkfail', func);
272 |
273 | // Callback for completed (success, abort or error) chunk upload requests:
274 | // chunkalways: function (e, data) {}, // .bind('fileuploadchunkalways', func);
275 |
276 | // The plugin options are used as settings object for the ajax calls.
277 | // The following are jQuery ajax settings required for the file uploads:
278 | processData: false,
279 | contentType: false,
280 | cache: false,
281 | timeout: 0
282 | },
283 |
284 | // A list of options that require reinitializing event listeners and/or
285 | // special initialization code:
286 | _specialOptions: [
287 | 'fileInput',
288 | 'dropZone',
289 | 'pasteZone',
290 | 'multipart',
291 | 'forceIframeTransport'
292 | ],
293 |
294 | _blobSlice: $.support.blobSlice && function () {
295 | var slice = this.slice || this.webkitSlice || this.mozSlice;
296 | return slice.apply(this, arguments);
297 | },
298 |
299 | _BitrateTimer: function () {
300 | this.timestamp = ((Date.now) ? Date.now() : (new Date()).getTime());
301 | this.loaded = 0;
302 | this.bitrate = 0;
303 | this.getBitrate = function (now, loaded, interval) {
304 | var timeDiff = now - this.timestamp;
305 | if (!this.bitrate || !interval || timeDiff > interval) {
306 | this.bitrate = (loaded - this.loaded) * (1000 / timeDiff) * 8;
307 | this.loaded = loaded;
308 | this.timestamp = now;
309 | }
310 | return this.bitrate;
311 | };
312 | },
313 |
314 | _isXHRUpload: function (options) {
315 | return !options.forceIframeTransport &&
316 | ((!options.multipart && $.support.xhrFileUpload) ||
317 | $.support.xhrFormDataFileUpload);
318 | },
319 |
320 | _getFormData: function (options) {
321 | var formData;
322 | if ($.type(options.formData) === 'function') {
323 | return options.formData(options.form);
324 | }
325 | if ($.isArray(options.formData)) {
326 | return options.formData;
327 | }
328 | if ($.type(options.formData) === 'object') {
329 | formData = [];
330 | $.each(options.formData, function (name, value) {
331 | formData.push({name: name, value: value});
332 | });
333 | return formData;
334 | }
335 | return [];
336 | },
337 |
338 | _getTotal: function (files) {
339 | var total = 0;
340 | $.each(files, function (index, file) {
341 | total += file.size || 1;
342 | });
343 | return total;
344 | },
345 |
346 | _initProgressObject: function (obj) {
347 | var progress = {
348 | loaded: 0,
349 | total: 0,
350 | bitrate: 0
351 | };
352 | if (obj._progress) {
353 | $.extend(obj._progress, progress);
354 | } else {
355 | obj._progress = progress;
356 | }
357 | },
358 |
359 | _initResponseObject: function (obj) {
360 | var prop;
361 | if (obj._response) {
362 | for (prop in obj._response) {
363 | if (obj._response.hasOwnProperty(prop)) {
364 | delete obj._response[prop];
365 | }
366 | }
367 | } else {
368 | obj._response = {};
369 | }
370 | },
371 |
372 | _onProgress: function (e, data) {
373 | if (e.lengthComputable) {
374 | var now = ((Date.now) ? Date.now() : (new Date()).getTime()),
375 | loaded;
376 | if (data._time && data.progressInterval &&
377 | (now - data._time < data.progressInterval) &&
378 | e.loaded !== e.total) {
379 | return;
380 | }
381 | data._time = now;
382 | loaded = Math.floor(
383 | e.loaded / e.total * (data.chunkSize || data._progress.total)
384 | ) + (data.uploadedBytes || 0);
385 | // Add the difference from the previously loaded state
386 | // to the global loaded counter:
387 | this._progress.loaded += (loaded - data._progress.loaded);
388 | this._progress.bitrate = this._bitrateTimer.getBitrate(
389 | now,
390 | this._progress.loaded,
391 | data.bitrateInterval
392 | );
393 | data._progress.loaded = data.loaded = loaded;
394 | data._progress.bitrate = data.bitrate = data._bitrateTimer.getBitrate(
395 | now,
396 | loaded,
397 | data.bitrateInterval
398 | );
399 | // Trigger a custom progress event with a total data property set
400 | // to the file size(s) of the current upload and a loaded data
401 | // property calculated accordingly:
402 | this._trigger(
403 | 'progress',
404 | $.Event('progress', {delegatedEvent: e}),
405 | data
406 | );
407 | // Trigger a global progress event for all current file uploads,
408 | // including ajax calls queued for sequential file uploads:
409 | this._trigger(
410 | 'progressall',
411 | $.Event('progressall', {delegatedEvent: e}),
412 | this._progress
413 | );
414 | }
415 | },
416 |
417 | _initProgressListener: function (options) {
418 | var that = this,
419 | xhr = options.xhr ? options.xhr() : $.ajaxSettings.xhr();
420 | // Accesss to the native XHR object is required to add event listeners
421 | // for the upload progress event:
422 | if (xhr.upload) {
423 | $(xhr.upload).bind('progress', function (e) {
424 | var oe = e.originalEvent;
425 | // Make sure the progress event properties get copied over:
426 | e.lengthComputable = oe.lengthComputable;
427 | e.loaded = oe.loaded;
428 | e.total = oe.total;
429 | that._onProgress(e, options);
430 | });
431 | options.xhr = function () {
432 | return xhr;
433 | };
434 | }
435 | },
436 |
437 | _isInstanceOf: function (type, obj) {
438 | // Cross-frame instanceof check
439 | return Object.prototype.toString.call(obj) === '[object ' + type + ']';
440 | },
441 |
442 | _initXHRData: function (options) {
443 | var that = this,
444 | formData,
445 | file = options.files[0],
446 | // Ignore non-multipart setting if not supported:
447 | multipart = options.multipart || !$.support.xhrFileUpload,
448 | paramName = $.type(options.paramName) === 'array' ?
449 | options.paramName[0] : options.paramName;
450 | options.headers = $.extend({}, options.headers);
451 | if (options.contentRange) {
452 | options.headers['Content-Range'] = options.contentRange;
453 | }
454 | if (!multipart || options.blob || !this._isInstanceOf('File', file)) {
455 | options.headers['Content-Disposition'] = 'attachment; filename="' +
456 | encodeURI(file.name) + '"';
457 | }
458 | if (!multipart) {
459 | options.contentType = file.type || 'application/octet-stream';
460 | options.data = options.blob || file;
461 | } else if ($.support.xhrFormDataFileUpload) {
462 | if (options.postMessage) {
463 | // window.postMessage does not allow sending FormData
464 | // objects, so we just add the File/Blob objects to
465 | // the formData array and let the postMessage window
466 | // create the FormData object out of this array:
467 | formData = this._getFormData(options);
468 | if (options.blob) {
469 | formData.push({
470 | name: paramName,
471 | value: options.blob
472 | });
473 | } else {
474 | $.each(options.files, function (index, file) {
475 | formData.push({
476 | name: ($.type(options.paramName) === 'array' &&
477 | options.paramName[index]) || paramName,
478 | value: file
479 | });
480 | });
481 | }
482 | } else {
483 | if (that._isInstanceOf('FormData', options.formData)) {
484 | formData = options.formData;
485 | } else {
486 | formData = new FormData();
487 | $.each(this._getFormData(options), function (index, field) {
488 | formData.append(field.name, field.value);
489 | });
490 | }
491 | if (options.blob) {
492 | formData.append(paramName, options.blob, file.name);
493 | } else {
494 | $.each(options.files, function (index, file) {
495 | // This check allows the tests to run with
496 | // dummy objects:
497 | if (that._isInstanceOf('File', file) ||
498 | that._isInstanceOf('Blob', file)) {
499 | formData.append(
500 | ($.type(options.paramName) === 'array' &&
501 | options.paramName[index]) || paramName,
502 | file,
503 | file.uploadName || file.name
504 | );
505 | }
506 | });
507 | }
508 | }
509 | options.data = formData;
510 | }
511 | // Blob reference is not needed anymore, free memory:
512 | options.blob = null;
513 | },
514 |
515 | _initIframeSettings: function (options) {
516 | var targetHost = $('
').prop('href', options.url).prop('host');
517 | // Setting the dataType to iframe enables the iframe transport:
518 | options.dataType = 'iframe ' + (options.dataType || '');
519 | // The iframe transport accepts a serialized array as form data:
520 | options.formData = this._getFormData(options);
521 | // Add redirect url to form data on cross-domain uploads:
522 | if (options.redirect && targetHost && targetHost !== location.host) {
523 | options.formData.push({
524 | name: options.redirectParamName || 'redirect',
525 | value: options.redirect
526 | });
527 | }
528 | },
529 |
530 | _initDataSettings: function (options) {
531 | if (this._isXHRUpload(options)) {
532 | if (!this._chunkedUpload(options, true)) {
533 | if (!options.data) {
534 | this._initXHRData(options);
535 | }
536 | this._initProgressListener(options);
537 | }
538 | if (options.postMessage) {
539 | // Setting the dataType to postmessage enables the
540 | // postMessage transport:
541 | options.dataType = 'postmessage ' + (options.dataType || '');
542 | }
543 | } else {
544 | this._initIframeSettings(options);
545 | }
546 | },
547 |
548 | _getParamName: function (options) {
549 | var fileInput = $(options.fileInput),
550 | paramName = options.paramName;
551 | if (!paramName) {
552 | paramName = [];
553 | fileInput.each(function () {
554 | var input = $(this),
555 | name = input.prop('name') || 'files[]',
556 | i = (input.prop('files') || [1]).length;
557 | while (i) {
558 | paramName.push(name);
559 | i -= 1;
560 | }
561 | });
562 | if (!paramName.length) {
563 | paramName = [fileInput.prop('name') || 'files[]'];
564 | }
565 | } else if (!$.isArray(paramName)) {
566 | paramName = [paramName];
567 | }
568 | return paramName;
569 | },
570 |
571 | _initFormSettings: function (options) {
572 | // Retrieve missing options from the input field and the
573 | // associated form, if available:
574 | if (!options.form || !options.form.length) {
575 | options.form = $(options.fileInput.prop('form'));
576 | // If the given file input doesn't have an associated form,
577 | // use the default widget file input's form:
578 | if (!options.form.length) {
579 | options.form = $(this.options.fileInput.prop('form'));
580 | }
581 | }
582 | options.paramName = this._getParamName(options);
583 | if (!options.url) {
584 | options.url = options.form.prop('action') || location.href;
585 | }
586 | // The HTTP request method must be "POST" or "PUT":
587 | options.type = (options.type ||
588 | ($.type(options.form.prop('method')) === 'string' &&
589 | options.form.prop('method')) || ''
590 | ).toUpperCase();
591 | if (options.type !== 'POST' && options.type !== 'PUT' &&
592 | options.type !== 'PATCH') {
593 | options.type = 'POST';
594 | }
595 | if (!options.formAcceptCharset) {
596 | options.formAcceptCharset = options.form.attr('accept-charset');
597 | }
598 | },
599 |
600 | _getAJAXSettings: function (data) {
601 | var options = $.extend({}, this.options, data);
602 | this._initFormSettings(options);
603 | this._initDataSettings(options);
604 | return options;
605 | },
606 |
607 | // jQuery 1.6 doesn't provide .state(),
608 | // while jQuery 1.8+ removed .isRejected() and .isResolved():
609 | _getDeferredState: function (deferred) {
610 | if (deferred.state) {
611 | return deferred.state();
612 | }
613 | if (deferred.isResolved()) {
614 | return 'resolved';
615 | }
616 | if (deferred.isRejected()) {
617 | return 'rejected';
618 | }
619 | return 'pending';
620 | },
621 |
622 | // Maps jqXHR callbacks to the equivalent
623 | // methods of the given Promise object:
624 | _enhancePromise: function (promise) {
625 | promise.success = promise.done;
626 | promise.error = promise.fail;
627 | promise.complete = promise.always;
628 | return promise;
629 | },
630 |
631 | // Creates and returns a Promise object enhanced with
632 | // the jqXHR methods abort, success, error and complete:
633 | _getXHRPromise: function (resolveOrReject, context, args) {
634 | var dfd = $.Deferred(),
635 | promise = dfd.promise();
636 | context = context || this.options.context || promise;
637 | if (resolveOrReject === true) {
638 | dfd.resolveWith(context, args);
639 | } else if (resolveOrReject === false) {
640 | dfd.rejectWith(context, args);
641 | }
642 | promise.abort = dfd.promise;
643 | return this._enhancePromise(promise);
644 | },
645 |
646 | // Adds convenience methods to the data callback argument:
647 | _addConvenienceMethods: function (e, data) {
648 | var that = this,
649 | getPromise = function (args) {
650 | return $.Deferred().resolveWith(that, args).promise();
651 | };
652 | data.process = function (resolveFunc, rejectFunc) {
653 | if (resolveFunc || rejectFunc) {
654 | data._processQueue = this._processQueue =
655 | (this._processQueue || getPromise([this])).pipe(
656 | function () {
657 | if (data.errorThrown) {
658 | return $.Deferred()
659 | .rejectWith(that, [data]).promise();
660 | }
661 | return getPromise(arguments);
662 | }
663 | ).pipe(resolveFunc, rejectFunc);
664 | }
665 | return this._processQueue || getPromise([this]);
666 | };
667 | data.submit = function () {
668 | if (this.state() !== 'pending') {
669 | data.jqXHR = this.jqXHR =
670 | (that._trigger(
671 | 'submit',
672 | $.Event('submit', {delegatedEvent: e}),
673 | this
674 | ) !== false) && that._onSend(e, this);
675 | }
676 | return this.jqXHR || that._getXHRPromise();
677 | };
678 | data.abort = function () {
679 | if (this.jqXHR) {
680 | return this.jqXHR.abort();
681 | }
682 | this.errorThrown = 'abort';
683 | that._trigger('fail', null, this);
684 | return that._getXHRPromise(false);
685 | };
686 | data.state = function () {
687 | if (this.jqXHR) {
688 | return that._getDeferredState(this.jqXHR);
689 | }
690 | if (this._processQueue) {
691 | return that._getDeferredState(this._processQueue);
692 | }
693 | };
694 | data.processing = function () {
695 | return !this.jqXHR && this._processQueue && that
696 | ._getDeferredState(this._processQueue) === 'pending';
697 | };
698 | data.progress = function () {
699 | return this._progress;
700 | };
701 | data.response = function () {
702 | return this._response;
703 | };
704 | },
705 |
706 | // Parses the Range header from the server response
707 | // and returns the uploaded bytes:
708 | _getUploadedBytes: function (jqXHR) {
709 | var range = jqXHR.getResponseHeader('Range'),
710 | parts = range && range.split('-'),
711 | upperBytesPos = parts && parts.length > 1 &&
712 | parseInt(parts[1], 10);
713 | return upperBytesPos && upperBytesPos + 1;
714 | },
715 |
716 | // Uploads a file in multiple, sequential requests
717 | // by splitting the file up in multiple blob chunks.
718 | // If the second parameter is true, only tests if the file
719 | // should be uploaded in chunks, but does not invoke any
720 | // upload requests:
721 | _chunkedUpload: function (options, testOnly) {
722 | options.uploadedBytes = options.uploadedBytes || 0;
723 | var that = this,
724 | file = options.files[0],
725 | fs = file.size,
726 | ub = options.uploadedBytes,
727 | mcs = options.maxChunkSize || fs,
728 | slice = this._blobSlice,
729 | dfd = $.Deferred(),
730 | promise = dfd.promise(),
731 | jqXHR,
732 | upload;
733 | if (!(this._isXHRUpload(options) && slice && (ub || mcs < fs)) ||
734 | options.data) {
735 | return false;
736 | }
737 | if (testOnly) {
738 | return true;
739 | }
740 | if (ub >= fs) {
741 | file.error = options.i18n('uploadedBytes');
742 | return this._getXHRPromise(
743 | false,
744 | options.context,
745 | [null, 'error', file.error]
746 | );
747 | }
748 | // The chunk upload method:
749 | upload = function () {
750 | // Clone the options object for each chunk upload:
751 | var o = $.extend({}, options),
752 | currentLoaded = o._progress.loaded;
753 | o.blob = slice.call(
754 | file,
755 | ub,
756 | ub + mcs,
757 | file.type
758 | );
759 | // Store the current chunk size, as the blob itself
760 | // will be dereferenced after data processing:
761 | o.chunkSize = o.blob.size;
762 | // Expose the chunk bytes position range:
763 | o.contentRange = 'bytes ' + ub + '-' +
764 | (ub + o.chunkSize - 1) + '/' + fs;
765 | // Process the upload data (the blob and potential form data):
766 | that._initXHRData(o);
767 | // Add progress listeners for this chunk upload:
768 | that._initProgressListener(o);
769 | jqXHR = ((that._trigger('chunksend', null, o) !== false && $.ajax(o)) ||
770 | that._getXHRPromise(false, o.context))
771 | .done(function (result, textStatus, jqXHR) {
772 | ub = that._getUploadedBytes(jqXHR) ||
773 | (ub + o.chunkSize);
774 | // Create a progress event if no final progress event
775 | // with loaded equaling total has been triggered
776 | // for this chunk:
777 | if (currentLoaded + o.chunkSize - o._progress.loaded) {
778 | that._onProgress($.Event('progress', {
779 | lengthComputable: true,
780 | loaded: ub - o.uploadedBytes,
781 | total: ub - o.uploadedBytes
782 | }), o);
783 | }
784 | options.uploadedBytes = o.uploadedBytes = ub;
785 | o.result = result;
786 | o.textStatus = textStatus;
787 | o.jqXHR = jqXHR;
788 | that._trigger('chunkdone', null, o);
789 | that._trigger('chunkalways', null, o);
790 | if (ub < fs) {
791 | // File upload not yet complete,
792 | // continue with the next chunk:
793 | upload();
794 | } else {
795 | dfd.resolveWith(
796 | o.context,
797 | [result, textStatus, jqXHR]
798 | );
799 | }
800 | })
801 | .fail(function (jqXHR, textStatus, errorThrown) {
802 | o.jqXHR = jqXHR;
803 | o.textStatus = textStatus;
804 | o.errorThrown = errorThrown;
805 | that._trigger('chunkfail', null, o);
806 | that._trigger('chunkalways', null, o);
807 | dfd.rejectWith(
808 | o.context,
809 | [jqXHR, textStatus, errorThrown]
810 | );
811 | });
812 | };
813 | this._enhancePromise(promise);
814 | promise.abort = function () {
815 | return jqXHR.abort();
816 | };
817 | upload();
818 | return promise;
819 | },
820 |
821 | _beforeSend: function (e, data) {
822 | if (this._active === 0) {
823 | // the start callback is triggered when an upload starts
824 | // and no other uploads are currently running,
825 | // equivalent to the global ajaxStart event:
826 | this._trigger('start');
827 | // Set timer for global bitrate progress calculation:
828 | this._bitrateTimer = new this._BitrateTimer();
829 | // Reset the global progress values:
830 | this._progress.loaded = this._progress.total = 0;
831 | this._progress.bitrate = 0;
832 | }
833 | // Make sure the container objects for the .response() and
834 | // .progress() methods on the data object are available
835 | // and reset to their initial state:
836 | this._initResponseObject(data);
837 | this._initProgressObject(data);
838 | data._progress.loaded = data.loaded = data.uploadedBytes || 0;
839 | data._progress.total = data.total = this._getTotal(data.files) || 1;
840 | data._progress.bitrate = data.bitrate = 0;
841 | this._active += 1;
842 | // Initialize the global progress values:
843 | this._progress.loaded += data.loaded;
844 | this._progress.total += data.total;
845 | },
846 |
847 | _onDone: function (result, textStatus, jqXHR, options) {
848 | var total = options._progress.total,
849 | response = options._response;
850 | if (options._progress.loaded < total) {
851 | // Create a progress event if no final progress event
852 | // with loaded equaling total has been triggered:
853 | this._onProgress($.Event('progress', {
854 | lengthComputable: true,
855 | loaded: total,
856 | total: total
857 | }), options);
858 | }
859 | response.result = options.result = result;
860 | response.textStatus = options.textStatus = textStatus;
861 | response.jqXHR = options.jqXHR = jqXHR;
862 | this._trigger('done', null, options);
863 | },
864 |
865 | _onFail: function (jqXHR, textStatus, errorThrown, options) {
866 | var response = options._response;
867 | if (options.recalculateProgress) {
868 | // Remove the failed (error or abort) file upload from
869 | // the global progress calculation:
870 | this._progress.loaded -= options._progress.loaded;
871 | this._progress.total -= options._progress.total;
872 | }
873 | response.jqXHR = options.jqXHR = jqXHR;
874 | response.textStatus = options.textStatus = textStatus;
875 | response.errorThrown = options.errorThrown = errorThrown;
876 | this._trigger('fail', null, options);
877 | },
878 |
879 | _onAlways: function (jqXHRorResult, textStatus, jqXHRorError, options) {
880 | // jqXHRorResult, textStatus and jqXHRorError are added to the
881 | // options object via done and fail callbacks
882 | this._trigger('always', null, options);
883 | },
884 |
885 | _onSend: function (e, data) {
886 | if (!data.submit) {
887 | this._addConvenienceMethods(e, data);
888 | }
889 | var that = this,
890 | jqXHR,
891 | aborted,
892 | slot,
893 | pipe,
894 | options = that._getAJAXSettings(data),
895 | send = function () {
896 | that._sending += 1;
897 | // Set timer for bitrate progress calculation:
898 | options._bitrateTimer = new that._BitrateTimer();
899 | jqXHR = jqXHR || (
900 | ((aborted || that._trigger(
901 | 'send',
902 | $.Event('send', {delegatedEvent: e}),
903 | options
904 | ) === false) &&
905 | that._getXHRPromise(false, options.context, aborted)) ||
906 | that._chunkedUpload(options) || $.ajax(options)
907 | ).done(function (result, textStatus, jqXHR) {
908 | that._onDone(result, textStatus, jqXHR, options);
909 | }).fail(function (jqXHR, textStatus, errorThrown) {
910 | that._onFail(jqXHR, textStatus, errorThrown, options);
911 | }).always(function (jqXHRorResult, textStatus, jqXHRorError) {
912 | that._onAlways(
913 | jqXHRorResult,
914 | textStatus,
915 | jqXHRorError,
916 | options
917 | );
918 | that._sending -= 1;
919 | that._active -= 1;
920 | if (options.limitConcurrentUploads &&
921 | options.limitConcurrentUploads > that._sending) {
922 | // Start the next queued upload,
923 | // that has not been aborted:
924 | var nextSlot = that._slots.shift();
925 | while (nextSlot) {
926 | if (that._getDeferredState(nextSlot) === 'pending') {
927 | nextSlot.resolve();
928 | break;
929 | }
930 | nextSlot = that._slots.shift();
931 | }
932 | }
933 | if (that._active === 0) {
934 | // The stop callback is triggered when all uploads have
935 | // been completed, equivalent to the global ajaxStop event:
936 | that._trigger('stop');
937 | }
938 | });
939 | return jqXHR;
940 | };
941 | this._beforeSend(e, options);
942 | if (this.options.sequentialUploads ||
943 | (this.options.limitConcurrentUploads &&
944 | this.options.limitConcurrentUploads <= this._sending)) {
945 | if (this.options.limitConcurrentUploads > 1) {
946 | slot = $.Deferred();
947 | this._slots.push(slot);
948 | pipe = slot.pipe(send);
949 | } else {
950 | this._sequence = this._sequence.pipe(send, send);
951 | pipe = this._sequence;
952 | }
953 | // Return the piped Promise object, enhanced with an abort method,
954 | // which is delegated to the jqXHR object of the current upload,
955 | // and jqXHR callbacks mapped to the equivalent Promise methods:
956 | pipe.abort = function () {
957 | aborted = [undefined, 'abort', 'abort'];
958 | if (!jqXHR) {
959 | if (slot) {
960 | slot.rejectWith(options.context, aborted);
961 | }
962 | return send();
963 | }
964 | return jqXHR.abort();
965 | };
966 | return this._enhancePromise(pipe);
967 | }
968 | return send();
969 | },
970 |
971 | _onAdd: function (e, data) {
972 | var that = this,
973 | result = true,
974 | options = $.extend({}, this.options, data),
975 | files = data.files,
976 | filesLength = files.length,
977 | limit = options.limitMultiFileUploads,
978 | limitSize = options.limitMultiFileUploadSize,
979 | overhead = options.limitMultiFileUploadSizeOverhead,
980 | batchSize = 0,
981 | paramName = this._getParamName(options),
982 | paramNameSet,
983 | paramNameSlice,
984 | fileSet,
985 | i,
986 | j = 0;
987 | if (!filesLength) {
988 | return false;
989 | }
990 | if (limitSize && files[0].size === undefined) {
991 | limitSize = undefined;
992 | }
993 | if (!(options.singleFileUploads || limit || limitSize) ||
994 | !this._isXHRUpload(options)) {
995 | fileSet = [files];
996 | paramNameSet = [paramName];
997 | } else if (!(options.singleFileUploads || limitSize) && limit) {
998 | fileSet = [];
999 | paramNameSet = [];
1000 | for (i = 0; i < filesLength; i += limit) {
1001 | fileSet.push(files.slice(i, i + limit));
1002 | paramNameSlice = paramName.slice(i, i + limit);
1003 | if (!paramNameSlice.length) {
1004 | paramNameSlice = paramName;
1005 | }
1006 | paramNameSet.push(paramNameSlice);
1007 | }
1008 | } else if (!options.singleFileUploads && limitSize) {
1009 | fileSet = [];
1010 | paramNameSet = [];
1011 | for (i = 0; i < filesLength; i = i + 1) {
1012 | batchSize += files[i].size + overhead;
1013 | if (i + 1 === filesLength ||
1014 | ((batchSize + files[i + 1].size + overhead) > limitSize) ||
1015 | (limit && i + 1 - j >= limit)) {
1016 | fileSet.push(files.slice(j, i + 1));
1017 | paramNameSlice = paramName.slice(j, i + 1);
1018 | if (!paramNameSlice.length) {
1019 | paramNameSlice = paramName;
1020 | }
1021 | paramNameSet.push(paramNameSlice);
1022 | j = i + 1;
1023 | batchSize = 0;
1024 | }
1025 | }
1026 | } else {
1027 | paramNameSet = paramName;
1028 | }
1029 | data.originalFiles = files;
1030 | $.each(fileSet || files, function (index, element) {
1031 | var newData = $.extend({}, data);
1032 | newData.files = fileSet ? element : [element];
1033 | newData.paramName = paramNameSet[index];
1034 | that._initResponseObject(newData);
1035 | that._initProgressObject(newData);
1036 | that._addConvenienceMethods(e, newData);
1037 | result = that._trigger(
1038 | 'add',
1039 | $.Event('add', {delegatedEvent: e}),
1040 | newData
1041 | );
1042 | return result;
1043 | });
1044 | return result;
1045 | },
1046 |
1047 | _replaceFileInput: function (data) {
1048 | var input = data.fileInput,
1049 | inputClone = input.clone(true),
1050 | restoreFocus = input.is(document.activeElement);
1051 | // Add a reference for the new cloned file input to the data argument:
1052 | data.fileInputClone = inputClone;
1053 | $('
').append(inputClone)[0].reset();
1054 | // Detaching allows to insert the fileInput on another form
1055 | // without loosing the file input value:
1056 | input.after(inputClone).detach();
1057 | // If the fileInput had focus before it was detached,
1058 | // restore focus to the inputClone.
1059 | if (restoreFocus) {
1060 | inputClone.focus();
1061 | }
1062 | // Avoid memory leaks with the detached file input:
1063 | $.cleanData(input.unbind('remove'));
1064 | // Replace the original file input element in the fileInput
1065 | // elements set with the clone, which has been copied including
1066 | // event handlers:
1067 | this.options.fileInput = this.options.fileInput.map(function (i, el) {
1068 | if (el === input[0]) {
1069 | return inputClone[0];
1070 | }
1071 | return el;
1072 | });
1073 | // If the widget has been initialized on the file input itself,
1074 | // override this.element with the file input clone:
1075 | if (input[0] === this.element[0]) {
1076 | this.element = inputClone;
1077 | }
1078 | },
1079 |
1080 | _handleFileTreeEntry: function (entry, path) {
1081 | var that = this,
1082 | dfd = $.Deferred(),
1083 | errorHandler = function (e) {
1084 | if (e && !e.entry) {
1085 | e.entry = entry;
1086 | }
1087 | // Since $.when returns immediately if one
1088 | // Deferred is rejected, we use resolve instead.
1089 | // This allows valid files and invalid items
1090 | // to be returned together in one set:
1091 | dfd.resolve([e]);
1092 | },
1093 | successHandler = function (entries) {
1094 | that._handleFileTreeEntries(
1095 | entries,
1096 | path + entry.name + '/'
1097 | ).done(function (files) {
1098 | dfd.resolve(files);
1099 | }).fail(errorHandler);
1100 | },
1101 | readEntries = function () {
1102 | dirReader.readEntries(function (results) {
1103 | if (!results.length) {
1104 | successHandler(entries);
1105 | } else {
1106 | entries = entries.concat(results);
1107 | readEntries();
1108 | }
1109 | }, errorHandler);
1110 | },
1111 | dirReader, entries = [];
1112 | path = path || '';
1113 | if (entry.isFile) {
1114 | if (entry._file) {
1115 | // Workaround for Chrome bug #149735
1116 | entry._file.relativePath = path;
1117 | dfd.resolve(entry._file);
1118 | } else {
1119 | entry.file(function (file) {
1120 | file.relativePath = path;
1121 | dfd.resolve(file);
1122 | }, errorHandler);
1123 | }
1124 | } else if (entry.isDirectory) {
1125 | dirReader = entry.createReader();
1126 | readEntries();
1127 | } else {
1128 | // Return an empy list for file system items
1129 | // other than files or directories:
1130 | dfd.resolve([]);
1131 | }
1132 | return dfd.promise();
1133 | },
1134 |
1135 | _handleFileTreeEntries: function (entries, path) {
1136 | var that = this;
1137 | return $.when.apply(
1138 | $,
1139 | $.map(entries, function (entry) {
1140 | return that._handleFileTreeEntry(entry, path);
1141 | })
1142 | ).pipe(function () {
1143 | return Array.prototype.concat.apply(
1144 | [],
1145 | arguments
1146 | );
1147 | });
1148 | },
1149 |
1150 | _getDroppedFiles: function (dataTransfer) {
1151 | dataTransfer = dataTransfer || {};
1152 | var items = dataTransfer.items;
1153 | if (items && items.length && (items[0].webkitGetAsEntry ||
1154 | items[0].getAsEntry)) {
1155 | return this._handleFileTreeEntries(
1156 | $.map(items, function (item) {
1157 | var entry;
1158 | if (item.webkitGetAsEntry) {
1159 | entry = item.webkitGetAsEntry();
1160 | if (entry) {
1161 | // Workaround for Chrome bug #149735:
1162 | entry._file = item.getAsFile();
1163 | }
1164 | return entry;
1165 | }
1166 | return item.getAsEntry();
1167 | })
1168 | );
1169 | }
1170 | return $.Deferred().resolve(
1171 | $.makeArray(dataTransfer.files)
1172 | ).promise();
1173 | },
1174 |
1175 | _getSingleFileInputFiles: function (fileInput) {
1176 | fileInput = $(fileInput);
1177 | var entries = fileInput.prop('webkitEntries') ||
1178 | fileInput.prop('entries'),
1179 | files,
1180 | value;
1181 | if (entries && entries.length) {
1182 | return this._handleFileTreeEntries(entries);
1183 | }
1184 | files = $.makeArray(fileInput.prop('files'));
1185 | if (!files.length) {
1186 | value = fileInput.prop('value');
1187 | if (!value) {
1188 | return $.Deferred().resolve([]).promise();
1189 | }
1190 | // If the files property is not available, the browser does not
1191 | // support the File API and we add a pseudo File object with
1192 | // the input value as name with path information removed:
1193 | files = [{name: value.replace(/^.*\\/, '')}];
1194 | } else if (files[0].name === undefined && files[0].fileName) {
1195 | // File normalization for Safari 4 and Firefox 3:
1196 | $.each(files, function (index, file) {
1197 | file.name = file.fileName;
1198 | file.size = file.fileSize;
1199 | });
1200 | }
1201 | return $.Deferred().resolve(files).promise();
1202 | },
1203 |
1204 | _getFileInputFiles: function (fileInput) {
1205 | if (!(fileInput instanceof $) || fileInput.length === 1) {
1206 | return this._getSingleFileInputFiles(fileInput);
1207 | }
1208 | return $.when.apply(
1209 | $,
1210 | $.map(fileInput, this._getSingleFileInputFiles)
1211 | ).pipe(function () {
1212 | return Array.prototype.concat.apply(
1213 | [],
1214 | arguments
1215 | );
1216 | });
1217 | },
1218 |
1219 | _onChange: function (e) {
1220 | var that = this,
1221 | data = {
1222 | fileInput: $(e.target),
1223 | form: $(e.target.form)
1224 | };
1225 | this._getFileInputFiles(data.fileInput).always(function (files) {
1226 | data.files = files;
1227 | if (that.options.replaceFileInput) {
1228 | that._replaceFileInput(data);
1229 | }
1230 | if (that._trigger(
1231 | 'change',
1232 | $.Event('change', {delegatedEvent: e}),
1233 | data
1234 | ) !== false) {
1235 | that._onAdd(e, data);
1236 | }
1237 | });
1238 | },
1239 |
1240 | _onPaste: function (e) {
1241 | var items = e.originalEvent && e.originalEvent.clipboardData &&
1242 | e.originalEvent.clipboardData.items,
1243 | data = {files: []};
1244 | if (items && items.length) {
1245 | $.each(items, function (index, item) {
1246 | var file = item.getAsFile && item.getAsFile();
1247 | if (file) {
1248 | data.files.push(file);
1249 | }
1250 | });
1251 | if (this._trigger(
1252 | 'paste',
1253 | $.Event('paste', {delegatedEvent: e}),
1254 | data
1255 | ) !== false) {
1256 | this._onAdd(e, data);
1257 | }
1258 | }
1259 | },
1260 |
1261 | _onDrop: function (e) {
1262 | e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer;
1263 | var that = this,
1264 | dataTransfer = e.dataTransfer,
1265 | data = {};
1266 | if (dataTransfer && dataTransfer.files && dataTransfer.files.length) {
1267 | e.preventDefault();
1268 | this._getDroppedFiles(dataTransfer).always(function (files) {
1269 | data.files = files;
1270 | if (that._trigger(
1271 | 'drop',
1272 | $.Event('drop', {delegatedEvent: e}),
1273 | data
1274 | ) !== false) {
1275 | that._onAdd(e, data);
1276 | }
1277 | });
1278 | }
1279 | },
1280 |
1281 | _onDragOver: getDragHandler('dragover'),
1282 |
1283 | _onDragEnter: getDragHandler('dragenter'),
1284 |
1285 | _onDragLeave: getDragHandler('dragleave'),
1286 |
1287 | _initEventHandlers: function () {
1288 | if (this._isXHRUpload(this.options)) {
1289 | this._on(this.options.dropZone, {
1290 | dragover: this._onDragOver,
1291 | drop: this._onDrop,
1292 | // event.preventDefault() on dragenter is required for IE10+:
1293 | dragenter: this._onDragEnter,
1294 | // dragleave is not required, but added for completeness:
1295 | dragleave: this._onDragLeave
1296 | });
1297 | this._on(this.options.pasteZone, {
1298 | paste: this._onPaste
1299 | });
1300 | }
1301 | if ($.support.fileInput) {
1302 | this._on(this.options.fileInput, {
1303 | change: this._onChange
1304 | });
1305 | }
1306 | },
1307 |
1308 | _destroyEventHandlers: function () {
1309 | this._off(this.options.dropZone, 'dragenter dragleave dragover drop');
1310 | this._off(this.options.pasteZone, 'paste');
1311 | this._off(this.options.fileInput, 'change');
1312 | },
1313 |
1314 | _setOption: function (key, value) {
1315 | var reinit = $.inArray(key, this._specialOptions) !== -1;
1316 | if (reinit) {
1317 | this._destroyEventHandlers();
1318 | }
1319 | this._super(key, value);
1320 | if (reinit) {
1321 | this._initSpecialOptions();
1322 | this._initEventHandlers();
1323 | }
1324 | },
1325 |
1326 | _initSpecialOptions: function () {
1327 | var options = this.options;
1328 | if (options.fileInput === undefined) {
1329 | options.fileInput = this.element.is('input[type="file"]') ?
1330 | this.element : this.element.find('input[type="file"]');
1331 | } else if (!(options.fileInput instanceof $)) {
1332 | options.fileInput = $(options.fileInput);
1333 | }
1334 | if (!(options.dropZone instanceof $)) {
1335 | options.dropZone = $(options.dropZone);
1336 | }
1337 | if (!(options.pasteZone instanceof $)) {
1338 | options.pasteZone = $(options.pasteZone);
1339 | }
1340 | },
1341 |
1342 | _getRegExp: function (str) {
1343 | var parts = str.split('/'),
1344 | modifiers = parts.pop();
1345 | parts.shift();
1346 | return new RegExp(parts.join('/'), modifiers);
1347 | },
1348 |
1349 | _isRegExpOption: function (key, value) {
1350 | return key !== 'url' && $.type(value) === 'string' &&
1351 | /^\/.*\/[igm]{0,3}$/.test(value);
1352 | },
1353 |
1354 | _initDataAttributes: function () {
1355 | var that = this,
1356 | options = this.options,
1357 | data = this.element.data();
1358 | // Initialize options set via HTML5 data-attributes:
1359 | $.each(
1360 | this.element[0].attributes,
1361 | function (index, attr) {
1362 | var key = attr.name.toLowerCase(),
1363 | value;
1364 | if (/^data-/.test(key)) {
1365 | // Convert hyphen-ated key to camelCase:
1366 | key = key.slice(5).replace(/-[a-z]/g, function (str) {
1367 | return str.charAt(1).toUpperCase();
1368 | });
1369 | value = data[key];
1370 | if (that._isRegExpOption(key, value)) {
1371 | value = that._getRegExp(value);
1372 | }
1373 | options[key] = value;
1374 | }
1375 | }
1376 | );
1377 | },
1378 |
1379 | _create: function () {
1380 | this._initDataAttributes();
1381 | this._initSpecialOptions();
1382 | this._slots = [];
1383 | this._sequence = this._getXHRPromise(true);
1384 | this._sending = this._active = 0;
1385 | this._initProgressObject(this);
1386 | this._initEventHandlers();
1387 | },
1388 |
1389 | // This method is exposed to the widget API and allows to query
1390 | // the number of active uploads:
1391 | active: function () {
1392 | return this._active;
1393 | },
1394 |
1395 | // This method is exposed to the widget API and allows to query
1396 | // the widget upload progress.
1397 | // It returns an object with loaded, total and bitrate properties
1398 | // for the running uploads:
1399 | progress: function () {
1400 | return this._progress;
1401 | },
1402 |
1403 | // This method is exposed to the widget API and allows adding files
1404 | // using the fileupload API. The data parameter accepts an object which
1405 | // must have a files property and can contain additional options:
1406 | // .fileupload('add', {files: filesList});
1407 | add: function (data) {
1408 | var that = this;
1409 | if (!data || this.options.disabled) {
1410 | return;
1411 | }
1412 | if (data.fileInput && !data.files) {
1413 | this._getFileInputFiles(data.fileInput).always(function (files) {
1414 | data.files = files;
1415 | that._onAdd(null, data);
1416 | });
1417 | } else {
1418 | data.files = $.makeArray(data.files);
1419 | this._onAdd(null, data);
1420 | }
1421 | },
1422 |
1423 | // This method is exposed to the widget API and allows sending files
1424 | // using the fileupload API. The data parameter accepts an object which
1425 | // must have a files or fileInput property and can contain additional options:
1426 | // .fileupload('send', {files: filesList});
1427 | // The method returns a Promise object for the file upload call.
1428 | send: function (data) {
1429 | if (data && !this.options.disabled) {
1430 | if (data.fileInput && !data.files) {
1431 | var that = this,
1432 | dfd = $.Deferred(),
1433 | promise = dfd.promise(),
1434 | jqXHR,
1435 | aborted;
1436 | promise.abort = function () {
1437 | aborted = true;
1438 | if (jqXHR) {
1439 | return jqXHR.abort();
1440 | }
1441 | dfd.reject(null, 'abort', 'abort');
1442 | return promise;
1443 | };
1444 | this._getFileInputFiles(data.fileInput).always(
1445 | function (files) {
1446 | if (aborted) {
1447 | return;
1448 | }
1449 | if (!files.length) {
1450 | dfd.reject();
1451 | return;
1452 | }
1453 | data.files = files;
1454 | jqXHR = that._onSend(null, data);
1455 | jqXHR.then(
1456 | function (result, textStatus, jqXHR) {
1457 | dfd.resolve(result, textStatus, jqXHR);
1458 | },
1459 | function (jqXHR, textStatus, errorThrown) {
1460 | dfd.reject(jqXHR, textStatus, errorThrown);
1461 | }
1462 | );
1463 | }
1464 | );
1465 | return this._enhancePromise(promise);
1466 | }
1467 | data.files = $.makeArray(data.files);
1468 | if (data.files.length) {
1469 | return this._onSend(null, data);
1470 | }
1471 | }
1472 | return this._getXHRPromise(false, data && data.context);
1473 | }
1474 |
1475 | });
1476 |
1477 | }));
1478 |
--------------------------------------------------------------------------------