bigskysoftware / htmx

</> htmx - high power tools for HTML
https://htmx.org
Other
38.19k stars 1.3k forks source link

Trouble with `hx.process()`, playing around with making an extension #425

Closed paxperscientiam closed 2 years ago

paxperscientiam commented 3 years ago

So, I've started some experimenting with making a pagination extension. What I've got so far is really messy, but that's beside the point. Current sticking point is that htmx.process(element) seems not work.

My goal is to update the hx-get attribute each time there's a page turn event. I figured that once I update the attribute value and then run htmx.process on the relevant element, all would be good. Unfortunately, the hx-get never gets updated internally.

Here's my code so far. What might I be doing wrong?

As an aside, is there a deeper explanation of how the extension API should be used? (https://htmx.org/extensions/#reference). Like with onEvent(), there's a slew of events, but it's unclear to me what augmentations should be done when. To me, initial DOM changes (creating previous and next buttons and attaching them) should be done when htmx:beforeProcessNode is fired.

Anyway, thanks!

<!doctype html>
<html lang="en">
    <head>
        <meta charset="UTF-8"/>
        <title>Document</title>

        <script type="text/javascript" src="src/htmx.js"></script>
        <script type="text/javascript" src="src/ext/my.js"></script>

        <style type="text/css" media="screen">
         header,#body,#hx-pagination-container,footer {
             border: 1px solid black;
             min-height: 200px;
         }

        </style>
    </head>
    <body>
        <header>header</header>
        <div id="body"
             hx-get="/body.html"
             hx-trigger="load,click"
             hx-ext="pagination-ext"
             hx-pagination-ext="initialpage:5,resourceRoot:/book1/,filePrefix:page-,fileExt:html"></div>
        </div>
        <footer>footer</footer>
    </body>

</html>
/*
  1. prefetch
  2. option to append at beginning of stack or end of stack
  3. ltr and rtl support

*/
(function(){

    function updateElements(elRoot, pageNumber) {
        elRoot.setAttribute("hx-pagination-current-page", pageNumber);
        let x = document.getElementById("hx-pagination-prev-button");
        let y = document.getElementById("hx-pagination-next-button");

        const parameterString = elRoot.getAttribute("hx-pagination-ext");
        const pairStrings = parameterString.split(",");
        const parameters = {};
        for (const pam of pairStrings) {
            const [k,v] = pam.split(":");
            parameters[k] = v;
        }

        const nextUrl = `${parameters['resourceRoot']}${parameters['filePrefix']}${pageNumber + 1}`;
        const prevUrl = `${parameters['resourceRoot']}${parameters['filePrefix']}${pageNumber - 1}`;

        x.setAttribute("hx-get", prevUrl);
        y.setAttribute("hx-get", nextUrl);
    }

    htmx.defineExtension('pagination-ext', {
        onEvent : function(name, evt) {
            console.log(name, evt)
            if ("htmx:pagination-prev-page" === name) {
                console.log('go pre');
                const elRoot = evt.srcElement;
                const pageNumber = Number.parseInt(elRoot.getAttribute("hx-pagination-current-page"), 10);
                updateElements(elRoot, (pageNumber - 1));
                htmx.process(elRoot);
            }

            if ("htmx:pagination-next-page" === name) {
                console.log('go pre');
                const elRoot = evt.srcElement;
                const pageNumber = Number.parseInt(elRoot.getAttribute("hx-pagination-current-page"), 10);
                updateElements(elRoot, (pageNumber + 1));
                htmx.process(elRoot);
            }

            // setup
            if ("htmx:beforeProcessNode" == name) {
                console.log('process');
                const elRoot = evt.srcElement;
                const elControlsContainer = document.createElement("div");
                const elPrev = document.createElement("div");
                const elNext = document.createElement("div");
                elPrev.textContent = "prev";
                elPrev.id = "hx-pagination-prev-button";
                elNext.textContent = "next";
                elNext.id = "hx-pagination-next-button";

                const parameterString = elRoot.getAttribute("hx-pagination-ext");
                const pairStrings = parameterString.split(",");
                const parameters = {};
                for (const pam of pairStrings) {
                    const [k,v] = pam.split(":");
                    parameters[k] = v;
                }

                const initialPage = Number.parseInt(parameters['initialpage'], 10);

                elRoot.setAttribute("hx-pagination-current-page", initialPage);

                const nextUrl = `${parameters['resourceRoot']}${parameters['filePrefix']}${initialPage + 1}`;
                const prevUrl = `${parameters['resourceRoot']}${parameters['filePrefix']}${initialPage - 1}`;

                elPrev.setAttribute("hx-get", prevUrl);
                elPrev.setAttribute("hx-target", `#${elRoot.id}`);

                elNext.setAttribute("hx-get", nextUrl);
                elNext.setAttribute("hx-target", `#${elRoot.id}`);

                elControlsContainer.setAttribute("hx-pagination-controls", "");
                elControlsContainer.id = "controls";
                elControlsContainer.appendChild(elPrev);
                elControlsContainer.appendChild(elNext);

                elRoot.insertAdjacentElement('afterend', elControlsContainer);

                htmx.on(`#${elNext.id}`, "click", function() {
                    htmx.trigger(elRoot, "htmx:pagination-next-page");
                });
                htmx.on(`#${elPrev.id}`, "click", function() {
                    htmx.trigger(elRoot, "htmx:pagination-prev-page");
                });

                htmx.process(elControlsContainer);
            }
            if ("htmx:afterSettle" === name) {
                console.log('aftersettle')
                htmx.process(document.body);
            }
        }
    });
})();
1cg commented 3 years ago

Right now we don't do a great job of detecting changes to htmx. In fact, we prevent any updates after the node is first initialized:

https://github.com/bigskysoftware/htmx/blob/3352d5c3e95dad01470e8fc725c61dee1d476593/src/htmx.js#L1327

The "right thing" here is to allow you to reinitialize the paging node, so maybe something like htmx.reinit(node) that internally calls https://github.com/bigskysoftware/htmx/blob/3352d5c3e95dad01470e8fc725c61dee1d476593/src/htmx.js#L560 and then htmx.process()

Or maybe we just expose the above internal method as htmx.clear()?

p-baum commented 2 years ago

Yes yes yes. Pleeeease! Begs the question why we cant have this in the first place? I guess to prevent people creating slow code. I accept the responsibilty. Please make it happen. Until then How can I access this hidden cleanUpElement function? Or is there another hack you would care to share?

1cg commented 2 years ago

As of 1.8.1, htmx.process() now will inspect a node and, if any attributes have changed, it will re-process them.

See https://github.com/bigskysoftware/htmx/commit/1dbb22b682c625c5054de7127da975f30dfbe6ea

So you can simply modify a node and call htmx.process() on it. This should fix issues with morphing, etc.