@@ -413,6 +413,7 @@ class IrisGridContextMenuHandler extends GridMouseHandler {
413413 } ;
414414
415415 if ( value == null ) {
416+ // null gets a special menu
416417 if ( quickFilters . get ( modelColumn ) ) {
417418 filterMenu . actions . push ( {
418419 title : 'And' ,
@@ -426,6 +427,22 @@ class IrisGridContextMenuHandler extends GridMouseHandler {
426427 } ) ;
427428 }
428429 filterMenu . actions . push ( ...this . nullFilterActions ( column ) ) ;
430+ } else if ( value === '' ) {
431+ // empty string gets a special menu
432+ if ( quickFilters . get ( modelColumn ) ) {
433+ filterMenu . actions . push ( {
434+ title : 'And' ,
435+
436+ actions : this . emptyStringFilterActions (
437+ column ,
438+ quickFilters . get ( modelColumn ) ,
439+ '&&'
440+ ) ,
441+ order : 2 ,
442+ group : ContextActions . groups . high ,
443+ } ) ;
444+ }
445+ filterMenu . actions . push ( ...this . emptyStringFilterActions ( column ) ) ;
429446 } else if ( TableUtils . isBooleanType ( column . type ) ) {
430447 // boolean should have OR condition, and handles it's own null menu options
431448 if ( quickFilters . get ( modelColumn ) ) {
@@ -918,6 +935,11 @@ class IrisGridContextMenuHandler extends GridMouseHandler {
918935 const actions = [ ] ;
919936 const { model } = this . irisGrid . props ;
920937 const columnIndex = model . getColumnIndexByName ( column . name ) ;
938+
939+ const quickFilterValueText :
940+ | string
941+ | null = TableUtils . escapeQuickTextFilter ( valueText ) ;
942+
921943 assertNotNull ( columnIndex ) ;
922944
923945 actions . push ( {
@@ -946,7 +968,7 @@ class IrisGridContextMenuHandler extends GridMouseHandler {
946968 ) ,
947969 IrisGridContextMenuHandler . getQuickFilterText (
948970 filterText ,
949- `= ${ valueText } ` ,
971+ `${ quickFilterValueText } ` ,
950972 operator
951973 )
952974 ) ;
@@ -967,7 +989,7 @@ class IrisGridContextMenuHandler extends GridMouseHandler {
967989 ) ,
968990 IrisGridContextMenuHandler . getQuickFilterText (
969991 filterText ,
970- `!=${ valueText } ` ,
992+ `!=${ quickFilterValueText } ` ,
971993 operator
972994 )
973995 ) ;
@@ -983,12 +1005,16 @@ class IrisGridContextMenuHandler extends GridMouseHandler {
9831005 columnIndex ,
9841006 IrisGridContextMenuHandler . getQuickFilterCondition (
9851007 filter ,
986- column . filter ( ) . contains ( filterValue ) ,
1008+ column
1009+ . filter ( )
1010+ . isNull ( )
1011+ . not ( )
1012+ . and ( column . filter ( ) . contains ( filterValue ) ) ,
9871013 operator
9881014 ) ,
9891015 IrisGridContextMenuHandler . getQuickFilterText (
9901016 filterText ,
991- `~${ valueText } ` ,
1017+ `~${ quickFilterValueText } ` ,
9921018 operator
9931019 )
9941020 ) ;
@@ -1004,12 +1030,15 @@ class IrisGridContextMenuHandler extends GridMouseHandler {
10041030 columnIndex ,
10051031 IrisGridContextMenuHandler . getQuickFilterCondition (
10061032 filter ,
1007- column . filter ( ) . contains ( filterValue ) . not ( ) ,
1033+ column
1034+ . filter ( )
1035+ . isNull ( )
1036+ . or ( column . filter ( ) . contains ( filterValue ) . not ( ) ) ,
10081037 operator
10091038 ) ,
10101039 IrisGridContextMenuHandler . getQuickFilterText (
10111040 filterText ,
1012- `!~${ valueText } ` ,
1041+ `!~${ quickFilterValueText } ` ,
10131042 operator
10141043 )
10151044 ) ;
@@ -1025,12 +1054,16 @@ class IrisGridContextMenuHandler extends GridMouseHandler {
10251054 columnIndex ,
10261055 IrisGridContextMenuHandler . getQuickFilterCondition (
10271056 filter ,
1028- column . filter ( ) . invoke ( 'startsWith' , filterValue ) ,
1057+ column
1058+ . filter ( )
1059+ . isNull ( )
1060+ . not ( )
1061+ . and ( column . filter ( ) . invoke ( 'startsWith' , filterValue ) ) ,
10291062 operator
10301063 ) ,
10311064 IrisGridContextMenuHandler . getQuickFilterText (
10321065 filterText ,
1033- `${ valueText } *` ,
1066+ `${ quickFilterValueText } *` ,
10341067 operator
10351068 )
10361069 ) ;
@@ -1046,12 +1079,16 @@ class IrisGridContextMenuHandler extends GridMouseHandler {
10461079 columnIndex ,
10471080 IrisGridContextMenuHandler . getQuickFilterCondition (
10481081 filter ,
1049- column . filter ( ) . invoke ( 'endsWith' , filterValue ) ,
1082+ column
1083+ . filter ( )
1084+ . isNull ( )
1085+ . not ( )
1086+ . and ( column . filter ( ) . invoke ( 'endsWith' , filterValue ) ) ,
10501087 operator
10511088 ) ,
10521089 IrisGridContextMenuHandler . getQuickFilterText (
10531090 filterText ,
1054- `*${ valueText } ` ,
1091+ `*${ quickFilterValueText } ` ,
10551092 operator
10561093 )
10571094 ) ;
@@ -1518,6 +1555,88 @@ class IrisGridContextMenuHandler extends GridMouseHandler {
15181555 return actions ;
15191556 }
15201557
1558+ emptyStringFilterActions (
1559+ column : Column ,
1560+ quickFilter ?: QuickFilter ,
1561+ operator ?: '&&' | '||' | null
1562+ ) : ContextAction [ ] {
1563+ const { dh } = this ;
1564+ const filterValue = dh . FilterValue . ofString ( '' ) ;
1565+ let newQuickFilter :
1566+ | {
1567+ filter : null | FilterCondition | undefined ;
1568+ text : string | null ;
1569+ }
1570+ | undefined
1571+ | null = quickFilter ;
1572+ if ( ! newQuickFilter ) {
1573+ newQuickFilter = { filter : null , text : null } ;
1574+ }
1575+ const { filter, text : filterText } = newQuickFilter ;
1576+ const actions = [ ] ;
1577+ const { model } = this . irisGrid . props ;
1578+ const columnIndex = model . getColumnIndexByName ( column . name ) ;
1579+ assertNotNull ( columnIndex ) ;
1580+
1581+ actions . push ( {
1582+ menuElement : (
1583+ < div className = "iris-grid-filter-menu-item-value" >
1584+ { operator
1585+ ? IrisGridContextMenuHandler . getOperatorAsText ( operator )
1586+ : '' } { ' ' }
1587+ < i className = "text-muted" > empty</ i >
1588+ </ div >
1589+ ) ,
1590+ order : 1 ,
1591+ group : ContextActions . groups . high ,
1592+ } ) ;
1593+
1594+ actions . push ( {
1595+ title : 'is empty string' ,
1596+ description : `Show only rows where ${ column . name } is empty` ,
1597+ action : ( ) => {
1598+ this . irisGrid . setQuickFilter (
1599+ columnIndex ,
1600+ IrisGridContextMenuHandler . getQuickFilterCondition (
1601+ filter ,
1602+ column . filter ( ) . eq ( filterValue ) ,
1603+ operator
1604+ ) ,
1605+ IrisGridContextMenuHandler . getQuickFilterText (
1606+ filterText ,
1607+ `=` ,
1608+ operator
1609+ )
1610+ ) ;
1611+ } ,
1612+ order : 10 ,
1613+ group : ContextActions . groups . low ,
1614+ } ) ;
1615+ actions . push ( {
1616+ title : 'is not empty string' ,
1617+ description : `Show only rows where ${ column . name } is not empty` ,
1618+ action : ( ) => {
1619+ this . irisGrid . setQuickFilter (
1620+ columnIndex ,
1621+ IrisGridContextMenuHandler . getQuickFilterCondition (
1622+ filter ,
1623+ column . filter ( ) . notEq ( filterValue ) ,
1624+ operator
1625+ ) ,
1626+ IrisGridContextMenuHandler . getQuickFilterText (
1627+ filterText ,
1628+ `!=` ,
1629+ operator
1630+ )
1631+ ) ;
1632+ } ,
1633+ order : 20 ,
1634+ group : ContextActions . groups . low ,
1635+ } ) ;
1636+
1637+ return actions ;
1638+ }
1639+
15211640 nullFilterActions (
15221641 column : Column ,
15231642 quickFilter ?: QuickFilter ,
@@ -1540,7 +1659,7 @@ class IrisGridContextMenuHandler extends GridMouseHandler {
15401659 { operator
15411660 ? IrisGridContextMenuHandler . getOperatorAsText ( operator )
15421661 : '' } { ' ' }
1543- " null"
1662+ < i className = "text-muted" > null</ i >
15441663 </ div >
15451664 ) ,
15461665 order : 1 ,
0 commit comments