List attribute options

Thanks for this reminder. I knew there was a way to do this but had forgotten what it was.

There is upside to using the ‘suggested values’ route. In the past, as @PaulWalters describes, the (only) approach we had was to load all the values (if a List) into the prototype or, worse, for String (single value) add a note to hold each discrete value. A downside of that approach is if you you want to use values() to fetch the actual used values of an attribute, the ‘seed’ values get included and thus mess up the count.

With the ‘suggested values’ approach, unused values form the suggested list are not recorded by values().

The only thing to watch is the Inspector boxes are quite small. If you need to add 10s or 100s of suggested values, you may do better to set them out one item per line in a plain text file, check items are correct (e.g. spelling) then replace line breaks with semi-colons, select all, copy and paste to the Inspector’s suggested values box.

Or you can used a code note, with $Text==standard values semi-colon delimited, and use a stamp to set $MySet in a prototype to $Text of the code note.

Mark A, I am going to use this as an “asking in public” moment about a process, possibly to help others along their TB discovery trail).

  • Let’s say I have an existing file with a lot of already-established values for a certain attribute. Let’s say they’re city names–London, Slough, Reading, Stoke-on-Trent, and so on.
  • I am creating a new file, and I would like to pre-populate its attribute list with existing values from the old file.
  • Is the simplest way to collect all existing values for an attribute from one file, so that I can cut-and-paste enter them into the “suggested values” box for an attribute in the new file, just to put the following code into a Rule for the parent container? $Text=collect(descendants,$Category).unique

That seems to give me a semicolon-separated list of the values, which I could then paste into the other file.
Just checking to see if this is The Approved Way. Thanks jf

Yes and no. If you want all values of $Category, for the whole document you could use values(), which returns a Set-type list, which is de-duped but whose sort order can’t be assumed. I’ll come to what you do with the list further below:

values("Category").sort

But, lets say you only want $Category values for part of the document, perhaps the descendants of the current container. Now we have to use collect() (or depending on the criteria collect_if()):

collect(descendants,$Category).unique.sort

Depending on the values you’re collecting you might consider using .isort instead of .sort so as to give a case-insensitive sort.

If you just want to save the data for the next document I’d add $MyList as a KA and use either of:

$MyList = values("Category").sort
$MyList = collect(descendants,$Category).unique.sort

By passing the list into a List-type attribute we ensure it doesn’t get re-sorted. If you then click into the KA all the content (including bits off-screen) are selected and you can copy/paste to elsewhere. If less trusting, once the focus is in the value box use Cmd+A as an explicit ‘select all’ before doing the copy paste.

But, maybe you’d like to review the list of values as well, or before you move them on. You could just pass the value() or collect() call in to $Text but that gives you all the values run together with semi-colons. Easier to read is to use the .format() with a line-break delimiter. So:

$Text = $MyList.format("\n")

Now $Text contains one value per line which can make it easier to review.

Does that round things out clearly enough? I know I’ve given options, but only to pre-empt some obvious folllow-up questions. :grinning:

Mark A – very useful and clear, thank you!

One after-action observation, one question.

  • Observation: this pairing of code is interesting, in highlighting the difference between putting an attribute name in quotes, but without a dollar sign –“Category”–versus using no quotes but with a dollar sign, as with $Category. I forget the rule about which you use when, but I see that the proper functioning of each of these lines of code depends on which you use.
  • Question: when I am setting up the likes of $MyList to receive the results of either values or collect, does it matter whether I define $MyList to be “set” versus “string”? Or will it auto-figure it out?

Again thanks for the clear and patient explanation.

Q1. Arguments using an attribute name with no prefix. This occurs where the app wants a literal string that is the name of an attribute. values(), the value lists of $KeyAttributes and $TableHeading (others?), the column name selector for column view (and in AB view columns) and in the KA chooser panel all fall into this category. The difference:

$AttributeName is a placeholder for the value of attribute ‘AttributeName’.
`“AttributeName”’ is a string representing the actual name of attribute ‘AttributeName’.

I’d agree that in the column heads and KA selector the distinction is moot. However, what’s obvious in our mind’s eye may be less obvious for a code algorithm to divine. So, Tinderbox likes us to help it out by indicating whether we’re stating the name or using the attribute’s value.

  1. $MyList is a built-in List. I was being lazy, using the general $My… to mean an attribute of that specific type, i.e. here List type. The convention started before Tinderbox helpfully added a predefined $MyString, $MyList, etc.

So to be clear, yes values() returns a List-type and collect() as Set-type but both are essentially lists. I pass them to a List-type attribute because:

  • We’ve either sorted the output of the supplying function (or we don’t care) but we don’t want the receiving attribute to change the order of the list of values.
  • List to List, or Set to List will not change the supplied ‘list’ (as in a generic list of multiple values - sorry I need another word for list here!).
  • Set to Set, may result in a change in value order. You won’t know, but if order matters…
  • List to Set will de-dupe the list if dupes are present and may result in a list order change, etc.

Thus passing a list of either type to a List is attribute is safest.

You can pass to a string as that essentially concatenates the incoming list using semi-colons. It is the same as more explicitly doing $SomeString==$SomeList.format(";"). In the absence of a format string Tinderbox uses the normal list value delimiter. In fact, it is what you see with a Set or List when shown as a KA - all the values with a semi-colon delimiter.

If the way you originally described feels right for you, then that’s fine too as long as you’re happy with the fact that values() is always whole document scope. In most cases that’s fine but occasionally you may need collect() for more limited scope. Indeed, values() had its origin in an easy way to ‘just’ get all the unique values used in a given attribute.

Mark, thanks again. Really appreciate your time, patience, and lucidity.

At this point:

  • I do understand the collect() / values() distinction, which I hadn’t focused on before.
  • I understand the various ramifications of sorting, de-duping, and all the rest.
  • I now understand the obvious-once-you-point-it-out conventions about $MyList, $MySet, $MyDate, and so on. And…
  • It is conceivable that at some point I’ll internalize the “Category” / $Category distinction! For now I’ll leave it in the realm of the Albigensian heresy or other topics I know are significant but that I couldn’t quite explain to anyone else. (Seriously, thanks for your patience and generosity to the community.)

Thanks for that summary. On the “Category” / $Category, if you get to a point where not sure which for to use in action code (and UI boxes) try using these expansions as substitution tests:

“Category” => ‘a string that is the name of the attribute called Category
$Category => ‘the current value of this note’s attribute called Category

I think in most of the situations described thus far, one of the long descriptions will be an obvious non-fit.

Confusion arises because of the ad hoc convention when discussing Tinderbox in forums, tutorials, etc., of using the $-prefix form for both the cases above! IOW, using $ to distinguish between the word ‘price’ and an attribute called ‘price’. In the latter case, we might write ‘$price’ to indicate we’re talking about either of the above ways to refer to an attribute (i.e. name vs. value) given that ‘price’ is a valid, if unusual name. Indeed, part of the convention of using CamelCase names for attributes is exactly so they can’t easily be mistaken for ‘normal’ words that we might use in string values.

So, when using values() with the Category attribute we want ‘all the values of the attribute with the name Category’. So consider:

values("a string that is the name of the attribute called Category")
values("the current value of this note's attribute called Category")

The second option wouldn’t make sense, so we don’t need a $ prefix. Now collect(): e.g. ‘for every descendant of this note fetch its value for the attribute named Category

collect(descendants, a string that is the name of the attribute called Category)
collect(descendants, the current value of this note's attribute called Category)

Now the first form doesn’t make sensor makes less immediate sense, so go with the $ prefix.

There’s a confusing edge case: sometimes an attribute can hold the name of an other attribute. This lets us do things like values($MyAtributeName) which allows the attribute whose values are being sampled to vary depending on the value of $MyAtributeName. That’s confusing to a very new user ("…but values() takes a quoted string"), yet is the sort of applied use an experienced user needs. In aTbRef I try to spell out these alternates precisely so people can look in moments of confusion, especially as not all action operators allow this switcheroo of replacing a quoted string literal value with an attribute (holding a similar value).

Of course, if you do make an error, Tinderbox may just work anyway. The explanation of that is because up until about v4 you could write:

A = B

At that time the left side could only be an attribute (thus $A in current terms) and if and attribute $B existed, then $A would take the value of $B. If no attribute $B was defined at all, $A would take the value “B”. Tinderbox also knew that if A = B was in a query, then that the ‘=’ implied an equality test instead of a value assignment. Such code examples linger. I’ve tried flushing them from aTbRef, Tinderbox’s Help and the cookbook but they linger on elsewhere, e.g. in old demos and more importantly in user’s files.

However, since then, Tinderbox action code has got significantly more complex. To the user our intent is clear, in our minds eye. We simply don’t, especially non-programmers, see a possible multiplicity of syntactic ambiguities and edge cases in the code we wrote; Tinderbox can’t guess, it has to figure it out. Thus the move in later versions to $-prefixes, quoting strings, = vs. ==, etc.

A lot of words to get to this point, but I hope it gives you a rubric to help figure out when you need a $-prefix and when you don’t.

[I’ll probably move this essay to a web page and expand it a bit, but other work beckons just now]

Mark A, again sincere thanks for taking the time to lay this out. In principle, I understand now! Thanks again.

@mwra may I suggest that helpful discussions like this one will be referenced within or, say, linked to from the respective entry within aTbRef

Yes, this crops up enough I ought to find a place to put it.

I found this old thread while looking up how to populate the suggested values of a list attribute. I want to add the example I’m working on, since it isn’t working for me yet (and I’m not sure what I’m missing).

I have:

  • a note called /Roles that contains several notes, each being a job role that someone might be interested in taking on; the $Name of each is the job title
  • a prototype called Learner, representing a person who’s learning technical subjects
  • a list-valued field in Learner called RoleInterests, representing roles a particular person’s interested in taking on

I want the suggested values for RoleInterests to be populated with the names of the roles, i.e., the $Name of each child of /Roles.

From what I read here, I can get (for example) the $Text of Roles to contain the list of its child titles, no problem. Per this page, it seems I should be able to run an action like

attribute("RoleInterests")["suggested"] = "foo;bar;baz"

on Learner to set the suggested values, and that to draw these values from Roles I could write

attribute("RoleInterests")["suggested"] = collect(children($Roles), $Name).sort

in a Stamp applied to Learner. However, this doesn’t seem to have an effect—and for all I know, perhaps this isn’t the ideal way to be doing what I’m doing. So, could y’all point me in the right direction?

I’m using 9.1.0 (b542).

Thanks!

Well, it should work, but I can confirm it doesn’t testing here in v9.1.0. not sure what’s amiss but I have reported it.

Assuming it returns to service, a note on your planned code:

attribute("RoleInterests")["suggested"] = collect(children($Roles), $Name).sort

You can’t use a path or name offset on a designator like children, though you can nest designators as in parent(original). Also I think you meant (looking at you post) to use Roles rather than $Roles as the latter would read a value form an attribute ‘Roles’ in the currently selected note. So, in this case you need to use find() with query, so like:

collect(find($Container(original)=="Roles"), $Name)

Of course until attribute() is fixed, it is all moot.

Also, if RoleInterests is a set, you don’t need the .sort as Sets now auto-sort alphabetically. Indeed, value pop-up lists in Displayed Attributes and Get Info auto-sort too.

Alright!

So if I understand correctly, this is what I want to put in a Stamp (or Rule or Edict) applied to Learner:

attribute("RoleInterests")["suggested"] = collect(find($Container(original) == "/Roles"), $Name)

In the meantime, I already wound up typing the roles into the RoleInterests field, so I can’t tell if the stamp had an effect or if the names I’m seeing are there because I already typed them. But I can make progress, so I’m happy for now.

Thanks for the reply!

Yes - but noting it won’t work until the issue with setting values via attribute() is fixed.

I note ‘Roles’ is at root level so you may need to use either /Roles unquoted or "Roles" (quoted, without the opening /, i.e. either unquoted $Path or quoted $Name. This quirk pertains to root-level notes only and I’m not entirely sure as to the underlying issue.

@mwra—Funny thing. I’ve just spent the last couple of hours trying to figure out how to set “default” and “suggested” for attributes. I was getting ready to report this problem, and see you’ve already documented and reported the problem.

Since this subject has come up, it would be very useful to be able to set the “description” for an attribute as well.

I will further note that when using the “attribute” operator to retrieve the settings for an attribute, these forms work:

$MyList=attribute($MyString)["default"];
$MyList=attribute($MyString)[default];
$MyList=attribute($MyString)["suggested"];
$MyList=attribute($MyString)[suggested];
$MyList=attribute($MyString)["type"];

However, this does not work:

$MyList=attribute($MyString)[type];

This is presumably because there is an operator called “type,” which retrieves the type of an attribute.

My notes on attribute() specify keys should be quoted. I would have taken that lead from official release notes. If the docs say quote, then quote and treat is as a rule not an option.

This is part of the wider ‘when to quote a string value’ dilemma. My general, rule of thumb is to quote all literal string values, except:

  • named designators (this apparently helps Tinderbox look for a designator match before testing $Name or $Path)
  • paths (i.e. a $Path value, but not for $Name as per previous bullet)

Part of the underlying issue is Action codes early years with minimal syntax. So then:

Color = blue

vs. now

$Color = "blue";

In your non-quoted key example, I assume Tinderbox looked for a designator named ‘type’ (even though they aren’t used in this context) and finding none failed silently. Spelunking why code fails is hard due to the loose enforcement of syntax norms surrounding quoting.

In part the increasing complexity has driven the need for more syntax. At the same time support for legacy syntax (and not forcing existing users to update their code) allows ‘old’ methods to survive. A negative aspect of this is occasional users a don’t feel the need to learn the newer syntax and many older files/samples use out-of-date syntax, further adding to the confusion.

In theory we might posit a ‘strict’ switch forcing application of quoting rules. But, first those rules would need formalising. For my 2¢, I think we’re easily past the point where the benefits of that would outweighs the demerits. Working on some v5-era files, I was surprised quite how much work was needed to get them ready for v9 use. Much was tidying (pruning extra windows, updating attribute defaults, etc.)

†. Before the new UI of v6, Tinderbox supported a lot of discrete window (text and views). When such a file is opening in v6+ every window in the old doc opens as a full document window and as doc windows share a same focus (unhelpfully IMO) you end up with lots of seeming duplicate windows. As screen sizes were smaller and layout mote compact, the window(s) need making bigger as may be the need with some icons on maps, etc. Regardless of above, some query terms need fixing, e.g. old Text("#+") becomes current $Text.contains("#+").

1 Like

@mwra—Thanks for the detailed clarification and explanation of the evolutionary paths that got us to where we are today. This really does help clear up some of the sources of confusion.

I agree that your aTbRef discussion of the attribute operator only shows the quoted use for keys. My apologies for confusing this issue for others.

However, the description of the document operator does state and illustrate that “Quotes around the key are optional.” Since the attribute and document operators are often used together in code, and appeared to have similar usage conventions, I experimented with the unquoted keys in the attribute operator in my efforts to try to use the attribute operator to update the “default” and “suggested” configurations of a user attribute from within Action code.

I absolutely agree that the variant that always works as expected should be what gets documented. So, should the same approach be taken with the document operator? At the very least, my take-away is that keys entered as literals for both attribute and document operators should, by convention, always be quoted. I’d prefer a consistent convention to avoid having to apply a convoluted set of mental conditionals to determine what does, or does not work.

My purpose in providing this response is to alert others working with these operators, so that they can avoid the question marks that I pondered and explored via experiments.

I’ll fix that (busy ATM but probably tomorrow)

Indeed, I’m minded to stop documenting (and remove existing references to) options mainly there for legacy use or for expert coders (who have the smarts to figure out silent fails). I think it more useful to community at large to document what works rather than what might work. The only valid optional context as export/action code optional inputs [sic]

†. For the same reasons I’m minded to normalise terminology in a vocabulary note (or more likely a number of notes). I think it makes sense to write for ordinary folk who need help; experienced folk who at worse may be upset their preferred terminology is not the one of choice can still use the info.

1 Like