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.
In attachment the files the above sample is refering to.
If any question, you can ask them.