DataviewJS Snippet Showcase


I’m looking to automatically generate the location: [lon, lat] in my YAML frontmatter for Daily Notes. Ideally with a link to Google. How could I accomplish this?

The Map View plugin might fit the bill? obsidian://show-plugin?id=obsidian-map-view

Looks like:
.sort(v => v[1].id, ‘asc’); // Sorted by the command name

should be:

.sort(v => v[1].name, ‘asc’); // Sorted by the command name

“name” not “id”


Yeah, that seems correct. I’m thinking about redoing that party of script, to also enable easier sorting and detection of duplicates.

But good catch!

Great snip! Would it be possible to split the Plugin Name into its own column? I see the Plugin Name is in the Name column of your code, but I would like them separate for sorting and data-extraction purposes.

Hey everyone,

I’m trying to get a hierarchical folder and file view of my Template folder using this

let title = "Dateien";
let dir = 'Templates';

let processed = [];

function listRecursive(folder, depth) {
	let files = [];
	// All pages in the scope of the current path
	let pages = dv.pages('"' + folder + '"')
	// Collect files in the current folder here
	let currentFiles = "";
	pages.forEach(page => {
		if (page.file.folder === folder) {
			// Page is in current folder
			currentFiles += + " <br> ";
		else {
			// Page is in subfolder
			let nestedFolder = page.file.folder;
			// Make sure nested folder is direct child, not any other descendant from current folder
			let isChild = folder.split('/').length + 1 == nestedFolder.split('/').length;
			// Make sure we dont process sub-directories multiple times
			if (!processed.includes(nestedFolder) && isChild) {
				// Result of recursive call is a list, by adding it to the current list we recursively build a tree
				files.push(listRecursive(nestedFolder, depth +1));
	if (currentFiles.endsWith(" | "))
		currentFiles = currentFiles.slice(0, -3);
	// Add files in current folder at the start
	if (currentFiles !== "") files.unshift(currentFiles);
	// Add current folder name at the start
	let path = folder.split('/');
	path = path [path.length - 1];
	if (depth == 0) path = path;
	files.unshift("<h3>" + path + "</h3>");
	return files;

let files = listRecursive(dir, 0);

dv.header(2, title);

I’ve minimally adapted it from here

Even in the original thread the writer jonasfranz says that it could use with some punch up to make it prettier.

I’m trying to remove the random bullet points the script inserts and am trying to get it scan the whole subfolder and not just the level one subfolders. Here are two screenshots of the problem:

I hope someone can help me :slight_smile: Oh and I’ve already got a css installed which shows less bullet points. So this is how it looks without that CSS if it helps:

As long as you’re using dv.pages to get the page, why don’t you use that to also group and sort your entries?

You’re kind of going around yourself with the current code, and doing it recursively when you (can) get all information on that start query.

I’m in a rather busy week just now, so I can’t type out an alternative solution, but I do believe there is room for improvements.

I’ll try to patch something up or research something along those lines. I’m a non-coder with a limited amount of knowledge so thank you for your input =)

Found a solution using this snippet, for anyone who is also interested in listing all “Template” files, this script increments the headers per subfolder and puts the “Templates” folder on the top. This was writtten using ChatGPT, which also annotated the script. Anyway, hope it helps:

let pages = dv.pages('"Templates"');

// Create a new array to store the sorted groups
let sortedGroups = [];

// Set the initial header level to 3
let headerLevel = 3;

// Iterate through the groups, and push the "Templates" group first
for (let group of pages.groupBy(b => b.file.folder.replace(/^Templates\//, ""))) {
    if (group.key === 'Templates') {
    } else {

for (let group of sortedGroups) {
    // Split the group key by '/' to check the number of subfolders
    let subfolders = group.key.split('/');
    // Increase the header level for each subfolder
    headerLevel += subfolders.length - 1;
    dv.header(headerLevel, group.key);
    // Reset the header level for the next group
    headerLevel = 3;
1 Like

Thank you so much for this.

I only wanted to see the list of hotkeys which I have modified. So I made a small change to get the desired output:

const getNestedObject = (nestedObj, pathArr) => {
    return pathArr.reduce((obj, key) =>
        (obj && obj[key] !== 'undefined') ? obj[key] : undefined, nestedObj);

function hilite(keys, how) {

	if(how != "**") return "x"
    // need to check if existing key combo is overridden by undefining it
    if (keys && keys[1][0] !== undefined) {
        return how + keys.flat(2).join('+').replace('Mod', 'Ctrl') + how;
    } else {
        return '–';

function getHotkey(arr, highlight=true) {
    if(!highlight) return; // THIS IS THE CHANGE
    let hi = highlight ? '**' : '';
    let defkeys = arr.hotkeys ? [[getNestedObject(arr.hotkeys, [0, 'modifiers'])],
    [getNestedObject(arr.hotkeys, [0, 'key'])]] : undefined;
    let ck = app.hotkeyManager.customKeys[];
    var hotkeys = ck ? [[getNestedObject(ck, [0, 'modifiers'])], [getNestedObject(ck, [0, 'key'])]] : undefined;
    return hotkeys ? hilite(hotkeys, hi) : hilite(defkeys, '');

let cmds = dv.array(Object.entries(app.commands.commands))
    .where(v => getHotkey(v[1]) != 'x')
    .sort(v => v[1].id, 'asc')
    .sort(v => getHotkey(v[1], false), 'asc');

dv.paragraph(cmds.length + " commands with assigned hotkeys; " +
    "non-default hotkeys <strong>bolded</strong>.<br><br>");

dv.table(["Command ID", "Name in current locale", "Hotkeys"], => {
            return [

Flattened List of tasks

Below code returns a flattened list of all tasks based on the filter criteria mentioned in the .where clause.

The returned task list doesn’t have any hierarchy other than the file that they are from.

	.where(t => !t.completed)
	.where(t => t.text.includes("2023-Jan-24"))
	.map(t => {
		t.children = []; 
		t.parent = 0;
		return t

Example Screenshot below:

1 Like

What is the query you wrote into chatGPT?

Hi folks, wondering if anyone can help with the following. Apologies if this is not the correct place to post. The aim is to reproduce the following snippet which lets you use full-calendar and dataviewjs to render the full calendar within a note with events from local calendar notes: Render main calendar in-line with a note with Dataview · Issue #79 · davish/obsidian-full-calendar · GitHub

The issue I have is with the following line:

let rawData = await dv.pages('"event-notes"').where(p => ="{{date:YYYY-MM-DD}}"));

Note that my event notes are stored in the folder event-notes in root. With the above I get no events populated. if I delete the filter where... in the above line my events do populate but all of them are rendered on a single day (i.e. today). All my event-notes include the date in the format represented by the filter in the metadata so not sure why it is not picking this up.

I have asked in the github discussion but not yet recieved a response. Again, thanks for any help.

I believe you need to use == for comparisons in js, so it would be ==

1 Like

I was wondering with DataviewJS could we take things one step further than this example:

   dv.header(2, 'Research Tasks');
    .where(t => !t.completed));

That will comb through pages with the tag “#research” and list out any incomplete tasks.

How could we extend this? What I’m trying to do is I have 3 different pages with 10 incomplete tasks among them. But what I want this to do is just grab the FIRST incomplete task from each of the 3 different pages. Basically listing for me the NEXT STEP for each #research page. I’m sure this is very close and just needs that last little bit of Javascript magic to make it work.

Thanks in advance for any help provided!

tasks have a line attribute, which you can sort by and limit in order to isolate. for multiple pages you might need to test the task line number against the smallest task line number of that page, if that makes sense.

It does not make sense, yet. But it definitely gives me some things I can start searching about. So thank you for the clues to follow up on!

So line attribute like the line of the page where the task is located? That makes sense.

So is there a way to loop over these tasks and know their page and line? I would want the lowest line for each page for the incomplete tasks, I think.

Would that be an altered .where() or would I have to go back to .file and look for the lines? I guess I’m looking for the collection to loop (or foreach) over and then figure out how to define the criteria or filter to apply to each iteration.

Seems to me. :slight_smile:

this sort of does the job:

TABLE file.tasks[0].text as task
FROM #your_tag
WHERE file.tasks

replace the from tag with whatever tag unifies your notes, or use some other source/add your filtering to the where block. Limit is just there in case of a mistake to prevent it blowing up, can be deleted.

One thing that occurs to me is it wouldn’t filter out completed tasks. js is probably required for a more complete solution. I might try later if I get some time.

as usual, the problem bothers me until I can solve it.

// grab first non-completed task from #test pages
let ftasks = dv.pages("#test").map(t => t.file.tasks.where(t => t.completed==false).sort(t.line).first() ) 

// filter undefined
let first_tasks = ftasks.filter(item => item !== undefined)

// render result
1 Like

Hey! That looks like a winner!