File upload in EGL Rich UI
Transcription
File upload in EGL Rich UI
File upload in EGL Rich UI Overview I want to create a drag and drop file upload component, which can be used with minimal configuration in several Rich UI handlers. It’s a simple box, on which a file can be dropped, with a description field and a list of file classifications above. What you see is the “FileDropZone” part of the component. This component accepts a drop of one or more files. The “FileDropZone” part then creates a “SingleFileUploader” part for each of the dropped files. These “SingleFileUploader” parts show a preview if it’s an image, and a progress bar below for each file. Each “SingleFileUploader“ knows the address of a servlet running on the webserver to receive the file, and has a Rich UI function set to be triggered once the file upload is complete. The servlet receives the file, calls a service function in our webservices project on Websphere to add the record for the file to the database. The function returns the path where the file should be stored by the servlet. Finally, the servlet returns the added file id to the frontend. Websphere on Power DB storage Webservice File storage Servlet Rich UI in Browser FileUploaderMultiExt FileUploaderMulti FileDropZone SingleFileUploader SingleFileUploader We have decided to put files on a windows file server, and access them over QNTC from Websphere. This is not a requirement. The files can be stored on the IFS just as easily. Web service interfaces The web service functions are used in the servlet. I’ll only describe the interfaces as the implementation is database specific: function addBestand(uiBest UIBestandExtRec inout) // add custom code to add a file record to the database end function getBestand(id decimal(8) in) returns (UIBestandRec) // add custom code to retrieve a file record from the database end function updateBestand(uIBestandExtRec UIBestandExtRec in) // add custom code to update a file record in the database End record UIBestandExtRec type BasicRecord bestandrec UIBestandRec; types UIBestandTypeRec[]; end record UIBestandRec type BasicRecord id decimal(8) ; // id of the file omschr string ; // description pad string ; // path to file extensie string ; // extension origBestandNaam string ; // orig filename DatumWijz timeStamp ; // date changed end record UIBestandTypeRec type BasicRecord typeId decimal(8) ; naam string ; end Java Servlet Requirements: Client interface to soap service described above. Apache commons FileUpload Apache commons Discovery Apache commons IO Apache commons logging PathUtils function GetExtension PathUtils function MovePathToQNC (this is only required if the files are stored on the network. This function can be omitted if files are stored on the IFS. The path the files are stored on is decide by the web service function. public static String getExtension(String path){ int dotInd = path.lastIndexOf('.'); String extension = (dotInd > 0 && dotInd < path.length()) ? path.substring(dotInd + 1) : null; return (extension); } public static String movePathToQNC(String origPath) { return origPath.replace("\\", "/").replace("//mol.local/Intern/Documenten/", "/QNTC/Molcy15/"); } package com.molcy.java.servlets.file; import import import import import import import import import import java.io.FileNotFoundException; java.io.FileOutputStream; java.io.IOException; java.io.PrintWriter; java.math.BigDecimal; java.rmi.RemoteException; java.util.ArrayList; java.util.Calendar; java.util.Iterator; java.util.List; import import import import javax.servlet.ServletException; javax.servlet.http.HttpServlet; javax.servlet.http.HttpServletRequest; javax.servlet.http.HttpServletResponse; import org.apache.commons.fileupload.FileItem; import org.apache.commons.fileupload.disk.DiskFileItemFactory; import org.apache.commons.fileupload.servlet.ServletFileUpload; import import import import import import com.molcy.java.util.PathUtils; com.molcy.webservices.Services.BestandService; com.molcy.webservices.Services.BestandServiceServiceLocator; com.molcy.webservices.UIRecords.UIBestandExtRec; com.molcy.webservices.UIRecords.UIBestandRec; com.molcy.webservices.UIRecords.UIBestandTypeRec; public class UploadFile extends HttpServlet { private static final long serialVersionUID = 1L; public UploadFile() { super(); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try { response.setContentType("text/html"); PrintWriter out = response.getWriter(); boolean isMultipartContent = ServletFileUpload.isMultipartContent(request); if (!isMultipartContent) { return; } String result = ""; ServletFileUpload servletFileUpload = new ServletFileUpload(new DiskFileItemFactory()); List<FileItem> fileItemsList = servletFileUpload.parseRequest(request); Iterator<FileItem> it = fileItemsList.iterator(); byte[] data = new byte[0]; int imid = 0; String origFilename = ""; String description = ""; String extension = ""; Calendar dateChange = Calendar.getInstance(); List<String> types = new ArrayList<String>(); // get all the files and metadata from the upload while(it.hasNext()){ FileItem fileItem = (FileItem) it.next(); if(fileItem.isFormField()){ if (fileItem.getFieldName().equals("omschrijving")) { // omschrijving = description description = fileItem.getString(); } else if (fileItem.getFieldName().equals("typelijst")) { // typelijst = typelist types.add(fileItem.getString()); } else if (fileItem.getFieldName().equals("imid")) { String s = fileItem.getString(); if (!s.equals("")){ imid = Integer.parseInt(s); } } } else { origFilename = fileItem.getName(); extension = PathUtils.getExtension(fileItem.getName()); data = fileItem.get(); } } BestandServiceServiceLocator blocator = new BestandServiceServiceLocator(); BestandService bserv = blocator.getBestandService(); UIBestandTypeRec[] ts = new UIBestandTypeRec[types.size()]; for (int i = 0; i < types.size(); i++){ UIBestandTypeRec uiTyp = new UIBestandTypeRec(); uiTyp.setNaam(""); uiTyp.setTypeId(new BigDecimal(types.get(i))); ts[i] = uiTyp; } try { if (imid == 0) { // new file UIBestandExtRec uiBestExt = new UIBestandExtRec(); // BestandRec is the database file record UIBestandRec uiBest = new UIBestandRec(); uiBest.setDatumWijz(dateChange); uiBest.setExtensie(extension); uiBest.setOmschr(description); uiBest.setOrigBestandNaam(origFilename); uiBest.setPad(""); uiBestExt.setBestandrec(uiBest); uiBestExt.setTypes(ts); uiBestExt = bserv.addBestand(uiBestExt); result = "" + uiBestExt.getBestandrec().getId(); // write file to file storage FileOutputStream fOutStream = new FileOutputStream(PathUtils.movePathToQNC(uiBestExt.getBestandrec().getPad())); fOutStream.write(data); fOutStream.close(); } else { // existing file change // get file record from database UIBestandRec bestandrec = bserv.getBestand(new BigDecimal(imid)); // BestandRec is the database file record, service call to web service to retrieve the existing record bestandrec.setOrigBestandNaam(origFilename); bestandrec.setDatumWijz(dateChange); bestandrec.setExtensie(extension); bestandrec.setOmschr(description); bestandrec.setPad(""); UIBestandExtRec uiBestExt = new UIBestandExtRec(); uiBestExt.setBestandrec(bestandrec); uiBestExt.setTypes(ts); // update file record in database bserv.updateBestand(uiBestExt); // service call UIBestandRec fileRec = bserv.getBestand(uiBestExt.getBestandrec().getId()); result = "" + fileRec.getId(); // update file in file storage String bestandpad = PathUtils.movePathToQNC(fileRec.getPad()); if (data.length != 0){ FileOutputStream fOutStream = new FileOutputStream(bestandpad); fOutStream.write(data); fOutStream.close(); } else { System.out.println("No data"); } } } catch (FileNotFoundException fex){ System.out.println("FilenotfoundException " + fex.getMessage()); } catch (RemoteException e){ System.out.println("Error retrieving file details"); } catch (IOException ioEx) { System.out.println("IOException " + ioEx.getMessage()); } out.println("{ \"imid\": \"" + result + "\", \"waarde2\": \"value2\" }"); out.flush(); out.close(); } catch (Exception e) { System.err.println(e.toString()); } } } Widgets FileUploaderMultiExt A file can have one or several file classifications. FileUploaderMultiExt is a wrapper component for FileUploaderMulti, with the only difference that the FileUploaderMultiExt contains a full list of possible file classifications and has the ability to add a description. package com.molcy.egl.generalwidgets; import import import import import com.ibm.egl.rui.widgets.ListMulti; com.ibm.egl.rui.widgets.TextField; com.ibm.egl.rui.widgets.TextLabel; com.molcy.webservices.UIRecords.UIBestandTypeRec; egl.ui.rui.Event; handler FileUploaderMultiExt type RUIWidget { targetWidget = alles, onConstructionFunction = start, cssFile="css/mol_widgets.css", @VEWidget{ category = "Custom" } } //------------------------------------------------// Events //------------------------------------------------onFileReady DataEvent{@EGLProperty}; onAllFileReady DataEvent{@EGLProperty}; //------------------------------------------------// Vars //------------------------------------------------const INPUT_WIDTH int = 170; allTypes UIBestandTypeRec[]{@EGLProperty}; private selectedTypeIds decimal(8)[0]; private serviceCallsArtikelUpdate int = 0; //------------------------------------------------// UI //------------------------------------------------alles MolBox{ children = [boxInput, ul], tableClass = "VolleBreedte", columns = 1 }; boxInput MolBox{ children = [ tlOmschrijving, inpOmschrijving, tlTypes, inpTypes ], columns = 2 }; tlOmschrijving TextLabel{text = "Omschrijving"}; inpOmschrijving TextField{text = "", onChange ::= inpOmschrijving_Change, width = INPUT_WIDTH}; tlTypes TextLabel{text = "Types"}; inpTypes ListMulti{ size = 10, onChange ::= inpTypes_Change, width = INPUT_WIDTH }; ul FileUploaderMulti{ onFileReady = ul_fileReady, onAllFileReady = ul_AllfilesReady }; //------------------------------------------------// Functions //------------------------------------------------function start() end private function displayError(e AnyException in) SysLib.writeStdout(e.message); end private function inpOmschrijving_Change (e Event in) ul.setOmschrijving(inpOmschrijving.text); end private function inpTypes_Change (e Event in) selectedTypeIds.removeAll(); typeIndexes int[] = inpTypes.getSelection(); for (i int from 1 to typeIndexes.getSize()) selectedTypeIds.appendElement(allTypes[typeIndexes[i]].typeId); end ul.setTypesBestand(selectedTypeIds); end private function ul_fileReady (rowdata any in, e Event in) onFileReady(rowdata, e); end private function ul_AllfilesReady (rowdata any in, e Event in) onAllFileReady(rowdata, e); end //------------------------------------------------// Getters & Setters //------------------------------------------------// onFileReady function getOnFileReady() returns (DataEvent) return (onFileReady); end function setOnFileReady(value DataEvent in) onFileReady = value; end // onAllFileReady function getOnAllFileReady() returns (DataEvent) return (onAllFileReady); end function setOnAllFileReady(value DataEvent in) onAllFileReady = value; end // allTypes function getAllTypes() returns (UIBestandTypeRec[]) return (allTypes); end function setAllTypes(value UIBestandTypeRec[] in) allTypes = value; options String[0]; for (i int from 1 to allTypes.getSize()) options.appendElement(alltypes[i].naam); end inpTypes.setValues(options); end end FileUploaderMulti This organizes the dynamic between the FileDropZone and the SingleFileUploader package com.molcy.egl.generalwidgets; import com.ibm.egl.rui.widgets.TextLabel; import com.molcy.egl.libraries.RUIConstantsLib; import import import import import egl.ui.rui.Event; egl.ui.rui.Widget; externaltypes.BrowserFunctions; externaltypes.FileDropZone; externaltypes.SingleFileUploader; handler FileUploaderMulti type RUIWidget {targetWidget = alles, onConstructionFunction = start, cssFile="css/mol_widgets.css", @VEWidget{ category = "Custom" }} //------------------------------------------------// Events //------------------------------------------------onFileReady DataEvent{@EGLProperty}; onAllFileReady DataEvent{@EGLProperty}; //------------------------------------------------// Vars //------------------------------------------------typesBestand Decimal(8)[]{@EGLProperty}; omschrijving String{@EGLProperty}; //------------------------------------------------// UI //------------------------------------------------alles MolBox{ children = [], tableClass = "VolleBreedte", columns = 1 }; tlNotSupported TextLabel{text = "Drag and drop file upload not supported. "}; uploaderBox molBox{ children = [] }; dz FileDropZone{ klasse = "FileDropZoneClass", text = "Laat hier uw bestand vallen" // drop file here }; b BrowserFunctions{}; //------------------------------------------------// Functions //------------------------------------------------function start() if (b.dragDropFileUploadSupported()) dz.setFileDropEvent(ez_FileDropped); alles.removeChildren(); alles.appendChildren([dz, uploaderBox]); dz.setKlasse("FileDropZoneClass"); else alles.removeChildren(); alles.appendChild(tlNotSupported); end end private function ez_FileDropped(bestand any in) su SingleFileUploader{ linkNaarServlet = RuiConstantsLib.UPLOAD_SERVLET_LINK }; if (b.getHostFromUrl(su.linkNaarServlet) != b.getHostFromAddressbar()) SysLib.writeStderr("Bestand wordt op geladen naar andere server dan van waar deze pagina is geladen. Opladen zal niet correct verlopen. "); end su.setTypesBestand(typesBestand); su.setOmschrijving(omschrijving); su.setFileUploadedEvent(uploadedEvent); su.uploadFile(bestand); uploaderBox.appendChild(su); end private function uploadedEvent(imid decimal(8) in, source Widget in) imidStr String = "" + imid; // foefelare magicare omdat er anders problemen zijn om er een decimal van te maken imid2 decimal(8) = imidStr; uploaderBox.removeChild(source); try onFileReady(imid2, new Event{}); if (uploaderBox.getChildren().getSize() == 0) onAllFileReady(imid2, new Event{}); end onException(exception AnyException) SysLib.writeStdout("fout bij FileUploaderMulti.uploadedEvent: " + exception.message); end end //------------------------------------------------// Getters & Setters //------------------------------------------------// onFileReady function getOnFileReady() returns (DataEvent) return (onFileReady); end function setOnFileReady(value DataEvent in) onFileReady = value; end // onAllFileReady function getOnAllFileReady() returns (DataEvent) return (onAllFileReady); end function setOnAllFileReady(value DataEvent in) onAllFileReady = value; end // typesBestand function getTypesBestand() returns (Decimal(8)[]) return (typesBestand); end function setTypesBestand(value Decimal(8)[] in) typesBestand = value; end // omschrijving function getOmschrijving() returns (String) return (omschrijving); end function setOmschrijving(value String in) omschrijving = value; end end FileDropZone Accepts one or more files and throws an event. package externalTypes; delegate FileDropEvent(bestand any in) end externalType FileDropZone extends Widget type JavaScriptObject { relativePath="customJavaScript/widgets", javaScriptName="FileDropZone"//, } text String{@JavaScriptProperty}; klasse String{@JavaScriptProperty}; function setFileDropEvent(event FileDropEvent in); function setText(text String in); function getText() returns (String); function setKlasse(text String in); function getKlasse() returns (String); end SingleFileUploader package externalTypes; delegate FileReadyEvent(bestandsId decimal(8) in, source Widget in) end externalType SingleFileUploader extends Widget type JavaScriptObject { relativePath="customJavaScript/widgets", javaScriptName="SingleFileUploader"//, } linkNaarServlet String{@JavaScriptProperty}; typesBestand decimal(8)[]{@JavaScriptProperty}; omschrijving String{@JavaScriptProperty}; function uploadFile(bestand any in); function getLinkNaarServlet() returns (String); function setLinkNaarServlet(value String in); function setFileUploadedEvent(event FileReadyEvent in); function getTypesBestand() returns (decimal(8)[]); function setTypesBestand(value decimal(8)[]); function getOmschrijving() returns (String); function setOmschrijving(value String in); end Externaltypes FileDropZone egl.defineWidget('customJavaScript.widgets', 'FileDropZone', 'egl.ui.rui', 'Widget', 'div', { "eze$$initializeDOMElement": function(){ //definition of main div this.uploadDiv = document.createElement('div'); this.uploadDiv.setAttribute('class', 'FileDropZoneClass'); this.uploadDiv.innerHTML = ' '; this.eze$$DOMElement.appendChild(this.uploadDiv); }, "constructor": function(){ this.text = ""; this.uploadDiv.innerHTML = this.text; this.klasse = ""; this.uploadDiv.setAttribute('class', this.klasse); var prevDef = function (event){ event.preventDefault(); }; var dropFunction = function(event){ event.preventDefault(); var files = event.dataTransfer.files; for (var i = 0; i < files.length; i++){ this.DropEvent(files[i]); } }; this.uploadDiv.ondragover = prevDef; this.uploadDiv.ondrop = dropFunction; this.uploadDiv.addEventListener( 'dragenter', function(event){ this.setAttribute("class", "FileDropZoneClassDragOver"); }, false ); this.uploadDiv.addEventListener( 'dragleave', function(event){ this.setAttribute("class", "FileDropZoneClass"); }, false ); }, "setFileDropEvent": function(event){ //alert ("dropEvent set"); this.uploadDiv.DropEvent = function (data){ event(egl.boxAny(data)); }; }, "setKlasse": function (klasse){ this.klasse = klasse; this.uploadDiv.setAttribute('class', this.klasse); }, "getKlasse": function(){ return (this.klasse); }, "setText": function(text){ this.text = text; this.uploadDiv.innerHTML = this.text; }, "getText": function(){ return (this.text); } } ) SingleFileUploader egl.defineWidget('customJavaScript.widgets', 'SingleFileUploader', 'egl.ui.rui', 'Widget', 'div', { "eze$$initializeDOMElement": function(){ this.uploadDiv = document.createElement('div'); this.eze$$DOMElement.appendChild(this.uploadDiv); this.bestandsnaamTekst = document.createElement('p'); this.uploadDiv.appendChild(this.bestandsnaamTekst); this.imgPreview = document.createElement('img'); this.imgPreview.setAttribute('width', '100'); this.uploadDiv.appendChild(this.imgPreview); var tmpPara = document.createElement('p'); this.meter = document.createElement('progress'); this.meter.setAttribute('max', '100'); tmpPara.appendChild(this.meter); this.uploadDiv.appendChild(tmpPara); this.statusText = document.createElement('p'); this.uploadDiv.appendChild(this.statusText); }, "constructor": function(){ this.linkNaarServlet = ""; this.typesBestand = []; this.omschrijving = ""; }, "uploadFile": function(file){ var reader = new FileReader(); reader.imgPreview = this.imgPreview; reader.bestandnaamTekst = this.bestandsnaamTekst; reader.onloadend = function(event){ //alert(this); if (file.type.match('image.*')){ this.imgPreview.setAttribute('src', event.target.result); } else { this.imgPreview.setAttribute('src', 'icons/UnknownFile.png'); } this.bestandnaamTekst.innerHTML = file.name; }; reader.readAsDataURL(file); var fd = new FormData(); if (this.omschrijving == ""){ fd.append("omschrijving", file.name); } else { fd.append("omschrijving", this.omschrijving); } fd.append("imid",""); fd.append("bestand", file, file.name); for (var i = 0; i < this.typesBestand.length; i++){ fd.append("typelijst", this.typesBestand[i]); } var oReq = new XMLHttpRequest(); oReq.upload.textveld = this.statusText; oReq.textveld = this.statusText; oReq.upload.meter = this.meter; oReq.meter = this.meter; oReq.source = this; oReq.uploadEvent = this.uploadDiv.uploadEvent; this.statusText.innerHTML = "start"; oReq.upload.addEventListener( "progress", function(evt){ var percentComplete = Math.round(evt.loaded * 100 / evt.total); this.meter.setAttribute('value', percentComplete); this.textveld.innerHTML = "" + percentComplete + " %"; }, false ); oReq.onreadystatechange = function() { if (oReq.readyState == 4) { var obj = JSON.parse(oReq.responseText); this.textveld.innerHTML = "100 %"; this.meter.setAttribute('value', '100'); this.uploadEvent(obj.imid, this.source); } } oReq.open("POST", this.linkNaarServlet, true); oReq.send(fd); }, "setFileUploadedEvent": function(event){ this.uploadDiv.uploadEvent = function (data, source){ event(data, source); }; }, "setLinkNaarServlet": function (linkNaarServlet){ this.linkNaarServlet = linkNaarServlet; }, "getLinkNaarServlet": function(){ return (this.linkNaarServlet); }, "setTypesBestand": function(types){ this.typesBestand = types; }, "getTypesBestand": function() { return this.typesBestand; }, "setOmschrijving": function(omschrijving){ this.omschrijving = omschrijving; }, "getOmschrijving": function(){ return (this.omschrijving); } } ) Support types and functions MolBox This is the same as a Box, but modified to set the class property of the table. This can be changed to a simple Box if you remove the TableClass property. DataEvent delegate DataEvent(rowdata Any in, e Event in) end BrowserFunctions Externaltype Library of javascript functions: dragDropFileUploadSupported (can be removed if no check for browser support is required "dragDropFileUploadSupported": function(){ return(typeof FileReader != 'undefined'); }, getHostFromUrl (to check if the servlet host is the same as the Rich UI url to prevent cross site scripting security errors. "getHostFromUrl": function(url){ var l = document.createElement("a"); l.href = url; return l.host; }, getHostFromAddressbar (To get the hostname from the Rich URL)