| |||||||||||||
|
|
Ext.ux.panel.UploadPanel for ExtJs 4Wit this extension of ExtJs you can use a normal panel to upload multiple files via Plupload Features
ImportantVersion 0.1: Use at your own risk. If you like the tool, please link to this page. Based on this extension for ExtJs 3. Example...coming soon. Feedback/Discussion... can be given at the forum at sencha.com. I would be grateful if you give me feedback on errors or improvements.
// Ext.ux.panel.UploadPanel for ExtJs 4
// 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.1
Ext.define('Ext.ux.panel.UploadPanel',
{ extend: 'Ext.grid.Panel',
alias: 'widget.xuploadpanel',
// Configuration
title: 'Upload',
url: '/upload.php', // URL to your server-side upload-script
chunk_size: '512kb', // The chunk-size
max_file_size: '100mb', // The max. allowed file-size
unique_names: false, // Make sure to use only unique-names
multipart: true, // Use multipart-uploads
pluploadPath: '/ttcms/ttcms_admin/js/plupload/', // Path to plupload
pluploadRuntimes: 'html5,gears,browserplus,silverlight,flash,html4', // All the runtimes you want to use
// Texts (language-dependent)
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", "Message"],
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 too big',
statusInvalidExtensionText: 'invalid file-type'
},
// Internal (do not change)
// Grid-View
multiSelect: true,
viewConfig:
{ deferEmptyText: false // For showing emptyText
},
// 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-Headers
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'
};
// Top-Bar
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'
})
]
};
// Progress-Bar (bottom)
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.status[value-1];
if (value == 2)
{ s += " "+record.get("percent")+" %";
}
return s;
},
/*
renderProgress: function(value, meta, record, rowIndex, colIndex, store, view)
{ return value;
console.log("Call renderer", value);
var id;
if (this.progressBars[rowIndex] === undefined)
{ console.log("Create Bar");
id = Ext.id();
this.progressBars[rowIndex] = id;
Ext.Function.defer(function(id, record)
{ console.log("Create bar ", id, value, record);
var bar = new Ext.ProgressBar(
{ height: 15,
renderTo: id,
value: (value / 100)
});
this.progressBars[record.id] = bar;
console.log("After create bar", id, value, record);
},
25,
this,
[id, record]);
}
else
{ if (Ext.isObject(this.progressBars[rowIndex]))
{ var bar = this.progressBars[rowIndex];
bar.setValue(value);
id = bar.getEl().dom.id;
console.log("Fetch bar ", id);
}
else
console.log("Wait for creation");
}
return (String.format('<div id="{0}"></div>', id));
},
*/
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+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;
//uploaded = file.loaded; // file.loaded sometimes is 1 step ahead, so we can not use it.
//uploaded = 0; if (file.percent > 0) uploaded = file.size * file.percent / 100.0; // But this solution is imprecise as well since percent is only a hint
uploaded = this.loadedFile; // So we use this Hack to store the value which is one step back
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);
// console.log("Runtime: ", data.runtime);
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 = 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 = String.format( '<span style="color: red">{0}</span>', this.texts.statusInvalidSizeText );
}
else if ( data.code == -700 )
{ data.file.msg = String.format( '<span style="color: red">{0}</span>', this.texts.statusInvalidExtensionText );
}
else
{ data.file.msg = String.format( '<span style="color: red">{2} ({0}: {1})</span>', data.code, data.details, data.message );
}
this.updateStoreFile(data.file);
this.updateProgress();
}
});
You need to add some more code:
// Advance File-Size
Ext.util.Format.fileSize = function(value)
{ if (value > 1)
{ var s = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];
var e = Math.floor(Math.log(value)/Math.log(1024));
if (e > 0)
return (value/Math.pow(1024, Math.floor(e))).toFixed(2)+" "+s[e];
else return value+" "+s[e];
}
else if (value == 1)
{ return "1 Byte";
}
return '-';
}
String.format = function()
{ var s = arguments[0];
for (var i = 0; i < arguments.length - 1; i++)
{ var reg = new RegExp("\\{" + i + "\\}", "gm");
s = s.replace(reg, arguments[i + 1]);
}
return s;
}
| ||||||||||||
|
| |||||||||||||