gs
2-Jul-2008, 10:56 AM
Hi,
Only recently I finally did implement David Martinko's AutoComplete
feature in a project. Very neat!
For *numeric* fields however the Search procedure appeared to
need a bit of extra work, as indeed the auctor intellectualis
himself noted.
The attached replacement for procedure NUMERIC_SEARCH seems to work,
for me at least.
Things could be further improved, I'm sure.
For instance, currently rows are always displayed in Ascending
order, irrespective of whether the index-segment involved has
an Up- or Down-direction. Amending that would require a different
seeding-loop, I guess.
Greetings,
Gerard.
Struct tNumeric_Search_Value
Number nValue
RowID riRow
End_Struct
// Class cLwDbAutoCompleteForm: replace by your own autocomplete-class
Use cLwDbAutoCompleteForm.pkg
Class cLwDbNumericAutoCompleteForm is a cLwDbAutoCompleteForm
Function FoundValueMatchesEnteredValue Integer iFile Integer iField Integer iCheckLen Number nCheckVal Returns Boolean
Boolean bMatch
Number nValue
Get_Field_Value iFile iField to nValue
Move (Left(nValue, iCheckLen) = nCheckVal) to bMatch
Function_Return bMatch
End_Function
Function NumberCompare tNumeric_Search_Value nsvNumber tNumeric_Search_Value nsvSearchNumber Returns Integer
Integer iRetVal
If (nsvNumber.nValue > nsvSearchNumber.nValue) ;
Function_Return (GT)
If (nsvNumber.nValue < nsvSearchNumber.nValue) ;
Function_Return (LT)
Function_Return (EQ)
End_Function
// Replacement for Procedure NUMERIC_SEARCH of AutoComplete_Mixin.Pkg
// Original procedure fails e.g. with indexes where the entry-field
// does not represent the first segment of the current index.
Procedure Numeric_Search Handle hoObj Handle hoList Number nEnteredValue
Boolean bOK bStop bMatch bNextSeed bIndexIsDescending bSearchFieldIsFirstInIndex
Handle hoServer
Integer i iLen iMaxLen iPrecision
Integer iSeedFactor iMaxSeedRounds
Integer iFindmode_FIRST iFindmode_NEXT iFindCount iMaxTotalFinds iValidCount iMaxListRows
Integer hFile hField iIdx iSegDirection iIdxFirstSeg
Number nValue nCurrentSeed
RowID riFirstOfCurrentRound riFirstOfLastRound
Get Integer_Value of oCol_Server item 0 to hoServer
Get Integer_Value of oCol_File item 0 to hFile
Get Integer_Value of oCol_Field item 0 to hField
Get Ordering of hoServer to iIdx
If (iIdx <=0) ; // No ordering given ==> use main index.
Get File_Field_Index of hoServer hFile hField to iIdx
If (iIdx <=0) ; // use autocomplete_index
Get piList_Index to iIdx
Integer iSegment iIdxSegmentCount iIdxField
Get_Attribute DF_INDEX_NUMBER_SEGMENTS of hFile iIdx to iIdxSegmentCount
For i from 1 to iIdxSegmentCount
Get_Attribute DF_INDEX_SEGMENT_FIELD of hFile iIdx i to iIdxField
If (iIdxField = hField) Move i to iSegment
Until (iSegment > 0)
If (not(iSegment)) Begin
// Field is no part of current index. Should never happen.
Procedure_Return
End
Get_Attribute DF_INDEX_SEGMENT_DIRECTION of hFile iIdx iSegment to iSegDirection
If (iSegDirection = DF_DESCENDING) Begin
Move (LE) to iFindmode_FIRST
Move (LT) to iFindmode_NEXT
End
Else Begin
Move (GE) to iFindmode_FIRST
Move (GT) to iFindmode_NEXT
End
Get_Attribute DF_INDEX_SEGMENT_FIELD of hFile iIdx 1 to iIdxFirstSeg
Move (iIdxFirstSeg = hField) to bSearchFieldIsFirstInIndex
If (not(bSearchFieldIsFirstInIndex)) Begin
// Define array for intermediate storage of search-results.
tNumeric_Search_Value[] narValues
End
// Maximize total number of finds, to avoid a hang-alike situations.
Move 250 to iMaxTotalFinds
// Maximize display-rows
Get piMax_Finds of hoObj to iMaxListRows
If (iMaxListRows <=0) Begin
Move 99 to iMaxListRows
End
// Get the length of the entry, this will be used to compare all finds
Move (Length(nEnteredValue)) to iLen
Get_Attribute DF_FIELD_LENGTH of hFile hField to iMaxLen
Get_Attribute DF_FIELD_PRECISION of hFile hField to iPrecision
Subtract iPrecision from iMaxLen
// Check whether current field represents the FIRST segment of the current index.
// If not, seeding makes no sense.
If (bSearchFieldIsFirstInIndex) Move (iMaxLen - iLen) to iMaxSeedRounds
Else Move 1 to iMaxSeedRounds
Clear hFile
For iSeedFactor from 0 to (iMaxSeedRounds -1)
Move ((10 ^ iSeedFactor) * nEnteredValue) to nCurrentSeed
If (bSearchFieldIsFirstInIndex) Begin
// If seeding-field is FIRST segment of the index: initialise buffer (indexjump).
// Send FillIndexForFindFirst of hoServer hFile iIdx iFindmode_FIRST // see VDF Help
Set_Field_Value hFile hField to nCurrentSeed
End
Send Request_Read of hoServer iFindmode_FIRST hFile iIdx
// Check RowID: must be different from first of last seeding-round.
Move (GetRowID(hFile)) to riFirstOfCurrentRound
If (not(IsSameRowID(riFirstOfCurrentRound, riFirstOfLastRound))) Begin
Move riFirstOfCurrentRound to riFirstOfLastRound
Repeat
Move (FoundValueMatchesEnteredValue(Self, hFile, hField, iLen, nEnteredValue)) to bMatch
If (bMatch) Begin
Get_Field_Value hFile hField to nValue
If (bSearchFieldIsFirstInIndex) Begin
Send Add_Item of hoList msg_None nValue
// Fill the other columns
Send FillOtherItems hoObj hoList
End
Else Begin
// in case current entry-field is NOT the first segment in the
// index: remember RowID; do sorting & listing AFTERWARDS.
Move nValue to narValues[iValidCount].nValue
Move (GetRowID(hFile)) to narValues[iValidCount].riRow
End
Increment iValidCount
Move (iValidCount >= iMaxListRows) to bStop
End
If (not(bStop)) Begin
Increment iFindCount
Move (iFindCount >= iMaxTotalFinds) to bStop
If (not(bStop)) Begin
If (bSearchFieldIsFirstInIndex) ;
Move (not(bMatch)) to bNextSeed
If (not(bNextSeed)) Begin
Send Request_Read of hoServer iFindmode_NEXT hFile iIdx
Move (not(Found)) to bStop
End
End
End
Until (bStop or bNextSeed)
End
Until (bStop)
If (not(bSearchFieldIsFirstInIndex)) Begin
// Sort array of found values ..
Move (SortArray(narValues, Self, get_NumberCompare)) to narValues
// .. and copy to autocomplete-list.
Integer iArrayCount
Move (SizeOfArray(narValues)) to iArrayCount
For i from 0 to (iArrayCount-1)
Send Add_Item of hoList msg_None narValues[i].nValue
// Refind record
Send Clear of hFile
Move (FindByRowId(hFile, narValues[i].riRow)) to bOK
If (bOK) Begin
// Fill the other columns
Send FillOtherItems hoObj hoList
End
Loop
End
// Check list if we found any matches.
If ((Item_Count(hoList)) > 0) Begin
Handle hoMain
Get phoListParent to hoMain
Send Resize_List of ghoAutocomplete
Send Add_Focus of ghoAutocomplete hoMain
End
Else Send Kill_AutoComplete
// Re-Synchronize the global buffer with the DD.
Send Refind_records of hoServer
End_Procedure // Numeric_Search
Procedure FillOtherItems Handle hoObj Handle hoList
Integer iItem hCol_File hCol_Field
String sValue
For iItem from 1 to (Item_Count(oCol_Server(hoObj)) - 1)
Get Integer_Value of oCol_File item iItem to hCol_File
Get Integer_Value of oCol_Field item iItem to hCol_Field
If (hCol_File) Get_Field_Value hCol_File hCol_Field to sValue
Else If (hCol_Field) Get hCol_Field to sValue
Send Add_Item of hoList msg_None sValue
Loop
End_Procedure
End_Class
Only recently I finally did implement David Martinko's AutoComplete
feature in a project. Very neat!
For *numeric* fields however the Search procedure appeared to
need a bit of extra work, as indeed the auctor intellectualis
himself noted.
The attached replacement for procedure NUMERIC_SEARCH seems to work,
for me at least.
Things could be further improved, I'm sure.
For instance, currently rows are always displayed in Ascending
order, irrespective of whether the index-segment involved has
an Up- or Down-direction. Amending that would require a different
seeding-loop, I guess.
Greetings,
Gerard.
Struct tNumeric_Search_Value
Number nValue
RowID riRow
End_Struct
// Class cLwDbAutoCompleteForm: replace by your own autocomplete-class
Use cLwDbAutoCompleteForm.pkg
Class cLwDbNumericAutoCompleteForm is a cLwDbAutoCompleteForm
Function FoundValueMatchesEnteredValue Integer iFile Integer iField Integer iCheckLen Number nCheckVal Returns Boolean
Boolean bMatch
Number nValue
Get_Field_Value iFile iField to nValue
Move (Left(nValue, iCheckLen) = nCheckVal) to bMatch
Function_Return bMatch
End_Function
Function NumberCompare tNumeric_Search_Value nsvNumber tNumeric_Search_Value nsvSearchNumber Returns Integer
Integer iRetVal
If (nsvNumber.nValue > nsvSearchNumber.nValue) ;
Function_Return (GT)
If (nsvNumber.nValue < nsvSearchNumber.nValue) ;
Function_Return (LT)
Function_Return (EQ)
End_Function
// Replacement for Procedure NUMERIC_SEARCH of AutoComplete_Mixin.Pkg
// Original procedure fails e.g. with indexes where the entry-field
// does not represent the first segment of the current index.
Procedure Numeric_Search Handle hoObj Handle hoList Number nEnteredValue
Boolean bOK bStop bMatch bNextSeed bIndexIsDescending bSearchFieldIsFirstInIndex
Handle hoServer
Integer i iLen iMaxLen iPrecision
Integer iSeedFactor iMaxSeedRounds
Integer iFindmode_FIRST iFindmode_NEXT iFindCount iMaxTotalFinds iValidCount iMaxListRows
Integer hFile hField iIdx iSegDirection iIdxFirstSeg
Number nValue nCurrentSeed
RowID riFirstOfCurrentRound riFirstOfLastRound
Get Integer_Value of oCol_Server item 0 to hoServer
Get Integer_Value of oCol_File item 0 to hFile
Get Integer_Value of oCol_Field item 0 to hField
Get Ordering of hoServer to iIdx
If (iIdx <=0) ; // No ordering given ==> use main index.
Get File_Field_Index of hoServer hFile hField to iIdx
If (iIdx <=0) ; // use autocomplete_index
Get piList_Index to iIdx
Integer iSegment iIdxSegmentCount iIdxField
Get_Attribute DF_INDEX_NUMBER_SEGMENTS of hFile iIdx to iIdxSegmentCount
For i from 1 to iIdxSegmentCount
Get_Attribute DF_INDEX_SEGMENT_FIELD of hFile iIdx i to iIdxField
If (iIdxField = hField) Move i to iSegment
Until (iSegment > 0)
If (not(iSegment)) Begin
// Field is no part of current index. Should never happen.
Procedure_Return
End
Get_Attribute DF_INDEX_SEGMENT_DIRECTION of hFile iIdx iSegment to iSegDirection
If (iSegDirection = DF_DESCENDING) Begin
Move (LE) to iFindmode_FIRST
Move (LT) to iFindmode_NEXT
End
Else Begin
Move (GE) to iFindmode_FIRST
Move (GT) to iFindmode_NEXT
End
Get_Attribute DF_INDEX_SEGMENT_FIELD of hFile iIdx 1 to iIdxFirstSeg
Move (iIdxFirstSeg = hField) to bSearchFieldIsFirstInIndex
If (not(bSearchFieldIsFirstInIndex)) Begin
// Define array for intermediate storage of search-results.
tNumeric_Search_Value[] narValues
End
// Maximize total number of finds, to avoid a hang-alike situations.
Move 250 to iMaxTotalFinds
// Maximize display-rows
Get piMax_Finds of hoObj to iMaxListRows
If (iMaxListRows <=0) Begin
Move 99 to iMaxListRows
End
// Get the length of the entry, this will be used to compare all finds
Move (Length(nEnteredValue)) to iLen
Get_Attribute DF_FIELD_LENGTH of hFile hField to iMaxLen
Get_Attribute DF_FIELD_PRECISION of hFile hField to iPrecision
Subtract iPrecision from iMaxLen
// Check whether current field represents the FIRST segment of the current index.
// If not, seeding makes no sense.
If (bSearchFieldIsFirstInIndex) Move (iMaxLen - iLen) to iMaxSeedRounds
Else Move 1 to iMaxSeedRounds
Clear hFile
For iSeedFactor from 0 to (iMaxSeedRounds -1)
Move ((10 ^ iSeedFactor) * nEnteredValue) to nCurrentSeed
If (bSearchFieldIsFirstInIndex) Begin
// If seeding-field is FIRST segment of the index: initialise buffer (indexjump).
// Send FillIndexForFindFirst of hoServer hFile iIdx iFindmode_FIRST // see VDF Help
Set_Field_Value hFile hField to nCurrentSeed
End
Send Request_Read of hoServer iFindmode_FIRST hFile iIdx
// Check RowID: must be different from first of last seeding-round.
Move (GetRowID(hFile)) to riFirstOfCurrentRound
If (not(IsSameRowID(riFirstOfCurrentRound, riFirstOfLastRound))) Begin
Move riFirstOfCurrentRound to riFirstOfLastRound
Repeat
Move (FoundValueMatchesEnteredValue(Self, hFile, hField, iLen, nEnteredValue)) to bMatch
If (bMatch) Begin
Get_Field_Value hFile hField to nValue
If (bSearchFieldIsFirstInIndex) Begin
Send Add_Item of hoList msg_None nValue
// Fill the other columns
Send FillOtherItems hoObj hoList
End
Else Begin
// in case current entry-field is NOT the first segment in the
// index: remember RowID; do sorting & listing AFTERWARDS.
Move nValue to narValues[iValidCount].nValue
Move (GetRowID(hFile)) to narValues[iValidCount].riRow
End
Increment iValidCount
Move (iValidCount >= iMaxListRows) to bStop
End
If (not(bStop)) Begin
Increment iFindCount
Move (iFindCount >= iMaxTotalFinds) to bStop
If (not(bStop)) Begin
If (bSearchFieldIsFirstInIndex) ;
Move (not(bMatch)) to bNextSeed
If (not(bNextSeed)) Begin
Send Request_Read of hoServer iFindmode_NEXT hFile iIdx
Move (not(Found)) to bStop
End
End
End
Until (bStop or bNextSeed)
End
Until (bStop)
If (not(bSearchFieldIsFirstInIndex)) Begin
// Sort array of found values ..
Move (SortArray(narValues, Self, get_NumberCompare)) to narValues
// .. and copy to autocomplete-list.
Integer iArrayCount
Move (SizeOfArray(narValues)) to iArrayCount
For i from 0 to (iArrayCount-1)
Send Add_Item of hoList msg_None narValues[i].nValue
// Refind record
Send Clear of hFile
Move (FindByRowId(hFile, narValues[i].riRow)) to bOK
If (bOK) Begin
// Fill the other columns
Send FillOtherItems hoObj hoList
End
Loop
End
// Check list if we found any matches.
If ((Item_Count(hoList)) > 0) Begin
Handle hoMain
Get phoListParent to hoMain
Send Resize_List of ghoAutocomplete
Send Add_Focus of ghoAutocomplete hoMain
End
Else Send Kill_AutoComplete
// Re-Synchronize the global buffer with the DD.
Send Refind_records of hoServer
End_Procedure // Numeric_Search
Procedure FillOtherItems Handle hoObj Handle hoList
Integer iItem hCol_File hCol_Field
String sValue
For iItem from 1 to (Item_Count(oCol_Server(hoObj)) - 1)
Get Integer_Value of oCol_File item iItem to hCol_File
Get Integer_Value of oCol_Field item iItem to hCol_Field
If (hCol_File) Get_Field_Value hCol_File hCol_Field to sValue
Else If (hCol_Field) Get hCol_Field to sValue
Send Add_Item of hoList msg_None sValue
Loop
End_Procedure
End_Class