Filter suggester by file metadata

I’ve got a ‘suggester’ that pulls in a list of ‘projects’ from a folder called ‘Projects’

let qcProject = (await tp.system.suggester((item) => item.basename, app.vault.getMarkdownFiles().filter(file => file.path.startsWith("Projects")), false, "Please select Project...")).basename

This works great, but now I want to only include files that are not marked as ‘archive’ in their metadata. The metadata for an archived project is:

archive: x

How can I add this to the suggester?

1 Like

This is very strange as I have the exact same issue…
I was looking here, and they had the following code to filter for files that contain a tag:

const tag = "#obsidian";
const cache = this.app.metadataCache;
const files = this.app.vault.getMarkdownFiles();
const files_with_tag = [] as TFile [];
files.forEach( (file) => {
    const file_cache = cache.getFileCache(file);
    const tags = getAllTags(file_cache);
    if (tags.includes(tag)) {
        files_with_tag.push(file);
    }
});
console.log(files_with_tag);

Something tells me that the solution is to be found in the metadataCache. I’ll have a play around and let you know if I find anything useful!

My friend, I have a solution! It may not be perfect, as I have no experience with javascript, but it works. This is the solution for my problem, where I’m trying to have a suggester for all my projects, in this case that’s all files with the tag #projects:

<%*
const dv = this.app.plugins.plugins["dataview"].api;
let projects = dv.pages("#projects").file.sort(n => n.name);
let suggestions = projects.name;
let values = projects.link;
await tp.system.suggester(suggestions,values);
%>

In your case, your projects are logically separated from other types by being in a certain directory, and you want to exclude files with certain metadata from your search results.

So I imagine your code would look something like this:

<%*
const dv = this.app.plugins.plugins["dataview"].api;
let all_projects = dv.pages('"Projects"');
let active_projects = all_projects.where(p => p.archive != "x").file.sort(n => n.name);
let suggestions = active_projects.name;
let values = active_projects.link;
await tp.system.suggester(suggestions,values);
%>
  1. Load in the dataview api (presumably you are using dataview if you have this archive metadata?)
  2. Grab all projects by specifying your project directory. Note: according to the documentation, “folders need to be double-quoted inside the string (i.e., dv.pages("folder") does not work, but dv.pages('"folder"') does) - this is to exactly match how sources are written in the query language.”
  3. Remove those projects that are marked as being archived by filtering all projects and returning those that don’t have archive: x. Finally, sort them alphabetically for easier navigation.
  4. For my use case, I needed to just the name for the suggestions, and the links as the values. You may well just want the names for both, plus your prompt and the other parameters for tp.system.suggester.

I hope that solved your problem!

You, my friend, are amazing!! That solution is not only excellent for my needs, but your explanation has helped me to start going further with this; adding multiple ‘where’ statements, etc.

Amazing!!!

1 Like

I’m super glad! It was uncanny, usually I google an issue and find a stackoverflow post from years ago. This time I found one from 8 hours ago, so felt a responsibility to assist!

@epabarker
I’ve tried the first solution you posted, and I’m having no luck passing variables into the tp.system.suggester function and having it write to the newly created file from the template. I can get the project list to display in the suggester dialog which appears after calling the template (in other words the function accepts suggestions), but after making a selection, no value is written into the file.

Any thoughts?

Hey @chandlergibbs, did you copy the code basically as is? And replace with a few of your own use-specific variables?

It could be a few things, it would be great to see your code for it! Maybe just the code you are calling, where you are calling it from, and the file you try to make from it.

I’ve tried several ways in an attempt to debug: using an exact copy of the code you posted and creating a few sample “project” files that are tagged with #projects; and using my own edited version of the code with the tag #project_moc

Here’s the code for the template file that I’m calling:

---
meeting_type: <% tp.system.suggester(["Call", "Site Visit", "Design Notes"], ["Call", "Site Visit", "Design Notes"]) %>
venue: <% tp.system.suggester(["Virtual", "On Site", "Office"], ["Virtual", "On Site", "Office"]) %>
meeting_date: <% tp.date.now("YYYY-MM-DD") %>
project_number: <%* const dv = this.app.plugins.plugins["dataview"].api; let projects = dv.pages("#project_moc"); let names = projects.project_name; let numbers = projects.project_number; await tp.system.suggester(names, numbers, false, "Please select the project"); %>
---

Project:
Ref:
Attendees:

# Summary

# Notes
- <% tp.file.cursor() %>

And here’s a sample of a #project_moc file with the information removed.

---
tags: project_moc
project_number: 00000
project_name: "Test Project Name"
---

# Notes

A few things to note:

  1. I’ve tried moving the relevant code out of the YAML frontmatter and placing it in the body of the note (because I read on another post that there may be differences in the types of code which can properly execute in YAML), but it still didn’t work for me there.
  2. I’ve tried using .projects.project_name.values and projects.project_numbers.values for the variables names and numbers, respectively, in an attempt to get the types of the variables to resemble the example code from the templater documentation, but this hasn’t worked either.
  3. I’ve also tried hard-coding let numbers = ["00000', "00000"] and ensuring that the length of numbers matches the length of names, but I still get no result.

For reference, when I run the code shown above, I do get values for the YAML values meeting_type, meeting_venue, and meeting_date, but nothing for project_number.

Okay @chandlergibbs, I’ve got to the bottom of it. I had a similar issue when I used this code in a new context.

Ultimately, in order to do what we wanted in this case, we needed to use javascript, which necessitated the use of the JavaScript Execution Command of templater.

Templater has three command types, and explains on the documentation here that:
Templater defines 3 types of opening tags, that defines 3 types of commands:

  • <%: Raw display command. It will just output the expression that’s inside.
  • <%~: Interpolation command. Same as the raw display tag, but adds some character escaping.
  • <%*: JavaScript execution command. It will execute the JavaScript code that’s inside. It does not output anything by default.

The closing tag for a command is always the same: %>

Note for JavaScript execution command “it does not output anything by default”.

There are two ways to fix this:

  1. Declare all the variables you need in a JS execution command, and then call tp.system.suggester later on with said variables in a raw display command.
  2. Make use of the tR variable to print output, for which more information is provided here

I happened to see that very line you noted about not outputting anything by default a few minutes after I posted the last comment and wondered if that might be the issue I was running into.

I got it to work by putting this javascript execution code at the top of the YAML

<%* const dv = this.app.plugins.plugins["dataview"].api; 
let projects = dv.pages("#project_moc"); 
let names = projects.project_name; 
let numbers = projects.project_number; 
-%>

Then calling

<% await tp.system.suggester(names, numbers, false, "Please select the project") %>

in the respective YAML location.

Thanks for your help!

I’m very new to javascript and coding in general, so this was a trial/error process. Out of curiosity, is there any substantive difference in using this method vs using the tR variable method?

2 Likes

Glad you solved it!

I also noticed in your code you only had one await. My understanding is that the execution of the templater commands can be asynchronous, so you may not always get the suggesters in the order you might expect.

If you pop await in front of all of them then you’ll get the order you want :slight_smile:

As for the differences between using tR and using a display command, I’m not entirely sure what the differences are, except that tR contains the replacement string for the whole file. I imagine with asynchronous commands, and the ability to alter the whole string, one might find it easier to mess up the template and positioning when using tR instead of an explicit display command

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