Dataviewjs query that separates 2nd level of nested tags into headers

Things I have tried

I have searched the forum and the Dataview documentation. Experimenting with dv.header, I was unable to get the results I am looking for.

What I’m trying to do

I am trying to create a dataviewjs query that does something similar to this:

FLATTEN file.etags AS Tags

But, I would like for the query to separate all of the 2nd to the top level nested tags into their own header. For example there might be a header that shows everything for #POV/Time.

I really appreciate any suggestions on how to do this or examples that may help me better learn dataviewjs. Thanks!

I’m not fully understanding your request, nor how your data is setup. So could you please provide some sample data, and how the current query look, and what you would like it to look like?

I think for sure its doable, but its hard to guess your data structure based on what you’ve written so far.

1 Like

I really appreciate this!

For example, Note A has the tag #elem/dad/death/change. Note B has the tag #elem/dad/death/life. Note C has the tag #elem/dad/gifts. Note D has the tag #scene/death/life.

Although this is not what I originally requested, I would ideally really like to have headers for the first three levels of the nested tag in order to break things up. I attached an image to demonstrate what the query might look like, best case scenario.

But, if that is not possible, I would still be thrilled to have the query look the same as the attached image, but just breaking things up with the headers elem/dad and scene/death.

Thanks so much!

I have been thinking about this and had some additional thoughts I wanted to mention. Since the proposed query would already have the links sorted into sections based on whether they contain a specific nested tag, it really wouldn’t be necessary to show the tag column. I can make up another image. It will basically be the same as the one I have already provided, but there will just be the single column for the

It was hopeful to hear that @holroy thought that creating this for at least one of the nested tag levels seemed to be possible. To clarify, I have no expectations for further help here, but any sort of tips or hints that might lead me in the right direction would be appreciated greatly. All of the solutions posted here on the forum are incredibly useful. I’m beginning to dig in and try to find relevant queries that may inform me in terms of how to proceed. I am totally new to dataviewjs, other than using already existing solutions.


So… After some thinking, and some other time consuming IRL stuff, I finally got around to coding something. The prerequisite of the following script is that your TABLE query produces a table where the first column is the tag, and the other columns are whatever they are. This version does not hide the tags, as per your example you only want to expand three levels of tags, and some of your examples have four levels of tags.

Fair warning: My test query is idiotic, and doesn’t have any useful values for the two other columns. It’s the literal text of ex 1 and ex 2 for all rows! :smiley:

So here is the query:

const expandLevels = 3     // How many levels of subtags to expand
const startHeaderLevel = 2 // Starting header level for first tag level
const consoleDebug = false  // Set to true, if you want debug in console

function clog(args) { if ( consoleDebug ) console.log(...args) } 

// In the following, replace the TABLE query with some query which lists
// nested tags in the first column, and have other columns...  

const result = await dv.query(`
  TABLE WITHOUT ID myTag, "ex 1" as someText, "ex 2" as anotherText
  FLATTEN list(
   ) as myTag

if ( result.successful ) {
  const values = result.value.values

  clog("\n\n\n\n*** New run***\n")

  // Define variables to be used to keep track of previous and
  // current tags, and which new headers or rows to output
  let prevTag = [], 
      currTag = [], 
      newHeaders = [],
      sectionRows = [],
  // Loop every value of the table
  for (let row of values) {
    currTag = row[0].slice(1).split("/")
    // Reset running variables
    newHeaders = [] 
    buildHeaders = false
    clog("\nprevTag: ", prevTag)
    currTag.forEach((subTag, i) => {
      // if current sub tag is different from previous tag at the 
      // same level, then build new headers from this level until
      // we expanded enough levels, or run out of subtags
      clog("subtags: ", i, subTag)
      clog("prevTag[i]: ", prevTag[i])

      // Assuming we're not already building headers, then detect
      // whether tags at this level differs, or didn't exist. Both
      // indicate that we need to build new headers
      if ( !buildHeaders ) {
        if ( prevTag && prevTag[i] ) {
          buildHeaders = subTag != prevTag[i] 
        } else {
          buildHeaders = true
      // If needed, push the new header onto the list
      if ( buildHeaders && i < expandLevels )
        newHeaders.push([i, currTag.slice(0, i + 1).join("/")])

    // currTag changed, so we've got some new headers to output
    if ( newHeaders.length > 0 ) {
      // Before the new headers, output previous sections rows ...
      if ( sectionRows.length > 0 ) {
        // If you want to preserve table headers, use result.value.headers,
        // or use [] to remove headers from table.
        dv.table([], sectionRows)
        sectionRows = []
      // ... and then output the new headers
      for (let header of newHeaders)
        dv.header(startHeaderLevel + header[0], header[1])
    // Store this row, and current tag for next iteration
    prevTag = currTag

  // Output remaining rows, given no new headers lately
  if ( sectionRows.length > 0 ) {
    // NB! If changing the header a few lines higher, then change this too!
    dv.table([], sectionRows) 
} else
  dv.paragraph("~~~~\n" + result.error + "\n~~~~")

And this produces the following output:

As can be seen, it builds the headers whenever it sees a change on the same (or higher) level, and it produces a table for each time the header changes.

However, since we don’t have proper control over the column widths, there is no coherent columns when doing it this way. If you want, you can add column headers (for each section), by following instructions in the code and changing: dv.table([], ... ), into dv.table(result.value.headers, ... ) in two places in the script.

The output image uses Minimal theme with colorful headings, but they’re at level H2 through H4.

I’ve seen this, and I’m not sure if you countered for the more than three levels as shown in the first variant. And I didn’t read your query well enough to actually see that it had only a as the column.

I might look into another variant, but I’m entering a busy period again, so I’m not sure I’ve got the time for it (within the next few days). But an idea if only having a file link, would be to present the related file links for any given section as a list, instead of a table. This would also solve the column width issue, but it would also “loose” the 4th tag level information.

Also note that in any case these headers are still not collapsible due to how dataview renders headers, and I’ve not found a way around that. Yet.

1 Like

Thanks @holroy !! Another triumph! I had marked it solved, but that affected my ability to respond. So, for now, I unmarked it.

I am just beginning to try to unpack and understand this. But, before I let too much time pass, I wanted to ask two quick questions:

  1. Is there a way to get all the file links that contain the certain nested tags to be listed in the table?

  2. Does the script require that I define the nested tags, or can it automatically list all the nested tags in my vault?

In your commented code, I noticed:

     // if current sub tag is different from previous tag at the 
      // same level, then build new headers from this level until
      // we expanded enough levels, or run out of subtags

But, then again, you made it pretty clear towards the beginning of the script with the following:

// In the following, replace the TABLE query with some query which lists
// nested tags in the first column, and have other columns...  

I know you are busy, so no worries if you can’t spend any more time on this. I totally understand. You have already created magic here!

Thanks so much!

You can change the query at the beginning to whatever query you’d like that produces a table output, and if that contains file links so be it. The script should then also output those file links.

The only important thing as the script stands, is that the tag needs to be the first column. Other than that, you should be able to enter whatever you want into the query.

The script will use whatever tags are present in the first column (hopefully the table is sorted on this column as well. Not sure what happens if the entries are not sorted… :smiley: ). So by default there is no need to defined any tags, just make the query which lists the tags in the first column.

1 Like

It works like a charm! Incredible! I am going to buy you a coffee. I was happy to see you have a link on your profile. I can’t imagine how many hours you have put in solving problems for people on the forum. It is very admirable. I know you solve these problems out of curiosity, and not for any sort of pay. But, this and many of your forum solutions are immensely helpful for what I am doing, and I feel indebted. Thanks so much! You are awesome!

1 Like

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.