Action-setfield and JSON

I can’t figure out what I’m doing wrong and I could need some help, please.
I’d like to add and remove elements to an example data tiddler like:

{
    "text": "no space left on device",
    "number": "0815",
    "colour": "red"
}

When I’m doing it with hard-coded indexes, it works:

\procedure btnact1()
<% if [{!!element1}match[]] %>
  <$action-setfield $tiddler=<<dataTiddler>> $index="element1" />
<% else %>
  <$action-setfield $tiddler=<<dataTiddler>> $index="element1" $value={{!!element1}} />
<% endif %>
\end btnact1

\procedure btnact2()
<% if [{!!element2}match[]] %>
  <$action-setfield $tiddler=<<dataTiddler>> $index="element2" />
<% else %>
  <$action-setfield $tiddler=<<dataTiddler>> $index="element2" $value={{!!element2}} />
<% endif %>
\end btnact2

<$let dataTiddler="data1">

!!Hard-coded index name
element1: <$edit-text field="element1" /> <$button actions=<<btnact1>> ><$text text={{{ [{!!element1}match[]then[clear]else[set]] }}} /></$button>  
element2: <$edit-text field="element2" /> <$button actions=<<btnact2>> ><$text text={{{ [{!!element2}match[]then[clear]else[set]] }}} /></$button>

<$transclude $tiddler=<<dataTiddler>> />

</$let>

As an improvement, I tried to write a code with indexes as a parameter. But this isn’t working as expected:

\procedure btnactions(field)
<$let val={{{ [<currentTiddler>get<field>] }}} >
<% if [<currentTiddler>has:field<field>!has<field>] %>
  <$action-setfield $tiddler=<<dataTiddler>> $index=<<field>> />
<% else %>
  <$action-setfield $tiddler=<<dataTiddler>> $index=<<field>> $value=<<val>> />
<% endif %>
</$let>
\end btnactions

<$let dataTiddler="data1">

!!Index as a parameter
element1: <$edit-text field="element1" /> <$button actions=<<btnactions element1>> ><$text text={{{ [{!!element1}match[]then[clear]else[set]] }}} /></$button>  
element2: <$edit-text field="element2" /> <$button actions=<<btnactions element2>> ><$text text={{{ [{!!element2}match[]then[clear]else[set]] }}} /></$button>

<$transclude $tiddler=<<dataTiddler>> />

</$let>

Set clears the whole data tiddler, which isn’t expected.
According to the docs, this should happen when neither $field nor $index is being set.
Adding some debug information, the variable <> has been set to the expected value.

I’m pretty sure I’m missing an important detail here - but which one is it?

Try replacing this:

<% if [<currentTiddler>has:field<field>!has<field>] %>

with:

<% if [<val>match[]] %>

-e

Your suggestion

<% if [<val>match[]] %>

was my last version I tried with before replacing it with

<% if [<currentTiddler>has:field<field>!has<field>] %>

I suspicted that I couldn’t retrieve an empty value the proper way, but unfortunately it is another (unknown) case.

After changing it back to your suggestion the tiddler is no longer empty, but it doesn’t change in any way, either.
I added some debug information to show what it does (or doesn’t):

\procedure btnactions(field)
<$let val={{{ [<currentTiddler>get<field>] }}} >
dataTiddler: <<dataTiddler>><br>
val: <<val>><br>
field: <<field>><br>
<% if [<val>match[]] %>
//if --> delete//
  <$action-setfield $tiddler=<<dataTiddler>> $index=<<field>> />
<% else %>
//else --> set//
  <$action-setfield $tiddler=<<dataTiddler>> $index=<<field>> $value=<<val>> />
<% endif %>
</$let>
\end btnactions

<$let dataTiddler="data1">

!!Index as a parameter
element1: <$edit-text field="element1" /> <$button actions=<<btnactions element1>> ><$text text={{{ [{!!element1}match[]then[clear]else[set]] }}} /></$button><br>
element2: <$edit-text field="element2" /> <$button actions=<<btnactions element2>> ><$text text={{{ [{!!element2}match[]then[clear]else[set]] }}} /></$button>

<$transclude $tiddler=<<dataTiddler>> />
<hr>
<<btnactions element1>><br>
<hr>
<<btnactions element2>>
</$let>

setfield4json.json (1.3 KB)

Found it! You need to put quotes around the $button actions params, like this:

element1: <$edit-text field="element1" /> <$button actions="<<btnactions element1>>" ><$text text={{{ [{!!element1}match[]then[clear]else[set]] }}} /></$button><br>
element2: <$edit-text field="element2" /> <$button actions="<<btnactions element2>>" ><$text text={{{ [{!!element2}match[]then[clear]else[set]] }}} /></$button>

Without the quotes, the btnactions macros are being evaluated when the $button widgets are initially rendered instead of when the buttons are clicked.

As an interesting aside… it also works if you use the old-style “inline” button actions:

element1: <$edit-text field="element1" /> <$button><<btnactions element1>><$text text={{{ [{!!element1}match[]then[clear]else[set]] }}} /></$button><br>
element2: <$edit-text field="element2" /> <$button><<btnactions element2>><$text text={{{ [{!!element2}match[]then[clear]else[set]] }}} /></$button>

in this syntax, the btnactions macros are also processed when the $button widgets are initially rendered and, because the macros contain $let val=..., they are also re-rendered on every keystroke, but the $action-setfield widgets are only processed when the button is clicked.

Note that while the old syntax works, the newer actions="..." syntax is better, since it avoids the extra re-rendering on each keystroke.

-e

1 Like

Thank you, Eric - this is the solution!
Big extra thanks for your detailed explanation.
It’s new to me that an actions-parameter works with enclosing quotation marks, too - I thought that this would mark it is a string and rendering the actions-macro-call unusable.

Another question comes to my mind:
What can I do to accept a “computed parameter” in an actions-parameter, let’s say a variable?
...actions=<<btnactions <<param1>> >>
… wouldn’t work, obviously.

This is what happens and that is why it works. If not marked as a string it is “wikitext” statement as a parameter value, and that does not work, instead you want to pass the whole string into the button widget, which it then uses to trigger those actions. You could put the same string into a procedure and use action=<<procedurename>> in part because the << >> delimits the action code.

The actions parameter is a special case. Because it can contain wikitext code to be evaluated “on click”, you want it to be a string. This defers any parsing of its contents so that it doesn’t get evaluated “on render”. When a click occurs, the TWCore takes that string and parses (“wikifies”) it right then and there to perform the actions.

The same thing applies if, for example, your actions uses a <<now>> macro to get the current date/time. You don’t want it to be evaluated when the $button is rendered because that would “freeze” the results of the <<now>> macro. Rather, you want the <<now>> macro to only be evaluated when the $button is clicked, so that it gives you that date/time value.

Note that if the $button actions=... doesn’t use any wikitext that depends upon values that can be changed by other events (such as user input), then you CAN omit the quotes. For example, let’s suppose you have a $button that toggles a configuration setting between “show” and “hide”… You could write:

\procedure togglesetting()
<$action-setfield $tiddler="MyConfig" text={{{ [{MyConfig}match[hide]then[show]else[hide]] }}}/>
\end
<$button actions=<<togglesetting>>>click me to toggle the setting</$button>

-e

2 Likes

This is fascinating @EricShulman - I hadn’t heard the distinction between these before, just generic notes of what is “preferred” without reasons attached.

So to make sure I’m understanding what you’re saying using an example:

\procedure myactions() <$action-setfield $tiddler="Output" $value=<<time>>/>

<$let time=<<now "0hh:0mm:0ss.XXX">>>

<$button>test-inline
<<myactions>>
</$button>

<$button actions=<<myactions>>>test-actions-noquote
</$button>

<$button actions="<<myactions>>">test-actions-quoted
</$button>

</$let>
  • test-inline: processes at click, but inefficient due to keystroke re-rendering
  • test-actions-noquote: processes at render, but more efficient (no re-rendering)
  • test-actions-quoted: processed at click, and efficient (no re-rendering)

In my testing with the above, I could confirm the difference in “now” value, couldn’t figure out how to test the other factor.

It’s interesting because in all of my wikis over the many years, I’d say I’m a mix of about 50% inline and 50% actions-noquote. Are there any reasons to not go back and switch ALL of my code to using the actions-quoted approach?

2 Likes

As you’ve noted, using “quoted actions parameters” is the best way, since it defers all processing until the click and doesn’t have any keystroke refresh overhead.

However, I would take a conservative approach and only make those changes gradually when you are making other changes to the same bits of code. As the saying goes: “if it ain’t broke, don’t fix it”.

-e

2 Likes

Absolutely, I didn’t know this, either.
Never tried it with quotes, beacuse I learned to use it as actions=<<macroname>>.

Big thanks to all of you, this is a detailed information.
Stobot’s example was a good help for me, too, to see all the differences in the actions-parameter in one place.

First I thought - oh my God, I did it wrong all the time and I have to adjust the actions-parameter - but as Eric said: “if it ain’t broke, don’t fix it” :sweat_smile: