API endpoint code sample for BOA.

Announcements about new features or events.
Post Reply
Message
Author
skiman
Posts: 1183
Joined: Thu Jan 28, 2010 1:22 am
Location: Sijsele, Belgium
Contact:

API endpoint code sample for BOA.

#1 Post by skiman »

Hi,

Hereby a sample of an endpoint as you can use it for BOA.

Below are some functions that can be used to start a customer multi tab form.
BOA will call the customers function with the following parameters:

To build an empty grid:
/customers/grid
To fill the grid after entering a search key
/customers?key=abc
Open a grid and fill with data and headers
/customers?key=abc&headers=1
To get all the tabpages with the titles and endpoints
/customers/form
To get all the info for an id, number of the tabpage, and all the labels to display
/customers/id?tab=1&labels=1

For this first test I added the DBF files you will need:
klant with 5000 customers
klantnum.ntx with index on nummer
klantnaa.ntx with index on naam
postnrs with the zipcodes, and with the indexes
postnrs.ntx index on newpost
poststad index on upper(plaats+postnr)
landcode with the countrycodes and indexes.
landcode.ntx with index on code
landcod1.ntx with index on landcode

In the customers function, there is a call to:
APIHEADERS: this will create the headers for the grid
fCustpages(cTabpage,lLabels,@aLabels) which will create the content for the tabpage.
APIblock('klant',cFields) which return the data for the row. cFields is created as a codeblock, and is evaluated.

I also added the following:
webfileopen() to open the files. I don't know how to specify the path.
allfileopen() to check if all files are correctly openend.
allfileclose(cFileopen) closes all files

I also added the source code for sendjson(), so you can see what this does. This also uses convertJSONtoCHAR to get the json object in a string.

Code: Select all

function rest_Customers(cCommand,aPara,cDossier,cTaal,cUser,cCodeVert,nCode)
****************************************************************************
Local aVarList, aVars:={}, cZoek:="" , nNummer := 0
local aJSON:={} , x:=0, rec, nLenZoek:=0 , index:=0 
Local cFileopen:=webfileopen("klant") + webfileopen("postnrs")+ webfileopen("landcode")

Local oClient:=ThreadObject(), bFilter:={|| .T. }
Local oRec , cVar , nPos , oSubRec 
Local cContent := "", cContentType := "" , oJson
Local nFields := 0 , lCorrectie := .F.
Local cField := "" , uValue := nil , nFieldpos , cType
Local aUnknown := {} ,  cIndexNr , over_arr := {} , uHeaders
Local lHeaders := .F. , aHeaders := {} , aOptions := {}
Local  uVar :="" , lOnlyHeaders := .F. , lLabels := .F.
Local amaandArray , nShowmaand := 1 , oOnclick , ctabPage := ""

Static cFields := "" , alabels := {} , cTab := "x" 

select klant
aVarList:=oClient:getVar(nil,VAR_QUERY)
IF Valtype(aVarList) == 'A'
	FOR x := 1 TO Len(aVarList)
		AAdd(aVars,aVarList[x])
	NEXT
ENDIF

if aPara[1] == "v1.0"
	x := 0
	if !empty(aVars)
		cFields := DC_HtmlGetVar(aVars, 'fields', .T.)
		cVar := DC_HtmlGetVar(aVars, 'key', .T.)
		lHeaders := if(var2char(DC_HtmlGetVar(aVars, 'headers', .T.))="1",.T.,.F.)
		lLabels  := if(var2char(DC_HtmlGetVar(aVars, 'labels', .T.))="1",.T.,.F.)
		cIndexNr := DC_HtmlGetVar(aVars, 'index', .T.)			// you can create endpoints which defines the ctive index.
		cTabPage := DC_HtmlGetVar(aVars, 'tab', .T.)			// which tab-page is needed.
	endif
	if cTab <> cTabpage .or. valtype(cFields) = "U"
		cFields := ""
		asize(aLabels,0)
		cTab := cTabpage
	endif
	if len(aPara)>2 .And. !empty(aPara[3])
		uVar := aPara[3]
		if upper(uVar) == "GRID"			// start for the grid. BOA will add this to the request. So you know you don't need to return data.
			lOnlyHeaders := .t.
		elseif upper(uVar) == "FORM"		// start a multi tab form. BOA will the form structure, so you can send the captions and number of pages.
											// each tab is a form, and each form has his own endpoint.
			cTabpage := "0"
		else
			nNummer := val(uVar)
		endif
	else
		if !empty(cVar)
			cZoek:=ConvUtfToCp(upper(cVar))
		endif
	endif
	
	if cTabpage == "0"		// send the info for all the tab-pages of the multi tab form.
		rec:=json():new()
		rec:title := "Address"
		rec:tab := '1'
		rec:endpoint := '/customers/${id}?tab=1'
		rec:pagetype := "form"
		aadd(aheaders,rec)
		
		rec:=json():new()
		rec:title := "Administration"
		rec:tab := '2'
		rec:endpoint := '/customers/${id}?tab=2'
		rec:pagetype := "form"
		aadd(aheaders,rec)
		
		rec:=json():new()
		rec:title := "Extra"
		rec:tab := '3'
		rec:endpoint := '/customers/${id}?tab=3'
		rec:pagetype := "form"
		aadd(aheaders,rec)
		
		rec:=json():new()
		rec:title := "History"
		rec:tab := '4'
		rec:endpoint := '/customers/${id}?tab=4'
		rec:pagetype := "form"
		aadd(aheaders,rec)

		rec:=json():new()
		rec:title := "Bill"
		rec:tab := '5' 
		rec:endpoint := '/customers/${id}?tab=5'
		rec:pagetype := "form"
		aadd(aheaders,rec)	
	
		rec:=json():new()
		rec:title := "Documents"
		rec:tab := '6'
		rec:endpoint := '/customers/${id}?tab=6'
		rec:pagetype := "form"
		aadd(aheaders,rec)		
		
		rec:=json():new()
		rec:title := "Turnover"
		rec:tab := '7'
		rec:endpoint := '/customers/${id}?tab=7'
		rec:pagetype := "form"
		aadd(aheaders,rec)		
		
		rec:=json():new()
		rec:title := "Data"
		rec:tab := '8'
		rec:endpoint := '/customers/${id}?tab=8'
		rec:pagetype := "form"
		aadd(aheaders,rec)	
		
		rec:=json():new()
		rec:file := "klant"
		rec:titlefield := 'naam'
		rec:tabpages := aHeaders
		allfileclose(cFileopen)
		return sendjson(rec)	
		
	endif

	IF lOnlyHeaders		// Send only headers, so this is done.
		APIheaders('klant',@aHeaders,cTaal)
		rec:=json():new()
		rec:file := "klant"
		rec:headers := aHeaders
		allfileclose(cFileopen)
		return sendjson(rec)		
	endif
	do while .T.
		Do CASE 
		case cCommand ="DELETE"
			if !empty(nNummer)    
				klant->(dbsetorder(1))	
				klant->(dbseek(nNummer))
				if !klant->(eof()) .and. klant->(locked())
					// Here you can delete a record.
					klant->(dbrunlock())
				endif
			endif
		case cCommand == "GET"
			if empty(nNummer) .and. empty(cFields) .and. lHeaders
				APIheaders('klant',@aHeaders,cTaal)			// with API headers you can create the headers for different requests. 
			endif		
			if !empty(nNummer) 
				if empty(cFields) .and. cTabPage <> "99"			// if tabpage == 99 , only return the fields for the grid.
					if lLabels
						rec:=json():new()
						rec:display := ""
						rec:fieldname := "id"
						rec:length := 6
						rec:inputtype := 'hidden'
						aadd(aLabels,rec)
					endif
					cFields := fCustpages(cTabpage,lLabels,@aLabels)
				endif
				if nNummer > 0
					klant->(dbsetorder(1))
					klant->(dbseek(nNummer))
					if !klant->(eof())
						oRec := APIblock('klant',cFields)
						aadd(aJson,oRec)
					else
						rec:=json():new()
						rec:type := "ID does not exist"
						rec:id := nNummer
						orec:=json():new()
						oRec:file := "klant"
						oRec:error := rec
						allfileclose(cFileopen)
						return sendjson(oRec)
					endif
				endif
			elseif !empty(cZoek)
				x:=0
				nNummer := 0
				cZoek:=upper(alltrim(strip(cZoek)))
				if left(cZoek,1)=="="
						nNummer := val(substr(cZoek,2))
						klant->(dbsetorder(1))
						klant->(dbseek(nNummer))
						oRec := APIblock('klant',cFields)
						aadd(aJson,oRec)		
				else
					nLenZoek:=len(cZoek)
					klant->(dbsetorder(2))
					klant->(dbseek(cZoek,.T.))
					Do while upper(left(strip(klant->naam),nLenZoek)) = cZoek
						x++
						oRec := APIblock('klant',cFields)
						aadd(aJson,oRec)
						klant->(dbskip(1))
					enddo
				endif
				if lHeaders .and. empty(aHeaders) .and. x > 0
					APIheaders('klant',@aHeaders,cTaal)
				endif
			endif
		case cCommand == "POST"			// create the record, then change command to PUT for update
			klant->(dbsetorder(1))
			set deleted off
			klant->(dbgobottom())
			nNummer := klant->nummer+1			// get next number for the customer
			if klant->(append())
				klant->nummer := nNummer
				klant->(dbrunlock())
			endif
			cCommand := "PUT"
			loop
		case cCommand == "PUT"			// update after creation
			cContent := oClient:HTTPRequest:content
			cContentType := oClient:HTTPRequest:contenttype
			if lower(cContentType) = "application/json"
				oJson := json():new(cContent)
				if empty(nNummer) .and. oJson:hasvar("id")
					nNummer := var2num(oJson:id)
				endif
				nFields := len(oJson:_aJson)		// oJson:_aJson returns an array with the fieldnames and the data
				klant->(dbsetorder(1))
				klant->(dbseek(nNummer))
				if klant->(eof())
					rec:=json():new()
					rec:type := "ID does not exist"
					rec:id := nNummer
					orec:=json():new()
					oRec:file := "klant"
					oRec:error := rec
					allfileclose(cFileopen)
					return sendjson(oRec)				
				endif				
				if klant->(locked())				
					for x = 1 to nFields
						cField := oJson:_aJson[x][1]
						uValue := oJson:_aJson[x][2]
						if (nFieldpos := klant->(fieldpos(cField))) > 0
							cType := upper( valtype(klant->(fieldget(nFieldpos))))
							if cType $ "CM" 		// Character or memo
								if valtype(uValue) = "L"
									uValue:=if(uValue,"J","N")
								elseif empty(uValue)
									uValue := ""
								endif							
								klant->(fieldput(nFieldpos, uValue))
							elseif cType == "N" // Numeric
								if empty(uValue)
									uValue := 0
								endif
								klant->(fieldput(nFieldpos, var2num(uValue)))
							elseif cType == "D" .and. !empty(uValue)	// Date
								if empty(uValue)
									uvalue := "  /  /  "
								endif
								klant->(fieldput(nFieldpos, ctod(uValue)))

							elseif cType == "L"		// Logical
								if empty(uValue)
									uValue := .f.
								endif
								if valtype(uValue) == "L"
									klant->(fieldput(nFieldpos, uValue))
								else
									klant->(fieldput(nFieldpos, if(upper(uValue)$"0NFALSE",.F.,.T.)))
								endif
							endif
						elseif upper(left(cField,2)) <> "ID"
							aadd(aUnknown,cField)
							// cUnknown += if(empty(cUnknown),""," | ") + cField 
						endif
					next
					klant->(dbrunlock())
				endif
				oRec := APIblock('klant',cFields)
				aadd(aJson,oRec)
			endif
		endcase
		exit
	enddo
endif  // end Version 1.0	
rec:=json():new()
rec:file := "klant"
if lHeaders .and. !empty(aheaders)	
	rec:headers := aHeaders
endif
if lLabels .and. !empty(aLabels)
	rec:labels := aLabels
endif
if nNummer >= 0
	rec:data := aJson
endif

allfileclose(cFileopen)

return sendjson(rec)


function fCustpages(cTabPage,lLabels,aLabels)
******************************************
Local rec , cFields, oSubRec , aOptions := {}
do case					
	case cTabpage = "1"
		if lLabels
			rec:=json():new()
			rec:display := "Name / Company:"
			rec:tooltip := "Name of the customer."
			rec:fieldname := 'naam'
			rec:length := 40
			rec:inputtype := 'edit'
			rec:newline := .T.
			rec:labelwidth := 2
			rec:fieldwidth := 4
			aadd(aLabels,rec)

			rec:=json():new()
			rec:display := "Contact:"
			rec:tooltip := "Name of contact person."
			rec:fieldname := 'naam2'
			rec:length := 40
			rec:inputtype := 'edit'
			rec:newline := .F.
			rec:labelwidth := 2
			rec:fieldwidth := 4

			
			aadd(aLabels,rec)	
			
			rec:=json():new()
			rec:display := "Address:"
			rec:tooltip := "Address:"
			rec:fieldname := 'adres'
			rec:length := 40
			rec:inputtype := 'edit'
			rec:newline := .T.
			rec:labelwidth := 2
			rec:fieldwidth := 4
			
			aadd(aLabels,rec)
		endif
	
		cFields := "naam,naam2,adres"
		if lLabels
			rec:=json():new()
			rec:display := "Address 2:"
			rec:tooltip := "Address 2:"
			rec:fieldname := 'adres2'
			rec:length := 40
			rec:inputtype := 'edit'
			rec:newline := .F.
			rec:labelwidth := 2
			rec:fieldwidth := 4
			
			aadd(aLabels,rec)							

			rec:=json():new()
			rec:display := ''
			rec:fieldname := 'idpostnr'
			rec:length := 10
			rec:inputtype := 'hidden'
			oSubRec := json():new()
			oSubrec:endpoint := "/files/postnrs"
			oSubrec:file := "postnrs"
			oSubrec:fieldname := "id"
			rec:data := oSubRec					
			aadd(aLabels,rec)							
		
			rec:=json():new()
			rec:display := ''
			rec:fieldname := 'newpost'
			rec:length := 10
			rec:inputtype := 'hidden'
			oSubRec := json():new()
			oSubrec:endpoint := "/postnrs"
			oSubrec:file := "postnrs"
			oSubrec:fieldname := "newpost"
			rec:data := oSubRec					
			aadd(aLabels,rec)
			
			rec:=json():new()
			rec:display := "Zipcode:"
			rec:tooltip := "Postal code."
			rec:fieldname := 'postnr'
			rec:length := 8
			rec:inputtype := 'search'
			rec:newline := .T.
			rec:labelwidth := 2
			rec:fieldwidth := 2
			rec:grid := "postnrs"
			
			oSubRec := json():new()
			oSubrec:endpoint := "/postnrs"
			oSubrec:file := "postnrs"
			oSubrec:fieldname := "postnr"
			oSubrec:buttons := "select,add,edit,exit"
			rec:data := oSubRec
			aadd(aLabels,rec)
			
			rec:=json():new()
			rec:display := ''
			rec:fieldname := 'idlandcode'
			rec:length := 10
			rec:inputtype := 'hidden'
			oSubRec := json():new()
			oSubrec:endpoint := "/landcodes"
			oSubrec:file := "/landcodes"
			oSubrec:fieldname := "id"
			rec:data := oSubRec					
			aadd(aLabels,rec)
			
			rec:=json():new()
			rec:display := ""   //fWebMessage(cTaal,60)
			rec:tooltip := "Country codes"
			rec:fieldname := 'land'
			rec:length := 3
			rec:inputtype := 'dropdown'
			rec:newline := .F.
			rec:labelwidth := 0
			rec:fieldwidth := 2							
			rec:grid := "landcode"
			oSubRec := json():new()
			oSubrec:endpoint := "/landcodes"
			oSubrec:file := "landcodes"
			oSubrec:fieldname := "code"
			rec:data := oSubRec							
			aadd(aLabels,rec)
		
			rec:=json():new()
			rec:display :=  "Hometown:"
			rec:tooltip := ""
			rec:fieldname := 'postnrs_plaats'
			rec:length := 30
			rec:inputtype := 'noedit'
			rec:newline := .F.
			rec:labelwidth := 2
			rec:fieldwidth := 4
			oSubRec := json():new()
			oSubrec:endpoint := "/postnrs"
			oSubrec:file := "postnrs"
			oSubrec:fieldname := "plaats"
			rec:data := oSubRec							
			aadd(aLabels,rec)
		
			rec:=json():new()
			rec:display := "Phone:"
			rec:tooltip := "Telephone"
			rec:fieldname := 'telefoon'
			rec:length := 16
			rec:inputtype := 'phone'
			rec:newline := .T.
			rec:labelwidth := 2
			rec:fieldwidth := 4							
			aadd(aLabels,rec)
		
			rec:=json():new()
			rec:display := "Fax."
			rec:tooltip := "Fax."
			rec:fieldname := 'telefoon2'
			rec:length := 16
			rec:inputtype := 'phone'
			rec:newline := .F.
			rec:labelwidth := 2
			rec:fieldwidth := 4							
			aadd(aLabels,rec)
		
			rec:=json():new()
			rec:display := "Mobile:"
			rec:tooltip := "Mobile number"
			rec:fieldname := 'gsm'
			rec:length := 16
			rec:inputtype := 'phone'
			rec:newline := .T.
			rec:labelwidth := 2
			rec:fieldwidth := 4						
			aadd(aLabels,rec)	
		

			rec:=json():new()
			rec:display := "E-Mail:"
			rec:tooltip :=  "E-Mail address."
			rec:fieldname := 'email'
			rec:length := 40
			rec:inputtype := 'email'
			rec:newline := .T.
			rec:labelwidth := 2
			rec:fieldwidth := 10							
			aadd(aLabels,rec)						
		
			rec:=json():new()
			rec:display := "VAT:"
			rec:tooltip := ""
			rec:fieldname := 'btw_kode'
			rec:length := 3
			rec:inputtype := 'combobox'
			rec:newline := .T.
			rec:labelwidth := 2
			rec:fieldwidth := 2							
			
			oSubRec := json():new()
			oSubrec:option := "Individual"
			oSubrec:value := "P"
			aadd(aOptions,oSubrec)

			oSubRec := json():new()
			oSubrec:option := "Company"
			oSubrec:value := "B"
			aadd(aOptions,oSubrec)

			oSubRec := json():new()
			oSubrec:option := "School"
			oSubrec:value := "V"
			aadd(aOptions,oSubrec)

			oSubRec := json():new()
			oSubrec:option := "No VAT"
			oSubrec:value := "N"
			aadd(aOptions,oSubrec)
			rec:options := aOptions						 
			aadd(aLabels,rec)						
		
			rec:=json():new()
			rec:display := ""
			rec:fieldname := 'landcode'
			rec:length := 3
			rec:inputtype := 'noedit'
			rec:newline := .F.
			rec:labelwidth := 0
			rec:fieldwidth := 2
			oSubRec := json():new()
			oSubrec:endpoint := "/landcodes"
			oSubrec:file := "landcodes"
			oSubrec:fieldname := "landcode"
			rec:data := oSubRec
			aadd(aLabels,rec)						

			rec:=json():new()
			rec:display := "VAT Nr."
			rec:tooltip := "VAT number of the customer."
			rec:fieldname := 'btw_nr'
			rec:length := 16
			rec:inputtype := 'edit'
			rec:newline := .F.
			rec:labelwidth := 2
			rec:fieldwidth := 4
			aadd(aLabels,rec)
			
			
		endif
		
		cFields += ",adres2,idpostnrs,postnr,newpost,idlandcode,land,postnrs->plaats,telefoon,telefoon2,gsm"
		cFields += ",email,btw_kode,landcode,btw_nr"						
	/* tabpage 2 to 9 not converted yet
	case cTabPage = "2"
		....

endcase
return cFields

function APIblock(cAlias,cFields,lLevel)		// creates the json for the data.
****************************************
Local oRecord := json():new() , aFields := {} , aSubFields := {}
Local cBlock := '' , x , nField := 2 , cFieldname , y , cField  // , bBlock := ""
Local nRecord := 0 , cZoek := "" , nParent := 0 , cType := "" , nFldPos := 0

Local bBlock := ""

default lLevel := .F.
cAlias := upper(cAlias)

// according to the opened file, actions can be done. Some of them can be avoided by using set relation while opening the files

	do case
		case "newpost" $ cFields .and. !cAlias== "POSTNRS"
			postnrs->(dbseek( (cAlias)->newpost))
		case "land" $ cFields .and. !cAlias== "LANDCODE"
			landcode->(dbseek( (cAlias)->land))
	endcase
	// a codeblock with all the data has to be created. This is evaluated and returns the result.
	cBlock := 	'{|rec| rec:id := alltrim(str(recno())),'
	if empty(cFields)
		cFields := (cAlias)->(fieldname(1))
		do while !empty( cFieldname := (cAlias)->(fieldname(nField)) )
			cFields += ','+cFieldname
			nField ++
		enddo
	endif

	aFields := dc_tokenarray(cFields,",")

	for x = 1 to len(aFields)
		cField := aFields[x]
		do case
			// In some cases the has to be an json object in the data. This is the system to have relations.
			// Sample in customers there is a link to the zipcodes.
			// Sample in a stock product there is a relation to productcategories
			// Below some samples to check and to create the data
			case "newpost" == cField .and. !cAlias == "POSTNRS"
				postnrs->(dbseek( (cAlias)->newpost))
				cBlock += 'rec:postnrs:= apiblock("postnrs","postnr,plaats,newpost",.t.),'
			case "land" == cField .and. !cAlias == "LANDCODE"
				landcode->(dbseek( (cAlias)->land))
				cBlock += 'rec:landcodes:= apiblock("landcode","code,land,landcode",.t.),'
		endcase
		endif
		if (nFldPos:=(cAlias)->(fieldpos( cField ))) > 0 .or. "->"$cField .or. "+"$cField
			cField := strtran(cField,"->","_")
			cField := strtran(cField,"+","_")
			cField := strtran(cField,"-","_")
			cField := strtran(cField,"(recno())","id")
			if nFldPos > 0
				cType := (cAlias)->(fieldinfo(nFldPos , FLD_TYPE))
				if cType == "M"
					cBlock += 'rec:'+cField+':=memoeon('+aFields[x]+'),'
				elseif cType == "D"
					cBlock += 'rec:'+cField+':=dtoc('+aFields[x]+'),'
				else
					cBlock += 'rec:'+cField+':='+aFields[x]+','
				endif
			else
				cBlock += 'rec:'+cField+':='+aFields[x]+','			
			endif
		endif
	next

	cBlock += 'rec}' 

	bBlock := &(cBlock)
endif

return (cAlias)->(eval(bBlock,oRecord ))

function APIHeaders(cAlias,aHeaders,cTaal)			// create headers for a grid. 
******************************************
Local oRecord := json():new()  
Local cBlock := '' , x , bBlock , nField := 2 , cFieldname

cAlias := upper(cAlias)
if cAlias == "KLANT"
	// you can create headers as below, or you could read some klant.headers.language.txt with the json in it.
	oRecord:title := 'Customer name:'
	oRecord:data := 'naam'
	oRecord:type := 'string'
	oRecord:width := '25%'
	aadd(aHeaders , oRecord)
	oRecord := json():new() 
	oRecord:title := 'Address:'
	oRecord:data := 'adres'
	oRecord:type := 'string'
	oRecord:width := '20%'
	aadd(aHeaders , oRecord)
	oRecord := json():new() 
	oRecord:title := 'City'
	oRecord:data := 'postnrs->plaats'
	oRecord:type := 'string'
	oRecord:width := '20%'
	aadd(aHeaders , oRecord)
	oRecord := json():new() 
	oRecord:title := 'Cell phone:'
	oRecord:data := 'gsm'
	oRecord:type := 'string'
	oRecord:width := '15%'	
	aadd(aHeaders , oRecord)
	oRecord := json():new() 
	oRecord:title := 'Email'
	oRecord:data := 'email'
	oRecord:type := 'string'
	oRecord:width := '20%'	
	aadd(aHeaders , oRecord)

elseif cAlias == "LEVER"
	oRecord:title := 'Supplier:'
	oRecord:data := 'naam'
	oRecord:type := 'string'
	oRecord:width := '25%'
	aadd(aHeaders , oRecord)
	oRecord := json():new() 
	oRecord:title := 'Address:'
	oRecord:data := 'adres'
	oRecord:type := 'string'
	oRecord:width := '20%'
	aadd(aHeaders , oRecord)
	oRecord := json():new() 
	oRecord:title := 'City:'
	oRecord:data := 'postnrs->plaats'
	oRecord:type := 'string'
	oRecord:width := '15%'
	aadd(aHeaders , oRecord)
	oRecord := json():new() 
	oRecord:title := 'Cell phone:'
	oRecord:data := 'gsm'
	oRecord:type := 'string'
	oRecord:width := '15%'
	aadd(aHeaders , oRecord)
	oRecord := json():new() 
	oRecord:title := 'Email:'
	oRecord:data := 'email'
	oRecord:type := 'string'
	oRecord:width := '25%'
	aadd(aHeaders , oRecord)
	
elseif cAlias == "STOCK"
....
else

endif

return .t.

Function  webfileopen(cFile)
***************************************
local nDelta := 0
cFile := upper(cFile)

// sample for the opening of the files and corresponding indexes.
if cFile == "KLANT" 
  do while nDelta < 10
     use klant alias klant new
     if .not. neterr()
        set index to klantnum,klantnaa
         return cFile+"/"
       else
         nDelta ++
     endif
     sleep(nDelta*3)
  enddo
  return 'error/'
endif

if cFile == "POSTNRS" 
  do while nDelta < 10
     use postnrs alias postnrs new
     if .not. neterr()
        set index to postnrs, poststad
         return cFile+"/"
       else
         nDelta ++
     endif
     sleep(nDelta*3)
  enddo
  return 'error/'
endif

if cFile == "LANDCODE" 
  do while nDelta < 10
     use landcode alias landcode new
     if .not. neterr()
        set index to landcode, landcod1
         return cFile+"/"
       else
         nDelta ++
     endif
     sleep(nDelta*3)
  enddo
  return 'error/'
endif
return ""


function allfileopen(cFiles)
****************************
if !"error" $ lower(cFiles)
    return .T.
  else
    allfileclose(cFiles)
endif
return .F.

function allfileclose(cFiles)
****************************
Local nPos
do while (nPos:=at("/",cFiles)) > 0
	close(substr(cFiles,1,nPos-1))
	cFiles := substr(cFiles,nPos+1)
enddo
return .F.

function SENDJSON(ocJSON,lFlat)  // 
*****************************
    local nErr     := 0
    local o        := threadobject()
    local cContent := if(valtype(ocJson)=="C",ocJson,convertJSONtoCHAR(ocJSON))
	default lFlat := .F.

    o:httpResponse:contenttype := 'application/json'
 
	if len(cContent) < 10000 .or. lFlat
		o:httpResponse:ContentEncoding("identity") 
		o:httpresponse:CompressLevel = 0  
		o:httpResponse:content :=  cContent   
	else
		o:httpResponse:ContentEncoding( "deflate" )  
		o:httpResponse:content :=  xbZCompress( cContent, 4, @nErr, .t. )  
	endif
return .t.
The above is not a copy/paste solution, since it is dependant on the json parser you uses. It should give a enough information to understand how BOA works, to get an idea about the work you have to do, and if further interested to start with a test which you can do for free. You can test BOA without buying anything.

In attachment the files the above sample is refering to.

If any question, you can ask them.
Best regards,

Chris.
www.aboservice.be

Post Reply