flowbite icon indicating copy to clipboard operation
flowbite copied to clipboard

Targeted parsing and initialization of components

Open samld opened this issue 2 years ago • 32 comments

I've seen a lot of issues from people who use the vanilla version with HTMX (see issues #812, #813, #820 [mine], #821). More precisely, content that is dynamically loaded and swapped needs to be parsed by Flowbite to allow for interactivity. Calling initFlowbite() will cause the whole DOM tree to be reparsed which can cause weird behaviour, e.g. if the loaded content is within a modal (see issue #820).

This PR intends to address these issues by making the internal initialization functions public so that they can be called on specific elements of the DOM tree. Furthermore, it adds an optional parameter to init[ComponentName] functions allowing for a more targeted parsing and initialization. The function initFlowbite itself has been given such an optional parameter. This works by leveraging the fact that the querySelector can work on any node from the DOM tree.

I believe that these changes will allow people using HTMX to target the elements to parsed and initialized within HTMX onload event without having to write their own initialization script. Given the growth of HTMX, I think this package benefits from being a little more agnostic about its initialization scenarios.

I also think that documenting both the integration of Flowbite within a HTMX-driven application and the newly-added functions would be greatly beneficial to the people using this package. I would be willing to provide such documentation in a separate PR provided that you accept my contribution to the package.

Finally, regarding the Datepicker plugin, if my contribution is accepted, I will address it in a future PR. I have not included it as I wanted to address the most common issues and I wasn't certain if I needed to add to inject the methods in the window object. This pattern seems to have been followed for all components, but, oddly enough, not for this plug in.

How to integrate it with HTMX

This is how I run it with the proposed version built and deployed in my current project.

// this will parse any DOM content loaded by HTMX, 
// including the body at the initial page render.
htmx.onLoad(function (content) {
    initFlowbite(content);
})

samld avatar Feb 29 '24 17:02 samld

Review PR in StackBlitz Codeflow Run & review this pull request in StackBlitz Codeflow.

It would be great if this was merged in! Solves many of my issues with Flowbite and HTMX integration.

lobot1010 avatar Mar 01 '24 04:03 lobot1010

It would be great if this was merged in! Solves many of my issues with Flowbite and HTMX integration.

In the meantime, you can clone it and build it with yarn build:js and get the content from the ./static/ directory. Be sure to be on the targeted-init branch. It should give you both the minimized file and a map file (among other files). Don't call flowbiteInit outside the htmx.onLoad, as I've already written, the element is triggered when the page is finished loading and the body is passed as the element parameter.

samld avatar Mar 01 '24 13:03 samld

Hey @samld,

Thanks for this amazing PR! One last thing that would be really cool is if you documented these new changes and API functionalities for this specific page: https://flowbite.com/docs/getting-started/javascript/

After that, we could even open up another guide page for usage with HTMX. The PR looks good, but I'll need to do some tests obviously and see how this affects out integration guides such as with React or Vue.

Cheers, Zoltan

zoltanszogyenyi avatar Mar 17 '24 00:03 zoltanszogyenyi

@zoltanszogyenyi Thanks for taking the time to parse through my contribution! Let me know the outcome of your tests, if I need to change anything. Meanwhile, I'll update the documentation. There should a commit by the end of the week!

samld avatar Mar 18 '24 13:03 samld

@zoltanszogyenyi I have updated the documentation adding a few more information about the targeted init methods.

samld avatar Mar 29 '24 14:03 samld

Great, this will help me a lot.

biellsilva5 avatar Apr 25 '24 05:04 biellsilva5

This would actually make my application work with htmx and flowbite.

malikvogt avatar Apr 25 '24 13:04 malikvogt

I need this as well, my application modify the dom and I need to reload parts of the DOM with initFlowbite() without calling it on the entire page.

quanturium avatar May 02 '24 00:05 quanturium

Any update on this? I need this fix too

dzonerzy avatar May 03 '24 10:05 dzonerzy

Hey @zoltanszogyenyi, have you had the time to look into the changes I proposed? It seems a few people here are eager for a merge to occur. If there is anything that needs to be changed, let me know!

Thanks!

samld avatar May 06 '24 17:05 samld

Hey @samld,

Thanks for this amazing PR! One last thing that would be really cool is if you documented these new changes and API functionalities for this specific page: https://flowbite.com/docs/getting-started/javascript/

After that, we could even open up another guide page for usage with HTMX. The PR looks good, but I'll need to do some tests obviously and see how this affects out integration guides such as with React or Vue.

Cheers, Zoltan

Are these changes planned for the near future?

malikvogt avatar Jun 27 '24 12:06 malikvogt

@zoltanszogyenyi Any update regarding this PR?

zkne avatar Jul 12 '24 12:07 zkne

Any updates on this? This change is needed in our project too.

ryanp-dev avatar Jul 29 '24 04:07 ryanp-dev

I will fix the conflicts so that the PR might stand a better chance of being accepted.

samld avatar Aug 08 '24 21:08 samld

Also facing this issue. Find it hard to believe this is not covered already or is being ignored. It's basically unusable otherwise.

stekyne avatar Aug 21 '24 23:08 stekyne

Hey guys,

Sorry, I was knocked out for a while because of a sickness. I'll have this reviewed for the next update.

Been doing some other stuff like the new datatables update:

https://flowbite.com/docs/plugins/datatables/

I need to see how will this affect other integration guides like for Angular, Vue, and so on.

zoltanszogyenyi avatar Aug 26 '24 12:08 zoltanszogyenyi

Also thank you @samld a bunch for this PR, resolving the merge conflicts will definitely help push this forward.

zoltanszogyenyi avatar Aug 26 '24 12:08 zoltanszogyenyi

Also since our latest version the datepicker is part of the main init engine so that should help for sure.

zoltanszogyenyi avatar Aug 26 '24 12:08 zoltanszogyenyi

I'm also looking forward to this PR getting merged.

W1M0R avatar Aug 29 '24 13:08 W1M0R

@samld @zoltanszogyenyi While we wait for this, the following method helps to reduce the number of calls to initFlowbite using the lodash debounce function:

import _ from 'lodash';
function initFlowbiteContent(content) {
  console.debug("flowbite initialised");
  initFlowbite();
  // Keep an eye out for this flowbite fix:
  // https://github.com/themesberg/flowbite/pull/824
  // initFlowbite(content);
}

// Enable this once the fix has landed.
// htmx.onLoad(initFlowbiteContent);
// For now we debounce initialisation to reduce the number of calls to initFlowbite.
htmx.onLoad(_.debounce(initFlowbiteContent, 300));
</script>

W1M0R avatar Aug 29 '24 14:08 W1M0R

Any update on this by any chance?

Thutmose3 avatar Nov 09 '24 23:11 Thutmose3

I just build this branch PR with yarn build:js and it seems to work perfectly. I can initialize specific dropdowns with

element = document.getElementById("btn_1")
initDropdownByElement(element);

This makes so much sense, instead of having to do initFlowbite(content); and refresh the whole page like a caveman from 1998.

Why is this PR not being merged or looked at? We need to have this merged ASAP.

@zoltanszogyenyi please have a look at this @samld Thanks for this PR and explanation. Any hopes this will be merged?

Thutmose3 avatar Nov 26 '24 16:11 Thutmose3

@zoltanszogyenyi @Thutmose3 @W1M0R @ryanp-dev @malikvogt I have finally had time to make the necessary backmerges. I have also added target initialization for datepickers.

@zoltanszogyenyi let me know if I have to make other changes, I've already bumped the version, but I can fix it if you don't want to bump the version. It is backwards compatible, and shouldn't break anything.

samld avatar Nov 26 '24 22:11 samld

I just ran into this while creating a modal using HTMX. Is there a work around I can use until this is merged?

BennieCopeland avatar Jan 02 '25 04:01 BennieCopeland

I just ran into this while creating a modal using HTMX. Is there a work around I can use until this is merged?

You can go on my fork, download the code and build it. I might make a release in the meantime it's merged.

samld avatar Jan 07 '25 14:01 samld

Ran into this issue while trying to get a drawer setup with htmx + flowbite. @samld tried to get your fork running by running npm i samld/flowbite -- which did install, but then ran into other build errors.

Anyways, an alternative for me ended up being to switch to using Alpine.js + PinesUI slide-over component.

Basically in your index.js you add:

import Alpine from 'alpinejs';
window.Alpine = Alpine;
document.addEventListener('DOMContentLoaded', () => {
    Alpine.start();
});

document.addEventListener('htmx:beforeSwap', () => {
    Alpine.deferMutations()
})
document.addEventListener('htmx:afterSettle', () => {
    Alpine.flushAndStopDeferringMutations()
})
// source: https://github.com/alpinejs/alpine/discussions/2605#discussioncomment-10487027

and then you can copy paste the PinesUI slideover component which is here: https://devdojo.com/pines/docs/slide-over

zachbellay avatar Jan 25 '25 23:01 zachbellay

I ran into this a while back. My situation is possibly slightly different to most but I am using these components in jinja2 template fragments which are sometimes loaded into a page through htmx.

My work around solution was to define a function that initiates the component and then define two triggers for the setup function, One trigger is DOMContentLoaded and the other is htmx:afterSettle. By doing it this way the component is initialized either at page load or when it is loaded into the page.

I have probably done a poor job of explaining this. I have added some sample code below. When looking at the below please note that everything is coming through jinja2 first so anything wrapped in {{}} or {% %} is jinja2 template logic not JS.

    <script>
        function {{id}}_setupDropdown() {
            let options = {
                placement: '{{ placement }}',
                triggerType: '{{ triggerType }}',
                offsetSkidding: {{ offsetSkidding }},
                offsetDistance: {{ offsetDistance }},
                delay: {{ delay }},
                ignoreClickOutsideClass: {% if not ignoreClickOutsideClass %}false{% else %}'{{ ignoreClickOutsideClass }}'{% endif %},
            };

            let instance_options = {
                id: '{{id}}_dropdown',
                override: true
            };

            let target = document.getElementById('{{id}}_dropdown_target');
            let trigger = document.getElementById('{{triggerElementId}}');
            let dropdown = new Dropdown(target, trigger, options, instance_options);
        };

        document.addEventListener("DOMContentLoaded", function() {
            {{id}}_setupDropdown();
        });

        document.addEventListener("htmx:afterSettle", function(event) {
            let $target = event.detail.target;
            let $trigger = document.getElementById('{{triggerElementId}}');
            if ($target.contains($trigger)) {
                {{id}}_setupDropdown();
            }
        });
    </script>

Paul-GSCS avatar Jan 26 '25 01:01 Paul-GSCS

It's almost been a year? Will it ever be merged? If not, I'll fork the project for HTMX and maintain it.

samld avatar Feb 06 '25 21:02 samld