├── .gitignore
├── CHANGELOG.md
├── README.md
├── docs
├── README.md
└── jsduck-config.json
├── lib
└── upload
│ ├── BrowseButton.js
│ ├── Dialog.js
│ ├── Item.js
│ ├── ItemGridPanel.js
│ ├── LegacyDialog.js
│ ├── Manager.js
│ ├── Panel.js
│ ├── Queue.js
│ ├── StatusBar.js
│ ├── Store.js
│ ├── css
│ └── upload.css
│ ├── data
│ └── Connection.js
│ ├── header
│ ├── AbstractFilenameEncoder.js
│ └── Base64FilenameEncoder.js
│ ├── img
│ ├── accept.png
│ ├── ajax-loader1.gif
│ ├── ajax-loader2.gif
│ ├── ajax-loader3.gif
│ ├── arrow_up.png
│ ├── cancel.png
│ ├── delete.png
│ ├── exclamation.png
│ ├── folder.png
│ ├── help.png
│ ├── loading.gif
│ └── tick.png
│ └── uploader
│ ├── AbstractUploader.js
│ ├── AbstractXhrUploader.js
│ ├── DummyUploader.js
│ ├── ExtJsUploader.js
│ ├── FormDataUploader.js
│ └── LegacyExtJsUploader.js
└── public
├── _common.php
├── _config.php.dist
├── app.js
├── docs
├── external
└── upload
├── img
└── extjs-upload-widget.jpeg
├── index-ext4.html
├── index.html
├── upload.php
└── upload_multipart.php
/.gitignore:
--------------------------------------------------------------------------------
1 | public/extjs
2 | public/_config.php
3 | docs/generated
4 | .buildpath
5 | .project
6 | .settings
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Ext JS Upload Widget Changelog
2 |
3 | ## v1.1.1
4 | (2013-11-05)
5 |
6 | - Option to re-try upload when errors occur (#9)
7 | - Add the DummyUploader to the example (#11)
8 | - Use "itemId" instead of "id" property (#14)
9 | - Fixed: The "Browse" button loses the "multiple" attribute (#10)
10 | - Fixed: Handle utf-8 encoded filenames properly (#15)
11 |
12 |
13 | ## v1.1.0
14 | (2013-06-12)
15 |
16 | - implemented the core functionality as a panel rather than a dialog
17 | - multiple uploader implementations support
18 | - you can now "inject" your own uploader implementation
19 | - new FormDataUploader implementing multipart upload
20 | - updated examples and documentation
21 |
22 |
23 | ## v1.0.1
24 | (2013-06-10)
25 |
26 | - increased default connection timeout
27 | - removed obsolete code
28 |
29 |
30 | ## v1.0.0
31 | (2012-09-27)
32 |
33 | - initial release
34 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # File upload widget for Sencha Ext JS
2 |
3 | ## Features
4 |
5 | - flexible and easily integratable
6 | - uses the native File API (HTML5)
7 | - allows selecting and uploading of multiple files at once
8 | - supports both raw PUT/POST upload and multipart upload
9 | - you can easily write and integrate your own upload mechanism, while preserving all (or most) of the UI functionality
10 | - displays upload progress
11 | - supports asynchronous (simultaneous) upload
12 |
13 | ## Online demo
14 |
15 | - [Demo included in this repository](http://debug.cz/demo/upload/)
16 |
17 | ## Requirements
18 |
19 | - [Sencha Ext JS 5.x](http://www.sencha.com/products/extjs/)
20 | - browser supporting the [File API](http://www.w3.org/TR/FileAPI/) - see more info about [browser compatibility](http://caniuse.com/fileapi)
21 | - server side to process the uploaded files
22 |
23 | ## Installation
24 |
25 | Clone the repository somewhere on your system and add the path with the prefix to `Ext.Loader`:
26 |
27 | Ext.Loader.setPath({
28 | 'Ext.ux.upload' : '/my/path/to/extjs-upload-widget/lib/upload'
29 | });
30 |
31 | ## Basic usage
32 |
33 | In the most simple case, you can just open the dialog and pass the `uploadUrl` paramter:
34 |
35 | var dialog = Ext.create('Ext.ux.upload.Dialog', {
36 | dialogTitle: 'My Upload Widget',
37 | uploadUrl: 'upload.php'
38 | });
39 |
40 | dialog.show();
41 |
42 | `Ext.ux.upload.Dialog` is just a simple wrapper window. The core functionality is implemented in the `Ext.uz.upload.Panel` object, so you can implement your own dialog and pass the panel:
43 |
44 | var myDialog = Ext.create('MyDialog', {
45 | items: [
46 | Ext.create('Ext.ux.upload.Panel', {
47 | uploadUrl: 'upload.php'
48 | });
49 | ]
50 | });
51 |
52 | ## Uploaders
53 |
54 | The intention behind the uploaders implementation is to have the upload process decoupled from the UI as much as possible. This allows us to create alternative uploader implementations to serve our use case and at the same time, we don't need to touch the UI.
55 |
56 | Currently, these uploaders are implemented:
57 |
58 | - __ExtJsUploader__ (default) - uploads the file by sending the raw file data in the body of a _XmlHttpRequest_. File metadata are sent through request HTTP headers. Actually, the standard `Ext.data.Connection` object is used with a small tweak to allow progress reporting.
59 | - __FormDataUploader__ - uploads the file through a _XmlHttpRequest_ as if it was submitted with a form.
60 |
61 | Each uploader requires different processing at the backend side. Check the `public/upload.php` file for the __ExtJsUploader__ and the `public/upload_multipart.php` for the __FormDataUploader__.
62 |
63 | ## Advanced usage
64 |
65 | The default uploader is the __ExtJsUploader__. If you want to use an alternative uploader, you need to pass the uploader class name to the upload panel:
66 |
67 | var panel = Ext.create('Ext.ux.upload.Panel', {
68 | uploader: 'Ext.ux.upload.uploader.FormDataUploader',
69 | uploaderOptions: {
70 | url: 'upload_multipart.php',
71 | timeout: 120*1000
72 | }
73 | });
74 |
75 | Or you can pass the uploader instance:
76 |
77 | var panel = Ext.create('Ext.ux.upload.Panel', {
78 | uploader: Ext.create('Ext.ux.upload.uploader.FormDataUploader', {
79 | url: 'upload_multipart.php',
80 | timeout: 120*1000
81 | });
82 | });
83 |
84 | ## Running the example
85 |
86 |
87 | ### Requirements:
88 |
89 | - web server with PHP support
90 | - Ext JS v4.x instance
91 |
92 | Clone the repository and make the `public` directory accessible through your web server. Open the `public/_config.php` file and set the _upload_dir_ option to point to a directory the web server can write to. If you just want to test the upload process and you don't really want to save the uploaded files, you can set the _fake_ option to true and no files will be written to the disk.
93 |
94 | The example `index.html` expects to find the Ext JS instance in the `public/extjs` directory. You can create a link to the instance or copy it there.
95 |
96 |
97 |
98 | ## Documentation
99 |
100 | - [API Docs](http://debug.cz/demo/upload/docs/generated/)
101 |
102 | ## Other links
103 |
104 | - [More info in the blogpost](http://blog.debug.cz/2012/05/file-upload-widget-for-extjs-4x.html)
105 | - [Sencha forums post](http://www.sencha.com/forum/showthread.php?205365-File-upload-widget-using-File-API-and-Ext.data.Connection)
106 |
107 | ## TODO
108 |
109 | - add more uploader implementations
110 | - add drag'n'drop support
111 | - improve documentation
112 |
113 | ## License
114 |
115 | - [BSD 3 Clause](http://debug.cz/license/bsd-3-clause)
116 |
117 | ## Author
118 |
119 | - [Ivan Novakov](http://novakov.cz/)
120 |
121 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | API documentation
2 | =================
3 |
4 | The documentation is generated with the [JSDuck tool](https://github.com/senchalabs/jsduck).
5 | From this (`doc/`) directory run:
6 |
7 | jsduck --config jsduck-config.json
8 |
9 |
--------------------------------------------------------------------------------
/docs/jsduck-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "--title" : "Ext JS Upload Widget",
3 | "--output" : "./generated",
4 | "--seo" : true,
5 | "--external" : [
6 | "Ext.Base", "Ext.util.Observable", "Ext.data.Connection", "Ext.window.Window", "Ext.util.MixedCollection",
7 | "Ext.panel.Panel", "Ext.panel.Panel", "Ext.form.field.File", "Ext.grid.Panel", "Ext.toolbar.Toolbar",
8 | "Ext.selection.CheckboxModel", "FileList", "File"
9 | ],
10 | "--" : [
11 | "../lib"
12 | ]
13 | }
--------------------------------------------------------------------------------
/lib/upload/BrowseButton.js:
--------------------------------------------------------------------------------
1 | /**
2 | * A "browse" button for selecting multiple files for upload.
3 | *
4 | */
5 | Ext.define('Ext.ux.upload.BrowseButton', {
6 | extend : 'Ext.form.field.File',
7 |
8 | buttonOnly : true,
9 |
10 | iconCls : 'ux-mu-icon-action-browse',
11 | buttonText : 'Browse...',
12 |
13 | initComponent : function() {
14 |
15 | Ext.apply(this, {
16 | buttonConfig : {
17 | iconCls : this.iconCls,
18 | text : this.buttonText
19 | }
20 | });
21 |
22 | this.on('afterrender', function() {
23 | /*
24 | * Fixing the issue when adding an icon to the button - the text does not render properly. OBSOLETE - from
25 | * ExtJS v4.1 the internal implementation has changed, there is no button object anymore.
26 | */
27 | /*
28 | if (this.iconCls) {
29 | // this.button.removeCls('x-btn-icon');
30 | // var width = this.button.getWidth();
31 | // this.setWidth(width);
32 | }
33 | */
34 |
35 | // Allow picking multiple files at once.
36 | this.setMultipleInputAttribute();
37 |
38 | }, this);
39 |
40 | this.on('change', function(field, value, options) {
41 | var files = this.fileInputEl.dom.files;
42 | if (files.length) {
43 | this.fireEvent('fileselected', this, files);
44 | }
45 | }, this);
46 |
47 | this.callParent(arguments);
48 | },
49 |
50 | reset : function() {
51 | this.callParent(arguments);
52 | this.setMultipleInputAttribute();
53 | },
54 |
55 | setMultipleInputAttribute : function(inputEl) {
56 | inputEl = inputEl || this.fileInputEl;
57 | inputEl.dom.setAttribute('multiple', '1');
58 | }
59 |
60 | // OBSOLETE - the method is not used by the superclass anymore
61 | /*
62 | createFileInput : function() {
63 | this.callParent(arguments);
64 | this.fileInputEl.dom.setAttribute('multiple', '1');
65 | }
66 | */
67 |
68 | }
69 | );
70 |
--------------------------------------------------------------------------------
/lib/upload/Dialog.js:
--------------------------------------------------------------------------------
1 | /**
2 | * The main upload dialog.
3 | *
4 | * Mostly, this may be the only object you need to interact with. Just initialize it and show it:
5 | *
6 | * @example
7 | * var dialog = Ext.create('Ext.ux.upload.Dialog', {
8 | * dialogTitle: 'My Upload Widget',
9 | * uploadUrl: 'upload.php'
10 | * });
11 | * dialog.show();
12 | *
13 | */
14 | Ext.define('Ext.ux.upload.Dialog', {
15 | extend : 'Ext.window.Window',
16 |
17 | /**
18 | * @cfg {Number} [width=700]
19 | */
20 | width : 700,
21 |
22 | /**
23 | * @cfg {Number} [height=500]
24 | */
25 | height : 500,
26 |
27 | border : 0,
28 |
29 | config : {
30 | /**
31 | * @cfg {String}
32 | *
33 | * The title of the dialog.
34 | */
35 | dialogTitle : '',
36 |
37 | /**
38 | * @cfg {boolean} [synchronous=false]
39 | *
40 | * If true, all files are uploaded in a sequence, otherwise files are uploaded simultaneously (asynchronously).
41 | */
42 | synchronous : true,
43 |
44 | /**
45 | * @cfg {String} uploadUrl (required)
46 | *
47 | * The URL to upload files to.
48 | */
49 | uploadUrl : '',
50 |
51 | /**
52 | * @cfg {Object}
53 | *
54 | * Params passed to the uploader object and sent along with the request. It depends on the implementation of the
55 | * uploader object, for example if the {@link Ext.ux.upload.uploader.ExtJsUploader} is used, the params are sent
56 | * as GET params.
57 | */
58 | uploadParams : {},
59 |
60 | /**
61 | * @cfg {Object}
62 | *
63 | * Extra HTTP headers to be added to the HTTP request uploading the file.
64 | */
65 | uploadExtraHeaders : {},
66 |
67 | /**
68 | * @cfg {Number} [uploadTimeout=6000]
69 | *
70 | * The time after the upload request times out - in miliseconds.
71 | */
72 | uploadTimeout : 60000,
73 |
74 | // strings
75 | textClose : 'Close'
76 | },
77 |
78 | /**
79 | * @private
80 | */
81 | initComponent : function() {
82 |
83 | if (!Ext.isObject(this.panel)) {
84 | this.panel = Ext.create('Ext.ux.upload.Panel', {
85 | synchronous : this.synchronous,
86 | scope: this.scope,
87 | uploadUrl : this.uploadUrl,
88 | uploadParams : this.uploadParams,
89 | uploadExtraHeaders : this.uploadExtraHeaders,
90 | uploadTimeout : this.uploadTimeout
91 | });
92 | }
93 |
94 | this.relayEvents(this.panel, [
95 | 'uploadcomplete'
96 | ]);
97 |
98 | Ext.apply(this, {
99 | title : this.dialogTitle,
100 | layout : 'fit',
101 | items : [
102 | this.panel
103 | ],
104 | dockedItems : [
105 | {
106 | xtype : 'toolbar',
107 | dock : 'bottom',
108 | ui : 'footer',
109 | defaults : {
110 | minWidth : this.minButtonWidth
111 | },
112 | items : [
113 | '->',
114 | {
115 | text : this.textClose,
116 | cls : 'x-btn-text-icon',
117 | scope : this,
118 | handler : function() {
119 | this.close();
120 | }
121 | }
122 | ]
123 | }
124 | ]
125 | });
126 |
127 | this.callParent(arguments);
128 | }
129 |
130 | });
--------------------------------------------------------------------------------
/lib/upload/Item.js:
--------------------------------------------------------------------------------
1 | /**
2 | * A single item designated for upload.
3 | *
4 | * It is a simple object wrapping the native file API object.
5 | */
6 | Ext.define('Ext.ux.upload.Item', {
7 | mixins : {
8 | observable : 'Ext.util.Observable'
9 | },
10 |
11 | STATUS_READY : 'ready',
12 | STATUS_UPLOADING : 'uploading',
13 | STATUS_UPLOADED : 'uploaded',
14 | STATUS_UPLOAD_ERROR : 'uploaderror',
15 |
16 | progress : null,
17 | status : null,
18 |
19 | /**
20 | * @cfg {Object} fileApiObject (required)
21 | *
22 | * A native file API object
23 | */
24 | fileApiObject : null,
25 |
26 | /**
27 | * @cfg {String}
28 | *
29 | * The upload error message associated with this file object
30 | */
31 | uploadErrorMessage : '',
32 |
33 | constructor : function(config) {
34 | this.mixins.observable.constructor.call(this);
35 |
36 | this.initConfig(config);
37 |
38 | Ext.apply(this, {
39 | status : this.STATUS_READY,
40 | progress : 0
41 | });
42 | },
43 |
44 | reset : function() {
45 | this.uploadErrorMessage = '';
46 | this.setStatus(this.STATUS_READY);
47 | this.setProgress(0);
48 | },
49 |
50 | getFileApiObject : function() {
51 | return this.fileApiObject;
52 | },
53 |
54 | getId : function() {
55 | return this.getFilename();
56 | },
57 |
58 | getName : function() {
59 | return this.getProperty('name');
60 | },
61 |
62 | getFilename : function() {
63 | return this.getName();
64 | },
65 |
66 | getSize : function() {
67 | return this.getProperty('size');
68 | },
69 |
70 | getType : function() {
71 | return this.getProperty('type');
72 | },
73 |
74 | getProperty : function(propertyName) {
75 | if (this.fileApiObject) {
76 | return this.fileApiObject[propertyName];
77 | }
78 |
79 | return null;
80 | },
81 |
82 | getProgress : function() {
83 | return this.progress;
84 | },
85 |
86 | getProgressPercent : function() {
87 | var progress = this.getProgress();
88 | if (!progress) {
89 | return 0;
90 | }
91 |
92 | var percent = Ext.util.Format.number((progress / this.getSize()) * 100, '0');
93 | if (percent > 100) {
94 | percent = 100;
95 | }
96 |
97 | return percent;
98 | },
99 |
100 | setProgress : function(progress) {
101 | this.progress = progress;
102 | this.fireEvent('progressupdate', this);
103 | },
104 |
105 | getStatus : function() {
106 | return this.status;
107 | },
108 |
109 | setStatus : function(status) {
110 | this.status = status;
111 | this.fireEvent('changestatus', this, status);
112 | },
113 |
114 | hasStatus : function(status) {
115 | var itemStatus = this.getStatus();
116 |
117 | if (Ext.isArray(status) && Ext.Array.contains(status, itemStatus)) {
118 | return true;
119 | }
120 |
121 | if (itemStatus === status) {
122 | return true;
123 | }
124 |
125 | return false;
126 | },
127 |
128 | isReady : function() {
129 | return (this.status == this.STATUS_READY);
130 | },
131 |
132 | isUploaded : function() {
133 | return (this.status == this.STATUS_UPLOADED);
134 | },
135 |
136 | setUploaded : function() {
137 | this.setProgress(this.getSize());
138 | this.setStatus(this.STATUS_UPLOADED);
139 | },
140 |
141 | isUploadError : function() {
142 | return (this.status == this.STATUS_UPLOAD_ERROR);
143 | },
144 |
145 | getUploadErrorMessage : function() {
146 | return this.uploadErrorMessage;
147 | },
148 |
149 | setUploadError : function(message) {
150 | this.uploadErrorMessage = message;
151 | this.setStatus(this.STATUS_UPLOAD_ERROR);
152 | },
153 |
154 | setUploading : function() {
155 | this.setStatus(this.STATUS_UPLOADING);
156 | }
157 | });
158 |
--------------------------------------------------------------------------------
/lib/upload/ItemGridPanel.js:
--------------------------------------------------------------------------------
1 | /**
2 | * The grid displaying the list of uploaded files (queue).
3 | *
4 | * @class Ext.ux.upload.ItemGridPanel
5 | * @extends Ext.grid.Panel
6 | */
7 | Ext.define('Ext.ux.upload.ItemGridPanel', {
8 | extend : 'Ext.grid.Panel',
9 |
10 | requires : [
11 | 'Ext.selection.CheckboxModel', 'Ext.ux.upload.Store'
12 | ],
13 |
14 | layout : 'fit',
15 | border : 0,
16 |
17 | viewConfig : {
18 | scrollOffset : 40
19 | },
20 |
21 | config : {
22 | queue : null,
23 |
24 | textFilename : 'Filename',
25 | textSize : 'Size',
26 | textType : 'Type',
27 | textStatus : 'Status',
28 | textProgress : '%'
29 | },
30 |
31 | initComponent : function() {
32 |
33 | if (this.queue) {
34 | this.queue.on('queuechange', this.onQueueChange, this);
35 | this.queue.on('itemchangestatus', this.onQueueItemChangeStatus, this);
36 | this.queue.on('itemprogressupdate', this.onQueueItemProgressUpdate, this);
37 | }
38 |
39 | Ext.apply(this, {
40 | store : Ext.create('Ext.ux.upload.Store'),
41 | selModel : Ext.create('Ext.selection.CheckboxModel', {
42 | checkOnly : true
43 | }),
44 | columns : [
45 | {
46 | xtype : 'rownumberer',
47 | width : 50
48 | }, {
49 | dataIndex : 'filename',
50 | header : this.textFilename,
51 | flex : 1
52 | }, {
53 | dataIndex : 'size',
54 | header : this.textSize,
55 | width : 100,
56 | renderer : function(value) {
57 | return Ext.util.Format.fileSize(value);
58 | }
59 | }, {
60 | dataIndex : 'type',
61 | header : this.textType,
62 | width : 150
63 | }, {
64 | dataIndex : 'status',
65 | header : this.textStatus,
66 | width : 50,
67 | align : 'right',
68 | renderer : this.statusRenderer
69 | }, {
70 | dataIndex : 'progress',
71 | header : this.textProgress,
72 | width : 50,
73 | align : 'right',
74 | renderer : function(value) {
75 | if (!value) {
76 | value = 0;
77 | }
78 | return value + '%';
79 | }
80 | }, {
81 | dataIndex : 'message',
82 | width : 1,
83 | hidden : true
84 | }
85 | ]
86 | });
87 |
88 | this.callParent(arguments);
89 | },
90 |
91 | onQueueChange : function(queue) {
92 | this.loadQueueItems(queue.getItems());
93 | },
94 |
95 | onQueueItemChangeStatus : function(queue, item, status) {
96 | this.updateStatus(item);
97 | },
98 |
99 | onQueueItemProgressUpdate : function(queue, item) {
100 | this.updateStatus(item);
101 | },
102 |
103 | /**
104 | * Loads the internal store with the supplied queue items.
105 | *
106 | * @param {Array} items
107 | */
108 | loadQueueItems : function(items) {
109 | var data = [];
110 | var i;
111 |
112 | for (i = 0; i < items.length; i++) {
113 | data.push([
114 | items[i].getFilename(),
115 | items[i].getSize(),
116 | items[i].getType(),
117 | items[i].getStatus(),
118 | items[i].getProgressPercent()
119 | ]);
120 | }
121 |
122 | this.loadStoreData(data);
123 | },
124 |
125 | loadStoreData : function(data, append) {
126 | this.store.loadData(data, append);
127 | },
128 |
129 | getSelectedRecords : function() {
130 | return this.getSelectionModel().getSelection();
131 | },
132 |
133 | updateStatus : function(item) {
134 | var record = this.getRecordByFilename(item.getFilename());
135 | if (!record) {
136 | return;
137 | }
138 |
139 | var itemStatus = item.getStatus();
140 | // debug.log('[' + item.getStatus() + '] [' + record.get('status') + ']');
141 | if (itemStatus != record.get('status')) {
142 | this.scrollIntoView(record);
143 |
144 | record.set('status', item.getStatus());
145 | if (item.isUploadError()) {
146 | record.set('tooltip', item.getUploadErrorMessage());
147 | }
148 | }
149 |
150 | record.set('progress', item.getProgressPercent());
151 | record.commit();
152 | },
153 |
154 | getRecordByFilename : function(filename) {
155 | var index = this.store.findExact('filename', filename);
156 | if (-1 == index) {
157 | return null;
158 | }
159 |
160 | return this.store.getAt(index);
161 | },
162 |
163 | getIndexByRecord : function(record) {
164 | return this.store.findExact('filename', record.get('filename'));
165 | },
166 |
167 | statusRenderer : function(value, metaData, record, rowIndex, colIndex, store) {
168 | var iconCls = 'ux-mu-icon-upload-' + value;
169 | var tooltip = record.get('tooltip');
170 | if (tooltip) {
171 | value = tooltip;
172 | } else {
173 | 'upload_status_' + value;
174 | }
175 | value = '';
176 | return value;
177 | },
178 |
179 | scrollIntoView : function(record) {
180 |
181 | var index = this.getIndexByRecord(record);
182 | if (-1 == index) {
183 | return;
184 | }
185 |
186 | this.getView().focusRow(index);
187 | return;
188 | var rowEl = Ext.get(this.getView().getRow(index));
189 | // var rowEl = this.getView().getRow(index);
190 | if (!rowEl) {
191 | return;
192 | }
193 |
194 | var gridEl = this.getEl();
195 |
196 | // debug.log(rowEl.dom);
197 | // debug.log(gridEl.getBottom());
198 |
199 | if (rowEl.getBottom() > gridEl.getBottom()) {
200 | rowEl.dom.scrollIntoView(gridEl);
201 | }
202 | }
203 | });
--------------------------------------------------------------------------------
/lib/upload/LegacyDialog.js:
--------------------------------------------------------------------------------
1 | /**
2 | * The main upload dialog.
3 | *
4 | * Mostly, this will be the only object you need to interact with. Just initialize it and show it:
5 | *
6 | * @example
7 | * var dialog = Ext.create('Ext.ux.upload.Dialog', {
8 | * dialogTitle: 'My Upload Widget',
9 | * uploadUrl: 'upload.php'
10 | * });
11 | *
12 | * dialog.show();
13 | *
14 | */
15 | Ext.define('Ext.ux.upload.Dialog', {
16 | extend : 'Ext.window.Window',
17 |
18 | requires : [
19 | 'Ext.ux.upload.ItemGridPanel',
20 | 'Ext.ux.upload.Manager',
21 | 'Ext.ux.upload.StatusBar',
22 | 'Ext.ux.upload.BrowseButton',
23 | 'Ext.ux.upload.Queue'
24 | ],
25 |
26 | /**
27 | * @cfg {Number} [width=700]
28 | */
29 | width : 700,
30 |
31 | /**
32 | * @cfg {Number} [height=500]
33 | */
34 | height : 500,
35 |
36 | config : {
37 | /**
38 | * @cfg {String}
39 | *
40 | * The title of the dialog.
41 | */
42 | dialogTitle : '',
43 |
44 | /**
45 | * @cfg {boolean} [synchronous=false]
46 | *
47 | * If true, all files are uploaded in a sequence, otherwise files are uploaded simultaneously (asynchronously).
48 | */
49 | synchronous : true,
50 |
51 | /**
52 | * @cfg {String} uploadUrl (required)
53 | *
54 | * The URL to upload files to.
55 | */
56 | uploadUrl : '',
57 |
58 | /**
59 | * @cfg {Object}
60 | *
61 | * Params passed to the uploader object and sent along with the request. It depends on the implementation of the
62 | * uploader object, for example if the {@link Ext.ux.upload.uploader.ExtJsUploader} is used, the params are sent
63 | * as GET params.
64 | */
65 | uploadParams : {},
66 |
67 | /**
68 | * @cfg {Object}
69 | *
70 | * Extra HTTP headers to be added to the HTTP request uploading the file.
71 | */
72 | uploadExtraHeaders : {},
73 |
74 | /**
75 | * @cfg {Number} [uploadTimeout=6000]
76 | *
77 | * The time after the upload request times out - in miliseconds.
78 | */
79 | uploadTimeout : 60000,
80 |
81 | // dialog strings
82 | textOk : 'OK',
83 | textClose : 'Close',
84 | textUpload : 'Upload',
85 | textBrowse : 'Browse',
86 | textAbort : 'Abort',
87 | textRemoveSelected : 'Remove selected',
88 | textRemoveAll : 'Remove all',
89 |
90 | // grid strings
91 | textFilename : 'Filename',
92 | textSize : 'Size',
93 | textType : 'Type',
94 | textStatus : 'Status',
95 | textProgress : '%',
96 |
97 | // status toolbar strings
98 | selectionMessageText : 'Selected {0} file(s), {1}',
99 | uploadMessageText : 'Upload progress {0}% ({1} of {2} souborů)',
100 |
101 | // browse button
102 | buttonText : 'Browse...'
103 | },
104 |
105 | /**
106 | * @property {Ext.ux.upload.Queue}
107 | */
108 | queue : null,
109 |
110 | /**
111 | * @property {Ext.ux.upload.ItemGridPanel}
112 | */
113 | grid : null,
114 |
115 | /**
116 | * @property {Ext.ux.upload.Manager}
117 | */
118 | uploadManager : null,
119 |
120 | /**
121 | * @property {Ext.ux.upload.StatusBar}
122 | */
123 | statusBar : null,
124 |
125 | /**
126 | * @property {Ext.ux.upload.BrowseButton}
127 | */
128 | browseButton : null,
129 |
130 | /**
131 | * @private
132 | */
133 | initComponent : function() {
134 |
135 | this.queue = this.initQueue();
136 |
137 | this.grid = Ext.create('Ext.ux.upload.ItemGridPanel', {
138 | queue : this.queue,
139 | textFilename : this.textFilename,
140 | textSize : this.textSize,
141 | textType : this.textType,
142 | textStatus : this.textStatus,
143 | textProgress : this.textProgress
144 | });
145 |
146 | this.uploadManager = Ext.create('Ext.ux.upload.Manager', {
147 | url : this.uploadUrl,
148 | synchronous : this.synchronous,
149 | params : this.uploadParams,
150 | extraHeaders : this.uploadExtraHeaders,
151 | uploadTimeout : this.uploadTimeout
152 | });
153 |
154 | this.uploadManager.on('uploadcomplete', this.onUploadComplete, this);
155 | this.uploadManager.on('itemuploadsuccess', this.onItemUploadSuccess, this);
156 | this.uploadManager.on('itemuploadfailure', this.onItemUploadFailure, this);
157 |
158 | this.statusBar = Ext.create('Ext.ux.upload.StatusBar', {
159 | dock : 'bottom',
160 | selectionMessageText : this.selectionMessageText,
161 | uploadMessageText : this.uploadMessageText
162 | });
163 |
164 | Ext.apply(this, {
165 | title : this.dialogTitle,
166 | autoScroll : true,
167 | layout : 'fit',
168 | uploading : false,
169 | items : [
170 | this.grid
171 | ],
172 | dockedItems : [
173 | this.getTopToolbarConfig(),
174 | {
175 | xtype : 'toolbar',
176 | dock : 'bottom',
177 | ui : 'footer',
178 | defaults : {
179 | minWidth : this.minButtonWidth
180 | },
181 | items : [
182 | '->',
183 | {
184 | text : this.textClose,
185 | // iconCls : 'ux-mu-icon-action-ok',
186 | cls : 'x-btn-text-icon',
187 | scope : this,
188 | handler : function() {
189 | this.close();
190 | }
191 | }
192 | ]
193 | },
194 | this.statusBar
195 | ]
196 | });
197 |
198 | this.on('afterrender', function() {
199 | this.stateInit();
200 | }, this);
201 |
202 | this.callParent(arguments);
203 | },
204 |
205 | /**
206 | * @private
207 | *
208 | * Returns the config object for the top toolbar.
209 | *
210 | * @return {Array}
211 | */
212 | getTopToolbarConfig : function() {
213 |
214 | this.browseButton = Ext.create('Ext.ux.upload.BrowseButton', {
215 | id : 'button_browse',
216 | buttonText : this.buttonText
217 | });
218 | this.browseButton.on('fileselected', this.onFileSelection, this);
219 |
220 | return {
221 | xtype : 'toolbar',
222 | dock : 'top',
223 | items : [
224 | this.browseButton,
225 | '-',
226 | {
227 | id : 'button_upload',
228 | text : this.textUpload,
229 | iconCls : 'ux-mu-icon-action-upload',
230 | scope : this,
231 | handler : this.onInitUpload
232 | },
233 | '-',
234 | {
235 | id : 'button_abort',
236 | text : this.textAbort,
237 | iconCls : 'ux-mu-icon-action-abort',
238 | scope : this,
239 | handler : this.onAbortUpload,
240 | disabled : true
241 | },
242 | '->',
243 | {
244 | id : 'button_remove_selected',
245 | text : this.textRemoveSelected,
246 | iconCls : 'ux-mu-icon-action-remove',
247 | scope : this,
248 | handler : this.onMultipleRemove
249 | },
250 | '-',
251 | {
252 | id : 'button_remove_all',
253 | text : this.textRemoveAll,
254 | iconCls : 'ux-mu-icon-action-remove',
255 | scope : this,
256 | handler : this.onRemoveAll
257 | }
258 | ]
259 | }
260 | },
261 |
262 | /**
263 | * @private
264 | *
265 | * Initializes and returns the queue object.
266 | *
267 | * @return {Ext.ux.upload.Queue}
268 | */
269 | initQueue : function() {
270 | var queue = Ext.create('Ext.ux.upload.Queue');
271 |
272 | queue.on('queuechange', this.onQueueChange, this);
273 |
274 | return queue;
275 | },
276 |
277 | onInitUpload : function() {
278 | if (!this.queue.getCount()) {
279 | return;
280 | }
281 |
282 | this.stateUpload();
283 | this.startUpload();
284 | },
285 |
286 | onAbortUpload : function() {
287 | this.uploadManager.abortUpload();
288 | this.finishUpload();
289 | this.switchState();
290 | },
291 |
292 | onUploadComplete : function(manager, queue, errorCount) {
293 | this.finishUpload();
294 | this.stateInit();
295 | this.fireEvent('uploadcomplete', this, manager, queue.getUploadedItems(), errorCount);
296 | },
297 |
298 | /**
299 | * @private
300 | *
301 | * Executes after files has been selected for upload through the "Browse" button. Updates the upload queue with the
302 | * new files.
303 | *
304 | * @param {Ext.ux.upload.BrowseButton} input
305 | * @param {FileList} files
306 | */
307 | onFileSelection : function(input, files) {
308 | this.queue.clearUploadedItems();
309 | this.queue.addFiles(files);
310 | this.browseButton.reset();
311 | },
312 |
313 | /**
314 | * @private
315 | *
316 | * Executes if there is a change in the queue. Updates the related components (grid, toolbar).
317 | *
318 | * @param {Ext.ux.upload.Queue} queue
319 | */
320 | onQueueChange : function(queue) {
321 | this.updateStatusBar();
322 |
323 | this.switchState();
324 | },
325 |
326 | /**
327 | * @private
328 | *
329 | * Executes upon hitting the "multiple remove" button. Removes all selected items from the queue.
330 | */
331 | onMultipleRemove : function() {
332 | var records = this.grid.getSelectedRecords();
333 | if (!records.length) {
334 | return;
335 | }
336 |
337 | var keys = [];
338 | var i;
339 | var num = records.length;
340 |
341 | for (i = 0; i < num; i++) {
342 | keys.push(records[i].get('filename'));
343 | }
344 |
345 | this.queue.removeItemsByKey(keys);
346 | },
347 |
348 | onRemoveAll : function() {
349 | this.queue.clearItems();
350 | },
351 |
352 | onItemUploadSuccess : function(item, info) {
353 |
354 | },
355 |
356 | onItemUploadFailure : function(item, info) {
357 |
358 | },
359 |
360 | startUpload : function() {
361 | this.uploading = true;
362 | this.uploadManager.uploadQueue(this.queue);
363 | },
364 |
365 | finishUpload : function() {
366 | this.uploading = false;
367 | },
368 |
369 | isUploadActive : function() {
370 | return this.uploading;
371 | },
372 |
373 | updateStatusBar : function() {
374 | if (!this.statusBar) {
375 | return;
376 | }
377 |
378 | var numFiles = this.queue.getCount();
379 |
380 | this.statusBar.setSelectionMessage(this.queue.getCount(), this.queue.getTotalBytes());
381 | },
382 |
383 | getButton : function(id) {
384 | return Ext.ComponentMgr.get(id);
385 | },
386 |
387 | switchButtons : function(info) {
388 | var id;
389 | for (id in info) {
390 | this.switchButton(id, info[id]);
391 | }
392 | },
393 |
394 | switchButton : function(id, on) {
395 | var button = this.getButton(id);
396 |
397 | if (button) {
398 | if (on) {
399 | button.enable();
400 | } else {
401 | button.disable();
402 | }
403 | }
404 | },
405 |
406 | switchState : function() {
407 | if (this.uploading) {
408 | this.stateUpload();
409 | } else if (this.queue.getCount()) {
410 | this.stateQueue();
411 | } else {
412 | this.stateInit();
413 | }
414 | },
415 |
416 | stateInit : function() {
417 | this.switchButtons({
418 | 'button_browse' : 1,
419 | 'button_upload' : 0,
420 | 'button_abort' : 0,
421 | 'button_remove_all' : 1,
422 | 'button_remove_selected' : 1
423 | });
424 | },
425 |
426 | stateQueue : function() {
427 | this.switchButtons({
428 | 'button_browse' : 1,
429 | 'button_upload' : 1,
430 | 'button_abort' : 0,
431 | 'button_remove_all' : 1,
432 | 'button_remove_selected' : 1
433 | });
434 | },
435 |
436 | stateUpload : function() {
437 | this.switchButtons({
438 | 'button_browse' : 0,
439 | 'button_upload' : 0,
440 | 'button_abort' : 1,
441 | 'button_remove_all' : 1,
442 | 'button_remove_selected' : 1
443 | });
444 | }
445 |
446 | });
--------------------------------------------------------------------------------
/lib/upload/Manager.js:
--------------------------------------------------------------------------------
1 | /**
2 | * The object is responsible for uploading the queue.
3 | *
4 | */
5 | Ext.define('Ext.ux.upload.Manager', {
6 | mixins : {
7 | observable : 'Ext.util.Observable'
8 | },
9 |
10 | requires : [
11 | 'Ext.ux.upload.uploader.AbstractUploader'
12 | ],
13 |
14 | uploader : null,
15 | uploaderOptions : null,
16 | synchronous : true,
17 | filenameEncoder : null,
18 |
19 | DEFAULT_UPLOADER_CLASS : 'Ext.ux.upload.uploader.ExtJsUploader',
20 |
21 | constructor : function(config) {
22 | this.mixins.observable.constructor.call(this);
23 |
24 | this.initConfig(config);
25 |
26 | if (!(this.uploader instanceof Ext.ux.upload.uploader.AbstractUploader)) {
27 | var uploaderClass = this.DEFAULT_UPLOADER_CLASS;
28 | if (Ext.isString(this.uploader)) {
29 | uploaderClass = this.uploader;
30 | }
31 |
32 | var uploaderOptions = this.uploaderOptions || {};
33 | Ext.applyIf(uploaderOptions, {
34 | success : this.onUploadSuccess,
35 | failure : this.onUploadFailure,
36 | progress : this.onUploadProgress,
37 | filenameEncoder : this.filenameEncoder
38 | });
39 |
40 | this.uploader = Ext.create(uploaderClass, uploaderOptions);
41 | }
42 |
43 | this.mon(this.uploader, 'uploadsuccess', this.onUploadSuccess, this);
44 | this.mon(this.uploader, 'uploadfailure', this.onUploadFailure, this);
45 | this.mon(this.uploader, 'uploadprogress', this.onUploadProgress, this);
46 |
47 | Ext.apply(this, {
48 | syncQueue : null,
49 | currentQueue : null,
50 | uploadActive : false,
51 | errorCount : 0
52 | });
53 | },
54 |
55 | uploadQueue : function(queue) {
56 | if (this.uploadActive) {
57 | return;
58 | }
59 |
60 | this.startUpload(queue);
61 |
62 | if (this.synchronous) {
63 | this.uploadQueueSync(queue);
64 | return;
65 | }
66 |
67 | this.uploadQueueAsync(queue);
68 |
69 | },
70 |
71 | uploadQueueSync : function(queue) {
72 | this.uploadNextItemSync();
73 | },
74 |
75 | uploadNextItemSync : function() {
76 | if (!this.uploadActive) {
77 | return;
78 | }
79 |
80 | var item = this.currentQueue.getFirstReadyItem();
81 | if (!item) {
82 | return;
83 | }
84 |
85 | this.uploader.uploadItem(item);
86 | },
87 |
88 | uploadQueueAsync : function(queue) {
89 | var i;
90 | var num = queue.getCount();
91 |
92 | for (i = 0; i < num; i++) {
93 | this.uploader.uploadItem(queue.getAt(i));
94 | }
95 | },
96 |
97 | startUpload : function(queue) {
98 | queue.reset();
99 |
100 | this.uploadActive = true;
101 | this.currentQueue = queue;
102 | this.fireEvent('beforeupload', this, queue);
103 | },
104 |
105 | finishUpload : function() {
106 | this.fireEvent('uploadcomplete', this, this.currentQueue, this.errorCount);
107 | },
108 |
109 | resetUpload : function() {
110 | this.currentQueue = null;
111 | this.uploadActive = false;
112 | this.errorCount = 0;
113 | },
114 |
115 | abortUpload : function() {
116 | this.uploader.abortUpload();
117 | this.currentQueue.recoverAfterAbort();
118 | this.resetUpload();
119 |
120 | this.fireEvent('abortupload', this, this.currentQueue);
121 | },
122 |
123 | afterItemUpload : function(item, info) {
124 | if (this.synchronous) {
125 | this.uploadNextItemSync();
126 | }
127 |
128 | if (!this.currentQueue.existUploadingItems()) {
129 | this.finishUpload();
130 | }
131 | },
132 |
133 | onUploadSuccess : function(item, info) {
134 | item.setUploaded();
135 |
136 | this.fireEvent('itemuploadsuccess', this, item, info);
137 |
138 | this.afterItemUpload(item, info);
139 | },
140 |
141 | onUploadFailure : function(item, info) {
142 | item.setUploadError(info.message);
143 |
144 | this.fireEvent('itemuploadfailure', this, item, info);
145 | this.errorCount++;
146 |
147 | this.afterItemUpload(item, info);
148 | },
149 |
150 | onUploadProgress : function(item, event) {
151 | item.setProgress(event.loaded);
152 | }
153 | });
154 |
--------------------------------------------------------------------------------
/lib/upload/Panel.js:
--------------------------------------------------------------------------------
1 | /**
2 | * The main upload panel, which ties all the functionality together.
3 | *
4 | * In the most basic case you need just to set the upload URL:
5 | *
6 | * @example
7 | * var uploadPanel = Ext.create('Ext.ux.upload.Panel', {
8 | * uploaderOptions: {
9 | * url: '/api/upload'
10 | * }
11 | * });
12 | *
13 | * It uses the default ExtJsUploader to perform the actual upload. If you want to use another uploade, for
14 | * example the FormDataUploader, you can pass the name of the class:
15 | *
16 | * @example
17 | * var uploadPanel = Ext.create('Ext.ux.upload.Panel', {
18 | * uploader: 'Ext.ux.upload.uploader.FormDataUploader',
19 | * uploaderOptions: {
20 | * url: '/api/upload',
21 | * timeout: 120*1000
22 | * }
23 | * });
24 | *
25 | * Or event an instance of the uploader:
26 | *
27 | * @example
28 | * var formDataUploader = Ext.create('Ext.ux.upload.uploader.FormDataUploader', {
29 | * url: '/api/upload'
30 | * });
31 | *
32 | * var uploadPanel = Ext.create('Ext.ux.upload.Panel', {
33 | * uploader: formDataUploader
34 | * });
35 | *
36 | */
37 | Ext.define('Ext.ux.upload.Panel', {
38 | extend : 'Ext.panel.Panel',
39 |
40 | requires : [
41 | 'Ext.ux.upload.ItemGridPanel',
42 | 'Ext.ux.upload.Manager',
43 | 'Ext.ux.upload.StatusBar',
44 | 'Ext.ux.upload.BrowseButton',
45 | 'Ext.ux.upload.Queue'
46 | ],
47 |
48 | config : {
49 |
50 | /**
51 | * @cfg {Object/String}
52 | *
53 | * The name of the uploader class or the uploader object itself. If not set, the default uploader will
54 | * be used.
55 | */
56 | uploader : null,
57 |
58 | /**
59 | * @cfg {Object}
60 | *
61 | * Configuration object for the uploader. Configuration options included in this object override the
62 | * options 'uploadUrl', 'uploadParams', 'uploadExtraHeaders', 'uploadTimeout'.
63 | */
64 | uploaderOptions : null,
65 |
66 | /**
67 | * @cfg {boolean} [synchronous=false]
68 | *
69 | * If true, all files are uploaded in a sequence, otherwise files are uploaded simultaneously (asynchronously).
70 | */
71 | synchronous : true,
72 |
73 | /**
74 | * @cfg {String} uploadUrl
75 | *
76 | * The URL to upload files to. Not required if configured uploader instance is passed to this panel.
77 | */
78 | uploadUrl : '',
79 |
80 | /**
81 | * @cfg {Object}
82 | *
83 | * Params passed to the uploader object and sent along with the request. It depends on the implementation of the
84 | * uploader object, for example if the {@link Ext.ux.upload.uploader.ExtJsUploader} is used, the params are sent
85 | * as GET params.
86 | */
87 | uploadParams : {},
88 |
89 | /**
90 | * @cfg {Object}
91 | *
92 | * Extra HTTP headers to be added to the HTTP request uploading the file.
93 | */
94 | uploadExtraHeaders : {},
95 |
96 | /**
97 | * @cfg {Number} [uploadTimeout=6000]
98 | *
99 | * The time after the upload request times out - in miliseconds.
100 | */
101 | uploadTimeout : 60000,
102 |
103 | /**
104 | * @cfg {Object/String}
105 | *
106 | * Encoder object/class used to encode the filename header. Usually used, when the filename
107 | * contains non-ASCII characters. If an encoder is used, the server backend has to be
108 | * modified accordingly to decode the value.
109 | */
110 | filenameEncoder : null,
111 |
112 | // strings
113 | textOk : 'OK',
114 | textUpload : 'Upload',
115 | textBrowse : 'Browse',
116 | textAbort : 'Abort',
117 | textRemoveSelected : 'Remove selected',
118 | textRemoveAll : 'Remove all',
119 |
120 | // grid strings
121 | textFilename : 'Filename',
122 | textSize : 'Size',
123 | textType : 'Type',
124 | textStatus : 'Status',
125 | textProgress : '%',
126 |
127 | // status toolbar strings
128 | selectionMessageText : 'Selected {0} file(s), {1}',
129 | uploadMessageText : 'Upload progress {0}% ({1} of {2} souborů)',
130 |
131 | // browse button
132 | buttonText : 'Browse...'
133 | },
134 |
135 | /**
136 | * @property {Ext.ux.upload.Queue}
137 | * @private
138 | */
139 | queue : null,
140 |
141 | /**
142 | * @property {Ext.ux.upload.ItemGridPanel}
143 | * @private
144 | */
145 | grid : null,
146 |
147 | /**
148 | * @property {Ext.ux.upload.Manager}
149 | * @private
150 | */
151 | uploadManager : null,
152 |
153 | /**
154 | * @property {Ext.ux.upload.StatusBar}
155 | * @private
156 | */
157 | statusBar : null,
158 |
159 | /**
160 | * @property {Ext.ux.upload.BrowseButton}
161 | * @private
162 | */
163 | browseButton : null,
164 |
165 | /**
166 | * @private
167 | */
168 | initComponent : function() {
169 |
170 | this.queue = this.initQueue();
171 |
172 | this.grid = Ext.create('Ext.ux.upload.ItemGridPanel', {
173 | queue : this.queue,
174 | textFilename : this.textFilename,
175 | textSize : this.textSize,
176 | textType : this.textType,
177 | textStatus : this.textStatus,
178 | textProgress : this.textProgress
179 | });
180 |
181 | this.uploadManager = this.createUploadManager();
182 |
183 | this.uploadManager.on('uploadcomplete', this.onUploadComplete, this);
184 | this.uploadManager.on('itemuploadsuccess', this.onItemUploadSuccess, this);
185 | this.uploadManager.on('itemuploadfailure', this.onItemUploadFailure, this);
186 |
187 | this.statusBar = Ext.create('Ext.ux.upload.StatusBar', {
188 | dock : 'bottom',
189 | selectionMessageText : this.selectionMessageText,
190 | uploadMessageText : this.uploadMessageText
191 | });
192 |
193 | Ext.apply(this, {
194 | title : this.dialogTitle,
195 | autoScroll : true,
196 | layout : 'fit',
197 | uploading : false,
198 | items : [
199 | this.grid
200 | ],
201 | dockedItems : [
202 | this.getTopToolbarConfig(), this.statusBar
203 | ]
204 | });
205 |
206 | this.on('afterrender', function() {
207 | this.stateInit();
208 | }, this);
209 |
210 | this.callParent(arguments);
211 | },
212 |
213 | createUploadManager : function() {
214 | var uploaderOptions = this.getUploaderOptions() || {};
215 |
216 | Ext.applyIf(uploaderOptions, {
217 | url : this.uploadUrl,
218 | params : this.uploadParams,
219 | extraHeaders : this.uploadExtraHeaders,
220 | timeout : this.uploadTimeout
221 | });
222 |
223 | var uploadManager = Ext.create('Ext.ux.upload.Manager', {
224 | uploader : this.uploader,
225 | uploaderOptions : uploaderOptions,
226 | synchronous : this.getSynchronous(),
227 | filenameEncoder : this.getFilenameEncoder()
228 | });
229 |
230 | return uploadManager;
231 | },
232 |
233 | /**
234 | * @private
235 | *
236 | * Returns the config object for the top toolbar.
237 | *
238 | * @return {Array}
239 | */
240 | getTopToolbarConfig : function() {
241 |
242 | this.browseButton = Ext.create('Ext.ux.upload.BrowseButton', {
243 | itemId : 'button_browse',
244 | buttonText : this.buttonText
245 | });
246 | this.browseButton.on('fileselected', this.onFileSelection, this);
247 |
248 | return {
249 | xtype : 'toolbar',
250 | itemId : 'topToolbar',
251 | dock : 'top',
252 | items : [
253 | this.browseButton,
254 | '-',
255 | {
256 | itemId : 'button_upload',
257 | text : this.textUpload,
258 | iconCls : 'ux-mu-icon-action-upload',
259 | scope : this,
260 | handler : this.onInitUpload
261 | },
262 | '-',
263 | {
264 | itemId : 'button_abort',
265 | text : this.textAbort,
266 | iconCls : 'ux-mu-icon-action-abort',
267 | scope : this,
268 | handler : this.onAbortUpload,
269 | disabled : true
270 | },
271 | '->',
272 | {
273 | itemId : 'button_remove_selected',
274 | text : this.textRemoveSelected,
275 | iconCls : 'ux-mu-icon-action-remove',
276 | scope : this,
277 | handler : this.onMultipleRemove
278 | },
279 | '-',
280 | {
281 | itemId : 'button_remove_all',
282 | text : this.textRemoveAll,
283 | iconCls : 'ux-mu-icon-action-remove',
284 | scope : this,
285 | handler : this.onRemoveAll
286 | }
287 | ]
288 | }
289 | },
290 |
291 | /**
292 | * @private
293 | *
294 | * Initializes and returns the queue object.
295 | *
296 | * @return {Ext.ux.upload.Queue}
297 | */
298 | initQueue : function() {
299 | var queue = Ext.create('Ext.ux.upload.Queue');
300 |
301 | queue.on('queuechange', this.onQueueChange, this);
302 |
303 | return queue;
304 | },
305 |
306 | onInitUpload : function() {
307 | if (!this.queue.getCount()) {
308 | return;
309 | }
310 |
311 | this.stateUpload();
312 | this.startUpload();
313 | },
314 |
315 | onAbortUpload : function() {
316 | this.uploadManager.abortUpload();
317 | this.finishUpload();
318 | this.switchState();
319 | },
320 |
321 | onUploadComplete : function(manager, queue, errorCount) {
322 | this.finishUpload();
323 | if (errorCount) {
324 | this.stateQueue();
325 | } else {
326 | this.stateInit();
327 | }
328 | this.fireEvent('uploadcomplete', this, manager, queue.getUploadedItems(), errorCount);
329 | manager.resetUpload();
330 | },
331 |
332 | /**
333 | * @private
334 | *
335 | * Executes after files has been selected for upload through the "Browse" button. Updates the upload queue with the
336 | * new files.
337 | *
338 | * @param {Ext.ux.upload.BrowseButton} input
339 | * @param {FileList} files
340 | */
341 | onFileSelection : function(input, files) {
342 | this.queue.clearUploadedItems();
343 | this.queue.addFiles(files);
344 | this.browseButton.reset();
345 | },
346 |
347 | /**
348 | * @private
349 | *
350 | * Executes if there is a change in the queue. Updates the related components (grid, toolbar).
351 | *
352 | * @param {Ext.ux.upload.Queue} queue
353 | */
354 | onQueueChange : function(queue) {
355 | this.updateStatusBar();
356 |
357 | this.switchState();
358 | },
359 |
360 | /**
361 | * @private
362 | *
363 | * Executes upon hitting the "multiple remove" button. Removes all selected items from the queue.
364 | */
365 | onMultipleRemove : function() {
366 | var records = this.grid.getSelectedRecords();
367 | if (!records.length) {
368 | return;
369 | }
370 |
371 | var keys = [];
372 | var i;
373 | var num = records.length;
374 |
375 | for (i = 0; i < num; i++) {
376 | keys.push(records[i].get('filename'));
377 | }
378 |
379 | this.queue.removeItemsByKey(keys);
380 | },
381 |
382 | onRemoveAll : function() {
383 | this.queue.clearItems();
384 | },
385 |
386 | onItemUploadSuccess : function(manager, item, info) {
387 |
388 | },
389 |
390 | onItemUploadFailure : function(manager, item, info) {
391 |
392 | },
393 |
394 | startUpload : function() {
395 | this.uploading = true;
396 | this.uploadManager.uploadQueue(this.queue);
397 | },
398 |
399 | finishUpload : function() {
400 | this.uploading = false;
401 | },
402 |
403 | isUploadActive : function() {
404 | return this.uploading;
405 | },
406 |
407 | updateStatusBar : function() {
408 | if (!this.statusBar) {
409 | return;
410 | }
411 |
412 | var numFiles = this.queue.getCount();
413 |
414 | this.statusBar.setSelectionMessage(this.queue.getCount(), this.queue.getTotalBytes());
415 | },
416 |
417 | getButton : function(itemId) {
418 | var topToolbar = this.getDockedComponent('topToolbar');
419 | if (topToolbar) {
420 | return topToolbar.getComponent(itemId);
421 | }
422 | return null;
423 | },
424 |
425 | switchButtons : function(info) {
426 | var itemId;
427 | for (itemId in info) {
428 | this.switchButton(itemId, info[itemId]);
429 | }
430 | },
431 |
432 | switchButton : function(itemId, on) {
433 | var button = this.getButton(itemId);
434 |
435 | if (button) {
436 | if (on) {
437 | button.enable();
438 | } else {
439 | button.disable();
440 | }
441 | }
442 | },
443 |
444 | switchState : function() {
445 | if (this.uploading) {
446 | this.stateUpload();
447 | } else if (this.queue.getCount()) {
448 | this.stateQueue();
449 | } else {
450 | this.stateInit();
451 | }
452 | },
453 |
454 | stateInit : function() {
455 | this.switchButtons({
456 | 'button_browse' : 1,
457 | 'button_upload' : 0,
458 | 'button_abort' : 0,
459 | 'button_remove_all' : 1,
460 | 'button_remove_selected' : 1
461 | });
462 | },
463 |
464 | stateQueue : function() {
465 | this.switchButtons({
466 | 'button_browse' : 1,
467 | 'button_upload' : 1,
468 | 'button_abort' : 0,
469 | 'button_remove_all' : 1,
470 | 'button_remove_selected' : 1
471 | });
472 | },
473 |
474 | stateUpload : function() {
475 | this.switchButtons({
476 | 'button_browse' : 0,
477 | 'button_upload' : 0,
478 | 'button_abort' : 1,
479 | 'button_remove_all' : 1,
480 | 'button_remove_selected' : 1
481 | });
482 | }
483 |
484 | });
485 |
--------------------------------------------------------------------------------
/lib/upload/Queue.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Data structure managing the upload file queue.
3 | *
4 | */
5 | Ext.define('Ext.ux.upload.Queue', {
6 | extend : 'Ext.util.MixedCollection',
7 |
8 | requires : [
9 | 'Ext.ux.upload.Item'
10 | ],
11 |
12 | /**
13 | * Constructor.
14 | *
15 | * @param {Object} config
16 | */
17 | constructor : function(config) {
18 |
19 | this.callParent(arguments);
20 |
21 | this.on('clear', function() {
22 | this.fireEvent('queuechange', this);
23 | }, this);
24 |
25 | },
26 |
27 | /**
28 | * Adds files to the queue.
29 | *
30 | * @param {FileList} fileList
31 | */
32 | addFiles : function(fileList) {
33 | var i;
34 | var items = [];
35 | var num = fileList.length;
36 |
37 | if (!num) {
38 | return;
39 | }
40 |
41 | for (i = 0; i < num; i++) {
42 | items.push(this.createItem(fileList[i]));
43 | }
44 |
45 | this.addAll(items);
46 |
47 | this.fireEvent('multiadd', this, items);
48 | this.fireEvent('queuechange', this);
49 | },
50 |
51 | /**
52 | * Uploaded files are removed, the rest are set as ready.
53 | */
54 | reset : function() {
55 | this.clearUploadedItems();
56 |
57 | this.each(function(item) {
58 | item.reset();
59 | }, this);
60 | },
61 |
62 | /**
63 | * Returns all queued items.
64 | *
65 | * @return {Ext.ux.upload.Item[]}
66 | */
67 | getItems : function() {
68 | return this.getRange();
69 | },
70 |
71 | /**
72 | * Returns an array of items by the specified status.
73 | *
74 | * @param {String/Array}
75 | * @return {Ext.ux.upload.Item[]}
76 | */
77 | getItemsByStatus : function(status) {
78 | var itemsByStatus = [];
79 |
80 | this.each(function(item, index, items) {
81 | if (item.hasStatus(status)) {
82 | itemsByStatus.push(item);
83 | }
84 | });
85 |
86 | return itemsByStatus;
87 | },
88 |
89 | /**
90 | * Returns an array of items, that have already been uploaded.
91 | *
92 | * @return {Ext.ux.upload.Item[]}
93 | */
94 | getUploadedItems : function() {
95 | return this.getItemsByStatus('uploaded');
96 | },
97 |
98 | /**
99 | * Returns an array of items, that have not been uploaded yet.
100 | *
101 | * @return {Ext.ux.upload.Item[]}
102 | */
103 | getUploadingItems : function() {
104 | return this.getItemsByStatus([
105 | 'ready', 'uploading'
106 | ]);
107 | },
108 |
109 | /**
110 | * Returns true, if there are items, that are currently being uploaded.
111 | *
112 | * @return {Boolean}
113 | */
114 | existUploadingItems : function() {
115 | return (this.getUploadingItems().length > 0);
116 | },
117 |
118 | /**
119 | * Returns the first "ready" item in the queue (with status STATUS_READY).
120 | *
121 | * @return {Ext.ux.upload.Item/null}
122 | */
123 | getFirstReadyItem : function() {
124 | var items = this.getRange();
125 | var num = this.getCount();
126 | var i;
127 |
128 | for (i = 0; i < num; i++) {
129 | if (items[i].isReady()) {
130 | return items[i];
131 | }
132 | }
133 |
134 | return null;
135 | },
136 |
137 | /**
138 | * Clears all items from the queue.
139 | */
140 | clearItems : function() {
141 | this.clear();
142 | },
143 |
144 | /**
145 | * Removes the items, which have been already uploaded, from the queue.
146 | */
147 | clearUploadedItems : function() {
148 | this.removeItems(this.getUploadedItems());
149 | },
150 |
151 | /**
152 | * Removes items from the queue.
153 | *
154 | * @param {Ext.ux.upload.Item[]} items
155 | */
156 | removeItems : function(items) {
157 | var num = items.length;
158 | var i;
159 |
160 | if (!num) {
161 | return;
162 | }
163 |
164 | for (i = 0; i < num; i++) {
165 | this.remove(items[i]);
166 | }
167 |
168 | this.fireEvent('queuechange', this);
169 | },
170 |
171 | /**
172 | * Removes the items identified by the supplied array of keys.
173 | *
174 | * @param {Array} itemKeys
175 | */
176 | removeItemsByKey : function(itemKeys) {
177 | var i;
178 | var num = itemKeys.length;
179 |
180 | if (!num) {
181 | return;
182 | }
183 |
184 | for (i = 0; i < num; i++) {
185 | this.removeItemByKey(itemKeys[i]);
186 | }
187 |
188 | this.fireEvent('multiremove', this, itemKeys);
189 | this.fireEvent('queuechange', this);
190 | },
191 |
192 | /**
193 | * Removes a single item by its key.
194 | *
195 | * @param {String} key
196 | */
197 | removeItemByKey : function(key) {
198 | this.removeAtKey(key);
199 | },
200 |
201 | /**
202 | * Perform cleanup, after the upload has been aborted.
203 | */
204 | recoverAfterAbort : function() {
205 | this.each(function(item) {
206 | if (!item.isUploaded() && !item.isReady()) {
207 | item.reset();
208 | }
209 | });
210 | },
211 |
212 | /**
213 | * @private
214 | *
215 | * Initialize and return a new queue item for the corresponding File object.
216 | *
217 | * @param {File} file
218 | * @return {Ext.ux.upload.Item}
219 | */
220 | createItem : function(file) {
221 |
222 | var item = Ext.create('Ext.ux.upload.Item', {
223 | fileApiObject : file
224 | });
225 |
226 | item.on('changestatus', this.onItemChangeStatus, this);
227 | item.on('progressupdate', this.onItemProgressUpdate, this);
228 |
229 | return item;
230 | },
231 |
232 | /**
233 | * A getKey() implementation to determine the key of an item in the collection.
234 | *
235 | * @param {Ext.ux.upload.Item} item
236 | * @return {String}
237 | */
238 | getKey : function(item) {
239 | return item.getId();
240 | },
241 |
242 | onItemChangeStatus : function(item, status) {
243 | this.fireEvent('itemchangestatus', this, item, status);
244 | },
245 |
246 | onItemProgressUpdate : function(item) {
247 | this.fireEvent('itemprogressupdate', this, item);
248 | },
249 |
250 | /**
251 | * Returns true, if the item is the last item in the queue.
252 | *
253 | * @param {Ext.ux.upload.Item} item
254 | * @return {boolean}
255 | */
256 | isLast : function(item) {
257 | var lastItem = this.last();
258 | if (lastItem && item.getId() == lastItem.getId()) {
259 | return true;
260 | }
261 |
262 | return false;
263 | },
264 |
265 | /**
266 | * Returns total bytes of all files in the queue.
267 | *
268 | * @return {number}
269 | */
270 | getTotalBytes : function() {
271 | var bytes = 0;
272 |
273 | this.each(function(item, index, length) {
274 | bytes += item.getSize();
275 | }, this);
276 |
277 | return bytes;
278 | }
279 | });
--------------------------------------------------------------------------------
/lib/upload/StatusBar.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Upload status bar.
3 | *
4 | * @class Ext.ux.upload.StatusBar
5 | * @extends Ext.toolbar.Toolbar
6 | */
7 | Ext.define('Ext.ux.upload.StatusBar', {
8 | extend : 'Ext.toolbar.Toolbar',
9 |
10 | config : {
11 | selectionMessageText : 'Selected {0} file(s), {1}',
12 | uploadMessageText : 'Upload progress {0}% ({1} of {2} file(s))',
13 | textComponentId : 'mu-status-text'
14 | },
15 |
16 | initComponent : function() {
17 |
18 | Ext.apply(this, {
19 | items : [
20 | {
21 | xtype : 'tbtext',
22 | itemId : this.textComponentId,
23 | text : ' '
24 | }
25 | ]
26 | });
27 |
28 | this.callParent(arguments);
29 | },
30 |
31 | setText : function(text) {
32 | this.getComponent(this.textComponentId).setText(text);
33 | },
34 |
35 | setSelectionMessage : function(fileCount, byteCount) {
36 | this.setText(Ext.String.format(this.selectionMessageText, fileCount, Ext.util.Format.fileSize(byteCount)));
37 | },
38 |
39 | setUploadMessage : function(progressPercent, uploadedFiles, totalFiles) {
40 | this.setText(Ext.String.format(this.uploadMessageText, progressPercent, uploadedFiles, totalFiles));
41 | }
42 |
43 | });
--------------------------------------------------------------------------------
/lib/upload/Store.js:
--------------------------------------------------------------------------------
1 | Ext.define('Ext.ux.upload.Store', {
2 | extend : 'Ext.data.Store',
3 |
4 | fields : [
5 | {
6 | name : 'filename',
7 | type : 'string'
8 | }, {
9 | name : 'size',
10 | type : 'integer'
11 | }, {
12 | name : 'type',
13 | type : 'string'
14 | }, {
15 | name : 'status',
16 | type : 'string'
17 | }, {
18 | name : 'message',
19 | type : 'string'
20 | }
21 | ],
22 |
23 | proxy : {
24 | type : 'memory',
25 | reader : {
26 | type : 'array',
27 | idProperty : 'filename'
28 | }
29 | }
30 | });
--------------------------------------------------------------------------------
/lib/upload/css/upload.css:
--------------------------------------------------------------------------------
1 | @CHARSET "UTF-8";
2 |
3 | .ux-mu-status-value {
4 | float: right;
5 | min-width: 16px;
6 | height: 16px;
7 | background-repeat: no-repeat;
8 | margin: 0 3px 0 2px;
9 | cursor: pointer;
10 | overflow: hidden;
11 | }
12 |
13 | .icon-help {
14 | background-image: url(../img/help.png) !important;
15 | }
16 |
17 | /*
18 | * Action icons
19 | */
20 | .ux-mu-icon-action-ok {
21 | background-image: url(../img/tick.png) !important;
22 | }
23 |
24 | .ux-mu-icon-action-upload {
25 | background-image: url(../img/arrow_up.png) !important;
26 | }
27 |
28 | .ux-mu-icon-action-abort {
29 | background-image: url(../img/cancel.png) !important;
30 | }
31 |
32 | .ux-mu-icon-action-remove {
33 | background-image: url(../img/delete.png) !important;
34 | }
35 |
36 | .ux-mu-icon-action-browse {
37 | background-image: url(../img/folder.png) !important;
38 | }
39 |
40 | /*
41 | * Upload status icons
42 | */
43 | .ux-mu-icon-upload-ready { /*
44 | background-image: url(../img/ready.png) !important;
45 | */
46 |
47 | }
48 |
49 | .ux-mu-icon-upload-uploading {
50 | background-image: url(../img/loading.gif) !important;
51 | }
52 |
53 | .ux-mu-icon-upload-uploaded {
54 | background-image: url(../img/accept.png) !important;
55 | }
56 |
57 | .ux-mu-icon-upload-uploaderror {
58 | background-image: url(../img/exclamation.png) !important;
59 | }
--------------------------------------------------------------------------------
/lib/upload/data/Connection.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Modified Ext.data.Connection object, adapted to be able to report progress.
3 | */
4 | Ext.define('Ext.ux.upload.data.Connection', {
5 | extend : 'Ext.data.Connection',
6 |
7 | /**
8 | * @cfg {Function}
9 | *
10 | * Callback fired when a progress event occurs (xhr.upload.onprogress).
11 | */
12 | progressCallback : null,
13 |
14 | request : function(options) {
15 | var progressCallback = options.progress;
16 | if (progressCallback) {
17 | this.progressCallback = progressCallback;
18 | }
19 |
20 | this.callParent(arguments);
21 | },
22 |
23 | getXhrInstance : function() {
24 | var xhr = this.callParent(arguments);
25 |
26 | if (this.progressCallback) {
27 | xhr.upload.onprogress = this.progressCallback;
28 | }
29 |
30 | return xhr;
31 | }
32 | });
--------------------------------------------------------------------------------
/lib/upload/header/AbstractFilenameEncoder.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Abstract filename encoder.
3 | */
4 | Ext.define('Ext.ux.upload.header.AbstractFilenameEncoder', {
5 |
6 | config : {},
7 |
8 | type : 'generic',
9 |
10 | encode : function(filename) {},
11 |
12 | getType : function() {
13 | return this.type;
14 | }
15 | });
16 |
--------------------------------------------------------------------------------
/lib/upload/header/Base64FilenameEncoder.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Base64 filename encoder - uses the built-in function window.btoa().
3 | * @see https://developer.mozilla.org/en-US/docs/Web/API/Window.btoa
4 | */
5 | Ext.define('Ext.ux.upload.header.Base64FilenameEncoder', {
6 | extend : 'Ext.ux.upload.header.AbstractFilenameEncoder',
7 |
8 | config : {},
9 |
10 | type : 'base64',
11 |
12 | encode : function(filename) {
13 | return window.btoa(unescape(encodeURIComponent(filename)));
14 | }
15 | });
16 |
--------------------------------------------------------------------------------
/lib/upload/img/accept.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ivan-novakov/extjs-upload-widget/70c49c62c17a08015158e8da939ab37bb9146ce5/lib/upload/img/accept.png
--------------------------------------------------------------------------------
/lib/upload/img/ajax-loader1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ivan-novakov/extjs-upload-widget/70c49c62c17a08015158e8da939ab37bb9146ce5/lib/upload/img/ajax-loader1.gif
--------------------------------------------------------------------------------
/lib/upload/img/ajax-loader2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ivan-novakov/extjs-upload-widget/70c49c62c17a08015158e8da939ab37bb9146ce5/lib/upload/img/ajax-loader2.gif
--------------------------------------------------------------------------------
/lib/upload/img/ajax-loader3.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ivan-novakov/extjs-upload-widget/70c49c62c17a08015158e8da939ab37bb9146ce5/lib/upload/img/ajax-loader3.gif
--------------------------------------------------------------------------------
/lib/upload/img/arrow_up.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ivan-novakov/extjs-upload-widget/70c49c62c17a08015158e8da939ab37bb9146ce5/lib/upload/img/arrow_up.png
--------------------------------------------------------------------------------
/lib/upload/img/cancel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ivan-novakov/extjs-upload-widget/70c49c62c17a08015158e8da939ab37bb9146ce5/lib/upload/img/cancel.png
--------------------------------------------------------------------------------
/lib/upload/img/delete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ivan-novakov/extjs-upload-widget/70c49c62c17a08015158e8da939ab37bb9146ce5/lib/upload/img/delete.png
--------------------------------------------------------------------------------
/lib/upload/img/exclamation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ivan-novakov/extjs-upload-widget/70c49c62c17a08015158e8da939ab37bb9146ce5/lib/upload/img/exclamation.png
--------------------------------------------------------------------------------
/lib/upload/img/folder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ivan-novakov/extjs-upload-widget/70c49c62c17a08015158e8da939ab37bb9146ce5/lib/upload/img/folder.png
--------------------------------------------------------------------------------
/lib/upload/img/help.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ivan-novakov/extjs-upload-widget/70c49c62c17a08015158e8da939ab37bb9146ce5/lib/upload/img/help.png
--------------------------------------------------------------------------------
/lib/upload/img/loading.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ivan-novakov/extjs-upload-widget/70c49c62c17a08015158e8da939ab37bb9146ce5/lib/upload/img/loading.gif
--------------------------------------------------------------------------------
/lib/upload/img/tick.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ivan-novakov/extjs-upload-widget/70c49c62c17a08015158e8da939ab37bb9146ce5/lib/upload/img/tick.png
--------------------------------------------------------------------------------
/lib/upload/uploader/AbstractUploader.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Abstract uploader object.
3 | *
4 | * The uploader object implements the the upload itself - transports data to the server. This is an "abstract" object
5 | * used as a base object for all uploader objects.
6 | *
7 | */
8 | Ext.define('Ext.ux.upload.uploader.AbstractUploader', {
9 | mixins : {
10 | observable : 'Ext.util.Observable'
11 | },
12 |
13 | /**
14 | * @cfg {Number} [maxFileSize=50000000]
15 | *
16 | * (NOT IMPLEMENTED) The maximum file size allowed to be uploaded.
17 | */
18 | maxFileSize : 50000000,
19 |
20 | /**
21 | * @cfg {String} url (required)
22 | *
23 | * The server URL to upload to.
24 | */
25 | url : '',
26 |
27 | /**
28 | * @cfg {Number} [timeout=60000]
29 | *
30 | * The connection timeout in miliseconds.
31 | */
32 | timeout : 60 * 1000,
33 |
34 | /**
35 | * @cfg {String} [contentType='application/binary']
36 | *
37 | * The content type announced in the HTTP headers. It is autodetected if possible, but if autodetection
38 | * cannot be done, this value is set as content type header.
39 | */
40 | contentType : 'application/binary',
41 |
42 | /**
43 | * @cfg {String} [filenameHeader='X-File-Name']
44 | *
45 | * The name of the HTTP header containing the filename.
46 | */
47 | filenameHeader : 'X-File-Name',
48 |
49 | /**
50 | * @cfg {String} [sizeHeader='X-File-Size']
51 | *
52 | * The name of the HTTP header containing the size of the file.
53 | */
54 | sizeHeader : 'X-File-Size',
55 |
56 | /**
57 | * @cfg {String} [typeHeader='X-File-Type']
58 | *
59 | * The name of the HTTP header containing the MIME type of the file.
60 | */
61 | typeHeader : 'X-File-Type',
62 |
63 | /**
64 | * @cfg {Object}
65 | *
66 | * Additional parameters to be sent with the upload request.
67 | */
68 | params : {},
69 |
70 | /**
71 | * @cfg {Object}
72 | *
73 | * Extra headers to be sent with the upload request.
74 | */
75 | extraHeaders : {},
76 |
77 | /**
78 | * @cfg {Object/String}
79 | *
80 | * Encoder object/class used to encode the filename header. Usually used, when the filename
81 | * contains non-ASCII characters.
82 | */
83 | filenameEncoder : null,
84 |
85 | filenameEncoderHeader : 'X-Filename-Encoder',
86 |
87 | /**
88 | * Constructor.
89 | * @param {Object} [config]
90 | */
91 | constructor : function(config) {
92 | this.mixins.observable.constructor.call(this);
93 |
94 | this.initConfig(config);
95 | },
96 |
97 | /**
98 | * @protected
99 | */
100 | initHeaders : function(item) {
101 | var headers = this.extraHeaders || {},
102 | filename = item.getFilename();
103 |
104 | /*
105 | * If there is a filename encoder defined - use it to encode the filename
106 | * in the header and set the type of the encoder as an additional header.
107 | */
108 | var filenameEncoder = this.initFilenameEncoder();
109 | if (filenameEncoder) {
110 | filename = filenameEncoder.encode(filename);
111 | headers[this.filenameEncoderHeader] = filenameEncoder.getType();
112 | }
113 | headers[this.filenameHeader] = filename;
114 | headers[this.sizeHeader] = item.getSize();
115 | headers[this.typeHeader] = item.getType();
116 |
117 | return headers;
118 | },
119 |
120 | /**
121 | * @abstract
122 | *
123 | * Upload a single item (file).
124 | * **Implement in subclass**
125 | *
126 | * @param {Ext.ux.upload.Item} item
127 | */
128 | uploadItem : function(item) {},
129 |
130 | /**
131 | * @abstract
132 | *
133 | * Aborts the current upload.
134 | * **Implement in subclass**
135 | */
136 | abortUpload : function() {},
137 |
138 | /**
139 | * @protected
140 | */
141 | initFilenameEncoder : function() {
142 | if (Ext.isString(this.filenameEncoder)) {
143 | this.filenameEncoder = Ext.create(this.filenameEncoder);
144 | }
145 |
146 | if (Ext.isObject(this.filenameEncoder)) {
147 | return this.filenameEncoder;
148 | }
149 |
150 | return null;
151 | }
152 |
153 | });
154 |
--------------------------------------------------------------------------------
/lib/upload/uploader/AbstractXhrUploader.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Abstract uploader with features common for all XHR based uploaders.
3 | */
4 | Ext.define('Ext.ux.upload.uploader.AbstractXhrUploader', {
5 | extend : 'Ext.ux.upload.uploader.AbstractUploader',
6 |
7 | onUploadSuccess : function(response, options, item) {
8 | var info = {
9 | success : true,
10 | message : '',
11 | response : response
12 | };
13 |
14 | if (response.responseText) {
15 | var responseJson = Ext.decode(response.responseText);
16 | if (responseJson) {
17 | Ext.apply(info, {
18 | success : responseJson.success,
19 | message : responseJson.message
20 | });
21 |
22 | var eventName = info.success ? 'uploadsuccess' : 'uploadfailure';
23 | this.fireEvent(eventName, item, info);
24 | return;
25 | }
26 | }
27 |
28 | this.fireEvent('uploadsuccess', item, info);
29 | },
30 |
31 | onUploadFailure : function(response, options, item) {
32 | var info = {
33 | success : false,
34 | message : 'http error',
35 | response : response
36 | };
37 |
38 | this.fireEvent('uploadfailure', item, info);
39 | },
40 |
41 | onUploadProgress : function(event, item) {
42 | this.fireEvent('uploadprogress', item, event);
43 | }
44 | });
--------------------------------------------------------------------------------
/lib/upload/uploader/DummyUploader.js:
--------------------------------------------------------------------------------
1 | Ext.define('Ext.ux.upload.uploader.DummyUploader', {
2 | extend : 'Ext.ux.upload.uploader.AbstractUploader',
3 |
4 | delay : 1000,
5 |
6 | uploadItem : function(item) {
7 | item.setUploading();
8 |
9 | var task = new Ext.util.DelayedTask(function() {
10 | this.fireEvent('uploadsuccess', item, {
11 | success : true,
12 | message : 'OK',
13 | response : null
14 | });
15 | }, this);
16 |
17 | task.delay(this.delay);
18 | },
19 |
20 | abortUpload : function() {
21 | }
22 | });
--------------------------------------------------------------------------------
/lib/upload/uploader/ExtJsUploader.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Uploader implementation - with the Connection object in ExtJS 4
3 | *
4 | */
5 | Ext.define('Ext.ux.upload.uploader.ExtJsUploader', {
6 | extend : 'Ext.ux.upload.uploader.AbstractXhrUploader',
7 |
8 | requires : [
9 | 'Ext.ux.upload.data.Connection'
10 | ],
11 |
12 | config : {
13 | /**
14 | * @cfg {String} [method='PUT']
15 | *
16 | * The HTTP method to be used.
17 | */
18 | method : 'PUT',
19 |
20 | /**
21 | * @cfg {Ext.data.Connection}
22 | *
23 | * If set, this connection object will be used when uploading files.
24 | */
25 | connection : null
26 | },
27 |
28 | /**
29 | * @property
30 | * @private
31 | *
32 | * The connection object.
33 | */
34 | conn : null,
35 |
36 | /**
37 | * @private
38 | *
39 | * Initializes and returns the connection object.
40 | *
41 | * @return {Ext.ux.upload.data.Connection}
42 | */
43 | initConnection : function() {
44 | var conn,
45 | url = this.url;
46 |
47 | if (this.connection instanceof Ext.data.Connection) {
48 | conn = this.connection;
49 | } else {
50 |
51 | if (this.params) {
52 | url = Ext.urlAppend(url, Ext.urlEncode(this.params));
53 | }
54 |
55 | conn = Ext.create('Ext.ux.upload.data.Connection', {
56 | disableCaching : true,
57 | method : this.method,
58 | url : url,
59 | timeout : this.timeout,
60 | defaultHeaders : {
61 | 'Content-Type' : this.contentType,
62 | 'X-Requested-With' : 'XMLHttpRequest'
63 | }
64 | });
65 | }
66 |
67 | return conn;
68 | },
69 |
70 | /**
71 | * @protected
72 | */
73 | initHeaders : function(item) {
74 | var headers = this.callParent(arguments);
75 |
76 | headers['Content-Type'] = item.getType();
77 |
78 | return headers;
79 | },
80 |
81 | /**
82 | * Implements {@link Ext.ux.upload.uploader.AbstractUploader#uploadItem}
83 | *
84 | * @param {Ext.ux.upload.Item} item
85 | */
86 | uploadItem : function(item) {
87 | var file = item.getFileApiObject();
88 | if (!file) {
89 | return;
90 | }
91 |
92 | item.setUploading();
93 |
94 | this.conn = this.initConnection();
95 |
96 | /*
97 | * Passing the File object directly as the "rawFata" option.
98 | * Specs:
99 | * https://dvcs.w3.org/hg/xhr/raw-file/tip/Overview.html#the-send()-method
100 | * http://dev.w3.org/2006/webapi/FileAPI/#blob
101 | */
102 | this.conn.request({
103 | scope : this,
104 | headers : this.initHeaders(item),
105 | rawData : file,
106 |
107 | success : Ext.Function.bind(this.onUploadSuccess, this, [
108 | item
109 | ], true),
110 | failure : Ext.Function.bind(this.onUploadFailure, this, [
111 | item
112 | ], true),
113 | progress : Ext.Function.bind(this.onUploadProgress, this, [
114 | item
115 | ], true)
116 | });
117 |
118 | },
119 |
120 | /**
121 | * Implements {@link Ext.ux.upload.uploader.AbstractUploader#abortUpload}
122 | */
123 | abortUpload : function() {
124 | if (this.conn) {
125 | /*
126 | * If we don't suspend the events, the connection abortion will cause a failure event.
127 | */
128 | this.suspendEvents();
129 | this.conn.abort();
130 | this.resumeEvents();
131 | }
132 | }
133 | });
--------------------------------------------------------------------------------
/lib/upload/uploader/FormDataUploader.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Uploader implementation which uses a FormData object to send files through XHR requests.
3 | *
4 | */
5 | Ext.define('Ext.ux.upload.uploader.FormDataUploader', {
6 | extend : 'Ext.ux.upload.uploader.AbstractXhrUploader',
7 |
8 | requires : [
9 | 'Ext.ux.upload.data.Connection'
10 | ],
11 |
12 | method : 'POST',
13 | xhr : null,
14 |
15 | initConnection : function() {
16 | if (this.params) {
17 | this.url = Ext.urlAppend(this.url, Ext.urlEncode(this.params));
18 | }
19 |
20 | var xhr = new XMLHttpRequest(),
21 | method = this.method,
22 | url = this.url;
23 |
24 | xhr.open(method, url, true);
25 |
26 | this.abortXhr = function() {
27 | this.suspendEvents();
28 | xhr.abort();
29 | this.resumeEvents();
30 | };
31 |
32 | return xhr;
33 | },
34 |
35 | uploadItem : function(item) {
36 | var file = item.getFileApiObject();
37 |
38 | item.setUploading();
39 |
40 | var formData = new FormData();
41 | formData.append(file.name, file);
42 |
43 | var xhr = this.initConnection();
44 |
45 | xhr.setRequestHeader(this.filenameHeader, file.name);
46 | xhr.setRequestHeader(this.sizeHeader, file.size);
47 | xhr.setRequestHeader(this.typeHeader, file.type);
48 |
49 | var loadendhandler = Ext.Function.bind(this.onLoadEnd, this, [
50 | item
51 | ], true);
52 |
53 | var progresshandler = Ext.Function.bind(this.onUploadProgress, this, [
54 | item
55 | ], true);
56 |
57 | xhr.addEventListener('loadend', loadendhandler, true);
58 | xhr.upload.addEventListener("progress", progresshandler, true);
59 |
60 | xhr.send(formData);
61 | },
62 |
63 | /**
64 | * Implements {@link Ext.ux.upload.uploader.AbstractUploader#abortUpload}
65 | */
66 | abortUpload : function() {
67 | this.abortXhr();
68 | },
69 |
70 | /**
71 | * @protected
72 | *
73 | * A placeholder for the abort procedure.
74 | */
75 | abortXhr : function() {
76 | },
77 |
78 | onLoadEnd : function(event, item) {
79 | var response = event.target;
80 |
81 | if (response.status != 200) {
82 | return this.onUploadFailure(response, null, item);
83 | }
84 |
85 | return this.onUploadSuccess(response, null, item);
86 | }
87 | });
--------------------------------------------------------------------------------
/lib/upload/uploader/LegacyExtJsUploader.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Uploader implementation - with the Connection object in ExtJS 4
3 | *
4 | */
5 | Ext.define('Ext.ux.upload.uploader.ExtJsUploader', {
6 | extend : 'Ext.ux.upload.uploader.AbstractUploader',
7 |
8 | requires : [
9 | 'Ext.ux.upload.data.Connection'
10 | ],
11 |
12 | /**
13 | * @property
14 | *
15 | * The connection object.
16 | */
17 | conn : null,
18 |
19 | /**
20 | * @private
21 | *
22 | * Initializes and returns the connection object.
23 | *
24 | * @return {Ext.ux.upload.data.Connection}
25 | */
26 | initConnection : function() {
27 | var url = this.url;
28 | if (this.params) {
29 | url = Ext.urlAppend(url, Ext.urlEncode(this.params));
30 | }
31 |
32 | var conn = Ext.create('Ext.ux.upload.data.Connection', {
33 | disableCaching : true,
34 | method : this.method,
35 | url : url,
36 | timeout : this.timeout,
37 | defaultHeaders : {
38 | 'Content-Type' : this.contentType,
39 | 'X-Requested-With' : 'XMLHttpRequest'
40 | }
41 | });
42 |
43 | return conn;
44 | },
45 |
46 | /**
47 | * Implements {@link Ext.ux.upload.uploader.AbstractUploader#uploadItem}
48 | *
49 | * @param {Ext.ux.upload.Item} item
50 | */
51 | uploadItem : function(item) {
52 | var file = item.getFileApiObject();
53 | if (!file) {
54 | return;
55 | }
56 |
57 | item.setUploading();
58 |
59 | this.conn = this.initConnection();
60 |
61 | this.conn.request({
62 | scope : this,
63 | headers : this.initHeaders(item),
64 | xmlData : file,
65 |
66 | success : Ext.Function.bind(this.onUploadSuccess, this, [
67 | item
68 | ], true),
69 | failure : Ext.Function.bind(this.onUploadFailure, this, [
70 | item
71 | ], true),
72 | progress : Ext.Function.bind(this.onUploadProgress, this, [
73 | item
74 | ], true)
75 | });
76 | },
77 |
78 | /**
79 | * Implements {@link Ext.ux.upload.uploader.AbstractUploader#abortUpload}
80 | */
81 | abortUpload : function() {
82 | if (this.conn) {
83 | this.conn.abort();
84 | }
85 | },
86 |
87 | onUploadSuccess : function(response, options, item) {
88 | var info = {
89 | success : false,
90 | message : 'general error',
91 | response : response
92 | };
93 |
94 | if (response.responseText) {
95 | var responseJson = Ext.decode(response.responseText);
96 | if (responseJson && responseJson.success) {
97 | Ext.apply(info, {
98 | success : responseJson.success,
99 | message : responseJson.message
100 | });
101 |
102 | this.fireEvent('uploadsuccess', item, info);
103 | return;
104 | }
105 |
106 | Ext.apply(info, {
107 | message : responseJson.message
108 | });
109 | }
110 |
111 | this.fireEvent('uploadfailure', item, info);
112 | },
113 |
114 | onUploadFailure : function(response, options, item) {
115 | var info = {
116 | success : false,
117 | message : 'http error',
118 | response : response
119 | };
120 |
121 | this.fireEvent('uploadfailure', item, info);
122 | },
123 |
124 | onUploadProgress : function(event, item) {
125 | this.fireEvent('uploadprogress', item, event);
126 | }
127 | });
--------------------------------------------------------------------------------
/public/_common.php:
--------------------------------------------------------------------------------
1 | $success,
13 | 'message' => $message
14 | );
15 |
16 | echo json_encode($response);
17 | exit();
18 | }
19 |
20 | function _error($message)
21 | {
22 | return _response(false, $message);
23 | }
--------------------------------------------------------------------------------
/public/_config.php.dist:
--------------------------------------------------------------------------------
1 | '/tmp/test-upload-dir',
4 | 'fake' => false
5 | );
--------------------------------------------------------------------------------
/public/app.js:
--------------------------------------------------------------------------------
1 | Ext.Loader.setPath({
2 | 'Ext.ux' : 'external'
3 | });
4 |
5 | Ext.application({
6 |
7 | requires : [
8 | 'Ext.ux.upload.Dialog'
9 | ],
10 |
11 | name : 'Example',
12 |
13 | appFolder : 'app',
14 |
15 | launch : function() {
16 | debug = console;
17 |
18 | Ext.create('Ext.container.Viewport', {
19 | layout : 'fit'
20 | });
21 |
22 | var appPanel = Ext.create('Ext.window.Window', {
23 | title : 'Files',
24 | width : 600,
25 | height : 400,
26 | closable : false,
27 | modal : true,
28 | bodyPadding : 5,
29 |
30 | uploadComplete : function(items) {
31 | var output = 'Uploaded files:
';
32 | Ext.Array.each(items, function(item) {
33 | output += item.getFilename() + ' (' + item.getType() + ', '
34 | + Ext.util.Format.fileSize(item.getSize()) + ')' + '
';
35 | });
36 |
37 | this.update(output);
38 | }
39 | });
40 |
41 | appPanel.syncCheckbox = Ext.create('Ext.form.field.Checkbox', {
42 | inputValue : true,
43 | checked : true
44 | });
45 |
46 | appPanel.addDocked({
47 | xtype : 'toolbar',
48 | dock : 'top',
49 | items : [
50 | {
51 | xtype : 'button',
52 | text : 'Raw PUT/POST Upload',
53 | scope : appPanel,
54 | handler : function() {
55 |
56 | var uploadPanel = Ext.create('Ext.ux.upload.Panel', {
57 | uploaderOptions : {
58 | url : 'upload.php'
59 | },
60 | filenameEncoder : 'Ext.ux.upload.header.Base64FilenameEncoder',
61 | synchronous : appPanel.syncCheckbox.getValue()
62 | });
63 |
64 | var uploadDialog = Ext.create('Ext.ux.upload.Dialog', {
65 | dialogTitle : 'My Upload Dialog',
66 | panel : uploadPanel
67 | });
68 |
69 | this.mon(uploadDialog, 'uploadcomplete', function(uploadPanel, manager, items, errorCount) {
70 | this.uploadComplete(items);
71 | if (!errorCount) {
72 | uploadDialog.close();
73 | }
74 | }, this);
75 |
76 | uploadDialog.show();
77 | }
78 | }, '-', {
79 | xtype : 'button',
80 | text : 'Multipart Upload',
81 | scope : appPanel,
82 | handler : function() {
83 |
84 | var uploadPanel = Ext.create('Ext.ux.upload.Panel', {
85 | uploader : 'Ext.ux.upload.uploader.FormDataUploader',
86 | uploaderOptions : {
87 | url : 'upload_multipart.php'
88 | },
89 | synchronous : appPanel.syncCheckbox.getValue()
90 | });
91 |
92 | var uploadDialog = Ext.create('Ext.ux.upload.Dialog', {
93 | dialogTitle : 'My Upload Dialog',
94 | panel : uploadPanel
95 | });
96 |
97 | this.mon(uploadDialog, 'uploadcomplete', function(uploadPanel, manager, items, errorCount) {
98 | this.uploadComplete(items);
99 | if (!errorCount) {
100 | uploadDialog.close();
101 | }
102 | }, this);
103 |
104 | uploadDialog.show();
105 | }
106 | }, '-', {
107 | xtype : 'button',
108 | text : 'Dummy upload',
109 | scope : appPanel,
110 | handler : function() {
111 |
112 | var uploadPanel = Ext.create('Ext.ux.upload.Panel', {
113 | uploader : 'Ext.ux.upload.uploader.DummyUploader',
114 | synchronous : appPanel.syncCheckbox.getValue()
115 | });
116 |
117 | var uploadDialog = Ext.create('Ext.ux.upload.Dialog', {
118 | dialogTitle : 'My Upload Dialog',
119 | panel : uploadPanel
120 | });
121 |
122 | this.mon(uploadDialog, 'uploadcomplete', function(uploadPanel, manager, items, errorCount) {
123 | this.uploadComplete(items);
124 | if (!errorCount) {
125 | uploadDialog.close();
126 | }
127 | }, this);
128 |
129 | uploadDialog.show();
130 | }
131 | }, '->', appPanel.syncCheckbox, 'Synchronous upload'
132 | ]
133 | })
134 |
135 | appPanel.show();
136 | }
137 |
138 | });
--------------------------------------------------------------------------------
/public/docs:
--------------------------------------------------------------------------------
1 | ../docs
--------------------------------------------------------------------------------
/public/external/upload:
--------------------------------------------------------------------------------
1 | ../../lib/upload
--------------------------------------------------------------------------------
/public/img/extjs-upload-widget.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ivan-novakov/extjs-upload-widget/70c49c62c17a08015158e8da939ab37bb9146ce5/public/img/extjs-upload-widget.jpeg
--------------------------------------------------------------------------------
/public/index-ext4.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |