Nested list of open tasks from Project folders with dataviewjs

What I’m trying to do

I have a folder structure like

Projects/
   Project 1/
      Project Note.md
      Meeting Note 1.md 
  Project 2/
      Project Note.md
      ....

I’m trying to create a list of lists such that the outer list will be the projects that have a status of open, and each list item has a list of open tasks.

I can get a list of all these folders.
I can get a list of all open tasks for these.
I just can’t figure out how to create a nested list of these.

So the result should look the same as if i had typed by hand

- Project 1
	- [ ] Task 1
	- [ ] Task 2
- Project 2
	- [ ] Task 1 

Am I trying to be too smart by using dataviewjs, and can this simply be done with inline dataview?

What I’ve tried

const folder = "Projects";  // Replace with your top-level folder

// Helper: Check if a folder contains a PoC file with 'active' status
function isFolderActive(folderPath) {
  const pocFileName = `${folderPath.split("/").pop()} Projects`;  // Construct the Project filename
  const pocFile = dv.page(`${folderPath}/${pocFileName}`);  // Get the Project file

  return pocFile && pocFile.status === "active";  // Return true if status is 'active'
}

// Collect all valid tasks from eligible folders
let allTasks = [];
dv.pages(`"${folder}"`).forEach(page => {
  const folderPath = page.file.path.split("/").slice(0, -1).join("/");  // Extract folder path

  if (isFolderActive(folderPath)) {
    page.file.tasks
      .filter(t => !t.completed)  // Filter open tasks
      .forEach(t => allTasks.push({ ...t, path: page.file.path }));  // Add task with path
  }
});

// Group tasks by relevant subfolder (excluding 'Projects' and 'year')
const groupedTasks = allTasks.reduce((acc, task) => {
  const pathParts = task.path.split("/");  // Split the path into parts
  const relevantFolder = pathParts.slice(2, -1).join("/");  // Remove 'Projects' and 'year'
  if (!acc[relevantFolder]) acc[relevantFolder] = [];
  acc[relevantFolder].push(task);
  return acc;
}, {});

dv.container.className = "el-ul"
// Render each group as a task list
const folders = [];
for (const [subfolder, tasks] of Object.entries(groupedTasks)) {
  const displayFolder = subfolder || "Uncategorized";  // Handle case with no valid subfolder
  folders.push(displayFolder);
  dv.el('h1',`${displayFolder}`);
 // stuck here!
}

Using this :

```dataview 
TASK FROM "Projects" 
WHERE !completed GROUP BY file.folder 

Gives the correct results, but as a bunch of <h4> tags per folder followed by a list.
I want those folder names as a list as well :grimacing:

Inside your for loop I think you need to nest another for loop to iterate through the tasks of each page and dv.el print them to ‘p’ type elements, below where you are stuck after printing the folder header. or something along those lines.

Why do you need the project names to be in a list, instead of headers?

Is it just for the visuals? Is it for folding purposes?

1 Like