PDA

View Full Version : Ext.ux.panel.UploadPanel for ExtJs 4 (file uploader) with plupload



ontho
31 Dec 2011, 8:02 AM
Hello!

A nice work is this panel which makes it possible to use plupload with ExtJs 3:
http://www.sencha.com/forum/showthread.php?98033-Ext.ux.Plupload-Panel-Button-(file-uploader)

Unfortunately, this doesn't work with ExtJs 4. So based on this code, I made a small example how it might work in Version 4.

Features:

Multiple uploads at one time.
Drag&Drop on some browsers.
Chunk-Uploads with progress-bars for current file & total files with estimated upload-time.
Can be used as any normal Ext.grid.Panel.

For other features see http://www.plupload.com (http://www.plupload.com/).

Downloading:
Please see http://www.thomatechnik.de/webmaster-tools/extjs-plupload/

Setup:


Download plupload.
Copy the code for Ext.ux.panel.UploadPanel into a js-file. Make sure to copy the code for Ext.util.Format.fileSize and String.format as well (if you don't already use it).
Edit the part after // Configuration to your needs.


Example:
I hope I can provide an example soon.

Hope it helps.

mitchellsimoens
31 Dec 2011, 9:21 AM
I would suggest to people to change the extension. When you create an instance, you can change the configuration:


Ext.create('Ext.ux.panel.UploadPanel', {
title : 'Upload a file'
});

Your extension code should not be changed by a dev unless for a new feature or bug fix.

Gummy
3 Jan 2012, 2:06 AM
In the function renderStatus(), shouldn't it be this.texts.status instead of this.status ?
If I put this.status I have a "this.status undefined" error.

geniodella
18 Feb 2012, 4:14 PM
I get this error : this.getTopToolbar().getComponent("addButton").getEl() is undefined

at this line of code :
this.getTopToolbar().getComponent('addButton').getEl().dom.id

do you have any guess?

lorezyra
21 Feb 2012, 5:20 PM
Looks like a very cool tool...=D>

However, my design requirements are that no special and/or additional plugins be required for my code to work on both desktop and mobile devices. While it would be nice to have a true progress bar for all uploads, I believe it's not necessary.

Eventually, the browser makers will fill in this obvious oversight and create a mechanism to show that data is being uploaded to the server. I don't see why it would be that difficult. They have the download progress... Why not do something similar with an upload progress???

Tchinkatchuk
11 Sep 2012, 2:25 AM
Hi folks !

Did someone has an example with extjs4 ?
Anyway I will certainly try it and use it for my project. Extjs and Plupload are awesome stuff !!

Greetings to their teams !

ostghost
28 Sep 2012, 12:17 PM
Hi, was somebody able to run this UX in IE, 8 or 9?

I tried in FF, WebKit, and works well but in IE add button does not open standard dialog for choosing files.

Working example, FF, WebKit only here http://jsfiddle.net/GefRK/2/

Standrad Plupload triggerd by simple div with id works OK even in IE.

Any idea?

Version: Plupload 1.5.4 & ExtJS 4.1.1 commercial.



Ext.application({
launch:function () {

// Ext.ux.panel.UploadPanel for ExtJs 4 with Plupload
// Source: http://www.thomatechnik.de/webmaster-tools/extjs-plupload/
// Based on: http://www.sencha.com/forum/showthread.php?98033-Ext.ux.Plupload-Panel-Button-%28file-uploader%29
// Please link to this page if you find this extension usefull
// Version 0.2
// @authors: mhd sulhan (ms@kilabit.org)

Ext.define('Ext.ux.panel.UploadPanel', {
extend:'Ext.grid.Panel',
alias:'widget.uploadpanel',

// configuration
title:'Upload',
url:'data/upload.jsp',
chunk_size:'1mb',
max_file_size:'10mb',
unique_names:false,
multipart:true,

pluploadPath:'public/js/plupload',
pluploadRuntimes:'html5,gears,browserplus,silverlight,flash,html4',

texts:{
status:['Queued', 'Uploading', 'Unknown', 'Failed', 'Done'],
dragDropAvailable:'Drag&drop files here',
noDragDropAvailable:'This Browser doesn\'t support drag&drop.',
emptyTextTpl:'<div style="color:#808080; margin:0 auto; text-align:center; top:48%; position:relative;">{0}</div>',
cols:['File', 'Size', 'State', 'Mesage'],
addButtonText:'Add file',
uploadButtonText:'Upload',
cancelButtonText:'Cancel',
deleteButtonText:'Delete',
deleteUploadedText:'Delete finished',
deleteAllText:'Delete all',
deleteSelectedText:'Delete selected',
progressCurrentFile:'Current file:',
progressTotal:'Total:',
statusInvalidSizeText:'File size is to big',
statusInvalidExtensionText:'Invalid file-type'
},

// internal do not change

// grid view
multiSelect:true,
viewConfig:{

// for showing emptyText
deferEmptyText:false
},

// hack: loaded of the actual file (plupload is sometimes a step ahead)
loadedFile:0,

constructor:function (config) {

// list of files
this.success = [];
this.failed = [];

// column header
config.columns = [
{
header:this.texts.cols[0],
flex:1,
dataIndex:'name'
},
{
header:this.texts.cols[1],
flex:1,
align:'right',
dataIndex:'size',
renderer:Ext.util.Format.fileSize
},
{
header:this.texts.cols[2],
flex:1,
dataIndex:'status',
renderer:this.renderStatus
},
{
header:this.texts.cols[3],
flex:1, dataIndex:'msg'
}
];

// model and store
if(!Ext.ModelManager.getModel('Plupload')) {
Ext.define('Plupload', {
extend:'Ext.data.Model',
fields:[ 'id', 'loaded', 'name', 'size', 'percent', 'status', 'msg' ]
});
}

config.store = {
type:'json',
model:'Plupload',
listeners:{
load:this.onStoreLoad,
remove:this.onStoreRemove,
update:this.onStoreUpdate,
scope:this
},
proxy:'memory'
};

this.tbar =
{
enableOverflow:true,
items:[
new Ext.Button({
text:this.texts.addButtonText,
itemId:'addButton',
iconCls:config.addButtonCls || 'pluploadAddCls',
disabled:true
}),
new Ext.Button({
text:this.texts.uploadButtonText,
handler:this.onStart,
scope:this,
disabled:true,
itemId:'upload',
iconCls:config.uploadButtonCls || 'pluploadUploadCls'
}),
new Ext.Button({
text:this.texts.cancelButtonText,
handler:this.onCancel,
scope:this,
disabled:true,
itemId:'cancel',
iconCls:config.cancelButtonCls || 'pluploadCancelCls'
}),
new Ext.SplitButton({
text:this.texts.deleteButtonText,
handler:this.onDeleteSelected,
menu:new Ext.menu.Menu({
items:[
{
text:this.texts.deleteUploadedText,
handler:this.onDeleteUploaded,
scope:this
},
'-',
{
text:this.texts.deleteAllText,
handler:this.onDeleteAll,
scope:this
},
'-',
{
text:this.texts.deleteSelectedText,
handler:this.onDeleteSelected,
scope:this
}
]
}),
scope:this,
disabled:true,
itemId:'delete',
iconCls:config.deleteButtonCls || 'pluploadDeleteCls'
})
]
};

// bottom progress bar
this.progressBarSingle = new Ext.ProgressBar(
{
flex:1,
animate:true
});
this.progressBarAll = new Ext.ProgressBar(
{
flex:2,
animate:true
});

this.bbar = {
layout:'hbox',
style:{
paddingLeft:'5px'
},
items:[
this.texts.progressCurrentFile,
this.progressBarSingle, {
xtype:'tbtext',
itemId:'single',
style:'text-align:right',
text:'',
width:100
},
this.texts.progressTotal,
this.progressBarAll,
{
xtype:'tbtext',
itemId:'all',
style:'text-align:right',
text:'',
width:100
},
{
xtype:'tbtext',
itemId:'speed',
style:'text-align:right',
text:'',
width:100
},
{
xtype:'tbtext',
itemId:'remaining',
style:'text-align:right',
text:'',
width:100
}
]
};

this.callParent(arguments);
},
afterRender:function () {
this.callParent(arguments);
this.initPlupload();
},
renderStatus:function (value, meta, record, rowIndex, colIndex, store, view) {
var s = this.texts.status [value - 1];
if(value == 2) {
s += ' ' + record.get('percent') + ' %';
}
return s;
},
getTopToolbar:function () {
var bars = this.getDockedItems('toolbar[dock="top"]');
return bars[0];
},
getBottomToolbar:function () {
var bars = this.getDockedItems('toolbar[dock="bottom"]');
return bars[0];
},
initPlupload:function () {
this.uploader = new plupload.Uploader({
url:this.url,
runtimes:this.pluploadRuntimes,
browse_button:this.getTopToolbar().getComponent('addButton').getEl().dom.id,
container:this.getEl().dom.id, max_file_size:this.max_file_size || '',
resize:this.resize || '',
flash_swf_url:this.pluploadPath + '/plupload.flash.swf',
silverlight_xap_url:this.pluploadPath + 'plupload.silverlight.xap',
filters:this.filters || [],
chunk_size:this.chunk_size,
unique_names:this.unique_names,
multipart:this.multipart,
multipart_params:this.multipart_params || null,
drop_element:this.getEl().dom.id,
required_features:this.required_features || null
});

// events
Ext.each(['Init', 'ChunkUploaded', 'FilesAdded', 'FilesRemoved', 'FileUploaded', 'PostInit'
, 'QueueChanged', 'Refresh', 'StateChanged', 'UploadFile', 'UploadProgress', 'Error' ]
, function (v) {
this.uploader.bind(v, eval('this.Plupload' + v), this);
}, this);

// init Plupload
this.uploader.init();
},
onDeleteSelected:function () {
Ext.each(this.getView().getSelectionModel().getSelection(), function (record) {
this.remove_file(record.get('id'));
}, this);
},
onDeleteAll:function () {
this.store.each(function (record) {
this.remove_file(record.get('id'));
}, this);
},
onDeleteUploaded:function () {
this.store.each(function (record) {
if(record.get('status') == 5) {
this.remove_file(record.get('id'));
}
}, this);
},
onCancel:function () {
this.uploader.stop();
this.updateProgress();
},
onStart:function () {
this.fireEvent('beforestart', this);

if(this.multipart_params) {
this.uploader.settings.multipart_params = this.multipart_params;
}
this.uploader.start();
},
remove_file:function (id) {
var fileObj = this.uploader.getFile(id);
if(fileObj) {
this.uploader.removeFile(fileObj);
}
else {
this.store.remove(this.store.getById(id));
}
},
updateStore:function (files) {
Ext.each(files, function (data) {
this.updateStoreFile(data);
}, this);
},
updateStoreFile:function (data) {
data.msg = data.msg || '';
var record = this.store.getById(data.id);
if(record) {
record.set(data);
record.commit();
}
else {
this.store.add(data);
}
},
onStoreLoad:function (store, record, operation) {
},
onStoreRemove:function (store, record, operation) {
if(!store.data.length) {
this.getTopToolbar().getComponent('delete').setDisabled(true);
this.uploader.total.reset();
}
var id = record.get('id');

Ext.each(this.success, function (v) {
if(v && v.id == id) {
Ext.Array.remove(this.success, v);
}
}, this);

Ext.each(this.failed, function (v) {
if(v && v.id == id) {
Ext.Array.remove(this.failed, v);
}
}, this);
},
onStoreUpdate:function (store, record, operation) {
var canUpload = false;
if(this.uploader.state != 2) {
this.store.each(function (record) {
if(record.get('status') == 1) {
canUpload = true;
return false;
}
}, this);
}
this.getTopToolbar().getComponent('upload').setDisabled(!canUpload);
},
updateProgress:function (file) {
var queueProgress = this.uploader.total;

// all
var total = queueProgress.size;
var uploaded = queueProgress.loaded;
this.getBottomToolbar().getComponent('all').setText(Ext.util.Format.fileSize(uploaded) + '/' + Ext.util.Format.fileSize(total));

if(total > 0) {
this.progressBarAll.updateProgress(queueProgress.percent / 100, queueProgress.percent + ' %');
}
else {
this.progressBarAll.updateProgress(0, ' ');
}

// speed plus remaining
var speed = queueProgress.bytesPerSec;
if(speed > 0) {
var totalSec = parseInt((total - uploaded) / speed);
var hours = parseInt(totalSec / 3600) % 24;
var minutes = parseInt(totalSec / 60) % 60;
var seconds = totalSec % 60;
var timeRemaining = result = (hours < 10 ? '0' + hours : hours) + ':' + (minutes < 10 ? '0' + minutes : minutes) + ':' + (seconds < 10 ? '0' + seconds : seconds);
this.getBottomToolbar().getComponent('speed').setText(Ext.util.Format.fileSize(speed) + '/s');
this.getBottomToolbar().getComponent('remaining').setText(timeRemaining);
}
else {
this.getBottomToolbar().getComponent('speed').setText('');
this.getBottomToolbar().getComponent('remaining').setText('');
}

// single
if(!file) {
this.getBottomToolbar().getComponent('single').setText('');
this.progressBarSingle.updateProgress(0, ' ');
}
else {
total = file.size;

// we use this Hack to store the value which is one step back
uploaded = this.loadedFile;

this.getBottomToolbar().getComponent('single').setText(Ext.util.Format.fileSize(uploaded) + '/' + Ext.util.Format.fileSize(total));
this.progressBarSingle.updateProgress(file.percent / 100, (file.percent).toFixed(0) + ' %');
}
},
PluploadInit:function (uploader, data) {
this.getTopToolbar().getComponent('addButton').setDisabled(false);
if(data.runtime == 'flash'
|| data.runtime == 'silverlight'
|| data.runtime == 'html4') {
this.view.emptyText = this.texts.noDragDropAvailable;
}
else {
this.view.emptyText = this.texts.dragDropAvailable
}
this.view.emptyText = Ext.String.format(this.texts.emptyTextTpl, this.view.emptyText);
this.view.refresh();

this.updateProgress();
},
PluploadChunkUploaded:function () {
},
PluploadFilesAdded:function (uploader, files) {
this.getTopToolbar().getComponent('delete').setDisabled(false);
this.updateStore(files);
this.updateProgress();
},
PluploadFilesRemoved:function (uploader, files) {
Ext.each(files, function (file) {
this.store.remove(this.store.getById(file.id));
}, this);

this.updateProgress();
},
PluploadFileUploaded:function (uploader, file, status) {
var response = Ext.JSON.decode(status.response);
if(response.success == true) {
file.server_error = 0;
this.success.push(file);
}
else {
if(response.message) {
file.msg = '<span style="color: red">' + response.message + '</span>';
}
file.server_error = 1;
this.failed.push(file);
}
this.updateStoreFile(file);
this.updateProgress(file);
},
PluploadPostInit:function () {
},
PluploadQueueChanged:function (uploader) {
this.updateProgress();
},
PluploadRefresh:function (uploader) {
this.updateStore(uploader.files);
this.updateProgress();
},
PluploadStateChanged:function (uploader) {
if(uploader.state == 2) {
this.fireEvent('uploadstarted', this);
this.getTopToolbar().getComponent('cancel').setDisabled(false);
}
else {
this.fireEvent('uploadcomplete', this, this.success, this.failed);
this.getTopToolbar().getComponent('cancel').setDisabled(true);
}
},
PluploadUploadFile:function () {
this.loadedFile = 0;
},
PluploadUploadProgress:function (uploader, file) {

// no chance to stop here. We get no response-text from the server.
// So just continue if something fails here. Will be fixed in next update, says plupload.
if(file.server_error) {
file.status = 4;
}

this.updateStoreFile(file);
this.updateProgress(file);
this.loadedFile = file.loaded;
},
PluploadError:function (uploader, data) {
data.file.status = 4;
if(data.code == -600) {
data.file.msg = Ext.String.format('<span style="color: red">{0}</span>', this.texts.statusInvalidSizeText);
} else if(data.code == -700) {
data.file.msg = Ext.String.format('<span style="color: red">{0}</span>', this.texts.statusInvalidExtensionText);
}
else {
data.file.msg = Ext.String.format('<span style="color: red">{2} ({0}: {1})</span>', data.code, data.details, data.message);
}
this.updateStoreFile(data.file);
this.updateProgress();
}
});
Ext.define('ET.view.knowlet.Upload', {
extend:'Ext.window.Window',
alias:['widget.widgetupload'],

height:300,
width:440,
layout:{
type:'fit'
},
closeAction:'destroy',

// to force user close window e.g. after upload
// cause in multipart_params are file specific data like url of document, id of entry etc.
modal:true,

// to prevent from opening more upload windows
id:'upload',

title:'Upload',

listeners:{
beforerender:function () {
var me = this;
if(Ext.get(me.id)) {
return false;
}
}
},

initComponent:function () {
var me = this;

Ext.applyIf(me, {
items:[
{
xtype:'uploadpanel',
url:me.url,
multipart_params:me.multipart_params || null,
header:false,
pluploadRuntimes:'html4'
}
]
});

me.callParent(arguments);
}

});


Ext.create('ET.view.knowlet.Upload', {
url:'url'
}).show();

}
});

adam.jimenez
8 May 2013, 12:21 PM
add this css to get it working in IE:


form{
z-index: 1000;
}

lombras
11 Jun 2013, 1:15 PM
Add two files and click remove all.
The result is an error - record is undefined

Anyone can help to fix?

mcamara
11 Feb 2014, 7:13 AM
It is happen that when u deleteAll or deleteUploaded, when remove_file and uploader.removeFile is called, this last funcition remove the item from store (I don't know why), so here is my personal fix:



...
onDeleteAll: function() {
var items = [];
this.store.each(
function(record) {
items.push(record.get('id'));
// this.remove_file(record.get('id'));
}, this
);
Ext.iterate(items, function(item) {
this.remove_file(item);
}, this);
},

onDeleteUploaded: function() {
var items = [];
this.store.each(
function(record) {
if (record.get('status') == 5) {
// this.remove_file(record.get('id'));
items.push(record.get('id'));
}
}, this
);
Ext.iterate(items, function(item) {
this.remove_file(item);
}, this);
},
...
...
...
remove_file: function(id) {
var fileObj = this.uploader.getFile(id);
if (fileObj) {
this.uploader.removeFile(fileObj);
} else {
// this.store.remove(this.store.getById(id));
}
},
...
...