Hi Antonio
Work on the xBase data server continues. Code is much better organised on the server end with a TServerQueryClass that will be responsible for gathering error information as well as the communication process. Structre is nice and flexible this way so that multiple queries can be handled in the one round trip. Thus, for example, you can not only retrieve the data needed for a particular object you can also get all the options that will be needed to populate list-boxes etc that may be part of the screen. TServerQuery has done away with the DO CASE and is using an array of function pointers. The current code for TServerQuery is:
Code: Select all | Expand
CLASS TServerQuery
DATA iErrorLevel
DATA aErrors
DATA aResponse
DATA aRequest
METHOD New()
METHOD Request( pClient )
METHOD FlagError( iErrorLevel, iErrorNumber )
ENDCLASS
METHOD New()
::aErrors := Array( 0 )
RETURN self
METHOD Request( pClient ) CLASS TServerQuery
LOCAL cBuffer
LOCAL nBytes
LOCAL cData
LOCAL aOUTPUT := ARRAY( 0 )
LOCAL aSTATUS := ARRAY( 3 )
::iErrorLevel := 0
ASize( ::aErrors, 0 )
::aResponse := Array( 3 )
? "Serving:", INetAddress( pClient )
cData := ""
lReceiving := .T.
INetSetTimeout( pClient, 100 )
DO WHILE lReceiving
cBuffer := Space( 4096 )
nBytes := INetRecv( pClient, @cBuffer )
? "Bytes received:", nBytes
IF nBytes < 1
lReceiving := .F.
ELSE
cData += Left( cBuffer, nBytes )
ENDIF
ENDDO
::aREQUEST := HB_Deserialize( cData )
IF lVerbose
? "Received request number: ", ::aREQUEST[1]
ENDIF
aSTATUS[1] := 0
iSelection := ::aREQUEST[1]
HB_Exec( aCompiled[iSelection], nil )
IF lVerbose
? "Press [Ctl-C] to quit"
ENDIF
aSTATUS[1] := ::iErrorLevel
aSTATUS[2] := 1
aSTATUS[3] := ""
::aRESPONSE[1] := aSTATUS
cData := HB_Serialize( ::aRESPONSE )
INetSend( pClient, cData )
RETURN nil
METHOD FlagError( iErrorLevel, iErrorNumber ) CLASS TServerQuery
::iErrorLevel := MAX( ::iErrorLevel, iErrorLevel )
IF PCount() > 1
AAdd( ::aErrors, iErrorNumber )
ENDIF
RETURN ni
Code for the individual queries is:
[code]
FUNCTION GetPatientListByName()
oPT_LIST_BY_NAME:IndexRead( 2, oQuery:aREQUEST[2][1], { | | UPPER( PT_NMFAMLY + PT_NMGIVEN ) } )
oQuery:aRESPONSE[2] := oPT_LIST_BY_NAME:aLIST
RETURN nil
FUNCTION GetPatientDataByKey()
oPT_BY_KEY:KeyRead( 1, oQuery:aREQUEST[2][1] )
oQuery:aRESPONSE[2] := oPT_BY_KEY:aOUTPUT
RETURN nil
FUNCTION WritePatientData()
oPT_BY_KEY:Write( 1, oQuery:aREQUEST[2], oQuery:aREQUEST[2][1][2] )
RETURN nil
FUNCTION GetPatientFileListByPatientKey()
oPF_LIST_BY_PT_KEY:IndexRead( 2, oQuery:aREQUEST[2][1], { | | PF_PTKEY } )
oQuery:aRESPONSE[2] := oPF_LIST_BY_PT_KEY:aLIST
RETURN nil
FUNCTION GetPatientFileDataByKey()
oPF_BY_KEY:KeyRead( 1, oQuery:aREQUEST[2][1] )
oQuery:aRESPONSE[2] := oPF_BY_KEY:aOUTPUT
oFL_LIST:ReadAll( nil )
oQuery:aRESPONSE[3] := oFL_LIST:aLIST
RETURN nil
FUNCTION GetFileLocationList()
oFL_LIST:ReadAll( nil )
oQuery:aRESPONSE[2] := oFL_LIST:aLIST
RETURN nil
FUNCTION WritePatientFileData()
oPF_BY_KEY:Write( 1, oQuery:aREQUEST[2], oQuery:aREQUEST[2][1][2] )
RETURN nil[code]
Note that aCompiled is set up as follows:
[code] PUBLIC aCompiled := Array( 7 )
aCompiled[1] := ( @GetPatientListByName() )
aCompiled[2] := ( @GetPatientDataByKey() )
aCompiled[3] := ( @WritePatientData() )
aCompiled[4] := ( @GetPatientFileListByPatientKey() )
aCompiled[5] := ( @GetPatientFileDataByKey() )
aCompiled[6] := ( @GetFileLocationList() )
aCompiled[7] := ( @WritePatientFileData() ])[/code]
The other main piece of setting up the queries is
[code]
// Patient by Key
oPT_BY_KEY := TSingleData():New( wa_PT_PATIENT, { { "PT_KEY", "sKey", 0 }, { "PT_NMFAMLY", "sNmFamly", 0 }, { "PT_NMGIVEN", "sNmGiven", 0 }, ;
{ "PT_NMOTHER", "sNmOther", 0 }, { "PT_NMPREV", "sNmPrev", 0 }, { "PT_NMPREF", "sNmPref", 0 }, { "PT_NMTITLE", "sNmTitle", 0 }, ;
{ "PT_DOB", "sDOB", 0 }, { "PT_GENDER", "cGender", 0 }, { "PT_ADLINE1", "sAdLine1", 0 }, { "PT_ADLINE2", "sAdLine2", 0 }, ;
{ "PT_ADSUBRB", "sAdSubrb", 0 }, { "PT_ADSTATE", "sAdState", 0 }, { "PT_ADPCODE", "sAdPCode", 0 }, { "PT_ADCNTRY", "sAdCntry", 0 }, ;
{ "PT_PHHOME", "sPhHome", 0 }, { "PT_PHWORK", "sPhWork", 0 }, { "PT_PHMOB", "sPhMob", 0 }, { "PT_PHFAX", "sPhFax", 0 }, ;
{ "PT_EMAIL", "sEmail", 0 }, { "PT_MEDIC", "sMedic", 0 }, { "PT_MEDPOS", "sMedPos", 0 }, { "PT_VETAFF", "sVetAff", 0 }, ;
{ "PT_ACTIVE", "cActive", 0 }, { "PT_LUBY", "sLUBy", 0 }, { "PT_LUWHEN", "sLUWhen", 0 }, { "PT_LUACTN", "cLUActn", 0 } }, ;
1, "Patient by Key" )
// Patient List by Name
oPT_LIST_BY_NAME := TListData():New( wa_PT_PATIENT, { { "PT_KEY", "sKey", 0 }, { "PT_NMFAMLY", "sNmFamly", 0 }, { "PT_NMGIVEN", "sNmGiven", 0 }, ;
{ "PT_DOB", "dDOB", 0 }, { "PT_GENDER", "cGender", 0 } }, "Patient List by Name" )
// Patient File List by Patient Key
oPF_LIST_BY_PT_KEY := TListData():New( wa_PF_PATIENTFILE, { { "PF_KEY", "aKey", 0 }, { "PF_FLKEY", "aFLKey", 0 }, ;
{ "PF_FLKEY", "aFLName", -2, 2, 1, "FL_NAME" }, { "PF_DTFIRST", "aDtFirst", 0 }, { "PF_DTLAST", "aDtLast", 0 }, ;
{ "PF_CLOSED", "aClosed", 0 } }, "Patient File List by Patient Key")
// Patient File by Key
oPF_BY_KEY := TSingleData():New( wa_PF_PATIENTFILE, { { "PF_KEY", "sKey", 0 }, { "PF_FLKEY", "sFLKey", 0 }, ;
{ "PF_FLKEY", "sFLName", -1, 2, 1, "FL_NAME" }, { "PF_DTFIRST", "sDtFirst", 0 }, { "PF_DTLAST", "sDtLast", 0 }, ;
{ "PF_CLOSED", "cClosed", 0 }, { "PF_ACTIVE", "cActive", 0 }, { "PF_LUBY", "sLUBy", 0 }, { "PF_LUWHEN", "sLUWhen", 0 }, ;
{ "PF_LUACTN", "cLUActn", 0 } }, 3, "Patient File by Key")
// File Location List
oFL_LIST := TListData():New( wa_FL_FILELOCATION, { { "FL_KEY", "aFLKey", 0 }, { "FL_NAME", "aFLName", 0 } }, "File Locations List" )
[/code]
Note that these objects may be used by more than 1 query, eg for reading a record and for updating or inserting it.