Docs only Groupedlists examples (#9954)

* Add GroupedLists examples

Document grouped lists with runnable test cases over a shared
bibliography sample.

* Cover plain grouping, search, tabs by first character, both combined,
  and grouping on one field while searching another
* Name the grouped field in one procedure, so the grouping changes in
  one line
* Import it with import-compound, and list the examples by tag so a new
  one needs no page edit
* Rewrite the opening paragraph, which described only the sidebar tabs

* Replace GroupedLists code dumps with examples

Turn the two sidebar tabs the page describes into runnable test cases,
and tighten how the examples handle an empty result.

* Give each new example its own dummy payload, and point the page at the
  live tabs instead of quoting core source
* Carry the values through the filter from the start, so an All tab with
  no matches yields nothing rather than the literal All
* Use the list-empty widget

* use tc-tiny-gap instead of -left -right
This commit is contained in:
Mario Pietsch
2026-08-16 13:16:04 +02:00
committed by GitHub
parent 7efbd01801
commit 68630c939d
9 changed files with 727 additions and 9 deletions
@@ -1,18 +1,22 @@
created: 20150106180000000
modified: 20241204085601176
list: GroupedLists/Example/TypesTab GroupedLists/Example/ByField GroupedLists/Example/WithSearch GroupedLists/Example/WithTabs GroupedLists/Example/TabsWithSearch GroupedLists/Example/SearchAnotherField GroupedLists/Example/RecentTab
modified: 20260729194941115
tags: ListWidget Lists
title: GroupedLists
type: text/vnd.tiddlywiki
The following sidebar tabs give examples of grouped lists created by nesting.
A grouped list is made by nesting one list inside another. The outer list collects each distinct value of a field, and the inner list finds the tiddlers holding that value.
!! [[Types Tab|$:/core/ui/MoreSideBar/Types]]
TiddlyWiki uses the pattern in two of its sidebar tabs. The [[Types Tab|$:/core/ui/MoreSideBar/Types]] groups tiddlers by their `type` field, and sits under ''More'' in the sidebar. The [[Recent Tab|$:/core/ui/SideBar/Recent]] groups them by day, through the <<.mlink timeline>> macro. Both are reproduced as examples below.
For the "Types Tab", the outer list filter as shown below selects each discrete value found in the `type` field. The inner list filter selects all the (non-system) tiddlers with that type.
!! Interactive Examples
<<tw-code "$:/core/ui/MoreSideBar/Types">>
Each example is a runnable test case carrying its own sample data. Where a field is grouped on, it is named once in <<.def groupField>>, so the grouping can be changed without touching anything else.
!! [[Recent Tab|$:/core/ui/SideBar/Recent]]
<<<
<$list filter="[tag[GroupedLists]]">
The list in the "Recent Tab" is generated using the <<.mlink timeline>> macro. Here, the outer list filter selects each discrete day found in the `modified` field, while the inner list filter selects all the tiddlers dated the same day in the `modified` field.
<<tw-code "$:/core/macros/timeline">>
; <$link/>
: {{!!description}}
</$list>
<<<
@@ -0,0 +1,173 @@
code-body: yes
created: 20260728220000000
description: Bibliography sample data shared by the GroupedLists examples
modified: 20260729180306417
title: GroupedLists/Bibliography
type: text/vnd.tiddlywiki-multiple
title: 3DWiki2011
bibtex-entry-type: InProceedings
bibtex-author: 3D Wiki Collective
bibtex-title: Spatial Notes in Three Dimensions
bibtex-date: 2011
+
title: 3rdWave2014
bibtex-entry-type: Article
bibtex-author: 3rd Wave Research Group
bibtex-title: Note Taking After the Desktop
bibtex-date: 2014
+
title: Aronsson2002
bibtex-entry-type: InProceedings
bibtex-author: Aronsson, Lars
bibtex-title: Operation of a Large Scale, General Purpose Wiki Website
bibtex-date: 2002
+
title: Atkinson2016
bibtex-entry-type: Article
bibtex-author: Atkinson, Paul
bibtex-title: Digital ethnographies
bibtex-date: 2016
+
title: Bakshi2016
bibtex-entry-type: Article
bibtex-author: Bakshi, Divya
bibtex-title: Hypertext and Feminisms: Voicing the Silence
bibtex-date: 2016
+
title: Barker2008
bibtex-entry-type: InProceedings
bibtex-author: Barker, Philip
bibtex-title: Using wikis and weblogs to enhance human performance
bibtex-date: 2008
+
title: Barker2008a
bibtex-entry-type: InProceedings
bibtex-author: Barker, Philip
bibtex-title: Using Wikis for Knowledge Management
bibtex-date: 2008
+
title: Bernstein2016
bibtex-entry-type: Book
bibtex-author: Bernstein, Mark
bibtex-title: Getting Started With Hypertext Narrative
bibtex-date: 2016
+
title: Dalgaard2001
bibtex-entry-type: InProceedings
bibtex-author: Dalgaard, Rune
bibtex-title: Hypertext and the Scholarly Archive: Intertexts, Paratexts and Metatexts at Work
bibtex-date: 2001
+
title: Dickinson2008
bibtex-entry-type: InProceedings
bibtex-author: Dickinson, Anne
bibtex-title: Is the e-Learning Object Create Interactive Accessible e-Learning Accessible?
bibtex-date: 2008
+
title: Finnemann2016
bibtex-entry-type: Article
bibtex-author: Finnemann, Niels Ole
bibtex-title: Hypertext configurations: Genres in networked digital media
bibtex-date: 2016
+
title: Frumkin2005
bibtex-entry-type: Article
bibtex-author: Frumkin, Jeremy
bibtex-title: The wiki and the digital library
bibtex-date: 2005
+
title: Maier2016
bibtex-entry-type: InCollection
bibtex-author: Maier, Carmen Daniela
bibtex-title: Hypertext and Hypermedia
bibtex-date: 2016
+
title: Morris2007
bibtex-entry-type: InProceedings
bibtex-author: Morris, Joseph C.
bibtex-title: DistriWiki: a distributed peer-to-peer wiki network
bibtex-date: 2007
+
title: Ruston2004
bibtex-entry-type: Book
bibtex-author: Ruston, Jeremy
bibtex-title: TiddlyWiki
bibtex-date: 2004
+
title: Rutherford2009
bibtex-entry-type: Thesis
bibtex-author: Rutherford, Jayne
bibtex-title: Graphical Input for TiddlyWiki
bibtex-date: 2009
+
title: Schaffert2006
bibtex-entry-type: InProceedings
bibtex-author: Schaffert, Sebastian
bibtex-title: IkeWiki: A semantic wiki for collaborative knowledge management
bibtex-date: 2006
+
title: Shang2016
bibtex-entry-type: Article
bibtex-author: Shang, Hui-Fang
bibtex-title: Online metacognitive strategies, hypermedia annotations, and motivation on hypertext comprehension
bibtex-date: 2016
+
title: Shang2016a
bibtex-entry-type: Article
bibtex-author: Shang, Hui-Fang
bibtex-title: Exploring demographic and motivational factors associated with hypertext reading by English as a foreign language students
bibtex-date: 2016
+
title: Skiba2005
bibtex-entry-type: Article
bibtex-author: Skiba, Diane J.
bibtex-title: Do your students wiki?
bibtex-date: 2005
+
title: Trentin2009
bibtex-entry-type: Article
bibtex-author: Trentin, Guglielmo
bibtex-title: Using a wiki to evaluate individual contribution to a collaborative learning project
bibtex-date: 2009
+
title: Truman2016
bibtex-entry-type: Article
bibtex-author: Truman, Gail
bibtex-title: Web Archiving Environmental Scan
bibtex-date: 2016
+
title: Wagner2004
bibtex-entry-type: Article
bibtex-author: Wagner, Christian
bibtex-title: Wiki: A technology for conversational knowledge management and group collaboration
bibtex-date: 2004
+
title: Wilson2007
bibtex-entry-type: Article
bibtex-author: Wilson, Tom D.
bibtex-title: Review of TiddlyWiki 2.1.3
bibtex-date: 2007
@@ -0,0 +1,45 @@
created: 20260728220100000
description: Group tiddlers by the value of a field chosen in one place
import-compound: [[GroupedLists/Bibliography]]
modified: 20260729185240642
tags: $:/tags/wiki-test-spec GroupedLists
title: GroupedLists/Example/ByField
type: text/vnd.tiddlywiki-multiple
title: Narrative
`groupField` names the field to group by. It is the only line to change when you want a different grouping.
Two nested lists do the work:
* `f.valuesAll` collects each distinct value of that field, sorted
* `f.tiddlersFor` returns the tiddlers holding one of those values
This test case imports 24 bibliography tiddlers from the `GroupedLists/Bibliography` payload. They came from a ~BibTeX import, so the field is `bibtex-author` rather than `author`.
Barker and Shang each published twice, so their groups list two titles. Set `groupField` to `bibtex-entry-type` to regroup the same data by publication type.
+
title: Output
\whitespace trim
<!-- bibtex-author<value> would make the field name the operator, which cannot be a variable, so f.tiddlersFor compares via get<groupField> -->
<!-- No trailing whitespace after the field name: it becomes part of the name and the list silently renders nothing -->
\procedure groupField() bibtex-author
\function f.valuesAll() [all[tiddlers]!is[system]has<groupField>each<groupField>get<groupField>sort[]]
\function f.tiddlersFor(value) [!is[system]has<groupField>] :filter[get<groupField>match<value>] +[sort[title]]
<$list filter="[f.valuesAll[]]" variable="_value">
<div class="tc-menu-list-item">
<<_value>>
<$list filter="[f.tiddlersFor<_value>]">
<div class="tc-menu-list-subitem">
<$link><$text text={{{ [<currentTiddler>get[bibtex-title]] :else[<currentTiddler>] }}}/></$link>
</div>
</$list>
</div>
</$list>
@@ -0,0 +1,51 @@
title: GroupedLists/Example/RecentTab
created: 20260729190100000
modified: 20260729190100000
description: Group tiddlers by day with the timeline macro, as the sidebar Recent tab does
tags: [[$:/tags/wiki-test-spec]] GroupedLists
type: text/vnd.tiddlywiki-multiple
title: Narrative
The `timeline` macro groups tiddlers by the day found in a date field. The ''Recent'' tab in the sidebar is a single call to it, which is all the Output below is.
The nesting happens inside the macro:
* the outer filter uses `eachday` to yield one tiddler per distinct day
* the inner filter uses `sameday` to collect every tiddler falling on that day
* `dateField` chooses which field to read, so the same macro groups by `created` just as well as by `modified`
The five dummy payload tiddlers below carry hand set `modified` dates. Nothing else here has a date field, so they are exactly what the timeline finds.
Pass `format` to change how each day is headed, and `limit` to cap how many tiddlers are considered.
+
title: Output
\whitespace trim
<$transclude $variable="timeline" format="DDth MMM YYYY"/>
+
title: Alpha
modified: 20260103120000000
Modified on the third, later in the day.
+
title: Beta
modified: 20260103100000000
Modified on the third, earlier in the day.
+
title: Gamma
modified: 20260102120000000
Modified on the second, later in the day.
+
title: Delta
modified: 20260102100000000
Modified on the second, earlier in the day.
+
title: Epsilon
modified: 20260101120000000
Modified on the first.
@@ -0,0 +1,116 @@
created: 20260728224000000
description: Group by one field while the search box filters on another
import-compound: [[GroupedLists/Bibliography]]
modified: 20260729190424885
tags: $:/tags/wiki-test-spec GroupedLists
title: GroupedLists/Example/SearchAnotherField
type: text/vnd.tiddlywiki-multiple
title: Narrative
`searchField` names the field the search box looks at, which no longer has to be the field the list groups by.
Three definitions carry the split:
* `groupField` is `bibtex-entry-type`, so the groups and the tabs are publication types
* `searchField` is `bibtex-author`, so typing still matches people
* `f.tiddlersMatching` narrows the tiddlers first, and the groups are derived from whatever survives
Because the groups are computed after the search, a whole group disappears once none of its entries match. The tab strip is still built from the unfiltered data, so the tabs themselves stay put.
This test case carries the same `_TabButton` and `_TabContent` payload tiddlers as the previous example.
Type `bar` to leave only ''InProceedings'', holding Barker's two papers. The other tabs drop to zero and show the `emptyMessage` of the list.
+
title: Output
\whitespace trim
<!-- Filter operator suffixes cannot be variables, so search:<searchField> is impossible.
f.tiddlersMatching searches the extracted value as a title, which works whichever field it came from. -->
<!-- No trailing whitespace after the field name: it becomes part of the name and the list silently renders nothing -->
\procedure groupField() bibtex-entry-type
\procedure searchField() bibtex-author
\procedure searchLabel() Author
\function f.valuesAll() [all[tiddlers]!is[system]has<groupField>each<groupField>get<groupField>sort[]]
\function f.tiddlersMatching() [all[tiddlers]!is[system]has<groupField>] :filter[get<searchField>search:title{$:/temp/GroupedLists/search-other}]
\function f.valuesFound() [f.tiddlersMatching[]each<groupField>get<groupField>sort[]]
\function f.letters() [f.valuesAll[]] :map[uppercase[]split[]first[]] +[unique[]sort[]]
\function f.valuesFor(letter) [f.valuesFound[]] :filter[uppercase[]split[]first[]match<letter>]
\function f.valuesShown(letter) [f.valuesFound[]] :filter[<letter>match[All]] :else[f.valuesFor<letter>]
\function f.tiddlersFor(value) [f.tiddlersMatching[]] :filter[get<groupField>match<value>] +[sort[title]]
<!-- The inline style below is only for this self-contained example. For production, define styles in a tiddler tagged $:/tags/Stylesheet instead. -->
<style>.demo-group-filter { margin-bottom: 1em; }</style>
<$macrocall
$name="tabs"
tabsList="[[All]] [f.letters[]]"
default="All"
state="$:/state/GroupedLists/tab-other"
buttonTemplate="_TabButton"
template="_TabContent"
/>
+
title: _TabButton
code-body: yes
\whitespace trim
<$text text=<<currentTab>>/><span class="tc-tiny-gap-left">(<$count filter="[f.valuesShown<currentTab>]"/>)</span>
+
title: _TabContent
code-body: yes
\whitespace trim
<!-- tm-focus-selector takes the first match in the document, so .tc-search input would hit the sidebar search. -->
<div class="tc-search demo-group-filter">
<label>
<<searchLabel>>:<$edit-text
tiddler="$:/temp/GroupedLists/search-other"
tag="input"
type="search"
default=""
placeholder={{{ [[Filter by ]addsuffix<searchLabel>] }}}
class="tc-tiny-gap"
/>
</label>
<%if [[$:/temp/GroupedLists/search-other]get[text]minlength[1]] %>
<$button
class="tc-btn-invisible"
tooltip="Clear the filter"
aria-label="Clear the filter"
>
<$action-deletetiddler $tiddler="$:/temp/GroupedLists/search-other"/>
<$action-sendmessage $message="tm-focus-selector" $param=".demo-group-filter input"/>
{{$:/core/images/close-button}}
</$button>
<%endif%>
</div>
<$list filter="[f.valuesShown<currentTab>]" variable="_value">
<$list-empty>
//No matches//
</$list-empty>
<div class="tc-menu-list-item">
<<_value>>
<$list filter="[f.tiddlersFor<_value>]">
<div class="tc-menu-list-subitem">
<$link><$text text={{{ [<currentTiddler>get[bibtex-title]] :else[<currentTiddler>] }}}/></$link>
</div>
</$list>
</div>
</$list>
@@ -0,0 +1,111 @@
created: 20260728220400000
description: Combine the tab strip with a search box inside the tab panel
import-compound: [[GroupedLists/Bibliography]]
modified: 20260729190424885
tags: $:/tags/wiki-test-spec GroupedLists
title: GroupedLists/Example/TabsWithSearch
type: text/vnd.tiddlywiki-multiple
title: Narrative
`_TabContent` holds the search box as well as the list, so the search sits inside the panel and filters within the selected tab.
Which values each definition reads is the whole design:
* `f.letters` reads the //unfiltered// values, so tabs never disappear while you type and the selected tab cannot be stranded on an empty panel
* `f.valuesShown` reads the //filtered// values, so the counts follow the search and a tab showing zero tells you not to look there
This test case carries the same `_TabButton` and `_TabContent` payload tiddlers as the previous example, with the search box added to the panel.
The input is a sibling of the list rather than a child of anything that recomputes, which is what keeps the cursor in the box while you type.
Type `ba` and watch the counts change. Any tab whose count falls to zero shows the `emptyMessage` of the list instead of nothing at all.
+
title: Output
\whitespace trim
<!-- Filter operator suffixes cannot be variables, so search:<groupField> and bibtex-author<value> are impossible.
f.valuesFound searches the collected values as titles, f.tiddlersFor compares via get<groupField>. -->
<!-- No trailing whitespace after the field name: it becomes part of the name and the list silently renders nothing -->
\procedure groupField() bibtex-author
\procedure groupLabel() Author
\function f.valuesAll() [all[tiddlers]!is[system]has<groupField>each<groupField>get<groupField>sort[]]
\function f.valuesFound() [f.valuesAll[]] :filter[search:title{$:/temp/GroupedLists/search}]
\function f.letters() [f.valuesAll[]] :map[uppercase[]split[]first[]] +[unique[]sort[]]
\function f.valuesFor(letter) [f.valuesFound[]] :filter[uppercase[]split[]first[]match<letter>]
\function f.valuesShown(letter) [f.valuesFound[]] :filter[<letter>match[All]] :else[f.valuesFor<letter>]
\function f.tiddlersFor(value) [!is[system]has<groupField>] :filter[get<groupField>match<value>] +[sort[title]]
<!-- The inline style below is only for this self-contained example. For production, define styles in a tiddler tagged $:/tags/Stylesheet instead. -->
<style>.demo-group-filter { margin-bottom: 1em; }</style>
<$macrocall
$name="tabs"
tabsList="[[All]] [f.letters[]]"
default="All"
state="$:/state/GroupedLists/tab-search"
buttonTemplate="_TabButton"
template="_TabContent"
/>
+
title: _TabButton
code-body: yes
\whitespace trim
<$text text=<<currentTab>>/><span class="tc-tiny-gap-left">(<$count filter="[f.valuesShown<currentTab>]"/>)</span>
+
title: _TabContent
code-body: yes
\whitespace trim
<!-- tm-focus-selector takes the first match in the document, so .tc-search input would hit the sidebar search. -->
<div class="tc-search demo-group-filter">
<label>
<<groupLabel>>:<$edit-text
tiddler="$:/temp/GroupedLists/search"
tag="input"
type="search"
default=""
placeholder={{{ [[Filter by ]addsuffix<groupLabel>] }}}
class="tc-tiny-gap"
/>
</label>
<%if [[$:/temp/GroupedLists/search]get[text]minlength[1]] %>
<$button
class="tc-btn-invisible"
tooltip="Clear the filter"
aria-label="Clear the filter"
>
<$action-deletetiddler $tiddler="$:/temp/GroupedLists/search"/>
<$action-sendmessage $message="tm-focus-selector" $param=".demo-group-filter input"/>
{{$:/core/images/close-button}}
</$button>
<%endif%>
</div>
<$list filter="[f.valuesShown<currentTab>]" variable="_value">
<$list-empty>
//No matches//
</$list-empty>
<div class="tc-menu-list-item">
<<_value>>
<$list filter="[f.tiddlersFor<_value>]">
<div class="tc-menu-list-subitem">
<$link><$text text={{{ [<currentTiddler>get[bibtex-title]] :else[<currentTiddler>] }}}/></$link>
</div>
</$list>
</div>
</$list>
@@ -0,0 +1,60 @@
title: GroupedLists/Example/TypesTab
created: 20260729190000000
modified: 20260729190000000
description: Group tiddlers by their type, as the sidebar Types tab does
tags: [[$:/tags/wiki-test-spec]] GroupedLists
type: text/vnd.tiddlywiki-multiple
title: Narrative
`$:/core/Filters/TypedTiddlers` collects one tiddler per distinct type, and the inner list then finds every tiddler sharing that type.
The two lists do different jobs:
* the outer filter ends in `each[type]`, so it yields one representative tiddler per type rather than every tiddler
* the inner filter reads `{!!type}` from that representative, so `currentTiddler` is what carries the type from the outer list to the inner one
This is the code behind the ''Types'' tab in the sidebar, under ''More''. Open that tab to see it running over a real wiki.
The five dummy payload tiddlers below are the only ones here with a `type` field, so they are exactly what the list finds.
+
title: Output
\whitespace trim
<$list filter={{$:/core/Filters/TypedTiddlers!!filter}}>
<div class="tc-menu-list-item">
<$view field="type"/>
<$list filter="[type{!!type}!is[system]sort[title]]">
<div class="tc-menu-list-subitem">
<$link to={{!!title}}><$view field="title"/></$link>
</div>
</$list>
</div>
</$list>
+
title: Alpha
type: image/svg+xml
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><circle cx="8" cy="8" r="7"/></svg>
+
title: Beta
type: image/svg+xml
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><rect x="1" y="1" width="14" height="14"/></svg>
+
title: Gamma
type: text/plain
A plain text sample.
+
title: Delta
type: text/plain
Another plain text sample.
+
title: Epsilon
type: application/json
{"sample": true}
@@ -0,0 +1,82 @@
created: 20260728220200000
description: Narrow a grouped list with a search box
import-compound: [[GroupedLists/Bibliography]]
modified: 20260729185240642
tags: $:/tags/wiki-test-spec GroupedLists
title: GroupedLists/Example/WithSearch
type: text/vnd.tiddlywiki-multiple
title: Narrative
`search:title` filters the values collected by `f.valuesAll`, so the grouped list narrows while you type without the tiddlers themselves being searched.
Two definitions are added to the plain grouped list:
* `groupLabel` is the human readable name shown beside the input
* `f.valuesFound` applies the search to the collected values
An empty box is a no operation, so the whole list comes back. The clear button appears only once there is something to clear, and puts the cursor back in the input.
Type `ann` to match Dickinson and Finnemann, `wi` to match several at once, or `zzz` to fall through to the `emptyMessage` of the outer list.
+
title: Output
\whitespace trim
<!-- Filter operator suffixes cannot be variables, so search:<groupField> and bibtex-author<value> are impossible.
f.valuesFound searches the collected values as titles, f.tiddlersFor compares via get<groupField>. -->
<!-- tm-focus-selector takes the first match in the document, so .tc-search input would hit the sidebar search. -->
<!-- No trailing whitespace after the field name: it becomes part of the name and the list silently renders nothing -->
\procedure groupField() bibtex-author
\procedure groupLabel() Author
\function f.valuesAll() [all[tiddlers]!is[system]has<groupField>each<groupField>get<groupField>sort[]]
\function f.valuesFound() [f.valuesAll[]] :filter[search:title{$:/temp/GroupedLists/search}]
\function f.tiddlersFor(value) [!is[system]has<groupField>] :filter[get<groupField>match<value>] +[sort[title]]
<!-- The inline style below is only for this self-contained example. For production, define styles in a tiddler tagged $:/tags/Stylesheet instead. -->
<style>.demo-group-filter { margin-bottom: 1em; }</style>
<div class="tc-search demo-group-filter">
<label>
<<groupLabel>>:<$edit-text
tiddler="$:/temp/GroupedLists/search"
tag="input"
type="search"
default=""
placeholder={{{ [[Filter by ]addsuffix<groupLabel>] }}}
class="tc-tiny-gap"
/>
</label>
<%if [[$:/temp/GroupedLists/search]get[text]minlength[1]] %>
<$button
class="tc-btn-invisible"
tooltip="Clear the filter"
aria-label="Clear the filter"
>
<$action-deletetiddler $tiddler="$:/temp/GroupedLists/search"/>
<$action-sendmessage $message="tm-focus-selector" $param=".demo-group-filter input"/>
{{$:/core/images/close-button}}
</$button>
<%endif%>
</div>
<$list filter="[f.valuesFound[]]" variable="_value">
<$list-empty>
//No matches//
</$list-empty>
<div class="tc-menu-list-item">
<<_value>>
<$list filter="[f.tiddlersFor<_value>]">
<div class="tc-menu-list-subitem">
<$link><$text text={{{ [<currentTiddler>get[bibtex-title]] :else[<currentTiddler>] }}}/></$link>
</div>
</$list>
</div>
</$list>
@@ -0,0 +1,76 @@
created: 20260728220300000
description: Bucket the groups into tabs by first character, with a count per tab
import-compound: [[GroupedLists/Bibliography]]
modified: 20260729190424885
tags: $:/tags/wiki-test-spec GroupedLists
title: GroupedLists/Example/WithTabs
type: text/vnd.tiddlywiki-multiple
title: Narrative
The `tabs` macro takes its tab list from a filter, so here the tabs come out of the data: one per distinct first character, plus an ''All'' tab.
Three definitions feed it:
* `f.letters` collects the distinct first characters
* `f.valuesFor` selects the groups filed under one character
* `f.valuesShown` returns everything for ''All'', one character otherwise
This test case carries two payload tiddlers, `_TabButton` and `_TabContent`, because the `tabs` macro transcludes its caption and its panel by tiddler title and cannot take inline wikitext.
Matching is case insensitive, so `de Vries` and `De Vries` would share a tab. A character that is not a letter gets its own tab, which is why the sample data produces a `3` tab. Each caption carries the number of groups behind it, so no tab is a dead end.
+
title: Output
\whitespace trim
<!-- bibtex-author<value> would make the field name the operator, which cannot be a variable, so f.tiddlersFor compares via get<groupField> -->
<!-- No trailing whitespace after the field name: it becomes part of the name and the list silently renders nothing -->
\procedure groupField() bibtex-author
\function f.valuesAll() [all[tiddlers]!is[system]has<groupField>each<groupField>get<groupField>sort[]]
\function f.letters() [f.valuesAll[]] :map[uppercase[]split[]first[]] +[unique[]sort[]]
\function f.valuesFor(letter) [f.valuesAll[]] :filter[uppercase[]split[]first[]match<letter>]
\function f.valuesShown(letter) [f.valuesAll[]] :filter[<letter>match[All]] :else[f.valuesFor<letter>]
\function f.tiddlersFor(value) [!is[system]has<groupField>] :filter[get<groupField>match<value>] +[sort[title]]
<$macrocall
$name="tabs"
tabsList="[[All]] [f.letters[]]"
default="All"
state="$:/state/GroupedLists/tab"
buttonTemplate="_TabButton"
template="_TabContent"
/>
+
title: _TabButton
code-body: yes
\whitespace trim
<$text text=<<currentTab>>/><span class="tc-tiny-gap-left">(<$count filter="[f.valuesShown<currentTab>]"/>)</span>
+
title: _TabContent
code-body: yes
\whitespace trim
<$list filter="[f.valuesShown<currentTab>]" variable="_value">
<$list-empty>
//No matches//
</$list-empty>
<div class="tc-menu-list-item">
<<_value>>
<$list filter="[f.tiddlersFor<_value>]">
<div class="tc-menu-list-subitem">
<$link><$text text={{{ [<currentTiddler>get[bibtex-title]] :else[<currentTiddler>] }}}/></$link>
</div>
</$list>
</div>
</$list>