Help with using Dataview to return the task count for all files in a folder

What I’m trying to do

I currently have a nice “Todo” output in my folder notes created with a Task view that lists all !completed tasks and groups them by file for all files in a folder and sub-folder:

What I am trying to do is to return the total count of !completed tasks, so in this example it would return 11 (2 + 8 + 1). Ideally, I would want this to appear on the callout header but it could also be the first line of the callout body:

Things I have tried

I have tried using variations from a similar post (Help with using dataview to get task count) using both Table and Task views.

TABLE WITHOUT ID
	key AS Key,
	length(rows) - length(filter(rows.tasks, (r) => r.completed)) AS "Task Count"
FROM
	"path/example"
FLATTEN
	file.tasks as tasks
GROUP BY
	tasks.text
SORT
	length(rows) DESC

In the folder I am trying to get this to work in, the above returns a list of 40 keys with 35 having a count of 0 (list of all the files without tasks) and 5 keys that account for 105 tasks (yes, there are many repeated tasks like “add detail” many times within a single file).

I’m guessing I need an inline dataview/JS solution to get what I’m looking for but I am not familiar enough with the dataview structure or the language to even guess at what it might be.

I have used = length(this.file.lists.task) to return the count for an individual file, but I cannot figure out how to do it for all files in a folder and sub-folder in a dynamic way.

LIST length(rows)
FROM "path/example"
FLATTEN file.tasks AS tasks
GROUP BY ""

this sort of works but doesn’t give you something you can use in the title of a callout – you’ll need dataviewjs for that

Thanks, this at least gets me half-way to what I want. Now I might be able to use this as a starting place to figure out the JS to get all the way there!

one thing that makes dataviewjs easier to get into is the prevalence of functions that let you use regular dataview queries as a starting point:

let query = await dv.tryQuery('TASK FROM "path/example"')
dv.paragraph(query.values.length)

Great, this is just what I was looking for, the only thing missing was WHERE !completed to make the number dynamic when I check off items.

let query = await dv.tryQuery('TASK FROM "path/example" WHERE !completed')
dv.paragraph(query.values.length)

Now, I just need to figure out a way to get it into the callout title.

my general approach these days is to render callouts from dataviewjs rather than insert dataviewjs into callouts, but there may be a way that you can use inline dvjs in the callout title to get your desired outcome

OK, I didn’t even realize I could do that!

Now I’ve got the following:

let query = await dv.tryQuery('TASK FROM "example/path" WHERE !completed)
dv.span('> [!todo]- Todo (' + query.values.length + ')')

Which gives me the callout heading I want. Now I just need to get the tasks back into the callout, haha.

hey you’re almost there! all you need is > at the start of all your query lines and then include it in your render. Rather than do it by hand I usually use a split and join.

here’s an example from somewhere in my vault:

let text = `
\`\`\`dataview
TASK
FROM !"template" AND #todo
WHERE !completed
SORT pri DESC
LIMIT 20
\`\`\`
`

text = "> [!TIP]+ tasks\n> "+text.split("\n").join("\n >")+"\n\n"
dv.paragraph(text)

Putting it all together:

let query = await dv.tryQuery('TASK FROM "2-areas/the-end-of-my-life" WHERE !completed')

let text = `
\`\`\`dataview
TASK
FROM
	"example/path"
WHERE
	!completed
GROUP BY
	link(file.link, file.path)
\`\`\`
`

text = "> [!todo]- Todo (" + query.values.length + ")\n> "+text.split("\n").join("\n >")+"\n\n"
dv.paragraph(text)```

Gives me this:

It is a bit annoying that there is an extra space between the the following callout, but I can live with it.

Thank you so much @cheezopath!