Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[IDEA] List widget allows for optional header and/or footer, shown only if list is non-empty #5302

Closed
rmunn opened this issue Dec 21, 2020 · 20 comments

Comments

@rmunn
Copy link
Contributor

rmunn commented Dec 21, 2020

Is your feature request related to a problem? Please describe.
I often want to show lists preceded by some kind of header, but only show the header if the list is non-empty. E.g., in a tiddler that represents one task on a todo list, I might want it to look like this:

Task Name
Task description, with some details of what it involves

Subtasks

  • Link to first subtask tiddler
  • Link to second subtask tiddler

The list of subtasks is presented by a $list widget, drawing from the subtasks field of the task tiddler. If the subtasks field is missing or empty, then I'd like to not show the Subtasks header at all. It would be nice if I could do this entirely within the $list widget, so that the "Subtasks" section of my todo template could be kept simple.

Describe the solution you'd like
The $list widget would acquire two new attributes, possibly named header and footer (though I've also considered prefix and suffix as possible names). Their contents would be wikitext that would be rendered before and after the list, respectively. If the list's filter resolves to empty output (so that the emptyMessage attribute is rendered instead) then the header and footer are not rendered. (If the user wants the header to always be rendered whether or not the list is empty, it's trivial to move that text to just before the list widget, or to include it as part of the emptyMessage).

It might also be a good idea to have attributes called headerTemplate and footerTemplate, to parallel the template and editTemplate attributes that already exist. Those templates would be rendered with the currentTiddler value being unchanged (i.e., it's probably the tiddler that contains the $list widget), just as if they were a normal transclusion. I haven't decided yet if both headerTemplate and header should be allowed as attributes, or if just one would be better. If both were allowed, we would need to decide which one "wins" if both are present and non-empty; I'd be inclined to say that headerTemplate wins over header, to parallel how other widgets tend to work (e.g., how the $set widget has the tiddler attribute trump the value attribute if both are set).

Describe alternatives you've considered
The way to achieve this right now would be to use a combination of widgets, such as:

<$vars header={{{ [list[!!subtasks]then[''Subtasks'']] }}}>
<<header>>
<$list filter="[list[!!subtasks]]" template="SubtaskTemplate" />
</$vars>

But I'd rather write that as:

<$list filter="[list[!!subtasks]]" template="SubtaskTemplate" header="''Subtasks''" />
@twMat
Copy link
Contributor

twMat commented Dec 21, 2020

Related #2202 - Add preMessage parameter to listwidget

@pmario
Copy link
Contributor

pmario commented Dec 21, 2020

Thx Mat. I knew there has been a discussion somewhere. ... You saved me from writing my concerns again.

I think the problem is, that the OP is a relatively specialized usecase, which from my point of view, isn't "best practice". I'm no fan of "hidden information". So if there is a possibility of "Subtasks" the user should be aware, that there are no subtasks. ... Hiding the "header" shouldn't be an option here. ... But that's only my point of view.

@Jermolene
Copy link
Owner

Thanks @rmunn, that's very clear. To answer your question, I think one would indeed need both headerTemplate and header for consistency.

However, there are some downsides with this approach. The first is that it doesn't work well for the usecase you mention of an optional heading before a list. The trouble is that we would normally wrap the <ul> or <ol> around the list widget, and so the heading would end up within the list.

The other downside is that it makes the list widget even more complex. It was added quite early in TW5's development to get the story river up and running. There is a lot of "magic" in the implementation (e.g. having separate edit and view templates) that we would handle differently now that we have so much more flexibility.

One interesting proposal we had in the past was to add counter variables to the list widget -- see #1330 (comment) and the later discussion at #3384, which would provide an alternate means to support the OP. I made a later proposal in #1523, but I think now I would advocate a new widget <$for> that is a stripped down list widget with support index counters.

@saqimtiaz
Copy link
Contributor

I think now I would advocate a new widget <$for> that is a stripped down list widget with support index counters.

+1

I would rather introduce a new widget than add more complexity to the <$list> widget. The extra optimization for refresh performance in the list widget is useful in situations outside of the story river as well when dealing with larger data sets, and should not be compromised.

@inmysocks
Copy link
Contributor

I agree about a new widget, and the coder in me wants to call it foreach instead of for, but I don't know if that would be clearer for most people.

@AnthonyMuscio
Copy link
Contributor

Solving the OT and related issues would stand to propel tiddlywiki to wards a 4th Generation report language.

I believe there is now a way to obtain an index value, However it is not so efficient; You must test the original filter every time.

<$list filter="[tag[TableOfContents]]">
   {{{ [tag[TableOfContents]allbefore<currentTiddler>count[]add[1]] }}}. <$link/><br>
</$list><br>

Finding a list item of 1 would allow the list widget contents (or template) to trigger a heading. The only way to detect the footer would be first to count the number of items and test for the last.

Perhaps a simple solution would be for the list widget to have a built in count, ie given the filter result +[count[]] the number of items, then use this to determine the last, and this be able to test if a footer is needed. eg; itemsVariable=total then gives the variable <<total>> inside the list widget/template (calculated only if asked for)

Not withstanding the above given the list widgets overloading perhaps if a new widget was to be written for more general purposes perhaps it could be a reportWidget including a total item count, header and footer triggers and an item count incremented for each item listed. An emptyReport=<<macroname>> would be used to present both headers and footers with the emptyReport message when empty. eg "No items", emptyMessage could do as current.

The idea of a reportWidget is in keeping with tiddlywikis excellent list handling facilities and could include multiple field based accumulators, like the new Reduce Operator. We could even introduce break points eg on change in fieldname value "display this" eg on newState.

I would be happy to prepare design requirements if someone can help build it.

Tones

@AnthonyMuscio
Copy link
Contributor

Post script,

Other page handling features would also be appropriate inside a reportWidget that may not be valid in the listWidget.

Eg; variable that exist only within the report widget, that can be reset according to conditions in the report widget.

Tones

@Jermolene
Copy link
Owner

Perhaps a simple solution would be for the list widget to have a built in count

I think that is what is discussed over at #3384.

@saqimtiaz
Copy link
Contributor

@rmunn has the requirement been met by the introduction of the counter and other new variables in the listWidget?

@rmunn
Copy link
Contributor Author

rmunn commented Apr 28, 2021

It's almost met, but not quite. I want to be able to produce something like the following:

<b>Header</b>
<ul>
<li>One</li>
<li>Two</li>
</ul>
<b>Footer</b>

Which renders as:

Header

  • One
  • Two
Footer

But if the list is empty, I want "Header" and "Footer" to be completely absent. I don't think I can produce this HTML with the list widget's new counter-first and counter-last variables. I'd like to do something like this:

<$list filter="One Two" variable="item">
<$if filter="[<counter-first>match[true]]">
<b>Header</b>
<ul>
</$if>
<li><<item>></li>
<$if filter="[<counter-last>match[true]]">
</ul>
<b>Footer</b>
</$if>
</$list>

But 1) there's no <$if> widget that takes the result of a filter and shows or hides the contents based on the filter (the <$reveal> widget uses state fields to track the hidden/revealed state, not filters, and setting a state field inside the <$list> widget would be a headache worse than the <$vars> workaround I've come up with). And 2) even if there was an <$if> widget like that, I don't know if the HTML fragments inside that <$if> widget would assemble properly into a syntactically valid tree, or whether the unmatched <ul> in the first <$if> block would end up causing some sort of error.

@pmario
Copy link
Contributor

pmario commented Apr 28, 2021

There is an if, it's named <$list or <$reveal

<$list filter="[tag[About]sort[title]]" counter="counter">
<$list filter="[<counter-first>match[yes]]" >
header list no surrounding html element<br>
</$list>
<$reveal type=nomatch text={{{ [<counter-first>match[yes]] }}} >
header reveal surrounded by SPAN - see: docs!<br>
</$reveal>
''<$text text=<<currentTiddler>>/>''<br>
</$list>

@pmario
Copy link
Contributor

pmario commented Apr 28, 2021

The only problem is the performance hit.

@pmario
Copy link
Contributor

pmario commented Apr 28, 2021

@Jermolene ... Would the Widget.prototype.getStateQualifier = function(name) { mechanism be able to count, without a performance hit?

just an idea

@Jermolene
Copy link
Owner

Hi @rmunn @saqimtiaz the subtlety is that these header and footer entries need to be outside the <ol>/<ul> of a list. That's the clue that these headers and footers can't be part of the list widget, but need to be a feature wrapped around it.

Would the Widget.prototype.getStateQualifier = function(name) { mechanism be able to count, without a performance hit?

No, for the same reason: the values of variables can only be changed by re-rendering their child widgets, which wouldn't happen if we were moving nodes around.

@kookma
Copy link
Contributor

kookma commented Apr 28, 2021

<$list filter="One Two" variable="item">
<$if filter="[<counter-first>match[true]]">
<b>Header</b>
<ul>
</$if>
<li><<item>></li>
<$if filter="[<counter-last>match[true]]">
</ul>
<b>Footer</b>
</$if>
</$list>

I think these can be simply addressed by a macro similar to list-links. There are cases like list-search (see Shiraz) where it creates a list of items from given tiddler plus a search box at the top! So, it seems simple macros with current $list widget can cover these use cases! Just my 2 cents!

@rmunn
Copy link
Contributor Author

rmunn commented May 24, 2022

This should become possible to implement myself once #6666 is implemented: I'll be able to write a $$headed-list widget looking something like this:

\widget $$headed-list(filter, header)
<$list filter="[enlist<filter>count[]compare:number:gt[0]]"><<header>></$list>
<$list filter=<<filter>>><$slot $name="ts-body"></$slot></$list>
\end

Edit: And with the $genesis widget added by #6666, I can even make sure that the <ul> element is added in the correct place:

\widget $$headed-list(filter, header, elem:"ul")
<$list filter="[enlist<filter>count[]compare:number:gt[0]]"><<header>></$list>
<$genesis $tag=<<elem>>>
<$list filter=<<filter>>><$slot $name="ts-body"></$slot></$list>
</$genesis>
\end

Usage:

<$$headed-list filter="one two three" header="<h2>Foo</h2>">
<li><<currentTiddler>></li>
</$$headed-list>

This makes the header stay outside of the <ul> element. And adding elem="ol" to my $$headed-list invocation creates a numbered list instead of bulleted. All very nice, and exactly what I wanted.

So once #6666 is merged, I'll close this issue as I'll be able to implement this feature myself without needing to change core TW.

@rmunn rmunn mentioned this issue May 24, 2022
53 tasks
@AnthonyMuscio
Copy link
Contributor

I would not say that the there is no value in header and footer features however I have already made a working solution by using the counter variable.

But first I use the filter to be used for the list and set a total

  • total is also available for use in the header/footer
<$set name=total filter="$filter% +[count][]">
<$list filter="$filter$" counter=item>
   <$list filter="[<item>match[1]]">Heading for <<total>> items</$list>
    each item
   <$list filter="[<item>match<total>]">Footer</$list>
</$list>
</$set>

above code not actually run.

@rmunn
Copy link
Contributor Author

rmunn commented Aug 23, 2023

Now that #6666 has been merged and released, it's time to close this feature request as it's possible to do this myself now.

Plus, I'm currently working on PR #7691 that adds an <$if> widget, similar to <$reveal> but using the result of a filter. So I could implement that "only print header if list is non-empty" feature even more easily now:

\define my-filter()
[all[shadows+tiddlers]tag[todo]!has[draft.of]]
\end

<$if filter=<<my-filter>> >

<$then>

<h2>Todo items</h2>

<ul>
<$list filter=<<my-filter>> >
<li><$link /></li>
</$list>
</ul>

</$then>

</$if>

That's perfectly adequate to satisfy me, and looks nice and clean and readable too. Closing this feature request as now TW can do what I wanted.

@rmunn rmunn closed this as completed Aug 23, 2023
@twMat
Copy link
Contributor

twMat commented Aug 23, 2023

@rmunn
FWIW, I just published another method some weeks ago, relying on CSS trickery:

https://liststyles.tiddlyhost.com/#:listheader%20About

I didn't announce it publicly because.... well, I don't expect anyone will use it anyway.

@AnthonyMuscio
Copy link
Contributor

I am yet to publish it but I have resolved this with a custom widget.

Happy to share.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants