I reviewed Michaels recordings on using functions, and have a fairly good understanding of them, however, I’m still a little unclear on how to pass “data” into functions.
For example, I have a collection note called people. Within this collection are a number of people notes, with a user attribute called $roles, that holes a list of strings for example role1, role2, role3.
I would like to pass this note into the function, and then obtain all of the descendants, and count the number of people for each role in the collection.
I am looking to return a string, such as the following , (role1: 12, role2: 34)
my goal would be to use this in the display expression to show a count of the different roles available in the people collection.
I’m having trouble understanding what I pass into this function, when it is called by the people collection?
I hope this makes sense, if I can clarify what I’m trying to do please let me know. Thanks in advance for any help. Better understanding what I can pass into functions.
In cases like this, where you’re getting lots of kinds of information about a container, you want to pass the $Path of the container to the function. (Exception: if you have various pathological complications, such as container names that contain “/”, or multiple notes with identical names, you might want $IDString. But use $Path if you can.)
The way to get a list of a note’s children is children(passedNote).
You mention wanting descendants but then test for children rather than descendants. See more on Tinderbox’s terminology for outline relationships here, and on designators used in action code here.
Your method suggests role1/role2 two are just indicative and there might be a larger/varying number of roles. If so, you want to make a list of roles and use the list.each(){} operator to iterate the list and get per-link counts.
Taking into account the last above re using paths, and using indicative names, consider:
function fCount(passedPath:string){
var:number r1Count = 0;
var:number r2Count = 0;
var:list childList = children.passedPath; //immediate children [sic] of this note
r1Count = count_if(childList, $roles.contains("role1"));
r2Count = count_if(childList, $roles.contains("role2"));
//count increments for each child note with that role type $role value
return $Name(passedPath)+" (role1: "+r1Count+" role2: "+r2Count+")";
}
I’m not sure if calling the function in $DisplayExpression, i.e. doing more than trival action is the DE is recommended. A more normal approach would be a rule or edict like so:
$MyDisplayString = fCount(this); // store in a user attribute
$DisplayExpression = $MyDisplayString;
This shorts the running of the code out of the context of the Display Expression which now simply reads in a a stored string value.
Also, whilst you may use all-lowercase user attribute names, I’d advise sticking with Tinderbox standard for system attributes. Alongside this thread we see another where this confusion over the case of attribute names tripped up some code.
See more on Attribute Naming. Enlightened self-interest suggests have a set of naming ‘rules’ (a simple convention) to follow makes us less prones to unintentional typos derailing our code.
Now, for those who program all day and a coding language that has existing different conventions for naming variables, etc. then trying to switch between conventions may argue for sticking with a different naming strategy. However, I’d wager the letter group is a small subsection of the general Tinderbox user base.
Thank you for your help and suggestions! I am still having problems and am sure I missing something subtle (as for naming, I was wrong in the initial post, my user attribute was $Roles, not $roles, but I appreciate the thoughts on naming and will use uppercase as per System Attributes - this comes from some prior programming experience as you assumed)
I changed to using a rule as oppose to calling the function from $DisplayExpression as you shared, interesting the $DisplayExpression and $MyDIsplayString returns from the function (although the counts are still not working), but the Container name does not change?
Note the “this” in the text of this note is my attempt at debugging from the function. Not sure if there is a better way to troubleshoot these issues??
As mentioned in prior paragraph, here is the function code, but does not seem to be getting any children when I try to debug by sending children count to the text of the container?
Yes, this worked and makes sense now that I have it complete. Sorry about not quite getting the context from the documentation and all the videos on Functions. I appreciate your time walking through this code.
As a side note, I am re-using (like many on this forum, I have been an on and off user of Tinderbox over the years and getting back into using it more extensively) it to manage a department I am the Chairman of. I see so much potential and am grateful for your help and an excellent tool!
Thanks again
Don
Oh, not how that crept in. Sorry, I’m deep in putting a troublesome publication to press so did not have time to run code.
As the code is in a function, we don’t want to hard set a path (e.f. “”/A"`) but this revised function works (I tested!)
function fCount(passedPath:string){
var:number r1Count = 0;
var:number r2Count = 0;
var:list childList = collect(children,$Path); //immediate children [sic] of this note
r1Count = count_if(childList, $Roles.contains("role1"));
r2Count = count_if(childList, $Roles.contains("role2"));
//count increments for each child note with that role type $role value
return $Name(passedPath)+" (role1: "+r1Count+", role2: "+r2Count+")";
}
I also added a new prototype for this task ‘People2’. If you want nest people, things get more complicated, but let’s fix one problam at a time.
Current assumptions:
the counts are needed only the container ‘People’—or any other container using the prototype ‘Person2’ and which contains People-based children.
the code assumes people are direct children of the container note (it ignores the children’s descendants)
a user String attribute $MyDisplayString exists.
$MyDisplayString is set via the ‘Person2’ prototype’s rule, but this could be backed off to an edict or stamp if needed at scale.
The code used for the rule is: $MyDisplayString = fCount($Path);
The ‘Person2’ stamp has both its $DisplayExpression and $Rule disabled in the prototype itself, which is why you don’t see the effect there but do in notes using the prototype.
As the function is evaluated in the context of the calling note, you could forgo passing the path, i.e. have no function arguments. In which case the call would be $MyDisplayString = fCount(); (empty parentheses must be used) and the last link pf the function code would start return $Name+... as it uses the $Name value of the calling note!
Thanks again for the help and maybe more importantly the thought(s) that went into solving this issue. I have learned more about the use of functions from this process and appreciate that!
As an aside, I do remember Michael Becker talking about using a cache attribute instead of directly setting $DisplayExpression so as to improve performance and I understanding using either Rules or Edicts to call the functions, is there another reason not to call a function “directly” from a note attribute? I cannot see a reason why one would be better than the other but this is just curiosity.
Let’s say you have a display expression that does a lot of work. For example, you might want to show the ratio of this note’s descendants to the entire document:
Chapter 2 (12.5%)
Figuring that out requires counting all the descendants of Chapter 2 and also counting all the notes in the document. So, if you’ve got 1000 notes in the document, you’re going to need to examine a few thousand notes to know what to display.
That’s no problem! At least not usually. But suppose nearly every note in the document uses this display expression. Then, a typical note, when it displays itself, needs to look at a few thousand notes. But outline view, in order to figure out its layout, needs to do that a thousand times. Now, we’re looking at a million operations.
When you find yourself needing to do a million things just to draw the name of a note, well, that’s a sign that maybe you want to make a note of the result, just so you don’t need to do that again (phew!). That’s Michael Becker’s caching strategy.
It’s great, but it only really matters in big documents that do complex things. My suggestion is: wait until you run into trouble. You might never have performance problems, even in big documents! If you do, you can always use a caching strategy to speed things up.
As the function is evaluated in the context of the calling note, you could forgo passing the path, i.e. have no function arguments. In which case the call would be $MyDisplayString = fCount(); (empty parentheses must be used) and the last link pf the function code would start return $Name+... as it uses the $Name value of the calling note
This makes more sense and helped me to understand the context of where the function is being run. I did not realize the context is in in note calling the function. Appreciate that info