while filling up my tinderbox, I have created a prototype called “Term”, which is intended for a glossary that is populated by an agent looking for all prototypes of the type “Term”. A term may be a scientific concept, a method, …
Whenever I create a new term, I would like to have TB check whether a note with this very name and prototype exists, and if so, instead copy an alias at my current location so that I can add up information to the existing term.
Tinderbox allows notes to have the same name (the UID is stored in $ID) and doesn’t have a built-in duplicate warning/or suppression. There was a duplicate title warning setting in v5 (here) but it appears that wasn’t carried over to the new v6+ re-write of the app - or if it is I can’t find it. So, for now checking for duplicates is a user task - you could do something like list all notes of pprt type ‘Term’ and sort on $Name and check if any has a previous sibling of the same name.
Do you mean ‘copy an alias’ or ‘create an alias’? As the former doesn’t make sense I’ll assume it’s that latter. If so, No, an action can’t create an alias. I assume this falls under the same pragmatic assumption that doesn’t allow action-based creation/deletion of notes - to guard against user error doing serious damage.
As the agent collecting terms will make an alias for each term, for what purpose is the extra alias proposed above. I ask only because there might be a different way to achieve your goal.
Glossary (Agent): displays all cards of type “Term”
Article 1. In here, I read about some research, and find that the researcher 1 is adding nice ideas to concept A. Hence, I make a card of type “Term” regarding concept A.
Article 2. In here, I read about some other research, and find that the researcher 2 is also adding nice ideas to concept A. Now my “Term”-Card of concept A already exists, so instead of creating a second card and having lots of individual cards for the same concept in my glossary-agent, I would have liked it if TB could have checked among the term-cards, and then tell me that there already is a card relating to that concept, move an alias of that card to the very same “Article 2-map”, so that when I want to read Article 2 again later, I can also see the relating context (regarding concept A) from Article 1, and at the same time add up to the glossary, as that compiles everything together.
I guess I could have agents for each keyword. Would there be a way to make my glossary function in a way that it creates an agent for each element in $keywords, which then collects all cards of type term relating to that keyword? Like, an agent within an agent?
Don’t make an agent per-keyword as that approach doesn’t scale well. Much better is to use a new tab with an Attribute browser view, which is designed for such a purpose. For a given Attribute (e.g. $Keyword) it will show you each discrete value in use in the document and each value lists the notes using that value. You can set the scope of the view, e.g. to just one container for example. Or you can apply an agent query to further filter the ranges of notes listed. The view also supports column view.
As to your workflow, I’m unclear in step #3 how you propose to tell Tinderbox the value you wish to test. Action code doesn’t do input dialogs for script. So, you’re going to have to put that value in an attribute if you want to test/act on it via action code. Remember, action code is not like a full programming language so don’t assume a programming feature exists (e.g. input dialogs). The best overview of action code options is probably this.
As to #3: I understand the restrictions, and I am too inexperienced to say whether they limit or liberate. The idea was to pass on that value through the title of a new note, which is then tested, discarded and replaced by an alias of the existing note of the same title.
Well, as action code doesn’t create new notes**, the issue is moot.
** As a design intent, action code does not (currently) allow new notes to be created, or existing ones deleted, or (apart for a workaround) to create aliases. This choice was because of the scope for user error to create run-away actions that resulted in a lot of work to repair unintended outcomes.
Apologies for reviving an old thread. But I have a similar question to the original poster.
I likewise am making a keyword glossary. So long as I work inside TBX, the Attribute Browser, as described by Mark, is perfect.
But what if I want to export my glossary? For each term, I wish to export a plain text file that has the glossary term as the note’s name, with the body of the text file listing the $Name of all notes that are tagged (in an attribute $Keyword) with that keyword.
Yes. ‘HTML’ export makes this perfectly possible, though you don’t have to be creating HTML formatted documents. The templates and export code you use in your document control the sort of document created.
In the discussion above, you had discouraged using agents to create a register. I can imagine how, using agents, I might use ‘HTML’ export to make one exported document per agent.
But could you kindly point me in the direction of how to do this otherwise? At the moment, my “keyword glossary” is just the Attribute Browser set to show me all notes organised according to the $Keyword attribute. How do I move from this to exporting one note per Keyword?
Please note - this code is not tested (as a bit busy right now) so don’t feel it’s rude to ask if the code doesn’t work.
Task is that for each $Keyword term:
export a plain text file that has the glossary term as the note’s name…
with the body of the text file listing the $Name of all notes that are tagged (in an attribute $Keyword) with that keyword.
So we make a note with this rule:
$MyList = values("Keywords")
$MyList now contains every discrete $Keywords value across the whole document - which is likely what your are using in AB view. so, now we have to export a note with info per discrete $Keywords value…
So, for each $MyList value (i.e each discrete keyword) we want a list of notes that use that value in $Keywords.
We can’t easily export a file per keyword (without making a note or agent per value - bad if there are tens or hundreds of values) but we can export a single file with a delimiter which makes it easy to split the exported file into discrete files using a text/code editor.
Add an extra user List-type attribute $MyList1.
So our export template is:
$MyString = aValue;
$MyString = $MyString + "\n" + aKeyword;
As noted, the above is _not_ tested (no spare time) but it should work or hopefully help you to the correct outcome.
Thank you so much for this, Mark, especially as you are busy.
I’ve given this a test run, but no luck. I’ve attached a link to a minimum working example.
(Note that in the action code of the export template in the file below, I changed all instances of “keyword” (singular) to “keywords” (plural). I only did this after testing with your original code, which did not work. I then assumed that ‘keyword’ was just an oversight since the attribute is $Keywords.)
Thank you, Paul. This is a step forward. However, only the keyword that comes last in the sorted list of $MyList is exported. So, out of “alfalfa;banana;broccoli;testing;”, only “testing” appears in the exported file.
First, I added appropriate prototypes & templates and containers for said objects. The error in my untested code was $MyString was getting overwritten each time the outer loop ran, so only the last keyword’s data was retained. I fixed that and note ‘keyword_list’ using template ‘t_keywords’, shows the result. I also added the code as an edict that writes the output to the same note’s $Text. This shows a quite useful way of debugging code that otherwise only runs during export. Of course, once working you would delete or turn off the edict and remove the test $Text.
So, the example now lists each discrete $Keywords value, but lists the full path of each note using it. This is because find() always returns a list of $Path values. Here, I assume just the note title would be better. So, in note ‘keyword_list1’ using template ‘t_keywords1’ I’ve amended the code so only the note’s $Name is listed.
Many thanks, Mark (and Paul): it is now exporting correctly! This is really very helpful, and I am grateful for the code.
(As an aside, are there any tutorial-type resources to learn how to produce such code, walking a student step-by-step through learning the grammar and vocabulary of TBX code-writing? Over the years, I’ve learnt how to use all the basic features of TBX that have a user-interface, and can do some basic rules, but things like the code you’ve provided above — things like “aValue”, and the general syntax — are beyond me.)
A further aside: this is a long-standing desideratum of mine: the ability to use action code to create new notes. I understand the scope for error, but most of my TBX projects would really benefit from such a feature.
I think the confusion arises in associating the code too closely with one output. In this thread there isn’t a pre-built function to do your task. Therefore we need to apply some generalised techniques. We need to:
Get a list of all the discrete values used in Keywords.
Iterate through that list - i.e. step through it value by value in order to to further work with each value.
Get a list of all the notes using that particular keyword value and loop through that to build the final output list.
Around this we need to make/use a few extra String and List type attributes to handle the data being processed
So le’ts walk through the code in t_keywords1. We start with some Markdown code which isn’t pertinent to our task be is needed as part on the final export file:
Now, because we want to run action code during export, we need to open an ^action()^ export code:
Now we can start out action code. First we reset the default value for $MyString to ensure we don’t get any data held over from a previous run of the code:
Now, we store all the discrete values used by $Keywords in a List-type attribute. It doesn’t have to be ‘$MyList’ but I assumed using that particular type of name would help indicate the type of attribute needed:
$MyList = values("Keywords");
We now have our list. We need to use the .each() operator to iterate across it. To use .each() you need to provide a name for the in-loop variable that with hold the value of the list item being used. I could have used ‘X’ or ‘babanna’ but ‘aValue’ tries to indicate the purpose. Note that when declared in the .each() command’s parentheses we do not use quotes around the name.
Now we are in the first loop. If $MyList as the values “apple;fig;pear”, on the first iteration of the loop, where you see the code ‘aValue’, it will actually pass the string “apple”, “fig” on the second iteration, etc. First, we add a line break and the current list value to $MyString. On the very first run, $MyString is empty and so you get a blank line (due to the “\n”) before the first text. You could at an if() clause here to catch that but it would only make the code more complex for beginners to understand and the blank line does no harm.
$MyString = $MyString + "\n" + aValue;
Now we make a new list ($MyList1) that finds any note where the value(s) of $Keywords match ‘aValue’.
now we start the second (inner loop). Again, we use .each(), this time using a different loop variable name - this time it is ‘aKeywords’. With hindsight 'aKeyword` might have amde more sense but i hope you get the general idea:
Here, we simply add a line break and the title (i.e. $Name) of each note in $MyList1:
$MyString = $MyString + "\n" + $Name(aKeywords);
Now close out both loops and the ^action()^ element::
Some more markdown and general export code follows:
Lastly we add a further ^action()^ element containing code that resets the 3 attributes we’ve been using to run this code. Clearing these ensures we aren’t persistently storing lots of temporary data after the code has run:
All done. Does that help make sense of things? Annotating code like this takes a while and can’t easily be done in Tinderbox as there isn’t a code-commenting mark-up.
This is truly very helpful, Mark. Thank you for taking the time to walk me through this. I really think it is such “step-by-step” tutorials for the coding of TBX that makes sense of what is otherwise very helpful but also very abstract information in the Tinderbox Cookbook and Reference File. What you’ve written above now makes the language more intelligible, even to someone as code-ignorant as I. Thank you again!