Variable and widgets and mixing them all

Here is a bit of real code of mine:

\define createItemButtons(nature id origin)
<$button actions=<<show$nature$ id:"$id$" origin:"$origin$">> class="show">See</$button>
<$button actions=<<edit$nature$ id:"$id$" origin:"$origin$" clear:"clear">> class="ed">Edit</$button>
\end

This is all very well if the origin variable can be supplied. But this is not always possible. So I had design a variable to take care of that. But I can’t use it because TW syntax doesn’t allow it. I can’t use $(var)$ for a variable set at the same level (but I can for a “let” variable at the same level - go figure!). Thus, I would need an auxiliary function, just to be able to use either $origin$or $(origin)$. This is annoying. And even more annoying because that pesty function would be global if my tiddler is tagged with “$:/tags/Macro”, forcing me to create a new tiddler and import it (cumbersome). So the best code I am able to use is illustrated below (using a function as a variable to avoid having the problem of a real function):

\define createItemButtons(nature id origin)
<$let seeBtn="""<$button actions=<<show$nature$ id:"$id$" origin:"$(origin)$">> class="show">See</$button>"""
editBtn="""<$button actions=<<edit$nature$ id:"$id$" origin:"$(origin)$" clear:"clear">> class="ed">Edit</$button>""">
<$set name=origin select=0 filter="[[$origin$] [<currentTiddler>get[host]] [<currentTiddler>] +[dump[3 origins]!is[null]first[]dump[origin selected]]">
<<seeBtn>>
<<editBtn>>
</$set></$let>
\end

How would you rate this code? What would you do yourself?

I rate this code as poor indeed. $(origin)$ produces an empty result within the variables. Real macro are needed, alas.

If you use the $macrocall widget, you can pass a variable as a macro parameter.

Try something like this:

\define createItemButtons(nature id origin)
<$let origin={{{ [[$origin$]] [<currentTiddler>get[host]] [<currentTiddler>] }}}>
<$button actions="""<$macrocall $name="show$nature$" id="$id$" origin=<<origin>>/>""" class="show">See</$button>
<$button actions="""<$macrocall $name="edit$nature$" id="$id$" origin=<<origin>> clear="clear"/>""">> class="ed">Edit</$button>
</$let>
\end

Note: Macros ARE variables, just with a little added preprocessing to handle substitutions ($arg$ and $(var)$).

The reason the seeBtn and editBtn “variables” work is because the $let widget is, in effect, defining private macros that only exist within the scope of the containing createItemButtons() macro.

Upon entry to createItemButtons(), all the references to $nature$, $id$ and $origin$ are immediately replaced with the passed-in values. However, as long as the origin variable was not defined before invoking createItemButtons(), the references to $(origin)$ are left unchanged.

Then, when <<seeBtn>> and <<editBtn>> are subsequently invoked, the origin variable has been defined by the surrounding <$set> widget, allowing the references to $(origin)$ occuring within seeBtn and editBtn to be replaced by the currently assigned value.

-e

1 Like

@EricShulman Very clear post. It would nicely fit in the official docs!

Eric, No ones ever said that before that I can remember, very elegant. Perhaps it could be extended to;

Note: Macros ARE variables, just with a little added pre-processing to handle substitutions ($arg$,$(var)$ and <<__var__>> ).

But then this made me wonder why variables can’t also not use the $(var)$ form. It would allow a very easy way to concatenate.

<$let state-tiddler="$:/state/$(currentTiddler)$">

</$let>

Or just

state="$:/state/$(currentTiddler)$"

Good question!

@jeremyruston would it not be possible to treat the rendering of tiddlers as though they were part of an anonymous macro definition where all current global-scope vars were available using that syntax?

Not completely sure what that would imply across the board, though. Or, indeed, the impact on backward compatibility. :thinking:

forward compatibility would be great anyway. The joy of something like below:

<<myMacro "$one$" "$(two)$">>

But what would be even better is the ability ro have a real macro appeal within a macro appeal and also the use of a widget within a widget. This is currently impossible and necessitate some fiddling taking time and bringing obscurity if not obfuscation.

<<myMacro <<myOthermacro "$one$" "$(two)$">> "second outer argument">>
<$list
filter=<$macrocall $name=myFilterMacro param01=firstName/>
template="""<$transclude tiddler=<<myMacroForFetchingSource 42>> field="myTemplateField$extension$"/>"""
/>

This coding is fare from being possible today but wouldn’it be fine if it was?

The $param$ and $(var)$ syntaxes are both processed as part of the textual substitution step when a macro is invoked. I think you’re asking whether there could be the same kind of textual substitution step when transcluding tiddlers.

A couple of answers:

  • Textual substitution breaks caching the parse tree corresponding to a tiddler or macro
  • While textual substitution can be a convenient way to handle concatenation, it has turned out not to be sufficient for robust macro parameters (hence the newer <<__param__>> syntax). Now I think it would have been better to provide textual substitution as a separate feature, probably as something like the JS templated string syntax

Interpolation style? Yes. Interesting. TWX I guess.

Perhaps he was; but Actually here I was simply wondering if when defining a variable with “set vars and let” to permit the use of $(varname)$ as if we were defining a macro. It would be the only valid form of substitution in a “set, vars and let” since the others are drawn from parameters.

It looks nice on the surface at least, but I defer to others if it were even possible.

<$let state-tiddler="$:/state/$(macroORvarname)$">

</$let>

It may actually imply it needs to be wikified before substitution. However no parameters can be passed, But perhaps this could be a way to get rid of the ugly wikify step;

<$wikify name=macroORvarname text="<<macroORvarname>>">
   Use the wikified <<macroORvarname>>
</$wikify>

Could just become;

  Use the Wikified substitution "$(macroORvarname)$"
or tooltip="Use $(macroORvarname)$ to do xyz"

But I expect I am just dreaming, and there is some underlying issue that makes this impossible. Perhaps confused by my procedural languages coding experience.

But regardless;

It would be a lovely functional advantage.

1 Like

you don’t need wikify. The following should work too and is also more flexible and less error prone

\define getState() $:/state/$(myVar)$

<$let myVar="something">
...
<$reveal state=<<getState>>... 

The advantage here is, that the \define getState() can be changed once and all parameters that use it are also changed. No risk to create typos. Even if there is a typo it is consistent for all params where <<getSate>> is used. So the code would still work

1 Like

Perhaps state was a bad example. Sometimes I need a lot of defines and it would be nice doing it inline but yes define when multiple use.

Back in 2018 we fixed a bug whereby textual substitution of $(vars)$ up until that point had erroneously been applied to all <$set> widgets, not just those that originated as a macro definition with \define. The ticket is here:

The problem was that it made it impossible to ever assign a string matching the substitution syntax. So, for example, attempts to wikify the documentation about the textual substitution feature would result in strange dropouts where every $(var)$ was removed.

As noted in one of my comments, we could add an attribute for the <$set> widget that made it possible to opt into textual substitution, but there would have to be a strong use case that wasn’t met by the existing \define syntax.

Thanks @jeremyruston

It is interesting to see the history. There is too much water under the bridge I expect, given, that what I asked for was in the past an unintended consequence, that resulted in a restriction. It All gets a bit too “meta” :upside_down_face:

  • A rhetorical question (no need to answer, perhaps only reflect on), would it be different it it was a planned and “intended consequence”, designed not to have side effects?

As a user focused on wiki text I do find setting a value in macros \define (effectively self closing variable definitions) useful, especially ahead of the “need” to close set, vars and let statements.

However macros do force the reader of wiki text each time a macro is used, to have to scroll to the top of the wikitext or seek out a global macro, at least when debugging.

  • I know you do not need to always close many widgets, if you can let it happen at the end of the tiddler, but I can’t trust this (as Eric does a lot) because I like to write reusable code modules, If I do not ensure closure, this causes problems when I reuse that code.

However I do crave a way to apply $wikify and $text INLINE. They seem such basic needs, it is a pity we do not have a shorthand form as we do for transclusions. They do complicate coding and confuse newer users. A wikify shortcut that permitted $(var)$ could possibly address the same need to permit inline concatenation as well.

Never the less, I am aware of my incomplete understanding of how parsing and wikitext is processed.

There is some good relevant discussion in the following thread, starting at the linked post, around ideas for making text substitution easier and why the use of wikify is an anti-pattern that should not be encouraged:

The need for better affordances for text substitution and concatenation in the core is pretty well established. The key is to add the affordance in a manner that has the greatest utility, which in turn means making it available to most widgets, leading to thinking around adding this via filters or a different syntax for widget attributes.

A different topic worth considering is whether we might be able to to support macro definitions in the body of a tiddler.

I thought this as well, but it could add complications we may not want. It depends on how it is implemented.

Then surely we must consider the cases where is is forced on us, and eliminate them?

I will try and come up with a good example where the wikify is forced on us. My feeling is we could hide this issue inside how some widgets and filters work.

Until then consider any example where the wikify widget is necessary, especially where it is wise to use it just in time.

<$wikify name=macrowikified text="""<<macroname>>"">
   ...
   <<macrowikified>>
   ...
</$wikify>

Searching system tiddler for <$wikify gives me $:/core/ui/Actions/new-journal consider this;

\define get-tags() $(textFieldTags)$ $(tagsFieldTags)$
<$vars journalTitleTemplate={{$:/config/NewJournal/Title}} textFieldTags={{$:/config/NewJournal/Tags}} tagsFieldTags={{$:/config/NewJournal/Tags!!tags}} journalText={{$:/config/NewJournal/Text}}>
<$wikify name="journalTitle" text="""<$macrocall $name="now" format=<<journalTitleTemplate>>/>""">
<$reveal type="nomatch" state=<<journalTitle>> text="">
<$action-sendmessage $message="tm-new-tiddler" title=<<journalTitle>> tags=<<get-tags>> text={{{ [<journalTitle>get[]] }}} journal-date=<<now "YYYY0MM0DD0hh0mm0ss000">>/>
</$reveal>
<$reveal type="match" state=<<journalTitle>> text="">
<$action-sendmessage $message="tm-new-tiddler" title=<<journalTitle>> tags=<<get-tags>> text=<<journalText>>  journal-date=<<now "YYYY0MM0DD0hh0mm0ss000">>/>
</$reveal>
</$wikify>
</$vars>

I am going on a hunch here, and happy to be corrected but I think wikify is only needed for the filter {{{ [<journalTitle>get[]] }}} to work. I think all its other uses can be rendered as normal.

We would need, perhaps as a global macro.
\define jounalTitle() <$macrocall $name="now" format=<<journalTitleTemplate>>/>

and I am not sure why, when a filter encounters [<jounalTitle>] it uses;
<$macrocall $name="now" format=<<journalTitleTemplate>>/>

and why it can’t “wikify” <jounalTitle> it to its value before use because this is clearly the intention.

Yes, I am wrong <<journalTitle>> needs to be wikified for more than just the filter.

Absolutely. Providing a more convenient way to do text substitution and concatenation is a part of that. The other part of it is encouraging better code patterns that use transclusion, templates and filters instead of writing macros and wikifying them to obtain a value or string to use in a filter or widget attribute. Some of the planned improvements for transclusion should help here.

A good example of code patterns is the TW core itself. Despite the entire interface user interface being built out of wikitext, the wikify widget is only used a small handful of times and even some of those instances could be avoided today given improvements in filters and other wikitext primitives.

If we were implementing that from scratch today the code might look something along these lines instead of the wikify widget usage:

<$let journalTitle={{{ [<now "YYYY0MM0DD0hh0mmss">format:date<journalTitleTemplate>] }}}>

1 Like

Using $wikify enables the use of other wikitext syntax to construct the journalTitle.

For example, if $:/config/NewJournal/Title contains DDth MMM YYYY ({{$:/status/UserN\ame}}), then it will produce a journal title like “15th April 2022 (Eric)”

Note: the backslash in “UserN\ame” prevents the “am” in the tiddler title from being processed as a special date formatting sequence. Otherwise, it would be converted to $:/status/UserNpme when the current time is after noon.

-e

Hi Eric. Yes I realize this which is why I only gave an example of what the approach might look like today if we had a fresh start, as opposed to it being a drop in replacement that preserved backwards compatibility. For greater flexibility it could be combined with a syntax allowing easier text substitution for variables.

We have had bugs reported related to characters in title templates being unexpectedly processed as date markers as most users do not know to use backslash, which only points to the brittleness of the current approach.