Tutorial🔗
This comprehensive tutorial walks through implementing the Taboga Interactive Filter in real Clarion applications, based on the actual demo application included with the library.
🎯 Project Overview🔗
We'll build a complete customer management system with advanced filtering capabilities, demonstrating:
- Customer Browse with basic filtering
- Product Browse with list-box interface
- Inventory History with date/numeric filtering
- Filter Persistence with save/load functionality
1. 🏗️ Application Foundation🔗
📊 Data Structure🔗
! Customer file structure
Customers FILE,DRIVER('TOPSPEED')
KeyCustNumber KEY(CUS:CustNumber),PRIMARY
KeyFullName KEY(CUS:LastName,CUS:FirstName,CUS:MI),DUP
KeyCompany KEY(CUS:Company),DUP
KeyZipCode KEY(CUS:ZipCode),DUP
StateKey KEY(CUS:State),DUP
Record RECORD,PRE(CUS)
CustNumber LONG
Company STRING(20)
FirstName STRING(20)
MI STRING(1)
LastName STRING(25)
Address1 STRING(35)
City STRING(25)
State STRING(2)
ZipCode STRING(10)
END
END
🔧 Application Setup🔗
-
Add Extension to application
! In Application Properties → Extensions [TABOGA] Enables "InteractiveFilter" functionality on the application/DLL -
Configure Multi-DLL (if applicable)
! Project Conditional Compilation Symbols _TIntFilterLinkMode_=>1;_TIntFilterDllMode_=>0
2. 📋 Customer Browse Implementation🔗
🎨 Window Design🔗
Customers PROCEDURE
! Generated browse view
BRW1::View:Browse VIEW(Customers)
PROJECT(CUS:Company)
PROJECT(CUS:FirstName)
PROJECT(CUS:MI)
PROJECT(CUS:LastName)
PROJECT(CUS:Address1)
PROJECT(CUS:City)
PROJECT(CUS:State)
PROJECT(CUS:ZipCode)
END
! Browse queue
Queue:Browse:1 QUEUE
CUS:Company LIKE(CUS:Company)
CUS:FirstName LIKE(CUS:FirstName)
CUS:MI LIKE(CUS:MI)
CUS:LastName LIKE(CUS:LastName)
CUS:Address1 LIKE(CUS:Address1)
CUS:City LIKE(CUS:City)
CUS:State LIKE(CUS:State)
CUS:ZipCode LIKE(CUS:ZipCode)
CUS:CustNumber LIKE(CUS:CustNumber)
Mark BYTE
ViewPosition STRING(1024)
END
! Interactive Filter class
TIF2 CLASS(TInteractiveFilterClass)
BrowseResetSort PROCEDURE(BYTE Force),DERIVED
BrowseSetFilter PROCEDURE(STRING Filter,STRING Id),DERIVED
InitComponents PROCEDURE(),BYTE,PROC,DERIVED
END
🎛️ Window Definition🔗
QuickWindow WINDOW('Browse the Customers File'),AT(,,447,197),RESIZE,CENTER
! Main browse control
LIST,AT(8,30,434,124),USE(?Browse:1),FORMAT('80L(2)|M~Company~@s20@80L(2)|M~First Name~@s20@...'),FROM(Queue:Browse:1)
! Interactive Filter controls (added by template)
BUTTON,AT(5,158,14,14),USE(?TFilterFor),LEFT,ICON('tint_FilterFor.ico'),FLAT
CHECK,AT(19,158,14,14),USE(?TFilterToggle),LEFT,ICON('tint_FilterToggle.ico'),FLAT
! Sort tabs for different views
SHEET,AT(4,4,443,172),USE(?CurrentTab)
TAB('Last Name/First Name'),USE(?Tab:2)
TAB('Company'),USE(?Tab:3)
TAB('Zip Code'),USE(?Tab:4)
TAB('State'),USE(?Tab:5)
END
! Standard browse buttons
BUTTON('&Insert'),AT(283,158,49,14),USE(?Insert:3)
BUTTON('&Change'),AT(336,158,49,14),USE(?Change:3)
BUTTON('&Delete'),AT(389,158,49,14),USE(?Delete:3)
BUTTON('Close'),AT(399,180,49,14),USE(?Close)
END
⚡ Initialization Code🔗
ThisWindow.Init PROCEDURE
ReturnValue BYTE,AUTO
CODE
// ...standard ABC initialization...
! Initialize Interactive Filter
TIF2.InitComponents
?TFilterToggle{PROP:Use} = TIF2.isInteractiveFilterOn
TIF2.InteractiveFilterToggleControl = ?TFilterToggle
DISABLE(?TFilterToggle)
! Setup filtering
TIF2.UpdateQuery(0) ! Auto-populate fields
TIF2.InitPopup ! Build popup menus
TIF2.CaseSensitivity = 0 ! Case insensitive
// ...rest of initialization...
RETURN ReturnValue
🔧 InitComponents Implementation🔗
TIF2.InitComponents PROCEDURE
ReturnValue BYTE,AUTO
CODE
ReturnValue = PARENT.InitComponents()
! Core configuration
SELF.myWindow &= QuickWindow
SELF.CollectionName = 'Customers' ! Filter collection name
SELF.AllowFreeFormQuery = FALSE
SELF.BrowseControl = ?Browse:1
SELF.BrowseView &= BRW1::View:Browse
SELF.BrowseQueueRef &= Queue:Browse:1
! Filter persistence
SELF.AllowQueryPersistance = TRUE
SELF.LoadFilterEvent = 4070 ! Load filter event
SELF.SaveFilterEvent = 4071 ! Save filter event
! UI configuration
SELF.OperatorInterface = 0 ! 0=buttons, 1=list-box
RETURN ReturnValue
3. 🎯 Event Handling🔗
📋 Filter Button Events🔗
ThisWindow.TakeAccepted PROCEDURE
ReturnValue BYTE,AUTO
CODE
// ...existing code...
CASE ACCEPTED()
OF ?TFilterFor
ThisWindow.Update()
x# = TIF2.FilterFor() ! Show filter dialog
TIF2.FromButton = FALSE ! Reset button flag
OF ?TFilterToggle
IF TIF2.isInteractiveFilterOn AND TIF2.GetFilter()
TIF2.ApplyFilter() ! Re-apply filter
ELSE
TIF2.DisableFilter() ! Disable filter
END
SELECT(TIF2.BrowseControl)
END
RETURN PARENT.TakeAccepted()
🔍 Browse Field Events🔗
ThisWindow.TakeFieldEvent PROCEDURE
ReturnValue BYTE,AUTO
CODE
// ...existing code...
CASE FIELD()
OF ?Browse:1
CASE EVENT()
ELSE
! Filter by selection (equals)
IF EVENT() = 4064
TIF2.FilterOnOperator(e_tint_equals)
END
! Filter excluding selection (not equals)
IF EVENT() = 4065
TIF2.FilterOnOperator(e_tint_NotEquals)
END
! Remove filter
IF EVENT() = 4067
TIF2.FilterRemove
END
! Context-sensitive filtering
CASE EVENT()
OF 4068 ! Contains/Less than or equal
CASE(TIF2.CurrentType)
OF 1 ! String (Contains/Does not contain)....
TIF2.FilterOnOperator(e_tint_Contains)
OF 2 ! Numeric (Less than or equal/More than or equal)...'
TIF2.FilterOnOperator(e_tint_LessOrEqualThan)
OF 3 ! Date (On or before/On or after)....'
TIF2.FilterOnOperator(e_tint_LessOrEqualThan)
OF 4 ! Time (On or before/On or after)....'
TIF2.FilterOnOperator(e_tint_LessOrEqualThan)
END
OF 4069 ! Not contains/Greater than or equal
CASE(TIF2.CurrentType)
OF 1 ! String (Contains/Does not contain)....
TIF2.FilterOnOperator(e_tint_NotContains)
OF 2 ! Numeric (Less than or equal/More than or equal)...'
TIF2.FilterOnOperator(e_tint_LargerOrEqualThan)
OF 3 ! Date (On or before/On or after)....'
TIF2.FilterOnOperator(e_tint_LargerOrEqualThan)
OF 4 ! Time (On or before/On or after)....'
TIF2.FilterOnOperator(e_tint_LargerOrEqualThan)
END
END
! Save/Load filter events
IF EVENT() = TIF2.LoadFilterEvent
TInteractiveFilter_LoadFilter(TIF2)
POST(EVENT:NewSelection,?Browse:1)
END
IF EVENT() = TIF2.SaveFilterEvent
TInteractiveFilter_SaveFilter(TIF2)
SELECT(?Browse:1)
END
END
END
RETURN PARENT.TakeFieldEvent()
4. 🎨 Browse Integration🔗
📊 Context Menu Setup🔗
BRW1.SetAlerts PROCEDURE
CODE
! Add filter items to browse popup
SELF.Popup.AddItem(TIF2.FilterBySelectionText,'TFilterSelection')
SELF.Popup.AddItemEvent('TFilterSelection',4064,?Browse:1)
SELF.Popup.SetIcon('TFilterSelection','tint_FilterBySelection.ico')
SELF.Popup.AddItem(TIF2.FilterExcludingSelectionText,'TFilterNotSelection')
SELF.Popup.AddItemEvent('TFilterNotSelection',4065,?Browse:1)
SELF.Popup.SetIcon('TFilterNotSelection','tint_FilterExcludingSelection.ico')
! Dynamic context-sensitive items
SELF.Popup.AddItem('','TFilterExtra1')
SELF.Popup.AddItemEvent('TFilterExtra1',4068,?Browse:1)
SELF.Popup.SetIcon('TFilterExtra1','tint_Empty.ico')
SELF.Popup.AddItem('','TFilterExtra2')
SELF.Popup.AddItemEvent('TFilterExtra2',4069,?Browse:1)
SELF.Popup.SetIcon('TFilterExtra2','tint_Empty.ico')
SELF.Popup.AddItem('-','TFilterExtraSeparator')
TIF2.ExtraPopupItemsAdded = TRUE
! Filter management items
SELF.Popup.AddItemMimic('TFilterFor',?TFilterFor,TIF2.FilterForText)
SELF.Popup.AddItem(TIF2.FilterRemoveText,'TFilterRemove')
SELF.Popup.AddItemEvent('TFilterRemove',4067,?Browse:1)
SELF.Popup.SetIcon('TFilterRemove','tint_FilterRemove.ico')
SELF.Popup.AddItem('-','TFilterSeparator')
PARENT.SetAlerts
🔄 Dynamic Menu Updates🔗
BRW1.TakeNewSelection PROCEDURE
xQueueFieldNo SHORT
xField CSTRING(101)
xTDisable BYTE(1)
CODE
IF ?Browse:1{PROPLIST:MouseDownRow} <> 0 AND ?Browse:1{PROPLIST:MouseDownZone}=LISTZONE:Field
SELF.UpdateBuffer
xQueueFieldNo = ?Browse:1{PROPLIST:FieldNo,?Browse:1{PROPLIST:MouseDownField}}
xField = UPPER(CLIP(WHO(Queue:Browse:1,xQueueFieldNo)))
IF TIF2.SetCurrentInfo(xField,xQueueFieldNo)
! Update menu text with current field/data
SELF.Popup.SetText('TFilterSelection',TIF2.ReplaceMacro(TIF2.EqualsMacro,TIF2.EqualsBlank))
SELF.Popup.SetText('TFilterNotSelection',TIF2.ReplaceMacro(TIF2.NotEqualsMacro,TIF2.NotEqualsBlank))
! Handle blank data
IF TIF2.isCurrentDataBlank
SELF.Popup.DeleteItem('TFilterExtra1')
SELF.Popup.DeleteItem('TFilterExtra2')
TIF2.ExtraPopupItemsAdded = FALSE
ELSE
! Re-add context items if needed
IF TIF2.ExtraPopupItemsAdded = FALSE
SELF.Popup.AddItem('','TFilterExtra1','TFilterNotSelection',0)
SELF.Popup.AddItemEvent('TFilterExtra1',4068,?Browse:1)
SELF.Popup.SetIcon('TFilterExtra1','tint_Empty.ico')
SELF.Popup.AddItem('','TFilterExtra2','TFilterExtra1',0)
SELF.Popup.AddItemEvent('TFilterExtra2',4069,?Browse:1)
SELF.Popup.SetIcon('TFilterExtra2','tint_Empty.ico')
TIF2.ExtraPopupItemsAdded = TRUE
END
! Set context-sensitive text based on data type
SELF.Popup.SetText('TFilterExtra1',CHOOSE(TIF2.CurrentType,|
TIF2.ReplaceMacro(TIF2.ContainsMacro),| ! String
TIF2.ReplaceMacro(TIF2.LessOrEqualMacro),| ! Numeric
TIF2.ReplaceMacro(TIF2.OnOrBeforeMacro))) ! Date
SELF.Popup.SetText('TFilterExtra2',CHOOSE(TIF2.CurrentType,|
TIF2.ReplaceMacro(TIF2.NotContainsMacro),| ! String
TIF2.ReplaceMacro(TIF2.MoreOrEqualMacro),| ! Numeric
TIF2.ReplaceMacro(TIF2.OnOrAfterMacro))) ! Date
END
! Enable/disable menu items
SELF.Popup.SetItemEnable('TFilterSelection',TRUE)
SELF.Popup.SetItemEnable('TFilterNotSelection',TRUE)
SELF.Popup.SetItemEnable('TFilterFor',TRUE)
IF TIF2.isInteractiveFilterOn = TRUE
SELF.Popup.SetItemEnable('TFilterRemove',TRUE)
ELSE
SELF.Popup.SetItemEnable('TFilterRemove',FALSE)
END
SELF.Popup.SetItemEnable('TFilterExtra1',TRUE)
SELF.Popup.SetItemEnable('TFilterExtra2',TRUE)
xTDisable = FALSE
ELSE
xTDisable = TRUE
END
END
! Disable all filter options if no valid field
IF xTDisable
SELF.Popup.SetText('TFilterSelection',TIF2.FilterBySelectionText)
SELF.Popup.SetText('TFilterNotSelection',TIF2.FilterExcludingSelectionText)
SELF.Popup.SetText('TFilterFor',TIF2.FilterForText)
SELF.Popup.SetText('TFilterRemove',TIF2.FilterRemoveText)
SELF.Popup.SetText('TFilterExtra1',TIF2.FilterByContainsText)
SELF.Popup.SetText('TFilterExtra2',TIF2.FilterByNotContainsText)
SELF.Popup.SetItemEnable('TFilterSelection',FALSE)
SELF.Popup.SetItemEnable('TFilterNotSelection',FALSE)
SELF.Popup.SetItemEnable('TFilterFor',FALSE)
SELF.Popup.SetItemEnable('TFilterRemove',FALSE)
SELF.Popup.SetItemEnable('TFilterExtra1',FALSE)
SELF.Popup.SetItemEnable('TFilterExtra2',FALSE)
END
PARENT.TakeNewSelection
5. 📊 Advanced Configuration🔗
🎨 Products Browse with List Interface🔗
For the Products browse, we'll use the list-box operator interface:
TIF11.InitComponents PROCEDURE
ReturnValue BYTE,AUTO
CODE
ReturnValue = PARENT.InitComponents()
SELF.myWindow &= QuickWindow
SELF.CollectionName = 'Products'
SELF.OperatorInterface = 1 ! Use list-box interface
SELF.InteractiveFilterResultFEQ = ?TFilter ! Display current filter
SELF.AllowFreeFormQuery = FALSE
RETURN ReturnValue
📅 Date/Time Filtering🔗
For the Inventory History browse with date/numeric data:
TIF7.InitComponents PROCEDURE
CODE
// ...existing code...
SELF.CollectionName = 'InvHist'
SELF.AllowFreeFormQuery = FALSE ! Disable free-form
SELF.OperatorInterface = 0 ! Use button interface
RETURN ReturnValue
6. 💾 Filter Persistence🔗
🔄 Save Filter Implementation🔗
TInteractiveFilter_SaveFilter PROCEDURE(* TInteractiveFilterClass pIFObj,<STRING pFilterName>)
xSavedFilterName LIKE(_TF:SavedFilterName)
i LONG
j LONG
FilesOpened BYTE(0)
Window WINDOW('Save Filter'),AT(,,269,38),GRAY,MDI
PROMPT('Filter Name:'),AT(3,6),USE(?Prompt1)
ENTRY(@s100),AT(49,6,212,11),USE(xSavedFilterName)
BUTTON('&OK'),AT(181,21,35,14),USE(?OkButton),DEFAULT
BUTTON('&Cancel'),AT(226,21,36,14),USE(?CancelButton)
END
CODE
! Get filter name from user if not provided
IF NOT OMITTED(2)
xSavedFilterName = CLIP(pFilterName)
ELSE
xSavedFilterName = ''
END
! Show dialog to get filter name if needed
IF NOT xSavedFilterName
OPEN(Window)
ACCEPT
CASE FIELD()
OF ?OkButton
POST(EVENT:CloseWindow)
OF ?CancelButton
xSavedFilterName = ''
POST(EVENT:CloseWindow)
END
END
CLOSE(Window)
IF NOT xSavedFilterName
RETURN TRUE ! User cancelled
END
END
! Save to database
DO OpenFiles
CLEAR(_TFilters)
_TF:FilterCollectionName = pIFObj.CollectionName
_TF:SavedFilterName = xSavedFilterName
GET(_TFilters,_TF:byFilterCollectionName)
IF ERRORCODE()
Access:_TFilters.PrimeRecord(TRUE)
Access:_TFilters.Insert
ELSE
DO DeleteFilterRows ! Clear existing rows
END
! Save individual filter components
j = RECORDS(pIFObj.FilterRows)
LOOP i = 1 TO j
GET(pIFObj.FilterRows,i)
_TFRow:SavedFilterID = _TF:SavedFilterID
_TFRow:ID = i
_TFRow:Filter = pIFObj.FilterRows.Filter
_TFRow:IsManual = pIFObj.FilterRows.IsManual
_TFRow:FieldName = pIFObj.FilterRows.FieldName
_TFRow:Picture = pIFObj.FilterRows.Picture
_TFRow:CaseSensitivity = pIFObj.FilterRows.CaseSensitivity
_TFRow:Operator = pIFObj.FilterRows.Operator
_TFRow:ConditionLow = pIFObj.FilterRows.ConditionLow
_TFRow:ConditionHigh = pIFObj.FilterRows.ConditionHigh
_TFRow:JoinOp = pIFObj.FilterRows.JoinOp
_TFRow:NewGroup = pIFObj.FilterRows.NewGroup
_TFRow:NewGroupJoinOp = pIFObj.FilterRows.NewGroupJoinOp
ADD(_TFilterRows)
END
DO CloseFiles
RETURN FALSE
OpenFiles ROUTINE
Access:_TFilters.Open
Access:_TFilters.UseFile
Access:_TFilterRows.Open
Access:_TFilterRows.UseFile
FilesOpened = True
CloseFiles ROUTINE
IF FilesOpened
Access:_TFilters.Close
Access:_TFilterRows.Close
FilesOpened = False
END
DeleteFilterRows ROUTINE
! Delete existing filter rows for this saved filter
// ...implementation...
📂 Load Filter Implementation🔗
TInteractiveFilter_LoadFilter PROCEDURE(* TInteractiveFilterClass pIFObj)
! Browse and load saved filters
v_TFilterRows VIEW(_TFilterRows),ORDER('_TFRow:SavedFilterID,_TFRow:ID'),FILTER('_TFRow:SavedFilterID=_TF:SavedFilterID')
i LONG
xAppendFilterAction STRING(5)
CODE
! Show browser for saved filters in this collection
_TF:FilterCollectionName = pIFObj.CollectionName
! User selects filter and action (Replace/AND/OR)
! xAppendFilterAction contains: '', 'AND', or 'OR'
! Load and apply selected filter
i = 0
OPEN(v_TFilterRows)
SET(v_TFilterRows)
LOOP
NEXT(v_TFilterRows)
IF ERRORCODE() THEN BREAK.
i += 1
! Copy filter row data
pIFObj.FilterRows.Filter = _TFRow:Filter
pIFObj.FilterRows.IsManual = _TFRow:IsManual
pIFObj.FilterRows.FieldName = _TFRow:FieldName
pIFObj.FilterRows.Picture = _TFRow:Picture
pIFObj.FilterRows.CaseSensitivity = _TFRow:CaseSensitivity
pIFObj.FilterRows.Operator = _TFRow:Operator
pIFObj.FilterRows.ConditionLow = _TFRow:ConditionLow
pIFObj.FilterRows.ConditionHigh = _TFRow:ConditionHigh
pIFObj.FilterRows.JoinOp = _TFRow:JoinOp
pIFObj.FilterRows.NewGroup = _TFRow:NewGroup
pIFObj.FilterRows.NewGroupJoinOp = _TFRow:NewGroupJoinOp
! Handle filter combination for first row
IF i = 1
IF xAppendFilterAction = 'AND'
pIFObj.FilterRows.NewGroupJoinOp = 'AND'
pIFObj.FilterRows.NewGroup = TRUE
ELSIF xAppendFilterAction = 'OR'
pIFObj.FilterRows.NewGroupJoinOp = 'OR'
pIFObj.FilterRows.NewGroup = TRUE
END
END
ADD(pIFObj.FilterRows)
END
CLOSE(v_TFilterRows)
! Apply the loaded filter
pIFObj.ApplyFilter
7. 🎯 Best Practices🔗
✅ Performance Optimization🔗
-
Index Usage
! Ensure filtered fields are properly indexed CUS:StateKey KEY(CUS:State),DUP,NOCASE,OPT -
Case Sensitivity
! For SQL drivers, use case sensitive (generates better queries) SELF.CaseSensitivity = 1 ! SQL SELF.CaseSensitivity = 0 ! ISAM -
Field Selection
! Only include fields users will actually filter on SELF.AddItemOverride('CUS:CustNumber','',TRUE) ! Skip internal IDs
🎨 User Experience🔗
-
Visual Feedback
! Always show filter status SELF.InteractiveFilterResultFEQ = ?FilterDisplay SELF.DisplayFilterOnToggleTip = TRUE -
Consistent Naming
! Use descriptive collection names SELF.CollectionName = 'CustomerBrowse' ! Not just 'Customers' -
Icon Consistency
! Include all required icons in your project #PROJECT('tint_FilterFor.ico') #PROJECT('tint_FilterToggle.ico') // ...etc
🚀 Complete Working Example🔗
Here's the complete customer browse implementation:
Customers PROCEDURE
! All declarations from above...
CODE
GlobalResponse = ThisWindow.Run()
ThisWindow.Init PROCEDURE
ReturnValue BYTE,AUTO
CODE
// ...standard initialization...
! Interactive Filter setup
TIF2.InitComponents
?TFilterToggle{PROP:Use} = TIF2.isInteractiveFilterOn
TIF2.InteractiveFilterToggleControl = ?TFilterToggle
DISABLE(?TFilterToggle)
TIF2.UpdateQuery(0)
TIF2.InitPopup
TIF2.CaseSensitivity = 0
// ...rest of initialization...
RETURN ReturnValue
! All event handling methods from above...
📖 Next Steps🔗
- Template Guide - Advanced template configuration
- API Reference - Complete method reference
- Examples - More implementation examples
You now have a complete understanding of implementing the Taboga Interactive Filter!