Skip to content

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🔗

  1. Add Extension to application

    ! In Application Properties → Extensions
    [TABOGA] Enables "InteractiveFilter" functionality on the application/DLL
    

  2. 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🔗

  1. Index Usage

    ! Ensure filtered fields are properly indexed
    CUS:StateKey         KEY(CUS:State),DUP,NOCASE,OPT
    

  2. Case Sensitivity

    ! For SQL drivers, use case sensitive (generates better queries)
    SELF.CaseSensitivity = 1  ! SQL
    SELF.CaseSensitivity = 0  ! ISAM
    

  3. Field Selection

    ! Only include fields users will actually filter on
    SELF.AddItemOverride('CUS:CustNumber','',TRUE)  ! Skip internal IDs
    

🎨 User Experience🔗

  1. Visual Feedback

    ! Always show filter status
    SELF.InteractiveFilterResultFEQ = ?FilterDisplay
    SELF.DisplayFilterOnToggleTip = TRUE
    

  2. Consistent Naming

    ! Use descriptive collection names
    SELF.CollectionName = 'CustomerBrowse'  ! Not just 'Customers'
    

  3. 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🔗


You now have a complete understanding of implementing the Taboga Interactive Filter!