var crfVisit={}; crfVisit.config=new Object(); crfVisit.setDebug= function(debug=null){ if (debug){ this.print=function(msg){debug.this.print(msg);}; this.clear=function(){debug.clear();} return; } //provide default functions if not debug object is available this.print=function(msg){console.log(msg);} this.clear=function(){;} } crfVisit.setDebug(); //harmonize signature //(schema,query,row,action=cvDoNothing,container=null crfVisit.insertRows= function(schema,query,rows,action=null,container=null,failure=null){ this.modifyRows('insert',schema,query,rows,action,container); } crfVisit.deleteRows= function(schema,query,rows,action=null,container=null,failure=null){ this.modifyRows('delete',schema,query,rows,action,container); } crfVisit.modifyRows= function(mode,schema,query,rows,action=null,container=null,failure=null){ //insert rows to container/schema/query and return with action let fName="[cvModifyRows/"+mode+"]"; this.print(fName+' '+schema+'/'+query); let qconfig=new Object(); qconfig.schemaName=schema; qconfig.queryName=query; if (container) qconfig.containerPath=container; if (!rows) { this.print(fName+' rows '+rows); return; } qconfig.rows=rows; qconfig.success=function(data){;}; if (action) qconfig.success=action; if (mode=='insert') LABKEY.Query.insertRows(qconfig); if (mode=='update') LABKEY.Query.updateRows(qconfig); if (mode=='delete') LABKEY.Query.deleteRows(qconfig); this.print(fName+" done"); } crfVisit.selectRows= function(schema,query,filters=[],action=null, container=null, failure=null, columns=null){ let fName="[cvSelectRows]"; this.print(fName+' '+schema+' '+query+' '+container); let qconfig=new Object(); qconfig.schemaName=schema; qconfig.queryName=query; if (container) qconfig.containerPath=container; qconfig.filterArray=filters; qconfig.success=function(data){;}; if (action) qconfig.success=action; if (failure) qconfig.failure=failure; if (columns) qconfig.columns=columns; LABKEY.Query.selectRows(qconfig); this.print(fName+" done"); } crfVisit.createCrfStatus= function(crfEntry){ let crfStatus=new Object(); crfStatus.entryId=crfEntry.entryId; crfStatus.submissionDate=new Date(); crfStatus.FormStatus=crfEntry.FormStatus; crfStatus.User=crfEntry.UserId; crfStatus.Form=crfEntry.Form; return crfStatus; } crfVisit.init= function(cb=null){ let that=this; let action=function(){that.scriptsLoaded(cb);}; LABKEY.Utils.requiresScript(["crfTecant/runQuery.js","crfTecant/crfReviewSection.js","crfTecant/participantIdManager.js","crfTecant/variableList.js","crfTecant/webdav.js","crfTecant/crfPrint.js"],action); } crfVisit.scriptsLoaded= function(cb=null){ participantIdManager.set(this); webdav.set(this); crfReviewSection.set(this); crfPrint.set(this); if (cb) cb(); } crfVisit.getElement= function(id){ return this.config.document.getElementById(id); } crfVisit.setContainer= function(label,container){ let config=this.config; if (!(config.formConfig.hasOwnProperty('container'))){ config.formConfig.container=new Array(); } config.formConfig.container[label]=container; } crfVisit.getContainer= function(label){ return this.config.formConfig.container[label]; } crfVisit.getCRFrefFirst= function(){ //crfRef is part of html call and gets stored in the page return this.getElement(this.config.crfRefId).innerHTML; } crfVisit.getCRFref= function (){ //'crfRefId' return this.config.formConfig.crfEntry['entryId']; } crfVisit.getCRFrefData= function(){ let parentCrf=this.config.formConfig.crfEntry['parentCrf']; if (parentCrf!=undefined) return parentCrf; return this.getCRFref(); } crfVisit.onFailure= function(errorInfo, options, responseObj){ if (errorInfo && errorInfo.exception) alert("Failure: " + errorInfo.exception); else alert("Failure: " + responseObj.statusText); } crfVisit.doNothing= function (){ this.print('doNothing called'); } crfVisit.getSnapshotObject= function(){ if (!("dataQueriesSnapshot" in this.config.formConfig)) this.config.formConfig.dataQueriesSnapshot=new Object(); return this.config.formConfig.dataQueriesSnapshot; } crfVisit.getQuerySnapshot= function(queryName){ //check whether queryName is in snapshotObject? return this.getSnapshotObject()[queryName]; } crfVisit.getLayoutObject= function(){ if (!("dataQueriesLayout" in this.config.formConfig)) this.config.formConfig.dataQueriesLayout=new Object(); return this.config.formConfig.dataQueriesLayout; } crfVisit.getQueryLayout= function(queryName){ //check whether queryName is in snapshotObject? return this.getLayoutObject()[queryName]; } crfVisit.getLookupObject= function(){ if (!("lookup" in this.config.formConfig)) this.config.formConfig.lookup=new Object(); return this.config.formConfig.lookup; } crfVisit.getLookup= function(queryName){ let x=this.getLookupObject(); if (queryName in x) return x[queryName]; return null; } crfVisit.getQueryList= function(){ if (!("queryList" in this.config.formConfig)) this.config.formConfig.queryList=new Object(); return this.config.formConfig.queryList; } crfVisit.getIdManager= function(){ if (!("idManager" in this.config.formConfig)) this.config.formConfig.idManager=participantIdManager.getObject(); return this.config.formConfig.idManager; } crfVisit.getAdditionalData= function(formSetupEntry){ //return information on additional data associated with the form //additionalData is a sub-list with multiple entries per patient/visit let config=this.config; //argument is the row of the formSetup setup list let queryName=config.formConfig.queryMap[formSetupEntry['queryName']]; let fName='[getAdditionalData/'+queryName+']'; this.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){ this.print(fName+': Returning preset value'); return config.formConfig.additionalData[queryName]; } //first time we see this query, so we have to do the setup this.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") { this.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 this.print(fName+": generateReport"); ad.isReview=true; return ad; } //setup the additionalData memory object this.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(); let crfValues=this.getCRFref(); let settings=this.config.formConfig.settings; let doMerge=false; if ("mergeListData" in settings){ let ar=settings["mergeListData"].split(','); if (ar.includes(ad.queryName)) doMerge=true; } let idField='participantStudyId'; let id=this.config.formConfig.crfEntry[idField]; this.print(fName+": studyId "+id+" doMerge "+doMerge); if (doMerge){ let entries=config.formConfig.crfEntries.rows; for (let i=0;ishould it be queryName? let fName='[generateReview]'; this.print(fName+" list "+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"; reviewSetup.reviewTable=true; 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"]=this.getCRFref(); if (config.formConfig.crfEntry.parentCrf){ reviewSetup.filters["crfRef"]=this.getCRFref()+";"+config.formConfig.crfEntry.parentCrf; } 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; let msg="Review: divId: "+divReviewId; msg+=" inputId: "+reviewSetup.getInputId; this.print(msg); this.updateListDisplay(divReviewListId,"reviewComments",reviewSetup.filters,true); if (! generateTableFlag) return; this.generateTable("reviewComments",divReviewId,new Object(),reviewSetup); } //>>>>>>>>>>trigger visibility of additional lists crfVisit.setListVisibility= function(input,setup,readonlyFlag){ let config=this.config; let fName="[setListVisibility/"+setup.queryName+"]"; this.print(fName); let additionalData=config.formConfig.additionalData[setup.queryName]; let x = this.getElement(additionalData.divName); this.print(fName+": Div: "+x); x.style.display="none"; let sText; if (readonlyFlag) sText=input.innerText; else sText=input.options[input.selectedIndex].text; this.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"; this.updateListDisplay(additionalData.divQueryName, additionalData.queryName,filters,readonlyFlag); } } //>>have list refresh when data is added (not optimal yet) // crfVisit.updateListDisplay= function(divName,queryName,filters,readonlyFlag){ //use Labkey.QueryWebPart to show list let fName="[updateListDisplay]"; this.print(fName+": UpdateListDisplay: Query - "+queryName +" div - "+divName); if (divName=="NONE") return; let crfRef=this.getCRFref(); let div=this.getElement(divName); this.print(fName+": generating WebPart: "+queryName); var qconfig=new Object(); qconfig.renderTo=divName; //point to data container qconfig.containerPath=this.getContainer('data'); qconfig.schemaName='lists'; qconfig.queryName=queryName; qconfig.buttonBarPosition='top'; qconfig.filters=[]; for (f in filters){ let fType=LABKEY.Filter.Types.EQUAL; this.print(fName+' filter ['+f+'] '+filters[f]+'/'+typeof(filters[f])+' ['+fType+']'); if (variableList.isFilterList(filters[f])){ fType=LABKEY.Filter.Types.IN; } qconfig.filters.push(LABKEY.Filter.create(f, filters[f],fType)); } let that=this; qconfig.success=function(data){that.updateSuccess(data);}; qconfig.failure=function(errorInfo,options,responseObj){that.onFailure(errorInfo,options,responseObj);}; //show only print button if (readonlyFlag){ qconfig.buttonBar=new Object(); qconfig.buttonBar.items=["print"]; } qconfig.showUpdateColumn=false; qconfig.showDetailsColumn=false; LABKEY.QueryWebPart(qconfig); } crfVisit.updateSuccess= function(data){ this.print("Update success"); } //TODO: this should trigger a data refresh on section, ie populateData(field) crfVisit.toggleVisibility= function(divName,buttonName){ let fName='[toggleVisibility/'+divName+']'; this.print(fName); let config=this.config; let x = this.getElement(divName); if (x.style.display === "none") { //exclude non data sections (like debug)... this.print(fName+': issuing setData(populateSection)'); x.style.display = "block"; this.getElement(buttonName).value="Hide"; let that=this; let cb=function(){that.populateSection(divName);}; this.setData(cb); } else { x.style.display = "none"; this.getElement(buttonName).value="Show"; } } crfVisit.generateButton= function(divName,caption,label,callbackLabel,callback=null){ this.print("generateButtonX"); let config=this.config; 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"; let that=this; if (callback) i1.onclick=callback; else i1.onclick=function(){that[callbackLabel]();}; 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=this.getElement(divName); this.print("generateButton: element["+divName+"]: "+el); el.appendChild(tb); } crfVisit.generateSubQuery= function(input, setup, readonlyFlag){ let fName="[generateSubQuery]"; let config=this.config; if (setup.isReview) return; if (!(setup.queryName in config.formConfig.additionalData)){ this.print(fName+': no additionalData entry (probably a subquery)'); return; } let additionalData=config.formConfig.additionalData[setup.queryName]; if (!("showFlag" in additionalData)) return; this.print(fName); let expId=setup.getInputId(additionalData.showFlag); if (expId!=input.id) { this.print(fName+": ignoring field "+input.id+"/"+expId); return; } this.print(fName+": Setting onChange to "+input.id); if (readonlyFlag) return; let that=this; input.onchange=function(){that.setListVisibility(input,setup,readonlyFlag)}; } //>>populate fields // // //split to field generation and field population // crfVisit.addFieldRow= function(tb,field,setup,additionalData){ let fName="[addFieldRow/"+setup.queryName+':'+field.name+']'; let config=this.config; let vName=field.name; let vType=field.type; let isLookup=("lookup" in field); this.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"; this.print("Creating checkbox"); break; } break; } input.id=setup.getInputId(vName); cell1.appendChild(input); this.print(fName+': adding element '+input.id); this.print(fName+': listing element '+this.getElement(input.id)); //connect associated list this.generateSubQuery(input,setup,readonlyFlag); if (readonlyFlag) { this.print(fName+': exiting(readonlyFlag)'); return; } if (!isLookup) { this.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 this.print(fName+": query: "+lookup.queryName); this.print(fName+": ElementId: "+input.id); this.print(fName+": No of options: " +lObject.LUT.length); this.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 = ""); //add other, label them with LUT for (let v in lObject.LUT) { this.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; } crfVisit.addSpecialFieldRows= function(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=variableList.parseVariables(specFieldSetup['actionParameters']); let config=this.config;//for add data this.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; generateRegistration.set(this); let gc=generateRegistration.getObject(q,setup.getInputId(fieldName)); let that=this; let action=function(){that.doNothing();}; if ('mailRecipient' in q){ gc.callback=function(data){that.sendEmail(data,q['mailRecipient'],action,q['subject']);}; } else gc.callback=function(data){that.doNothing();}; if ("addData" in q){ vars=q["addData"].split(','); gc.addData=new Array(); for (let v in vars){ let s=vars[v] //variable name can be written as A/B where A is the name in addData and B is the variable name in crfEntry //useful for mocking up crfId from daughter crf-s such as registration let sArray=s.split('/'); let sTarget=sArray[0]; let sSource=sArray[sArray.length-1]; gc.addData[sTarget]=config.formConfig.crfEntry[sSource]; this.print(fName+" addData ["+sTarget+"]: "+gc.addData[sTarget]); } } 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(){generateRegistration.execute(gc);}; b.value="Generate ID"; cell1.appendChild(b); } } crfVisit.populateFieldRow= function(entry,field,setup){ this.populateField(entry,field,setup); this.populateSubQuery(entry,field,setup); } crfVisit.populateSubQuery= function(entry,field,setup){ let fName='[populateSubQuery/'+setup.queryName+':'+field.name+']'; let config=this.config; if (setup.isReview) return; if (!(setup.queryName in config.formConfig.additionalData)){ let msg=fName+': no additionalData entry for '+setup.queryName; msg+=' (probably a subquery)'; this.print(msg); return; } //find if field is connected to a sub array //find queryName // let additionalData=config.formConfig.additionalData[setup.queryName]; this.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) { this.print(fName+": ignoring field "+id+"/"+eId); return; } this.print(fName+': id '+id); //hard to estimate readonlyFlag // let input=this.getElement(id); let eType=input.nodeName.toLowerCase(); let readonlyFlag=eType!="select"; this.setListVisibility(input,setup,readonlyFlag); } crfVisit.clearField= function(field,setup){ let foo=new Object(); this.populateField(foo,field,setup); } crfVisit.populateField= function(entry,field,setup){ let vName=field.name; let fName='[populateFieldName/'+vName+']'; let config=this.config; let varValue=null; //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); this.print(fName+' v='+varValue+'/'+isLookup+' ['+ setup.getInputId(field.name)+']'); let vType=field.type; let id=setup.getInputId(vName); let input=this.getElement(id); //date if (vType=="date"){ if (varValue) varValue=new Date(varValue); } //lookup for readonly if (isLookup && varValue){ let lookup=field["lookup"]; //get all values from config.formConfig.lookup[X] let lObject=config.formConfig.lookup[lookup.queryName]; varValue=lObject.LUT[varValue]; } this.print('Element: '+input); //figure out the element type let eType=input.nodeName.toLowerCase(); this.print('Element type: '+eType); //change varValue for printing //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;i0) entry=fQuery.rows[0]; let fields=queryLayout.fields; for (f in fields){ let field=fields[f]; //each field is a new row this.print(fName+": Adding field: "+f+'/'+field.name+' hidden: '+field.hidden+' type:'+field.type); if (field.hidden) continue; if (field.name=="crfRef") continue; this.populateFieldRow(entry,field,setup); let fieldUID=listName+":"+field.name; if (fieldUID in this.config.formConfig["specialFields"]){ let specFieldSetup=this.config.formConfig["specialFields"][fieldUID]; this.populateSpecialFieldRow(specFieldSetup,entry,field,setup); } } } crfVisit.generateTable= function(listName,divName,additionalData,setup){ let fName="[generateTable/"+listName+"]"; let config=this.config; this.print(fName); //is listName and setup.queryName a duplicate of the same value this.print(fName+': setup.queryName '+setup.queryName); //assume data is set in config.formConfig.dataQueries[data.queryName].rows; let populateData=true; if ("subTable" in setup){ this.print(fName+" is subTable"); populateData=false; } let entry=new Object(); //data snapshot let fQuerySnapshot=this.getQuerySnapshot(listName); let queryLayout=this.getQueryLayout(listName); //here I assume that listName was parsed during setDataLayout and setData //so that rows was set (even if they are empty) this.print(fName+": Nrows "+fQuerySnapshot.rows.length); if (fQuerySnapshot.rows.length>0) entry=fQuerySnapshot.rows[0]; if ("reviewTable" in setup){ entry['reviewComment']=''; delete entry["ModifiedBy"]; } let tb=config.document.createElement('table'); tb.className="t2"; this.getElement(divName).appendChild(tb); //this are the fields (probably constant) let fields=queryLayout.fields; for (f in fields){ let field=fields[f]; let fieldUID=listName+":"+field.name; //each field is a new row this.print(fName+": Adding field: "+f+'/'+field.name+' ('+fieldUID+').'); //unique name if (field.hidden) continue; if (field.name=="crfRef") continue; this.addFieldRow(tb,field,setup,additionalData); if (populateData) this.populateFieldRow(entry,field,setup); if (fieldUID in config.formConfig["specialFields"]){ let specFieldSetup=config.formConfig["specialFields"][fieldUID]; this.addSpecialFieldRows(tb,specFieldSetup,setup); this.populateSpecialFieldRow(specFieldSetup,entry,field,setup); } } //finish of if apply button is not required if (!("addApply" in setup)) { this.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 let that=this; input.onclick=function(){that.saveReview(listName,cell1.id,setup)}; } crfVisit.setEntryFromElement= function(entry,elementId, field){ //set value to entry from element using representation (field) from labkey // // let fName='setEntryFromElement'; let config=this.config; let el=this.getElement(elementId); if (!el) { this.print(fName+" element: "+elementId+" not found"); return; } this.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) return; date.setUTCHours(12); entry[vName]=date.toString(); this.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=this.getElement(id1); this.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 this.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") { this.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; } crfVisit.saveReview= function(queryName,elementId,setup){ //loads any queryName let debug=true; let fName='[saveReview/'+queryName+']'; this.print(fName+" elementId "+elementId); let unique=("unique" in setup); //data snapshot let fQuerySnapshot=this.getQuerySnapshot(queryName); let nRows=fQuerySnapshot.rows.length; let mode='insert'; //data layout let queryLayout=this.getQueryLayout(queryName); let entry=new Object(); //determine mode based on entry uniqueness and presence of data if (unique && nRows>0){ entry=fQuerySnapshot.rows[0]; mode='update'; } this.print(fName+' unique '+unique+' mode '+mode+' nRows '+nRows); entry.crfRef=this.getCRFrefData(); this.print(fName+" set crfRef="+entry.crfRef); let fields=queryLayout.fields; for (f in fields){ let field=fields[f]; this.print(fName+" saveReview field: "+field.name); if (field.hidden) continue; let vName=field.name; let vType=field.type; this.print(fName+" vType: "+vType); if (vName=="crfRef") continue; //need to save queryName for reviewComments let eId=setup.getInputId(vName); //copy values from form to entry this.setEntryFromElement(entry,eId,field); //clear field value if (!unique) this.clearField(field,setup); } let that=this; let action=function(data){that.updateLastSavedFlag(data,setup,elementId)}; this.modifyRows(mode,'lists',queryName,[entry],action,this.getContainer('data')); } crfVisit.updateLastSavedFlag= function(data,setup,elementId){ let fName='[updateLastSavedFlag]'; let config=this.config; this.print(fName+" update last saved flag to "+elementId); let el=this.getElement(elementId); let dt=new Date(); el.innerHTML="Last saved "+dt.toString(); if (data.queryName=="reviewComments"){ this.updateListDisplay(setup.divReviewListId,"reviewComments",setup.filters,true); } //refresh stored data! let writeMode=!setup.readonlyFlag(); let that=this; if ("unique" in setup) this.setData(function (){that.populateTable(data.queryName,writeMode);}); if ("masterQuery" in setup){ let ad=config.formConfig.additionalData[setup.masterQuery]; this.print('Updating list display: '+setup.queryName+'/'+ad.queryName); this.updateListDisplay(ad.divQueryName,ad.queryName,ad.filters,false); } } //******************************************upload to database ********************* crfVisit.onDatabaseUpload= function(){ let fName='[onDatabaseUpload]'; this.print(fName); let config=this.config; config.upload=new Object(); let fc=new Object(); let pM=this.getIdManager(); fc.participantId=participantIdManager.getParticipantIdFromCrfEntry(pM); this.print(fName+' id '+fc.participantId); this.afterParticipantId(fc); } crfVisit.afterParticipantId= function(fc){ this.print("Setting participantId to "+fc.participantId); let config=this.config; 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(); this.print("Form rows: "+formSetupRows.length); for (let i=0;i0) { let that=this; let action=function(data){that.afterStudyUpload(data);}; this.modifyRows('update','study',queryObj.queryName,studyRows,action,this.getContainer('data')); this.print(fName+'updateRows sent'); } else{ let data=new Object(); data.rows=new Array(); this.afterStudyUpload(data); } } crfVisit.afterStudyUpload= function(data){ let fName='[afterStudyUpload] '; let config=this.config; let that=this; this.print(fName); //let participantField=config.participantField; let participantField=config.formConfig.studyData["SubjectColumnName"]; this.print(fName+' participantField: '+participantField); let queryObj=config.upload.queries[config.upload.queryId]; let queryName=queryObj.queryName; this.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;i1){ entry.SequenceNum+=i/100; } this.print( "Adding sequence number "+entry.SequenceNum); rows.push(entry); } if (rows.length>0){ let action=function(data){that.afterListUpload(data);}; this.insertRows('study',queryName,rows,action,this.getContainer('data')); } else{ let data=new Object(); data.rows=rows; this.afterListUpload(data); } } crfVisit.afterListUpload= function(data){ let config=this.config; let queryObj=config.upload.queries[config.upload.queryId]; let queryName=queryObj.queryName; this.printErr("Inserted "+data.rows.length+" rows to "+queryName); queryObj.queryStatus="DONE"; config.upload.queryId+=1; this.copyToDataset(); } //*************************update for further review ************************* crfVisit.onUpdateForReview= function(){ let config=this.config; let targetStatus=config.formConfig.targetStatus['onUpdateForReview']; let targetRecipient=config.formConfig.targetRecipient['onUpdateForReview']; let action=new Object(); action.name='onUpdateForReview'; let that=this; let redirect=function(){that.redirect();}; action.cb=function(data){that.sendEmail(data,targetRecipient,redirect,'Form updated for review');}; this.updateFlag(targetStatus,action); } crfVisit.updateFlag= function(flag,action){ let fName='[updateFlag 1]'; let config=this.config; let entry=config.formConfig.crfEntry; entry.FormStatus=flag; let uId=config.formConfig.currentUser.UserId; entry[config.formConfig.operator]=uId; this.print(fName+': Form: '+entry.Form); this.print(fName+": set form status to "+entry.FormStatus); let that=this; let cb=function(data){that.completeWithFlag(data,action);}; this.modifyRows('update','lists','crfEntry',[entry],cb,this.getContainer('data')); } crfVisit.completeWithFlag= function(data,action){ let fName='[completeWithFlag]'; this.print(fName+': nrows '+data.rows.length); let fentry=data.rows[0]; this.print(fName+': form status '+fentry.FormStatus); this.print(fName+': form '+fentry.Form); let crfStatus=this.createCrfStatus(fentry); let config=this.config; crfStatus.operator=config.formConfig.operator; crfStatus.action=action.name; let that=this; let cb=function(){that.doNothing();}; if (action.cb) cb=action.cb; this.insertRows('lists','crfStatus',[crfStatus],cb,this.getContainer('data')); } //************************************************ submit ******************************************* crfVisit.onSubmit= function(){ //update list storage and change status this.hideErr(); this.clearErr(); this.printErr("onSubmit"); let that=this; let action=function(){that.verifyData();}; this.setData(action); } crfVisit.verifyData= function(){ let fName='[verifyData]'; let config=this.config; let qList=this.getQueryList(); let that=this; let doNothing=function(data){that.doNothing();}; for (q in qList){ let qData=this.getQuerySnapshot(q); if (q=="reviewComments") continue; //copy snapshot to history if (qData.rows.length==0){ this.print(fName+' no rows for '+q); } else this.insertRows('lists',q+'History',qData.rows,doNothing,this.getContainer('data')); //if it doesn't have additionalData, it is a sub query if (!(q in config.formConfig.additionalData)){ continue; } if (qData.rows.length<1){ this.printErr('Missing entry for query '+q); return false; } } //this is necessary only for Generated to Generation completed step let actionSettings=config.formConfig.actionSettings['onSubmit']; if (variableList.hasVariable(actionSettings,"updateRegistration")){ this.updateRegistration(); } let targetStatus=config.formConfig.targetStatus['onSubmit']; let targetRecipient=config.formConfig.targetRecipient['onSubmit']; this.print(fName+' targetStatus: '+targetStatus); let finalStep=function(){that.redirect();}; if (variableList.hasVariable(actionSettings,"finalStep")){ //set to doNothing to remain on submit window if (actionSettings.finalStep=="doNothing"){ finalStep=doNothing; } } let action=new Object(); action.name='onSubmit'; action.cb=function(data){that.sendEmail(data,targetRecipient,finalStep,'Form sumbitted');}; this.updateFlag(targetStatus,action); } crfVisit.getEmail= function(recipientCode){ this.print('getEmail w/'+recipientCode); let config=this.config; 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=this.getUser(parentCrf.rows[0].UserId,'parentUser'); } let recipientCategories=recipientCode.split(','); for (let i=0;i0) 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; this.generateErrorMsg(msg); return; } } this.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(); config.formConfig.actionSettings=new Array(); for (let i=0; i0){ this.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 ********************* crfVisit.onGenerateQuery= function(queryName){ let fName='[onGenerateQuery]'; this.print(fName+' '+queryName); // let config=this.config; let cfgRows=config.formConfig.generateConfigData.rows; // //queryName to queryId? let queryId=config.formConfig.fields[queryName].queryId; let cfgRow=undefined; for (let i=0;i0) { this.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=this.getCRFref(); crfEntry["Date"]=new Date(); crfEntry["View"]="[VIEW]"; crfEntry.formStatus=1;//In progress //checks for both field presence (if not in query, undefined) and field value (if not set, null) this.print(fName+' setup status: '+formCfg.formStatus); if (formCfg.formStatus){ crfEntry.formStatus=formCfg.formStatus; } //get local Id let pM=this.getIdManager(); crfEntry[participantIdManager.getCrfEntryFieldName(pM)]=participantIdManager.getParticipantIdFromCrfEntry(pM); // //set other variables //requires studyData as part of formConfig // let studyData=config.formConfig.studyData; this.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;i0) return; //create new list entry let pM=this.getIdManager(); let e2=new Object(); e2.crfRef=this.getCRFref(); e2.registrationStatus=0; e2.submissionDate=new Date(); e2[participantIdManager.getCrfEntryFieldName(pM)]=participantIdManager.getParticipantIdFromCrfEntry(pM); this.print('set values'); this.insertRows('lists',queryName,[e2],cb,this.getContainer('data')); } // ******************** end form generator (Registration) ******************** //jump to populate table/generate review, etc defined at the begining of the file //entry point from generateMasterForm crfVisit.setFormConfig= function(){ let fName="[setFormConfig]"; let config=this.config; //add object to store form related data config.formConfig=new Object(); config.formConfig.softwareVersion='T.15.68'; this.print(fName+" generateMasterForm"); //set containers for data and configuration //TODO: set this from a query // this.setContainer('data',LABKEY.ActionURL.getContainer()); this.setContainer('config',LABKEY.ActionURL.getContainer()); this.setContainer('CRF',LABKEY.ActionURL.getContainer()); //this is local data let that=this; let action=function(data){that.afterSettings(data);}; this.selectRows('lists','crfSettings',[],action,this.getContainer('CRF')); //store form related data to this object } crfVisit.afterSettings= function(data){ let fName='[afterSettings]'; let config=this.config; config.formConfig.settings=variableList.convertToDictionary(data.rows); let st=config.formConfig.settings; this.print('afterSettings'); for (let k in st){ this.print(fName+'\t'+k+'='+st[k]); } //if ('dataContainer' in st){ // setContainer('data',st['dataContainer']); //} let vname='configContainer'; if (vname in st){ this.setContainer('config',st[vname]); } this.print('Config: '+this.getContainer('config')); this.print('Data: '+this.getContainer('data')); //use first-> we must first establish link to the rigth crf entry let filters=[LABKEY.Filter.create('entryId',this.getCRFrefFirst())]; let that=this; let action=function(data){that.afterCRFEntry(data);}; this.selectRows('lists','crfEntry',filters,action,this.getContainer('data')); } crfVisit.afterCRFEntry= function(data){ let config=this.config; let fName='[afterCRFEntry]'; config.formConfig.crfEntry=data.rows[0]; this.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 this.print('parentCrf set to '+config.formConfig.crfEntry.parentCrf); this.collectData(); } crfVisit.collectData= function(){ let config=this.config; let targetObject=config.formConfig; let queryArray=new Array(); //k //site queryArray.push(runQuery.makeQuery(targetObject,'config','site','siteData',[])); //users queryArray.push(runQuery.makeQuery(targetObject,'CRF','users','userData',[])); queryArray[queryArray.length-1].schemaName='core'; //crfEditors queryArray.push(runQuery.makeQuery(targetObject,'config','crfEditors','crfEditorsData',[])); //crfMonitors queryArray.push(runQuery.makeQuery(targetObject,'config','crfMonitors','crfMonitorsData',[])); //crfSponsors queryArray.push(runQuery.makeQuery(targetObject,'config','crfSponsors','crfSponsorsData',[])); //crfManagers queryArray.push(runQuery.makeQuery(targetObject,'config','crfManagers','crfManagersData',[])); //study static data queryArray.push( runQuery.makeQuery(targetObject,'data','crfStaticVariables','crfStaticVariables',[])); queryArray.push(runQuery.makeQuery(targetObject,'data','specialFields','specialFieldsQuery',[])); //study queryArray.push(runQuery.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( runQuery.makeQuery(targetObject,'config','FormStatus','formStatusData',[formFilter])); //crfButtons let statusFilter=LABKEY.Filter.create(varLabel,formStatus); queryArray.push( runQuery.makeQuery(targetObject,'config','crfButtons','crfButtons',[statusFilter])); //Forms queryArray.push(runQuery.makeQuery(targetObject,'config','Forms','formData',[])); //FormSetup queryArray.push(runQuery.makeQuery(targetObject,'config','FormSetup','formSetup',[])); //generateConfig queryArray.push( runQuery.makeQuery(targetObject,'config','generateConfig','generateConfigData',[])); //inputLists queryArray.push( runQuery.makeQuery(targetObject,'config','inputLists','inputLists',[])); //parentCrf let parentCrf=config.formConfig.crfEntry['parentCrf']; if (parentCrf!=undefined){ let crfFilter=LABKEY.Filter.create('entryId',parentCrf); queryArray.push(runQuery.makeQuery(targetObject,'data','crfEntry','parentCrfData',[crfFilter])); } //crfEntries queryArray.push( runQuery.makeQuery(targetObject,'data','crfEntry','crfEntries',[])); this.print('running getDataFromQueries'); let that=this; //let action=function(data){that.doNothing();}; let action=function(){that.addStudyData();}; runQuery.getDataFromQueries(this,queryArray,action); } crfVisit.addStudyData= function(){ let fName='addStudyData'; let config=this.config; //convert specialFields to array let q=config.formConfig["specialFieldsQuery"].rows; config.formConfig.specialFields=variableList.convertToAssociatedArray(q,"fieldUID"); this.print(fName); let queryArray=new Array(); let targetObject=config.formConfig; //study queryArray.push(runQuery.makeQuery(targetObject,'data','Study','studyDataAll',[])); //queryArray.push(runQuery.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;i0) 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=variableList.parseVariables(config.formConfig.settings['registrationQuery']); let regQuery=regQueryPars['query']; let regSchema='study'; if ('schema' in regQueryPars){ regSchema=regQueryPars['schema']; } queryArray.push(runQuery.makeQuery(targetObject,'data',regQuery,'registrationData',[])); queryArray[queryArray.length-1].schemaName=regSchema; let that=this; let action=function(){that.fcontinue();}; runQuery.getDataFromQueries(this,queryArray,action); } crfVisit.fcontinue= function(){ //debug let fName='[fcontinue]'; let config=this.config; let varRows=config.formConfig['crfStaticVariables'].rows; let studyVars=config.formConfig['studyDataAll'].rows[0]; for (let i=0;i>>>>>>>>>>>>>>>>new>>>>>>>>>>>> crfVisit.setDataLayout= function(cb){ let fName='[setDataLayout]'; let config=this.config; this.print(fName); let rowsSetup=config.formConfig.formSetupRows; let queryArray=new Array(); let dS=this.getLayoutObject();//reference only let qList=this.getQueryList(); let qMap=config.formConfig.queryMap; //config.formConfig.lookup=new Object(); for (let i=0;i0){ let file=inputElement.files[0]; this.print('uploadFile: '+inputElement.value+'/'+file.size); webdav.uploadFile(file,context); } } crfVisit.printForm= function(){ crfPrint.printForm(); }