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();
	ad.filters['crfRef']=this.getCRFref();

   //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;
	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]));	
	}	

	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();
}