Created
November 5, 2023 17:21
-
-
Save acheong08/0c5cfe8276c1fba072c2556bdf4ffe60 to your computer and use it in GitHub Desktop.
HTMX but with JSON
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class HTMJ { | |
constructor() { | |
this.init(); | |
} | |
init() { | |
document.addEventListener("DOMContentLoaded", () => { | |
this.parseTemplates(); | |
}); | |
} | |
parseTemplates() { | |
const templates = document.querySelectorAll("template[hx-endpoint]"); | |
templates.forEach((template) => { | |
const endpoint = template.getAttribute("hx-endpoint"); | |
const dataSources = template.getAttribute("hx-data-sources"); | |
let method = template.getAttribute("hx-method"); | |
if (!method) { | |
method = dataSources ? "POST" : "GET"; | |
} | |
const event = template.getAttribute("hx-event") || "onload"; | |
const errorFuncName = template.getAttribute("hx-error"); | |
let target = template.getAttribute("hx-target"); | |
target = target ? document.querySelector(target) : template.parentNode; | |
// Check for a different event target | |
let eventTargetSelector = template.getAttribute("hx-event-target"); | |
const eventTarget = eventTargetSelector | |
? document.querySelector(eventTargetSelector) | |
: target; | |
const fetchDataAndRender = async (e) => { | |
if (e) e.preventDefault(); // Prevent default form submission | |
const data = await this.fetchData(endpoint, method, dataSources); | |
if (data.error && errorFuncName) { | |
window[errorFuncName]?.(data.error); | |
} else if (data) { | |
this.renderTemplate(template, data, target); | |
} | |
}; | |
if (event === "onload") { | |
fetchDataAndRender(); | |
} else if (event === "onclick") { | |
eventTarget.addEventListener("click", fetchDataAndRender); | |
} else if (event === "onsubmit") { | |
eventTarget.addEventListener("submit", fetchDataAndRender); | |
} | |
}); | |
} | |
async fetchData(endpoint, method, dataSources) { | |
try { | |
const dataToSend = this.prepareData(dataSources); | |
const options = { | |
method, | |
headers: { "Content-Type": "application/json" }, | |
}; | |
// Only attach body if method is not GET and dataSources are defined | |
if (method !== "GET" && dataToSend) { | |
options.body = JSON.stringify(dataToSend); | |
} | |
const response = await fetch(endpoint, options); | |
const data = await response.json(); | |
if (!response.ok) { | |
return { error: data.error || "An error occurred" }; | |
} | |
return data; | |
} catch (error) { | |
console.error("Failed to fetch data:", error); | |
return { error: "Failed to fetch data" }; | |
} | |
} | |
prepareData(dataSources) { | |
if (!dataSources) return null; | |
const sources = dataSources.split(",").map((src) => src.trim()); | |
const dataToSend = {}; | |
sources.forEach((src) => { | |
const element = document.querySelector(src); | |
if (element) { | |
// Remove the '#' from the key | |
const key = src.startsWith("#") ? src.slice(1) : src; | |
dataToSend[key] = element.value || element.innerText; | |
} | |
}); | |
return dataToSend; | |
} | |
renderTemplate(template, data, target) { | |
const fragment = document.createDocumentFragment(); | |
// Helper function to replace placeholders and append clone to fragment | |
const renderData = (item) => { | |
const clone = document.importNode(template.content, true); | |
clone.querySelectorAll("*").forEach((node) => { | |
node.innerHTML = node.innerHTML.replace( | |
/\$\{([^}]+)\}/g, | |
(_, key) => item[key] || "", | |
); | |
}); | |
fragment.appendChild(clone); | |
}; | |
// Check if data is an array or object | |
if (Array.isArray(data)) { | |
data.forEach(renderData); | |
} else { | |
renderData(data); | |
} | |
const action = template.getAttribute("hx-action") || "update"; | |
switch (action) { | |
case "append": | |
target.appendChild(fragment); | |
break; | |
case "swap": | |
target.replaceWith(fragment); | |
break; | |
case "update": | |
default: | |
target.innerHTML = ""; | |
target.appendChild(fragment); | |
break; | |
} | |
} | |
} | |
new HTMJ(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment