Timeline view -- help needed on some tweaks

Hello, all!

I’ve begun to use timeline view as a project management tool as I develop and enact an entry plan for a new job.

I’ve made a few rules, etc. that are helping keep everything together. For example, I have a rule that looks up to any note’s parent note, and sets the timelineband to match the parent note’s timelineband. This keeps everything together on the timelinview, and allows me to do some basic visual nesting. I just set the timelineband of the parent item, and everything contained within falls in underneath it.

I’m stumped on one thing … I’d like to set the parent item’s enddate to the latest enddate of any of the child notes. That way, if I drop in a child item and set the enddate way out there, the parent item will adjust its enddate to “contain” that child note on the timeline.

So, I’m thinking it would be a rule in the prototype for timeline actions that would check to see if there are any children. If none, it would do nothing. If there are children, though, it would determine the latest enddate of any child note. Then, it would adopt that latest child note enddate as its own enddate.

Any hints on how to get started off on the right foot? Some sort of loop to work through the child notes?

So you want the parent $EnDate to be exactly the same as the latest $EndDate of any child. There a are number of aways to do this. The most straightforward is perhaps to use collect_if() scoped to a notes children:

if($ChildCount>0) {
   $EndDate = collect_if(children,$EndDate!="never",$EndDate).sort.($EndDate).at(-1);
}; // otherwise do nothing

Let’s break this down:

  • collect_if() looks at a given scope—here, the immediate child notes, checks if they meet query—here, if they have a value for $EndDate, and for matching notes it returns the specified attribute—here-the child’s $EndDate. So the result is a list of all child $EndDate values, excluding any child with no $EndDate value.
  • by chaining a .sort() to the list returned by collect_if() and specifying an argument of $EndDate, the list of end date values is sorted in ascending date order.
  • As we want only the last (latest by date) $EndDate, we get the last list item using the operator .at().

Notes:

  • See that for Date-type attribute values the default order is ascending. So the latest is last, but
  • A Date-type value of “never” ( i.e. not/never set) always lists after all other values. As we want the last valid end date we ensure, via the collect_if() query argument, that we only collect actual dates and filter out defaults.

[Edit: In giving the less verbose examples I confused myself. Here `$EndDate~ needs to be used twice as two different `collect_if(0` arguments. The first is doing the short-form test for a non-default $EndDate value. The second is the value of the attribute to past back t= the output list … which just so happens to be exactly the same (looking) code as the first argument.

For even terser code, once we’re happy it all works:

if($ChildCount) {
   $EndDate = collect_if(children,$EndDate).sort.($EndDate).at(-1);
}; // otherwise do nothing
Edit: ``` if($ChildCount) { $EndDate = collect_if(children,$EndDate,$EndDate).sort.($EndDate).at(-1); }; // otherwise do nothing ``` For both the initial query and the query within `collect_if()` , calling just an attribute is the [short-form test](https://www.acrobatfaq.com/atbref95/index/Automating_Tinderbox/Coding/Action_Code/Operators/Full_Operator_List/AttributeName_i_e_a_short_form_test_for_no_value.html) for it having a non-default value.

Separately, as well as using List.at(N) we can use the newer List[N] notation. So:

$EndDate = collect_if(children,$EndDate).sort.($EndDate).at(-1);
// is the *same* as
$EndDate = (collect_if(children,$EndDate).sort.($EndDate))[-1];
Edit: ``` $EndDate = collect_if(children,$EndDate,$EndDate).sort.($EndDate).at(-1); // is the *same* as $EndDate = (collect_if(children,$EndDate,$EndDate).sort.($EndDate))[-1]; ```

The extra parentheses in the second line aren’t strictly needed (i.e. Tinderbox’s parser will guess correctly), but they tell Tinderbox do the collect and sort operations before trying to return the last item in the resulting list.

Note: the shortenings above don’t have any noticeable effect of the performance of the code, but some people like to use shorter code at least once they understand the syntax!).

HTH :slight_smile:

I think we’re close! Thank you!

I have 3 children under the current test note, with EndDates as follows:

3/31/24
6/30/24
7/30/24

^^ This is resulting in “never.”

^^ This is yielding 3/31. Changing -1 in at() to 0, etc., doesn’t change the result. I can get some different results by removing the period after “sort” but I’m not sure if that’s correct. Either way, still can’t get the correct answer of 7/30.

1 Like

Also, when the children are not of the appropriate prototype (“Timeline”), it seems to be setting the parent EndDate to “never.”

Here’s the latest code I’m using:

if($ChildCount) {$EndDate = collect_if(children,$EndDate!="never" & $Prototype=="Timeline",$EndDate).sort($EndDate).at(-1);
};
1 Like

OK … I think I have this working along with my other rules/controls. I also had to set some sorting on the prototype to get everything ordering up properly.

Below also checks to make sure the children are the appropriate prototype. Each item, here, can have several children, and I only want it considering those that are timeline events.

if(count(collect_if(children,$EndDate!="never" & $Prototype=="Timeline",$EndDate))) {$EndDate = collect_if(children,$EndDate!="never" & $Prototype=="Timeline",$EndDate).sort($EndDate).at(-1);
};

Note: I’ve fixed an error in my latter examples for you, although that wasn’t, I think, the underlying cause of your failure to get the desired result.

Ah, I’d assumed from your starting comment that the children are all of the desired type. So now you need to also filter for notes of the appropriate type.

That can be fixed by:

if($ChildCount>0) {
$EndDate = collect_if(children,$Prototype=="TimeLine"&$EndDate,$EndDate).sort.($EndDate).at(-1);
}

Now the collect_if() only collects and $EndDate for children that have both a prototype of “Timeline” and a non-default value for $EndDate.

Using .at(0) won’t help as we’re sorting Dates in ascending order and .at(0) is the first. also reversing the sort order (lest you try) also won’t help as the the underlying ‘problem’ is you need to ensure your list of dates contains no default "never" items as these tend always sort where you don’t want them. IOW:

  1. Make sure the list of dates has no default values
  2. Then sort the list and take the last (.at(-1) or first (.at(0)) as needed.

If still stuck, please upload a small test TBX that illustrates your problem. There’s no fault here but it is easy to overlook unmentioned factors that you are currently unaware have an effect of the outcome you desire by the manner described. For instance: the children of a Timeline container may varying prototypes—on none.

Looking at your replies above, another learning point. You starting changing things at the wrong ‘end’ of the chain, e.g. by altering the .at() value. Given the computer adage “Garbage in, garbage out” the first test is to make sure you have a sensible output from collect_if(). So, when the original solution failed, i would have used code like:

$MyList = collect_if(children,$EndDate,$EndDate);

and looked at the values. Aha, we see list mixing dates with “never”. So, our query needs to add extra term(s) to filter out those annoying “never” values . So we make our test:

$MyList = collect_if(children,$Prototype=="never"&$EndDate,$EndDate);

Now $MyList, shows only actual dates. Now we can test the sort:

$MyList = collect_if(children,$Prototype=="never"&$EndDate,$EndDate).sort($EndDate);

The list should now be sorting in oldest to most recent dates. So now we can test catching just the last date:

$MyList = (collect_if(children,$Prototype=="never"&$EndDate,$EndDate).sort($EndDate)).at(-1);

Your list should now only hold 1 item, the latest of the dates. Note, if several source items have the same $EndDate the value chosen is the full-date-time value (as two $EndDates on the same daty but different times will sort so the one with the latest time comes last). But as you’re really only interested in the day of the last dates that nuance likely doesn’t matter.

Now your list has only one 9correct) value, we can use the code:

if($ChildCount) {
   $EndDate = (collect_if(children,$Prototype=="never"&$EndDate,$EndDate).sort($EndDate)).at(-1);
};

If still stuck, do consider posting a TBX showing the problem with attribute values such as you are actually using.

†. Before you ask there isn’t a pre-build action code to filter lists of dates to remove non-date values. You could write a function for that, but it turns out it is easier just to not collect them in the first place (as shown above)

Yes, I’d expect that should work.

Thank you so much!!! I’ll keep an eye on everything moving forward.

1 Like

What are pros and cons of using this approach vs. max?

$EndDate=values(children,$EndDate).max();

None really. As so often there a numerous ways to go about a task.

This is certainly shorter than the previous example. It doesn’t allow for filtering the children—which isn’t exactly a ‘con’. Indeed, you could use a find() for the first argument.

This has made me go revisit

… in fact I need to dig deeper as different parts of action code sort the never value inconsistently.

  • never first, then oldest → newest: List.nsort(). And, by implicitly using an internal numerical sort:
    ** max(), min(), List.max(), List.min()
  • never first, then newest → oldest. View sort via $Sort and $SortAlso.
  • never last, then oldest → newest: List.sort(). Lexical sort.

So, 3 different sort styles. Unintentional in design but confusing nonetheless—especially if you don’t know enough to understand the cause of the difference.

†. In a lexical sort, the values are parsed and ordered on the character (ASCII, since Unicode) number of each sequential character in the string. Thus we get a sort of ‘1,11,2’. In a numerical sort non number values are ignored: they are essentially assessed as 0, i.e. no number. So in an ascending value sort, never lists first. This is why min() returns “never” if used on a list of dates where some are not set. See more on Lexical vs. numeric sorting.

1 Like