Accordion macro: showcase, discussion, and questions

Showcase

I ran across the thread TW Classic Animated Accordion Menus, and when I looked at the solutions thought, “Those are just sliders. Where are the full accordions?” When I didn’t find them (although now I find Mohammad’s KISS Macro to Create an Accordion, which is close to what I was imagining, and I’ll study it soon), I thought that, although I have no current need for one, that it might be a useful thing to try to build, to see if I can create something reasonable depending only on my current knowledge and the docs, without reaching for more help. I’m very satisfied to report that I was able to make a reasonable first pass on my own in only a few hours. The result is at Accordion Demo.

video

In my mind, an accordion is a fixed block with a group of headers visible as well as the content associated with one of them. When you click on a different header, the current open one slides closed as the new one slides open, keeping the overall structure the same size. In this case the blocks are tiddlers (of course!) The accordion is just a widget that accepts a filter for a collection of tiddlers, displays their titles, and when one is opened, shows its transcluded body.

It can be used with any filter for a list of tidders. Accordion 1 shows one using something like [tag[MyTag]]. Accordion 2 shows one using Foo Bar Baz [[Bizz Buzz]]. This one also demonstrates overriding the default height. At the moment that is the only override available.

To use it just drag these two tiddlers to your wiki:

Or test it by dragging accordion.json (3.7 KB) onto https://tiddlywiki.com and opening examples/accordion101.

The CSS work is not original. It’s something that I modified a few years ago for a different (non-Tiddlywiki) project from one of the approximately 3.7 zillion examples available on the web. (I wish I could find the link; sorry.) I reduced and simplified the CSS, but the basic transition stuff is left intact.

The Macro

This is the current code:

\define accordion(filter, height:"300px")

<$set name="height" value="$height$" >
<$set name="acc" value=<<qualify "accordion">> >
<$set name="classname" value={{{ [<acc>addprefix[accordion ac-]] }}} >

<section class=<<classname>> >
<$list filter="$filter$ +[first[]]" counter="counter">
<$set name="id" value={{{ [<counter>addprefix[-]addprefix<acc>addprefix[ac-]] }}}>
<$wikify name="content" text={{!!text}} output="html">
    <div>
        <input id=<<id>> name=<<acc>> type="radio" checked="checked">
        <label for=<<id>> >{{!!title}}</label>
        <article><div><<content>></div></article>
    </div>
</$wikify>
</$set>
</$list>
<$list filter="$filter$ +[rest[]]" counter="counter">
<$set name="id" value={{{ [<counter>add[1]addprefix[-]addprefix<acc>addprefix[ac-]] }}}>
<$wikify name="content" text={{!!text}} output="html">
    <div>
        <input id=<<id>> name=<<acc>> type="radio">
        <label for=<<id>> >{{!!title}}</label>
        <article><div><<content>></div></article>
    </div>
</$wikify>
</$set>
</$list>
</section>

<style>
    <$text text={{{ [<acc>addprefix[.accordion.ac-]addsuffix[ input:checked ~ article {height: ]addsuffix<height>addsuffix[ ;} ]] }}} />
</style>

</$set>
</$set>
</$set>

\end

Discussion

Duplication

I don’t think I can use the radio widget for this, as I don’t (at least for now) want to store the content choice in any tiddler. I may come back to that, but the whole mechanism is CSS-only, using the checked state of the (hidden) radio buttons in the group to determine what to show and hide, and trigger the transitions. If I’m wrong, and I can use the radio widget without specifying a tiddler/field for its state, please let me know. So I’m managing the radios manually, and that seems to work.

But there is some really stupid duplication to handle the first element of the list differently from others. I know I could usually use counter-first to do something like this. But what I need to do is to put this on the first one:

        <input id="id-xyz-1" name="accordion-xyz" type="radio" checked="checked">

and this on subsequent ones:

        <input id="id-xyz-2" name="accordion-xyz" type="radio">
        <input id="id-xyz-3" name="accordion-xyz" type="radio">
        <!-- ... -->

and I cannot figure out a way to dynamically add the checked attribute based on counter-first. Of the five or so hours I spent on creating this, nearly four of them were trying to figure that out on my own. I never did it, so went with two nearly identical <$list>s distinguished only by +[first[]] and +[rest[]], a tweak to increment the counter in the latter, and the absence or presence of checked = "checked". This feels really stupid.

Per-invocation styling

The ability to override the height per invocation was trickier than I’d hoped, and I’m sure it can be done more simply, on two different fronts.

First, I generate something like this:

<style>
    .accordion.ac-accordion-1905655034 input:checked ~ article {height: 250px ;}
</style>

The text accordion-1905655034 is stored in the variable acc, which is generated by the qualify macro; acc is used several times. And 250px is the value of the macro parameter height. I can’t think of a better way to override this bit from the stylesheet:

.accordion input:checked ~ article {
    height: 300px;
}

than by adding a stylesheet with an additional classname in the selector to increase specificity. But someone here might have a better idea.

(As I write this up, I realize that the ac- prefixes are no longer necessary. I’ll try to remove them soon.)

Second, the way I generate that output feels way too complex. This feels ridiculous, even though it works:

<style>
    <$text text={{{ [<acc>addprefix[.accordion.ac-]addsuffix[ input:checked ~ article {height: ]addsuffix<height>addsuffix[ ;} ]] }}} />
</style>

Scrollbars

I’m no expert on CSS transitions, and there’s something ugly during the transitions: appearing, resizing, and disappearing scrollbars. The original demo this came from and my own prior use of this were able to size the box appropriately to hold all the content and so we could use overflow: hidden. We can’t do this when the content could hold arbitrarily-sized tidders, so I use overflow: auto, and it works fine except for a minor, but annoying artifact during transitions. Perhaps someone here has more skills in this.

Questions

Beyond the obvious, “What do you think?” questions, I have several technical questions:

  • Is there a straightforward way to add an attribute dynamically in wikitext? In my case, I want to add the checked attribute only for the first item in my list. I struggled a long time and found nothing that would work.

  • Is there a cleaner way of overriding the height of certain nested elements based on an input parameter to the macro? I end up adding a class name to the root element and writing a stylesheet, which feels heavyweight. (Note that I cannot put this on the elements themselves, because it only applies based on the CSS :checked pseudo-class of a sibling.)

  • Whether or not the previous version is the right way to do it, is there a cleaner way to write that stylesheet than my addprefix/addsuffix-laden filter? I’m simply trying to generate

    <style>
        .accordion.ac-accordion-1905655034 input:checked ~ article {height: 250px ;}
    </style>
    

    using the variable acc, with value of accordion-1905655034 and the macro parameter height of 250px. I feel like this should be simple, but everything I tried failed. Some was problems with extra spaces where I couldn’t have them. (This needs to be void of spaces: .accordion.ac-accordion-1905655034) And some of it was an inability to properly get the value of my height parameter to show up. I’m feeling like all this was simple lapses on my part. I have a version that works. Can someone show me how to do it better?

  • Does anyone know how to update the CSS transitions so that a hidden panel has scroll: hidden until the transition is complete and it gets scroll: auto. (I haven’t done any real research on this one yet. Please don’t do any digging on my behalf, but if you happen to know, I’d appreciate it if you could share.)

  • More generally, what other things should be improved in this code? Where am I doing things the hard war, or the ill-performant way? Where am I missing Tiddlywiki best practices. (E.g., should I be displaying captions if they exist and titles otherwise?) Am I really going off the rails by not using the State mechanism? Etc.

2 Likes

@Scott_Sauyet I don’t know the technical details. I just did a short testing in mobile. My first impression is it’s very nice with soothing animation.

1 Like

Oh, nice! I hadn’t gotten around to mobile testing. Glad to know it works.

(semi-)Answering my own question here. While I still don’t know how to dynamically set an attribute, I can reduce the pain point to just one spot, and not a whole <$list>:

<$list filter="[<counter-first>match[yes]]">
        <input id=<<id>> name=<<acc>> type="radio" checked="checked">
</$list>
<$list filter="[<counter-first>!match[yes]]">
        <input id=<<id>> name=<<acc>> type="radio">
</$list>

This is much nicer, and I blame the wee hour hours of the morning for my not seeing it earlier. :slight_smile: . I’ll update the demo soon with this fix. Updated. The new version is still at https://crosseye.github.io/TW5-demos/2023-01-28a/. The first one is at https://crosseye.github.io/TW5-demos/2023-01-28a/0.0.1/.

Could you show the actual code you want but cannot achieve?

And I just found one more question that I forgot to ask:

I <$set> the local variable height from the macro parameter height so that I can use it like this:

<style>
    <$text text={{{ [<acc>addprefix[.accordion.ac-]addsuffix[ input:checked ~ article {height: ]addsuffix<height>addsuffix[ ;} ]] }}} />
<!--     ... way over there ==>                                                                          ^^^^^^^^     <== nope, too far     -->
</style>

There clearly should be a way to use that parameter directly, but I can’t figure out the syntax.

How do I use the value of a macro parameter inside a filter?

It was in the discussion section, but I also figured that one out on my own. Much simpler than the gymnastics I was trying last night.

I still would love to know if there is a still simpler way to achieve this, but this update reduced the duplication until it doesn’t bother me too much.

1 Like

I was trying to use the accordion macro in the left sidebar of my wiki - See the demo. I am seeing one problem. The tiddler contents are not shown correctly. When I tried to do the same using Mohammed’s accordion macro, it was working correctly - may be because in his macro Mohammed is transcluding the tiddlers in block mode. Can you do something about it?
Also can a config option be added to enable double clicking the header to close its contents. I know your vision is opposite of that. Still it would be nice to have that option even if it’s not default config.

Possibly, possibly not. This is mostly a learning exercise for me, and I will continue to poke at it a bit, and see if I can fix that. But I don’t expect (at least for now) to put this up as a supported plugin. I’m not ready for that – I’m behind on the two I’ve already started! But in order to debug, it would help if you could send my the details of the call to the accordion. I might be able to find it, but it’s hard to navigate, because your sidebar is broken! :wink:

Probably not, or at least not easily. This is only slightly integrated with TW, and I don’t think there are likely to be any mechanisms to do that because of the way it’s built. This works by creating a group of hidden radio buttons. Each one is tied to a label, which is the visible header, and they’re together in a container with an article that houses the content. The underlying HTML/JS engine is responsible for deselecting the other radios when a new one is chosen, and our CSS uses that for hide/show and transitions. But the underlying system does not give a way to deselect all buttons, so I don’t see how this can be accomplished.

Sorry about that.

I will upload a fixed version later.

1 Like

@Scott_Sauyet https://focus-tiddler-demo.tiddlyhost.com/
Check this wiki to see what I am trying to do in the right sidebar.
Here I have used callout macro from shiraz. I will add a tiddler using accordion macro later today.

@arunnbabu81: I see what you’re doing there, and I’ll try to look this evening.

@arunnbabu81: Sorry, didn’t manage to get to this yesterday, and probably won’t today either. I will look over the weekend.