I’m hoping someone has a good trick for this. Is it possible to keep an incrementing counter (like provided by the counter parameter of the $list widget) that works through nested lists?
For example an outer $list that has projects and then inside of that an inner $list that has tasks. Is there a way to keep track of when you’re halfway through the overall list that you’re on “total row” X?
Here’s a toy example of the problem, though in this case there’s the same number of “inners” per “outer” so that’d be easy to do with math. In reality, each project has a different number of tasks.
I can only think of two approaches - neither of them ideal:
Do all the outers and inners in the same $list so that you can use the counter parameter as-is, and then separate outer from inner afterwards. This is what I’ve done before, but one of my filter steps has recursion and breaks the ability to join with the <currentTiddler> prior to the function.
Instead of a displaying list, put the routine in an action that loops and stamps along the way, then account for any activity that produces or changes the list, and ensure it’s re-run. Possible but brittle.
Any other creative ideas? The purpose of which is for keyboard based navigation. This is an occasion where the ability to store a variable at the each filter run (other than currentTiddler) would really help, as then I could use #1 above.
I am not sure, what you try to achieve. A plain text description would probably make more sense for me. For me your code is only confusing.
But if you have some tiddlers tagged task, you can count them without any lists. IMO a task is a task. It does not really matter in which project it is. eg: [tag[task]count[]]
If the project matters you can use [tag[project]tag[task]count[]]
As I wrote, you should be more specific with plain text.
Also how does the number of tasks relate to keyboard navigation?
outer: 1 and inner: 1 and total: 1
outer: 1 and inner: 2 and total: 2
outer: 1 and inner: 3 and total: 3
outer: 2 and inner: 1 and total: 4
outer: 2 and inner: 2 and total: 5
outer: 2 and inner: 3 and total: 6
outer: 3 and inner: 1 and total: 7
outer: 3 and inner: 2 and total: 8
outer: 3 and inner: 3 and total: 9
outer: 4 and inner: 1 and total: 10
outer: 4 and inner: 2 and total: 11
outer: 4 and inner: 3 and total: 12
outer: 5 and inner: 1 and total: 13
outer: 5 and inner: 2 and total: 14
outer: 5 and inner: 3 and total: 15
This only works if you have a fixed-size inner list for every outer one. If they’re dynamic, a more complex technique would be required.
The raw truth is no, we cant maintain an incrementing counter, but we can find a solution. I have raised this a few times but the terminology clashes with existing terminology, this always makes it harder to put an argument.
The idea is what I would call a global variable (already means something different). A variable against which you could add/subtract/increment across more than one procedure or nested widgets.
There are options and I will state them at a high level and provide more details on request.
If the filter permits you can use counts in filters to prepopulate sums, counts etc… that may assist you however it gets more complex with nested variable lists, as you have found.
One workaround is a button, because within a button you can nest actions that read, increment and save a value in a field or temp tiddler. However the button requires a trigger.
One of the reasons I have suggested we have a trigger on open tiddler. This could collect the totals for subsequent use.
If we could address the missing volatile “global variable” it would be great, until then I think there may be a number of slightly convoluted ways to achieve what you want however the specific application can alter which is the most efficient way.
The following is behind the below solution
Another way is to use hierarchical number system 1.1 2.3 2.4 etc… the first and second part can be arbitrarily high but if you have a separate nested listing, whose output is every pair you can count these pairs to get the total count, then divide by 2 to get the middle nth, then lookup the pair name eg 3.4 as the middle entry (Your subsequent nested list can then test if you are at the middle).
It may be possible to use nesting within a function to generate this list of pairs.
The word index here refers to the compound list item reference
just-indexes could be rewritten as a function, or nested function with a little work.
Another way is to make all-items the list of “all titles”, regardless of depth in the hierarchy and count those, but storing the index needs less memory.
Thanks everyone for your comments, my situation is fairly complex and I feared would be far too long of a post to properly explain things, but my toy example was definitely not enough - sorry about that.
@Scott_Sauyet, agreed that if each it was fixed size it’d just be arithmetic, but I unfortunately it’s not.
@TW_Tones, yes your mention of a button was my “not ideal” option 2, trying hard to avoid that.
I also realized that my use of the word middle was taken more literally than I intended - I just mean that I need to know the relative position of each item on the line.
@pmario and others, here’s a little more on the why:
I have an interface where a list of todos shows in various sidebars under different groupings, dates, assigned person, or project. There’s a search bar that also filters the options. So, I need to display the tasks grouped and sorted depending on the tab. While the search bar is selected, clicking up or down runs an integer counter starting at 1 for “first task in list” and so on as a temp tiddler. Then in the display of the filtered, grouped, and sorted tasks, I need to highlight and act upon the tasks where their index matches the number generated by the keyboard navigation up/down etc. With a single $list this is easy because of the counter attribute. Then on the line to be displayed, I can set the style to a filtered transclusion comparing the counter variable to the value of the temp variable adjusted with the up/down. Importantly it’s not only for highlighting the color, but other keystrokes like ctrl+enter will mark the task is complete - again that button logic is in the list, but it has to have the counter variable to compare to the index.
Hopefully, now you can see how that breaks down when the lists are nested . In the gif shown you see it working because in the case of “When”, the when value is also a property of the task, so some extensive use of :map:flat allows me to store the date and the todo tiddler titles in a long manually joined string that I then later have to parse out for display purposes, but at least I can still use the counter variable. I have been trying to use that for the “What” tab which groups into project, but the way of figuing out what project to attribute the task to is dynamically look “up” a hierarchy by a recursive function to find links that look like task names. Unfortunately this recursive function means that after it’s called, the value of currentTiddler is no longer the result of the previous filter run, which means I can’t use the same technique as the “When” tab of just joining everything needed and then parsing it within the list.
Sorry for the wall of text, but hopefully that gives a bit more color to my situation. To repeat what I know could work but I an trying to avoid, yes I can procedurally generate the whole list in a button-triggered routine which will allow me to generate a “global index/counter”, but then I have to worry about changes that would make that inaccurate. Hopefully this also explains why if we had the previously requested ability to set the end of a filter run to a variable other than currentTiddler, then it wouldn’t be overwritten and I could go back to what I’m doing on the “When” side, as my recursive function wouldn’t overwrite it.
It does make much more sense now. – And – It is complex.
I’m not sure, if there is an easy solution. – Is it possible to create a minimal testcase at tiddlyhost, without any sensitive data.
Your desire is clear now, but without the real data structure, we will probably be unable to help you much.
You mentioned :map:flat – so I assume you would need the value of the filter just before the :map in a currentResult variable, that can be joined with the result of the :map filter-run
We did discuss this, but there is no implementation atm. There also is no specified way to how we can push variables. I think what you would need is, something like this. eg:
Where push[test] pushes the incoming currentTiddler variable into variable test, so it can be used after the currentTiddler has changed. In my example get[type] will overwrite the currentTiddler variable, so it is lost.
push[test]maybe possible to push test.index, test.count, test.isFirst and test.isLast to the “stack”. – maybe
I’m not sure if the “compiled” filter can handle that, but it seems we need that info often.
Would it be enough for you if <test> would be available like this?
I will work on a TiddlyHost version, will take some time to get enough necessary bits to re-create things.
Your assumption is correct, if that push[test] existed, it would indeed solve my problem. You understand that perfectly. Just storing the incoming value would be good for this current scenario, I’d have to think through the usecases for the others
Just had an idea that worked. It’s not very efficient since I have to do all of the recursive stuff much more than is really needed, but is probably still good enough to use until something like push[] exists.
Here is a TiddlyHost explaining my outline structure: TiddlyHost Example Structure. I built an outliner program in-between my “Bullets” plugin while Saq was working on Streams - they work similarly though mine is made to work more like a combination of Logseq & OneNote. I will eventually release it publicly.
The ideal and efficient solution for my structure is to run through all todos once and append the project and then sort by it, so it’d be like:
<$list filter="[todo-status[todo]] :map:flat[.up[]links[]prefix[# ]first[]addprefix<delim>addprefix<currentTiddler>sort[]]" counter="counter">
<$let
project={{{ [<currentTiddler>split<delim>nth[1]] }}}
todo={{{ [<currentTiddler>split<delim>nth[2]] }}}>
</$let>
..efficient test for project against outer list - variable match variable..
</$list>
but since the addprefix<currentTiddler> gets messed up after .up[] I can’t save both the project and todo.
You can see that this is less efficient because I run the expensive recursive function .up[] again for each todo, but if the list of todos is smallish, it shouldn’t be too bad.
OK. That’s clever and tricky. I did never think about building a tree which uses the links[] operator. Especially if you “travel” the structure recursively. – There are some brain twisters.
I do have no real ideas atm how we can solve you usecase easily. – May be thinking about push[] will be easier
I will admit to being a bit under the weather at present and may not be thinking completely clearly, but that sounds like a bug. Are you able to create a minimal test case that shows this behaviour?
Calling a function should not change the value of currentTiddler outside of the function itself.
We have had bugs in the past with the correct value of currentTiddler not being available inside of functions, this sounds like the inverse of that where the value of currentTiddler inside the function is leaking outside. I have a strong suspicion as to where the problem lies but a mimimal test case to reproduce the problem would be helpful.
You may also want to consider using the tiddler titles of the to do tiddlers instead of a counter to drive the UI. My Notation plugin takes this approach, going down the list is then as simple as finding the tiddler title after the current one (allafter{temp-tiddler}first[]).
I’m stunned! As usual your suggestion is perfect. I’ve just been staring at it and trying to learn from it. I think the short version is that I thought the [all[]] in the getProject() function would return the “incoming value” which had already been prefixed instead of the currentTiddler.
Example? Yes, I’ve now added a tiddler at the TiddlyHost and here’s a quick image
I’ll do some testing. That seems like it would be slower as what I’m doing now is just incrementing a temp value by 1, but I’ve also heard that the counter attribute can be a performance issue. Thanks for the tip.
The code I posted is a workaround for the bug I mentioned, essentially removing the need for the currentTiddler variable to be correct.
The value of the variable currentTiddler inside a function should not change the value of that variable outside of it. The fix is very easy, I just need to be able to add a sensible test for it, which requires being able to reproduce it with as few tiddlers as possible.
Let me know how that pans out. Using the nth-child selector to highlight the appropriate list item does have a certain appeal.
Edit:
@stobot note how getProject is not used as a custom operator but rather as a variable.
Did you look at the working example I provided @stobot ?, it did provide an approach where you obtain the index value by wikifying the nested lists and then reuse that when iterating the nested lists. This code pattern can be used to implement what I perceived to be the requirements you subsequently elaborated on (but I did not study in detail). In the method I gave no button needed.
I am confident this list of index numbers could be generated within a function or nested functions something others have done.
Those numbers can then be used to lookup a title, use as a unique id and other mechanisms.
It seems to me the algorithm I used could be generalized, and perhaps supported by a widget/custom widget to assist in problems of this kind. Basically multiple passes through the data and prepare lists in variables for lookup later. To overcome this structural gap of accumulation across widgets or nestings.
Thanks @TW_Tones. I didn’t dig fully into your code before but have now done so - and you’re right that the concept could be adapted to work - thank you for that!
The concept boils down to (for my own future reference):
Write the whole nested mechanism that’s needed for listing results twice:
a. Once inside a wikify in a way that can be easily split and counted (you used a #.# notation)
b. Once for the actual display of the content
For each line in the display, you can look up the current value or index against the list to get position
If I’m thinking through it properly, the #.# notation could easily be replaced with just the titles themselves (importantly only if unique). Then instead of generating the line index, doing a split, count, etc. you could do an enlist-input, allbefore, count. Gets you to the same place I think.
I like having multiple options so thanks for prompting me to take another look. This one costs me a wikify and having to run through the whole thing twice - once for the “lookup list” and once for display, so I expect it to be a little slower than a single-pass method identified above, but yours is likely more flexible in other applications.
I am glad you have understood what i have done. I completed it to meet what i thought was the current requirements. As you say saving the list of titles is more semantic but would not have allowed the hierarchy numbers (not that you asked for them).
Title lists can be very big so the index I used is much less bytes.
I am still developing this approach but will start with using a hierarchical function with [format:titleist[]join[ ]] so no wikification is needed. We can use a list of titles as the input to the next step rather than filter the whole wiki again.
There is also the index feature on the set widget that may be useful.
Once we have this position in the full list we could use it as an index to all types of matching lists even with a “blank” value for empty items to maintain the number of entries.
If you follow this approach do share any new algorithms you develop