2

For my Django application I am using Flowbite to make interactive UI components. I am also using HTMX to dynamically load some html content from the back-end (Django) into the page without refresh.

Basically I have a button that sends a HTMX get requests to the backend and then targets a modal window (a Flowbite component) with the received html (nothing ground-breaking). The mechanism works well, however here is the twist: the html code that my backend returns, contains another Flowbite component i.e. a dropdown menu.


<!-- MODAL BTN -->
<button hx-get="{% url '<myurl>' %}"
  hx-target="#newItemModal"
  data-modal-target="newItemModal" data-modal-toggle="newItemModal"
  class="block text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center"
  type="button">
  Modal open
</button>

<!-- MODAL WINDOW -->
<div id="newItemModal" tabindex="-1" aria-hidden="true"
  class="fixed top-20 left-0 right-0 z-50 hidden w-full p-4 overflow-x-hidden overflow-y-auto md:inset-0 h-[calc(100%-1rem)] md:h-full">
</div>

The HTML returned by the backend with the hx-get is as follows:


<div>
  ...
    <!-- MENU BTN -->
    <button type="button" data-dropdown-toggle="dropdownEditMenu" 
    class="aspect-square h-10 ml-2 rounded-lg text-white drop-shadow-[0_1.2px_1.2px_rgba(0,0,0,0.8)] hover:text-slate-100">
      <span class="mdi mdi-dots-vertical text-3xl"></span>
    </button>

    <!--    DROPDOWN MENU   -->
    <div id="dropdownEditMenu"
        class="z-10 hidden rounded-lg shadow w-48 max-h-48 lg:max-h-none bg-slate-600 overflow-auto">
        <ul class="text-sm text-white">
            <li class="hover:bg-slate-700 px-4 py-2 truncate">
                <a href="{% url <myurl> %}" class="w-full inline-flex items-center">
                  <span class="mdi mdi-square-edit-outline text-xl"></span>
                  <span class="ml-2 grow whitespace-nowrap text-left">Edit</span>
                </a>
            </li>
        </ul>
    </div>
          

</div>

When this HTML code is added as content to the modal window, the dropdown does not work. I suspect the reason is that the flowbite checks for its components when the page is loaded. Thus, if I dynamically add another component to the page afterwards, Flowbite is not aware of this extra HTML and the component is not initialised. This might explain why the modal component works but the dropdown component does not.

If this is indeed the case, how can I add the dynamically loaded dropdown as a Flowbite component? Is there a way to tell the flowbite script to reload when HTMX returns additional code?

I have not found a viable solution but as explained above I might have found the cause of the problem.

Amberclust
  • 23
  • 5

3 Answers3

0

The issue is that Flowbite initialises all the "data attributes with dropdown" just once, at start. We can see that in the export function initFlowbite() (here is link):

export function initFlowbite() {
    ...
    initDismisses();
    initDropdowns();
    initModals();
    ...

And this is the default initialisation of export function initDropdowns()

What you need to do, is "re-enable" the JS "magic" after the new HTML is added on you page. For this you can use the hx-on attribute of HTMX, with the afterSettle event. Here is an example:

<button hx-post="/example"
        hx-on="htmx: afterSettle: doSomething();">
    Get me the dropdown!
</button>

In that JS portion, you need to:

  1. Re-initialise all the dropdowns by calling Flowbite's initDropdowns() function

or

  1. Create a function to initialise the specific dropdown, as specified in Flowbite's documentation

     // set the dropdown menu element
     const $targetEl = document.getElementById('dropdownMenu');
    
     // set the element that trigger the dropdown menu on click
     const $triggerEl = document.getElementById('dropdownButton');
    
     // options with default values
     const options = {
       placement: 'bottom',
       triggerType: 'click',
       offsetSkidding: 0,
       offsetDistance: 10,
       delay: 300,
       onHide: () => {
           console.log('dropdown has been hidden');
       },
       onShow: () => {
           console.log('dropdown has been shown');
       },
       onToggle: () => {
           console.log('dropdown has been toggled');
       }
     };
    
    
     const dropdown = new Dropdown($targetEl, $triggerEl, options);
    
foobarna
  • 854
  • 5
  • 16
  • Well, as I call `initDropdowns()` I get `caught ReferenceError: initDropdowns is not defined` – DayDreamer Apr 23 '23 at 19:10
  • Also while following the documentation, I get `caught SyntaxError: Cannot use import statement outside a module` when I import something. Most annoying and useless – DayDreamer Apr 23 '23 at 19:13
  • @DayDreamer have you tried resolving that error? For example, here are some suggestions https://stackoverflow.com/a/66976498/2692704 – foobarna Apr 24 '23 at 08:35
  • 1
    thank you. Alas, none of the solutions in the thread work. Declaring that Flowbite is a module changes nothing, no matter how many restarts. All the other options imply some sort of js-server or npm, none of which I am using. FYI, my setup consists of a FastAPI+Python solution. – DayDreamer Apr 24 '23 at 16:11
  • And how did you set it up? It does require "npm" setup, tho https://flowbite.com/docs/getting-started/flask/ – foobarna Apr 24 '23 at 16:53
  • The way I see it, npm is required for the devs to "compile" into css. The rest is npm-free. My initial issue is with not being able to run flowbite.js from the client's side of the webpage. There was no way of importing. Now with me being able to test the js code from console, I can at least start figuring out what to put inside the webpage to always call for biteflow's init() each time a HTMX sub-page gets loaded. Work in progress still :) – DayDreamer Apr 24 '23 at 18:49
0

Disclaimer: Bing chat helped me out. Thanks to devs at Bing. Long story short, I'll just quote the Bing reply.

Me asking Bing: I am importing flowbite in Google Chrome's console

Bing replying:

I see. The import statement is not supported in the Chrome console. If you want to use the Flowbite library in the Chrome console, you can try accessing it through the global window object after importing it via a script tag in your HTML file. For example, if you have added the Flowbite library to your HTML file using a script tag like this:

<script type="module" src="https://cdnjs.cloudflare.com/ajax/libs/flowbite/1.6.5/flowbite.min.js"></script>

You can then access the Dropdown object in the Chrome console like this:

const Dropdown = window.Dropdown;

...and so it worked... all I can do is to share, what I managed to find. Hope this helps.

DayDreamer
  • 107
  • 9
  • 1
    I know it's not the TRUE solution to the question, it's more like my solution to my flavor of the same problem. In the spirit of not cloning the question, I hope my post will help other people out. I am yet to try to call the init function foobama talked about. – DayDreamer Apr 24 '23 at 18:43
0

I ran into the same problem, so after looking out in the docs of htmx and flowbite i found a solution. Because htmx load dynamicly content, you have to force the initialisation of flowbite js fonction, so you have to use the htmx.onLoad() fonction inside your head tag like this:

<head> 
       ...
       <script>
           htmx.onLoad(function(content) {
               initFlowbite();
           })
       </script>
       ...
</head>