I’ve searched in Dataview Docs, this forum and Discord and couldn’t find a solution. I’d appreciate if someone could point me to documentation or offer any suggestions on how to achieve it.
Many thanks
What I’m trying to do
I want to retrieve a recursive list of all outgoing links from a specific note.
Generating a list/table of outgoing links from a “root” page and, in the same result list, include all outgoing links from the previous level of outgoing links.
Ideally, the query would include unlimited depth (listing outgoing of outgoing links indefinitely) similarly to what obsidian graph does.
Given the graph below, I’d like to use dataview to return outgoing links recursively as in the pseudo-dataview queries provided as examples below:
I reckon you can do that using DataviewJS. Build the query function, and then call it with your root page, and potentially a max depth indicator to avoid eternal recursion.
If you go down this rabbit holepath, be sure to use a dictionary or set to write result in, so that you don’t traverse in circles.
I figured out how to achieve what I wanted and I’ll leave some guidance here for future reference.
My interest was in recursively listing only “downstream” (children) of pages whose direct or indirect relationship is explicitly determined by the “up” metadata field.
Although my implementation only requires inlinks, I decided to leave both inlinks and outlinks in the code shared here so it can be used as a baseline by those who (like me) are not familiar with dataviewjs.
The implementation supports multiple parents for the same page. E.g: up:: [[Parent A]], [[Parent B]]
There are two versions:
Not-Flattened
When a page has multiple parents, it displays one row for the page.
Parents are displayed as bulleted list in the same row.
Flattened
When a page has multiple parents, it repeats the page in multiple rows, as many times as there are parents.
Each parent is displayed in a separate row.
The screenshot demonstrates the expected results from a data sample based on the diagram originally posted when I created the topic.
//---------------------------
// Render a table containing the children notes as defined by the
// "up::" metadata. (Where up:: value denotes a "Parent" relationship, and multple parents are acceptable)
// Notes are retrieved recursively. So children, grandchildren, etc.... are retrieved
// In this non-flattened version, when multiple parents exist, all parents are listed as
// bulleted list items in the same row (multiple-parents per row)
// --------------------------
// Author: Renato Mendonca
// Date: 2022-12-23
//---------------------------
let page = dv.current().file.path;
let pages = new Set();
//Add the dv.current() page to the stack as the starting point.
let stack = [page];
// Iterate through linked pages. Starting from the current page (dv.current)
while (stack.length > 0) {
// Get the from the stack to check its in/out links
let elem = stack.pop();
// Meta is this iteration's page that is the source of outlinks and target of inlinks
let meta = dv.page(elem);
// If there isn't a page in the stack. Leave the loop.
if (!meta) continue;
// Iterate through all in and out links from this iteration's page (meta)
for (let inlink of meta.file.inlinks.concat(meta.file.outlinks).array()) {
// Iterate through in/out links
// Declare current in/out link as a page
let link_up = dv.page(inlink.path)
// Retrieve the "up::" value for the current link
// If linked file is not yet created (just a placeholder link), skip and don't try to get "up::" value
if (link_up === undefined) continue;
// if linked page contains an "up::" one or more values, store them in an Array.
// If linked page don't have an "up::" field, PathArray is empty.
let link_up_PathArray = (dv.page(inlink.path).up === undefined ? [""] : dv.array(dv.page(inlink.path).up));
let link_up_Path = (dv.isArray(link_up_PathArray))? link_up_PathArray.join(", ") : [""];
// if this iteration's source page contains an "up::" one or more values, store them in an Array.
// If this iteration's source page doesn't have an "up::" field, PathArray is empty.
let this_up_PathArray = (meta.up !== undefined)? dv.array(meta.up) : undefined;
let this_up_Path = (meta.up !== undefined)? this_up_PathArray.join(", ") : [""];
// If the linked page is already in the Set or if it is equal to the this source page parent ("up::"), skip
// As we don't want duplicates and we want to ignore any upstream links for now.
if (pages.has(inlink.path) || inlink.path == this_up_Path) continue;
// If the this source page is one of the values in the "up::" field of the linked page then:
// Add to the pages Set and push to the stack
if (link_up_Path.indexOf(meta.file.path)>=0) {
pages.add(inlink.path);
stack.push(inlink.path);
}
}
}
// Data is now the file metadata for every page that directly OR indirectly links to the current page.
let data = dv.array(Array.from(pages)).map(p => dv.page(p));
//Render a Header and a Table
dv.header(1,"Downstream notes | Recursive | Not-Flattened")
dv.table(['Note','Created',"up"], data
.sort(p => p.file.mtime,"desc")
.map(p => [p.file.link,p.file.ctime,p.up])
);
Code: Flattened version
dataviewjs
//---------------------------
// Render a table containing the children notes as defined by the
// "up::" metadata. (Where up:: value denotes a "Parent" relationship, and multple parents are acceptable)
// Notes are retrieved recursively. So children, grandchildren, etc.... are retrieved
// In this Flattened version, when multiple parents exist, each parent record
// is represented in a separate row (1 parent per row)
// --------------------------
// Author: Renato Mendonca
// Date: 2022-12-23
//---------------------------
let page = dv.current().file.path;
let pages = new Set();
//Add the dv.current() page to the stack as the starting point.
let stack = [page];
// Iterate through linked pages. Starting from the current page (dv.current)
while (stack.length > 0) {
// Get the from the stack to check its in/out links
let elem = stack.pop();
// Meta is this iteration's page that is the source of outlinks and target of inlinks
let meta = dv.page(elem);
// If there isn't a page in the stack. Leave the loop.
if (!meta) continue;
// Iterate through all in and out links from this iteration's page (meta)
for (let inlink of meta.file.inlinks.concat(meta.file.outlinks).array()) {
// Iterate through in/out links
// Declare current in/out link as a page
let link_up = dv.page(inlink.path)
// Retrieve the "up::" value for the current link
// If linked file is not yet created (just a placeholder link), skip and don't try to get "up::" value
if (link_up === undefined) continue;
// if linked page contains an "up::" one or more values, store them in an Array.
// If linked page don't have an "up::" field, PathArray is empty.
let link_up_PathArray = (dv.page(inlink.path).up === undefined ? [""] : dv.array(dv.page(inlink.path).up));
let link_up_Path = (dv.isArray(link_up_PathArray))? link_up_PathArray.join(", ") : [""];
// if this iteration's source page contains an "up::" one or more values, store them in an Array.
// If this iteration's source page doesn't have an "up::" field, PathArray is empty.
let this_up_PathArray = (meta.up !== undefined)? dv.array(meta.up) : undefined;
let this_up_Path = (meta.up !== undefined)? this_up_PathArray.join(", ") : [""];
// If the linked page is already in the Set or if it is equal to the this source page parent ("up::"), skip
// As we don't want duplicates and we want to ignore any upstream links for now.
if (pages.has(inlink.path) || inlink.path == this_up_Path) continue;
// If the this source page is one of the values in the "up::" field of the linked page then:
// Add to the pages Set and push to the stack
if (link_up_Path.indexOf(meta.file.path)>=0) {
pages.add(inlink.path);
stack.push(inlink.path);
}
}
}
// Data is now the file metadata for every page that directly OR indirectly links to the current page.
let data = dv.array(Array.from(pages)).map(p => dv.page(p));
//Render a Header and a Table
dv.header(1,"Downstream notes | Recursive | Flattened")
dv.table(['Note','Created',"up"], data
.sort(p => p.file.mtime,"desc")
.flatMap(p => dv.array(p.up)
.map(item => [p.file.link,p.file.ctime,item]))
);
This is also a good example on how to use flatMap() (I didn’t find many examples and struggled a bit with it).
I also found other related topics on this forum, so I’m linking them here: