A while ago, I tried to create a reusable file upload management panel for the internal intranet system. So I ended up making an extension from a grid panel with DnD and multiple files upload field. This solution allows user to select multiple files to upload as long as the browser supports HTML5. It also indicates the upload progress on each file.
This widget validates file names and sizes on the client side instead of server side. The file does not need to be uploaded to server to validate its properties. Since the file is streamed to server, you may need to work out how you handle the code on the server side. I have provided a simple php code at the end of this article for anyone to start with.
Anyway in order to make this widget to work, you may need the following three extensions.
1. MultipleFileField
The default file field from ExtJs only allows you to upload single file at a time. To enable uploading multiple file, you need to tweak the DOM settings.
Ext.define('Ext.ux.form.field.MultipleFileField',{extend:'Ext.form.field.File',alias:['widget.multifilefield','widget.multiplefilefield','widget.multifile'],/**
* Override to add a "multiple" attribute.
*/onRender:function(){this.callParent(arguments);this.fileInputEl.set({multiple:'multiple'});}});
2. FileDrop
The FileDrop plugin allows user to drop their files via their desktop. FileDrop plugin is created by Mitchell Simoens and you can download it from here.
Please note: you may need to change the class name from "Ext.plugin.extjs.FileDrop" to "Ext.ux.form.FileDrop"
3. IFrame
The purpose of using IFrame widget is to show the file without downloading it. Those files might be *.html, *.txt, *.xml or *.pdf. Some users may just want to view it instead of downloading it.
You can download the widget from here and change the class name to 'Ext.ux.IFrame'
File upload grid panel
Ext.define('Ext.ux.form.FileUploadPanel',{extend:'Ext.panel.Panel',alias:['widget.filepanel','fileuploadpanel'],requires:['Ext.ux.form.field.MultipleFileField','Ext.ux.form.FileDrop','Ext.ux.IFrame'],layout:'card',autoLoadStore:true,maxFileSize:10485760,// 10MblistUrl:'/file/getFileList',// your MVC path to get file listlistParams:{},uploadUrl:'/file/upload',// your MVC path upload filesuploadParams:{},downloadUrl:'/file/download',// your MVC path to download the filedownloadParams:{},deleteUrl:'/file/delete',// your MVC path to delete the filedeleteParams:{},/**
* initialising the components
*/initComponent:function(){varme=this;// Create the datastore if availableif(!Ext.isDefined(me.store)){me.store=Ext.create('Ext.data.Store',{autoLoad:me.autoLoadStore,fields:['name','size','type',{name:'lastModifiedDate',type:'date',dateFormat:'Y-m-d H:i:s'},'iconCls',{name:'new_window',type:'bool'}],proxy:{type:'ajax',url:me.listUrl,extraParams:me.listParams||undefined,reader:{type:'json',root:'data'},listeners:{exception:Ext.onException}}});};// the temporary store to handle file uploading me.progressStore=Ext.create('Ext.data.Store',{fields:['name','size','type',{name:'lastModifiedDate',type:'date',dateFormat:'Y-m-d H:i:s'},{name:'progress',type:'int'}],});// create a tool barif(me.bbar!==false){me.bbar=[{xtype:'tbtext',text:'Drap and drop your files to the grid'},'->',{xtype:'multifile',buttonOnly:true,allowBlank:true,submitValue:false,buttonConfig:{text:'Add attachments',iconCls:'icon-add'},listeners:{render:function(){varel=this.getEl(),field=this;el.dom.onchange=function(e){me.checkFileUpload(e.target.files);};}}}];};// default config varconfig={defaults:{xtype:'grid',enableColumnHide:false,enableColumnMove:false,plugins:[{ptype:'filedrop',readType:'DataURL'}]},tools:[{type:'refresh',handler:function(){me.store.load();}}],items:[{store:me.store,columns:[{header:'Files',flex:1,dataIndex:'name',renderer:function(value,metaData,record){varexportMode=false;if(Ext.isDefined(metaData)&&Ext.isDefined(metaData.exportMode)){exportMode=metaData.exportMode};if(exportMode){returnvalue;}else{varlastModifiedDate=record.get('lastModifiedDate');vardate=Ext.util.Format.date(lastModifiedDate,"d/m/Y g:i a");return'<div class="'+record.get('iconCls')+'" style="background-position: center left!important; padding: 0 15px 0 25px;">'+'<b style="display: block;" title="'+value+'">'+value+'</b>'+'<span style="font-size: 80%;">Size: '+record.get('size')+', Last Modified: '+date+'</span></div>';};}},{xtype:'actioncolumn',cls:'x-icon-column-header x-action-disk-column-header',// define your css icon herewidth:24,icon:'/public/images/ext/silk/arrow_down.png',// change your icon path hereiconCls:'x-hidden',tooltip:'Download selected file',menuDisabled:true,sortable:false,handler:function(gridView,rowIndex,colIndex,item,e,record){gridView.select(record);varparams=Ext.applyIf({fileName:record.get('name')},me.downloadParams),queryStr=Ext.Object.toQueryString(params),url=me.downloadUrl+'/?'+queryStr;if(record.get('new_window')===true){me.showDownloadWindow(url,record);}else{window.location=url;};}},{xtype:'actioncolumn',cls:'x-icon-column-header x-action-delete-column-header',// define your css icon herewidth:24,icon:'/public/images/ext/silk/delete.png',iconCls:'x-hidden',tooltip:'Delete selected file',menuDisabled:true,sortable:false,handler:function(gridView,rowIndex,colIndex,item,e,record){gridView.select(record);varparams=Ext.applyIf({fileName:record.get('name')},me.deleteParams);Ext.Ajax.request({url:me.deleteUrl,params:params,success:function(response){me.store.load();}});}}],listeners:{itemmouseenter:function(view,list,node,rowIndex,e){varicons=Ext.DomQuery.select('.x-action-col-icon',node);Ext.each(icons,function(icon){Ext.get(icon).removeCls('x-hidden');});},itemmouseleave:function(view,list,node,rowIndex,e){varicons=Ext.DomQuery.select('.x-action-col-icon',node);Ext.each(icons,function(icon){Ext.get(icon).addCls('x-hidden');});},loadstart:function(cmp,e,file){me.checkFileUpload([file]);}}},{store:me.progressStore,viewConfig:{markDirty:false},columns:[{header:'Files',dataIndex:'name',flex:1},{header:'Progress',dataIndex:'progress',align:'center',width:90,renderer:function(value,meta,record){varcolor=(value<100)?'red':'green';returnExt.String.format('<b style="color: {0};">{1}%</b>',color,value);}}],listeners:{loadstart:function(cmp,e,file){me.checkFileUpload([file]);}}}]};// appy to this configExt.applyIf(me,config);// apply to the initialConfigExt.applyIf(me.initialConfig,config);// call the argumentsme.callParent(arguments);// init the settingsme.on('render',me.onPanelRender,me);// assign title prefixme.titlePrefix=me.title||'Attachments';},// init store events when renderonPanelRender:function(){varme=this;if(me.store){// create grid value to the store for future useme.store.grid=me.items[0];// change title when loadme.store.on('load',function(){varcount=me.store.count();if(count>0){me.setTitle(me.titlePrefix+' ('+count+')');}else{me.setTitle(me.titlePrefix);};});};if(me.progressStore){// if new file is uploading, then show progress panelme.progressStore.on('add',function(){me.getLayout().setActiveItem(1);});me.progressStore.on('remove',function(){varcount=me.progressStore.count();if(count==0){me.getLayout().setActiveItem(0);};});};},// when user uses filefieldcheckFileUpload:function(files){varme=this,invalidList=[];Ext.each(files,function(file){if(file.size>me.maxFileSize){invalidList.push(file);};});if(invalidList.length>0){varmsg='<ul>';Ext.each(invalidList,function(file){msg+='<li>'+file.name+'</li>';});msg+='</ul>';varpl=invalidList.length>1;msg+=Ext.String.format('{0} file{1} exceed{2} file upload limit. Please try again!!',invalidList.length,(pl?'s':''),(pl?'':'s'));Ext.Msg.show({title:'Exceed file upload limit',msg:msg,buttons:Ext.Msg.OK,icon:Ext.Msg.WARNING});returnfalse;};// check the current file listif(me.store.count()>0){Ext.each(files,function(file){if(me.store.findExact('name',file.name)!==-1){invalidList.push(file);};});if(invalidList.length>0){varmsg='<ul>';Ext.each(invalidList,function(file){msg+='<li>'+file.name+'</li>';});msg+='</ul>';varpl=invalidList.length>1;msg+=Ext.String.format('{0} file{1} already exist{2}.Please try again!!',invalidList.length,(pl?'s':''),(pl?'':'s'));Ext.Msg.show({title:'File exists',msg:msg,buttons:Ext.Msg.OK,icon:Ext.Msg.WARNING});returnfalse;};};// check the current uploading listif(me.progressStore.count()>0){Ext.each(files,function(file){if(me.progressStore.findExact('name',file.name)!==-1){invalidList.push(file);};});if(invalidList.length>0){varmsg='<ul>';Ext.each(invalidList,function(file){msg+='<li>'+file.name+'</li>';});msg+='</ul>';varpl=invalidList.length>1;msg+=Ext.String.format('{0} file{1} exist{2} in uploading list. Please try again!!',invalidList.length,(pl?'s':''),(pl?'':'s'));Ext.Msg.show({title:'File exists',msg:msg,buttons:Ext.Msg.OK,icon:Ext.Msg.WARNING});returnfalse;};};returnme.prepareFileUpload(files);},// prepare file uploadprepareFileUpload:function(files){varme=this;Ext.each(files,function(file){// create a file model varf=Ext.applyIf({progress:'0%'},file);// add to upload store varrecords=me.progressStore.add(f);// start uploading fileme.uploadFile(file,records[0]);});// show the progress gridme.getLayout().setActiveItem(1);returntrue;},// set global paramssetGlobalParams:function(params,load){this.listParams=this.uploadParams=this.downloadParams=this.deleteParams=params;// assign params to store proxyvarproxy=this.store.getProxy();proxy.extraParams=params;// load the store to display filesif(load&&load==true){this.store.load();};},// upload the filesuploadFile:function(file,record){if(!Ext.isEmpty(file)){varme=this;// create http request objectvarxhr=XMLHttpRequest?newXMLHttpRequest():newActiveXObject('Microsoft.XMLHttp');// hook progress xhr.upload.addEventListener("progress",function(e){if(e.lengthComputable){varcurrent=e.position||e.loaded,total=e.totalSize||e.total;varpercent=Math.round((current/total)*100);// if we have recordif(record){record.set('progress',percent);};};},false);// when upload is completedxhr.upload.addEventListener("loadend",function(e){// if we have recordif(record){Ext.defer(function(){me.progressStore.remove(record);},1500);};},false);// check return eventxhr.onreadystatechange=function(){if(xhr.readyState==4){if(xhr.status==200){varresult=Ext.decode(xhr.responseText,true);if(result&&result.success){me.store.load();}else{Ext.Msg.show({title:'Error',msg:result.message||'Unknown Error',buttons:Ext.Msg.OK,icon:Ext.Msg.ERROR});};};};};// void open(DOMString method, DOMString url, boolean async);xhr.open('put',me.uploadUrl,true);xhr.setRequestHeader("Content-Type","application/octet-stream");xhr.setRequestHeader("X-File-Name",file.name||file.fileName);xhr.setRequestHeader("X-File-Size",file.size||file.fileSize);xhr.setRequestHeader("X-File-Type",file.type);varparams=Ext.encode(me.uploadParams);xhr.setRequestHeader("X-File-Params",params);xhr.setRequestHeader("X-Requested-With",'XMLHttpRequest');if('getAsBinary'infile){xhr.sendAsBinary(file.getAsBinary());}else{xhr.send(file);};};},// show download windowshowDownloadWindow:function(url,record){varme=this;if(!me.win){me.win=Ext.create('Ext.Window',{layout:'fit',iconCls:record.get('iconCls'),title:record.get('name'),modal:true,width:800,height:600,border:false,maximizable:true,closeAction:'hide',items:{xtype:'uxiframe'},listeners:{beforeshow:function(){Ext.suspendLayouts();this.setSize(document.body.clientWidth*0.5,document.body.clientHeight*0.8);Ext.resumeLayouts(true);}}});};if(!me.iframe){me.iframe=me.win.down('uxiframe');};// set icon on the windowme.win.setIconCls(record.get('iconCls'))// set title of the windowme.win.setTitle(record.get('name'));// show the windowme.win.show();// load the contentme.iframe.load(url);}});
Server Side (PHP Code)
privatefunctionupload($dstPath){$fileName=$_SERVER['HTTP_X_FILE_NAME'];$fileSize=$_SERVER['HTTP_X_FILE_SIZE'];$fileType=$_SERVER['HTTP_X_FILE_TYPE'];// open input stream$input=fopen('php://input','r');// open output stream$output=fopen($dstPath.$fileName,'a');// start to write datawhile($data=fread($input,1024)){fwrite($output,$data);}// close input streamfclose($input);// close output streamfclose($output);$result=array('success'=>true,'data'=>array('src'=>$dstPath.$fileName,'file'=>$fileName,'size'=>$fileSize,'type'=>$fileType),'total'=>'1','message'=>'File uploaded successfully');return$result;}privatefunctiondelete($filePath){$result=true;if(file_exists($filePath)){$result=unlink($filePath);}$result=array('success'=>$result,'message'=>'Success','file'=>$filePath);return$result;}
The standard paragraphs Welcome to this demo page! Here, you’ll get an exclusive preview of our cutting-edge platform designed to revolutionise your digital experience. Our...