Wikitext version of switch/nested if-else/nested ternary statements

Is there a simpler wikitext implementation of the following code?

\define results(candidacy)
<$text text={{{ [<__candidacy__>!has[won]then[ ]] }}} />
<$text text={{{ [<__candidacy__>get[won]match[yes]then[✅]] }}} />
<$text text={{{ [<__candidacy__>get[won]match[no]then[❌]] }}} />
<$text text={{{ [<__candidacy__>get[won]match[pending]then[◎]] }}} />
\end

This is working code, but it seems far too redundant. In JS, I would probably do something like

const results = (c, {won = ''} = c ) => 
  won == 'yes' ?  '✅' : won == 'no' ? '❌' : won == 'pending' ? '◎' : ' '

but it could easily be done with a switch statement or a sequence of if-else statements.

Are there cleaner TW techniques for this?

I’m assuming this could be done with a data-tiddler equivalent of the following code, but that also feels more heavy than I would like for this simple function:

const results = (c) =>
  ({yes: '✅', no: '❌', pending: '◎', '': ' '})[c?.won ?? '']

Is there a simple technique I’m missing?


(I do recognize that these are not precise equivalents to the first version above, with very slightly different behaviors if there is a won property that differs from the three values I define. These JS ones are actually the preferred behavior, but the difference won’t matter in my wiki.)

2 Likes

Hi @Scott_Sauyet

Not simpler, but here’s another version:


\define results(candidacy)
<$text text={{{ 
   [<__candidacy__>!has[won]then[ ]]
  :else[<__candidacy__>get[won]match[yes]then[✅]]
  :else[<__candidacy__>get[won]match[no]then[❌]]
  :else[<__candidacy__>get[won]match[pending]then[◎]]
 }}} />
\end

Also, depending on the value of candidacy, this could work: :else[{$candidacy$!!won}match[...

Fred

1 Like

You could also do something very similar with a function and thus eliminate the text widget:

\function results(candidacy)
[<candidacy>!has[won]then[ ]]
~[<candidacy>get[won]match[yes]then[✅]]
~[<candidacy>get[won]match[no]then[❌]]
~[<candidacy>get[won]match[pending]then[◎]]
\end

Or a slightly different approach:

\function results(candidacy)
\define yes() ✅
\define no() ❌
\define pending() ◎
[<candidacy>get[won]getvariable[]]
\end

It is somewhat cleaner, though, as it doesn’t involve multiple text widgets.

I like avoiding the multiple <$text...> widgets, but there is still a great deal of duplication… which of course would get worse with more cases.

And we have a winner! This is very nice. I know I’ve seen getvariable, and maybe used it once, but it doesn’t leap to mind for me yet. It’s perfect here.

Thank you both!

1 Like

Or perhaps I spoke too soon.

I don’t have it working yet. In my existing code, I had this definition in a tiddler with the tag $:/tags/Macro and I called it like this:

<$transclude $variable="results" candidacy=<<candidacy>> />

or

<$transclude $variable="results" candidacy=<<currentTiddler>> />

The same does not work for this version, even if I use $:/tags/Global.

Am I missing something else that would help me use this?

Hmm, I assumed it would be possible to nest a single-line pragma within a function definition (as you can do with procedures and traditional macros), but apparently it’s not. Moving the variable definitions outside the function should fix it, though. Sorry about that!

\define yes() ✅
\define no() ❌
\define pending() ◎
\function results(candidacy)
[<candidacy>get[won]getvariable[]]
\end

I can have a look @Scott_Sauyet given some recent tricks I have developed with functions. I will update this reply.

  • referencing a function as a variable only returns the first result, and allows you to avoid the text widget.
  • I have being seeking a case structure.
  • in this case its fine but with such questions it is of value explaining the desired outcome with plain language or pseudocode so we;
    • re do not need to reverse engineer the code
    • we dont get caught by any wrong assumptions in that code.

Here are two examples depending on the parameters you wish to use;

\function display.status() [match[yes]then[✅]] :else[match[no]then[❌]] :else[match[pending]then[◎]] :else[[ ]]

# display.status '{{{ [<currentTiddler>get[won]display.status[]] }}}'
  • In this one you have to give the value you want a status symbol for, in this case currentTiddler is the candidacy
  • use text to stop the result being a link :frowning:
\function field.status(fieldname:"won") [all[current]get<fieldname>match[yes]then[✅]] :else[all[current]get<fieldname>match[no]then[❌]] :else[all[current]get<fieldname>match[pending]then[◎]] :else[[ ]]

# field.status '<<field.status>>'
# field.status of status-field '<<field.status status-field>>'
  • Again the current tiddler is the candidacy,
  • defaults to won field
  • It using preview you need to refresh the preview after changing the field.
  • Unlike the first this does not result in a link, but only text :grinning_face_with_smiling_eyes:
  • This allows any field to have the same rules applied

Notes
You can see here we can use functions, filters and there results can be displayed, or used in filters of subsequent conditional sections.

  • I am confident we can build switch (we have above), case, if then else etc…, in functions.
  • If you see recent examples of recursion using two functions, we may even be able to nest these.

I was working on reducing the length of the filter in field.status by using one or more “filter run prefix”, but I am not yet that well versed in their use.

  • It is not that importiant
  • Perhaps someone else can suggest how?

However

We can abstract our functions more to both simplify the way they are used, read and make them much more reusable. See my next post.

As I mentioned here we can simplify functions to “to both simplify the way they are used, read and make them much more reusable”.

This function is easy to read;

\function short.status(fieldname:"won") [status.field[]if.yes.then[]] :else[status.field[]if.no.then[]] :else[status.field[]if.pending.then[]] :else[[ ]]

# short.status of status-field '<<short.status>>'

To support this we defined a number of sub functions, however you will notice they initialy look more complex but each individual one is simple, and they become available to use and reuse just by calling them.

\function current.field(fieldname) [all[current]get<fieldname>] <!-- not in use -->
\function status.field(fieldname:"status-field") [all[current]get<fieldname>]
\function if.yes.then(symbol:"✅") [match[yes]then<symbol>]
\function if.no.then(symbol:"❌") [match[no]then<symbol>]
\function if.pending.then(symbol:"◎") [match[pending]then<symbol>]
\function short.status() [status.field[]if.yes.then[]] :else[status.field[]if.no.then[]] :else[status.field[]if.pending.then[]] :else[[ ]]

Note:
Since if.yes and if.no are too generic, you can rename these to if.won and if.lost

I try and always write my functions, macros etc… to act on the current tiddler, so all I need to do is choose which tiddler to apply it to.

I have a hunch this can be reduced even more.

It sure does!

Not at all! Thank you very much for your help.

These are both nice improvements on my original code. But I think @etardiff’s version is overall the simplest.

I can see the value in that for other uses. For this one, it’s just a distraction.

That trips me up all the time!

It should be possible. I’ll try to look soon.

2 Likes

Yes, I could implement the same reduction @etardiff did into my solution as well. By using a getvariable as a result if the goal is to reduce as much as possible, but my approach is to abstract, generalise and self document, especialy the second (short.status) example.

For a simple solution Emily’s is the best.

Further to @etardiff’s solution.

  • FYI in @etardiff’s solution the defines can be replaces with a \function or \procedure without change in behaviour
  • My desire to explore functions and abstract leads me to observe;

We could define a set of “unicode” icons for any known value yes, no, maybe, true/false, on/of show/hide, pending etc…

I may share this extended discussion in its own topic

1 Like

Just for fun, here’s a genericized approach that lets you specify a field to check, a list of possible options and the desired output for each, and a fall-back option to use if the field is blank:

\procedure conditional-display(field, options, blank)
<$setmultiplevariables
	$names="[<options>split[; ]] :map[split[: ]nth[1]]"
	$values="[<options>split[; ]] :map[split[: ]nth[2]]">
<$transclude $variable={{{ [<currentTiddler>get<field>] ~blank }}} />
</$setmultiplevariables>
\end

<<conditional-display "won" options:"yes: ✅; no: ❌; pending: ◎" blank:"something else!">>

In this case, since we’re constructing variables on the fly and using using $transclude rather than $text to display them, you can use wikitext in a “definition” and it will be properly wikified for display. So, for instance, this would also work:

<<conditional-display "won" options:"yes: ✅; no: {{{ [tag[HelloThere]] }}}; pending: ◎" blank:"[[HelloThere]]">>

I chose to use : to separate each value and its output, as a dictionary tiddler does, and ; to separate each value pair in the list—but you could change them to the delimiters of your choice. If we wanted to put each pair on its own line, for example:

\procedure conditional-display(field, options, blank)
<$setmultiplevariables
	$names="[<options>splitregexp[\n]] :map[split[: ]nth[1]]"
	$values="[<options>splitregexp[\n]] :map[split[: ]nth[2]]">
<$transclude $variable={{{ [<currentTiddler>get<field>] ~blank }}} />
</$setmultiplevariables>
\end

<<conditional-display 
	field:"won"
	options:"yes: ✅
no: {{{ [tag[HelloThere]] }}}
pending: here's some longer text that could benefit from its own line"
	blank:"[[HelloThere]]">>
1 Like

This is a good point! In this case, I used \define rather than one of the more “modern” alternatives because 1) it’s slightly shorter, and 2) it highlights the fact that we’re defining variables to be used by the function.

I have used that too :nerd_face:

@etardiff some interesting ideas in your conditional-display, it expands in my mind what can be passed into a macro. I too have exploered this further but its better I stop exploring this path for now. I wont start another topic. So here is my somewhat final example;

\widget $value.icon(field:"fieldname" tiddler tiddlervar:"currentTiddler") 
   \function yes() ✅
   \function no() ❌
   \function pending() ◎
   \function tiddlername() [<tiddler>!is[blank]] :else[<tiddlervar>getvariable[]] 
   \function fieldvalue() [<tiddlername>get<field>] 
   \function value.icon()  [<fieldvalue>getvariable[]!is[blank]] :else[<fieldvalue>]
    <span title={{{ [<field>] [[has value:]] [<fieldvalue>] +[join[ ]] }}}><<value.icon>></span>
\end $value.icon

# <$let currentTitle="value icons with suffix"><$value.icon tiddlervar="currentTitle" /></$let>
# <$value.icon/>
# <$value.icon field=status/>
# <$value.icon tiddler="value icons with suffix" />
# <$value.icon field=status tiddler="value icons with suffix" />

Replace the tiddler names and fields with ones relavant to your own test data

  • Using the get variable we can actualy pass in a tiddlervar to use, otherise it uses currentTiddler or tiddlername (this is a great feature to use elsewhere)
  • We can use simple names for the value icons because they are only known within this widget, Keep It Simple Sweetheart (principal)
  • Widgets are good if the only thing you need from them is to display the result, as is the case, with displaying icons.
  • For any value you use you can come to the widget and give that value an icon and just use the same widget.
  • There is a tooltip for each icon, you could add the tiddlername as well.
  • If there is no matching icon the value will be displayed, so you can find an icon later if desired.

(Update: added a “bad data scenario” with a :bomb: and a “no value scenario” with a :question:)

Potential internals for a macro/function:

<$let won_options="yes:✅, no:❌, pending:◎, unknown:❓"
         won_val={{{ [[candidacy]get[won]else[unknown]] }}}>

<$text text={{{ [<won_options>split[,]regexp<won_val>split[:]nth[2]else[💣]] }}} />

</$let>
3 Likes

You could also use cascade prefix. Only you need have stored your filters to call it. I read in documentatio the use with append, it is not simpler. I found a more simple version of cascade with append filter, but you have to store al filter together.

My dummy test :

\define won() 2

\define filters() [match[1]then[A]] [match[2]then[B]]

{{{ [<won>]  :cascade[[]append<filters>] }}}

Maybe the longer version is more readable. but if you have many filter, you have to add n-1 time append operator.

\define won() 2

\define filter1() [match[1]then[A]] 
\define filter2() [match[2]then[B]]

{{{ [<won>]  :cascade[<filter1>append<filter2>] }}}
2 Likes

Not exactly what you were asking for, but if you have a lot of these value lookups you could store them in a datatiddler and use getindex[].

Yes, I mentioned that in the OP, and was trying that right now, getting stuck. I’ll post another version in a few minutes asking how to fix my approach.

I’ve been playing around with these. I think I agree with @stobot simply because it’s all too “wordy”. I did, however, have a brain-fart for a new operator…

Instead of…

get[won]match[yes]then[✅]get[won]match[no]then[❌]

use…

where[won],[yes],[✅],[❌]

EDIT: oops…

where{won},[yes],[✅],[❌]

1 Like

I’m curious as to why the following alternative does not work. I’m almost certain it’s a simple syntax error, but I can’t find it. You can test it with this:

UsingDataTiddler.json (5.5 KB)

Any ideas?

Definitions

\function lookup.in(table, key) [<table>getindex<key>] :else[<table>getindex[_default_]]
\function results.symbol(candidacy) [lookup.in[$:/_/adtc/lookups/election-wins],<candidacy>get[won]]

Content of $:/_/adtc/lookups/election-wins

{
  "_default_": " ",
  "yes": "✅",
  "no": "❌",
  "pending": "◎"
}

Sample Candidacy

caption: 1stSel - Adrian Mandeville (R)
contest: 2023-General
created: 20230821202714539
modified: 20231110203113532
name: Adrian Mandeville
organization: First Selectman
party: Republican
revision: 0
tags: Candidacy
title: Candidacy/1
type: text/vnd.tiddlywiki
votes: 392
won: no

lookup.in works as expected

Wikitext

* "{{{ [lookup.in[$:/_/adtc/lookups/election-wins],[yes]] }}}"
* "{{{ [lookup.in[$:/_/adtc/lookups/election-wins],[no]] }}}"
* "{{{ [lookup.in[$:/_/adtc/lookups/election-wins],[pending]] }}}"
* "{{{ [lookup.in[$:/_/adtc/lookups/election-wins],[]] }}}"

Results

  • :white_check_mark:
  • :x:
  • " "

But results.symbol does not

Wikitext

* "{{{ [results.symbol[Candidacy/1]] }}}"
* "<$list filter="[[Candidacy/1]]">{{{ [results.symbol<currentTiddler>] }}}</$list>"

Results

  • “”
  • “”
1 Like