const config=new Object(); function clear(){ let el=config.document.getElementById(config.debugId); if (el===null) { //alert("Debug section not initialized"); return; } config.document.getElementById(config.debugId).value=""; } function print(msg){ let el=config.document.getElementById(config.debugId); if (el===null) { //alert("Debug section not initialized. Message: "+msg); return; } el.value+="\n"+msg; } function getCRFrefFirst(){ //crfRef is part of html call and gets stored in the page return config.document.getElementById(config.crfRefId).innerHTML; } function getCRFref(){ //'crfRefId' return config.formConfig.crfEntry['entryId']; } function getCRFrefData(){ let parentCrf=config.formConfig.crfEntry['parentCrf']; if (parentCrf!=undefined) return parentCrf; return getCRFref(); } function onFailure(errorInfo, options, responseObj){ if (errorInfo && errorInfo.exception) alert("Failure: " + errorInfo.exception); else alert("Failure: " + responseObj.statusText); } function doNothing(){ print('doNothing called'); } function generateDebugSection(){ //let debug=true; //if (debug) print("generateDebugSection "+sectionName); let formName=config.debugDiv; let sectionName="debugSection"; let sectionTitle="Debug Messages"; let tb=config.document.createElement('table'); tb.className='t2'; let row=tb.insertRow(); let cell=config.document.createElement('th'); row.appendChild(cell); cell.setAttribute("colspan","4"); cell.style.fontSize="20px"; cell.style.textAlign="center"; let cellData=config.document.createTextNode(sectionTitle); cell.appendChild(cellData); cell=row.insertCell(); let input=config.document.createElement("input"); input.type="button"; input.id="toggle"+sectionName+"VisbilityButton"; input.onclick=function(){toggleVisibility(sectionName,input.id)}; cell.appendChild(input); config.document.getElementById(formName).appendChild(tb); let div=config.document.createElement('div'); div.id=sectionName; config.document.getElementById(formName).appendChild(div); //start open (for debug) //input.value="Hide"; //div.style.display="block"; //start hidden (for production) input.value="Show"; div.style.display="none"; let debugArea=config.document.createElement('textarea'); debugArea.rows=10; debugArea.cols=95; debugArea.id=config.debugId; div.appendChild(debugArea); //print('ver: 0.10'); } function getAdditionalData(formSetupEntry){ //return information on additional data associated with the form //additionalData is a sub-list with multiple entries per patient/visit //argument is the row of the formSetup setup list let queryName=config.formConfig.queryMap[formSetupEntry['queryName']]; let fName='[getAdditionalData/'+queryName+']'; print(fName); //additionalData holds a reference to all queries already parsed //this helps in reducing number of calls to the database (I assume) if (queryName in config.formConfig.additionalData){ print(fName+': Returning preset value'); return config.formConfig.additionalData[queryName]; } //first time we see this query, so we have to do the setup print(fName+': generating'); config.formConfig.additionalData[queryName]=new Object(); //takes address, so further changes will be to the newly created object //in fact, ad is just a short alias of the long variable name on the right let ad=config.formConfig.additionalData[queryName]; //no additional data if (formSetupEntry["showFlag"]==="NONE") { print(fName+": empty"); return ad; } //use showFlag to setup report section of the CRF list if (formSetupEntry["showFlag"]==="REVIEW") { //abuse additionalData to signal different segment print(fName+": generateReport"); ad.isReview=true; return ad; } //setup the additionalData memory object print(fName+': setting values'); ad.showFlag=formSetupEntry["showFlag"]; ad.showFlagValue=formSetupEntry["showFlagValue"]; ad.queryName=formSetupEntry["showQuery"]; //for data queries, limit to present CRF only ad.filters=new Object(); ad.filters['crfRef']=getCRFref(); //compose a long debug message let msg=fName+": flag "+ad.showFlag; msg+=" value "+ad.showFlagValue; msg+=" query "+ad.queryName; print(msg); return ad; } function fullAccessSetup(listName){ //generate setup object whcih should contain fields: //readonlyFlag - whether the dataset is writeable //filters - selection fields that allow creation of LABKEY.Filter.create() //getInputId - formating of unique ids for html elements //addApply - whether a submit/Save button is generated //unique - whether entries in list are unique let debug=true; if (debug) print("fullAccessSetup"); let setup=new Object(); setup.queryName=listName; setup.readonlyFlag=function(vName){return false}; setup.filters=new Object(); setup.filters['crfRef']=getCRFref(); setup.getInputId=function(vName){return listName+"_"+vName;} setup.addApply="Save"; setup.isReview=false; return setup; } function readonlySetup(listName){ //see definition of setup object above let debug=true; if (debug) print("readonlySetup"); let setup=new Object(); setup.queryName=listName; setup.readonlyFlag=function(vName){return true}; setup.filters=new Object(); setup.filters['crfRef']=getCRFref(); setup.getInputId=function(vName){return listName+'_'+vName;} setup.isReview=false; return setup; } function getSetup(listName,writeAccess=true){ //change to section granulated permission of type EDIT, COMMENT, READ //let formStatus=config.formConfig.formStatus; //equivalent to READ if (!writeAccess) //if (formStatus=="Submitted") return readonlySetup(listName); //if (formStatus=="Approved") // return readonlySetup(listName); return fullAccessSetup(listName); } function generateSection(formSetupEntry){ let listName=config.formConfig.queryMap[formSetupEntry['queryName']]; let fName='[generateSection/'+listName+']'; let sectionTitle=formSetupEntry['title']; let accessModeColumn=config.formConfig.operator+'Mode'; let accessMode=formSetupEntry[accessModeColumn]; //this will fix it for later use as well let additionalData=getAdditionalData(formSetupEntry); print(fName); let formName=config.masterForm;//this is HTML designator of area on page let debug=true; let tb=config.document.createElement('table'); tb.className='t2'; let row=tb.insertRow(); let cell=config.document.createElement('th'); row.appendChild(cell); cell.setAttribute("colspan","4"); cell.style.fontSize="20px"; cell.style.textAlign="center"; let cellData=config.document.createTextNode(sectionTitle); cell.appendChild(cellData); cell=row.insertCell(); let input=config.document.createElement("input"); input.type="button"; input.value="Show"; input.id="toggle"+listName+"VisbilityButton"; input.onclick=function(){toggleVisibility(listName,input.id)}; cell.appendChild(input); config.document.getElementById(formName).appendChild(tb); let div=config.document.createElement('div'); div.id=listName; div.style.display="none"; config.document.getElementById(formName).appendChild(div); let divTable=config.document.createElement('div'); divTable.id=listName+"Table"; div.appendChild(divTable); if ("showFlag" in additionalData) { additionalData.divName=listName+"SubDiv"; additionalData.divQueryName=listName+"SubDivList"; let div1=config.document.createElement('div'); div1.id=additionalData.divName; div1.style.display="none"; div.appendChild(div1); let div2=config.document.createElement('div'); div2.id=additionalData.divQueryName; div1.appendChild(div2); } if (debug) print("generate master table"); let writeMode=accessMode=="EDIT"; let setup=getSetup(listName,writeMode); if ("isReview" in additionalData){ generateReviewSection(listName,div.id,generateReviewSectionCB); return; } //master table is unique per visit setup.unique=true; generateTable(listName,divTable.id,additionalData,setup); if (debug) print("generate master table: done"); let generateSubTable=true; //generateSubTable equivalent to read/write access to section if (accessMode != "EDIT") generateSubTable=false; if (! ("showFlag" in additionalData) ) generateSubTable=false; if (generateSubTable){ let qName=additionalData.queryName; let dName=additionalData.divName; let setup=fullAccessSetup(qName); //only set master query for additionalData setup.masterQuery=listName; //if (readonly) setup=readonlySetup(config); generateTable(qName,dName,additionalData,setup); //generateTable(formSetupEntry,qName,dName,additionalData,setup); } print("generate review"); let divReviewList=config.document.createElement('div'); divReviewList.id=listName+"ReviewList"; div.appendChild(divReviewList); let divReview=config.document.createElement('div'); divReview.id=listName+"Review"; div.appendChild(divReview); //assume we already have listId (content of config.setupQueryName is listId) //we need listName also //qconfig.queryName=config.setupQueryName; generateReview(divReview.id,divReviewList.id,listName,accessMode); if (accessMode!='GENERATE') return; print('Adding generate button'); //add generateButton let divGenerateButton=config.document.createElement('div'); divGenerateButton.id=listName+'GenerateButton'; div.appendChild(divGenerateButton); print('Adding generate button completed to here'); let cb=function(){onGenerateQuery(listName);}; generateButton(divGenerateButton.id,'Generate','Generate '+listName,'XX',cb); print('Adding generate button completed'); } //>>>>reviewSection associated routines function parseResponseXML(){ //print(config.config,'Status:' +this.status); print('Status:'+this.status); if (this.status!=200) return; config.loadFileConfig.json=JSON.parse(this.responseText); config.loadFileConfig.cb(); } function loadFile(){ print('YY: '+config.loadFileConfig.url); let connRequest=new XMLHttpRequest(); connRequest.addEventListener("loadend",parseResponseXML); //function(e){parseResponseXML(e,config);}); connRequest.open("GET", config.loadFileConfig.url); connRequest.send(); } function getBasePath(){ let server=LABKEY.ActionURL.getBaseURL(); let basePath=server+"_webdav"; basePath+=LABKEY.ActionURL.getContainer(); return basePath; } function generateReviewSection(listName,id,callback){ //callback should be generateReviewSectionCB and it takes no arguments print("generateReviewSection"); //need base path config.loadFileConfig=new Object(); config.loadFileConfig.cb=callback; config.loadFileConfig.id=id; config.loadFileConfig.url=getBasePath()+'/@files/reportSetup/'+listName+'.json'; loadFile(); //load file and continue in the next function } function generateErrorMessage(id,listName,msg){ print('generateErrorMessage:'); let eid=listName+"_errorMsg"; let el=config.document.getElementById(eid); if (el===null){ el=config.document.createElement("p"); config.document.getElementById(id).appendChild(el); } el.innerHTML=msg; } function clearErrorMessage(listName){ let eid=listName+"_errorMsg"; let el=config.document.getElementById(eid); if (el===null) return; el.remove(); } function getParticipantCode(pid){ let selectRows=new Object(); selectRows.schemaName='lists'; //we should now which form we are in let mfId=config.formConfig.form['masterQuery']; selectRows.queryName=config.formConfig.queryMap[mfId]; //point to data container selectRows.containerPath=getContainer('data'); //selectRows.queryName='PET'; pid.afterId=setParticipantCode; pid.participantField=config.formConfig.studyData["SubjectColumnName"]; selectRows.success=function(data){afterRegistration(pid,data);} selectRows.filterArray=[LABKEY.Filter.create("crfRef",getCRFref())]; LABKEY.Query.selectRows(selectRows); } function visitCodeFromVisitId(visitId){ if (visitId<0) return "NONE"; let project=getContainer('data'); print('visitCodeFromVisitId: '+project.search('retro')); if (project.search('retro')>-1) visitId-=1; return 'VISIT_'+visitId.toString(); } function replaceSlash(x){ return x.replace(/\//,'_'); } function setParticipantCode(pid){ let fName='[setParticipantCode]'; let rows=pid.registration.rows; //pick from study let participantField=config.formConfig.studyData["SubjectColumnName"]; if (rows.length==1){ print(fName+': '+rows[0][participantField]+'/'+rows[0].visitId); let visitCode=visitCodeFromVisitId(rows[0].visitId); print('setParticipantCode: '+pid.participantId+'/'+visitCode); pid.participantCode=replaceSlash(pid.participantId); pid.visitCode=visitCode; } generateReviewSection2(pid); } function generateReviewSectionCB(){ let listName=config.loadFileConfig.listName; let id=config.loadFileConfig.id; clearErrorMessage(listName); let pid=new Object(); pid.participantCode="NONE"; pid.visitCode="NONE"; getParticipantCode(pid); print('Get participant code sent'); //involves database search, continue after callback } function getValueFromElement(id,defaultValue){ let e=config.document.getElementById(id); if (e!=null){ defaultValue=e.innerHTML; } return defaultValue; } function pickParticipantCodeFromPage(){ let pid=new Object(); pid.participantCode=getValueFromElement("participantCode","NIX-LJU-D2002-IRAE-A000"); pid.visitCode=getValueFromElement("visitCode","VISIT_1"); generateReviewSection2(pid); } function patternReplace(src,replacements,values){ for (rep in replacements){ let txt1=src.replace(new RegExp(rep),values[replacements[rep]]); src=txt1; } return src; } function plotImage(cell,k,row,rowVariable,obj,pid){ let baseDir=patternReplace(obj.imageDir,obj.replacements,pid); print('Base dir: '+pid.basePath); pid[obj.variable]=obj.values[k]; cell.id=pid[obj.variable]+"_"+rowVariable+pid[rowVariable]; let img=null; let imgId=cell.id+'_img_'; img=config.document.getElementById(imgId); if (img===null){ img=config.document.createElement('img'); img.id=imgId; cell.appendChild(img); } let imgSrc=patternReplace(obj.file,obj.replacements,pid); print('Image: '+imgSrc); let imagePath=pid.basePath+'/'+baseDir+'/'+imgSrc; img.src=imagePath; img.width="300"; } function showReport(cell,k,row,rowVariable,obj,pid){ cell.width="300px"; cell.id='report_'+obj.values[k]+"_"+rowVariable+pid[rowVariable]; let reportConfig=new Object(); reportConfig.partName="Report"; reportConfig.renderTo=cell.id; //reportConfig.showFrame=false; //reportConfig.width="300"; reportConfig.frame="none"; reportConfig.partConfig=new Object(); reportConfig.partConfig.width="300"; reportConfig.partConfig.title="R Report"; reportConfig.partConfig.reportName=obj.values[k]; for (f in obj.parameters){ reportConfig.partConfig[f]=pid[f]; } reportConfig.partConfig.showSection="myscatterplot"; let reportWebPartRenderer = new LABKEY.WebPart(reportConfig); print('Render to: '+reportConfig.renderTo); reportWebPartRenderer.render(); } function showProbability(cell,k,row,rowSetup,j,obj,pid){ print('showProbability: '+rowSetup); let rowVariable=rowSetup.variable; cell.id='prob_'+obj.values[k]+"_"+rowVariable+pid[rowVariable]; let probDensity=new Object(); probDensity.mean=rowSetup.mean[j]; probDensity.sigma=rowSetup.sigma[j]; print('showProbability: mean '+probDensity.mean+' sigma '+probDensity.sigma); probDensity.func=obj.values[k]; probDensity.organCode=pid.organCode; pid[obj.variable]=rowSetup[obj.variable][j]; probDensity.percentile=pid.percentile; let selectRows=new Object(); selectRows.queryName=obj.queryName; selectRows.schemaName="study"; selectRows.filterArray=[]; selectRows.containerPath=getContainer('data'); for (let f in obj.filters){ selectRows.filterArray.push( LABKEY.Filter.create(f,pid[obj.filters[f]])); print('Filter ['+f+']: '+pid[obj.filters[f]]); } selectRows.success=function(data){ drawProbability(data,cell,obj,pid,probDensity);} LABKEY.Query.selectRows(selectRows); } function erf(x){ let fx=[0,0.02,0.04,0.06,0.08,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9, 1,1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8,1.9,2, 2.1,2.2,2.3,2.4,2.5,3,3.5]; let fy=[0,0.222702589,0.328626759,0.428392355,0.520499878, 0.603856091,0.677801194,0.742100965,0.796908212, 0.842700793,0.880205070,0.910313978,0.934007945, 0.952285120,0.966105146,0.976348383, 0.983790459,0.989090502,0.992790429,0.995322265, 0.997020533,0.998137154,0.998856823,0.999311486, 0.999593048,0.999977910,0.999999257]; let n=32; let i0=n-1; for (let i=1;i<n;i++){ if (Math.abs(x)>fx[i]) continue; i0=i-1; break; } let fval=1; if (i0<n-1){ //interpolate let y1=fy[i0+1]; let y0=fy[i0]; let x1=fx[i0+1]; let x0=fx[i0]; fval=y0+(y1-y0)/(x1-x0)*(Math.abs(x)-x0); } print('Erf: '+fval); if (x<0) return -fval; return fval; } function setLine(fbox,name,value,fontSize){ let fpId=fbox.id+name; let fp=config.document.getElementById(fpId); if (fp===null){ fp=config.document.createElement("p"); fp.id=fpId; fbox.appendChild(fp); } fp.classList.add("center"); fp.style.textAlign="center"; fp.style.fontSize=fontSize; fp.innerText=value; } function drawProbability(data,cell,obj,pid,probDensity){ print('drawProbability'); if (data.rows.length!=1){ print("drawProbability row length mismatch: "+data.rows.length); return; } //possible mismatch; I assume the dataset will have a field called value let val=data.rows[0].value; let prob=0; let fz=-100; if (probDensity.func=="gaus"){ fz=(val-probDensity.mean)/probDensity.sigma/Math.sqrt(2); prob=0.5+0.5*erf(fz); } let color="red"; let fzx=fz*Math.sqrt(2); print('drawProbability '+fzx); for (let i=1;i<obj.intervals.n;i++){ if (fzx>obj.intervals.zlimits[i]) continue; color=obj.intervals.colors[i-1]; break; } let fboxId=cell.id+'_fbox_'; let fbox=config.document.getElementById(fboxId); if (fbox===null){ fbox=config.document.createElement("div"); fbox.id=fboxId; cell.appendChild(fbox); } fbox.style.backgroundColor=color; fbox.style.width="180px"; fbox.style.height="180px"; print('organCode '+probDensity.organCode); let organName="Lung"; if (probDensity.organCode==4){ organName="Thyroid"; } if (probDensity.organCode==5){ organName="Bowel"; } setLine(fbox,'_fp4_',organName,"16px"); setLine(fbox,'_fp_',val.toPrecision(3),"25px"); setLine(fbox,'_fp1_',"SUV("+probDensity.percentile+"%)","16px"); setLine(fbox,'_fp2_',fzx.toPrecision(3),"25px"); setLine(fbox,'_fp3_',"z-value","16px"); } function generateReviewSection2(pid){ let listName=config.loadFileConfig.listName; let id=config.loadFileConfig.id; print('generateReviewSection2: '+pid.participantCode+'/'+ pid.visitCode); if (pid.participantCode=="NONE" || pid.visitCode=="NONE"){ generateErrorMessage(id,listName, "ParticipantId/visitId not set"); return; } print('JSON: '+config.loadFileConfig.json); let json=config.loadFileConfig.json; let nrows=json.rows.values.length; let ncol=json.columns.length; pid.basePath=getBasePath()+"/@files"; let el=config.document.getElementById(id); let tableId=id+'_Table'; let table=config.document.getElementById(tableId); if (table==null){ table=config.document.createElement('table'); table.id=tableId; el.appendChild(table); } table.style.tableLayout="fixed"; table.style.columnWidth="300px"; for (let i=0;i<nrows;i++){ pid[json.rows.variable]=json.rows.values[i]; //let organ=organs[i]; let row=null; if (i<table.rows.length) row=table.rows[i]; else row=table.insertRow(); let ic=0; for (let j=0;j<ncol;j++){ let obj=json.columns[j]; let nv=obj.values.length; for (let k=0;k<nv;k++){ let cell=null; if (ic<row.cells.length) cell=row.cells[ic]; else cell=row.insertCell(); if (obj.display=="image") plotImage(cell,k,row,json.rows.variable,obj,pid); if (obj.display=="report") showReport(cell,k,row,json.rows.variable,obj,pid); if (obj.display=="probability"){ showProbability(cell,k,row,json.rows,i,obj,pid); } ic++; } } } } ///>>>>>>>>>>>>>>end of reviewSection(REPORT) function generateReview(divReviewId,divReviewListId, listName, accessMode){ let listId=config.formConfig.fields[listName].queryId; //listId is a number->should it be queryName? let debug=true; if (debug) print("Generate review for: "+listId+'/'+listName); let reviewSetup=new Object(); reviewSetup.readonlyFlag=function(vName){ if (vName=="queryName") return true; if (vName=="queryname") return true; if (vName=="ModifiedBy") return true; return false;}; reviewSetup.addApply="Add Review"; let generateTableFlag=true; let formStatus=config.formConfig.formStatus; //COMMENTS allowed or not //three levels of access: EDIT, COMMENT, READ if (accessMode == "READ"){ //if (formStatus == "Approved" ){ delete reviewSetup.addApply; reviewSetup.readonlyFlag=function(vName){return false;} generateTableFlag=false; } reviewSetup.filters=new Object(); reviewSetup.filters["crfRef"]=getCRFref(); reviewSetup.filters["queryName"]=listId;//entry in reviewComments list is queryname, all in small caps //needs listName, in argument reviewSetup.getInputId=function(vName){return listName+"_add"+vName}; reviewSetup.divReviewListId=divReviewListId; reviewSetup.isReview=true; if (debug) { let msg="Review: divId: "+divReviewId; msg+=" inputId: "+reviewSetup.getInputId; print(msg); } updateListDisplay(divReviewListId,"reviewComments",reviewSetup.filters,true); if (! generateTableFlag) return; generateTable("reviewComments",divReviewId,new Object(),reviewSetup); } //>>>>>>>>>>trigger visibility of additional lists function setListVisibility(input,setup,readonlyFlag){ let debug=true; let fName="[setListVisibility/"+setup.queryName+"]"; print(fName); let additionalData=config.formConfig.additionalData[setup.queryName]; let x = config.document.getElementById(additionalData.divName); if (debug) print(fName+": Div: "+x); x.style.display="none"; let sText; if (readonlyFlag) sText=input.innerText; else sText=input.options[input.selectedIndex].text; if (debug) print(fName+": Selected option text: "+sText); if (sText == additionalData.showFlagValue){ let filters=new Object(); if ("filters" in additionalData) filters=additionalData.filters; x.style.display = "block"; updateListDisplay(additionalData.divQueryName, additionalData.queryName,filters,readonlyFlag); } } //>>have list refresh when data is added (not optimal yet) function updateListDisplay(divName,queryName,filters,readonlyFlag){ //use Labkey.QueryWebPart to show list let debug=true; let fName="[updateListDisplay]"; if (debug) print(fName+": UpdateListDisplay: Query - "+queryName +" div - "+divName); if (divName=="NONE") return; let crfRef=getCRFref(); let div=config.document.getElementById(divName); if (debug) print(fName+": generating WebPart: "+queryName); var qconfig=new Object(); qconfig.renderTo=divName; //point to data container qconfig.containerPath=getContainer('data'); qconfig.schemaName='lists'; qconfig.queryName=queryName; qconfig.buttonBarPosition='top'; qconfig.filters=[]; for (f in filters){ qconfig.filters.push(LABKEY.Filter.create(f, filters[f])); } qconfig.success=updateSuccess; qconfig.failure=updateFailure; //show only print button if (readonlyFlag){ qconfig.buttonBar=new Object(); qconfig.buttonBar.items=["print"]; } LABKEY.QueryWebPart(qconfig); } function updateSuccess(data){ print("Update success"); } function updateFailure(data){ print("Update failed"); } //TODO: this should trigger a data refresh on section, ie populateData(field) function toggleVisibility(divName,buttonName){ let fName='[toggleVisibility/'+divName+']'; print(fName); let x = config.document.getElementById(divName); if (x.style.display === "none") { //exclude non data sections (like debug)... print(fName+': issuing setData(populateSection)'); x.style.display = "block"; config.document.getElementById(buttonName).value="Hide"; let cb=function(){populateSection(divName);}; setData(cb); } else { x.style.display = "none"; config.document.getElementById(buttonName).value="Show"; } } function generateButtonBU(divName,title,buttonName,callback, callbackParameters){ let debug=true; if (debug) print("generateButtonBU"); let tb=config.document.createElement('table'); tb.className="t2"; let r1=tb.insertRow(); th=config.document.createElement('th'); r1.appendChild(th); th.innerHTML=title; //*!* let c2=r1.insertCell(); let i1=config.document.createElement("input"); i1.type="button"; i1.value=buttonName; i1.style.fontSize="20px"; i1.onclick=function(){callback(callbackParameters);} c2.appendChild(i1); let c1=r1.insertCell(); c1.setAttribute("colspan","1"); c1.id=callbackParameters.submitReportId; let el=config.document.getElementById(divName); if (debug) print("generateButton: element["+divName+"]: "+el); el.appendChild(tb); } function generateButton(divName,caption,label,callbackLabel,callback){ let debug=true; if (debug) print("generateButtonX"); let tb=config.document.createElement('table'); tb.className="t2"; let r1=tb.insertRow(); th=config.document.createElement('th'); r1.appendChild(th); th.innerHTML=caption; //*!* let c2=r1.insertCell(); let i1=config.document.createElement("input"); i1.type="button"; i1.value=label; i1.style.fontSize="20px"; i1.onclick=callback; i1.id='button_'+callbackLabel; c2.appendChild(i1); let c1=r1.insertCell(); c1.setAttribute("colspan","1"); //this is only for saveReview? c1.id=divName+'_reportField'; //c1.id=config.submitReportId; let el=config.document.getElementById(divName); if (debug) print("generateButton: element["+divName+"]: "+el); el.appendChild(tb); } function generateSubQuery(input, setup, readonlyFlag){ let fName="[generateSubQuery]"; if (setup.isReview) return; if (!(setup.queryName in config.formConfig.additionalData)){ print(fName+': no additionalData entry (probably a subquery)'); return; } let additionalData=config.formConfig.additionalData[setup.queryName]; if (!("showFlag" in additionalData)) return; print(fName); let expId=setup.getInputId(additionalData.showFlag); if (expId!=input.id) { print(fName+": ignoring field "+input.id+"/"+expId); return; } print(fName+": Setting onChange to "+input.id); if (!readonlyFlag) input.onchange=function(){setListVisibility(input,setup,readonlyFlag)}; } //>>populate fields // // //split to field generation and field population // function addFieldRow(tb,field,setup,additionalData){ let fName="[addFieldRow/"+setup.queryName+':'+field.name+']'; let vName=field.name; let vType=field.type; let isLookup=("lookup" in field); print(fName+": ["+vName+"/"+vType+'/'+isLookup+"]"); let row=tb.insertRow(); let cell=config.document.createElement('th'); cell.style.width='300px'; row.appendChild(cell); let text = config.document.createTextNode(field.shortCaption); cell.appendChild(text); let input=null; let colSpan="3"; let cell1=row.insertCell(); cell1.colSpan=colSpan; let readonlyFlag=setup.readonlyFlag(vName); //set the html input object while (1){ if (readonlyFlag){ input=config.document.createElement('label'); input.innerText='Loading'; break; } //lookup if (isLookup){ input = config.document.createElement("select"); break; } //date if (vType=="date"){ input = config.document.createElement("input"); input.type="date"; break; } //string if (vType=="string"){ //we have to make sure UNDEF is carried to below //since we are adapting file to either show //current file or allow user to select a file // //TODO change this so one can always select file //but also show the selected file if(vName.search("reviewComment")>-1){ input = config.document.createElement("textarea"); input.cols="65"; input.rows="5"; break; } input=config.document.createElement('input'); input.type="text"; if (vName.search('_file_')<0) break; cell1.setAttribute('colspan',"1"); let cell2=row.insertCell(); cell2.setAttribute('colspan',"2"); let input1=config.document.createElement('input'); input1.type="file"; input1.id=setup.getInputId(vName)+'_file_'; cell2.appendChild(input1); break; } if (vType=="float"){ input = config.document.createElement("input"); input.type="text"; break; } if (vType=="boolean"){ input = config.document.createElement("input"); input.type="checkbox"; print("Creating checkbox"); break; } break; } input.id=setup.getInputId(vName); cell1.appendChild(input); print(fName+': adding element '+input.id); print(fName+': listing element '+config.document.getElementById(input.id)); //connect associated list generateSubQuery(input,setup,readonlyFlag); if (readonlyFlag) { print(fName+': exiting(readonlyFlag)'); return; } if (!isLookup) { print(fName+': exiting (not lookup)'); return; } let lookup=field["lookup"]; //get all values from config.formConfig.lookup[X] let lObject=config.formConfig.lookup[lookup.queryName]; //debug print(fName+": query: "+lookup.queryName); print(fName+": ElementId: "+input.id); print(fName+": No of options: " +lObject.LUT.length); print(fName+": Element: "+input); //set the lut value (input is text label) for readonly //clear existing fields from input for(let i = input.options.length; i >= 0; i--) { input.remove(i); } //create option -1 let opt = config.document.createElement("option"); opt.text = "<Select>"; opt.value = -1; input.options[0] = opt; print(fName+": Adding <Select>"); //add other, label them with LUT for (let v in lObject.LUT) { print(fName+': populating '+v+': '+lObject.LUT[v]); let opt = config.document.createElement("option"); opt.text = lObject.LUT[v]; opt.value = v; input.options[input.options.length] = opt; } input.selectedIndex=0; } function parseVariables(pars){ let pA=pars.split(";"); let q=new Object(); for (let i=0;i<pA.length;i++){ let vA=pA[i].split('='); q[vA[0]]=vA[1]; } return q; } function addSpecialFieldRow(tb,specFieldSetup,setup){ //tb is the table, specFieldSetup is a row from the table where special fields are being setup //the first column is fieldUID, which is a colon joined amalgation of queryName:fieldName let fieldUID=specFieldSetup["fieldUID"]; let x=fieldUID.split(':'); let fieldName=x[1]; let fName="[addSpecialFieldRow/"+fieldUID+"]"; let q=parseVariables(specFieldSetup['actionParameters']); print(fName); if (specFieldSetup['actionType']=='textArea'){ let row=tb.insertRow(); let cell1=row.insertCell(); cell1.colSpan="4"; cell1.style.textAlign="justify"; cell1.style.padding="10px"; cell1.style.backgroundColor="#e0e0e0"; cell1.innerText=q['description']; return; } if (specFieldSetup['actionType']=='generationObject'){ //only in EDIT mode!! let ro=setup.readonlyFlag(fieldName); if (ro) return; config.print=print; let gc=getGenerationObject(config,q,setup.getInputId(fieldName)); let row=tb.insertRow(); let cell=config.document.createElement('th'); row.appendChild(cell); let text = config.document.createTextNode("Automatic ID generator"); cell.appendChild(text); let cell1=row.insertCell(); cell1.colSpan="3"; let b=config.document.createElement("input"); b.type="button"; b.id="generateIdButton"; b.onclick=function(){gc.execute();}; b.value="Generate ID"; cell1.appendChild(b); } } function populateFieldRow(entry,field,setup){ populateField(entry,field,setup); populateSubQuery(entry,field,setup); } function populateSubQuery(entry,field,setup){ let fName='[populateSubQuery/'+setup.queryName+':'+field.name+']'; if (setup.isReview) return; if (!(setup.queryName in config.formConfig.additionalData)){ let msg=fName+': no additionalData entry for '+setup.queryName; msg+=' (probably a subquery)'; print(msg); return; } //find if field is connected to a sub array //find queryName // let additionalData=config.formConfig.additionalData[setup.queryName]; print(fName); //let flag=additionalData.showFlag; if (!("showFlag" in additionalData)) return; let eId=setup.getInputId(additionalData.showFlag); let id=setup.getInputId(field.name); if (eId!=id) { print(fName+": ignoring field "+id+"/"+eId); return; } print(fName+': id '+id); //hard to estimate readonlyFlag // let input=config.document.getElementById(id); let eType=input.nodeName.toLowerCase(); let readonlyFlag=eType!="select"; setListVisibility(input,setup,readonlyFlag); } function populateField(entry,field,setup){ let vName=field.name; let fName='[populateFieldName/'+vName+']'; let varValue="UNDEF"; //if (vName in setup.filters) varValue=setup.filters[vName]; if (vName in entry) varValue=entry[vName]; //if part of the filter, set it to value if (vName in setup.filters) varValue=setup.filters[vName]; let isLookup=("lookup" in field); print(fName+' v='+varValue+'/'+isLookup+' ['+ setup.getInputId(field.name)+']'); let vType=field.type; let id=setup.getInputId(vName); let input=config.document.getElementById(id); //date if (vType=="date"){ if (varValue=="UNDEF") varValue=new Date(); else varValue=new Date(varValue); } //lookup for readonly if (isLookup && varValue!="UNDEF"){ let lookup=field["lookup"]; //get all values from config.formConfig.lookup[X] let lObject=config.formConfig.lookup[lookup.queryName]; varValue=lObject.LUT[varValue]; } print('Element: '+input); //figure out the element type let eType=input.nodeName.toLowerCase(); print('Element type: '+eType); //change varValue for printing if (varValue=="UNDEF") varValue=""; //HTMLTextArea, createElement(textArea) if (eType==="textarea"){ input.value=varValue; return; } //Text, createTextNode if (eType==="#text"){ input.nodeValue=varValue; return; } //HTMLLabelElement, createElement('label') if (eType==="label"){ input.innerText=varValue; return; } //HTMLSelectElement, createElement('select') if (eType==="select"){ input.selectedIndex=0; for (let i=0;i<input.options.length;i++){ let v=input.options[i].text; if (v!=varValue) continue; input.selectedIndex=i; break; } return; } if (eType!="input"){ print('Unknown type: '+eType+' encountered, igonring'); return; } //HTMLInputElement let type=input.type; if (type=="date"){ input.valueAsDate=varValue; return; } //string,float if (type=="text"){ input.value=varValue; return; } //boolean if (type=="checkbox"){ input.checked=varValue; return; } print('Unknown input type: '+type+'. Ignoring.'); } function populateTable(listName,writeMode){ //function populateTable(formSetupEntry){ //let listName=config.formConfig.queryMap[formSetupEntry['queryName']]; //let accessMode=config.formConfig.operator+'Mode'; //let writeMode=formSetupEntry[accessMode]=='EDIT'; let fName='[populateTable/'+listName+']'; let setup=getSetup(listName,writeMode); let entry=new Object(); //data snapshot let fQuery=config.formConfig.dataQueries[listName]; //here I assume that listName was parsed during setDataLayout and setData //so that rows was set (even if they are empty) print(fName+"]: nrows "+fQuery.rows.length); if (fQuery.rows.length>0) entry=fQuery.rows[0]; let fields=fQuery.fields; for (f in fields){ let field=fields[f]; //each field is a new row print(fName+": Adding field: "+f+'/'+field.name+' hidden: '+field.hidden); if (field.hidden) continue; if (field.name=="crfRef") continue; populateFieldRow(entry,field,setup); } } function generateTable(listName,divName,additionalData,setup){ let debug=true; let fName="[generateTable/"+listName+"]"; if (debug) print(fName); //is listName and setup.queryName a duplicate of the same value print(fName+': setup.queryName '+setup.queryName); //assume data is set in config.formConfig.dataQueries[data.queryName].rows; let entry=new Object(); //data snapshot let fQuery=config.formConfig.dataQueries[listName]; //here I assume that listName was parsed during setDataLayout and setData //so that rows was set (even if they are empty) print(fName+": Nrows "+fQuery.rows.length); if (fQuery.rows.length>0) entry=fQuery.rows[0]; let tb=config.document.createElement('table'); tb.className="t2"; config.document.getElementById(divName).appendChild(tb); //this are the fields (probably constant) let fields=fQuery.fields; for (f in fields){ let field=fields[f]; let fieldUID=listName+":"+field.name; //each field is a new row print(fName+": Adding field: "+f+'/'+field.name+' ('+fieldUID+').'); //unique name if (field.hidden) continue; if (field.name=="crfRef") continue; addFieldRow(tb,field,setup,additionalData); populateFieldRow(entry,field,setup); if (fieldUID in config.formConfig["specialFields"]){ let specFieldSetup=config.formConfig["specialFields"][fieldUID]; addSpecialFieldRow(tb,specFieldSetup,setup); } } //finish of if apply button is not required if (!("addApply" in setup)) { print(fName+"populateTable: done"); return; } let row=tb.insertRow(); let th=config.document.createElement('th'); row.appendChild(th); th.innerHTML=setup.addApply; let cell=row.insertCell(); //cell.setAttribute("colspan","2"); let input=config.document.createElement("input"); input.type="button"; input.value=setup.addApply; cell.appendChild(input); let cell1=row.insertCell(); cell1.setAttribute("colspan","2"); cell1.id=setup.getInputId("rerviewLastSave"); cell1.innerHTML="No recent update"; //saveReview is a generic name for saving content of the html page to a list entry input.onclick=function(){saveReview(listName,cell1.id,setup)}; } function setEntryFromElement(entry,elementId, field){ //set value to entry from element using representation (field) from labkey // // let fName='setEntryFromElement'; let el=config.document.getElementById(elementId); if (!el) { print(fName+" element: "+elementId+" not found"); return; } print(fName+" element: "+elementId); let vName=field.name; let vType=field.type; let eType=el.nodeName.toLowerCase(); if (eType==="select"){ entry[vName]=el.options[el.selectedIndex].value; return; } if (eType==="td"){ entry[vName]=el.innerText; return; } if (vType=="date"){ let date=el.valueAsDate; if (date==="null") return; date.setUTCHours(12); entry[vName]=date.toString(); print(fName+" setting date to "+entry[vName]); return; } if (vType=="string"){ entry[vName]=el.value; if (vName.search('_file_')<0) return; //upload file let id1=elementId+'_file_'; let input1=config.document.getElementById(id1); print(fName+' attachment field: '+input1.value); //entry[vName]=el.files[0].stream(); let ctx=new Object(); ctx['dirName']='consent'; ctx['ID']=entry['crfRef']; //should point to data container ctx['project']=getContainer('data'); //need ID->crf! //assume crfRef will get set before this //element is encountered uploadFile(input1,ctx); let fv=el.value; let suf=fv.split('.').pop(); entry[vName]=entry['crfRef']+'.'+suf; return; } if (vType=="float" || vType=="int"){ entry[vName]=el.value; if (vName=="queryName") { print(fName+' parsing queryName: '+el.innerText); entry[vName]=config.formConfig.fields[el.innerText].queryId; //use queryMap lookup } return; } if (vType=="boolean"){ entry[vName]=el.checked; return; } return; } function saveReview(queryName,elementId,setup){ //loads any queryName let debug=true; if (debug) print("saveReview: elementId "+elementId+" queryName "+queryName); let useInsert=false; let unique=("unique" in setup); if (!unique) useInsert=true; let fQuery=config.formConfig.dataQueries[queryName]; if (fQuery.rows.length==0) useInsert=true; let entry=new Object(); if (!useInsert){ entry=fQuery.rows[0]; } entry.crfRef=getCRFrefData(); if (debug) print("Set crfRef="+entry.crfRef); //if ("queryName" in setup.filters) { // entry.queryName=setup.filters["queryName"]; // if (debug) print("Setting queryName: "+entry.queryName); //} let fields=fQuery.fields; for (f in fields){ let field=fields[f]; if (debug) print("saveReview field: "+field.name); if (field.hidden) continue; let vName=field.name; let vType=field.type; if (debug) print("vType: "+vType); if (vName=="crfRef") continue; //need to save queryName for reviewComments let eId=setup.getInputId(vName); //copy values from form to entry setEntryFromElement(entry,eId,field); if (!unique){ let foo=Object(); //reset the field value populateField(foo,field,setup); } } let qconfig=new Object(); qconfig.rows=[entry]; //should point to data container qconfig.containerPath=getContainer('data'); qconfig.schemaName='lists'; qconfig.queryName=queryName; //only update comments print("modifyRows: useInsert "+useInsert); qconfig.success=function(data){updateLastSavedFlag(data,setup,elementId)}; if (!useInsert){ LABKEY.Query.updateRows(qconfig); } else{ LABKEY.Query.insertRows(qconfig); } } function updateLastSavedFlag(data,setup,elementId){ let debug=true; if (debug) print("Update last saved flag to "+elementId); let el=config.document.getElementById(elementId); let dt=new Date(); el.innerHTML="Last saved "+dt.toString(); if (data.queryName=="reviewComments"){ updateListDisplay(setup.divReviewListId,"reviewComments",setup.filters,true); } //refresh stored data! let writeMode=!setup.readonlyFlag(); if ("unique" in setup) setData(function (){populateTable(data.queryName,writeMode);}); if ("masterQuery" in setup){ let ad=config.formConfig.additionalData[setup.masterQuery]; print('Updating list display: '+setup.queryName+'/'+ad.queryName); updateListDisplay(ad.divQueryName,ad.queryName,ad.filters,false); } } //******************************************upload to database ********************* function onDatabaseUpload(){ let fName='[onDatabaseUpload]'; print(fName); config.upload=new Object(); let fc=new Object(); let pM=getParticipantManagerObject(config); fc.participantId=pM.getParticipantIdFromCrfEntry(); print(fName+' id '+fc.participantId); afterParticipantId(fc); } function onDatabaseUpload1(){ let fName='[onDatabaseUpload]'; print(fName); config.upload=new Object(); //figure out the participantId let masterQueryId=config.formConfig.form["masterQuery"]; print(fName+': master query: '+masterQueryId); let pidClass=new Object(); pidClass.afterId=afterParticipantId; //use stored name of participantField //for migrating data from lists to study //this is to avoid conflicts in column assignments //since datasets contain all fields of a list plus //default fields of which participantId is one pidClass.participantField=config.registrationParticipantIdField; let qconfig=new Object(); qconfig.queryName=config.formConfig.queryMap[masterQueryId]; //queryMap holds mapping for queries in visit; //masterQuery should be one of them, so this is safe. qconfig.schemaName='lists'; qconfig.containerPath=getContainer('data'); qconfig.filterArray=[LABKEY.Filter.create('crfRef',getCRFref())]; qconfig.success=function(data){afterRegistration(data,pidClass);}; LABKEY.Query.selectRows(qconfig); //waitForCompleteUpload(config);// } function afterRegistration(data,fc){ let fName='[afterRegistration/'+data.queryName+']'; print(fName+": rows:"+data.rows.length); fc.registration=data; let registrationData=fc.registration; clearErr(); if (registrationData.rows.length!=1){ let msg=fName+": ERROR: Found "+registrationData.rows.length; msg+=" registration entries for crfrefid "+getCRFref(); print(msg); fc.afterId(fc); return; } print(fName+'registration participant field: '+fc.participantField); fc.participantId=registrationData.rows[0][fc.participantField]; //could be a lookup field (particularly for studies) print('ID: '+fc.participantId); let fields=registrationData.metaData.fields; let field="NONE"; for (f in fields){ if (fields[f]["name"]==fc.participantField) field=fields[f]; } if ("lookup" in field){ let pid=fc.participantId; print("Using lookup for participantId: "+pid); let lookup=field["lookup"]; print("Lookup: ["+lookup.schemaName+','+lookup.queryName+']'); let qconfig=new Object(); //should point to data container qconfig.containerPath=getContainer('data'); qconfig.schemaName=lookup.schemaName; qconfig.queryName=lookup.queryName; qconfig.filterArray= [LABKEY.Filter.create(lookup.keyColumn,pid)]; qconfig.success=function(data){ afterRegistrationLookup(data,lookup.displayColumn,fc)}; LABKEY.Query.selectRows(qconfig); } else{ //afterParticipantId(configUpload); fc.afterId(fc); } } function afterRegistrationLookup(data,displayColumn,fc){ print("afterRegistrationLookup"); let entry=data.rows[0]; fc.participantId=entry[displayColumn]; print('Setting to '+fc.participantId); fc.afterId(fc); //afterParticipantId(configUpload); } function afterParticipantId(fc){ print("Setting participantId to "+fc.participantId); config.upload.participantId=fc.participantId; //another select rows to update all queries from setup //just use registration for test let formSetupRows=config.formConfig.formSetupRows; config.upload.queries=new Array(); print("Form rows: "+formSetupRows.length); for (let i=0;i<formSetupRows.length;i++){ let entry=formSetupRows[i]; //skip reviews if (entry.showFlag=="REVIEW") continue; //use lookup table to convert from id to name let queryName=config.formConfig.queryMap[entry.queryName]; config.upload.queries.push({queryName:queryName,queryStatus:"QUEUED"}); print('form ['+i+']='+queryName+' '+entry.showFlag+'/'+entry.showQuery); if (entry.showQuery=="NONE") continue; config.upload.queries.push({queryName:entry.showQuery,queryStatus:"QUEUED"}); } //add reviews config.upload.queries.push({queryName:"reviewComments",queryStatus:"QUEUED"}); config.upload.queryId=0; copyToDataset(); } function copyToDataset(){ let fName='[copyToDataset]: '; print(fName+'['+config.upload.queryId+'/'+config.upload.queries.length+']'); //watch dog + scheduler // //watchdog part if (config.upload.queryId==config.upload.queries.length) { print(fName+'completing'); let targetStatus=config.formConfig.targetStatus['onDatabaseUpload']; let targetRecipient=config.formConfig.targetRecipient['onDatabaseUpload']; let action=new Object(); action.name='onDatabaseUpload'; action.cb=function(data){sendEmail(data,targetRecipient,redirect,'Form uploaded');} updateFlag(targetStatus,action);//Approved return; } //scheduler let queryName=config.upload.queries[config.upload.queryId].queryName; print("copyToDataset["+config.upload.queryId+"/"+ config.upload.queries.length+"]: "+queryName); let qconfig=new Object(); qconfig.queryName=queryName; qconfig.schemaName="lists"; qconfig.containerPath=getContainer('data'); qconfig.filterArray=[LABKEY.Filter.create('crfRef',getCRFref())]; qconfig.success=afterListData; LABKEY.Query.selectRows(qconfig); } function afterListData(data){ let fName='[afterListData]: '; let queryName=config.upload.queries[config.upload.queryId].queryName; print(fName+" ["+queryName+"/list]: "+data.rows.length+" entries"); config.upload.queries[config.upload.queryId].listData=data; let id=config.upload.participantId; let qconfig=new Object(); qconfig.queryName=queryName; qconfig.schemaName="study"; qconfig.containerPath=getContainer('data'); qconfig.filterArray=[LABKEY.Filter.create('crfRef',getCRFref())]; qconfig.filterArray.push(LABKEY.Filter.create('ParticipantId',id)); qconfig.success=afterStudyData; LABKEY.Query.selectRows(qconfig); } function afterStudyData(data){ let fName='[afterStudyData]: '; let queryObj=config.upload.queries[config.upload.queryId]; queryObj.studyData=data; let msg=fName+"["+queryObj.queryName+"/study]: "+data.rows.length+" entries"; print(msg); let listRows=queryObj.listData.rows; //skip uploading an empty set if (listRows.length==0){ printErr("List "+queryObj.queryName+" empty."); queryObj.queryStatus="DONE"; config.upload.queryId+=1; //back to watchdog copyToDataset(); return; } let studyRows=queryObj.studyData.rows; for (let i=0;i<studyRows.length;i++){ let entry=studyRows[i]; // if (! (i<listRows.length) ) continue; let entryList=listRows[i]; //keeps study only variables (ParticipantId, SequenceNum) for (let f in entryList) { entry[f]=entryList[f]; print(fName+"Copying ["+f+"]: "+entry[f]+"/"+entryList[f]); } } print(fName+' copying completed'); if (studyRows.length>0) { let qconfig=new Object(); qconfig.queryName=queryObj.queryName; qconfig.schemaName="study"; qconfig.rows=studyRows; qconfig.containerPath=getContainer('data'); qconfig.success=afterStudyUpload; LABKEY.Query.updateRows(qconfig); print(fName+'updateRows sent'); } else{ let data=new Object(); data.rows=new Array(); afterStudyUpload(data); } } function afterStudyUpload(data){ let fName='[afterStudyUpload] '; print(fName); //let participantField=config.participantField; let participantField=config.formConfig.studyData["SubjectColumnName"]; print(fName+' participantField: '+participantField); let queryObj=config.upload.queries[config.upload.queryId]; let queryName=queryObj.queryName; printErr("Updated "+data.rows.length+" rows to "+queryName); let studyRows=queryObj.studyData.rows; let listRows=queryObj.listData.rows; let rows=new Array(); //also updating existing rows, if they exist for (let i=studyRows.length;i<listRows.length;i++){ let entry=listRows[i]; //make sure you have the participantField right // entry[participantField]=config.upload.participantId; entry.crfRef=getCRFref(); entry.SequenceNum=getCRFref(); entry.SequenceNum=entry.SequenceNum % 1000000000; if (listRows.length>1){ entry.SequenceNum+=i/100; } print( "Adding sequence number "+entry.SequenceNum); rows.push(entry); } if (rows.length>0){ let qconfig=new Object(); qconfig.queryName=queryName; qconfig.schemaName="study"; qconfig.containerPath=getContainer('data'); qconfig.success=afterListUpload; qconfig.rows=rows; LABKEY.Query.insertRows(qconfig); } else{ let data=new Object(); data.rows=rows; afterListUpload(data); } } function afterListUpload(data){ let queryObj=config.upload.queries[config.upload.queryId]; let queryName=queryObj.queryName; printErr("Inserted "+data.rows.length+" rows to "+queryName); queryObj.queryStatus="DONE"; config.upload.queryId+=1; copyToDataset(); } //*************************update for further review ************************* function onUpdateForReview(){ let targetStatus=config.formConfig.targetStatus['onUpdateForReview']; let targetRecipient=config.formConfig.targetRecipient['onUpdateForReview']; let action=new Object(); action.name='onUpdateForReview'; action.cb=function(data){sendEmail(data,targetRecipient,redirect,'Form updated for review');}; updateFlag(targetStatus,action); } function updateFlag(flag,action){ let fName='[updateFlag 1]'; let debug=true; let entry=config.formConfig.crfEntry; entry.FormStatus=flag; let uId=config.formConfig.currentUser.UserId; entry[config.formConfig.operator]=uId; print(fName+': Form: '+entry.Form); print(fName+": set form status to "+entry.FormStatus); let qconfig=new Object(); qconfig.schemaName='lists'; qconfig.queryName='crfEntry'; qconfig.containerPath=getContainer('data'); qconfig.rows=[entry]; //qconfig.success=function(data){completeWithFlag(data,flag);} qconfig.success=function(data){completeWithFlag(data,action);}; LABKEY.Query.updateRows(qconfig); } function completeWithFlag(data,action){ let fName='[completeWithFlag]'; print(fName+': nrows '+data.rows.length); let fentry=data.rows[0]; print(fName+': form status '+fentry.FormStatus); print(fName+': form '+fentry.Form); let entry=new Object(); entry.entryId=getCRFref(); entry.submissionDate=new Date(); entry.FormStatus=fentry.FormStatus; entry.User=config.formConfig.currentUser.UserId; entry.Form=fentry.Form; entry.operator=config.formConfig.operator; entry.action=action.name; let qconfig=new Object(); qconfig.schemaName='lists'; qconfig.queryName='crfStatus'; qconfig.containerPath=getContainer('data'); qconfig.rows=[entry]; //qconfig.success=function(data){completeWithFlag(data,flag);} qconfig.success=action.cb; LABKEY.Query.insertRows(qconfig); } //************************************************ submit ******************************************* function onSubmit(){ //update list storage and change status let debug=true; hideErr(); clearErr(); printErr("onSubmit"); setData(verifyData); } function verifyData(){ let queries=config.formConfig.dataQueries; for (q in queries){ let qData=queries[q]; if (q=="reviewComments") continue; //if it doesn't have additionalData, it is a sub query if (!(q in config.formConfig.additionalData)) continue; if (qData.rows.length<1){ printErr('Missing entry for query '+q); return false; } } let targetStatus=config.formConfig.targetStatus['onSubmit']; let targetRecipient=config.formConfig.targetRecipient['onSubmit']; print('verifyStatus: targetStatus: '+targetStatus); let fName='verifyStatus'; //useful for debug //let finalStep=doNothing; //production mode let finalStep=redirect; let action=new Object(); action.name='onSubmit'; action.cb=function(data){sendEmail(data,targetRecipient,finalStep,'Form sumbitted');}; updateFlag(targetStatus,action); } function getEmail(recipientCode){ print('getEmail w/'+recipientCode); let recipients=new Array(); let typeTo=LABKEY.Message.recipientType.to; let create=LABKEY.Message.createRecipient; let currentUser=config.formConfig.currentUser; let formCreator=config.formConfig.formCreator; let currentSite=config.formConfig.currentSite; let userRows=config.formConfig.userRows; let parentUser=undefined; if ("parentCrfData" in config.formConfig){ let parentCrf=config.formConfig.parentCrfData; parentUser=getUser(parentCrf.rows[0].UserId,'parentUser'); } let recipientCategories=recipientCode.split(','); for (let i=0;i<recipientCategories.length;i++){ let recipient=recipientCategories[i]; print('Checking '+recipient); if (recipient=='crfEditor'){ print('Adding :'+formCreator.Email); recipients.push(create(typeTo,formCreator.Email)); if (parentUser==undefined) continue; print('Adding :'+parentUser.Email); recipients.push(create(typeTo,parentUser.Email)); continue; } //Monitor or Sponsor let fList=recipient+'s'; let fRows=config.formConfig[fList]; for (let i=0;i<fRows.length;i++){ print('Checking '+fRows[i].User+'/'+fRows[i].Site); if (fRows[i].Site!=currentSite.siteNumber) continue; for (let j=0;j<userRows.length;j++){ if (userRows[j].UserId!=fRows[i].User) continue; print('Adding :'+userRows[j].Email); recipients.push(create(typeTo,userRows[j].Email)); break; } } } return recipients; } function sendEmail(data,recipient='crfEditor',cb=redirect,subj='Form submitted'){ print('sendEmail; recipient: '+recipient); let st=config.formConfig.settings; let cvar='sendEmail'; if (cvar in st){ print(cvar+' set to '+st[cvar]); if (st[cvar]=='FALSE'){ print('Skipping sending emails'); cb(); return; } } if (recipient==null){ print('Skipping sending emails w/ no recipients'); cb(); return; } print('send email '+data.rows.length); let crf=data.rows[0]['entryId']; let formId=data.rows[0]['Form']; let link=LABKEY.ActionURL.getBaseURL(); link+=LABKEY.ActionURL.getContainer(); link+='/crf_tecant-visit.view?'; link+='entryId='+crf; link+='&formId='+formId; link+='&role='+recipient; //debug let recipients=getEmail(recipient); //from crfManagers list let typeHtml=LABKEY.Message.msgType.html; let typePlain=LABKEY.Message.msgType.plain; let msg1=LABKEY.Message.createMsgContent(typePlain,link); //let cb=doNothing; //let cb=redirect; LABKEY.Message.sendMessage({ msgFrom:'labkey@fmf.uni-lj.si', msgSubject:subj, msgRecipients:recipients, msgContent:[msg1], success: cb }); } function hideErr(){ let el=config.document.getElementById("errorDiv"); el.style.display="none"; } function clearErr(){ let el=config.document.getElementById("errorTxt"); el.value=""; } function showErr(){ let el=config.document.getElementById("errorDiv"); el.style.display="block"; } function printErr(msg){ showErr(); el=config.document.getElementById("errorTxt"); el.style.color="red"; el.value+="\n"+msg; } //************************************************** // function onRemoveCRF(){ let debug=true; if (debug){ print("Removing CRF"); } let selectRows=new Object(); //points to data container selectRows.containerPath=getContainer('config'); selectRows.schemaName="lists"; selectRows.queryName="inputLists"; selectRows.success=afterInputLists; LABKEY.Query.selectRows(selectRows); } function afterInputLists(data){ let debug=true; if (debug) print("After input lists"); config.inputLists=data; config.inputListsIterator=0; removeCRFLoop(); } function removeCRFLoop(){ let debug=true; let i=config.inputListsIterator; let iMax=config.inputLists.rows.length; //in fact, we are adding two additional passages of the loop, one for //crfEntry, the second for the same query, but using parentCrf as the //selection variable let iTotal=iMax+1; //let iTotal=iMax+1; if (debug) print("removeCRFLoop ["+i+"/"+iMax+"]"); if (i>iTotal){ if (0) return; redirect(); } let queryName="crfEntry"; let idVar="entryId"; let idValue=getCRFref(); if (i<iMax){ //in all but crfEntry, variable is called crfRef queryName=config.inputLists.rows[i].queryName; idVar="crfRef"; } //for i=iMax and i=iMax+1, query is crfEntry. //delete also crfEntries where parentCrf is set to crf that we are deleting if (i==iTotal){ idVar='parentCrf'; } if (debug) print("["+i+"/"+iMax+"] "+queryName+":"+idVar+'/'+idValue); let selectRows=new Object(); //points to data container selectRows.containerPath=getContainer('data'); selectRows.schemaName="lists"; selectRows.queryName=queryName; selectRows.filterArray=[LABKEY.Filter.create(idVar,idValue)]; selectRows.success=removeListCRF; selectRows.failure=skipListCRF; LABKEY.Query.selectRows(selectRows); } function removeListCRF(data){ let debug=true; if (debug) print(data.queryName+": "+data.rows.length); config.inputListsIterator+=1; if (data.rows.length==0){ removeCRFLoop(); return; } let deleteRows=new Object(); //points to data container deleteRows.containerPath=getContainer('data'); deleteRows.schemaName=data.schemaName; deleteRows.queryName=data.queryName; deleteRows.success=function(data){removeCRFLoop()}; deleteRows.rows=data.rows; LABKEY.Query.deleteRows(deleteRows); } function skipListCRF(errorInfo){ let debug=true; if (debug) print("Error in removeCRF: "+errorInfo.exception); config.inputListsIterator+=1; removeCRFLoop(); } function redirect(){ let debug=false; let formUrl="begin"; let params=new Object(); params.name=formUrl; params.pageId="CRF"; //points to crf container let containerPath=getContainer('CRF'); // This changes the page after building the URL. //Note that the wiki page destination name is set in params. var homeURL = LABKEY.ActionURL.buildURL( "project", formUrl , containerPath, params); print("Redirecting to "+homeURL); if (debug) return; window.location = homeURL; } //printing section function checkBlob(){ print("checkBlob: "+config.blob); if (config.blob) { clearInterval(config.blobInterval); config.a.href = config.window.URL.createObjectURL(config.blob); print("HREF: "+config.a.href); config.a.download = 'test.pdf'; config.a.click(); config.window.URL.revokeObjectURL(config.a.href); } config.count=config.count+1; print("Eval: "+config.count); if (config.count>100){ clearInterval(config.blobInterval); } } function printForm(){ config.doc=new PDFDocument(); //config.doc.end(); let stream = config.doc.pipe(blobStream()).on("finish",function(){ config.blob=stream.toBlob("application/pdf");}); print("BLob: "+config.blob); config.a = config.document.createElement("a"); config.document.body.appendChild(config.a); config.a.innerHTML="Download PDF"; config.a.style = "display: none"; config.count=0; //run until blob is set config.blobInterval=setInterval(checkBlob,1000); //pick data from crfForm list print("Printing form"); printHeader(); setData(formatPrintData); } function printHeader(){ config.doc.fontSize(25).text(config.formConfig.form['formName']); config.doc.moveDown(); let crfEntry=config.formConfig.crfEntry; let site=config.formConfig.currentSite; let val=new Object(); let user=config.formConfig.user; val['A']={o:crfEntry,f:'EudraCTNumber',t:'Eudra CT Number'}; val['B']={o:crfEntry,f:'StudyCoordinator',t:'Study Coordinator'}; val['C']={o:crfEntry,f:'StudySponsor',t:'Study Sponsor'}; val['D']={o:site,f:'siteName',t:'Site'}; val['E']={o:site,f:'sitePhone',t:'Phone'}; val['F']={o:user,f:'DisplayName',t:'Investigator'}; for (let f in val){ print('Printing for '+f); let e=val[f]; let entry=new Object(); entry[f]=e.o[e.f]; printPDF(entry, {name:f,caption:e.t,type:'string'},null); } config.doc.moveDown(); } function formatPrintData(){ qS=config.formConfig.dataQueries; for (let q in qS){ print('Setting up '+q); let qData=qS[q]; print('Number of rows: '+qData.rows.length); if (qData.rows.length>0){ config.doc.fontSize(20).text(qData.title); } for (let i=0;i<qData.rows.length;i++){ let entry=qData.rows[i]; for (let f in qData.fields){ let field=qData.fields[f]; let lookup=null; if (field.lookup){ lookup=config.formConfig.lookup[field.lookup.queryName]; } if (field.hidden) continue; printPDF(entry,field,lookup); } } config.doc.moveDown(); } print("All done"); config.doc.end(); } function printPDF(entry,field,lookup){ //object field should have a name, type, caption //entry should have field.name //lookup is null or has a lookup table LUT //for value v of entry[field.name] // //the total width of a A4 page is 598 px, //left margin is 72. With a right margin of 50, //the total available with is 476 px. let w=476; let spacing=25; let w1=(w-spacing)*0.5; let fontSize=14; print('printPDF: entry['+field.name+']='+entry[field.name]); let v=entry[field.name]; if (lookup!=null){ v=lookup.LUT[v]; } print('printPDF: field type:'+field.type); if (field.type=="date"){ let d=new Date(v); v=d.getDate()+'/'+(d.getMonth()+1)+'/'+d.getFullYear(); } if (v===null) v=' / '; if (v===undefined) v=' / '; //measure text let label=field.caption; let opt={width:w1}; config.doc.fontSize(fontSize); //for more eloquent display the height of the text //can be measured prior to output //use currentLineHeight to scale height //let lineH=config.doc.currentLineHeight(1); //let h=config.doc.heightOfString(label,opt)/lineH; //print label config.doc.font('Courier').text(label,opt); //align last row of description w/ first row of value config.doc.moveUp(); //store x value for later use let tx=config.doc.x; let ty=config.doc.y; //shift for value output config.doc.x+=w1+spacing; config.doc.font('Courier-Bold').text(v,opt); //restore x value config.doc.x=tx; } //master section, entry point from html files function generateMasterForm(){ generateDebugSection(); //read enviroment from lists disperesed on labkey setFormConfig(); } //helper function to set basic parameters on web page //(fields defined in html file) function populateBasicData(){ let staticData=new Object(); let titles=new Object(); staticData['version']=config.formConfig.softwareVersion; titles['version']='Software version'; let varRows=config.formConfig['crfStaticVariables'].rows; for (let i=0;i<varRows.length;i++){ let vName=varRows[i].staticVariable; let val=config.formConfig.crfEntry[vName]; if (val==undefined) continue; staticData[vName]=val; titles[vName]=varRows[i].Title; } staticData['investigatorName']=config.formConfig.user['DisplayName']; titles['investigatorName']='Investigator'; staticData['email']=config.formConfig.user['Email']; titles['email']='Email'; staticData['siteName']=config.formConfig.currentSite['siteName']; titles['siteName']='Site'; staticData['sitePhone']=config.formConfig.currentSite['sitePhone']; titles['sitePhone']='Telephone(site)'; for (f in staticData){ addStaticData(f,titles[f],staticData[f]); } } function addStaticData(f,title,value){ let el=config.document.getElementById(f); //populate only if (el!=undefined){ el.innerText=value; return; } //add row to table if element cannot be found let table=config.document.getElementById('staticTable'); let row=table.insertRow(); let cell=row.insertCell(); cell.innerText=title; let cell1=row.insertCell(); cell1.id=f; cell1.style.fontWeight='bold'; //populate cell1.innerText=value; } //come here after the layout is read from labkey page // function generateErrorMsg(msg){ let txt=config.document.createElement('p'); txt.innerText=msg; config.document.getElementById(config.masterForm).appendChild(txt); generateButton("submitDiv",'Exit','Exit','redirect',redirect); } function getUser(id,field){ if (field in config.formConfig) return config.formConfig[field]; let uRows=config.formConfig.userRows; for (let i=0;i<uRows.length;i++){ let userId=uRows[i].UserId; if (userId!=id) continue; config.formConfig[field]=uRows[i]; return config.formConfig[field]; } return null; } function afterConfig(){ let debug=true; if (debug) print("afterConfig"); populateBasicData(); //check if user has permission on the form let currentUser=getUser(LABKEY.Security.currentUser.id,'currentUser'); let currentSite=config.formConfig.currentSite; let formCreator=getUser(config.formConfig.crfEntry.UserId,'formCreator'); let formCreatorId=formCreator.UserId; //let formSite=config.formConfig.crfEntry.Site; let fList=config.formConfig.operator+'s'; let fRows=config.formConfig[fList]; //let currentSiteId=-1; //depending on operator mode, we should decide what is right let operator=config.formConfig.operator; if (operator=='crfEditor'){ //editor can only edit its own forms if (currentUser.UserId!=formCreatorId){ let msg='User '+currentUser.DisplayName; msg+=' has no permission on this form'; generateErrorMsg(msg); return; } } if (operator=='crfMonitor' || operator=='crfSponsor'){ //monitor can look at forms based on his site //find monitor line let operatorSites=new Array(); for (let i=0;i<fRows.length;i++){ if (fRows[i].User!=currentUser.UserId) continue; operatorSites.push(fRows[i].Site); } print('operator Site: '+operatorSites.length); if (operatorSites.length==0){ let msg='User '+currentUser.DisplayName; msg+=' is not a '+operator; generateErrorMsg(msg); return; } let selectedSite=-1; let siteCandidates="["; for (let i=0;i<operatorSites.length;i++){ if (i>0) siteCandidates+=", "; siteCandidates+=operatorSites[i]; if (operatorSites[i]!=currentSite.siteNumber) continue; selectedSite=currentSite.siteNumber; break; } siteCandidates+="]"; if (selectedSite==-1){ let msg='User '+currentUser.DisplayName; msg+=' is not a '+operator+' for site '; msg+=currentSite.siteName+'('+currentSite.siteNumber+')'; msg+='/'+siteCandidates; generateErrorMsg(msg); return; } } print('User '+currentUser.DisplayName+'/'+ config.formConfig.currentSite['siteName']+ ' acting as '+config.formConfig.operator); let rows=config.formConfig.crfButtons.rows; config.formConfig.targetStatus=new Array(); config.formConfig.targetRecipient=new Array(); for (let i=0; i<rows.length; i++){ let action=rows[i].action;//String let tstatus=rows[i].targetFormStatus; let trecip=rows[i].targetRecipient; config.formConfig.targetStatus[action]=tstatus; config.formConfig.targetRecipient[action]=trecip; } let formStatus=config.formConfig.formStatus; //let functionArray=new Array(); print("Generating buttons for formStatus \""+ formStatus+"\""); let buttonRows=config.formConfig.crfButtons.rows; for (let i=0;i<buttonRows.length;i++){ let bt=buttonRows[i]; if (typeof window[bt.action]==="function"){ generateButton("submitDiv",bt.caption,bt.label,bt.action, window[bt.action]); } else{ print('No match for function :'+bt.action+ ' obj: '+window[bt.action]); } } print('Here'); //here we should get data. For now, just initialize objects that will hold data setDataLayout(afterDataLayout);//callback is afterDataLayout } function afterDataLayout(){ setData(afterData);//callback is afterData } function verifyCrfStudyId(pM){ //is studyId already set for the crf let studyId=pM.getParticipantIdFromCrfEntry('STUDY'); if (!studyId) return; pM.mode="STUDY"; pM.readOnly="TRUE"; } function verifyRegistration(pM){ //if registration is in, //then local id should not be changed any longer let idFieldName=pM.getCrfEntryFieldName("STUDY"); //let registrationQuery=config.formConfig.settings['registrationQuery']; //if (!registrationQuery) return; //LOCAL is OK //let fQuery=config.formConfig.dataQueries[registrationQuery]; //if (!fQuery) return; //no registration query, then it should be ignored let fQuery=config.formConfig.registrationData; if (fQuery.rows.length==0) return; //registration is empty let studyId=fQuery.rows[0][idFieldName]; if (!studyId) return; //study id not set //set pM.mode="STUDY"; pM.readOnly="TRUE"; //set crf (this happens later, but probably before the form will be corrected) pM.setParticipantIdToCrfEntry(studyId,"STUDY"); pM.updateCrfEntry(); } function afterData(){ let fName='afterData'; //operatorBasedAccessMode let accessMode=config.formConfig.operator+'Mode'; let rowsSetup=config.formConfig.formSetupRows; let idMode=config.formConfig.form['idMode']; //set default value if no value is in the list (read value is null) if (!idMode) idMode="STUDY:EDIT"; print(fName+': idMode '+idMode); //add print to config so participantManager can use it config.print=print; let pM=getParticipantManagerObject(config); pM.updateCrfEntry=function(){updateFlag(config.formConfig.crfEntry['FormStatus'],doNothing);}; let idModeArray=idMode.split(':'); pM.mode="STUDY"; if (idModeArray.includes("LOCAL")) { pM.mode="LOCAL"; //OK, but check if CRF or registration indicate that study id is already set verifyCrfStudyId(pM); verifyRegistration(pM); } if (idModeArray.includes("READONLY")){ pM.readOnly="TRUE"; } let pId=pM.getParticipantIdFromCrfEntry(); if (!pId){ pM.setEditMode(); } else{ let label=pId; if (pM.mode=="STUDY"){ let loc=pM.getParticipantIdFromCrfEntry('LOCAL'); label=pId+':'+loc; pM.readOnly="true"; } pM.setLabelMode(label); //in STUDY mode also change LOCAL ID from crfEntry } for (let i=0;i<rowsSetup.length;i++){ let entry=rowsSetup[i]; let queryName=config.formConfig.queryMap[entry['queryName']]; print(fName+" ["+queryName+"]: showFlag: "+entry["showFlag"]); print(fName+" ["+queryName+"]: accessMode: "+entry[accessMode]); const nData=config.formConfig.dataQueries[queryName].rows.length; print(fName+" ["+queryName+"]: nData: "+nData); //skip sections //also from fields if (entry[accessMode]=="NONE") continue; //skip readonly empty records //if (entry[accessMode]=="READ" && nData==0) continue; //let additionalData=new Object(); //setAdditionalData(additionalData,entry); //section fits one dataset/list generateSection(entry); //generateSection(queryName,entry["title"],entry[accessMode], // additionalData); } } function findSetupRow(queryName){ let rowsSetup=config.formConfig.formSetupRows; for (let i=0;i<rowsSetup.length;i++){ let e=rowsSetup[i]; let queryName1=config.formConfig.queryMap[e['queryName']]; if (e.formName!=config.formId) continue; if (queryName1!=queryName) continue; return e; } return null; } function populateSection(queryName){ let fName='[populateSection/'+queryName+']'; print(fName); let entry=findSetupRow(queryName); //ignore names without associated entry in formSetup if (entry==undefined){ print(fName+': no matching FormSetup entry found'); return; } //populate comes after generate, we should be pretty safe in taking //already generated additionalData if (!(queryName in config.formConfig.additionalData)){ print(fName+': no additionalData generated for '+queryName); return; } let additionalData=config.formConfig.additionalData[queryName]; print(fName+': using additionalData '+additionalData); if ("isReview" in additionalData){ generateReviewSection(queryName,queryName,generateReviewSectionCB); return; } let accessMode=config.formConfig.operator+'Mode'; let aM=entry[accessMode]; print(fName+': accessMode '+aM); if (aM!='GENERATE'){ let writeMode=entry[accessMode]=='EDIT'; print(fName+': mode='+writeMode); populateTable(queryName,writeMode); return; } //deal with generate // //already available -> shift to READ mode let divTable=queryName+'Table'; let divObj=config.document.getElementById(divTable); let divRev=config.document.getElementById(queryName+'Review'); let divRLi=config.document.getElementById(queryName+'ReviewList'); let divGBu=config.document.getElementById(queryName+'GenerateButton'); print('div GBU: '+divGBu); divObj.style.display="block"; divRev.style.display="block"; divRLi.style.display="block"; if (divGBu!=undefined) divGBu.style.display="none"; let nData=config.formConfig.dataQueries[queryName].rows.length; print('['+queryName+']: nrows '+nData); if (nData>0){ populateTable(queryName,0); return; } //hide table divObj.style.display="none"; divRev.style.display="none"; divRLi.style.display="none"; if (divGBu!=undefined) divGBu.style.display="block"; //add buttons? //is button already generated? //populateTable(entry); } //******* generateQuery infrastructure ********************* function onGenerateQuery(queryName){ let fName='[onGenerateQuery]'; print(fName+' '+queryName); // let cfgRows=config.formConfig.generateConfigData.rows; // //queryName to queryId? let queryId=config.formConfig.fields[queryName].queryId; let cfgRow=undefined; for (let i=0;i<cfgRows.length;i++){ if (cfgRows[i].queryId!=queryId) continue; cfgRow=cfgRows[i]; break; } if (cfgRow==undefined){ print('generateConfig for queryName['+queryId+']='+queryName+' not found'); return; } //add config to the list if (!("generateConfig" in config.formConfig)){ config.formConfig.generateConfig=new Object(); } config.formConfig.generateConfig[queryName]=cfgRow; if (!("generateForm" in config.formConfig)){ config.formConfig.generateForm=new Object(); } // let formRows=config.formConfig.formRows; let formId=cfgRow.formId; for (let i=0;i<formRows.length;i++){ if (formRows[i].Key==formId) { config.formConfig.generateForm[queryName]=formRows[i]; break; } } //print('XcfgRow '+config.formConfig.generateForm[queryName]); // // //check if all required datasets were at least saved checkGenerationFields(queryName); } function checkGenerationFields(queryName){ let fName='[checkGenerationFields]'; let genForm=config.formConfig.generateForm[queryName]; let genCfg=config.formConfig.generateConfig[queryName]; let mailRecipient=genCfg.emailRecipient; //list of queries that are part of Registration form print(fName); print(fName+' setRecipient: '+mailRecipient); let formId=genForm.Key; print(fName+" Checking form w/id "+formId); let selectGenerationRows=selectFormSetupRows(formId); //registration rows for (let i=0;i<selectGenerationRows.length;i++){ let row=selectGenerationRows[i]; let queryId=row.queryName; let fQueryName=config.formConfig.queryMap[queryId]; if (fQueryName==queryName) continue; let fQuery=config.formConfig.dataQueries[fQueryName]; print('Checking '+fQueryName+' nrows: '+fQuery.rows.length); if (fQuery.rows.length==0){ generateError(queryName,fQueryName); return; } } generateMessage(queryName,'Vailidation OK'); print('callback: set recipient: '+mailRecipient); let cb=function(){prepareForm(queryName,formId,mailRecipient);}; generateListEntry(formId,queryName,cb); } function prepareForm(queryName,formId,mailRecipient){ print('prepareForm: recipient '+mailRecipient); // // return; //look for existing registration entry let selectRows=new Object(); //inputLists should be in configuration container selectRows.containerPath=getContainer('data'); selectRows.schemaName='lists'; selectRows.queryName='crfEntry'; selectRows.success=function(data){generateForm(data,queryName,mailRecipient);}; let formFilter=LABKEY.Filter.create('Form',formId); let parentCrfFilter=LABKEY.Filter.create('parentCrf',getCRFref()); selectRows.filterArray=[formFilter,parentCrfFilter]; LABKEY.Query.selectRows(selectRows); } function generateError(queryName,fQueryName){ let elName=queryName+'GenerateButton'+'_reportField'; let el=config.document.getElementById(elName); el.innerText='Error: '+fQueryName+' was not set'; el.style.color='red'; } function generateMessage(queryName,msg){ let elName=queryName+'GenerateButton'+'_reportField'; let el=config.document.getElementById(elName); el.innerText=msg; el.style.color='green'; } function generateForm(data,queryName,mailRecipient){ print('generateForm, recipient: '+mailRecipient); // const nData=data.rows.length; print('Registration: '+nData+' rows'); let formRow=config.formConfig.generateForm[queryName]; //we have to generate masterQuery with parentCrf and crfRef //and crfEntry with new entryId and parentCrf equal to crfRef if (nData>0) { generateMessage(queryName,'Registration already generated.'); return; } let formId=formRow.Key; let formName=formRow.formName; let crfBase=config.formConfig.crfEntry; let crfEntry=new Object(); //add new reference crfEntry.entryId=Date.now(); crfEntry.parentCrf=getCRFref(); crfEntry["Date"]=new Date(); crfEntry["View"]="[VIEW]"; crfEntry.formStatus=1;//In progress //get local Id let pM=getParticipantManagerObject(config); crfEntry[pM.getCrfEntryFieldName()]=pM.getParticipantIdFromCrfEntry(); // //set other variables //requires studyData as part of formConfig // let studyData=config.formConfig.studyData; print('Adding study: '+crfBase.EudraCTNumber); crfEntry.EudraCTNumber=crfBase.EudraCTNumber; crfEntry.StudyCoordinator=crfBase.StudyCoordinator; crfEntry.StudySponsor=crfBase.StudySponsor; crfEntry.RegulatoryNumber=crfBase.RegulatoryNumber; // // //find sponsor for site let site=crfBase.Site; let crfSponsors=config.formConfig.crfSponsors; let users=config.formConfig.userRows; for (let i=0;i<crfSponsors.length;i++){ //print('Checking for site '+crfSponsors[i].Site); if (crfSponsors[i].Site!=site) continue; config.formConfig.sponsorId=crfSponsors[i].User; //print('Setting id '+config.formConfig.sponsorId); //finds first break; } for (let j=0;j<users.length;j++){ if (config.formConfig.sponsorId!=users[j].UserId) continue; config.formConfig.sponsor=users[j]; //finds first (should be unique) break; } print('Selecting '+config.formConfig.sponsor.DisplayName+' as sponsor'); //different user than the original form... //should be set to the study sponsor crfEntry.UserId=config.formConfig.sponsor.UserId; crfEntry.Site=site; // //set formId to one found through registration search crfEntry.Form=formId; //// let qconfig=new Object(); qconfig.schemaName='lists'; qconfig.queryName='crfEntry'; qconfig.success=function(data){sendEmail(data,mailRecipient,doNothing,formName+' generated');} qconfig.rows=[crfEntry]; LABKEY.Query.insertRows(qconfig); } // function generateListEntry(formId,queryName,cb){ //check if registration was already generated let formRows=config.formConfig.formRows; let qForm=undefined; for (let i=0;i<formRows.length;i++){ if (formRows[i].Key!=formId) continue; qForm=formRows[i]; } let nData=config.formConfig.dataQueries[queryName].rows.length if (nData>0) return; let pM=getParticipantManagerObject(config); let e2=new Object(); e2.crfRef=getCRFref(); e2.registrationStatus=0; e2.submissionDate=new Date(); e2[pM.getCrfEntryFieldName()]=pM.getParticipantIdFromCrfEntry(); print('set values'); let qconfig=new Object(); qconfig.containerPath=getContainer('data'); qconfig.schemaName='lists'; qconfig.queryName=queryName; qconfig.success=cb; qconfig.rows=[e2] LABKEY.Query.insertRows(qconfig); } // ******************** end form generator (Registration) ******************** //jump to populate table/generate review, etc defined at the begining of the file function setContainer(label,container){ if (!(config.formConfig.hasOwnProperty('container'))){ config.formConfig.container=new Array(); } config.formConfig.container[label]=container; } function getContainer(label){ return config.formConfig.container[label]; } //entry point from generateMasterForm function setFormConfig(){ //add object to store form related data config.formConfig=new Object(); config.formConfig.softwareVersion='T.15.23'; let debug=true; if (debug) print("generateMasterForm1"); //set containers for data and configuration //TODO: set this from a query // setContainer('data',LABKEY.ActionURL.getContainer()); setContainer('config',LABKEY.ActionURL.getContainer()); setContainer('CRF',LABKEY.ActionURL.getContainer()); let selectRows=new Object(); //this is local data selectRows.containerPath=getContainer('CRF'); selectRows.schemaName='lists'; selectRows.queryName='crfSettings'; //store form related data to this object selectRows.success=afterSettings; LABKEY.Query.selectRows(selectRows); } function convertToDictionary(rows){ let x=new Array(); for (let i=0;i<rows.length;i++){ let n=rows[i]['name']; let v=rows[i]['value']; x[n]=v; } return x; } function convertToAssociatedArray(rows,fieldName="name"){ let fName="[convertToAssociatedArray]"; let x=new Object(); for (let i=0;i<rows.length;i++){ let n=rows[i][fieldName]; x[n]=rows[i]; } return x; } function afterSettings(data){ config.formConfig.settings=convertToDictionary(data.rows); let st=config.formConfig.settings; print('afterSettings'); for (let k in st){ print('\t'+k+'='+st[k]); } //if ('dataContainer' in st){ // setContainer('data',st['dataContainer']); //} let vname='configContainer'; if (vname in st){ setContainer('config',st[vname]); } print('Config: '+getContainer('config')); print('Data: '+getContainer('data')); let selectRows=new Object(); //this is local data selectRows.containerPath=getContainer('data'); selectRows.schemaName='lists'; selectRows.queryName='crfEntry'; //use first-> we must first establish link to the rigth crf entry selectRows.filterArray=[LABKEY.Filter.create('entryId',getCRFrefFirst())]; //store form related data to this object selectRows.success=afterCRFEntry; LABKEY.Query.selectRows(selectRows); } function afterCRFEntry(data){ config.formConfig.crfEntry=data.rows[0]; print("Setting crfEntry (x) to "+config.formConfig.crfEntry["entryId"]); //for empty records or those with parentCrf not set, parentCrf comes up as null //nevertheless, with two equal signs, check against undefined also works print('parentCrf set to '+config.formConfig.crfEntry.parentCrf); collectData(); } function collectData(){ let targetObject=config.formConfig; targetObject.print=print; targetObject.getContainer=getContainer; let queryArray=new Array(); //k //site queryArray.push(makeQuery(targetObject,'config','site','siteData',[])); //users queryArray.push(makeQuery(targetObject,'CRF','users','userData',[])); queryArray[queryArray.length-1].schemaName='core'; //crfEditors queryArray.push(makeQuery(targetObject,'config','crfEditors','crfEditorsData',[])); //crfMonitors queryArray.push(makeQuery(targetObject,'config','crfMonitors','crfMonitorsData',[])); //crfSponsors queryArray.push(makeQuery(targetObject,'config','crfSponsors','crfSponsorsData',[])); //study static data queryArray.push( makeQuery(targetObject,'data','crfStaticVariables','crfStaticVariables',[])); queryArray.push(makeQuery(targetObject,'data','specialFields','specialFieldsQuery',[])); //study queryArray.push(makeQuery(targetObject,'data','Study','studyDataAll1',[])); let e=queryArray[queryArray.length-1]; //overload schema name e.schemaName='study'; //make sure variables not part of default view are loaded //here we should already have read crfStaticVariables table e.columns="SubjectColumnName,EudraCTNumber,StudySponsor"; e.columns+=",StudyCoordinator,RegulatoryNumber"; //formStatus let varLabel='sourceFormStatus'; let formStatus=config.formConfig.crfEntry['FormStatus']; let formFilter=LABKEY.Filter.create('Key',formStatus); queryArray.push( makeQuery(targetObject,'config','FormStatus','formStatusData',[formFilter])); //crfButtons let statusFilter=LABKEY.Filter.create(varLabel,formStatus); queryArray.push( makeQuery(targetObject,'config','crfButtons','crfButtons',[statusFilter])); //Forms queryArray.push(makeQuery(targetObject,'config','Forms','formData',[])); //FormSetup queryArray.push(makeQuery(targetObject,'config','FormSetup','formSetup',[])); //generateConfig queryArray.push( makeQuery(targetObject,'config','generateConfig','generateConfigData',[])); //parentCrf let parentCrf=config.formConfig.crfEntry['parentCrf']; if (parentCrf!=undefined){ let crfFilter=LABKEY.Filter.create('entryId',parentCrf); queryArray.push(makeQuery(targetObject,'data','crfEntry','parentCrfData',[crfFilter])); } print('running getDataFromQueries'); getDataFromQueries(queryArray,addStudyData); } function addStudyData(){ let fName='addStudyData'; //convert specialFields to array let q=config.formConfig["specialFieldsQuery"].rows; config.formConfig.specialFields=convertToAssociatedArray(q,"fieldUID"); print(fName); let queryArray=new Array(); let targetObject=config.formConfig; targetObject.print=print; targetObject.getContainer=getContainer; //study queryArray.push(makeQuery(targetObject,'data','Study','studyDataAll',[])); //queryArray.push(makeQuery('data','Study','studyDataAll',[])); let e=queryArray[queryArray.length-1]; //overload schema name e.schemaName='study'; //make sure variables not part of default view are loaded //here we should already have read crfStaticVariables table let staticVarRows=config.formConfig['crfStaticVariables'].rows; let columnModel="" for (let i=0;i<staticVarRows.length;i++){ if (i>0) columnModel+=','; columnModel+=staticVarRows[i]['staticVariable']; } e.columns=columnModel; //also collect ids already in study //registrationQuery should be a dataset //since monitors can review late, it might be profitable to use lists //rather than study let regQueryPars=parseVariables(config.formConfig.settings['registrationQuery']); let regQuery=regQueryPars['query']; let regSchema='study'; if ('schema' in regQueryPars){ regSchema=regQueryPars['schema']; } let crfFilter=LABKEY.Filter.create('crfRef',getCRFref()); queryArray.push(makeQuery(targetObject,'data',regQuery,'registrationData',[crfFilter])); queryArray[queryArray.length-1].schemaName=regSchema; getDataFromQueries(queryArray,fcontinue); } function selectFormSetupRows(formId){ let formSetupRows=new Array(); let allRows=config.formConfig.formSetup.rows; for (let i=0;i<allRows.length;i++){ let formEntry=allRows[i]; if (formEntry.formName==formId) formSetupRows.push(formEntry); } return formSetupRows; } function fcontinue(){ //debug let fName='[fcontinue]'; let varRows=config.formConfig['crfStaticVariables'].rows; let studyVars=config.formConfig['studyDataAll'].rows[0]; for (let i=0;i<varRows.length;i++){ let vName=varRows[i].staticVariable; print(fName+' '+vName+': '+studyVars[vName]); } //parse site config.formConfig.siteRows=config.formConfig.siteData.rows; let sRows=config.formConfig.siteRows; for (let i=0;i<sRows.length;i++){ let siteId=sRows[i].siteNumber; print('site '+siteId); if (siteId==config.formConfig.crfEntry.Site){ config.formConfig.currentSite=sRows[i]; break; } } //config.formConfig.site=data.rows[0]; print("Setting site name to "+config.formConfig.currentSite.siteName); //study config.formConfig.studyData=config.formConfig.studyDataAll.rows[0]; print("XSetting participantField to "+ config.formConfig.studyData["SubjectColumnName"]); config.formConfig.crfEditors=config.formConfig.crfEditorsData.rows; config.formConfig.crfMonitors=config.formConfig.crfMonitorsData.rows; config.formConfig.crfSponsors=config.formConfig.crfSponsorsData.rows; config.formConfig.userRows=config.formConfig.userData.rows; let uRows=config.formConfig.userRows; for (let i=0;i<uRows.length;i++){ let userId=uRows[i].UserId; if (userId==config.formConfig.crfEntry.UserId){ config.formConfig.user=uRows[i]; break; } } //config.formConfig.user=data.rows[0]; print("Setting user to "+config.formConfig.user["DisplayName"]); let fsRows=config.formConfig.formStatusData.rows; config.formConfig.formStatus=fsRows[0].formStatus; config.formConfig.operator=config.role; //config.formConfig.operator=fsRows[0].operator; print('Setting operator to: '+config.formConfig.operator); config.formConfig.formRows=config.formConfig.formData.rows; let formRows=config.formConfig.formRows; //filter out the current form for (let i=0;i<formRows.length;i++){ if (formRows[i].Key==config.formId){ config.formConfig.form=formRows[i]; break; } } config.formConfig.formSetupRows=selectFormSetupRows(config.formId); print("Number of datasets for form ["+config.formId+"]: "+ config.formConfig.formSetupRows.length); let fields=config.formConfig.formSetup.metaData.fields; //get the lookup for queryName column let formQueryName='queryName'; let field="NONE"; for (f in fields){ if (fields[f]['name']!=formQueryName) continue; field=fields[f]; break; } let lookup=field.lookup; print("Getting dataset names from "+lookup.queryName); let selectRows=new Object(); //inputLists should be in configuration container selectRows.containerPath=getContainer('config'); selectRows.schemaName=lookup.schemaName; selectRows.queryName=lookup.queryName; selectRows.success=afterFormDatasets; LABKEY.Query.selectRows(selectRows); } function afterFormDatasets(data){ print('afterFormDatasets: '+data.rows.length); config.formConfig.formDatasets=data;//inputLists config.formConfig.fields=new Object(); config.formConfig.queryMap=new Object(); config.formConfig.additionalData=new Object(); let rows=config.formConfig.formSetupRows; //should skip report only rows for (let i=0;i<rows.length;i++){ let entry=rows[i]; let reviewField=(entry['showFlag']=='REVIEW'); //is the operator set yet? let accessMode=config.formConfig.operator+'Mode'; let skipField=(entry[accessMode]=="NONE"); let queryId=entry['queryName']; let lookupRows=config.formConfig.formDatasets.rows; print('QueryID['+i+']='+queryId); let dentry; for (let j=0;j<lookupRows.length;j++){ if (queryId!=lookupRows[j]['Key']) continue; dentry=lookupRows[j]; break; } let qName=dentry['queryName']; //update list of dataset formConfig is observing (fields/queryMap) while (1){ //review contains no data if (reviewField) break; if (skipField) break; //already in fields if (qName in config.formConfig.fields) break; config.formConfig.fields[qName]=new Object(); break; } while(1){ //already done if (queryId in config.formConfig.queryMap) break; config.formConfig.queryMap[queryId]=qName; break; } if (reviewField) continue; if (skipField) continue; //only do this for real lists let field=config.formConfig.fields[qName]; field.title=entry['title']; field.queryId=queryId; } print("List of datasets in form : "); for (f in config.formConfig.fields){ let field=config.formConfig.fields[f]; print("\t"+f+" ID: "+field.queryId+' title '+field.title); } afterConfig(); } //>>>>>>>>>>>>>>>>>new>>>>>>>>>>>> function setDataLayout(cb){ let rowsSetup=config.formConfig.formSetupRows; config.formConfig.dataQueries=new Object(); let dS=config.formConfig.dataQueries;//reference only let qMap=config.formConfig.queryMap; config.formConfig.lookup=new Object(); for (let i=0;i<rowsSetup.length;i++){ let entry=rowsSetup[i]; //skip review rows if (entry['showFlag']=='REVIEW') continue; let queryId=entry['queryName']; let queryName=qMap[entry['queryName']]; dS[queryName]=new Object(); dS[queryName].title=entry['title']; if (entry['showQuery']!="NONE"){ let sqName=entry['showQuery']; dS[sqName]=new Object(); dS[sqName].title=findTitle(sqName); } } //always add reviews // config.formConfig.dataQueries['reviewComments']=new Object(); //perhaps we will need queryId, but this is stuff for later for (q in config.formConfig.dataQueries){ //callback will be a watchdog and will complete only //when all data will be gathered let dq=config.formConfig.dataQueries[q]; dq.collectingLayout="INITIALIZED"; let selectRows=new Object(); selectRows.queryName=q; selectRows.schemaName='lists'; selectRows.containerPath=getContainer('data'); //we are only interested in metadata selectRows.success=function(data){afterDatasets(data,cb);} LABKEY.Query.selectRows(selectRows); } } function findTitle(queryName){ //find by name from formDatasets //and set associated title as title let rows=config.formConfig.formDatasets.rows; for (let i=0;i<rows.length;i++){ let entry=rows[i]; if (entry['queryName']!=queryName) continue; return entry['title']; } return "NONE"; } //this happens after the for loop, so all dataQueries objects are set function afterDatasets(data,cb){ let qobject=config.formConfig.dataQueries[data.queryName]; print("Inspecting layout for "+data.queryName+" "+qobject); qobject.fields=data.metaData.fields; qobject.collectingLayout="STARTED"; //qobject.started="TRUE"; qobject.lookup=new Object();//keep track of objects let i=0; for (let f in qobject.fields){ //anything else is simple but lookup let field=qobject.fields[f]; if (!("lookup" in field)) continue; qobject.lookup[f]="PENDING"; print("Adding pending lookup for field "+f); if (field.lookup.queryName in config.formConfig.lookup){ qobject.lookup[f]="DONE"; continue; } print("Setting up query for field "+f); config.formConfig.lookup[field.lookup.queryName]=new Object(); let lObject=config.formConfig.lookup[field.lookup.queryName]; lObject.keyColumn=field.lookup.keyColumn; lObject.displayColumn=field.lookup.displayColumn; let selectRows=new Object(); selectRows.schemaName=field.lookup.schemaName; selectRows.queryName=field.lookup.queryName; selectRows.containerPath=getContainer('data'); if ("containerPath" in field.lookup){ selectRows.containerPath=field.lookup.containerPath; } selectRows.columns=field.lookup.keyColumn+","+ field.lookup.displayColumn; //wait for all lookups to return selectRows.success=function(data){addLookup(data,qobject,f,cb);}; print("Sending query: ["+ field.lookup.queryName+"] "+ field.lookup.keyColumn+'/'+ field.lookup.displayColumn); LABKEY.Query.selectRows(selectRows); i+=1; } if (i==0){ print("No lookups for "+data.queryName); qobject.collectingLayout="FINISHED"; } //check if done (both here and in lookup //this only passes if no lookups are anywhere in the dataset assembly if (!dataLayoutSet()) return; cb(); } function addLookup(data,qobject,f,cb){ print("Adding lookup "+data.queryName+' '+qobject+' '+f); let lObject=config.formConfig.lookup[data.queryName]; lObject.LUT=new Array();//key to value lObject.ValToKey=new Array();//value to key let key=lObject.keyColumn; let val=lObject.displayColumn; for (let i=0;i<data.rows.length;i++){ lObject.LUT[data.rows[i][key]]=data.rows[i][val]; lObject.ValToKey[data.rows[i][val]]=data.rows[i][key]; } qobject.lookup[f]="DONE"; if (!dataLayoutSet()) return; cb(); } function dataLayoutSet(){ print("Checking layout completeness"); let dq=config.formConfig.dataQueries; for (f in dq){ print("["+f+"]: "+dq[f].collectingLayout); if (dq[f].collectingLayout=="INITIALIZED") return 0; if (dq[f].collectingLayout=="FINISHED") continue; //OK let qobject=dq[f]; for (q in qobject.lookup){ if (qobject.lookup[q]=="DONE") { print("["+f+"/"+q+"]: DONE"); continue;//OK } print("["+f+"/"+q+"]: "+qobject.lookup[q]); return 0; } dq[f].collectingLayout="FINISHED"; } print("Success"); return 1; } function setData(cb){ fName='[setData]'; //print(fName+': cb '+cb); let crfMatch=getCRFref(); let parentCrf=config.formConfig.crfEntry['parentCrf']; if (parentCrf!=undefined) crfMatch=parentCrf; print(fName+' form crf ['+getCRFref()+'] matching for crfRef='+crfMatch); //collect data and execute callback cb for queries in cb.queryList for (q in config.formConfig.dataQueries){ let fQuery=config.formConfig.dataQueries[q]; fQuery.collectingData="STARTED"; let selectRows=new Object(); selectRows.containerPath=getContainer('data'); selectRows.queryName=q; selectRows.schemaName='lists'; selectRows.filterArray=[ LABKEY.Filter.create("crfRef",crfMatch) ]; selectRows.success=function(data){assembleData(data,cb);}; LABKEY.Query.selectRows(selectRows); } } function assembleData(data,cb){ let fName='[assembleData/'+data.queryName+']'; let fQuery=config.formConfig.dataQueries[data.queryName]; fQuery.rows=data.rows; fQuery.collectingData="FINISHED"; print(fName+': adding data'); for (q in config.formConfig.dataQueries){ let dq=config.formConfig.dataQueries[q]; //print("assembleData ["+q+"]: "+dq.collectingData); if (dq.collectingData=="STARTED") return; } print(fName+': completing'); cb(); } function uploadFile(inputElement,context){ //context should have ID and dirName attributes; //path will be dirName/ID/fieldName_ID.suf //where suf is identical to localPath content picked from //inputElement print('uploadFile: '+inputElement.value+'/'); if (inputElement.type=="text") return; print('uploadFile: '+inputElement.files+'/'); print('uploadFile: '+inputElement.files.length+'/'); if (inputElement.files.length>0){ let file=inputElement.files[0]; print('uploadFile: '+inputElement.value+'/'+file.size); } let url=LABKEY.ActionURL.getBaseURL(); url+='_webdav'; url+=LABKEY.ActionURL.getContainer(); url+='/@files'; url+='/'+context['dirName']; print('uploadFile url: '+url); let uploadConfig=new Object(); uploadConfig.inputElement=inputElement; uploadConfig.context=context; uploadConfig.url=url; uploadConfig.success=afterBaseDir; uploadConfig.failure=tryMakeDir; webdavCheck(uploadConfig); } function afterBaseDir(cfg){ print('afterBaseDir'); cfg.url+='/'+cfg.context['ID']; cfg.success=afterIDDir; cfg.failure=tryMakeDir; webdavCheck(cfg); } function afterIDDir(cfg){ print('afterIDDir'); if (cfg.inputElement.files.length==0){ print('No files found'); return; } let file=cfg.inputElement.files[0]; print('Uploading '+file.name); let suf=file.name.split('.').pop(); cfg.url+='/'+cfg.context['ID']+'.'+suf; cfg.success=afterUpload; cfg.failure=onFailure; cfg.data=file; webdavPut(cfg); } function afterUpload(cfg){ print('afterUpload'); } function tryMakeDir(cfg){ print('tryMakeDir '+cfg.url); cfg.failure=onFailure; webdavMakeDir(cfg); } function request(cfg,verb,data){ print('request['+verb+'] '+cfg.url); let connRequest=new XMLHttpRequest(); connRequest.addEventListener("loadend", function(){checkResponse(connRequest,cfg);}); connRequest.open(verb, cfg.url); connRequest.send(data); //print('request['+verb+'] sent'); } function checkResponse(xrq,cfg){ //print('checkResponse: readyState '+xrq.readyState); //print('checkResponse: status '+xrq.status); if (xrq.status<400) { //client errors 400-499 //server errors 500-599 cfg.success(cfg); return; } cfg.status=xrq.status; cfg.failure(cfg); } function webdavMakeDir(cfg){ request(cfg,'MKCOL',null);} function webdavCheck(cfg) { request(cfg,'GET',null);} function webdavPut(cfg) { request(cfg,'PUT',cfg.data);} function onFailure(cfg){ print('request failed with status='+cfg.status); }