DataviewJS Snippet Showcase

Hey @Moonbase59 ! Thanks a lot for sharing this code. It has been conducive to me.
I had to make a minor change to remove the Luxon dependency, which was breaking for me.
Also, as I use a different folder structure for my daily notes, I added some code to search the pages from the daily notes configured folder. The relevant lines of code I changed:

var folder = app['internalPlugins']['plugins']['daily-notes']['instance']['options']['folder'] || dv.current().file.folder
var p = dv.pages('"'+folder+'"').where(p => p.file.day).map(p => [p.file.name, p.file.day.toISODate()]).sort(p => p[1]);
var t = dv.current().file.day ? dv.current().file.day.toISODate() : dv.date("now").toFormat("yyyy-MM-dd");

Cheers,
Gilmar

Please keep posts in this thread for “share and showcase”, and ask questions in Help

Noted with thanks.

Wanted a drop down menu for my dashboard so I used chat gpt to create this query and a css snippet to style it. I’m sure the query could be simplified or there is a better way to do this but it works and links to the pages AND it’s all updated automatically.

function formatDate(dateString) {
  let date = new Date(dateString);
  let options = { year: "2-digit", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", hour12: true };
  return date.toLocaleString("en-US", options);
}

let folderTree = new Map();

function addToFolderTree(path, content) {
  let parts = path.split("/");
  let currentLevel = folderTree;
  for (let i = 0; i < parts.length; i++) {
    let part = parts[i];
    if (!currentLevel.has(part)) {
      currentLevel.set(part, { folders: new Map(), files: "" });
    }
    if (i === parts.length - 1) {
      currentLevel.get(part).files = content;
    } else {
      currentLevel = currentLevel.get(part).folders;
    }
  }
}

for (let group of dv.pages('"----01. PROJECTS"').groupBy(p => p.file.folder)) {
  let folderName = group.key;
  let rows = group.rows
    .map(
      k => `| [${k.file.name.replace(/\|/g, "\\|")}](/${k.file.path.replace(/ /g, "%20")}) | ${formatDate(k.file.ctime)} | ${formatDate(k.file.mtime)} |`
    )
    .join("\n");

  let tableContent = `
| Name | Created | Modified |
| ---- | ------- | -------- |
${rows}
`;

  addToFolderTree(folderName, tableContent);
}

function renderFolderTree(folderMap, level = 0) {
  let content = "";
  for (let [folder, data] of folderMap.entries()) {
    let subcontent = data.folders.size > 0 ? renderFolderTree(data.folders, level + 1) : "";
    let folderContent = data.files ? data.files : "";
    if (level > 0) {
      content += `### ${folder}\n<details><summary>Click to expand</summary>\n\n${subcontent}${folderContent}\n\n</details>\n`;
    } else {
      content += `${subcontent}${folderContent}`;
    }
  }
  return content;
}

dv.header(6, renderFolderTree(folderTree));

1 Like

Hello!

There’s an error somewhere in this piece of code:

let pages = dv.pages("#Q_A").where(p => p.file.outlinks = "[[consultation]]")

or

let pages = dv.pages("#Q_A").where(p => p.file.outlinks.includes("[[consultation]]")

I don’t understand how to write it correctly to make it work.

Whole code:

```dataviewjs
let pages = dv.pages("#Q_A").where(p => p.file.outlinks === "[[консультация]]")


for (let group of pages.groupBy(p => p.status)) {
    dv.header(3, group.key);
    dv.table(["Column1", "Column2"],
        group.rows  
          .map(k => [k.file.link, k["created"]]
))
}

Help me, please.

Thank you so much!


Updated: In discord helped River :+1::

dv.pages("[[consultation]]")
  .where(p => p.file.tags.includes("#Q_A"))
1 Like

SEARCH FOR AND DISPLAY LIST OF CALLOUTS (WITH LINKS TO NOTES) IN A SPECIFIED FOLDER WITH A SPECIFIED TAG:

I scrolled through a fair amount of this thread looking for a way to list ‘todo’ callouts with a link, but only in a specific folder with a specified tag. I could not find it, so I am posting this for posterity’s sake. Credit to this question thread which allowed me to form a base to build this.

```dataviewjs
// Replace TAG with whatever tag you wish.
const pages = dv.pages('#TAG')

//Use the regular expression to define what kind of Callout you want to check
//Replace !todo with whatever callout type you wish.
const regex = />\s\[\!todo\]\s(.+?)((\n>\s.*?)*)\n/

//check for more types of callouts by adding new const regex# where # is a number
const regex2 = />\s\[\!TODO\]\s(.+?)((\n>\s.*?)*)\n/

const rows = []
for (const page of pages) {
    const file = app.vault.getAbstractFileByPath(page.file.path);
    //get file path as string
    var checkMe = "" + page.file.path;
    // Read the file contents
    const contents = await app.vault.read(file)
    // Extract the summary via regex
	for (const callout of contents.match(new RegExp(regex, 'sg')) || []) {

        //check if the file path is in your designated file path.
        //change FILEPATH to any keyword, folder name, or file path
        //You can also change to exclude a file path by changing "==true" to "!=true"
	    if(checkMe.includes('FILEPATH') == true){
		    const match = callout.match(new RegExp(regex, 's')) 
		    rows.push([match[1], page.file.link])
		}
    }

     //duplicate the for loop for each const regex# you created
     //adjust regex to the appropriate name
    for (const callout of contents.match(new RegExp(regex2, 'sg')) || []) {
	    if(checkMe.includes('FILEPATH') == true){
		    const match = callout.match(new RegExp(regex2, 's')) 
		    rows.push([match[1], page.file.link])
		}
    }
}

dv.table(['Term', 'Link'], rows)
```

I am still playing with this, and if I update it, I will edit this post or reply to it.
Hope this helps someone.

1 Like

SEARCH FOR AND DISPLAY LIST OF CALLOUTS (WITH LINKS TO NOTES) IN A SPECIFIED FOLDER WITH A SPECIFIED TAG (UPDATE):
I went and tried to edit the post I made to include the regex Array, but I was unable to do so.

```dataviewjs
// Replace TAG with whatever tag you wish.
const pages = dv.pages('#TAG')

//Use the regular expression to define what kind of Callout you want to check
//Replace !todo with whatever callout type you wish.
//check for more types of callouts by adding new entries in the array.
//Remember: every key in the Array has to have a comma at the end, except for the last one
const regex = {
	0: />\s\[\!todo\]\s(.+?)((\n>\s.*?)*)\n/,
	1: />\s\[\!TODO\]\s(.+?)((\n>\s.*?)*)\n/
}
const rows = []
for (const page of pages) {
    const file = app.vault.getAbstractFileByPath(page.file.path);
    //get file path as string
    var checkMe = "" + page.file.path;
    // Read the file contents
    const contents = await app.vault.read(file)
    //cycle through the callout type array. 
    //Be sure to change the 'i<2' to be one more than your highest array key.
    for(var i = 0; i < 2; i++){
        // Extract the summary via regex
		for (const callout of contents.match(new RegExp(regex[i], 'sg')) || []) {
            //check if the file path is in your designated file path.
            //change FILEPATH to any keyword, folder name, or file path
            //You can also change to exclude a file path by changing "==true" to "!=true"
		    if(checkMe.includes('FILEPATH') == true){
			    const match = callout.match(new RegExp(regex[i], 's')) 
			    rows.push([match[1], page.file.link])
			}
	    }
	}
}

dv.table(['Term', 'Link'], rows);
```

How do you use this?

This is great and I would love to implement this, however I can’t get it to work.

In the People note I have added this frontmatter:
tags: [family, friends]
birthday: 2021-08-17

In the Birthdays note I have this frontmatter:
#searchterm:#family or #friends
searchterm: ‘“People”’
duration: 1 year
dateformat: “YYYY-MM-DD”

In this note I have placed the dataviewjs code.
It displays a Dataview query with no results.

Any help would be greatly appreciated.

Not the OP, but the following works in a local diagnostic (see screenshot below).

Person File:

---
birthday: 2021-08-17
tags: family
---

Birthdays File

---
searchterm: '#family'
duration: 1 year
---

```dataviewjs
var start = moment().startOf('day');
var end = moment(start).add(dv.current().duration);
var dateformat = "YYYY-MM-DD";
if (dv.current().dateformat) { dateformat = dv.current().dateformat; }

// info text above table, {0}=duration, {1}=start date, {2}=end date
// parameters can be left out, or the string empty
var infotext = "Upcoming birthdays for {0} from now ({1} – {2})<br><br>";

//=============

function nextBirthday(birthday) {
    // Get person’s next birthday on or after "start"
    // returns a moment
    
    // need to "unparse" because DV has already converted YAML birthday to DateTime object
    // shouldn’t harm if already a string
    var bday = moment(birthday.toString());
    var bdayNext = moment(bday).year(start.year());
    if (bdayNext.isBefore(start, 'day')) {
        bdayNext.add(1, "year");
    }
    return bdayNext;
}

function turns(birthday) {
    // Get the age in years a person will turn to on their next birthday

    // need to "unparse" because DV has already converted YAML birthday to DateTime object
    // shouldn’t harm if already a string
    var bday = moment(birthday.toString());
    return nextBirthday(birthday).diff(bday, 'years');
}

function showBirthday(birthday) {
    // Determine if this birthday is in the range to be shown
    // including the start date, excluding the end date
    // because that comes from a duration calculation
    // for use with "where", returns true or false
    
    if (birthday) {
        // need to "unparse" because DV has already converted YAML birthday to DateTime object
        // shouldn’t harm if already a string
        var bday = moment(birthday.toString());
        var bdayNext = nextBirthday(birthday);
        if (bdayNext.isBetween(start, end, 'day', '[)')) {
            return true;
        } else {
            return false;
        }
    } else {
        return false;
    }
}

function sortByNextBirthday(a, b) {
    // comparator function for "sort"
    
  if (nextBirthday(a).isBefore(nextBirthday(b))) {
    return -1;
  }
  if (nextBirthday(a).isAfter(nextBirthday(b))) {
    return 1;
  }
  // they’re equal
  return 0;
}


//======================================================================

dv.paragraph(infotext.format(moment.duration(dv.current().duration.toString()).humanize(), start.format(dateformat), end.format(dateformat)));

dv.table(
    ["Name", "Birthday", "Turns"],
    dv.pages(dv.current().searchterm)
        // use a function to see if this birthday is in range to be shown
        .where(p => showBirthday(p.birthday))
        // use a comparator function to sort by next birthday
        .sort(p => p.birthday, 'asc', sortByNextBirthday)
        .map(p => [
            p.file.link,
            p.birthday ? nextBirthday(p.birthday).format(dateformat) : '–',
            turns(p.birthday)
        ])
);
```

2 Likes

Thank you so much! I must have misunderstood how to add the frontmatter to the person’s note. How can I query note’s tagged family or friend?

Any idea how to query friends or family with birthday month and day same as today? Would like to show today’s birthdays in a daily note.

how can you change the duration?

Hi Sam,

thanks for your work which works fine on my desktop device. Unfortunately it’s not working on my mobile (iOS) device. I added the same script folder as on my desktop but I get the following error:

Evaluation Error: eval
code@
eval@[native code]
@
...

I created the script file “journal.js”:

class Journal {
    add_life_progress_tracker(){
    }
}

And I execute the code in a dataviewjs block:

const {Journal} = customJS
Journal.add_life_progress_tracker()

Am I doing something wrong? For me it seems that the iOS device can’t find the script but it seems to be stored in my local Obsidian Vault.

Thanks
Dennis

I found the solution. By default Obsidian does not sync all file types within your Vault. You need to explicitly allow the synchronization of unknown files in the sync settings on all devices. After a restart of Obsidian everything works fine.
Maybe you could add this as hint to the customJS plugin description.

Hey, folks. I’m sure I’m missing something obvious: I’m working with Metadata Menu and in a lookup field, I want to query only the files that link to the page the field is on. How would I limit them in dv.pages() ? Thank you!

@Thecookiemomma , this is a thread mainly for showcasing solutions, not asking for help. Please repost your question in Help .

I have a code like this:

dv.header(2, 'Project');

dv.taskList(dv.pages().file.tasks 
  .where(t => !t.completed)
  .where(t => t.text.includes("[[Project]]")), false )

with this result:
10598_Obsidian_NvuTzksz

Could you please tell me how to remove Project from each task?
I want it to show without project, like this:
10599_Obsidian_38NtfC7L

THANK YOU VERY MUCH!

Please post new questions in Help , @friendlmickle , not as a showcase example…

2 Likes


image
I’ve fixed embeds. both dv.sectionLink as well as dv.fileLink work now after this pull is merged:
Fix: markdown render api change due to deprecation by GottZ · Pull Request #2266 · blacksmithgu/obsidian-dataview (github.com)

![[file]] will be fixed then.

1 Like

Does anyone have an example of creating a normal HTML table with dataviewjs? Preferably not using literal HTML tag strings? I’m having trouble getting createElement working?