How to reuse a Dataview Query (DQL)?

Reuse a Dataview Query

Here is how to reuse a regular Dataview Query (DQL).
The goal is to have a single update point when I need to change a query used in an Obsidian template (for instance).

Context

My Obsidian Person Template (see below) contains a Dataview query that lists a person’s meetings.
I have created many notes (persons) from this template.
Each of these notes contains a copy of this query.

Say I need to modify this query after the fact to change its behavior because my goals have changed.
This would require updating the query in the template and all the copies of that query in the notes generated with that template.

Here is the Person Template (with Templater syntax):

---
in:
 - "[[People]]"
title: 
company: 
email: 
phone: 
tags:
 - person
created: <% tp.date.now("YYYY-MM-DD HH:MM") %>
---

# [[<% tp.file.title %>]]

<% await tp.file.move ("/Atlas/Notes/People/" + tp.file.title) %>

## Notes


## Meetings

```dataview
TABLE  date AS "Date", summary AS "Summary"
FROM [[<% tp.file.title %>]]
WHERE contains(in, [[Meetings]])
SORT date DESC
```

Issue

Updating the Dataview query is time-consuming because it requires updating the query in the template and all the Markdown files (persons) generated.

Solution

  • Create and use a Dataview View file
  • Update the template and all notes generated with the template (one time).
    • Replace the existing query with a reference to the view
    • Using a reference to the view instead of a regular query has the following benefits:
      • update a single file instead of the template where the query is defined and all the notes generated with it
      • the change is immediately taken into account in all notes using the view
  • From now on:
    • The template will generate notes using a reference to view instead of a copy of the query
    • To update the query, I update a single view file. I no longer update the template and the generated notes.

What is a Dataview View?

A Dataview view is a Javascript file that usually contains a DataviewJS query.
This query is in a single location and can then be referenced in multiple Markdown files.
If I need to update the query I simply update a single view file and then all the markdown files using it will instantly benefit from the update.

The syntax of a DataviewJS query is different from a regular Dataview query. It is Javascript code instead of regular Dataview Query Language (DQL).

:question: Wait! Do I need to switch to using a Dataview JS Query (Javascript) instead of the regular Dataview Query (DQL) I am used to?

No, with a little trick in the view file, we can continue to use most of the DQL syntax we are used to without diving into JS.
This gives us the best of both worlds:

  • change a single file to update the query
  • allow us to continue using the regular Dataview Query Language (DQL) instead of the Dataview JS.

Here is the solution I came up with while scouring the Web (See the Resources section).
This post is a compilation of the good tricks I found there.

Create the View File

Create the person_meetings.js view file in the folder where you put your scripts. (My scripts are in /Atlas/Utilities/Scripts).

// /Atlas/Utilities/Scripts/person_meetings.js
dv.execute(`
TABLE  date AS "Date", summary AS "Summary"
FROM  ${dv.current().file.link}
WHERE contains(in, [[Meetings]])
SORT date DESC
`);

The (Dataview) View file

  • is a Javascript file
  • must have a .js extension/suffix.
    • You cannot create a Javascript file in Obsidian
    • You cannot edit a Javascript file in Obsidian.
    • Javascript files are not visible in Obsiian’s File Explorer View.
    • Therefore, use your system file explorer and favorite text editor to create and edit the View file
    • In my case, the view file is in the /Atlas/Utilities/Scripts Ideaverse Pro folder, but feel free to use a path of your choosing and update the template accordingly.
  • We wrap the regular Dataview query (DQL) ...
    with dv.execute(` ... `)
    • Do note that we surround the DQL query with backticks (`)
    • This JS code calls the Dataview API, we wrap the multiline query in a Template Literal (aka. Template String) delimited with backticks (`). Inside, any JS expression with this notation ${expression} is interpolated (i.e. evaluated and replaced with its value).
    • dv.execute( ... ) takes a regular Dataview query (DQL) as a parameter (not a Dataview JS query), runs the query, and replaces itself with the query results.
    • We remove [[<% tp.file.title %>]] from the Dataview query which is useless in the view. We do not need the link to the view itself but to the Markdown file calling the view (i.e. the person file created with the template. This is where ${dv.current().file.link} comes to the rescue.
    • ${dv.current().file.link} is replaced with the internal link (WikiLink format) of the Markdown file the view is called from.
      For example, if the Person Template generated a Markdown file named Eric Bouchut then the link will be [[Eric Bouchut]].

Edit the template

Edit the Person Template to replace the existing Dataview query code block with this one that dynamically calls the view:

```dataviewjs
dv.view("/Atlas/Utilities/Scripts/person_meetings");
```

:information_source: Template File

In the Template file, when calling the view:

  • Use a code block of type dataviewjs
  • Use the absolute path of your view file (from the Vault root) but…
  • do not specify the .js extension/suffix i.e.
    use person_meetings but do not use person_meetings.js

Here is the updated template using the view.

---
in:
   - "[[People]]"
title: 
company: 
email: 
phone: 
tags:
  - person
created: <% tp.date.now("YYYY-MM-DD HH:MM") %>
---

# [[<% tp.file.title %>]]

<% await tp.file.move ("/Atlas/Notes/People/" + tp.file.title) %>

## Notes


## Meetings
```dataviewjs
dv.view("/Atlas/Utilities/Scripts/person_meetings");
```

Sources

Here are the resources that helped me immensely.
I decided to compile them all in this practical blog post.

3 Likes

This is really useful, thankyou! I have run into one problem, though: I can’t use api calls within the query (I’m using dataviewjs rather than DQL in this case). The following code works fine in a regular dataviewjs block:

const {createButton} = app.plugins.plugins["buttons"]
createButton({app, el: this.container, args: {name: "+/-", type: "command", action: "Templater: Insert Templates/Templater/ChangeLevel.md", class: "button-default button-inline"}})

But if I include it in a view, it throws an error

Dataview: Failed to execute view 'Templates/Views/testview.js'.

TypeError: Cannot read properties of undefined (reading 'createEl')

Do you know if it’s possible to run an API call like this via a view?

1 Like

@CaptainJanegay Try upgrading Dataview to 0.5.64.

1 Like

I’m already on that version - is there a recent change that should make this work?

No, I just wanted to check that you were running the latest version.

The error seems due to the fact that this does not have a container.
this.container is undefined.

TypeError: Cannot read properties of undefined (reading ‘createEl’)

Here is how I tried to troubleshoot and fix the issue you are facing:

  • installed and enabled the Buttons Obsidian plugin
  • Created this view in MY_VAULT/Atlas/Utilities/Scripts/test_buttons.js:
    const {createButton} = app.plugins.plugins["buttons"];
    
    dv.paragraph(
      createButton(
      	{ 	app, 
      		el: this, 
      		args: {
      			name: "+/-", 
      			type: "command", 
      			action: "Templater: Insert Atlas/Utilities/Templater/Appartement Template.md", 
      			class: "button-default button-inline"
      		}
      	}
      )
    );
    
  • Added this dataviewjs code block in a new Obsidian note
    ```dataviewjs
    dv.view("/Atlas/Utilities/Scripts/test_buttons")
    ```
    
  • Replaced el: this.container with el: this (Not sure if this could help)
  • Wrapped the button in a paragraph:
     dv.paragraph(
      createButton(...)
    )
    

I now get this:

1 Like

Thankyou, that worked!

I’m glad it helped.