What I’m trying to do
Hey guys. I am trying to create a dataview of my book notes in Obsidian (I tried using projects (plugin), and it works but it doesn’t look as good as I would like
Here’s the code I am using, in a dataviewjs box
function valueMatches(input, target) {
if (Array.isArray(input)) {
return input.map(String).includes(String(target));
}
return String(input) === String(target);
}
// Get books
const booksRead2025 = dv.pages().where(book =>
valueMatches(book.year, 2025) &&
valueMatches(book.status, "Read")
);
const booksTBR = dv.pages().where(book =>
valueMatches(book.status, "TBR")
);
const booksWishlist = dv.pages().where(book =>
valueMatches(book.status, "Wishlist")
);
const booksReadAll = dv.pages().where(book =>
valueMatches(book.status, "Read")
);
// Create wrapper
const wrapper = dv.el("div", "", {
style: "position: relative; text-align: center;"
});
// Button container
const buttonContainer = document.createElement("div");
buttonContainer.style.cssText = `
margin-bottom: 12px;
display: flex;
justify-content: center;
gap: 10px;
flex-wrap: wrap;
`;
const buttons = {
read2025: createButton("📚 2025 Books", "read2025"),
tbr: createButton("📝 TBR", "tbr"),
wishlist: createButton("📌 Wishlist", "wishlist"),
readAll: createButton("✅ Read (All)", "readAll")
};
Object.values(buttons).forEach(btn => buttonContainer.appendChild(btn));
wrapper.appendChild(buttonContainer);
// Function to create button
function createButton(label, section) {
const btn = document.createElement("button");
btn.innerText = label;
btn.style.cssText = `
padding: 6px 12px;
border-radius: 5px;
cursor: pointer;
font-weight: bold;
`;
btn.onclick = () => toggleSection(section);
return btn;
}
// Function to get year color
function getYearColor(year) {
const pastelColors = [
"#FFD1DC", "#C1E1C1", "#FFECB3", "#B3E5FC", "#D1C4E9",
"#F8BBD0", "#C8E6C9", "#FFF9C4", "#B2DFDB", "#E1BEE7"
];
const index = parseInt(year) % pastelColors.length;
return pastelColors[index];
}
// Function to render books
function renderBooks(books, showYear = false) {
return books.map(book => {
const yearBadge = showYear && book.year ? `
<div style="
background-color: ${getYearColor(book.year)};
display: inline-block;
padding: 2px 8px;
margin-top: 4px;
font-size: 0.75em;
border-radius: 12px;
color: #333;
">
${book.year}
</div>` : "";
return `
<div style="
display: inline-block;
width: 18%;
min-width: 140px;
max-width: 180px;
margin: 1%;
vertical-align: top;
text-align: center;
">
<a href="${book.file.path}">
<img src="${book.cover}" style="height: 150px; margin: 0 auto; display: block; border-radius: 6px;" />
</a>
<div style="font-weight: bold; margin-top: 6px;">
<a href="${book.file.path}" style="text-decoration: none; color: inherit;">
${book.file.name}
</a>
</div>
<div style="font-size: 0.85em;">${book.pages ?? ""}</div>
<div style="font-size: 0.85em; color: gray;">${book.author ?? ""}</div>
${yearBadge}
</div>
`;
}).join("");
}
// Content container
const container = document.createElement("div");
container.style.cssText = `
max-height: 0;
overflow: hidden;
transition: max-height 0.5s ease;
text-align: center;
width: 100%;
padding: 0 10px;
`;
wrapper.appendChild(container);
// Toggle logic
let currentExpanded = null;
function toggleSection(section) {
let books;
let showYear = false;
if (section === "read2025") books = booksRead2025;
else if (section === "tbr") books = booksTBR;
else if (section === "wishlist") books = booksWishlist;
else if (section === "readAll") {
books = booksReadAll;
showYear = true;
}
if (currentExpanded === section) {
container.style.maxHeight = "0";
currentExpanded = null;
} else {
container.innerHTML = renderBooks(books, showYear);
container.style.maxHeight = container.scrollHeight + "px";
currentExpanded = section;
}
}
It’s got the look that I want, but the titles of the books are no longer clickable. I’d like to be able to click on the title or cover and get to the note of that book. Can anyone help?
Things I have tried
I tinkered with dv.fileLink() a ton… Sometimes I got the links back but they were separated from the book card (see below) I also tried to create a property called notelink that contains a link to the specific note and display that under the author
#this code split the linked titles from the cards
function valueMatches(input, target) {
if (Array.isArray(input)) {
return input.map(String).includes(String(target));
}
return String(input) === String(target);
}
// Book queries
const booksRead2025 = dv.pages().where(book =>
valueMatches(book.year, 2025) && valueMatches(book.status, "Read")
);
const booksTBR = dv.pages().where(book =>
valueMatches(book.status, "TBR")
);
const booksWishlist = dv.pages().where(book =>
valueMatches(book.status, "Wishlist")
);
const booksReadAll = dv.pages().where(book =>
valueMatches(book.status, "Read")
);
// Main wrapper
const wrapper = dv.el("div", "", { style: "position: relative; text-align: center;" });
// Button row
const buttonContainer = document.createElement("div");
buttonContainer.style.cssText = `
margin-bottom: 12px;
display: flex;
justify-content: center;
gap: 10px;
flex-wrap: wrap;
`;
// Buttons
const buttons = [
{ label: "📚 2025 Books", id: "read2025" },
{ label: "📝 TBR", id: "tbr" },
{ label: "📌 Wishlist", id: "wishlist" },
{ label: "✅ Read (All)", id: "readAll" }
];
buttons.forEach(({ label, id }) => {
const btn = document.createElement("button");
btn.innerText = label;
btn.style.cssText = `
padding: 6px 12px;
border-radius: 5px;
cursor: pointer;
`;
btn.onclick = () => toggleSection(id);
buttonContainer.appendChild(btn);
});
wrapper.appendChild(buttonContainer);
// Year badge color
function getYearColor(year) {
const pastelColors = [
"#FFD1DC", "#C1E1C1", "#FFECB3", "#B3E5FC", "#D1C4E9",
"#F8BBD0", "#C8E6C9", "#FFF9C4", "#B2DFDB", "#E1BEE7"
];
const index = parseInt(year) % pastelColors.length;
return pastelColors[index];
}
// Main display area
const container = document.createElement("div");
container.style.cssText = `
max-height: 0;
overflow: hidden;
transition: max-height 0.5s ease;
text-align: center;
width: 100%;
padding: 0 10px;
`;
wrapper.appendChild(container);
// Render function (with dv.el() for the clickable title)
function renderBooks(books, showYear = false) {
container.innerHTML = ""; // clear existing content
books.forEach(book => {
const card = document.createElement("div");
card.style.cssText = `
display: inline-block;
width: 18%;
min-width: 140px;
max-width: 180px;
margin: 1%;
vertical-align: top;
text-align: center;
`;
const img = document.createElement("img");
img.src = book.cover;
img.style.cssText = "height: 150px; margin: 0 auto; display: block; border-radius: 6px;";
card.appendChild(img);
const title = document.createElement("div");
title.style.cssText = "font-weight: bold; margin-top: 6px;";
// Use Dataview to render the link properly
dv.el("span", book.file.link, {}, title);
card.appendChild(title);
if (book.pages) {
const pages = document.createElement("div");
pages.innerText = book.pages;
pages.style.fontSize = "0.85em";
card.appendChild(pages);
}
if (book.author) {
const author = document.createElement("div");
author.innerText = book.author;
author.style.cssText = "font-size: 0.85em; color: gray;";
card.appendChild(author);
}
if (showYear && book.year) {
const badge = document.createElement("div");
badge.innerText = book.year;
badge.style.cssText = `
background-color: ${getYearColor(book.year)};
display: inline-block;
padding: 2px 8px;
margin-top: 4px;
font-size: 0.75em;
border-radius: 12px;
color: #333;
`;
card.appendChild(badge);
}
container.appendChild(card);
});
container.style.maxHeight = container.scrollHeight + "px";
}
// Section toggle logic
let currentExpanded = null;
function toggleSection(section) {
let books;
let showYear = false;
if (section === "read2025") books = booksRead2025;
else if (section === "tbr") books = booksTBR;
else if (section === "wishlist") books = booksWishlist;
else if (section === "readAll") {
books = booksReadAll;
showYear = true;
}
if (currentExpanded === section) {
container.style.maxHeight = "0";
currentExpanded = null;
} else {
renderBooks(books, showYear);
currentExpanded = section;
}
}
This is it, if anyone could help it would be awesome. If anyoen has better code than this to do the same thing that’s cool too