/**
* Implements a file-chooser, drago-drop functionality (Webbrowser only) and webcam functionality for ELO wf Forms
*
* @author ESt, ELO Digital Office GmbH
* @version 1.03.006
*
* In some forms, you'd maybe like to let the user select a picture. And save it for later use.
* If so, please use the 113_capturepic_webcam "HR"-Form as an inspiration.
*
* As soon as you defined your own form having all required fields, you can use the following config for initializing this class
* ### Example config
*
* {
* name: "personnelphotopicker",
* webcamName: "personnelphotocam",
* webcamConfig: {
* javaStartupButton: "WEBCAM_JAVA",
* varNameBtnReset: "JS_WEBCAM_RESET",
* varNameBtnSnap: "JS_WEBCAM_SNAP",
* varNameContainer: "WEBCAM_INIT",
* width: 540,
* height: 390,
* dest_width: 720,
* dest_height: 520,
* crop_width: 400,
* crop_height: 520,
* image_format: "jpeg",
* jpeg_quality: 90,
* swfURL: "lib_webcam.swf",
* fps: 45,
* showIfNoCam: true
* },
* dropZoneId: "dropZone",
* filePickerId: "filePicker",
* accept: "image/jpeg, image/jpg, image/png",
* maxSize: "3", //Megabyte (float values possible)
* maskNameForRule: "Personnel file",
* solTypeForRule: "PERSONNELFILE",
* photoReferenceField: "HR_PERSONNEL_PHOTO_GUID",
* photoReferenceFieldObjId: "HR_PERSONNEL_PHOTO_OBJID",
* clearPreviewField: "JS_PICTURE_CLEAR",
* filePickerField: "JS_FILEPICKER",
* photoConfig: {
* maskName: "Personnel file document",
* pictureName: "Mitarbeiterfoto"
* }
* }
*
* ### Setup
* Add the following to your `Header.txt`
*
* function onInit() {
* this.fcv = sol.create("sol.common.forms.FileChooserVariants", config);
* }
*
* // i recommend to save using these precautions, otherwise empty images might get saved
* // also, note that "webbrowser"-webcam pictures need to be saved using the function "webcam.savePicture" instead of calling "fcv.uploadImage" directly
* function nextClicked(id) {
* return (
* sol.common.forms.Utils.disableCancelButtonValidation(id, ["sol.common.wf.node.cancel"])
* || (typeof this.fcv.displayImage !== "undefined" && this.fcv.displayImage.src && this.fcv.uploadImage(this.fcv.displayImage.src))
* || ($var(config.webcamConfig.varNameContainer) && this.fcv.webcam.savePicture(this.fcv.uploadImage.bind(this.fcv)));
* );
* }
*
*/
sol.define("sol.common.forms.FileChooserVariants", {
//instruments
webcam: undefined,
dropZone: undefined,
filePicker: undefined,
// base64 temporary stores
dragdropfile: undefined,
webacamImage: undefined,
//divs
displayDiv: undefined,
displayImage: undefined,
defaultConfig: {
name: "filepicker",
webcamName: "photocam",
webcamConfig: {
javaStartupButton: "WEBCAM_JAVA",
varNameBtnReset: "JS_WEBCAM_RESET",
varNameBtnSnap: "JS_WEBCAM_SNAP",
varNameContainer: "WEBCAM_INIT",
width: 540,
height: 390,
dest_width: 720,
dest_height: 520,
crop_width: 400,
crop_height: 520,
image_format: "jpeg",
jpeg_quality: 90,
swfURL: "lib_webcam.swf",
fps: 45,
showIfNoCam: true
},
dropZoneId: "dropZone",
filePickerId: "filePicker",
accept: "image/jpeg, image/jpg, image/png",
maxSize: "3",
photoReferenceField: "",
photoReferenceFieldObjId: "FILEPICKER_FILE_OBJID",
clearPreviewField: "JS_PICTURE_CLEAR",
filePickerField: "JS_FILEPICKER",
photoConfig: {
maskName: "Basic Entry",
pictureName: "my_file"
}
},
/**
* Initialize the tools
* @param {Object} config Configuration
*/
initialize: function (config) {
var me = this,
fakeWebcamDiv;
sol.common.forms.Utils.initializeIxSession();
me.merge(config, me.defaultConfig);
me.config = config;
// initialize webcam
me.webcam = (navigator.userAgent.indexOf("JavaFX") === -1) && sol.create("sol.common.forms.Webcam", me.config.webcamConfig);
if (/*(navigator.userAgent.indexOf("JavaFX") > -1) && */$var(me.config.webcamConfig.varNameContainer)) {
$var(me.config.webcamConfig.varNameContainer).style.width = String(me.calcLiveCrop()[0]) + "px";
$var(me.config.webcamConfig.varNameContainer).style.height = String(me.calcLiveCrop()[1]) + "px";
}
//initialize dropZone
me.dropZone = document.createElement("div");
me.dropZone.id = me.config.dropZoneId;
me.dropZone.style = "background: gray; position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: 999; opacity: 0.6; visibility: hidden;";
document.body.appendChild(me.dropZone);
me.dropZone.addEventListener("dragleave", me.hideDropZone.bind(me));
me.dropZone.addEventListener("drop", me.fileDroppedHandler.bind(me));
me.dropZone.addEventListener("dragenter", function (e) {
e.dataTransfer.dropEffect = "copy"; e.preventDefault();
});
me.dropZone.addEventListener("dragover", function (e) {
e.dataTransfer.dropEffect = "copy"; e.preventDefault();
});
window.addEventListener("dragenter", me.showDropZone.bind(me));
//initialize filePicker
me.filePicker = document.createElement("input");
me.filePicker.type = "file";
me.filePicker.id = me.config.filePickerId;
me.filePicker.accept = me.config.accept;
me.filePicker.style = "display:none";
me.filePicker.addEventListener("change", me.filePickedInBrowser.bind(me)); // will only be called in Browser
$var(me.config.filePickerField).addEventListener("click", me.filePickedHandler.bind(me));
//assign event-handlers
$var(me.config.webcamConfig.varNameBtnSnap).onclick = me.JS_WEBCAM_SNAP.bind(me);
$var(me.config.webcamConfig.varNameBtnReset).onclick = me.webcam && me.webcam.reset.bind(me);
$var(me.config.webcamConfig.javaStartupButton).onclick = me.JS_WEBCAM_SNAP.bind(me);
$var(me.config.clearPreviewField).onclick = me.JS_PICTURE_CLEAR.bind(me);
if (navigator.userAgent.indexOf("JavaFX") === -1) {
$hide(me.config.webcamConfig.javaStartupButton); //Filereader only exists in Web-Clients. Hide Java Webcambutton in Webclient
} else {
$hide(me.config.webcamConfig.varNameBtnSnap); // Hide JS Webcambutton in Java Client
fakeWebcamDiv = document.createElement("div");
fakeWebcamDiv.classList.add("sol-webcam");
(document.getElementById(me.config.webcamConfig.varNameContainer)).appendChild(fakeWebcamDiv);
}
me.buttonMode("noReset");
},
calcLiveCrop: function () {
var me = this, c = me.config.webcamConfig;
return [Math.floor(c.crop_width / c.dest_width * c.width), Math.floor(c.crop_height / c.dest_height * c.height)];
},
javaBrowser: function () {
return (navigator.userAgent.indexOf("JavaFX") > -1);
},
buttonMode: function (mode) {
var me = this,
jCam = me.config.webcamConfig.javaStartupButton,
wCam = me.config.webcamConfig.varNameBtnSnap,
xChoose = me.config.filePickerField,
xReset = me.config.clearPreviewField;
if (mode === "resetOnly") {
$show(xReset);
$hide(xChoose);
if (me.javaBrowser()) {
$hide(jCam);
} else {
$hide(wCam);
}
} else if (mode === "noReset") {
$hide(xReset);
$show(xChoose);
if (me.javaBrowser()) {
$show(jCam);
} else {
Webcam.loaded && $show(wCam);
}
}
},
// guarded file upload for nextClicked or saveClicked
uploadFile: function () {
var me = this;
(typeof me.displayImage !== "undefined" && me.displayImage.src && me.uploadImage(me.displayImage.src)) ||
($var(me.config.webcamConfig.varNameContainer) && me.webcam && me.webcam.savePicture(me.uploadImage.bind(me)));
},
// function can be used to upload an image
uploadImage: function (image) {
var me = this,
base64, result;
try {
ELOF.showLoadingDiv();
base64 = image.replace(/^data:image\/(jpeg|png|gif|bmp);base64,/, "");
if (base64 !== "") {
result = sol.common.IxUtils.execute("RF_sol_common_document_service_UploadFile", {
objId: ELO_PARAMS.ELO_OBJID,
base64Content: base64,
encryptionSet: 0,
cfg: me.config.photoConfig
});
sol.common.forms.Utils.setHiddenValue({ type: "GRP", key: me.config.photoReferenceField }, result.guid);
sol.common.forms.Utils.setHiddenValue({ type: "MAP", key: me.config.photoReferenceFieldObjId }, result.objId);
}
} catch (err) {
console.info("Error: " + err);
} finally {
ELOF.hideLoadingDiv();
}
return false;
},
createDivsIfNoneExist: function () {
var me = this;
if (!me.displayImage) {
me.displayDiv = document.createElement("div");
me.displayDiv.classList.add("display-image-result-container");
me.displayDiv = (document.getElementById(me.config.webcamConfig.varNameContainer)).appendChild(me.displayDiv);
me.displayImage = document.createElement("img");
me.displayImage.classList.add("display-image-result");
me.displayDiv.appendChild(me.displayImage);
}
},
displayImageInPage: function (reader, file) {
var me = this;
me.createDivsIfNoneExist();
me.displayImage.file = file;
reader.onload = (function (aImg) { return function (e) { aImg.src = e.target.result; }; })(me.displayImage);
reader.readAsDataURL(file);
me.buttonMode("resetOnly");
$var(me.config.webcamConfig.varNameContainer) && $var(me.config.webcamConfig.varNameContainer).classList.add("webcamImageHidden");
},
fileExtOk: function (allowedTypes, fileName) {
var allowedExts = allowedTypes.map(function (type) {
var match = type.match(/.*\/(\w+)$/);
return match ? match[1] : type;
}),
fileExt = (function (name) {
var match = name.match(/.*\.(\w+)$/);
return match ? match[1] : "";
})(fileName);
return allowedExts.indexOf(fileExt) > -1;
},
preconditionsFulfilled: function (file) {
var me = this,
ftMsg = ($val("WF_MAP_FILEPICKER_FILETYPEMSG") || "File type not supported. Allowed file types:") + " ",
fsMsg = ($val("WF_MAP_FILEPICKER_FILESIZEMSG") || "File too big. Maximum allowed file size:") + " ",
fcMsg = ($val("WF_MAP_FILEPICKER_FILECORRUPTMSG") || "This file is corrupt and can't be uploaded. Please try to repair it or convert it to a supported format:") + " ",
allowedTypes = (me.config.accept.split(",").map(function (s) { return s.trim(); }));
if (file.size <= (Math.floor(me.config.maxSize * 1000000))) {
if (allowedTypes.indexOf(file.type) > -1) {
return true;
} else if (file.name && me.fileExtOk(allowedTypes, file.name)) {
return true;
} else if (!file.name && (!file.type || file.type === "null")) {
eloAlert(fcMsg + me.config.accept, "Error");
} else {
eloAlert(ftMsg + me.config.accept, "Info");
}
} else {
eloAlert(fsMsg + me.config.maxSize + "MB.", "Info");
}
return false;
},
fileDroppedHandler: function (ev) {
var me = this, dt = ev.dataTransfer, reader = new FileReader();
ev.preventDefault();
me.hideDropZone();
// If dropped items aren't files, reject them
if (dt.items && dt.items.length > 0 && dt.items[0].kind == "file") {
me.dragdropfile = dt.items[0].getAsFile();
me.preconditionsFulfilled(me.dragdropfile) && me.displayImageInPage(reader, me.dragdropfile);
}
},
filePickedInBrowser: function (ev) {
var me = this, dt = ev.target, reader = new FileReader();
if (navigator.userAgent.indexOf("JavaFX") === -1) { //web-client
ev.preventDefault();
if (dt.files && dt.files.length > 0) {
me.dragdropfile = dt.files[0];
me.preconditionsFulfilled(me.dragdropfile) && me.displayImageInPage(reader, me.dragdropfile);
}
}
},
filePickedHandler: function (e) {
var me = this, data;
if (navigator.userAgent.indexOf("JavaFX") === -1) { // Web-Client
if (me.filePicker) {
me.filePicker.value = null;
me.filePicker.click(); //this executes the actual handler for opening the file selection dialog. This trick is required, because I did not
//want to use the standard filepicker Button. (it would not look like an ELO Button)
}
} else { // Java-Client
data = { text: "capture", objId: ELO_PARAMS.ELO_OBJID }; // I think, sendCustomMessage needs this?
me.createDivsIfNoneExist();
api.communication.Parent.sendCustomMessage("workspace.showFileChooserDialog_" + JSON.stringify({ accept: me.config.accept, maxSize: me.config.maxSize }), data, function (data, event) {
me.dragdropfile = { base64: String(data.response.base64), type: String(data.response.type) };
if (me.preconditionsFulfilled(data.response)) {
if (me.dragdropfile.base64 !== "") {
me.displayImage.src = "data:" + data.response.type + ";base64," + me.dragdropfile.base64;
me.buttonMode("resetOnly");
$var(me.config.webcamConfig.varNameContainer) && $var(me.config.webcamConfig.varNameContainer).classList.add("webcamImageHidden");
}
}
});
}
},
merge: function (custom, base, log, path, assignCallback, recursionCheck) {
var me = this,
prop;
log = log || [];
path = path || "";
recursionCheck = recursionCheck || function (custom, base, prop) {
return base[prop] instanceof Object && !(base[prop] instanceof Array) && !(base[prop] instanceof Date);
};
assignCallback = assignCallback || function (target, source, propertyName) {
target[propertyName] = source[propertyName];
};
for (prop in base) {
if (base.hasOwnProperty(prop)) {
//check for same type (array must be checked separately) and use default property instead
if (typeof custom[prop] !== "undefined" && ((typeof custom[prop] !== typeof base[prop]) ||
(Array.isArray(base[prop]) !== Array.isArray(custom[prop])))) {
log.push("Warning: The type of custom property " + path + "." + prop + " is not the same as in the target. Custom property is ignored.");
custom[prop] = me.clone(base[prop]);
} else if (recursionCheck(custom, base, prop)) {
//recursion
custom[prop] = me.merge(custom[prop] || {}, base[prop], log, path + "." + prop, assignCallback, recursionCheck); //return empty object if p does not exist in target
} else if (custom[prop] === undefined) {
//copy default property only if not exist in custom
if (base[prop] instanceof Date) {
custom[prop] = new Date(base[prop]);
} else if (base[prop] instanceof Array) {
custom[prop] = me.clone(base[prop]);
} else {
assignCallback(custom, base, prop);
}
}
}
}
return custom;
},
/**
* small wrapper around interval functions
*
* This function is 100% decoupled from any ELO-specifics and can be used in pure Javascript environments
*
* f: function to perform work
*
* until: function to check if f should be run again or not
*
* after: function to run after until conditions were met
*
* intervalTime: time in ms (interval runs every x ms)
*
* params: {
* f: any pass f any param you like
* f_message:String console.log before f is called
* until:any pass any any param you like
* until_message: String console.log before until is called
* conditionmet_message:String console.log if contition is met
* after:any pass after any param you like
* afterafter_message:String console.log after after was called
* logging: Boolean enables logging if set to true
* maxtries: Integer how often the f function will be executed before the interval is stopped
* name: String name in logger
* }
*
* if the messages are defined as empty strings, no console-logging will be done.
*
* Of course the execution time of f and until is synchronized. f always runs before until.
*
* @returns a function, which can later be used to stop the interval, if the until condition seems to never reach an end
*/
intervalUntil: function (f, until, after, intervalTime, params) {
var intervals = [], parms = params || {}, maxTries = params.maxtries - 1, executed = 0,
allowF = true, allowUntil = false; //for synchronizing f and until
function logIt(message, paramMsg) {
parms.logging && paramMsg !== "" && console.log("Interval " + (parms.name ? "'" + parms.name + "'" : "") + ":" + (paramMsg || message));
}
intervals.push(
setInterval( //setInterval is a standard Javascript function
function () {
if (!allowF) {
return;
}
logIt("performing work", parms.f_message);
f && f(parms.f);
executed++;
allowF = false;
allowUntil = true;
}, intervalTime
)
);
intervals.push(
setInterval(
function () {
if (!allowUntil) {
return;
}
logIt("until condition met?", parms.until_message);
if (parms.maxtries && maxTries-- === 0 || until && until(parms.until)) {
logIt(maxTries === 0 ? "Tried too many times:" + parms.maxTries : "until condition met!", maxTries === 0 ? parms.maxTriesMessage : parms.conditionmet_message);
intervals.forEach(clearInterval); //clearInterval is a standard Javascript function
after && after(parms.after);
logIt("after callback executed. Interval finished & closed after " + executed + " cycles!", parms.afterafter_message);
}
allowF = true;
allowUntil = false;
}, intervalTime
)
);
return function () {
intervals.forEach(clearInterval);
logIt("closed manually after " + executed + " cycles!", parms.clearedManually);
};
},
// takes a webcam-picture
JS_WEBCAM_SNAP: function () {
var me = this,
data = { text: "capture", objId: ELO_PARAMS.ELO_OBJID, cropImageDimension: { width: me.config.webcamConfig.width, height: me.config.webcamConfig.height } };
me.createDivsIfNoneExist();
if (navigator.userAgent.indexOf("JavaFX") === -1) { // Web-Client
me.JS_PICTURE_CLEAR();
Webcam.freeze(); //library function
me.webcam.state = true;
me.buttonMode("resetOnly");
} else { // Java-Client
try {
me.webcamImage = "";
api.communication.Parent.sendCustomMessage("sol.common.jc.WebcamUtils.getImage", data, function (data, event) {
me.webcamImage = data.response;
if (typeof me.webcamImage === "string" && me.webcamImage !== "") {
me.displayImage.src = "data:image/jpeg;base64," + me.webcamImage;
me.buttonMode("resetOnly");
$var(me.config.webcamConfig.varNameContainer) && $var(me.config.webcamConfig.varNameContainer).classList.add("webcamImageHidden");
}
});
} catch (e) {
console.log("Webcam already in use in another application");
}
}
},
// clears / resets the captured picture
JS_PICTURE_CLEAR: function () {
var me = this;
me.webcam.state = false;
me.webcam.capture = false;
Webcam.unfreeze(); //library function
if (typeof me.displayImage !== "undefined" && me.displayImage.src) {
me.displayImage.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+P+/HgAFhAJ/wlseKgAAAABJRU5ErkJggg=="; // base64 empty pixel
me.displayImage.src = "";
me.buttonMode("noReset");
$var(me.config.webcamConfig.varNameContainer) && $var(me.config.webcamConfig.varNameContainer).classList.remove("webcamImageHidden");
} else {
me.buttonMode("noReset");
}
},
showDropZone: function (e) {
this.dropZone.style.visibility = "visible";
},
hideDropZone: function (e) {
this.dropZone.style.visibility = "hidden";
}
});