String field content down a subject tree together

I have a tiddler tree with book subjects, and tiddlers about books in a small local library. The books are tagged with one or more subjects. There is no root subject, but there are a few subjects on the top level, marked as such with a special tag. Every subject has a one-letter code in a field named ‘spinecode’.

I will give an example in order to explain what I want to accomplish.
This is a path from head subject to a book, including the spinecodes:

History (A) - WWII (F) - Military operations (K) - book

So the book is tagged ‘Military operations’. Now I want to give the book a sticker on the spine marked ‘AFK’ in order to put all books on shelves, sorted by sticker code. (If a book has more subject tags, I would like to see all possible sticker codes and then decide where the book will be placed.) Also I want the sticker to have a unique number after the spinecode, but I will cross that bridge when I get to it.

So I need TW code that strings the spinecodes from the tree together, given a certain book. I have spent hours trying to write recursive filters and functions, also tried using the kin filter, but it is beyond me (also see earlier post).

Any suggestions?

Give this a try:

\procedure stickertree()
<$list filter="[<currentTiddler>tags[]]">
   <%if [<currentTiddler>has[spinecode]] %>
      <$let stickercode={{{ [<stickercode>addprefix{!!spinecode}] }}}>
         <<stickertree>>
      </$let>
   <%else%>
      <$button actions="<$action-setfield $tiddler=<<book>> stickercode=<<stickercode>>/>">
         <<stickercode>>
      </$button>
   <%endif%>
</$list>
\end

<$edit-text field=book/>

<$let book={{!!book}}>
Possible sticker codes:
<$tiddler tiddler=<<book>>><<stickertree>></$tiddler>

Notes:

  • The stickertree procedure does a recursive “walk”, starting from a book tiddler
  • For each tag on the currentTiddler:
    • Note that the $list widget re-assigns the value of currentTiddler, so that in this line: <%if [<currentTiddler>has[spinecode]] %>, currentTiddler actually refers to the individual tag tiddler.
    • If the tag tiddler has a spinecode, then it is a subject tiddler
      • Add the subject tiddler’s spinecode to the beginning of an accumulated “stickercode” variable
      • Continue the recursive “walk” up the tree, from the current subject tiddler
    • If the tag tiddler does NOT have a spinecode, we’ve reached the “top” of the tree
      • Display a button to show the accumulated stickercode
      • Pressing the button assigns that stickercode to the book tiddler

The UI let’s you input the title of a book (in the {{!!book}} field) and sets that input into a book variable (used later to set the stickercode for that book). It then sets the currentTiddler to that book title, and invokes the <<stickertree>> procedure to recursively generate all possible sticker code buttons for that book.

I tested this out on TiddlyWiki.com using your example subjects, and added an extra subject tiddler (“Europe”, tagged with “History” and with sticker code “E”). I then added tag “Europe” to the book tiddler. The result for entering the book title of “book” was TWO possible sticker codes, “AFK” and “AE”.

Let me know how it goes…

enjoy,
-e

Addendum:

Note that the recursive stickertree() procedure can get into an infinite loop if the subject tags are defined badly.

For example, suppose tiddlerA is tagged with tiddlerB, and tiddlerB is tagged with tiddlerC, and tiddlerC is tagged with tiddlerA, creating a “tag loop”. In this case, starting from “tiddlerA” will result in a never-ending recursive sequence:

tiddlerA > tiddlerB > tiddlerC > tiddlerA > tiddlerB > tiddlerC > tiddlerA > etc.

To avoid this, we can keep a list of tiddlers “visited” during the recursion, and only visit them once, like this:

\procedure stickertree()
<$list filter="[<currentTiddler>tags[]!enlist<visited>]">
   <%if [<currentTiddler>has[spinecode]] %>
      <$set name=visited filter="[enlist<visited>] [<currentTiddler>]">
         <$let stickercode={{{ [<stickercode>addprefix{!!spinecode}] }}}>
            <<stickertree>>
         </$let>
      </$set>
   <%else%>
      <$button actions="<$action-setfield $tiddler=<<book>> stickercode=<<stickercode>>/>">
         <<stickercode>>
      </$button>
   <%endif%>
</$list>
\end

Notes:

  • When the <<stickertree>> macro is initially invoked, the visited list is empty
  • The $set widget uses a filter to add the currentTiddler title to the list of visited subject tiddlers
  • The $list widget uses !enlist<visited> to ignore any subject tiddler that has already been visited

enjoy,
-e

1 Like

Amazing! It works like a charm, after having to tweak your code a bit because:

  1. my tree does not have a real subject top, i.e. a top tiddler that is a subject in itself. (All real subjects are used in an record edit template with checkboxes, so the top tiddler must be an exception)
  2. I use various book tags for other purposes, so ‘If the tag tiddler does NOT have a spinecode, we’ve reached the “top” of the tree’ does not hold.

Thanks a lot!

Later I will send an update to record what I learned from your code.

Good point, of course. I will keep a link to this trick.

My subject tree has no cycles, but it is also not a strict tree, mathematically speaking. For instance, the subject ‘Church’ is a sub-subject of both ‘Local institutions’ (next to ‘School’ and ‘Municipality’) and ‘Local networks’ (next to ‘Clubs’). Your original code handles this perfectly.