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;i<entries.length;i++){ let e=entries[i]; if (e[idField]==id){ crfValues+=";"+e['entryId']; } } } this.print(fName+": crfValues "+crfValues); ad.filters['crfRef']=crfValues; //compose a long debug message let msg=fName+": flag "+ad.showFlag; msg+=" value "+ad.showFlagValue; msg+=" query "+ad.queryName; this.print(msg); return ad; } crfVisit.selectFormSetupRows= function(formId){ let formSetupRows=new Array(); let config=this.config; 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; } crfVisit.findTitle= function(queryName){ //find by name from formDatasets //and set associated title as title let config=this.config; 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"; } crfVisit.makeSetup= function(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 let fName='[Setup]'; this.print(fName+' '+listName); let setup=new Object(); setup.queryName=listName; setup.readonlyFlag=function(vName){return false}; setup.filters=new Object(); setup.filters['crfRef']=this.getCRFref(); setup.getInputId=function(vName){return listName+"_"+vName;} setup.isReview=false; return setup; } crfVisit.getFullAccessSetup= function(listName){ //addApply - whether a submit/Save button is generated let setup=this.makeSetup(listName); setup.addApply="Save"; return setup; } crfVisit.getReadonlySetup= function(listName){ let setup=this.makeSetup(listName); //see definition of setup object above, change readonly flag setup.readonlyFlag=function(vName){return true}; return setup; } crfVisit.getSetup= function(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 this.getReadonlySetup(listName); //if (formStatus=="Approved") // return readonlySetup(listName); return this.getFullAccessSetup(listName); } crfVisit.generateSection= function(formSetupEntry){ let config=this.config; let that=this; let listName=config.formConfig.queryMap[formSetupEntry['queryName']]; //if (!listName) is for debugSection if (!listName){ listName="debugSection"; } 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=this.getAdditionalData(formSetupEntry); this.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(){that.toggleVisibility(listName,input.id)}; cell.appendChild(input); this.getElement(formName).appendChild(tb); let div=config.document.createElement('div'); div.id=listName; div.style.display="none"; this.getElement(formName).appendChild(div); //here divert for debugArea if (listName=="debugSection"){ let debugArea=config.document.createElement('textarea'); debugArea.rows=10; debugArea.cols=95; debugArea.id=config.debugId; div.appendChild(debugArea); return; } 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); } this.print(fName+" generate master table"); let writeMode=accessMode=="EDIT"; let setup=this.getSetup(listName,writeMode); if ("isReview" in additionalData){ crfReviewSection.set(this); let action=function(){crfReviewSection.CB();}; crfReviewSection.generateSection(listName,div.id,action); return; } //master table is unique per visit setup.unique=true; this.generateTable(listName,divTable.id,additionalData,setup); if (debug) this.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 xsetup=this.getFullAccessSetup(qName); //only set master query for additionalData xsetup.masterQuery=listName; //if (readonly) setup=readonlySetup(config); xsetup.subTable=true; this.generateTable(qName,dName,additionalData,xsetup); //generateTable(formSetupEntry,qName,dName,additionalData,setup); } this.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; this.generateReview(divReview.id,divReviewList.id,listName,accessMode); if (accessMode!='GENERATE') return; this.print('Adding generate button'); //add generateButton let divGenerateButton=config.document.createElement('div'); divGenerateButton.id=listName+'GenerateButton'; div.appendChild(divGenerateButton); this.print('Adding generate button completed to here'); let cb=function(){that.onGenerateQuery(listName);}; this.generateButton(divGenerateButton.id,'Generate','Generate '+listName,'onGenerateQuery',cb); this.print('Adding generate button completed'); } crfVisit.generateReview= function(divReviewId,divReviewListId, listName, accessMode){ let config=this.config; let listId=config.formConfig.fields[listName].queryId; //listId is a number->should 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 = "<Select>"; opt.value = -1; input.options[0] = opt; this.print(fName+": Adding <Select>"); //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="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); 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=="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]; } this.print('Element: '+input); //figure out the element type let eType=input.nodeName.toLowerCase(); this.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"){ this.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; } this.print('Unknown input type: '+type+'. Ignoring.'); } crfVisit.populateTable= function(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=this.getSetup(listName,writeMode); let entry=new Object(); //data snapshot let fQuery=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 "+fQuery.rows.length); if (fQuery.rows.length>0) 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); } } 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); } } //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;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"}); this.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; this.copyToDataset(); } crfVisit.copyToDataset= function(){ let fName='[copyToDataset]: '; let config=this.config; this.print(fName+'['+config.upload.queryId+'/'+config.upload.queries.length+']'); //watch dog + scheduler // let that=this; //watchdog part if (config.upload.queryId==config.upload.queries.length) { this.print(fName+'completing'); let targetStatus=config.formConfig.targetStatus['onDatabaseUpload']; let targetRecipient=config.formConfig.targetRecipient['onDatabaseUpload']; let action=new Object(); action.name='onDatabaseUpload'; let redirect=function(){that.redirect();}; action.cb=function(data){that.sendEmail(data,targetRecipient,redirect,'Form uploaded');} this.updateFlag(targetStatus,action);//Approved return; } //scheduler let queryName=config.upload.queries[config.upload.queryId].queryName; this.print("copyToDataset["+config.upload.queryId+"/"+ config.upload.queries.length+"]: "+queryName); let filters=[LABKEY.Filter.create('crfRef',this.getCRFref())]; let action=function(data){that.afterListData(data);}; this.selectRows('lists',queryName,filters,action,this.getContainer('data')); } crfVisit.afterListData= function(data){ let fName='[afterListData]: '; let config=this.config; let queryName=config.upload.queries[config.upload.queryId].queryName; this.print(fName+" ["+queryName+"/list]: "+data.rows.length+" entries"); config.upload.queries[config.upload.queryId].listData=data; let id=config.upload.participantId; let filters=[LABKEY.Filter.create('crfRef',this.getCRFref()),LABKEY.Filter.create('ParticipantId',id)]; let that=this; let action=function(data){that.afterStudyData(data);}; this.selectRows('study',queryName,filters,action,this.getContainer('data')); } crfVisit.afterStudyData= function(data){ let fName='[afterStudyData]: '; let config=this.config; let queryObj=config.upload.queries[config.upload.queryId]; queryObj.studyData=data; let msg=fName+"["+queryObj.queryName+"/study]: "+data.rows.length+" entries"; this.print(msg); let listRows=queryObj.listData.rows; //skip uploading an empty set if (listRows.length==0){ this.printErr("List "+queryObj.queryName+" empty."); queryObj.queryStatus="DONE"; config.upload.queryId+=1; //back to watchdog this.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]; this.print(fName+"Copying ["+f+"]: "+entry[f]+"/"+entryList[f]); } } this.print(fName+' copying completed'); if (studyRows.length>0) { 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;i<listRows.length;i++){ let entry=listRows[i]; //make sure you have the participantField right // entry[participantField]=config.upload.participantId; entry.crfRef=this.getCRFref(); entry.SequenceNum=this.getCRFref(); entry.SequenceNum=entry.SequenceNum % 1000000000; if (listRows.length>1){ 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;i<recipientCategories.length;i++){ let recipient=recipientCategories[i]; this.print('Checking '+recipient); if (recipient=='crfEditor'){ this.print('Adding :'+formCreator.Email); recipients.push(create(typeTo,formCreator.Email)); if (parentUser==undefined) continue; this.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++){ this.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; this.print('Adding :'+userRows[j].Email); recipients.push(create(typeTo,userRows[j].Email)); break; } } } return recipients; } crfVisit.sendEmail= function(data,recipient='crfEditor',cb=null,subj='Form submitted'){ this.print('sendEmail; recipient: '+recipient); let config=this.config; let that=this; if (!cb) cb=function(){that.redirect();}; let st=config.formConfig.settings; let cvar='sendEmail'; if (cvar in st){ this.print(cvar+' set to '+st[cvar]); if (st[cvar]=='FALSE'){ this.print('Skipping sending emails'); cb(); return; } } if (recipient==null){ this.print('Skipping sending emails w/ no recipients'); cb(); return; } this.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=this.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 }); } crfVisit.hideErr= function(){ let el=this.getElement("errorDiv"); el.style.display="none"; } crfVisit.clearErr= function(){ let el=this.getElement("errorTxt"); el.value=""; } crfVisit.showErr= function(){ let el=this.getElement("errorDiv"); el.style.display="block"; } crfVisit.printErr= function(msg){ this.showErr(); el=this.getElement("errorTxt"); el.style.color="red"; el.value+="\n"+msg; } //************************************************** // crfVisit.onRemoveCRF= function(){ let fName='[onRemoveCRF]'; let config=this.config; config.inputListsIterator=0; this.print(fName+' starting loop'); //let rd=function(data){redirect();}; //let cb=function(){cvInsertRows('lists','crfStatus',[crfStatus],rd,getContainer('data'));}; let that=this; let action=function(){that.redirect();}; let cb=function(){that.removeCrfEntries(action);}; this.removeCRFLoop(cb); } crfVisit.removeCRFLoop= function(cb){ let fName='[removeCRFLoop()]'; let config=this.config; let that=this; let i=config.inputListsIterator; let iMax=config.formConfig.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; // let actionSettings=config.formConfig.actionSettings['onRemoveCRF']; let queryNameDeleteWithParentCrf='NONE'; if (variableList.hasVariable(actionSettings,'removeWithParentCrf')){ queryNameDeleteWithParentCrf=actionSettings['removeWithParentCrf']; } this.print(fName+" ["+i+"/"+iMax+"]"); if (!(i<iMax)){ cb(); return; } //in all but crfEntry, variable is called crfRef let queryName=config.formConfig.inputLists.rows[i].queryName; let idVar="crfRef"; let idValue=config.formConfig.crfEntry['entryId']; //delete also crfEntries where parentCrf is set to crf that we are deleting if (queryNameDeleteWithParentCrf==queryName){ idValue=config.formConfig.crfEntry['parentCrf']; } this.print(fName+" ["+i+"/"+iMax+"] "+queryName+":"+idVar+'/'+idValue); let filters=[LABKEY.Filter.create(idVar,idValue)]; let action=function(data){that.removeListCRF(data,cb);}; let failure=function(errorInfo){that.skipListCRF(errorInfo,cb);}; this.selectRows('lists',queryName,filters,action,this.getContainer('data'),failure); //selectRows.failure=skipListCRF; } crfVisit.removeListCRF= function(data,cb){ let fName="[removeListCRF]"; let config=this.config; let that=this; this.print(fName+" "+data.queryName+": "+data.rows.length); config.inputListsIterator+=1; if (data.rows.length==0){ this.removeCRFLoop(cb); return; } let action=function(data){that.removeCRFLoop(cb)}; this.deleteRows(data.schemaName,data.queryName,data.rows,action,this.getContainer('data')); } crfVisit.skipListCRF= function(errorInfo,cb){ let fName='[skipListCRF]'; this.print(fName+" error in removeCRF: "+errorInfo.exception); let config=this.config; config.inputListsIterator+=1; this.removeCRFLoop(cb); } crfVisit.removeCrfEntries= function(cb){ let queryName="crfEntry"; let idVar="entryId"; let crfRef=this.getCRFref(); let that=this; let filters=[LABKEY.Filter.create('entryId',crfRef)]; let action=function(data){that.deleteAndUpdateCrfStatus(data,null);} this.selectRows('lists',queryName,filters,action,this.getContainer('CRF')); let action1=function(data){that.deleteAndUpdateCrfStatus(data,cb);} let filters1=[LABKEY.Filter.create('parentCrf',crfRef)]; this.selectRows('lists',queryName,filters1,action1,this.getContainer('CRF')); } crfVisit.deleteAndUpdateCrfStatus= function(data,cb){ let fName='[deleteAndUpdateCrfStatus]'; let config=this.config; let that=this; let rows=data.rows; let stack=new Array(); stack.push(cb); for (let i=0;i<rows.length;i++){ //generate crfStatus entry out of crfEntry let crfStatus=this.createCrfStatus(rows[i]); crfStatus.action='onRemoveCRF'; crfStatus.FormStatus=config.formConfig.targetStatus[crfStatus.action]; this.print(fName+' status '+crfStatus.FormStatus); crfStatus.operator=config.formConfig.operator; let k=stack.length-1; let containerPath=this.getContainer('CRF'); stack.push(function(){that.insertRows('lists','crfStatus',[crfStatus],stack[k],containerPath);}); let k1=k+1; stack.push(function(){that.deleteRows('lists','crfEntry',[rows[i]],stack[k1],containerPath);}); } //execute the whole stack let m=stack.length-1; stack[m](); } crfVisit.redirect= function(){ let debug=false; let formUrl="begin"; let params=new Object(); params.name=formUrl; params.pageId="CRF"; //points to crf container let containerPath=this.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); this.print("Redirecting to "+homeURL); if (debug) return; window.location = homeURL; } //master section, entry point from html files crfVisit.generateMasterForm= function(){ let that=this; let action=function(){that.setFormConfig();} this.init(action); } //helper function to set basic parameters on web page //(fields defined in html file) crfVisit.populateBasicData= function(){ let staticData=new Object(); let titles=new Object(); let config=this.config; 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){ this.addStaticData(f,titles[f],staticData[f]); } } crfVisit.addStaticData= function(f,title,value){ let el=this.getElement(f); //populate only if (el!=undefined){ el.innerText=value; return; } //add row to table if element cannot be found let table=this.getElement('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 // crfVisit.generateErrorMsg= function(msg){ let config=this.config; let txt=config.document.createElement('p'); txt.innerText=msg; this.getElement(config.masterForm).appendChild(txt); this.generateButton("submitDiv",'Exit','Exit','redirect'); } crfVisit.getUser= function(id,field){ let config=this.config; 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; } crfVisit.afterConfig= function(){ let fName='[afterConfig]'; let config=this.config; this.print(fName); this.populateBasicData(); //check if user has permission on the form let currentUser=this.getUser(LABKEY.Security.currentUser.id,'currentUser'); let currentSite=config.formConfig.currentSite; let formCreator=this.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; let operatorSites=new Array(); for (let i=0;i<fRows.length;i++){ if (fRows[i].User!=currentUser.UserId) continue; operatorSites.push(fRows[i].Site); } //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){ if ("allowFormReassignment" in config.formConfig.settings){ if (!operatorSites.includes(currentSite.siteNumber)){ let msg='User '+currentUser.DisplayName; msg+=' has no permission for site '+currentSite.siteName; this.generateErrorMsg(msg); return; } let that=this; let action=new Object(); action.name="formReassignement"; action.cb=function(){that.doNothing();} config.formConfig.crfEntry['UserId']=currentUser.UserId; let status=config.formConfig.crfEntry['FormStatus']; this.updateFlag(status,action); } else{ let msg='User '+currentUser.DisplayName; msg+=' has no permission on this form'; this.generateErrorMsg(msg); return; } } } if (operator=='crfMonitor' || operator=='crfSponsor'){ //monitor can look at forms based on his site //find monitor line this.print('operator Site: '+operatorSites.length); if (operatorSites.length==0){ let msg='User '+currentUser.DisplayName; msg+=' is not a '+operator; this.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; 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; 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; //allow for settings to be promoted with each action (and potentially parsed and acted upon) config.formConfig.actionSettings[action]=undefined; let aSet=rows[i].actionSettings; if (aSet){ config.formConfig.actionSettings[action]=variableList.parseVariables(aSet); variableList.printVariables(this,config.formConfig.actionSettings[action]); } } let formStatus=config.formConfig.formStatus; //let functionArray=new Array(); this.print("Generating buttons for formStatus \""+ formStatus+"\""); let allButtonRows=config.formConfig.crfButtons.rows; let buttonRows=new Array(); //specifying role=X in actionSettings will limit button to that role for (let i=0;i<allButtonRows.length;i++){ let action=allButtonRows[i]['action']; //filter on actionSettings let as=config.formConfig.actionSettings[action]; if (variableList.hasVariable(as,'role')){ this.print('Role['+config.formConfig.operator+'/'+as['role']+'] limited for action '+action); //mismatch skips addition of button to buttonRows if (config.formConfig.operator!=as['role']) continue; } buttonRows.push(allButtonRows[i]); } for (let i=0;i<buttonRows.length;i++){ let bt=buttonRows[i]; //if (typeof window[bt.action]==="function"){ this.generateButton("submitDiv",bt.caption,bt.label,bt.action,null); //} //else{ // this.print('No match for function :'+bt.action+ // ' obj: '+window[bt.action]); //} } this.print('Here'); //here we should get data. For now, just initialize objects that will hold data let that=this; let action=function(){that.afterDataLayout();}; this.setDataLayout(action);//callback is afterDataLayout } crfVisit.afterDataLayout= function(){ let that=this; let action=function(){that.afterData();}; //let action=function(){that.doNothing();}; this.setData(action);//callback is afterData } crfVisit.updateRegistration= function(){ let fName="[updateRegistration]"; let config=this.config; this.print(fName); let pM=this.getIdManager(); let idFieldName=participantIdManager.getCrfEntryFieldName(pM,"STUDY"); //have to reload query data let regQueryPars=variableList.parseVariables(config.formConfig.settings['registrationQuery']); let regQuery=regQueryPars['query']; let fQuery=this.getQuerySnapshot(regQuery); if (fQuery.rows.length==0) { this.print(fName+" registration is empty"); return; //registration is empty } let regEntry=fQuery.rows[0]; for (x in regEntry){ this.print(fName+" ["+x+"] "+regEntry[x]); } let studyId=fQuery.rows[0][idFieldName]; if (!studyId) { this.print(fName+" study id not set ("+idFieldName+'/'+studyId+")"); return; //study id not set } //set participantIdManager.setParticipantIdToCrfEntry(pM,studyId,"STUDY"); //this will only update crfEntry in memory, but not on LabKey, //we are counting on updateFlag to follow updateRegistration //update parentCRF as well, here we schedule update of data entry as well if ("parentCrfData" in config.formConfig){ let parentCrfEntry=config.formConfig.parentCrfData.rows[0]; parentCrfEntry[idFieldName]=studyId; let that=this; let action={name:"updateRegistration",cb:function(){that.doNothing();}}; let cb=function(data){that.completeWithFlag(data,action);}; this.modifyRows('update','lists','crfEntry',[parentCrfEntry],cb,this.getContainer('CRF')); } } crfVisit.afterData= function(){ let fName='afterData'; let config=this.config; //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"; this.print(fName+': idMode '+idMode); //add print to config so participantManager can use it let pM=this.getIdManager(); //extend object let that=this; let action=new Object(); action.name='updateCrfEntry'; action.cb=function(){that.doNothing();}; pM.updateCrfEntry=function(){that.updateFlag(config.formConfig.crfEntry['FormStatus'],action);}; 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 participantIdManager.verifyCrfStudyId(pM); //study id should already be set by updateRegistration //verifyRegistration(pM); } if (idModeArray.includes("READONLY")){ pM.readOnly="TRUE"; } let pId=participantIdManager.getParticipantIdFromCrfEntry(pM); if (!pId){ participantIdManager.setEditMode(pM); } else{ let label=pId; if (pM.mode=="STUDY"){ let loc=participantIdManager.getParticipantIdFromCrfEntry(pM,'LOCAL'); label=pId+':'+loc; pM.readOnly="true"; } participantIdManager.setLabelMode(pM,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']]; this.print(fName+" ["+queryName+"]: showFlag: "+entry["showFlag"]); this.print(fName+" ["+queryName+"]: accessMode: "+entry[accessMode]); const nData=this.getQuerySnapshot(queryName).rows.length; this.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 this.generateSection(entry); //generateSection(queryName,entry["title"],entry[accessMode], // additionalData); } } crfVisit.findSetupRow= function(queryName,formId){ let config=this.config; 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!=formId) continue; if (queryName1!=queryName) continue; return e; } return null; } crfVisit.populateSection= function(queryName){ let fName='[populateSection/'+queryName+']'; let config=this.config; this.print(fName); //old setting let formId=config.formId; //new setting formId=config.formConfig.formId; let entry=this.findSetupRow(queryName,formId); //ignore names without associated entry in formSetup if (entry==undefined){ this.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)){ this.print(fName+': no additionalData generated for '+queryName); return; } let additionalData=config.formConfig.additionalData[queryName]; this.print(fName+': using additionalData '+additionalData); if ("isReview" in additionalData){ let action=function(){crfReviewSection.CB();}; crfReviewSection.generateSection(queryName,queryName,action); return; } let accessMode=config.formConfig.operator+'Mode'; let aM=entry[accessMode]; this.print(fName+': accessMode '+aM); if (aM!='GENERATE'){ let writeMode=entry[accessMode]=='EDIT'; this.print(fName+': mode='+writeMode); this.populateTable(queryName,writeMode); return; } //deal with generate // //already available -> shift to READ mode let divTable=queryName+'Table'; let divObj=this.getElement(divTable); let divRev=this.getElement(queryName+'Review'); let divRLi=this.getElement(queryName+'ReviewList'); let divGBu=this.getElement(queryName+'GenerateButton'); this.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=this.getQuerySnapshot(queryName).rows.length; this.print('['+queryName+']: nrows '+nData); if (nData>0){ 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;i<cfgRows.length;i++){ if (cfgRows[i].queryId!=queryId) continue; cfgRow=cfgRows[i]; break; } if (cfgRow==undefined){ this.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; } } //this.print('XcfgRow '+config.formConfig.generateForm[queryName]); // // //check if all required datasets were at least saved this.checkGenerationFields(queryName); } crfVisit.checkGenerationFields= function(queryName){ let fName='[checkGenerationFields]'; let config=this.config; 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 this.print(fName); this.print(fName+' setRecipient: '+mailRecipient); let formId=genForm.Key; this.print(fName+" Checking form w/id "+formId); let selectGenerationRows=this.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=this.getQuerySnapshot(fQueryName); this.print('Checking '+fQueryName+' nrows: '+fQuery.rows.length); if (fQuery.rows.length==0){ this.generateError(queryName,fQueryName); return; } } this.generateMessage(queryName,'Vailidation OK'); this.print('callback: set recipient: '+mailRecipient); let that=this; let cb=function(){that.prepareForm(queryName,formId,mailRecipient);}; this.generateListEntry(formId,queryName,cb); } crfVisit.prepareForm= function(queryName,formId,mailRecipient){ let fName="[prepareForm]"; this.print(fName+' recipient '+mailRecipient); //look for existing registration entry let that=this; let action=function(data){that.generateForm(data,queryName,mailRecipient);}; let formFilter=LABKEY.Filter.create('Form',formId); let parentCrfFilter=LABKEY.Filter.create('parentCrf',this.getCRFref()); let filters=[formFilter,parentCrfFilter]; this.selectRows('lists','crfEntry',filters,action,this.getContainer('data')); } crfVisit.generateError= function(queryName,fQueryName){ let elName=queryName+'GenerateButton'+'_reportField'; let el=this.getElement(elName); el.innerText='Error: '+fQueryName+' was not set'; el.style.color='red'; } crfVisit.generateMessage= function(queryName,msg){ let elName=queryName+'GenerateButton'+'_reportField'; let el=this.getElement(elName); el.innerText=msg; el.style.color='green'; } crfVisit.generateForm= function(data,queryName,mailRecipient){ let fName='[generateForm]'; this.print(fName+' recipient: '+mailRecipient); // const nData=data.rows.length; this.print(fName+' Registration: '+nData+' rows'); let config=this.config; let formRow=config.formConfig.generateForm[queryName]; let formCfg=config.formConfig.generateConfig[queryName]; //we have to generate masterQuery with parentCrf and crfRef //and crfEntry with new entryId and parentCrf equal to crfRef if (nData>0) { 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;i<crfSponsors.length;i++){ //this.print('Checking for site '+crfSponsors[i].Site); if (crfSponsors[i].Site!=site) continue; config.formConfig.sponsorId=crfSponsors[i].User; //this.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; } this.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 crfStatus=this.createCrfStatus(crfEntry); crfStatus.operator=config.formConfig.operator; crfStatus.action='generateForm'; let that=this; let action=function(){that.doNothing();}; let cb=function(data){that.sendEmail(data,mailRecipient,action,formName+' generated');} let containerPath=this.getContainer('data'); let pass=function(data){that.insertRows('lists','crfStatus',[crfStatus],cb,containerPath);}; this.insertRows('lists','crfEntry',[crfEntry],pass,this.getContainer('data')); } crfVisit.generateListEntry= function(formId,queryName,cb){ //check if registration was already generated let config=this.config; 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=this.getQuerySnapshot(queryName).rows.length; if (nData>0) 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;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=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<varRows.length;i++){ let vName=varRows[i].staticVariable; this.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; this.print('site '+siteId); if (siteId==config.formConfig.crfEntry.Site){ config.formConfig.currentSite=sRows[i]; break; } } //config.formConfig.site=data.rows[0]; this.print("Setting site name to "+config.formConfig.currentSite.siteName); //study config.formConfig.studyData=config.formConfig.studyDataAll.rows[0]; this.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.crfManagers=config.formConfig.crfManagersData.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]; this.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; this.print('Setting operator to: '+config.formConfig.operator); config.formConfig.formRows=config.formConfig.formData.rows; //point formId to point to form set in crfEntry config.formConfig.formId=config.formConfig.crfEntry['Form']; //old setting, set from URL in visit.html let formId=config.formId; //new setting, set from crfEntry formId=config.formConfig.formId; let formRows=config.formConfig.formRows; //filter out the current form for (let i=0;i<formRows.length;i++){ if (formRows[i].Key==formId){ config.formConfig.form=formRows[i]; break; } } config.formConfig.formSetupRows=this.selectFormSetupRows(formId); this.print("Number of datasets for form ["+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; this.print("Getting dataset names from "+lookup.queryName); //inputLists should be in configuration container let that=this; let action=function(data){that.afterFormDatasets(data);}; //let action=function(data){that.doNothing();}; this.selectRows(lookup.schemaName,lookup.queryName,[],action,this.getContainer('config')); } crfVisit.afterFormDatasets= function(data){ let fName='[afterFormDatasets]'; this.print(fName+' nrows '+data.rows.length); let config=this.config; 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; this.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; } this.print("List of datasets in form : "); for (f in config.formConfig.fields){ let field=config.formConfig.fields[f]; this.print("\t"+f+" ID: "+field.queryId+' title '+field.title); } this.afterConfig(); } //>>>>>>>>>>>>>>>>>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;i<rowsSetup.length;i++){ let entry=rowsSetup[i]; //skip review rows if (entry['showFlag']=='REVIEW') continue; let queryId=entry['queryName']; let q=qMap[queryId]; queryArray.push(runQuery.makeQuery(dS,'data',q,q,[])); qList[q]=0; this.print(fName+' adding '+q); if (entry['showQuery']!="NONE"){ let sq=entry['showQuery']; queryArray.push(runQuery.makeQuery(dS,'data',sq,sq,[])); qList[sq]=0; this.print(fName+' adding '+sq); } } //always add reviews let q='reviewComments'; queryArray.push(runQuery.makeQuery(dS,'data',q,q,[])); qList[q]=0; let that=this; let action=function(){that.processLayout(cb);}; runQuery.getDataFromQueries(this,queryArray,action); } //this happens after the for loop, so all dataQueries objects are set crfVisit.processLayout= function(cb){ let fName='[processLayout]'; let qList=this.getQueryList(); //for layouts let queryArray=new Array(); let targetObject=this.getLookupObject(); let lookupSet=new Object(); for (let q in qList){ let qobject=this.getQueryLayout(q); this.print(fName+" inspecting layout for "+q+" "+qobject); qobject.fields=qobject.metaData.fields; qobject.title=this.findTitle(q); //check for lookups for (let f in qobject.fields){ //anything else is simple but lookup let field=qobject.fields[f]; if (!("lookup" in field)) continue; let lookup=field.lookup; let qObject=this.getLookup(lookup.queryName); if (qObject) continue; //add to list let qName=lookup.queryName; let qCode=qName+':'+lookup.keyColumn+':'+lookup.displayColumn; let e=runQuery.makeQuery(targetObject,'data',qName,qCode,[]); //adjust minor settings if (lookup.containerPath) e.containerPath=lookup.containerPath; e.schemaName=lookup.schemaName; e.columns=lookup.keyColumn+','+lookup.displayColumn; lookupSet[qCode]=e; this.print(fName+' inserting '+qCode); } } for (let x in lookupSet){ queryArray.push(lookupSet[x]); this.print(fName+' adding '+x); for (let v in lookupSet[x]){ this.print(fName+' value ['+v+'] '+lookupSet[x][v]); } } //this.print(fName+' print '+targetObject.print); let that=this; let action=function(){that.processLookup(cb);}; this.print(fName+' getDataFromQueries'); runQuery.getDataFromQueries(this,queryArray,action); this.print(fName+' getDataFromQueries done'); } crfVisit.processLookup= function(cb){ let fName="[processLookup]"; let obj=this.getLookupObject(); for (let q in obj){ this.print(fName+" "+q); let a=q.split(':'); if (a.length<3) continue; let lookupName=a[0]; let key=a[1]; let val=a[2]; obj[lookupName]=new Object(); this.print(fName+' adding ['+lookupName+'] '+key+'/'+val); let lObject=obj[lookupName]; lObject.LUT=new Array();//key to value lObject.ValToKey=new Array();//value to key lObject.keyColumn=key lObject.displayColumn=val; let qRows=obj[q].rows; for (let i=0;i<qRows.length;i++){ let r=qRows[i]; this.print(fName+' LUT ['+r[key]+'] '+r[val]); lObject.LUT[r[key]]=r[val]; lObject.ValToKey[r[val]]=r[key]; } } cb(); } crfVisit.setData= function(cb){ fName='[setData]'; let crfMatch=this.getCRFref(); let config=this.config; let parentCrf=config.formConfig.crfEntry['parentCrf']; if (parentCrf!=undefined) crfMatch=parentCrf; this.print(fName+' form crf ['+this.getCRFref()+'] matching for crfRef='+crfMatch); let queryArray=new Array(); let targetObject=this.getSnapshotObject(); //collect data and execute callback cb for queries in cb.queryList let qList=this.getQueryList(); for (q in qList){ let filters=[LABKEY.Filter.create("crfRef",crfMatch)]; queryArray.push(runQuery.makeQuery(targetObject,'data',q,q,filters)); } runQuery.getDataFromQueries(this,queryArray,cb); } crfVisit.uploadFile= function(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 this.print('uploadFile: '+inputElement.value+'/'); if (inputElement.type=="text") return; this.print('uploadFile: '+inputElement.files+'/'); this.print('uploadFile: '+inputElement.files.length+'/'); if (inputElement.files.length>0){ let file=inputElement.files[0]; this.print('uploadFile: '+inputElement.value+'/'+file.size); webdav.uploadFile(file,context); } } crfVisit.printForm= function(){ crfPrint.printForm(); }