Struggling with a transclusion from a state tiddler with a variable field name

Hello all!

I’m trying to use the <$select> widget in a procedure to do the following:

  1. build a list of tiddlers that have a certain tag, field, and value in that field, to be rendered as a dropdown menu
  2. use a state tiddler to hold the relevant selection, that will create or update a field which name comes from the procedure call, and the value comes from what the user selects in the dropdown
  3. transclude the created/updated field and its value from the state tiddler to whatever tiddler the procedure is called from

I currenlty have the following procedure:

\procedure select-mod(advantage)
<$select tiddler='$:/generated-list-mod-state' field=<<advantage>> actions='<$action-setfield $field=<<advantage>> $value={{{ [{$:/generated-list-mod-state!!<advantage>}] }}}/>'><option disabled>Select modifier</option>
<optgroup label='Special'>
<$list filter='[tag[GURPS Modifier]search:to-advantages:<advantage>]'>
<option><$view field='title'/> (<$view field='modifier-value'/>%)</option></$list></optgroup>
<optgroup label='Global modifiers'>
<$list filter='[tag[GURPS Modifier]!has[to-advantages]] -[title[Modifier template]]'>
<option><$view field='title'/> (<$view field='modifier-value'/>%)</option></$list></optgroup></$select>
<$tiddler tiddler={{$:/generated-list-mod-state}}><$transclude mode='block'/></$tiddler>
\end

Testing the procedure, the menu builds fine, choosing an option of the menu does populate the state tiddler correctly with a field named from the variable <advantage>, with the correct value selected, but the final transclusion only transcludes the correct field name, without its value (i.e. it remains empty). This looks like a bracket/syntax issue of some sort, but despite looking again the select widget documentation and Those Pesky Brackets, I can’t seem to figure it out?

Hi

Would this work?

<$select tiddler='$:/generated-list-mod-state' actions='<$action-setfield $field=<<advantage>> $value={{$:/generated-list-mod-state}}/>'>

Fred

Hi Fred :slight_smile: Not sure I understand your proposition. Why do you change the select widget that works, when it’s the transclude one/tiddler one that doesn’t?

Edit: it does work, but I still don’t understand exactly why…

Usually, you don’t need to use the same target field in the state tiddler as in the destination tiddler. Code is simpler if you always use state tiddler’s text field, and only in the action widget choose the target field.

Fred

I’ll have to re-read that a few more times I think :sweat_smile:

I just realised that my approach is going to raise issues, because I intend to use the macro in a list of advantages, and I fear that the state tiddler should be unique for all of them?

Right now, your procedure applies selection to field <<advantage>> of <<currentTiddler>>.
State tiddler isn’t really useful in this case, and <$select> widget could work without any state tiddler like this:
<$select field=<<advantage>>>

Without state tiddler, no state tiddler conflict! :wink:

Fred

Mmmm :slight_smile: Thanks for the suggestion :slight_smile:

I have a problem though, I need that select to show the fields I indicated in the list filters, but add only the title field, not the modifier one, into the advantage field of the current Tiddler. So example in the case of

<$list filter='[tag[GURPS Modifier]search:to-advantages:<advantage>]'>
<option><$view field='title'/> (<$view field='modifier-value'/>%)</option></$list>

I need to list both the title and modifier-value in the dropdown of the select widget, but add only the title one to <<advantage>>; is there a way to do that?

Additionally, since an <<advantage>> can have multiple modifiers, I should actually list them (separated by a ", " because mod names can have spaces), and probably use action-listops instead of action-setfield. Finally, each modifier can have a level (zero by default, always positive), so to give out an example:

Let’s say I’m currently dealing with the <<advantage>> called Magery:

  • list the options as above, (in this example let’s say the options “Musical”, “One College Only” and “Solitary”), like this:

    • Musical (-50%)
    • One College Only (-40%)
    • Solitary (-40%)
  • create/modify the field called ‘Magery’ when an option is selected in the dropdown by the user, like this:

    • Magery: Musical 0, One College Only 0, Solitary 0

something like this? (doesn’t work, but that’s sort of what I’m at for now)

<$select field=<<advantage>> actions='<$action-listops $field=<<advantage>> $subfilter="+[toggle[]]"]/>'>

… bit out of my depth here :stuck_out_tongue_winking_eye:

For this you can use <option> html tag syntax:
<option value="Field value">Display label</option>

In your case, “Field value” is the result you want to store in <<advantage>> field, and “Display label” is the label displayed in the dropdown list. Both values should be computed with functions or filtered transclusions.

Fred

When user selects a value in the list, should the result be stored in <<advantage>> or be appended to current <<advantage>> value?

Fred

Appended, or removed if already there :slight_smile:

I tried to declare the value of the list items as a list variable named “modTitle” in my list widgets to be able to come up with the correct option value (and use a conditional to indicate whether it’s already in the field or not), like this:

\procedure select-mods(advantage, advbook)
<$select tiddler=<<advbook>> field=<<advantage>>><option disabled>Select modifier</option>
<optgroup label='Special'>
<$list filter='[tag[GURPS Modifier]search:to-advantages:<advantage>]' variable='modTitle'>
<option value=<<modTitle>>><<modTitle>> ({{{ [<modTitle>get[modifier-value]] }}}%)<% if [<advbook>search:<advantage>:<modTitle>] %>&nbsp;&#x2714;<% endif %></option></$list></optgroup>
<optgroup label='Global modifiers'>
<$list filter='[tag[GURPS Modifier]!has[to-advantages]] -[title[Modifier template]]' variable='modTitle'>
<option value=<<modTitle>>><<modTitle>> ({{{ [<modTitle>get[modifier-value]] }}}%)<% if [<advbook>search:<advantage>:<modTitle>] %>&nbsp;&#x2714;<% endif %></option></$list></optgroup></$select>
\end

Core functionality works now, but it’s still not an append/toggle, and I still need to get the conditionals working (I have no clue why they don’t ;P).

edit: tried that for the append/toggle, but it doesn’t work at all (field is emptied)

<option value={{{ [<advantage> +[toggle<modTitle>]] }}}><<modTitle>> ({{{ [<modTitle>get[modifier-value]] }}}%)<% if [<advbook>search:<advantage>:<modTitle>] %>&nbsp;&#x2714;<% endif %></option>

I suspect a problem in search operator syntax. An operator suffix can’t contain a variable part (here, <advantage>.
I failed to find an alternative filter for this conditional, hopefully someone with greater skills can help.
I got stuck because the compound data format you chose (storing multiple coma separated options along with their level in a single field) makes it difficult to deal with subvalues. At least too difficult for me :slightly_smiling_face:

Fred

A satisfying alternative to this whole UX could be:

  • dropdown <$selectwidget> listing the mods like before
  • add a button to add or remove what is selected, depending on whether or not it’s already in the field.

Would that help?

New failed attempt:

\procedure mod-select-actions()
<$action-listops $tiddler=<<book>> $field=<<advantage>> $subfilter="+[toggle[<modTitle>, ]]"/>
\end

\procedure select-modsB(advantage, book)
<$select tiddler=<<book>> field=<<advantage>>><option disabled>Select modifier</option>
<optgroup label='Special'>
<$list filter='[tag[GURPS Modifier]search:to-advantages:<advantage>]' variable='modTitle'>
<option value=<<mod-select-actions>>><<modTitle>> ({{{ [<modTitle>get[modifier-value]] }}}%)<% if [<advbook>search:<advantage>:<modTitle>] %>&nbsp;&#x2714;<% endif %></option></$list></optgroup>
<optgroup label='Global modifiers'>
<$list filter='[tag[GURPS Modifier]!has[to-advantages]] -[title[Modifier template]]' variable='modTitle'>
<option value=<<mod-select-actions>>><<modTitle>> ({{{ [<modTitle>get[modifier-value]] }}}%)<% if [<advbook>search:<advantage>:<modTitle>] %>&nbsp;&#x2714;<% endif %></option></$list></optgroup></$select>
\end

I haven’t read the thread exhaustively, but here are some suggestions based primarily on your most recent code.

\procedure mod-select-actions()
<$action-listops $tiddler=<<book>>
	$field=<<advantage>>
	$subfilter="+[toggle{$:/temp/generated-list-mod-state}]"/>
\end

\procedure mod-select-option()
<option value={{!!title}}>
{{!!title}} ({{{ [{!!title}get[modifier-value]] }}}%)
<$list filter=`[<advbook>search:$(advantage)${!!title}]`>&#x2714;</$list>
</option>
\end

\procedure select-modsB(advantage, book)
<$select tiddler="$:/temp/generated-list-mod-state"
	default=""
	actions=<<mod-select-actions>>
>
	<option value="" disabled>Select modifier</option>
	<optgroup label="Special">
		<$list filter="[tag[GURPS Modifier]search:to-advantages:<advantage>]">
			<<mod-select-option>>
		</$list>
	</optgroup>
	<optgroup label="Global modifiers">
		<$list filter="[tag[GURPS Modifier]!has[to-advantages]] -[[Modifier template]]">
			<<mod-select-option>>
		</$list>
	</optgroup>
</$select>
\end
  • There’s nothing wrong with specifying a modTitle variable if you find it easier to follow, but in this case, I don’t think you really need it: you can omit it and use the default <<currentTiddler>>/{{!!title}} instead.
  • You shouldn’t be using an action widget as the value of an option; instead, use the actions attribute of the select widget. <option> values are used to specify the string that is actually written to the specified tiddler/field (as contrasted with what that option displays. In your case, I believe you want that to be {{!!title}}, i.e. the current $list item (your original <<modTitle>>).
  • Since you’re using the same <option> format for both optgroup lists, I moved it to a procedure that can be invoked as a variable to make your code a little more concise.
  • As @tw-FRed pointed out, you can’t use a <variable> as an operator suffix (as you were trying to do with <advbook>search:<advantage>). However, if we change the <% if ... %> syntax to a $list, we can make use of substituted attribute values and write the variable as <advbook>search:$(advantage)$ instead. By surrounding the filter in backticks rather than quotes, we indicate that the $(variable)$ should be substituted before the filter is evaluated.
  • Finally, as Fred suggested, you’ll want to write the selected value to a secondary tiddler and then append the value of that tiddler to your <<advantage>> field. Otherwise, any existing contents of that field will be overwritten by your selection.
    • toggle is intended to work with a title list, not a list of comma-separated strings, so I’ve amended your $subfilter to reflect this. Generally speaking, I’d recommend using title lists in your fields as they’re more filter-friendly, and adding commas in as necessary in your display templates (e.g. {{{ [<currentTiddler>get<advantage>enlist-input[]join[, ]] }}}).

I hope this gets you moving in the right direction! Let me know if any of this needs further explanation.

2 Likes

Thanks again for this detailed explanation and pointing me to susbstitutions (didn’t know those at all).

However; the addition of the check marks :heavy_check_mark: after the options doesn’t seem to work.

Additionally, I do need the list to be formated with a comma and a space in between modifiers and their level, because as you’ve seen in the other thread you solved, that is the field I then manipulate to calculate the bonus/malus values of those modifiers depending on their level, and modifiers can have spaces in their names (which is handled by your current code but through adding double square brackets around those titles containing spaces). The default level when adding a new modifier would be 1. (My next problem to solve will be to be able to edit those levels by replacing them in the list with an <$edittext> widget, which I suspect will also be tricky for me but one step at a time)

In other words, the field should look like this:

<advantagename>: <modifier name 1> <level1>, <modifier name 2> <level2>, <modifier name n> <leveln>

This means that in the procedure mod-select-option() I need to change

<option value={{!!title}}>

to something like:

<option value=<modTitleAndLevel>>

where modTitleAndLevel could be defined elsewhere; and in mod-select-actions(), I need to account for the space character and the level in <modTitleAndLevel> to be removed as well when they’re in the field…

Sorry, an extra colon snuck into my code here:

<$list filter=`[<advbook>search:$(advantage)$:{!!title}]`>
                                             ^

Does it work if you take it out?

Hmm, okay, I’d try something like this:

\procedure mod-select-actions()
<$let
	currentTiddler=<<book>>
	current={{{ [<book>get<advantage>] }}}
	modTitleAndLevel=...
>
<!--  ^ However you want to derive this; you'll presumably need {$:/temp/generated-list-mod-state}, which stores the option selected by the $select widget -->
<% if [<current>search<modTitleAndLevel>] %>
<$action-setfield
	$field=<<advantage>>
	$value={{{ [<current>search-replace<modTitleAndLevel>,[]search-replace:g[ ,],[]] }}} />
<% else %>
<$action-setfield
	$field=<<advantage>>
	$value={{{ [<current>] [<modTitleAndLevel>] +[join[, ]] }}} />
<% endif %>
</$let>
\end

Since we’re not working with a title list, I’ve switched from $action-listops to $action-setfield, which requires us to retrieve the existing field value (stored here as <<current>>) and then modify that. toggle doesn’t work well in this scenario, since <<modTitleAndLevel>> might or might not need a comma after it; instead, we’ll need a separate action widget for each scenario.

  • If <<modTitleAndLevel>> is already present in the field…
    • replace <<modTitleAndLevel>> with nothing (i.e., delete it)
    • replace the comma left behind (now preceded by a space) with nothing, too.
  • Otherwise, append <<modTitleAndLevel>> to the existing field value, joining them with a comma + space.
  • You’ll see I also moved the tiddler=<<book>> attribute out of the $action-setfields and refined <<currentTiddler>> in the $let widget. Since the action widgets default to modifying <<currentTiddler>> if their tiddler attributes aren’t specified, this lets us remove a bit of redundancy.

No it doesn’t… I haven’t the faintest idea of why.

For the ModTitleAndLevel, I’m experiencing a bit of chicken and egg reasoning issue in my head, but, we basically need to look for the option selected (stored in $:/temp/generated-list-mod-state) followed by a space character and a number, in the current value of the field (so in your code, that would be <current>. I was tempted to do something along the lines of

<$list filter="[<current>split[, ]search::[[$:/temp/generated-list-mod-state]addsuffix[ #]]" variable=ModTitleAndLevel>

but as you can see in that line (which syntax I’m not even sure of), using search has a few issues:

  1. I don’t know how to add the “space and number” to each split result - but we would probably have to rely in part on regexp[\s\d+$] because we don’t know what the “#” number would be
  2. If the search fails to find what I’m looking for, which means it’s not in the , then I would want to add ModTitle 1

I’m a bit too tired to reason on this case I think. Maybe I’ll see things more clearly tomorrow…

I don’t have the broader context, but I do notice that you’ve got two similar variables here, <<book>> and <<advbook>>. Is it possible that <advbook> should be <book> instead?

If all newly added mods should start at 1, you could try something like this:

\procedure mod-select-actions()
<$let
	currentTiddler=<<book>>
	current={{{ [<book>get<advantage>] }}}
	selected={{$:/temp/generated-list-mod-state}}
	new=`$(selected)$ 1`
	existing=`$(selected)$ \d+(,\s?)?`
>
<!--  Use backtick substitution to define two permutations:
	<<new>>, for "ModTitle 1")
	<<existing>>, for ModTitle + any digit(s). This second variable defines a regexp string. -->
<% if [<current>search<selected>] %>
<$action-setfield
	$field=<<advantage>>
	$value={{{ [<current>search-replace::regexp<existing>,[]] }}} />
<!--                                    ^ 2 colons! The first is for the flag-list, where we'd put g for global if we were using it; the second precedes regexp. -->
<% else %>
<$action-setfield
	$field=<<advantage>>
	$value={{{ [<current>] [<new>] +[join[, ]] }}} />
<% endif %>
</$let>
\end

I’ve never actually tried to use backtick substitution to define a regex string this way, but conceptually I think it ought to work. I also moved the possible comma+space suffix into <<existing>>, since we’re using regex anyway; that lets us eliminate the second search-replace.

Get some sleep and let me know how it goes. :slight_smile:

Good catch, that was it :slight_smile:

That works!

Only two things to fix;

  1. test if the field is empty so we don’t start it with “, ” (that shouldn’t impede the system otherwise, but it tickles my OCD and would make the field a tiny bit less readable)
  2. reset the selected option to nothing once a change has happened (otherwise, you can’t remove what you just added or add what you just removed)

Which I did like so:

\procedure mod-select-actions()
<$let
	currentTiddler=<<book>>
	current={{{ [<book>get<advantage>] }}}
	selected={{$:/temp/generated-list-mod-state}}
	new=`$(selected)$ 1`
	existing=`$(selected)$ \d+(,\s?)?`
>
<!--  Use backtick substitution to define two permutations:
	<<new>>, for "ModTitle 1")
	<<existing>>, for ModTitle + any digit(s). This second variable defines a regexp string. -->
<% if [<book>!has<advantage>] %>
<$action-setfield
	$field=<<advantage>>
	$value={{{ [<new>] }}} />
<$action-setfield
	$tiddler="$:/temp/generated-list-mod-state"
	$field=text
	$value="" />
</$let>
<% elseif [<current>search<selected>] %>
<$action-setfield
	$field=<<advantage>>
	$value={{{ [<current>search-replace::regexp<existing>,[]] }}} />
<$action-setfield
	$tiddler="$:/temp/generated-list-mod-state"
	$field=text
	$value="" />
</$let>
<% else %>
<$action-setfield
	$field=<<advantage>>
	$value={{{ [<current>] [<new>] +[join[, ]] }}} />
<$action-setfield
	$tiddler="$:/temp/generated-list-mod-state"
	$field=text
	$value="" />
</$let>
<% endif %>
\end

…I believe it all works correctly now :slight_smile: Thank you!

Quick question:
If I wanted to default the book parameter to the currentTiddler (i.e., here, the Tiddler where the procedure is called from) in the declaration of \procedure select-mods (advantage, book), how should I write it? I tried:

\procedure select-mods(advantage, book:<currentTiddler>)

and

\procedure select-mods(advantage, book:<<currentTiddler>>)

… seemingly to no avail?