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)