DataviewJS Filtered Table with Checkboxes to Toggle Tags

What I’m trying to do

I’m slowly going through old notes, items imported from TiddlyWiki and so on. I have many notes with no tags. There are certain tags which many notes need, but it’s awkward to find the right notes and apply the tag. What I want:

  • Table filtered to show notes without tags A and/or B
  • For each note, have buttons or checkboxes which would set tags A and B.

Then I could go through the table and set the tags as necessary.

Things I have tried

I’ve found various posts in the forum about Dataview, DataviewJS, Buttons (depreciated I believe), MetaEdit, Meta Bind, and Metadata Menu. The Snippet Showcase has been useful but I’ll be honest and I got lost with all the options.

I have the basics of a DataviewJS table with a filter for a tag:

dv.table(["Note",], 
	dv.pages("#TagA")
    .map(t => [
		t.file.link,
		t.file.tags,
    ]))

What I can’t figure out is this:

  • How to make the filter find all items with “NOT tag A, OR NOT tag B, OR no tags at all”
  • How to add a button or checkbox to set or unset Tag A or Tag B

The table would look like this:

File Tags Toggle Tag A Toggle Tag B
Link to file A
Link to file B, C
Link to file C
Link to file (no tags)

Grateful for any suggestions!

Your example kind of shows every file available. So which file should it show or shouldn’t it show?

Which of the following questions can you answer yes to?

  1. Show those with #TagA ?
  2. Show those with #TagB ?
  3. Show files without any tags?
  4. Show files without both #TagA and #TagB ?
  5. Show files with tags, but neither #TagA nor #TagB?

Hi holroy, glad you’re on the case!

  1. Show those with #TagA ? Yes
  2. Show those with #TagB ? Yes
  3. Show files without any tags? Yes
  4. Show files without both #TagA and #TagB ? Yes
  5. Show files with tags, but neither #TagA nor #TagB? Yes

I see what you mean about returning ALL notes. The first question mainly about finding notes with no tags, plus some other conditions. Once I have that working, I can change them around and find what I need. The “no tags” is tricky.

The second part is more important. I really want to be able to have this table:

Note tagged #location tagged #PlacesIveBeen Field “locationType” set to “Country”
Note1
Note1
Note1
Note1

The checkboxes would toggle the tag, and also set the ‘locationType’

I’ve totally misread your request as a request on how to filter out a file based on those tags. I’ve not read, what I realised earlier today, that you want to trigger the change of these values from your table.

In an ideal world you could use a plugin like meta bind (or possibly metadata menu) to trigger such changes, but after a very quick look through I don’t see any of them capable of adding/removing single tags from a tag list. That means you’d need to build the table from dataviewjs and add onclick methods to handle this yourself.

The handling would then need to call api.fileManager.processFrontMatter() to change the tags and potentially the change of locationType, before lastly it should issue a rebuild of the current view to make it show the now updated version.

In other words, this require a bit of coding, and I’m having issues with my test vault, and running out of time. Are you capable of doing such coding on your own, or do you need help with that? I now there are some examples in this forum on how to use processFrontMatter, and there are examples on how to connect buttons to a dataview table, but I’m unsure if there are any example of combining these two.

(And I could be mistaken that either of the plugins mentioned (or some other plugin) aren’t capable of doing this somehow)

Hi,
Appreciate your response. I did see the possibility of using Meta Bind and Metadata Menu. I got the feeling they could possibly do it.
Doing it with dataviewjs seems to be the way. I will have a go at it myself, and possibly (probably?) report back.

I’ve played around with this, and I’m not sure if this is the cleanest solution, but it seems to be a solution! :smiley:

Some premises/disclaimers:

  • Handling all the various variations of how a tag can be defined, can cause the script to fail, but I’ve tried to cover the main bases
  • For the locationType handling, I’ve opted to not do anything when you untick the value, but it do try to set it to Country when you tick it
  • I’ve tried making the functions kind of generic to be able to either toggle a tag using the buildTagToggle(file, tagName) function, or checking for (and setting to) a fixed value using the isFixedValue(page, fieldKey, fieldValue)
  • I’m not sure what happens if you mess with the current file. Haven’t tested, and don’t want to test that variant! :smiley:
  • In my tests the page updates itself due to auto-refreshing, but if that’s not the case for you you might need to trigger a rebuilding of the current view (or reopen the file)

OK, so here is the script:

```dataviewjs
const cur = dv.current()

function buildTagToggle(file, tag) {
  const button = dv.el('input', '', {attr: { centered: "", type: "checkbox" }})
  if ( file.etags.includes("#" + tag) ) {
    button.checked = "checked"
  }

   
  button.onclick = (e) => {
    // console.log(dv)
    // console.log("file: ", file)
    // console.log("e: ", e.target.checked, " vs ", file.etags.includes("#" + tag))
    // console.log("app:", app)
    
    const tFile = app.vault.getAbstractFileByPath(file.path);
    app.fileManager.processFrontMatter(tFile, fm => {
      // Build a new tag list, based on the variants of tags
      let tagList = []
      
      if (!fm.hasOwnProperty("tags")) {
        // console.log("no tags")
        ; 
      } else if (typeof(fm.tags) == "string") {
        if (fm.tags.includes(",")) {
          tagList.push(...fm.tags.split(/\s*,\s*/))
        } else {
          tagList.push(fm.tags)
        }
      } else if (typeof(fm.tags) == "object") {
        tagList.push(...fm.tags)
      }
      console.log("fixed: ", tagList)
      if (e.target.checked) {
        // Add tag
        if (!tagList.includes(tag)) {
          // console.log("To be added:", tag)
          tagList.push(tag)
          fm.tags = tagList
        }    
      } else {
        // Remove tag
        if (tagList.includes(tag)) {
          console.log("To be removed:", tag)
          const index = tagList.indexOf(tag)
          if (index > -1) {
            tagList.splice(index, 1)
            fm.tags = tagList
          }
        }
      }
    })
  }
  
  return button
}

function isFixedValue(page, fieldKey, fieldValue) {
  const button = dv.el('input', '', {attr: { centered: "", type: "checkbox" }})
  console.log(fieldKey, page[fieldKey])
  
  // Set to checked if it matches the fixed value
  if ( page.hasOwnProperty(fieldKey) && 
       page[fieldKey] &&
       page[fieldKey].includes(fieldValue) ) {
    button.checked = "checked"
  }
  // Create the function to set to fixed value, if needed
  button.onclick = (e) => {
    if (!e.target.checked) 
      return; // Don't do anything when check mark is cleared...

    // Set to the fixedValue
    const tFile = app.vault.getAbstractFileByPath(page.file.path);
    app.fileManager.processFrontMatter(tFile, fm => {
      fm.locationType = fieldValue
    })
  }
  return button
}

const result = dv.pages('"ForumStuff/f76/f76942"')
  .where(p => p.file.name.startsWith("no"))
  .map(p => [
    p.file.link, 
    buildTagToggle(p.file, "location"),
    buildTagToggle(p.file, "PlacesIveBeen"),
    isFixedValue(p, "locationType", "Country")
 
  ])

dv.table(["Note",  "#location?", "#PlacesIveBeen?", "locationType=Country?"], result)
```

The gist of the script is to first define the two functions which builds an input element of type checkbox with an initial value according to its tag/field value. This function also holds an onclick method which is the brains of the matter, capable of triggering a call to app.fileManager.processFrontMatter(). Both functions should only trigger a file write if there is something changing.

After the two helper function we simply build a table, and use the functions to insert the checkboxes at appropriate places, before we just display the table using dv.table(). It’s the area around the const result = dv.pages(...).where(...) you need to adapt to your own vault. As it stands I’m limiting it to my test vault folder, and to files starting with “no”.

With the addition of the following CSS in a snippet:

td:has([centered]) {
    text-align: center !important;
    /* background-color: green; /* */
}

This now displays as:

And I’m able to tick or untick the various boxes to either add/remove the tag in question, or to set locationType = Country if it’s ticked. Other tags are preserved as part of the tag editing process.

Have fun, go mad… At least I felt like going mad at times due to strange stuff happening in my test vault these last few days. Not related to this post, but more general issues occurring…

This is great, it does just what I need. I had to expand the columns, and be more specific with the filter to do things in chunks, otherwise its slow to respond.
Thank you very much!

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