Tabs macro default tab

Something that has annoyed me ever since I found the built-in tabs macro, is that if you don’t supply a default tab, then the default display is that no tab is selected. IMHO, that is plain ugly and also inconsistent, since once you have selected a tab there is no way to go back to nothing selected (apart from deleting the relevant state tiddler).

I created a modified tabs macro that defaults to the first title if no default is supplied and it seems to be OK:

Edit: the changes are the two $set widgets after the $qualify

\define tabs(tabsList,default,state:"$:/state/tab",class,template,buttonTemplate,retain,actions,explicitState)
\whitespace trim
<$qualify title=<<__state__>> name="qualifiedState">
<$set name="firsttab" filter="[filter<__tabsList__>first[]]">
<$set name="__default__" filter="[<__default__>is[tiddler]else<firsttab>]">
	<$let tabsState={{{ [<__explicitState__>minlength[1]] ~[<qualifiedState>] }}}>
		<div class={{{ [[tc-tab-set]addsuffix[ ]addsuffix<__class__>] }}}>
			<div class={{{ [[tc-tab-buttons]addsuffix[ ]addsuffix<__class__>] }}}>
				<<tabs-tab-list>>
			</div>
			<div class={{{ [[tc-tab-divider]addsuffix[ ]addsuffix<__class__>] }}}/>
			<div class={{{ [[tc-tab-content]addsuffix[ ]addsuffix<__class__>] }}}>
				<<tabs-tab-body>>
			</div>
		</div>
	</$let>
</$set>
</$set>
</$qualify>
\end

Is there a reason for defaulting to no tabs? Why not default to the first title?

1 Like

I share your point of view. I’m using the following workarounds for my purposes:

  • if the tab list is dynamic, I set a filter-based variable that hold the tab tiddlers; the tabs macro uses the first element from this list as the default tab
  • an empty tab tiddler with caption “×” is added to the end of the tab list, selecting this empty tab can be used to “collapse” the tabs macro.

A simplified pseudo code

<$set name="tabsList" filter="filter expression that returns relevant tabs, including the empty × tab at the end" >
	<% if [<tabsList>!match[the empty × tab]] %>
		<!-- Do not show tabs macro at all if the only tab in tabsList is the empty × one -->
		<$transclude $variable="tabs" tabsList=<<tabsList>> default={{{ [enlist<tabsList>] }}}  />
	<% endif %>
</$set>

No tabs has been the default behavior, changing it now would break backwards compatibility.
But maybe some easy way to optionally use first tab as default or to have some way to close all tabs would be worth implementing?
E.g. <<tabs "tab1 tab2" defaultFirst:"yes" allowReset:"yes">>, but with some better choice of wording.

1 Like

I’ve thought about this problem too, but without getting around to generating the workaround or posting about it here.

It seems the direct specification of the default tab was a reasonable behavior back when tab sets tended not to be dynamic. Given the increasing power of dynamic sets of tabs, we really need to have a dynamic way of setting the default tab.

Thanks for starting the conversation, along with directions toward a solution!

If the default tab setting was a filter then it would continue to accept a named tiddler Or ‘[tag[tagname]first[]]’

I think wherever we permit a title as the value of a parameter we could enhance it by permitting a filter

However given tw 5.3.x you can do this as follows;

Default=`${ [tag[tagname]first[]] }$`

Ah, I realize my point of frustration is not with basic tabs macro, but with toc-tabbed macros, which do not seem to have any default parameter at all! (I didn’t realize this until I tried the solution by @TW_Tones — in my toc-tabbed solutions — and then went looking for the Default parameter documentation when it didn’t work :rofl:. Not just not dynamic — not there.)

I would love to have this parameter available, and — in parallel with the theme of this post — specifiable by way of a filter so that it can be effectively used in templates, etc.

1 Like

It would be different, but I think it would be backwards compatible. I think “not showing anything” has never been misused by 3rd party authors, which would prevent us to change it now.

IMO the first item would be a good choice.


There is also an issue, if the current state of the tabs macro points to a “non existent” tiddler. This also shows “nothing”, which imo is a pain.

2 Likes

I would add too, that the tabs filter or a custom operator can return a list of titles as follows;

introTab [tag[tabtag]]

where introtab could be defaulted and contain a message such as select one of the above tabs to see its contents.

  • in many cases this could also contain sophisticated info about the tab including a draggable list, history and more.
  • an abouttabs tiddler also

It seems for backwards compatibility, the existing tabs macro is unlikely to be changed.

So, after a bit of playing around, I’ve come up with a new macro:

\procedure tabs-defaultfirst(tabsList,default,state,class,template,buttonTemplate,retain,actions,explicitState)
<$transclude $variable=tabs
  tabsList=<<tabsList>>
  default={{{ [<default>is[tiddler]] ~[subfilter<tabsList>first[]] }}}
  state=<<state>>
  class=<<class>>
  template=<<template>>
  buttonTemplate=<<buttonTemplate>>
  retain=<<retain>>
  actions=<<actions>>
  explicitState=<<explicitState>>
/>
\end

It seems a bit nasty to be needing to explicitly copy all the parameters, but it works (unlike my code above which failed when tiddler titles had spaces).

I’m not sure why this would be so, and @pmario’s comments also seem open to an improvement.

No prior expectations would be broken by adding, for example, an additional available parameter to the tabs macro — which I’d suggest calling avoidnullstate or something like that. When specified, it causes the tab macro to display the first tab if there is no selected or default value specified. (Of course, if the tabs macro is fed a filter with zero results, that’s a different problem. “Avoid” implies an attempt, not magical prevention. :wink: )

I’d suggest doing the same for the tabbed-toc macros.

Thanks. The problem I had was that my original attempts at modifying the tabs macro all failed when dealing with tiddlers with spaces in their title. However, you’ve encouraged me to revisit that, and this is what I’ve come up with:

\define tabs(tabsList,default,state:"$:/state/tab",class,template,buttonTemplate,retain,actions,explicitState,defaultFilter)
\whitespace trim
<$qualify title=<<__state__>> name="qualifiedState">
	<$let
		__defaultFilter__={{{ [<__defaultFilter__>minlength[1]] ~[{$:/config/TabsMacro/defaultFilter}] }}}
		__default__={{{ [<__default__>is[tiddler]] ~[subfilter<__tabsList__>subfilter<__defaultFilter__>] }}}
		tabsState={{{ [<__explicitState__>minlength[1]] ~[<qualifiedState>] }}}
	>
		<div class={{{ [[tc-tab-set]addsuffix[ ]addsuffix<__class__>] }}}>
			<div class={{{ [[tc-tab-buttons]addsuffix[ ]addsuffix<__class__>] }}}>
				<<tabs-tab-list>>
			</div>
			<div class={{{ [[tc-tab-divider]addsuffix[ ]addsuffix<__class__>] }}}/>
			<div class={{{ [[tc-tab-content]addsuffix[ ]addsuffix<__class__>] }}}>
				<<tabs-tab-body>>
			</div>
		</div>
	</$let>
</$qualify>
\end

(Edit: rolled the $sets into the $let, Edit2: replaced backtick braces with triple braces, for consistency with existing code)

How it works:

New parameter defaultFilter is a filter that is applied to tabsList to determine the default tab. If you want to default to the first tab title, set it to [first[]]. Maybe someone would prefer [last[]]? Or something else!

I’ve also added support for a tiddler $:/config/TabsMacro/defaultFilter. If you’d like all your tabs macros to have a default filter of [first[]] (like I’d like it to), then that filter goes in this tiddler.

If you have none of this defaultFilter stuff, then it works just like now.

I did a test with this code. No extra options. first[] is hardcoded. It makes the code much simpler. IMO a new parameter increases complexity quite a bit.

This solution still has the problem, that it shows an empty body, if the state-tiddler contains a non existent tiddler title. eg: If that tiddler got deleted. But since that’s a much rarer edge-case I did not bother to resolve that one. It makes the whole code much more complex, imo for a very little win.

\define tabs(tabsList,default,state:"$:/state/tab",class,template,buttonTemplate,retain,actions,explicitState)
\whitespace trim
<$let __default__={{{ [<__default__>is[tiddler]then<__default__>]
	:else[subfilter<__tabsList__>enlist_input[]frist[]] }}}
>
<$qualify title=<<__state__>> name="qualifiedState">
	<$let tabsState={{{ [<__explicitState__>minlength[1]] ~[<qualifiedState>] }}}>
		<div class={{{ [[tc-tab-set]addsuffix[ ]addsuffix<__class__>] }}}>
			<div class={{{ [[tc-tab-buttons]addsuffix[ ]addsuffix<__class__>] }}}>
				<<tabs-tab-list>>
			</div>
			<div class={{{ [[tc-tab-divider]addsuffix[ ]addsuffix<__class__>] }}}/>
			<div class={{{ [[tc-tab-content]addsuffix[ ]addsuffix<__class__>] }}}>
				<<tabs-tab-body>>
			</div>
		</div>
	</$let>
</$qualify>
</$let>
\end

My experience of TiddlyWiki development has been of a keen interest in not changing existing functionality - that would include no selected tabs if no default was specified. I’ve already stated in my OP I think no selected tabs is a silly idea, in which case it should be as simple as (your then<__default__> and enlist_input[] seem superfluous):

\define tabs(tabsList,default,state:"$:/state/tab",class,template,buttonTemplate,retain,actions,explicitState)
\whitespace trim
<$qualify title=<<__state__>> name="qualifiedState">
	<$let
		__default__={{{ [<__default__>is[tiddler]] ~[subfilter<__tabsList__>first[]] }}}
		tabsState={{{ [<__explicitState__>minlength[1]] ~[<qualifiedState>] }}}
	>
		<div class={{{ [[tc-tab-set]addsuffix[ ]addsuffix<__class__>] }}}>
			<div class={{{ [[tc-tab-buttons]addsuffix[ ]addsuffix<__class__>] }}}>
				<<tabs-tab-list>>
			</div>
			<div class={{{ [[tc-tab-divider]addsuffix[ ]addsuffix<__class__>] }}}/>
			<div class={{{ [[tc-tab-content]addsuffix[ ]addsuffix<__class__>] }}}>
				<<tabs-tab-body>>
			</div>
		</div>
	</$let>
</$qualify>
\end

I’d like to see the built-in tabs macro changed, it’s a question of what degree of changes would be considered acceptable?