in-tree extjs-upload-widget
|
@ -0,0 +1,33 @@
|
||||||
|
# Ext JS Upload Widget Changelog
|
||||||
|
|
||||||
|
## v1.1.1
|
||||||
|
(2013-11-05)
|
||||||
|
|
||||||
|
- Option to re-try upload when errors occur (#9)
|
||||||
|
- Add the DummyUploader to the example (#11)
|
||||||
|
- Use "itemId" instead of "id" property (#14)
|
||||||
|
- Fixed: The "Browse" button loses the "multiple" attribute (#10)
|
||||||
|
- Fixed: Handle utf-8 encoded filenames properly (#15)
|
||||||
|
|
||||||
|
|
||||||
|
## v1.1.0
|
||||||
|
(2013-06-12)
|
||||||
|
|
||||||
|
- implemented the core functionality as a panel rather than a dialog
|
||||||
|
- multiple uploader implementations support
|
||||||
|
- you can now "inject" your own uploader implementation
|
||||||
|
- new FormDataUploader implementing multipart upload
|
||||||
|
- updated examples and documentation
|
||||||
|
|
||||||
|
|
||||||
|
## v1.0.1
|
||||||
|
(2013-06-10)
|
||||||
|
|
||||||
|
- increased default connection timeout
|
||||||
|
- removed obsolete code
|
||||||
|
|
||||||
|
|
||||||
|
## v1.0.0
|
||||||
|
(2012-09-27)
|
||||||
|
|
||||||
|
- initial release
|
|
@ -0,0 +1,120 @@
|
||||||
|
# File upload widget for Sencha Ext JS
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- flexible and easily integratable
|
||||||
|
- uses the native File API (HTML5)
|
||||||
|
- allows selecting and uploading of multiple files at once
|
||||||
|
- supports both raw PUT/POST upload and multipart upload
|
||||||
|
- you can easily write and integrate your own upload mechanism, while preserving all (or most) of the UI functionality
|
||||||
|
- displays upload progress
|
||||||
|
- supports asynchronous (simultaneous) upload
|
||||||
|
|
||||||
|
## Online demo
|
||||||
|
|
||||||
|
- [Demo included in this repository](http://debug.cz/demo/upload/)
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- [Sencha Ext JS 5.x](http://www.sencha.com/products/extjs/)
|
||||||
|
- browser supporting the [File API](http://www.w3.org/TR/FileAPI/) - see more info about [browser compatibility](http://caniuse.com/fileapi)
|
||||||
|
- server side to process the uploaded files
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Clone the repository somewhere on your system and add the path with the prefix to `Ext.Loader`:
|
||||||
|
|
||||||
|
Ext.Loader.setPath({
|
||||||
|
'Ext.ux.upload' : '/my/path/to/extjs-upload-widget/lib/upload'
|
||||||
|
});
|
||||||
|
|
||||||
|
## Basic usage
|
||||||
|
|
||||||
|
In the most simple case, you can just open the dialog and pass the `uploadUrl` paramter:
|
||||||
|
|
||||||
|
var dialog = Ext.create('Ext.ux.upload.Dialog', {
|
||||||
|
dialogTitle: 'My Upload Widget',
|
||||||
|
uploadUrl: 'upload.php'
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog.show();
|
||||||
|
|
||||||
|
`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:
|
||||||
|
|
||||||
|
var myDialog = Ext.create('MyDialog', {
|
||||||
|
items: [
|
||||||
|
Ext.create('Ext.ux.upload.Panel', {
|
||||||
|
uploadUrl: 'upload.php'
|
||||||
|
});
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
## Uploaders
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Currently, these uploaders are implemented:
|
||||||
|
|
||||||
|
- __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.
|
||||||
|
- __FormDataUploader__ - uploads the file through a _XmlHttpRequest_ as if it was submitted with a form.
|
||||||
|
|
||||||
|
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__.
|
||||||
|
|
||||||
|
## Advanced usage
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
var panel = Ext.create('Ext.ux.upload.Panel', {
|
||||||
|
uploader: 'Ext.ux.upload.uploader.FormDataUploader',
|
||||||
|
uploaderOptions: {
|
||||||
|
url: 'upload_multipart.php',
|
||||||
|
timeout: 120*1000
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Or you can pass the uploader instance:
|
||||||
|
|
||||||
|
var panel = Ext.create('Ext.ux.upload.Panel', {
|
||||||
|
uploader: Ext.create('Ext.ux.upload.uploader.FormDataUploader', {
|
||||||
|
url: 'upload_multipart.php',
|
||||||
|
timeout: 120*1000
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
## Running the example
|
||||||
|
|
||||||
|
|
||||||
|
### Requirements:
|
||||||
|
|
||||||
|
- web server with PHP support
|
||||||
|
- Ext JS v4.x instance
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
- [API Docs](http://debug.cz/demo/upload/docs/generated/)
|
||||||
|
|
||||||
|
## Other links
|
||||||
|
|
||||||
|
- [More info in the blogpost](http://blog.debug.cz/2012/05/file-upload-widget-for-extjs-4x.html)
|
||||||
|
- [Sencha forums post](http://www.sencha.com/forum/showthread.php?205365-File-upload-widget-using-File-API-and-Ext.data.Connection)
|
||||||
|
|
||||||
|
## TODO
|
||||||
|
|
||||||
|
- add more uploader implementations
|
||||||
|
- add drag'n'drop support
|
||||||
|
- improve documentation
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
- [BSD 3 Clause](http://debug.cz/license/bsd-3-clause)
|
||||||
|
|
||||||
|
## Author
|
||||||
|
|
||||||
|
- [Ivan Novakov](http://novakov.cz/)
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
API documentation
|
||||||
|
=================
|
||||||
|
|
||||||
|
The documentation is generated with the [JSDuck tool](https://github.com/senchalabs/jsduck).
|
||||||
|
From this (`doc/`) directory run:
|
||||||
|
|
||||||
|
jsduck --config jsduck-config.json
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"--title" : "Ext JS Upload Widget",
|
||||||
|
"--output" : "./generated",
|
||||||
|
"--seo" : true,
|
||||||
|
"--external" : [
|
||||||
|
"Ext.Base", "Ext.util.Observable", "Ext.data.Connection", "Ext.window.Window", "Ext.util.MixedCollection",
|
||||||
|
"Ext.panel.Panel", "Ext.panel.Panel", "Ext.form.field.File", "Ext.grid.Panel", "Ext.toolbar.Toolbar",
|
||||||
|
"Ext.selection.CheckboxModel", "FileList", "File"
|
||||||
|
],
|
||||||
|
"--" : [
|
||||||
|
"../lib"
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
/**
|
||||||
|
* A "browse" button for selecting multiple files for upload.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
Ext.define('Ext.ux.upload.BrowseButton', {
|
||||||
|
extend : 'Ext.form.field.File',
|
||||||
|
|
||||||
|
buttonOnly : true,
|
||||||
|
|
||||||
|
iconCls : 'ux-mu-icon-action-browse',
|
||||||
|
buttonText : 'Browse...',
|
||||||
|
|
||||||
|
initComponent : function() {
|
||||||
|
|
||||||
|
Ext.apply(this, {
|
||||||
|
buttonConfig : {
|
||||||
|
iconCls : this.iconCls,
|
||||||
|
text : this.buttonText
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.on('afterrender', function() {
|
||||||
|
/*
|
||||||
|
* Fixing the issue when adding an icon to the button - the text does not render properly. OBSOLETE - from
|
||||||
|
* ExtJS v4.1 the internal implementation has changed, there is no button object anymore.
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
if (this.iconCls) {
|
||||||
|
// this.button.removeCls('x-btn-icon');
|
||||||
|
// var width = this.button.getWidth();
|
||||||
|
// this.setWidth(width);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Allow picking multiple files at once.
|
||||||
|
this.setMultipleInputAttribute();
|
||||||
|
|
||||||
|
}, this);
|
||||||
|
|
||||||
|
this.on('change', function(field, value, options) {
|
||||||
|
var files = this.fileInputEl.dom.files;
|
||||||
|
if (files.length) {
|
||||||
|
this.fireEvent('fileselected', this, files);
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
|
|
||||||
|
this.callParent(arguments);
|
||||||
|
},
|
||||||
|
|
||||||
|
reset : function() {
|
||||||
|
this.callParent(arguments);
|
||||||
|
this.setMultipleInputAttribute();
|
||||||
|
},
|
||||||
|
|
||||||
|
setMultipleInputAttribute : function(inputEl) {
|
||||||
|
inputEl = inputEl || this.fileInputEl;
|
||||||
|
inputEl.dom.setAttribute('multiple', '1');
|
||||||
|
}
|
||||||
|
|
||||||
|
// OBSOLETE - the method is not used by the superclass anymore
|
||||||
|
/*
|
||||||
|
createFileInput : function() {
|
||||||
|
this.callParent(arguments);
|
||||||
|
this.fileInputEl.dom.setAttribute('multiple', '1');
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
}
|
||||||
|
);
|
|
@ -0,0 +1,130 @@
|
||||||
|
/**
|
||||||
|
* The main upload dialog.
|
||||||
|
*
|
||||||
|
* Mostly, this may be the only object you need to interact with. Just initialize it and show it:
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* var dialog = Ext.create('Ext.ux.upload.Dialog', {
|
||||||
|
* dialogTitle: 'My Upload Widget',
|
||||||
|
* uploadUrl: 'upload.php'
|
||||||
|
* });
|
||||||
|
* dialog.show();
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
Ext.define('Ext.ux.upload.Dialog', {
|
||||||
|
extend : 'Ext.window.Window',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @cfg {Number} [width=700]
|
||||||
|
*/
|
||||||
|
width : 700,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @cfg {Number} [height=500]
|
||||||
|
*/
|
||||||
|
height : 500,
|
||||||
|
|
||||||
|
border : 0,
|
||||||
|
|
||||||
|
config : {
|
||||||
|
/**
|
||||||
|
* @cfg {String}
|
||||||
|
*
|
||||||
|
* The title of the dialog.
|
||||||
|
*/
|
||||||
|
dialogTitle : '',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @cfg {boolean} [synchronous=false]
|
||||||
|
*
|
||||||
|
* If true, all files are uploaded in a sequence, otherwise files are uploaded simultaneously (asynchronously).
|
||||||
|
*/
|
||||||
|
synchronous : true,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @cfg {String} uploadUrl (required)
|
||||||
|
*
|
||||||
|
* The URL to upload files to.
|
||||||
|
*/
|
||||||
|
uploadUrl : '',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @cfg {Object}
|
||||||
|
*
|
||||||
|
* Params passed to the uploader object and sent along with the request. It depends on the implementation of the
|
||||||
|
* uploader object, for example if the {@link Ext.ux.upload.uploader.ExtJsUploader} is used, the params are sent
|
||||||
|
* as GET params.
|
||||||
|
*/
|
||||||
|
uploadParams : {},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @cfg {Object}
|
||||||
|
*
|
||||||
|
* Extra HTTP headers to be added to the HTTP request uploading the file.
|
||||||
|
*/
|
||||||
|
uploadExtraHeaders : {},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @cfg {Number} [uploadTimeout=6000]
|
||||||
|
*
|
||||||
|
* The time after the upload request times out - in miliseconds.
|
||||||
|
*/
|
||||||
|
uploadTimeout : 60000,
|
||||||
|
|
||||||
|
// strings
|
||||||
|
textClose : 'Close'
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
initComponent : function() {
|
||||||
|
|
||||||
|
if (!Ext.isObject(this.panel)) {
|
||||||
|
this.panel = Ext.create('Ext.ux.upload.Panel', {
|
||||||
|
synchronous : this.synchronous,
|
||||||
|
scope: this.scope,
|
||||||
|
uploadUrl : this.uploadUrl,
|
||||||
|
uploadParams : this.uploadParams,
|
||||||
|
uploadExtraHeaders : this.uploadExtraHeaders,
|
||||||
|
uploadTimeout : this.uploadTimeout
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.relayEvents(this.panel, [
|
||||||
|
'uploadcomplete'
|
||||||
|
]);
|
||||||
|
|
||||||
|
Ext.apply(this, {
|
||||||
|
title : this.dialogTitle,
|
||||||
|
layout : 'fit',
|
||||||
|
items : [
|
||||||
|
this.panel
|
||||||
|
],
|
||||||
|
dockedItems : [
|
||||||
|
{
|
||||||
|
xtype : 'toolbar',
|
||||||
|
dock : 'bottom',
|
||||||
|
ui : 'footer',
|
||||||
|
defaults : {
|
||||||
|
minWidth : this.minButtonWidth
|
||||||
|
},
|
||||||
|
items : [
|
||||||
|
'->',
|
||||||
|
{
|
||||||
|
text : this.textClose,
|
||||||
|
cls : 'x-btn-text-icon',
|
||||||
|
scope : this,
|
||||||
|
handler : function() {
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
this.callParent(arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
|
@ -0,0 +1,157 @@
|
||||||
|
/**
|
||||||
|
* A single item designated for upload.
|
||||||
|
*
|
||||||
|
* It is a simple object wrapping the native file API object.
|
||||||
|
*/
|
||||||
|
Ext.define('Ext.ux.upload.Item', {
|
||||||
|
mixins : {
|
||||||
|
observable : 'Ext.util.Observable'
|
||||||
|
},
|
||||||
|
|
||||||
|
STATUS_READY : 'ready',
|
||||||
|
STATUS_UPLOADING : 'uploading',
|
||||||
|
STATUS_UPLOADED : 'uploaded',
|
||||||
|
STATUS_UPLOAD_ERROR : 'uploaderror',
|
||||||
|
|
||||||
|
progress : null,
|
||||||
|
status : null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @cfg {Object} fileApiObject (required)
|
||||||
|
*
|
||||||
|
* A native file API object
|
||||||
|
*/
|
||||||
|
fileApiObject : null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @cfg {String}
|
||||||
|
*
|
||||||
|
* The upload error message associated with this file object
|
||||||
|
*/
|
||||||
|
uploadErrorMessage : '',
|
||||||
|
|
||||||
|
constructor : function(config) {
|
||||||
|
this.mixins.observable.constructor.call(this);
|
||||||
|
|
||||||
|
this.initConfig(config);
|
||||||
|
|
||||||
|
Ext.apply(this, {
|
||||||
|
status : this.STATUS_READY,
|
||||||
|
progress : 0
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
reset : function() {
|
||||||
|
this.uploadErrorMessage = '';
|
||||||
|
this.setStatus(this.STATUS_READY);
|
||||||
|
this.setProgress(0);
|
||||||
|
},
|
||||||
|
|
||||||
|
getFileApiObject : function() {
|
||||||
|
return this.fileApiObject;
|
||||||
|
},
|
||||||
|
|
||||||
|
getId : function() {
|
||||||
|
return this.getFilename();
|
||||||
|
},
|
||||||
|
|
||||||
|
getName : function() {
|
||||||
|
return this.getProperty('name');
|
||||||
|
},
|
||||||
|
|
||||||
|
getFilename : function() {
|
||||||
|
return this.getName();
|
||||||
|
},
|
||||||
|
|
||||||
|
getSize : function() {
|
||||||
|
return this.getProperty('size');
|
||||||
|
},
|
||||||
|
|
||||||
|
getType : function() {
|
||||||
|
return this.getProperty('type');
|
||||||
|
},
|
||||||
|
|
||||||
|
getProperty : function(propertyName) {
|
||||||
|
if (this.fileApiObject) {
|
||||||
|
return this.fileApiObject[propertyName];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
|
getProgress : function() {
|
||||||
|
return this.progress;
|
||||||
|
},
|
||||||
|
|
||||||
|
getProgressPercent : function() {
|
||||||
|
var progress = this.getProgress();
|
||||||
|
if (!progress) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var percent = Ext.util.Format.number((progress / this.getSize()) * 100, '0');
|
||||||
|
if (percent > 100) {
|
||||||
|
percent = 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
return percent;
|
||||||
|
},
|
||||||
|
|
||||||
|
setProgress : function(progress) {
|
||||||
|
this.progress = progress;
|
||||||
|
this.fireEvent('progressupdate', this);
|
||||||
|
},
|
||||||
|
|
||||||
|
getStatus : function() {
|
||||||
|
return this.status;
|
||||||
|
},
|
||||||
|
|
||||||
|
setStatus : function(status) {
|
||||||
|
this.status = status;
|
||||||
|
this.fireEvent('changestatus', this, status);
|
||||||
|
},
|
||||||
|
|
||||||
|
hasStatus : function(status) {
|
||||||
|
var itemStatus = this.getStatus();
|
||||||
|
|
||||||
|
if (Ext.isArray(status) && Ext.Array.contains(status, itemStatus)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemStatus === status) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
isReady : function() {
|
||||||
|
return (this.status == this.STATUS_READY);
|
||||||
|
},
|
||||||
|
|
||||||
|
isUploaded : function() {
|
||||||
|
return (this.status == this.STATUS_UPLOADED);
|
||||||
|
},
|
||||||
|
|
||||||
|
setUploaded : function() {
|
||||||
|
this.setProgress(this.getSize());
|
||||||
|
this.setStatus(this.STATUS_UPLOADED);
|
||||||
|
},
|
||||||
|
|
||||||
|
isUploadError : function() {
|
||||||
|
return (this.status == this.STATUS_UPLOAD_ERROR);
|
||||||
|
},
|
||||||
|
|
||||||
|
getUploadErrorMessage : function() {
|
||||||
|
return this.uploadErrorMessage;
|
||||||
|
},
|
||||||
|
|
||||||
|
setUploadError : function(message) {
|
||||||
|
this.uploadErrorMessage = message;
|
||||||
|
this.setStatus(this.STATUS_UPLOAD_ERROR);
|
||||||
|
},
|
||||||
|
|
||||||
|
setUploading : function() {
|
||||||
|
this.setStatus(this.STATUS_UPLOADING);
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,203 @@
|
||||||
|
/**
|
||||||
|
* The grid displaying the list of uploaded files (queue).
|
||||||
|
*
|
||||||
|
* @class Ext.ux.upload.ItemGridPanel
|
||||||
|
* @extends Ext.grid.Panel
|
||||||
|
*/
|
||||||
|
Ext.define('Ext.ux.upload.ItemGridPanel', {
|
||||||
|
extend : 'Ext.grid.Panel',
|
||||||
|
|
||||||
|
requires : [
|
||||||
|
'Ext.selection.CheckboxModel', 'Ext.ux.upload.Store'
|
||||||
|
],
|
||||||
|
|
||||||
|
layout : 'fit',
|
||||||
|
border : 0,
|
||||||
|
|
||||||
|
viewConfig : {
|
||||||
|
scrollOffset : 40
|
||||||
|
},
|
||||||
|
|
||||||
|
config : {
|
||||||
|
queue : null,
|
||||||
|
|
||||||
|
textFilename : 'Filename',
|
||||||
|
textSize : 'Size',
|
||||||
|
textType : 'Type',
|
||||||
|
textStatus : 'Status',
|
||||||
|
textProgress : '%'
|
||||||
|
},
|
||||||
|
|
||||||
|
initComponent : function() {
|
||||||
|
|
||||||
|
if (this.queue) {
|
||||||
|
this.queue.on('queuechange', this.onQueueChange, this);
|
||||||
|
this.queue.on('itemchangestatus', this.onQueueItemChangeStatus, this);
|
||||||
|
this.queue.on('itemprogressupdate', this.onQueueItemProgressUpdate, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ext.apply(this, {
|
||||||
|
store : Ext.create('Ext.ux.upload.Store'),
|
||||||
|
selModel : Ext.create('Ext.selection.CheckboxModel', {
|
||||||
|
checkOnly : true
|
||||||
|
}),
|
||||||
|
columns : [
|
||||||
|
{
|
||||||
|
xtype : 'rownumberer',
|
||||||
|
width : 50
|
||||||
|
}, {
|
||||||
|
dataIndex : 'filename',
|
||||||
|
header : this.textFilename,
|
||||||
|
flex : 1
|
||||||
|
}, {
|
||||||
|
dataIndex : 'size',
|
||||||
|
header : this.textSize,
|
||||||
|
width : 100,
|
||||||
|
renderer : function(value) {
|
||||||
|
return Ext.util.Format.fileSize(value);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
dataIndex : 'type',
|
||||||
|
header : this.textType,
|
||||||
|
width : 150
|
||||||
|
}, {
|
||||||
|
dataIndex : 'status',
|
||||||
|
header : this.textStatus,
|
||||||
|
width : 50,
|
||||||
|
align : 'right',
|
||||||
|
renderer : this.statusRenderer
|
||||||
|
}, {
|
||||||
|
dataIndex : 'progress',
|
||||||
|
header : this.textProgress,
|
||||||
|
width : 50,
|
||||||
|
align : 'right',
|
||||||
|
renderer : function(value) {
|
||||||
|
if (!value) {
|
||||||
|
value = 0;
|
||||||
|
}
|
||||||
|
return value + '%';
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
dataIndex : 'message',
|
||||||
|
width : 1,
|
||||||
|
hidden : true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
this.callParent(arguments);
|
||||||
|
},
|
||||||
|
|
||||||
|
onQueueChange : function(queue) {
|
||||||
|
this.loadQueueItems(queue.getItems());
|
||||||
|
},
|
||||||
|
|
||||||
|
onQueueItemChangeStatus : function(queue, item, status) {
|
||||||
|
this.updateStatus(item);
|
||||||
|
},
|
||||||
|
|
||||||
|
onQueueItemProgressUpdate : function(queue, item) {
|
||||||
|
this.updateStatus(item);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the internal store with the supplied queue items.
|
||||||
|
*
|
||||||
|
* @param {Array} items
|
||||||
|
*/
|
||||||
|
loadQueueItems : function(items) {
|
||||||
|
var data = [];
|
||||||
|
var i;
|
||||||
|
|
||||||
|
for (i = 0; i < items.length; i++) {
|
||||||
|
data.push([
|
||||||
|
items[i].getFilename(),
|
||||||
|
items[i].getSize(),
|
||||||
|
items[i].getType(),
|
||||||
|
items[i].getStatus(),
|
||||||
|
items[i].getProgressPercent()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loadStoreData(data);
|
||||||
|
},
|
||||||
|
|
||||||
|
loadStoreData : function(data, append) {
|
||||||
|
this.store.loadData(data, append);
|
||||||
|
},
|
||||||
|
|
||||||
|
getSelectedRecords : function() {
|
||||||
|
return this.getSelectionModel().getSelection();
|
||||||
|
},
|
||||||
|
|
||||||
|
updateStatus : function(item) {
|
||||||
|
var record = this.getRecordByFilename(item.getFilename());
|
||||||
|
if (!record) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var itemStatus = item.getStatus();
|
||||||
|
// debug.log('[' + item.getStatus() + '] [' + record.get('status') + ']');
|
||||||
|
if (itemStatus != record.get('status')) {
|
||||||
|
this.scrollIntoView(record);
|
||||||
|
|
||||||
|
record.set('status', item.getStatus());
|
||||||
|
if (item.isUploadError()) {
|
||||||
|
record.set('tooltip', item.getUploadErrorMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
record.set('progress', item.getProgressPercent());
|
||||||
|
record.commit();
|
||||||
|
},
|
||||||
|
|
||||||
|
getRecordByFilename : function(filename) {
|
||||||
|
var index = this.store.findExact('filename', filename);
|
||||||
|
if (-1 == index) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.store.getAt(index);
|
||||||
|
},
|
||||||
|
|
||||||
|
getIndexByRecord : function(record) {
|
||||||
|
return this.store.findExact('filename', record.get('filename'));
|
||||||
|
},
|
||||||
|
|
||||||
|
statusRenderer : function(value, metaData, record, rowIndex, colIndex, store) {
|
||||||
|
var iconCls = 'ux-mu-icon-upload-' + value;
|
||||||
|
var tooltip = record.get('tooltip');
|
||||||
|
if (tooltip) {
|
||||||
|
value = tooltip;
|
||||||
|
} else {
|
||||||
|
'upload_status_' + value;
|
||||||
|
}
|
||||||
|
value = '<span class="ux-mu-status-value ' + iconCls + '" data-qtip="' + value + '" />';
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
|
||||||
|
scrollIntoView : function(record) {
|
||||||
|
|
||||||
|
var index = this.getIndexByRecord(record);
|
||||||
|
if (-1 == index) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.getView().focusRow(index);
|
||||||
|
return;
|
||||||
|
var rowEl = Ext.get(this.getView().getRow(index));
|
||||||
|
// var rowEl = this.getView().getRow(index);
|
||||||
|
if (!rowEl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var gridEl = this.getEl();
|
||||||
|
|
||||||
|
// debug.log(rowEl.dom);
|
||||||
|
// debug.log(gridEl.getBottom());
|
||||||
|
|
||||||
|
if (rowEl.getBottom() > gridEl.getBottom()) {
|
||||||
|
rowEl.dom.scrollIntoView(gridEl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,446 @@
|
||||||
|
/**
|
||||||
|
* The main upload dialog.
|
||||||
|
*
|
||||||
|
* Mostly, this will be the only object you need to interact with. Just initialize it and show it:
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* var dialog = Ext.create('Ext.ux.upload.Dialog', {
|
||||||
|
* dialogTitle: 'My Upload Widget',
|
||||||
|
* uploadUrl: 'upload.php'
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* dialog.show();
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
Ext.define('Ext.ux.upload.Dialog', {
|
||||||
|
extend : 'Ext.window.Window',
|
||||||
|
|
||||||
|
requires : [
|
||||||
|
'Ext.ux.upload.ItemGridPanel',
|
||||||
|
'Ext.ux.upload.Manager',
|
||||||
|
'Ext.ux.upload.StatusBar',
|
||||||
|
'Ext.ux.upload.BrowseButton',
|
||||||
|
'Ext.ux.upload.Queue'
|
||||||
|
],
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @cfg {Number} [width=700]
|
||||||
|
*/
|
||||||
|
width : 700,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @cfg {Number} [height=500]
|
||||||
|
*/
|
||||||
|
height : 500,
|
||||||
|
|
||||||
|
config : {
|
||||||
|
/**
|
||||||
|
* @cfg {String}
|
||||||
|
*
|
||||||
|
* The title of the dialog.
|
||||||
|
*/
|
||||||
|
dialogTitle : '',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @cfg {boolean} [synchronous=false]
|
||||||
|
*
|
||||||
|
* If true, all files are uploaded in a sequence, otherwise files are uploaded simultaneously (asynchronously).
|
||||||
|
*/
|
||||||
|
synchronous : true,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @cfg {String} uploadUrl (required)
|
||||||
|
*
|
||||||
|
* The URL to upload files to.
|
||||||
|
*/
|
||||||
|
uploadUrl : '',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @cfg {Object}
|
||||||
|
*
|
||||||
|
* Params passed to the uploader object and sent along with the request. It depends on the implementation of the
|
||||||
|
* uploader object, for example if the {@link Ext.ux.upload.uploader.ExtJsUploader} is used, the params are sent
|
||||||
|
* as GET params.
|
||||||
|
*/
|
||||||
|
uploadParams : {},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @cfg {Object}
|
||||||
|
*
|
||||||
|
* Extra HTTP headers to be added to the HTTP request uploading the file.
|
||||||
|
*/
|
||||||
|
uploadExtraHeaders : {},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @cfg {Number} [uploadTimeout=6000]
|
||||||
|
*
|
||||||
|
* The time after the upload request times out - in miliseconds.
|
||||||
|
*/
|
||||||
|
uploadTimeout : 60000,
|
||||||
|
|
||||||
|
// dialog strings
|
||||||
|
textOk : 'OK',
|
||||||
|
textClose : 'Close',
|
||||||
|
textUpload : 'Upload',
|
||||||
|
textBrowse : 'Browse',
|
||||||
|
textAbort : 'Abort',
|
||||||
|
textRemoveSelected : 'Remove selected',
|
||||||
|
textRemoveAll : 'Remove all',
|
||||||
|
|
||||||
|
// grid strings
|
||||||
|
textFilename : 'Filename',
|
||||||
|
textSize : 'Size',
|
||||||
|
textType : 'Type',
|
||||||
|
textStatus : 'Status',
|
||||||
|
textProgress : '%',
|
||||||
|
|
||||||
|
// status toolbar strings
|
||||||
|
selectionMessageText : 'Selected {0} file(s), {1}',
|
||||||
|
uploadMessageText : 'Upload progress {0}% ({1} of {2} souborů)',
|
||||||
|
|
||||||
|
// browse button
|
||||||
|
buttonText : 'Browse...'
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property {Ext.ux.upload.Queue}
|
||||||
|
*/
|
||||||
|
queue : null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property {Ext.ux.upload.ItemGridPanel}
|
||||||
|
*/
|
||||||
|
grid : null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property {Ext.ux.upload.Manager}
|
||||||
|
*/
|
||||||
|
uploadManager : null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property {Ext.ux.upload.StatusBar}
|
||||||
|
*/
|
||||||
|
statusBar : null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property {Ext.ux.upload.BrowseButton}
|
||||||
|
*/
|
||||||
|
browseButton : null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
initComponent : function() {
|
||||||
|
|
||||||
|
this.queue = this.initQueue();
|
||||||
|
|
||||||
|
this.grid = Ext.create('Ext.ux.upload.ItemGridPanel', {
|
||||||
|
queue : this.queue,
|
||||||
|
textFilename : this.textFilename,
|
||||||
|
textSize : this.textSize,
|
||||||
|
textType : this.textType,
|
||||||
|
textStatus : this.textStatus,
|
||||||
|
textProgress : this.textProgress
|
||||||
|
});
|
||||||
|
|
||||||
|
this.uploadManager = Ext.create('Ext.ux.upload.Manager', {
|
||||||
|
url : this.uploadUrl,
|
||||||
|
synchronous : this.synchronous,
|
||||||
|
params : this.uploadParams,
|
||||||
|
extraHeaders : this.uploadExtraHeaders,
|
||||||
|
uploadTimeout : this.uploadTimeout
|
||||||
|
});
|
||||||
|
|
||||||
|
this.uploadManager.on('uploadcomplete', this.onUploadComplete, this);
|
||||||
|
this.uploadManager.on('itemuploadsuccess', this.onItemUploadSuccess, this);
|
||||||
|
this.uploadManager.on('itemuploadfailure', this.onItemUploadFailure, this);
|
||||||
|
|
||||||
|
this.statusBar = Ext.create('Ext.ux.upload.StatusBar', {
|
||||||
|
dock : 'bottom',
|
||||||
|
selectionMessageText : this.selectionMessageText,
|
||||||
|
uploadMessageText : this.uploadMessageText
|
||||||
|
});
|
||||||
|
|
||||||
|
Ext.apply(this, {
|
||||||
|
title : this.dialogTitle,
|
||||||
|
autoScroll : true,
|
||||||
|
layout : 'fit',
|
||||||
|
uploading : false,
|
||||||
|
items : [
|
||||||
|
this.grid
|
||||||
|
],
|
||||||
|
dockedItems : [
|
||||||
|
this.getTopToolbarConfig(),
|
||||||
|
{
|
||||||
|
xtype : 'toolbar',
|
||||||
|
dock : 'bottom',
|
||||||
|
ui : 'footer',
|
||||||
|
defaults : {
|
||||||
|
minWidth : this.minButtonWidth
|
||||||
|
},
|
||||||
|
items : [
|
||||||
|
'->',
|
||||||
|
{
|
||||||
|
text : this.textClose,
|
||||||
|
// iconCls : 'ux-mu-icon-action-ok',
|
||||||
|
cls : 'x-btn-text-icon',
|
||||||
|
scope : this,
|
||||||
|
handler : function() {
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
this.statusBar
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
this.on('afterrender', function() {
|
||||||
|
this.stateInit();
|
||||||
|
}, this);
|
||||||
|
|
||||||
|
this.callParent(arguments);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*
|
||||||
|
* Returns the config object for the top toolbar.
|
||||||
|
*
|
||||||
|
* @return {Array}
|
||||||
|
*/
|
||||||
|
getTopToolbarConfig : function() {
|
||||||
|
|
||||||
|
this.browseButton = Ext.create('Ext.ux.upload.BrowseButton', {
|
||||||
|
id : 'button_browse',
|
||||||
|
buttonText : this.buttonText
|
||||||
|
});
|
||||||
|
this.browseButton.on('fileselected', this.onFileSelection, this);
|
||||||
|
|
||||||
|
return {
|
||||||
|
xtype : 'toolbar',
|
||||||
|
dock : 'top',
|
||||||
|
items : [
|
||||||
|
this.browseButton,
|
||||||
|
'-',
|
||||||
|
{
|
||||||
|
id : 'button_upload',
|
||||||
|
text : this.textUpload,
|
||||||
|
iconCls : 'ux-mu-icon-action-upload',
|
||||||
|
scope : this,
|
||||||
|
handler : this.onInitUpload
|
||||||
|
},
|
||||||
|
'-',
|
||||||
|
{
|
||||||
|
id : 'button_abort',
|
||||||
|
text : this.textAbort,
|
||||||
|
iconCls : 'ux-mu-icon-action-abort',
|
||||||
|
scope : this,
|
||||||
|
handler : this.onAbortUpload,
|
||||||
|
disabled : true
|
||||||
|
},
|
||||||
|
'->',
|
||||||
|
{
|
||||||
|
id : 'button_remove_selected',
|
||||||
|
text : this.textRemoveSelected,
|
||||||
|
iconCls : 'ux-mu-icon-action-remove',
|
||||||
|
scope : this,
|
||||||
|
handler : this.onMultipleRemove
|
||||||
|
},
|
||||||
|
'-',
|
||||||
|
{
|
||||||
|
id : 'button_remove_all',
|
||||||
|
text : this.textRemoveAll,
|
||||||
|
iconCls : 'ux-mu-icon-action-remove',
|
||||||
|
scope : this,
|
||||||
|
handler : this.onRemoveAll
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*
|
||||||
|
* Initializes and returns the queue object.
|
||||||
|
*
|
||||||
|
* @return {Ext.ux.upload.Queue}
|
||||||
|
*/
|
||||||
|
initQueue : function() {
|
||||||
|
var queue = Ext.create('Ext.ux.upload.Queue');
|
||||||
|
|
||||||
|
queue.on('queuechange', this.onQueueChange, this);
|
||||||
|
|
||||||
|
return queue;
|
||||||
|
},
|
||||||
|
|
||||||
|
onInitUpload : function() {
|
||||||
|
if (!this.queue.getCount()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.stateUpload();
|
||||||
|
this.startUpload();
|
||||||
|
},
|
||||||
|
|
||||||
|
onAbortUpload : function() {
|
||||||
|
this.uploadManager.abortUpload();
|
||||||
|
this.finishUpload();
|
||||||
|
this.switchState();
|
||||||
|
},
|
||||||
|
|
||||||
|
onUploadComplete : function(manager, queue, errorCount) {
|
||||||
|
this.finishUpload();
|
||||||
|
this.stateInit();
|
||||||
|
this.fireEvent('uploadcomplete', this, manager, queue.getUploadedItems(), errorCount);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*
|
||||||
|
* Executes after files has been selected for upload through the "Browse" button. Updates the upload queue with the
|
||||||
|
* new files.
|
||||||
|
*
|
||||||
|
* @param {Ext.ux.upload.BrowseButton} input
|
||||||
|
* @param {FileList} files
|
||||||
|
*/
|
||||||
|
onFileSelection : function(input, files) {
|
||||||
|
this.queue.clearUploadedItems();
|
||||||
|
this.queue.addFiles(files);
|
||||||
|
this.browseButton.reset();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*
|
||||||
|
* Executes if there is a change in the queue. Updates the related components (grid, toolbar).
|
||||||
|
*
|
||||||
|
* @param {Ext.ux.upload.Queue} queue
|
||||||
|
*/
|
||||||
|
onQueueChange : function(queue) {
|
||||||
|
this.updateStatusBar();
|
||||||
|
|
||||||
|
this.switchState();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*
|
||||||
|
* Executes upon hitting the "multiple remove" button. Removes all selected items from the queue.
|
||||||
|
*/
|
||||||
|
onMultipleRemove : function() {
|
||||||
|
var records = this.grid.getSelectedRecords();
|
||||||
|
if (!records.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var keys = [];
|
||||||
|
var i;
|
||||||
|
var num = records.length;
|
||||||
|
|
||||||
|
for (i = 0; i < num; i++) {
|
||||||
|
keys.push(records[i].get('filename'));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.queue.removeItemsByKey(keys);
|
||||||
|
},
|
||||||
|
|
||||||
|
onRemoveAll : function() {
|
||||||
|
this.queue.clearItems();
|
||||||
|
},
|
||||||
|
|
||||||
|
onItemUploadSuccess : function(item, info) {
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
onItemUploadFailure : function(item, info) {
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
startUpload : function() {
|
||||||
|
this.uploading = true;
|
||||||
|
this.uploadManager.uploadQueue(this.queue);
|
||||||
|
},
|
||||||
|
|
||||||
|
finishUpload : function() {
|
||||||
|
this.uploading = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
isUploadActive : function() {
|
||||||
|
return this.uploading;
|
||||||
|
},
|
||||||
|
|
||||||
|
updateStatusBar : function() {
|
||||||
|
if (!this.statusBar) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var numFiles = this.queue.getCount();
|
||||||
|
|
||||||
|
this.statusBar.setSelectionMessage(this.queue.getCount(), this.queue.getTotalBytes());
|
||||||
|
},
|
||||||
|
|
||||||
|
getButton : function(id) {
|
||||||
|
return Ext.ComponentMgr.get(id);
|
||||||
|
},
|
||||||
|
|
||||||
|
switchButtons : function(info) {
|
||||||
|
var id;
|
||||||
|
for (id in info) {
|
||||||
|
this.switchButton(id, info[id]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
switchButton : function(id, on) {
|
||||||
|
var button = this.getButton(id);
|
||||||
|
|
||||||
|
if (button) {
|
||||||
|
if (on) {
|
||||||
|
button.enable();
|
||||||
|
} else {
|
||||||
|
button.disable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
switchState : function() {
|
||||||
|
if (this.uploading) {
|
||||||
|
this.stateUpload();
|
||||||
|
} else if (this.queue.getCount()) {
|
||||||
|
this.stateQueue();
|
||||||
|
} else {
|
||||||
|
this.stateInit();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
stateInit : function() {
|
||||||
|
this.switchButtons({
|
||||||
|
'button_browse' : 1,
|
||||||
|
'button_upload' : 0,
|
||||||
|
'button_abort' : 0,
|
||||||
|
'button_remove_all' : 1,
|
||||||
|
'button_remove_selected' : 1
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
stateQueue : function() {
|
||||||
|
this.switchButtons({
|
||||||
|
'button_browse' : 1,
|
||||||
|
'button_upload' : 1,
|
||||||
|
'button_abort' : 0,
|
||||||
|
'button_remove_all' : 1,
|
||||||
|
'button_remove_selected' : 1
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
stateUpload : function() {
|
||||||
|
this.switchButtons({
|
||||||
|
'button_browse' : 0,
|
||||||
|
'button_upload' : 0,
|
||||||
|
'button_abort' : 1,
|
||||||
|
'button_remove_all' : 1,
|
||||||
|
'button_remove_selected' : 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
|
@ -0,0 +1,153 @@
|
||||||
|
/**
|
||||||
|
* The object is responsible for uploading the queue.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
Ext.define('Ext.ux.upload.Manager', {
|
||||||
|
mixins : {
|
||||||
|
observable : 'Ext.util.Observable'
|
||||||
|
},
|
||||||
|
|
||||||
|
requires : [
|
||||||
|
'Ext.ux.upload.uploader.AbstractUploader'
|
||||||
|
],
|
||||||
|
|
||||||
|
uploader : null,
|
||||||
|
uploaderOptions : null,
|
||||||
|
synchronous : true,
|
||||||
|
filenameEncoder : null,
|
||||||
|
|
||||||
|
DEFAULT_UPLOADER_CLASS : 'Ext.ux.upload.uploader.ExtJsUploader',
|
||||||
|
|
||||||
|
constructor : function(config) {
|
||||||
|
this.mixins.observable.constructor.call(this);
|
||||||
|
|
||||||
|
this.initConfig(config);
|
||||||
|
|
||||||
|
if (!(this.uploader instanceof Ext.ux.upload.uploader.AbstractUploader)) {
|
||||||
|
var uploaderClass = this.DEFAULT_UPLOADER_CLASS;
|
||||||
|
if (Ext.isString(this.uploader)) {
|
||||||
|
uploaderClass = this.uploader;
|
||||||
|
}
|
||||||
|
|
||||||
|
var uploaderOptions = this.uploaderOptions || {};
|
||||||
|
Ext.applyIf(uploaderOptions, {
|
||||||
|
success : this.onUploadSuccess,
|
||||||
|
failure : this.onUploadFailure,
|
||||||
|
progress : this.onUploadProgress,
|
||||||
|
filenameEncoder : this.filenameEncoder
|
||||||
|
});
|
||||||
|
|
||||||
|
this.uploader = Ext.create(uploaderClass, uploaderOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.mon(this.uploader, 'uploadsuccess', this.onUploadSuccess, this);
|
||||||
|
this.mon(this.uploader, 'uploadfailure', this.onUploadFailure, this);
|
||||||
|
this.mon(this.uploader, 'uploadprogress', this.onUploadProgress, this);
|
||||||
|
|
||||||
|
Ext.apply(this, {
|
||||||
|
syncQueue : null,
|
||||||
|
currentQueue : null,
|
||||||
|
uploadActive : false,
|
||||||
|
errorCount : 0
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
uploadQueue : function(queue) {
|
||||||
|
if (this.uploadActive) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.startUpload(queue);
|
||||||
|
|
||||||
|
if (this.synchronous) {
|
||||||
|
this.uploadQueueSync(queue);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.uploadQueueAsync(queue);
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
uploadQueueSync : function(queue) {
|
||||||
|
this.uploadNextItemSync();
|
||||||
|
},
|
||||||
|
|
||||||
|
uploadNextItemSync : function() {
|
||||||
|
if (!this.uploadActive) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var item = this.currentQueue.getFirstReadyItem();
|
||||||
|
if (!item) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.uploader.uploadItem(item);
|
||||||
|
},
|
||||||
|
|
||||||
|
uploadQueueAsync : function(queue) {
|
||||||
|
var i;
|
||||||
|
var num = queue.getCount();
|
||||||
|
|
||||||
|
for (i = 0; i < num; i++) {
|
||||||
|
this.uploader.uploadItem(queue.getAt(i));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
startUpload : function(queue) {
|
||||||
|
queue.reset();
|
||||||
|
|
||||||
|
this.uploadActive = true;
|
||||||
|
this.currentQueue = queue;
|
||||||
|
this.fireEvent('beforeupload', this, queue);
|
||||||
|
},
|
||||||
|
|
||||||
|
finishUpload : function() {
|
||||||
|
this.fireEvent('uploadcomplete', this, this.currentQueue, this.errorCount);
|
||||||
|
},
|
||||||
|
|
||||||
|
resetUpload : function() {
|
||||||
|
this.currentQueue = null;
|
||||||
|
this.uploadActive = false;
|
||||||
|
this.errorCount = 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
abortUpload : function() {
|
||||||
|
this.uploader.abortUpload();
|
||||||
|
this.currentQueue.recoverAfterAbort();
|
||||||
|
this.resetUpload();
|
||||||
|
|
||||||
|
this.fireEvent('abortupload', this, this.currentQueue);
|
||||||
|
},
|
||||||
|
|
||||||
|
afterItemUpload : function(item, info) {
|
||||||
|
if (this.synchronous) {
|
||||||
|
this.uploadNextItemSync();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.currentQueue.existUploadingItems()) {
|
||||||
|
this.finishUpload();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onUploadSuccess : function(item, info) {
|
||||||
|
item.setUploaded();
|
||||||
|
|
||||||
|
this.fireEvent('itemuploadsuccess', this, item, info);
|
||||||
|
|
||||||
|
this.afterItemUpload(item, info);
|
||||||
|
},
|
||||||
|
|
||||||
|
onUploadFailure : function(item, info) {
|
||||||
|
item.setUploadError(info.message);
|
||||||
|
|
||||||
|
this.fireEvent('itemuploadfailure', this, item, info);
|
||||||
|
this.errorCount++;
|
||||||
|
|
||||||
|
this.afterItemUpload(item, info);
|
||||||
|
},
|
||||||
|
|
||||||
|
onUploadProgress : function(item, event) {
|
||||||
|
item.setProgress(event.loaded);
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,484 @@
|
||||||
|
/**
|
||||||
|
* The main upload panel, which ties all the functionality together.
|
||||||
|
*
|
||||||
|
* In the most basic case you need just to set the upload URL:
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* var uploadPanel = Ext.create('Ext.ux.upload.Panel', {
|
||||||
|
* uploaderOptions: {
|
||||||
|
* url: '/api/upload'
|
||||||
|
* }
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* It uses the default ExtJsUploader to perform the actual upload. If you want to use another uploade, for
|
||||||
|
* example the FormDataUploader, you can pass the name of the class:
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* var uploadPanel = Ext.create('Ext.ux.upload.Panel', {
|
||||||
|
* uploader: 'Ext.ux.upload.uploader.FormDataUploader',
|
||||||
|
* uploaderOptions: {
|
||||||
|
* url: '/api/upload',
|
||||||
|
* timeout: 120*1000
|
||||||
|
* }
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* Or event an instance of the uploader:
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* var formDataUploader = Ext.create('Ext.ux.upload.uploader.FormDataUploader', {
|
||||||
|
* url: '/api/upload'
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* var uploadPanel = Ext.create('Ext.ux.upload.Panel', {
|
||||||
|
* uploader: formDataUploader
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
Ext.define('Ext.ux.upload.Panel', {
|
||||||
|
extend : 'Ext.panel.Panel',
|
||||||
|
|
||||||
|
requires : [
|
||||||
|
'Ext.ux.upload.ItemGridPanel',
|
||||||
|
'Ext.ux.upload.Manager',
|
||||||
|
'Ext.ux.upload.StatusBar',
|
||||||
|
'Ext.ux.upload.BrowseButton',
|
||||||
|
'Ext.ux.upload.Queue'
|
||||||
|
],
|
||||||
|
|
||||||
|
config : {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @cfg {Object/String}
|
||||||
|
*
|
||||||
|
* The name of the uploader class or the uploader object itself. If not set, the default uploader will
|
||||||
|
* be used.
|
||||||
|
*/
|
||||||
|
uploader : null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @cfg {Object}
|
||||||
|
*
|
||||||
|
* Configuration object for the uploader. Configuration options included in this object override the
|
||||||
|
* options 'uploadUrl', 'uploadParams', 'uploadExtraHeaders', 'uploadTimeout'.
|
||||||
|
*/
|
||||||
|
uploaderOptions : null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @cfg {boolean} [synchronous=false]
|
||||||
|
*
|
||||||
|
* If true, all files are uploaded in a sequence, otherwise files are uploaded simultaneously (asynchronously).
|
||||||
|
*/
|
||||||
|
synchronous : true,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @cfg {String} uploadUrl
|
||||||
|
*
|
||||||
|
* The URL to upload files to. Not required if configured uploader instance is passed to this panel.
|
||||||
|
*/
|
||||||
|
uploadUrl : '',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @cfg {Object}
|
||||||
|
*
|
||||||
|
* Params passed to the uploader object and sent along with the request. It depends on the implementation of the
|
||||||
|
* uploader object, for example if the {@link Ext.ux.upload.uploader.ExtJsUploader} is used, the params are sent
|
||||||
|
* as GET params.
|
||||||
|
*/
|
||||||
|
uploadParams : {},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @cfg {Object}
|
||||||
|
*
|
||||||
|
* Extra HTTP headers to be added to the HTTP request uploading the file.
|
||||||
|
*/
|
||||||
|
uploadExtraHeaders : {},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @cfg {Number} [uploadTimeout=6000]
|
||||||
|
*
|
||||||
|
* The time after the upload request times out - in miliseconds.
|
||||||
|
*/
|
||||||
|
uploadTimeout : 60000,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @cfg {Object/String}
|
||||||
|
*
|
||||||
|
* Encoder object/class used to encode the filename header. Usually used, when the filename
|
||||||
|
* contains non-ASCII characters. If an encoder is used, the server backend has to be
|
||||||
|
* modified accordingly to decode the value.
|
||||||
|
*/
|
||||||
|
filenameEncoder : null,
|
||||||
|
|
||||||
|
// strings
|
||||||
|
textOk : 'OK',
|
||||||
|
textUpload : 'Upload',
|
||||||
|
textBrowse : 'Browse',
|
||||||
|
textAbort : 'Abort',
|
||||||
|
textRemoveSelected : 'Remove selected',
|
||||||
|
textRemoveAll : 'Remove all',
|
||||||
|
|
||||||
|
// grid strings
|
||||||
|
textFilename : 'Filename',
|
||||||
|
textSize : 'Size',
|
||||||
|
textType : 'Type',
|
||||||
|
textStatus : 'Status',
|
||||||
|
textProgress : '%',
|
||||||
|
|
||||||
|
// status toolbar strings
|
||||||
|
selectionMessageText : 'Selected {0} file(s), {1}',
|
||||||
|
uploadMessageText : 'Upload progress {0}% ({1} of {2} souborů)',
|
||||||
|
|
||||||
|
// browse button
|
||||||
|
buttonText : 'Browse...'
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property {Ext.ux.upload.Queue}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
queue : null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property {Ext.ux.upload.ItemGridPanel}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
grid : null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property {Ext.ux.upload.Manager}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
uploadManager : null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property {Ext.ux.upload.StatusBar}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
statusBar : null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property {Ext.ux.upload.BrowseButton}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
browseButton : null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
initComponent : function() {
|
||||||
|
|
||||||
|
this.queue = this.initQueue();
|
||||||
|
|
||||||
|
this.grid = Ext.create('Ext.ux.upload.ItemGridPanel', {
|
||||||
|
queue : this.queue,
|
||||||
|
textFilename : this.textFilename,
|
||||||
|
textSize : this.textSize,
|
||||||
|
textType : this.textType,
|
||||||
|
textStatus : this.textStatus,
|
||||||
|
textProgress : this.textProgress
|
||||||
|
});
|
||||||
|
|
||||||
|
this.uploadManager = this.createUploadManager();
|
||||||
|
|
||||||
|
this.uploadManager.on('uploadcomplete', this.onUploadComplete, this);
|
||||||
|
this.uploadManager.on('itemuploadsuccess', this.onItemUploadSuccess, this);
|
||||||
|
this.uploadManager.on('itemuploadfailure', this.onItemUploadFailure, this);
|
||||||
|
|
||||||
|
this.statusBar = Ext.create('Ext.ux.upload.StatusBar', {
|
||||||
|
dock : 'bottom',
|
||||||
|
selectionMessageText : this.selectionMessageText,
|
||||||
|
uploadMessageText : this.uploadMessageText
|
||||||
|
});
|
||||||
|
|
||||||
|
Ext.apply(this, {
|
||||||
|
title : this.dialogTitle,
|
||||||
|
autoScroll : true,
|
||||||
|
layout : 'fit',
|
||||||
|
uploading : false,
|
||||||
|
items : [
|
||||||
|
this.grid
|
||||||
|
],
|
||||||
|
dockedItems : [
|
||||||
|
this.getTopToolbarConfig(), this.statusBar
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
this.on('afterrender', function() {
|
||||||
|
this.stateInit();
|
||||||
|
}, this);
|
||||||
|
|
||||||
|
this.callParent(arguments);
|
||||||
|
},
|
||||||
|
|
||||||
|
createUploadManager : function() {
|
||||||
|
var uploaderOptions = this.getUploaderOptions() || {};
|
||||||
|
|
||||||
|
Ext.applyIf(uploaderOptions, {
|
||||||
|
url : this.uploadUrl,
|
||||||
|
params : this.uploadParams,
|
||||||
|
extraHeaders : this.uploadExtraHeaders,
|
||||||
|
timeout : this.uploadTimeout
|
||||||
|
});
|
||||||
|
|
||||||
|
var uploadManager = Ext.create('Ext.ux.upload.Manager', {
|
||||||
|
uploader : this.uploader,
|
||||||
|
uploaderOptions : uploaderOptions,
|
||||||
|
synchronous : this.getSynchronous(),
|
||||||
|
filenameEncoder : this.getFilenameEncoder()
|
||||||
|
});
|
||||||
|
|
||||||
|
return uploadManager;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*
|
||||||
|
* Returns the config object for the top toolbar.
|
||||||
|
*
|
||||||
|
* @return {Array}
|
||||||
|
*/
|
||||||
|
getTopToolbarConfig : function() {
|
||||||
|
|
||||||
|
this.browseButton = Ext.create('Ext.ux.upload.BrowseButton', {
|
||||||
|
itemId : 'button_browse',
|
||||||
|
buttonText : this.buttonText
|
||||||
|
});
|
||||||
|
this.browseButton.on('fileselected', this.onFileSelection, this);
|
||||||
|
|
||||||
|
return {
|
||||||
|
xtype : 'toolbar',
|
||||||
|
itemId : 'topToolbar',
|
||||||
|
dock : 'top',
|
||||||
|
items : [
|
||||||
|
this.browseButton,
|
||||||
|
'-',
|
||||||
|
{
|
||||||
|
itemId : 'button_upload',
|
||||||
|
text : this.textUpload,
|
||||||
|
iconCls : 'ux-mu-icon-action-upload',
|
||||||
|
scope : this,
|
||||||
|
handler : this.onInitUpload
|
||||||
|
},
|
||||||
|
'-',
|
||||||
|
{
|
||||||
|
itemId : 'button_abort',
|
||||||
|
text : this.textAbort,
|
||||||
|
iconCls : 'ux-mu-icon-action-abort',
|
||||||
|
scope : this,
|
||||||
|
handler : this.onAbortUpload,
|
||||||
|
disabled : true
|
||||||
|
},
|
||||||
|
'->',
|
||||||
|
{
|
||||||
|
itemId : 'button_remove_selected',
|
||||||
|
text : this.textRemoveSelected,
|
||||||
|
iconCls : 'ux-mu-icon-action-remove',
|
||||||
|
scope : this,
|
||||||
|
handler : this.onMultipleRemove
|
||||||
|
},
|
||||||
|
'-',
|
||||||
|
{
|
||||||
|
itemId : 'button_remove_all',
|
||||||
|
text : this.textRemoveAll,
|
||||||
|
iconCls : 'ux-mu-icon-action-remove',
|
||||||
|
scope : this,
|
||||||
|
handler : this.onRemoveAll
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*
|
||||||
|
* Initializes and returns the queue object.
|
||||||
|
*
|
||||||
|
* @return {Ext.ux.upload.Queue}
|
||||||
|
*/
|
||||||
|
initQueue : function() {
|
||||||
|
var queue = Ext.create('Ext.ux.upload.Queue');
|
||||||
|
|
||||||
|
queue.on('queuechange', this.onQueueChange, this);
|
||||||
|
|
||||||
|
return queue;
|
||||||
|
},
|
||||||
|
|
||||||
|
onInitUpload : function() {
|
||||||
|
if (!this.queue.getCount()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.stateUpload();
|
||||||
|
this.startUpload();
|
||||||
|
},
|
||||||
|
|
||||||
|
onAbortUpload : function() {
|
||||||
|
this.uploadManager.abortUpload();
|
||||||
|
this.finishUpload();
|
||||||
|
this.switchState();
|
||||||
|
},
|
||||||
|
|
||||||
|
onUploadComplete : function(manager, queue, errorCount) {
|
||||||
|
this.finishUpload();
|
||||||
|
if (errorCount) {
|
||||||
|
this.stateQueue();
|
||||||
|
} else {
|
||||||
|
this.stateInit();
|
||||||
|
}
|
||||||
|
this.fireEvent('uploadcomplete', this, manager, queue.getUploadedItems(), errorCount);
|
||||||
|
manager.resetUpload();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*
|
||||||
|
* Executes after files has been selected for upload through the "Browse" button. Updates the upload queue with the
|
||||||
|
* new files.
|
||||||
|
*
|
||||||
|
* @param {Ext.ux.upload.BrowseButton} input
|
||||||
|
* @param {FileList} files
|
||||||
|
*/
|
||||||
|
onFileSelection : function(input, files) {
|
||||||
|
this.queue.clearUploadedItems();
|
||||||
|
this.queue.addFiles(files);
|
||||||
|
this.browseButton.reset();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*
|
||||||
|
* Executes if there is a change in the queue. Updates the related components (grid, toolbar).
|
||||||
|
*
|
||||||
|
* @param {Ext.ux.upload.Queue} queue
|
||||||
|
*/
|
||||||
|
onQueueChange : function(queue) {
|
||||||
|
this.updateStatusBar();
|
||||||
|
|
||||||
|
this.switchState();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*
|
||||||
|
* Executes upon hitting the "multiple remove" button. Removes all selected items from the queue.
|
||||||
|
*/
|
||||||
|
onMultipleRemove : function() {
|
||||||
|
var records = this.grid.getSelectedRecords();
|
||||||
|
if (!records.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var keys = [];
|
||||||
|
var i;
|
||||||
|
var num = records.length;
|
||||||
|
|
||||||
|
for (i = 0; i < num; i++) {
|
||||||
|
keys.push(records[i].get('filename'));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.queue.removeItemsByKey(keys);
|
||||||
|
},
|
||||||
|
|
||||||
|
onRemoveAll : function() {
|
||||||
|
this.queue.clearItems();
|
||||||
|
},
|
||||||
|
|
||||||
|
onItemUploadSuccess : function(manager, item, info) {
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
onItemUploadFailure : function(manager, item, info) {
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
startUpload : function() {
|
||||||
|
this.uploading = true;
|
||||||
|
this.uploadManager.uploadQueue(this.queue);
|
||||||
|
},
|
||||||
|
|
||||||
|
finishUpload : function() {
|
||||||
|
this.uploading = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
isUploadActive : function() {
|
||||||
|
return this.uploading;
|
||||||
|
},
|
||||||
|
|
||||||
|
updateStatusBar : function() {
|
||||||
|
if (!this.statusBar) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var numFiles = this.queue.getCount();
|
||||||
|
|
||||||
|
this.statusBar.setSelectionMessage(this.queue.getCount(), this.queue.getTotalBytes());
|
||||||
|
},
|
||||||
|
|
||||||
|
getButton : function(itemId) {
|
||||||
|
var topToolbar = this.getDockedComponent('topToolbar');
|
||||||
|
if (topToolbar) {
|
||||||
|
return topToolbar.getComponent(itemId);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
|
switchButtons : function(info) {
|
||||||
|
var itemId;
|
||||||
|
for (itemId in info) {
|
||||||
|
this.switchButton(itemId, info[itemId]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
switchButton : function(itemId, on) {
|
||||||
|
var button = this.getButton(itemId);
|
||||||
|
|
||||||
|
if (button) {
|
||||||
|
if (on) {
|
||||||
|
button.enable();
|
||||||
|
} else {
|
||||||
|
button.disable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
switchState : function() {
|
||||||
|
if (this.uploading) {
|
||||||
|
this.stateUpload();
|
||||||
|
} else if (this.queue.getCount()) {
|
||||||
|
this.stateQueue();
|
||||||
|
} else {
|
||||||
|
this.stateInit();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
stateInit : function() {
|
||||||
|
this.switchButtons({
|
||||||
|
'button_browse' : 1,
|
||||||
|
'button_upload' : 0,
|
||||||
|
'button_abort' : 0,
|
||||||
|
'button_remove_all' : 1,
|
||||||
|
'button_remove_selected' : 1
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
stateQueue : function() {
|
||||||
|
this.switchButtons({
|
||||||
|
'button_browse' : 1,
|
||||||
|
'button_upload' : 1,
|
||||||
|
'button_abort' : 0,
|
||||||
|
'button_remove_all' : 1,
|
||||||
|
'button_remove_selected' : 1
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
stateUpload : function() {
|
||||||
|
this.switchButtons({
|
||||||
|
'button_browse' : 0,
|
||||||
|
'button_upload' : 0,
|
||||||
|
'button_abort' : 1,
|
||||||
|
'button_remove_all' : 1,
|
||||||
|
'button_remove_selected' : 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
|
@ -0,0 +1,279 @@
|
||||||
|
/**
|
||||||
|
* Data structure managing the upload file queue.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
Ext.define('Ext.ux.upload.Queue', {
|
||||||
|
extend : 'Ext.util.MixedCollection',
|
||||||
|
|
||||||
|
requires : [
|
||||||
|
'Ext.ux.upload.Item'
|
||||||
|
],
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param {Object} config
|
||||||
|
*/
|
||||||
|
constructor : function(config) {
|
||||||
|
|
||||||
|
this.callParent(arguments);
|
||||||
|
|
||||||
|
this.on('clear', function() {
|
||||||
|
this.fireEvent('queuechange', this);
|
||||||
|
}, this);
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds files to the queue.
|
||||||
|
*
|
||||||
|
* @param {FileList} fileList
|
||||||
|
*/
|
||||||
|
addFiles : function(fileList) {
|
||||||
|
var i;
|
||||||
|
var items = [];
|
||||||
|
var num = fileList.length;
|
||||||
|
|
||||||
|
if (!num) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < num; i++) {
|
||||||
|
items.push(this.createItem(fileList[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.addAll(items);
|
||||||
|
|
||||||
|
this.fireEvent('multiadd', this, items);
|
||||||
|
this.fireEvent('queuechange', this);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uploaded files are removed, the rest are set as ready.
|
||||||
|
*/
|
||||||
|
reset : function() {
|
||||||
|
this.clearUploadedItems();
|
||||||
|
|
||||||
|
this.each(function(item) {
|
||||||
|
item.reset();
|
||||||
|
}, this);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all queued items.
|
||||||
|
*
|
||||||
|
* @return {Ext.ux.upload.Item[]}
|
||||||
|
*/
|
||||||
|
getItems : function() {
|
||||||
|
return this.getRange();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of items by the specified status.
|
||||||
|
*
|
||||||
|
* @param {String/Array}
|
||||||
|
* @return {Ext.ux.upload.Item[]}
|
||||||
|
*/
|
||||||
|
getItemsByStatus : function(status) {
|
||||||
|
var itemsByStatus = [];
|
||||||
|
|
||||||
|
this.each(function(item, index, items) {
|
||||||
|
if (item.hasStatus(status)) {
|
||||||
|
itemsByStatus.push(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return itemsByStatus;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of items, that have already been uploaded.
|
||||||
|
*
|
||||||
|
* @return {Ext.ux.upload.Item[]}
|
||||||
|
*/
|
||||||
|
getUploadedItems : function() {
|
||||||
|
return this.getItemsByStatus('uploaded');
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of items, that have not been uploaded yet.
|
||||||
|
*
|
||||||
|
* @return {Ext.ux.upload.Item[]}
|
||||||
|
*/
|
||||||
|
getUploadingItems : function() {
|
||||||
|
return this.getItemsByStatus([
|
||||||
|
'ready', 'uploading'
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true, if there are items, that are currently being uploaded.
|
||||||
|
*
|
||||||
|
* @return {Boolean}
|
||||||
|
*/
|
||||||
|
existUploadingItems : function() {
|
||||||
|
return (this.getUploadingItems().length > 0);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the first "ready" item in the queue (with status STATUS_READY).
|
||||||
|
*
|
||||||
|
* @return {Ext.ux.upload.Item/null}
|
||||||
|
*/
|
||||||
|
getFirstReadyItem : function() {
|
||||||
|
var items = this.getRange();
|
||||||
|
var num = this.getCount();
|
||||||
|
var i;
|
||||||
|
|
||||||
|
for (i = 0; i < num; i++) {
|
||||||
|
if (items[i].isReady()) {
|
||||||
|
return items[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears all items from the queue.
|
||||||
|
*/
|
||||||
|
clearItems : function() {
|
||||||
|
this.clear();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the items, which have been already uploaded, from the queue.
|
||||||
|
*/
|
||||||
|
clearUploadedItems : function() {
|
||||||
|
this.removeItems(this.getUploadedItems());
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes items from the queue.
|
||||||
|
*
|
||||||
|
* @param {Ext.ux.upload.Item[]} items
|
||||||
|
*/
|
||||||
|
removeItems : function(items) {
|
||||||
|
var num = items.length;
|
||||||
|
var i;
|
||||||
|
|
||||||
|
if (!num) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < num; i++) {
|
||||||
|
this.remove(items[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.fireEvent('queuechange', this);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the items identified by the supplied array of keys.
|
||||||
|
*
|
||||||
|
* @param {Array} itemKeys
|
||||||
|
*/
|
||||||
|
removeItemsByKey : function(itemKeys) {
|
||||||
|
var i;
|
||||||
|
var num = itemKeys.length;
|
||||||
|
|
||||||
|
if (!num) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < num; i++) {
|
||||||
|
this.removeItemByKey(itemKeys[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.fireEvent('multiremove', this, itemKeys);
|
||||||
|
this.fireEvent('queuechange', this);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a single item by its key.
|
||||||
|
*
|
||||||
|
* @param {String} key
|
||||||
|
*/
|
||||||
|
removeItemByKey : function(key) {
|
||||||
|
this.removeAtKey(key);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform cleanup, after the upload has been aborted.
|
||||||
|
*/
|
||||||
|
recoverAfterAbort : function() {
|
||||||
|
this.each(function(item) {
|
||||||
|
if (!item.isUploaded() && !item.isReady()) {
|
||||||
|
item.reset();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*
|
||||||
|
* Initialize and return a new queue item for the corresponding File object.
|
||||||
|
*
|
||||||
|
* @param {File} file
|
||||||
|
* @return {Ext.ux.upload.Item}
|
||||||
|
*/
|
||||||
|
createItem : function(file) {
|
||||||
|
|
||||||
|
var item = Ext.create('Ext.ux.upload.Item', {
|
||||||
|
fileApiObject : file
|
||||||
|
});
|
||||||
|
|
||||||
|
item.on('changestatus', this.onItemChangeStatus, this);
|
||||||
|
item.on('progressupdate', this.onItemProgressUpdate, this);
|
||||||
|
|
||||||
|
return item;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A getKey() implementation to determine the key of an item in the collection.
|
||||||
|
*
|
||||||
|
* @param {Ext.ux.upload.Item} item
|
||||||
|
* @return {String}
|
||||||
|
*/
|
||||||
|
getKey : function(item) {
|
||||||
|
return item.getId();
|
||||||
|
},
|
||||||
|
|
||||||
|
onItemChangeStatus : function(item, status) {
|
||||||
|
this.fireEvent('itemchangestatus', this, item, status);
|
||||||
|
},
|
||||||
|
|
||||||
|
onItemProgressUpdate : function(item) {
|
||||||
|
this.fireEvent('itemprogressupdate', this, item);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true, if the item is the last item in the queue.
|
||||||
|
*
|
||||||
|
* @param {Ext.ux.upload.Item} item
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
isLast : function(item) {
|
||||||
|
var lastItem = this.last();
|
||||||
|
if (lastItem && item.getId() == lastItem.getId()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns total bytes of all files in the queue.
|
||||||
|
*
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
getTotalBytes : function() {
|
||||||
|
var bytes = 0;
|
||||||
|
|
||||||
|
this.each(function(item, index, length) {
|
||||||
|
bytes += item.getSize();
|
||||||
|
}, this);
|
||||||
|
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,43 @@
|
||||||
|
/**
|
||||||
|
* Upload status bar.
|
||||||
|
*
|
||||||
|
* @class Ext.ux.upload.StatusBar
|
||||||
|
* @extends Ext.toolbar.Toolbar
|
||||||
|
*/
|
||||||
|
Ext.define('Ext.ux.upload.StatusBar', {
|
||||||
|
extend : 'Ext.toolbar.Toolbar',
|
||||||
|
|
||||||
|
config : {
|
||||||
|
selectionMessageText : 'Selected {0} file(s), {1}',
|
||||||
|
uploadMessageText : 'Upload progress {0}% ({1} of {2} file(s))',
|
||||||
|
textComponentId : 'mu-status-text'
|
||||||
|
},
|
||||||
|
|
||||||
|
initComponent : function() {
|
||||||
|
|
||||||
|
Ext.apply(this, {
|
||||||
|
items : [
|
||||||
|
{
|
||||||
|
xtype : 'tbtext',
|
||||||
|
itemId : this.textComponentId,
|
||||||
|
text : ' '
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
this.callParent(arguments);
|
||||||
|
},
|
||||||
|
|
||||||
|
setText : function(text) {
|
||||||
|
this.getComponent(this.textComponentId).setText(text);
|
||||||
|
},
|
||||||
|
|
||||||
|
setSelectionMessage : function(fileCount, byteCount) {
|
||||||
|
this.setText(Ext.String.format(this.selectionMessageText, fileCount, Ext.util.Format.fileSize(byteCount)));
|
||||||
|
},
|
||||||
|
|
||||||
|
setUploadMessage : function(progressPercent, uploadedFiles, totalFiles) {
|
||||||
|
this.setText(Ext.String.format(this.uploadMessageText, progressPercent, uploadedFiles, totalFiles));
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
|
@ -0,0 +1,30 @@
|
||||||
|
Ext.define('Ext.ux.upload.Store', {
|
||||||
|
extend : 'Ext.data.Store',
|
||||||
|
|
||||||
|
fields : [
|
||||||
|
{
|
||||||
|
name : 'filename',
|
||||||
|
type : 'string'
|
||||||
|
}, {
|
||||||
|
name : 'size',
|
||||||
|
type : 'integer'
|
||||||
|
}, {
|
||||||
|
name : 'type',
|
||||||
|
type : 'string'
|
||||||
|
}, {
|
||||||
|
name : 'status',
|
||||||
|
type : 'string'
|
||||||
|
}, {
|
||||||
|
name : 'message',
|
||||||
|
type : 'string'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
proxy : {
|
||||||
|
type : 'memory',
|
||||||
|
reader : {
|
||||||
|
type : 'array',
|
||||||
|
idProperty : 'filename'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,59 @@
|
||||||
|
@CHARSET "UTF-8";
|
||||||
|
|
||||||
|
.ux-mu-status-value {
|
||||||
|
float: right;
|
||||||
|
min-width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
margin: 0 3px 0 2px;
|
||||||
|
cursor: pointer;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-help {
|
||||||
|
background-image: url(../img/help.png) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Action icons
|
||||||
|
*/
|
||||||
|
.ux-mu-icon-action-ok {
|
||||||
|
background-image: url(../img/tick.png) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ux-mu-icon-action-upload {
|
||||||
|
background-image: url(../img/arrow_up.png) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ux-mu-icon-action-abort {
|
||||||
|
background-image: url(../img/cancel.png) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ux-mu-icon-action-remove {
|
||||||
|
background-image: url(../img/delete.png) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ux-mu-icon-action-browse {
|
||||||
|
background-image: url(../img/folder.png) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Upload status icons
|
||||||
|
*/
|
||||||
|
.ux-mu-icon-upload-ready { /*
|
||||||
|
background-image: url(../img/ready.png) !important;
|
||||||
|
*/
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.ux-mu-icon-upload-uploading {
|
||||||
|
background-image: url(../img/loading.gif) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ux-mu-icon-upload-uploaded {
|
||||||
|
background-image: url(../img/accept.png) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ux-mu-icon-upload-uploaderror {
|
||||||
|
background-image: url(../img/exclamation.png) !important;
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
/**
|
||||||
|
* Modified Ext.data.Connection object, adapted to be able to report progress.
|
||||||
|
*/
|
||||||
|
Ext.define('Ext.ux.upload.data.Connection', {
|
||||||
|
extend : 'Ext.data.Connection',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @cfg {Function}
|
||||||
|
*
|
||||||
|
* Callback fired when a progress event occurs (xhr.upload.onprogress).
|
||||||
|
*/
|
||||||
|
progressCallback : null,
|
||||||
|
|
||||||
|
request : function(options) {
|
||||||
|
var progressCallback = options.progress;
|
||||||
|
if (progressCallback) {
|
||||||
|
this.progressCallback = progressCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.callParent(arguments);
|
||||||
|
},
|
||||||
|
|
||||||
|
getXhrInstance : function() {
|
||||||
|
var xhr = this.callParent(arguments);
|
||||||
|
|
||||||
|
if (this.progressCallback) {
|
||||||
|
xhr.upload.onprogress = this.progressCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
return xhr;
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,15 @@
|
||||||
|
/**
|
||||||
|
* Abstract filename encoder.
|
||||||
|
*/
|
||||||
|
Ext.define('Ext.ux.upload.header.AbstractFilenameEncoder', {
|
||||||
|
|
||||||
|
config : {},
|
||||||
|
|
||||||
|
type : 'generic',
|
||||||
|
|
||||||
|
encode : function(filename) {},
|
||||||
|
|
||||||
|
getType : function() {
|
||||||
|
return this.type;
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,15 @@
|
||||||
|
/**
|
||||||
|
* Base64 filename encoder - uses the built-in function window.btoa().
|
||||||
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/Window.btoa
|
||||||
|
*/
|
||||||
|
Ext.define('Ext.ux.upload.header.Base64FilenameEncoder', {
|
||||||
|
extend : 'Ext.ux.upload.header.AbstractFilenameEncoder',
|
||||||
|
|
||||||
|
config : {},
|
||||||
|
|
||||||
|
type : 'base64',
|
||||||
|
|
||||||
|
encode : function(filename) {
|
||||||
|
return window.btoa(unescape(encodeURIComponent(filename)));
|
||||||
|
}
|
||||||
|
});
|
After Width: | Height: | Size: 781 B |
After Width: | Height: | Size: 723 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 372 B |
After Width: | Height: | Size: 587 B |
After Width: | Height: | Size: 715 B |
After Width: | Height: | Size: 701 B |
After Width: | Height: | Size: 537 B |
After Width: | Height: | Size: 786 B |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 537 B |
|
@ -0,0 +1,153 @@
|
||||||
|
/**
|
||||||
|
* Abstract uploader object.
|
||||||
|
*
|
||||||
|
* The uploader object implements the the upload itself - transports data to the server. This is an "abstract" object
|
||||||
|
* used as a base object for all uploader objects.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
Ext.define('Ext.ux.upload.uploader.AbstractUploader', {
|
||||||
|
mixins : {
|
||||||
|
observable : 'Ext.util.Observable'
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @cfg {Number} [maxFileSize=50000000]
|
||||||
|
*
|
||||||
|
* (NOT IMPLEMENTED) The maximum file size allowed to be uploaded.
|
||||||
|
*/
|
||||||
|
maxFileSize : 50000000,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @cfg {String} url (required)
|
||||||
|
*
|
||||||
|
* The server URL to upload to.
|
||||||
|
*/
|
||||||
|
url : '',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @cfg {Number} [timeout=60000]
|
||||||
|
*
|
||||||
|
* The connection timeout in miliseconds.
|
||||||
|
*/
|
||||||
|
timeout : 60 * 1000,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @cfg {String} [contentType='application/binary']
|
||||||
|
*
|
||||||
|
* The content type announced in the HTTP headers. It is autodetected if possible, but if autodetection
|
||||||
|
* cannot be done, this value is set as content type header.
|
||||||
|
*/
|
||||||
|
contentType : 'application/binary',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @cfg {String} [filenameHeader='X-File-Name']
|
||||||
|
*
|
||||||
|
* The name of the HTTP header containing the filename.
|
||||||
|
*/
|
||||||
|
filenameHeader : 'X-File-Name',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @cfg {String} [sizeHeader='X-File-Size']
|
||||||
|
*
|
||||||
|
* The name of the HTTP header containing the size of the file.
|
||||||
|
*/
|
||||||
|
sizeHeader : 'X-File-Size',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @cfg {String} [typeHeader='X-File-Type']
|
||||||
|
*
|
||||||
|
* The name of the HTTP header containing the MIME type of the file.
|
||||||
|
*/
|
||||||
|
typeHeader : 'X-File-Type',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @cfg {Object}
|
||||||
|
*
|
||||||
|
* Additional parameters to be sent with the upload request.
|
||||||
|
*/
|
||||||
|
params : {},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @cfg {Object}
|
||||||
|
*
|
||||||
|
* Extra headers to be sent with the upload request.
|
||||||
|
*/
|
||||||
|
extraHeaders : {},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @cfg {Object/String}
|
||||||
|
*
|
||||||
|
* Encoder object/class used to encode the filename header. Usually used, when the filename
|
||||||
|
* contains non-ASCII characters.
|
||||||
|
*/
|
||||||
|
filenameEncoder : null,
|
||||||
|
|
||||||
|
filenameEncoderHeader : 'X-Filename-Encoder',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
* @param {Object} [config]
|
||||||
|
*/
|
||||||
|
constructor : function(config) {
|
||||||
|
this.mixins.observable.constructor.call(this);
|
||||||
|
|
||||||
|
this.initConfig(config);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
initHeaders : function(item) {
|
||||||
|
var headers = this.extraHeaders || {},
|
||||||
|
filename = item.getFilename();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If there is a filename encoder defined - use it to encode the filename
|
||||||
|
* in the header and set the type of the encoder as an additional header.
|
||||||
|
*/
|
||||||
|
var filenameEncoder = this.initFilenameEncoder();
|
||||||
|
if (filenameEncoder) {
|
||||||
|
filename = filenameEncoder.encode(filename);
|
||||||
|
headers[this.filenameEncoderHeader] = filenameEncoder.getType();
|
||||||
|
}
|
||||||
|
headers[this.filenameHeader] = filename;
|
||||||
|
headers[this.sizeHeader] = item.getSize();
|
||||||
|
headers[this.typeHeader] = item.getType();
|
||||||
|
|
||||||
|
return headers;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @abstract
|
||||||
|
*
|
||||||
|
* Upload a single item (file).
|
||||||
|
* **Implement in subclass**
|
||||||
|
*
|
||||||
|
* @param {Ext.ux.upload.Item} item
|
||||||
|
*/
|
||||||
|
uploadItem : function(item) {},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @abstract
|
||||||
|
*
|
||||||
|
* Aborts the current upload.
|
||||||
|
* **Implement in subclass**
|
||||||
|
*/
|
||||||
|
abortUpload : function() {},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
initFilenameEncoder : function() {
|
||||||
|
if (Ext.isString(this.filenameEncoder)) {
|
||||||
|
this.filenameEncoder = Ext.create(this.filenameEncoder);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Ext.isObject(this.filenameEncoder)) {
|
||||||
|
return this.filenameEncoder;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
|
@ -0,0 +1,44 @@
|
||||||
|
/**
|
||||||
|
* Abstract uploader with features common for all XHR based uploaders.
|
||||||
|
*/
|
||||||
|
Ext.define('Ext.ux.upload.uploader.AbstractXhrUploader', {
|
||||||
|
extend : 'Ext.ux.upload.uploader.AbstractUploader',
|
||||||
|
|
||||||
|
onUploadSuccess : function(response, options, item) {
|
||||||
|
var info = {
|
||||||
|
success : true,
|
||||||
|
message : '',
|
||||||
|
response : response
|
||||||
|
};
|
||||||
|
|
||||||
|
if (response.responseText) {
|
||||||
|
var responseJson = Ext.decode(response.responseText);
|
||||||
|
if (responseJson) {
|
||||||
|
Ext.apply(info, {
|
||||||
|
success : responseJson.success,
|
||||||
|
message : responseJson.message
|
||||||
|
});
|
||||||
|
|
||||||
|
var eventName = info.success ? 'uploadsuccess' : 'uploadfailure';
|
||||||
|
this.fireEvent(eventName, item, info);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.fireEvent('uploadsuccess', item, info);
|
||||||
|
},
|
||||||
|
|
||||||
|
onUploadFailure : function(response, options, item) {
|
||||||
|
var info = {
|
||||||
|
success : false,
|
||||||
|
message : 'http error',
|
||||||
|
response : response
|
||||||
|
};
|
||||||
|
|
||||||
|
this.fireEvent('uploadfailure', item, info);
|
||||||
|
},
|
||||||
|
|
||||||
|
onUploadProgress : function(event, item) {
|
||||||
|
this.fireEvent('uploadprogress', item, event);
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,22 @@
|
||||||
|
Ext.define('Ext.ux.upload.uploader.DummyUploader', {
|
||||||
|
extend : 'Ext.ux.upload.uploader.AbstractUploader',
|
||||||
|
|
||||||
|
delay : 1000,
|
||||||
|
|
||||||
|
uploadItem : function(item) {
|
||||||
|
item.setUploading();
|
||||||
|
|
||||||
|
var task = new Ext.util.DelayedTask(function() {
|
||||||
|
this.fireEvent('uploadsuccess', item, {
|
||||||
|
success : true,
|
||||||
|
message : 'OK',
|
||||||
|
response : null
|
||||||
|
});
|
||||||
|
}, this);
|
||||||
|
|
||||||
|
task.delay(this.delay);
|
||||||
|
},
|
||||||
|
|
||||||
|
abortUpload : function() {
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,133 @@
|
||||||
|
/**
|
||||||
|
* Uploader implementation - with the Connection object in ExtJS 4
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
Ext.define('Ext.ux.upload.uploader.ExtJsUploader', {
|
||||||
|
extend : 'Ext.ux.upload.uploader.AbstractXhrUploader',
|
||||||
|
|
||||||
|
requires : [
|
||||||
|
'Ext.ux.upload.data.Connection'
|
||||||
|
],
|
||||||
|
|
||||||
|
config : {
|
||||||
|
/**
|
||||||
|
* @cfg {String} [method='PUT']
|
||||||
|
*
|
||||||
|
* The HTTP method to be used.
|
||||||
|
*/
|
||||||
|
method : 'PUT',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @cfg {Ext.data.Connection}
|
||||||
|
*
|
||||||
|
* If set, this connection object will be used when uploading files.
|
||||||
|
*/
|
||||||
|
connection : null
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property
|
||||||
|
* @private
|
||||||
|
*
|
||||||
|
* The connection object.
|
||||||
|
*/
|
||||||
|
conn : null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*
|
||||||
|
* Initializes and returns the connection object.
|
||||||
|
*
|
||||||
|
* @return {Ext.ux.upload.data.Connection}
|
||||||
|
*/
|
||||||
|
initConnection : function() {
|
||||||
|
var conn,
|
||||||
|
url = this.url;
|
||||||
|
|
||||||
|
if (this.connection instanceof Ext.data.Connection) {
|
||||||
|
conn = this.connection;
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if (this.params) {
|
||||||
|
url = Ext.urlAppend(url, Ext.urlEncode(this.params));
|
||||||
|
}
|
||||||
|
|
||||||
|
conn = Ext.create('Ext.ux.upload.data.Connection', {
|
||||||
|
disableCaching : true,
|
||||||
|
method : this.method,
|
||||||
|
url : url,
|
||||||
|
timeout : this.timeout,
|
||||||
|
defaultHeaders : {
|
||||||
|
'Content-Type' : this.contentType,
|
||||||
|
'X-Requested-With' : 'XMLHttpRequest'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return conn;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
initHeaders : function(item) {
|
||||||
|
var headers = this.callParent(arguments);
|
||||||
|
|
||||||
|
headers['Content-Type'] = item.getType();
|
||||||
|
|
||||||
|
return headers;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements {@link Ext.ux.upload.uploader.AbstractUploader#uploadItem}
|
||||||
|
*
|
||||||
|
* @param {Ext.ux.upload.Item} item
|
||||||
|
*/
|
||||||
|
uploadItem : function(item) {
|
||||||
|
var file = item.getFileApiObject();
|
||||||
|
if (!file) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
item.setUploading();
|
||||||
|
|
||||||
|
this.conn = this.initConnection();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Passing the File object directly as the "rawFata" option.
|
||||||
|
* Specs:
|
||||||
|
* https://dvcs.w3.org/hg/xhr/raw-file/tip/Overview.html#the-send()-method
|
||||||
|
* http://dev.w3.org/2006/webapi/FileAPI/#blob
|
||||||
|
*/
|
||||||
|
this.conn.request({
|
||||||
|
scope : this,
|
||||||
|
headers : this.initHeaders(item),
|
||||||
|
rawData : file,
|
||||||
|
|
||||||
|
success : Ext.Function.bind(this.onUploadSuccess, this, [
|
||||||
|
item
|
||||||
|
], true),
|
||||||
|
failure : Ext.Function.bind(this.onUploadFailure, this, [
|
||||||
|
item
|
||||||
|
], true),
|
||||||
|
progress : Ext.Function.bind(this.onUploadProgress, this, [
|
||||||
|
item
|
||||||
|
], true)
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements {@link Ext.ux.upload.uploader.AbstractUploader#abortUpload}
|
||||||
|
*/
|
||||||
|
abortUpload : function() {
|
||||||
|
if (this.conn) {
|
||||||
|
/*
|
||||||
|
* If we don't suspend the events, the connection abortion will cause a failure event.
|
||||||
|
*/
|
||||||
|
this.suspendEvents();
|
||||||
|
this.conn.abort();
|
||||||
|
this.resumeEvents();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,87 @@
|
||||||
|
/**
|
||||||
|
* Uploader implementation which uses a FormData object to send files through XHR requests.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
Ext.define('Ext.ux.upload.uploader.FormDataUploader', {
|
||||||
|
extend : 'Ext.ux.upload.uploader.AbstractXhrUploader',
|
||||||
|
|
||||||
|
requires : [
|
||||||
|
'Ext.ux.upload.data.Connection'
|
||||||
|
],
|
||||||
|
|
||||||
|
method : 'POST',
|
||||||
|
xhr : null,
|
||||||
|
|
||||||
|
initConnection : function() {
|
||||||
|
if (this.params) {
|
||||||
|
this.url = Ext.urlAppend(this.url, Ext.urlEncode(this.params));
|
||||||
|
}
|
||||||
|
|
||||||
|
var xhr = new XMLHttpRequest(),
|
||||||
|
method = this.method,
|
||||||
|
url = this.url;
|
||||||
|
|
||||||
|
xhr.open(method, url, true);
|
||||||
|
|
||||||
|
this.abortXhr = function() {
|
||||||
|
this.suspendEvents();
|
||||||
|
xhr.abort();
|
||||||
|
this.resumeEvents();
|
||||||
|
};
|
||||||
|
|
||||||
|
return xhr;
|
||||||
|
},
|
||||||
|
|
||||||
|
uploadItem : function(item) {
|
||||||
|
var file = item.getFileApiObject();
|
||||||
|
|
||||||
|
item.setUploading();
|
||||||
|
|
||||||
|
var formData = new FormData();
|
||||||
|
formData.append(file.name, file);
|
||||||
|
|
||||||
|
var xhr = this.initConnection();
|
||||||
|
|
||||||
|
xhr.setRequestHeader(this.filenameHeader, file.name);
|
||||||
|
xhr.setRequestHeader(this.sizeHeader, file.size);
|
||||||
|
xhr.setRequestHeader(this.typeHeader, file.type);
|
||||||
|
|
||||||
|
var loadendhandler = Ext.Function.bind(this.onLoadEnd, this, [
|
||||||
|
item
|
||||||
|
], true);
|
||||||
|
|
||||||
|
var progresshandler = Ext.Function.bind(this.onUploadProgress, this, [
|
||||||
|
item
|
||||||
|
], true);
|
||||||
|
|
||||||
|
xhr.addEventListener('loadend', loadendhandler, true);
|
||||||
|
xhr.upload.addEventListener("progress", progresshandler, true);
|
||||||
|
|
||||||
|
xhr.send(formData);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements {@link Ext.ux.upload.uploader.AbstractUploader#abortUpload}
|
||||||
|
*/
|
||||||
|
abortUpload : function() {
|
||||||
|
this.abortXhr();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @protected
|
||||||
|
*
|
||||||
|
* A placeholder for the abort procedure.
|
||||||
|
*/
|
||||||
|
abortXhr : function() {
|
||||||
|
},
|
||||||
|
|
||||||
|
onLoadEnd : function(event, item) {
|
||||||
|
var response = event.target;
|
||||||
|
|
||||||
|
if (response.status != 200) {
|
||||||
|
return this.onUploadFailure(response, null, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.onUploadSuccess(response, null, item);
|
||||||
|
}
|
||||||
|
});
|
127
orun/extjs/static/extjs-upload-widget/lib/upload/uploader/LegacyExtJsUploader.js
vendored
100644
|
@ -0,0 +1,127 @@
|
||||||
|
/**
|
||||||
|
* Uploader implementation - with the Connection object in ExtJS 4
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
Ext.define('Ext.ux.upload.uploader.ExtJsUploader', {
|
||||||
|
extend : 'Ext.ux.upload.uploader.AbstractUploader',
|
||||||
|
|
||||||
|
requires : [
|
||||||
|
'Ext.ux.upload.data.Connection'
|
||||||
|
],
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property
|
||||||
|
*
|
||||||
|
* The connection object.
|
||||||
|
*/
|
||||||
|
conn : null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*
|
||||||
|
* Initializes and returns the connection object.
|
||||||
|
*
|
||||||
|
* @return {Ext.ux.upload.data.Connection}
|
||||||
|
*/
|
||||||
|
initConnection : function() {
|
||||||
|
var url = this.url;
|
||||||
|
if (this.params) {
|
||||||
|
url = Ext.urlAppend(url, Ext.urlEncode(this.params));
|
||||||
|
}
|
||||||
|
|
||||||
|
var conn = Ext.create('Ext.ux.upload.data.Connection', {
|
||||||
|
disableCaching : true,
|
||||||
|
method : this.method,
|
||||||
|
url : url,
|
||||||
|
timeout : this.timeout,
|
||||||
|
defaultHeaders : {
|
||||||
|
'Content-Type' : this.contentType,
|
||||||
|
'X-Requested-With' : 'XMLHttpRequest'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return conn;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements {@link Ext.ux.upload.uploader.AbstractUploader#uploadItem}
|
||||||
|
*
|
||||||
|
* @param {Ext.ux.upload.Item} item
|
||||||
|
*/
|
||||||
|
uploadItem : function(item) {
|
||||||
|
var file = item.getFileApiObject();
|
||||||
|
if (!file) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
item.setUploading();
|
||||||
|
|
||||||
|
this.conn = this.initConnection();
|
||||||
|
|
||||||
|
this.conn.request({
|
||||||
|
scope : this,
|
||||||
|
headers : this.initHeaders(item),
|
||||||
|
xmlData : file,
|
||||||
|
|
||||||
|
success : Ext.Function.bind(this.onUploadSuccess, this, [
|
||||||
|
item
|
||||||
|
], true),
|
||||||
|
failure : Ext.Function.bind(this.onUploadFailure, this, [
|
||||||
|
item
|
||||||
|
], true),
|
||||||
|
progress : Ext.Function.bind(this.onUploadProgress, this, [
|
||||||
|
item
|
||||||
|
], true)
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements {@link Ext.ux.upload.uploader.AbstractUploader#abortUpload}
|
||||||
|
*/
|
||||||
|
abortUpload : function() {
|
||||||
|
if (this.conn) {
|
||||||
|
this.conn.abort();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onUploadSuccess : function(response, options, item) {
|
||||||
|
var info = {
|
||||||
|
success : false,
|
||||||
|
message : 'general error',
|
||||||
|
response : response
|
||||||
|
};
|
||||||
|
|
||||||
|
if (response.responseText) {
|
||||||
|
var responseJson = Ext.decode(response.responseText);
|
||||||
|
if (responseJson && responseJson.success) {
|
||||||
|
Ext.apply(info, {
|
||||||
|
success : responseJson.success,
|
||||||
|
message : responseJson.message
|
||||||
|
});
|
||||||
|
|
||||||
|
this.fireEvent('uploadsuccess', item, info);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ext.apply(info, {
|
||||||
|
message : responseJson.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.fireEvent('uploadfailure', item, info);
|
||||||
|
},
|
||||||
|
|
||||||
|
onUploadFailure : function(response, options, item) {
|
||||||
|
var info = {
|
||||||
|
success : false,
|
||||||
|
message : 'http error',
|
||||||
|
response : response
|
||||||
|
};
|
||||||
|
|
||||||
|
this.fireEvent('uploadfailure', item, info);
|
||||||
|
},
|
||||||
|
|
||||||
|
onUploadProgress : function(event, item) {
|
||||||
|
this.fireEvent('uploadprogress', item, event);
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,23 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
function _log($value)
|
||||||
|
{
|
||||||
|
error_log(print_r($value, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function _response($success = true, $message = 'OK')
|
||||||
|
{
|
||||||
|
$response = array(
|
||||||
|
'success' => $success,
|
||||||
|
'message' => $message
|
||||||
|
);
|
||||||
|
|
||||||
|
echo json_encode($response);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
function _error($message)
|
||||||
|
{
|
||||||
|
return _response(false, $message);
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?php
|
||||||
|
return array(
|
||||||
|
'upload_dir' => '/tmp/test-upload-dir',
|
||||||
|
'fake' => false
|
||||||
|
);
|
|
@ -0,0 +1,138 @@
|
||||||
|
Ext.Loader.setPath({
|
||||||
|
'Ext.ux' : 'external'
|
||||||
|
});
|
||||||
|
|
||||||
|
Ext.application({
|
||||||
|
|
||||||
|
requires : [
|
||||||
|
'Ext.ux.upload.Dialog'
|
||||||
|
],
|
||||||
|
|
||||||
|
name : 'Example',
|
||||||
|
|
||||||
|
appFolder : 'app',
|
||||||
|
|
||||||
|
launch : function() {
|
||||||
|
debug = console;
|
||||||
|
|
||||||
|
Ext.create('Ext.container.Viewport', {
|
||||||
|
layout : 'fit'
|
||||||
|
});
|
||||||
|
|
||||||
|
var appPanel = Ext.create('Ext.window.Window', {
|
||||||
|
title : 'Files',
|
||||||
|
width : 600,
|
||||||
|
height : 400,
|
||||||
|
closable : false,
|
||||||
|
modal : true,
|
||||||
|
bodyPadding : 5,
|
||||||
|
|
||||||
|
uploadComplete : function(items) {
|
||||||
|
var output = 'Uploaded files: <br>';
|
||||||
|
Ext.Array.each(items, function(item) {
|
||||||
|
output += item.getFilename() + ' (' + item.getType() + ', '
|
||||||
|
+ Ext.util.Format.fileSize(item.getSize()) + ')' + '<br>';
|
||||||
|
});
|
||||||
|
|
||||||
|
this.update(output);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
appPanel.syncCheckbox = Ext.create('Ext.form.field.Checkbox', {
|
||||||
|
inputValue : true,
|
||||||
|
checked : true
|
||||||
|
});
|
||||||
|
|
||||||
|
appPanel.addDocked({
|
||||||
|
xtype : 'toolbar',
|
||||||
|
dock : 'top',
|
||||||
|
items : [
|
||||||
|
{
|
||||||
|
xtype : 'button',
|
||||||
|
text : 'Raw PUT/POST Upload',
|
||||||
|
scope : appPanel,
|
||||||
|
handler : function() {
|
||||||
|
|
||||||
|
var uploadPanel = Ext.create('Ext.ux.upload.Panel', {
|
||||||
|
uploaderOptions : {
|
||||||
|
url : 'upload.php'
|
||||||
|
},
|
||||||
|
filenameEncoder : 'Ext.ux.upload.header.Base64FilenameEncoder',
|
||||||
|
synchronous : appPanel.syncCheckbox.getValue()
|
||||||
|
});
|
||||||
|
|
||||||
|
var uploadDialog = Ext.create('Ext.ux.upload.Dialog', {
|
||||||
|
dialogTitle : 'My Upload Dialog',
|
||||||
|
panel : uploadPanel
|
||||||
|
});
|
||||||
|
|
||||||
|
this.mon(uploadDialog, 'uploadcomplete', function(uploadPanel, manager, items, errorCount) {
|
||||||
|
this.uploadComplete(items);
|
||||||
|
if (!errorCount) {
|
||||||
|
uploadDialog.close();
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
|
|
||||||
|
uploadDialog.show();
|
||||||
|
}
|
||||||
|
}, '-', {
|
||||||
|
xtype : 'button',
|
||||||
|
text : 'Multipart Upload',
|
||||||
|
scope : appPanel,
|
||||||
|
handler : function() {
|
||||||
|
|
||||||
|
var uploadPanel = Ext.create('Ext.ux.upload.Panel', {
|
||||||
|
uploader : 'Ext.ux.upload.uploader.FormDataUploader',
|
||||||
|
uploaderOptions : {
|
||||||
|
url : 'upload_multipart.php'
|
||||||
|
},
|
||||||
|
synchronous : appPanel.syncCheckbox.getValue()
|
||||||
|
});
|
||||||
|
|
||||||
|
var uploadDialog = Ext.create('Ext.ux.upload.Dialog', {
|
||||||
|
dialogTitle : 'My Upload Dialog',
|
||||||
|
panel : uploadPanel
|
||||||
|
});
|
||||||
|
|
||||||
|
this.mon(uploadDialog, 'uploadcomplete', function(uploadPanel, manager, items, errorCount) {
|
||||||
|
this.uploadComplete(items);
|
||||||
|
if (!errorCount) {
|
||||||
|
uploadDialog.close();
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
|
|
||||||
|
uploadDialog.show();
|
||||||
|
}
|
||||||
|
}, '-', {
|
||||||
|
xtype : 'button',
|
||||||
|
text : 'Dummy upload',
|
||||||
|
scope : appPanel,
|
||||||
|
handler : function() {
|
||||||
|
|
||||||
|
var uploadPanel = Ext.create('Ext.ux.upload.Panel', {
|
||||||
|
uploader : 'Ext.ux.upload.uploader.DummyUploader',
|
||||||
|
synchronous : appPanel.syncCheckbox.getValue()
|
||||||
|
});
|
||||||
|
|
||||||
|
var uploadDialog = Ext.create('Ext.ux.upload.Dialog', {
|
||||||
|
dialogTitle : 'My Upload Dialog',
|
||||||
|
panel : uploadPanel
|
||||||
|
});
|
||||||
|
|
||||||
|
this.mon(uploadDialog, 'uploadcomplete', function(uploadPanel, manager, items, errorCount) {
|
||||||
|
this.uploadComplete(items);
|
||||||
|
if (!errorCount) {
|
||||||
|
uploadDialog.close();
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
|
|
||||||
|
uploadDialog.show();
|
||||||
|
}
|
||||||
|
}, '->', appPanel.syncCheckbox, 'Synchronous upload'
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
appPanel.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
|
@ -0,0 +1 @@
|
||||||
|
../docs
|
|
@ -0,0 +1 @@
|
||||||
|
../../lib/upload
|
After Width: | Height: | Size: 40 KiB |
|
@ -0,0 +1,14 @@
|
||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
|
<title>File upload</title>
|
||||||
|
<link rel="stylesheet" type="text/css" href="extjs/resources/css/ext-all.css">
|
||||||
|
<link rel="stylesheet" type="text/css" href="external/upload/css/upload.css">
|
||||||
|
<script type="text/javascript" src="extjs/ext-debug.js"></script>
|
||||||
|
<script type="text/javascript" src="app.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,16 @@
|
||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
|
<title>File upload</title>
|
||||||
|
<!-- <link rel="stylesheet" type="text/css" href="extjs/resources/css/ext-all.css">
|
||||||
|
-->
|
||||||
|
<link rel="stylesheet" type="text/css" href="extjs/packages/ext-theme-classic/build/resources/ext-theme-classic-all.css">
|
||||||
|
<link rel="stylesheet" type="text/css" href="external/upload/css/upload.css">
|
||||||
|
<script type="text/javascript" src="extjs/build/ext-debug.js"></script>
|
||||||
|
<script type="text/javascript" src="app.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,74 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Example processing of raw PUT/POST uploaded files.
|
||||||
|
* File metadata may be sent through appropriate HTTP headers:
|
||||||
|
* - file name - the 'X-File-Name' proprietary header
|
||||||
|
* - file size - the standard 'Content-Length' header or the 'X-File-Size' proprietary header
|
||||||
|
* - file type - the standard 'Content-Type' header or the 'X-File-Type' proprietary header
|
||||||
|
*
|
||||||
|
* Raw data are read from the standard input.
|
||||||
|
* The response should be a JSON encoded string with these items:
|
||||||
|
* - success (boolean) - if the upload has been successful
|
||||||
|
* - message (string) - optional message, useful in case of error
|
||||||
|
*/
|
||||||
|
require __DIR__ . '/_common.php';
|
||||||
|
$config = require __DIR__ . '/_config.php';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* You should check these values for XSS or SQL injection.
|
||||||
|
*/
|
||||||
|
if (!isset($_SERVER['HTTP_X_FILE_NAME'])) {
|
||||||
|
_error('Unknown file name');
|
||||||
|
}
|
||||||
|
$fileName = $_SERVER['HTTP_X_FILE_NAME'];
|
||||||
|
if (isset($_SERVER['HTTP_X_FILENAME_ENCODER']) && 'base64' == $_SERVER['HTTP_X_FILENAME_ENCODER']) {
|
||||||
|
$fileName = base64_decode($fileName);
|
||||||
|
}
|
||||||
|
$fileName = htmlspecialchars($fileName);
|
||||||
|
|
||||||
|
$mimeType = htmlspecialchars($_SERVER['HTTP_X_FILE_TYPE']);
|
||||||
|
$size = intval($_SERVER['HTTP_X_FILE_SIZE']);
|
||||||
|
|
||||||
|
|
||||||
|
$inputStream = fopen('php://input', 'r');
|
||||||
|
$outputFilename = $config['upload_dir'] . '/' . $fileName;
|
||||||
|
$realSize = 0;
|
||||||
|
$data = '';
|
||||||
|
|
||||||
|
if ($inputStream) {
|
||||||
|
if (! $config['fake']) {
|
||||||
|
$outputStream = fopen($outputFilename, 'w');
|
||||||
|
if (! $outputStream) {
|
||||||
|
_error('Error creating local file');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (! feof($inputStream)) {
|
||||||
|
$bytesWritten = 0;
|
||||||
|
$data = fread($inputStream, 1024);
|
||||||
|
|
||||||
|
if (! $config['fake']) {
|
||||||
|
$bytesWritten = fwrite($outputStream, $data);
|
||||||
|
} else {
|
||||||
|
$bytesWritten = strlen($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (false === $bytesWritten) {
|
||||||
|
_error('Error writing data to file');
|
||||||
|
}
|
||||||
|
$realSize += $bytesWritten;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $config['fake']) {
|
||||||
|
fclose($outputStream);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_error('Error reading input');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($realSize != $size) {
|
||||||
|
_error('The actual size differs from the declared size in the headers');
|
||||||
|
}
|
||||||
|
|
||||||
|
_log(sprintf("[raw] Uploaded %s, %s, %d byte(s)", $fileName, $mimeType, $realSize));
|
||||||
|
_response();
|
|
@ -0,0 +1,39 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* Simple example backend script to serve the FormDataUploader (multipart upload).
|
||||||
|
*/
|
||||||
|
require __DIR__ . '/_common.php';
|
||||||
|
$config = require __DIR__ . '/_config.php';
|
||||||
|
|
||||||
|
$fileName = '';
|
||||||
|
$mimeType = '';
|
||||||
|
$fileSize = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If there is no file data, something wrong has happened. One possible reason is - the uploaded
|
||||||
|
* file size exceeds the maximum allowed POST or upload size.
|
||||||
|
*/
|
||||||
|
if (empty($_FILES)) {
|
||||||
|
_error('No file received');
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($_FILES as $fileName => $fileData) {
|
||||||
|
if ($fileData['error'] !== 0) {
|
||||||
|
_error(sprintf("Upload error '%d'", $fileData['error']));
|
||||||
|
}
|
||||||
|
|
||||||
|
$fileName = htmlspecialchars($fileData['name']);
|
||||||
|
$mimeType = $fileData['type'];
|
||||||
|
$fileSize = $fileData['size'];
|
||||||
|
|
||||||
|
$targetFile = $config['upload_dir'] . '/' . $fileName;
|
||||||
|
|
||||||
|
if (! $config['fake']) {
|
||||||
|
if (! move_uploaded_file($fileData['tmp_name'], $targetFile)) {
|
||||||
|
_error('Error saving uploaded file');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_log(sprintf("[multipart] Uploaded %s, %s, %d byte(s)", $fileName, $mimeType, $fileSize));
|
||||||
|
_response();
|