themesberg / flowbite

Open-source UI component library and front-end development framework based on Tailwind CSS
https://flowbite.com
MIT License
7.7k stars 723 forks source link

Javascript doesn't work in Vue3 #3

Closed stanislavhannes closed 2 years ago

stanislavhannes commented 2 years ago

Hey there, I am using a fresh project with tailwind2, flowbite 1.1.1 and vue3. The backend stack ist laravel 8 (php8) with inertia.js. If I'm creating a modal inside a Vue component, then I can't open/toggle the model (data-modal-toggle="default-modal" is set to the button) . It only works, if i move the button and modal out of the component, into the final html generated site (with Blade Template Engin). It affects not only the modal, but also the alert dismiss button and basically everything that need flowbite javascript.

Has someone the same issue and know a workaround?

radudiaconu0 commented 2 years ago

i have this issue too on a simple vue 3 project with either vite or webpack

SalimiHabib commented 2 years ago

same issue in Blazor web assembly flowbite.bundle.js exist in client side but not working at all

zoltanszogyenyi commented 2 years ago

The way the flowbite.bundle.js works is that it looks for the data attributes and then applies event listeners, this means that the JS needs to have an already rendered DOM to make it work.

We are aware of the compatibility issues regarding virtual DOM based libraries and frameworks, we're trying to find solutions.

Please take into account that the other non-interactive components (ie. buttons, alerts, badges) still work regardless of the JS file.

n-studio commented 2 years ago

A simple way would be to encapsulate the event listeners into a public function (let's say Tooltip.start();) and let the user call it in their own listeners.

document.addEventListener("dom-reload", function() {
  Tooltip.start();
});
aleqsio commented 2 years ago

Unfortunately this makes the library unusable in what amounts to i'd wager 80% of modern web applications :/ I'm making a hack like this to make it work, but i'd suggest a more performant solution down the line.

Edited the flowbite.bundle.js with the following addition:

const liveQueries = [];
const container = document.getElementById("root");

const observer = new MutationObserver((records) => {
  liveQueries.forEach((q) => q());
});
observer.observe(container, {
  childList: true,
  subtree: true,
});

const liveQuerySelector = (selector, callback) => {
  const update = () => {
    const found = container.querySelectorAll(selector);
    found.forEach(callback);
  };
  liveQueries.push(update);
};

And replaced each of the 5 top level occurences of document.querySelectorAll(*some selector*).forEach(function with liveQuerySelector(*some selector*, function

It seems that references to elements are still sometimes lost and this might lag on large DOMs, but It's better than nothing.

The code is also up on gist: https://gist.github.com/aleqsio/6273a99f18ff8f494ca7337d8be7d955

zoltanszogyenyi commented 2 years ago

A simple way would be to encapsulate the event listeners into a public function (let's say Tooltip.start();) and let the user call it in their own listeners.


document.addEventListener("dom-reload", function() {

  Tooltip.start();

});

I'll look into this solution tomorrow. Thanks.

zoltanszogyenyi commented 2 years ago

Unfortunately this makes the library unusable in what amounts to i'd wager 80% of modern web applications :/

I'm making a hack like this to make it work, but i'd suggest a more performant solution down the line.

Edited the flowbite.bundle.js with the following addition:


const liveQueries = [];

const container = document.getElementById("root");

const observer = new MutationObserver((records) => {

  liveQueries.forEach((q) => q());

});

observer.observe(container, {

  childList: true,

  subtree: true,

});

const liveQuerySelector = (selector, callback) => {

  const update = () => {

    const found = container.querySelectorAll(selector);

    found.forEach(callback);

  };

  liveQueries.push(update);

};

And replaced each of the 5 top level occurences of

document.querySelectorAll(*some selector*).forEach(function

with

liveQuerySelector(*some selector*, function

It seems that references to elements are still sometimes lost and this might lag on large DOMs, but It's better than nothing.

The code is also up on gist:

https://gist.github.com/aleqsio/6273a99f18ff8f494ca7337d8be7d955

Thanks for the workaround and info.

We are looking into a solution to make the event listeners work overall.

We also started a project where we will build separate React and Vue components.

aleqsio commented 2 years ago

No worries, thanks for looking into this. Hope there'll be an official solution soon :) I think even exporting the individual functions as onClick handlers would be a significant step up in functionality while being a hopefully small workload.

zoltanszogyenyi commented 2 years ago

With the v1.3.0 release of Flowbite we have now solved the problem of event listeners not registering with front-end libraries and frameworks such as React.js and Vue.

Please check out this guide on how to use Tailwind CSS and Flowbite inside a Vue project.

PS: there's no official Vue package just yet, we're working on that. But interactive elements like tooltips and dropdowns should now work.

aleqsio commented 2 years ago

You're using document.addEventListener('DOMContentLoaded' so it still won't work anytime there is a programmatic change in DOM after initial load (i think). What about cases like using react-router - will all interactive components not work on react router pages? What about initial loading spinners?

I'm either missing something and integrated this wrong, or this still doesn't work for any real life use in react :(

zoltanszogyenyi commented 2 years ago

Hey @aleqsio,

Can you send me a repo to test that out?

Thanks!

PS: we'll launch a standalone React and Vue.js component library at the beginning of Q2, so all of these problems will be solved and you won't need to use the data attributes anymore.

MilesWuCode commented 2 years ago

You're using document.addEventListener('DOMContentLoaded' so it still won't work anytime there is a programmatic change in DOM after initial load (i think). What about cases like using react-router - will all interactive components not work on react router pages? What about initial loading spinners?

I'm either missing something and integrated this wrong, or this still doesn't work for any real life use in react :(

+1 , not working vue-router , only index.vue

vite + vite-plugin-vue-layouts + vite-plugin-pages + vue-router

https://github.com/MilesWuCode/my-vite-template/tree/flowbite

EX: working http://localhost:3000

not working http://localhost:3000/todo

Morzaram commented 2 years ago

I'm having the same issue with basic html5 setup (using rails 7)

The dropdown no longer triggers after a page change. I have to refresh the page.

INFy93 commented 2 years ago

I also have this problems, but with dropdown elements Vue 3 + Laravel 8, flowbite 1.3.4 I'm creating table using axios. In one colum I have dropdown with unique "id". But dropdowns isn't work in it. Outside of element everything works fine. When im using DaizyUI, dropdowns works. But I have some problems with styling this, so I want use flowbite. Is any solutions now?..

UPD: Like temporary solution I use dropdowns form Headless UI. This dropdowns work fine

necony286 commented 2 years ago

Still having this same problem

knutole commented 2 years ago

PS: we'll launch a standalone React and Vue.js component library at the beginning of Q2, so all of these problems will be solved and you won't need to use the data attributes anymore.

@zoltanszogyenyi ETA for Vue.js version?

col-bc commented 2 years ago

I'm also having problems getting interactive components to work. The setup instructions for vue work, but when vue-rpouter is added to the project, everything stops again.

ralphschuler commented 2 years ago

Same issue here,

when using Vue3 reactivity (v-if) the event listener "breaks" and won't pickup any changes.

I'm using flowbite: 1.4.7 vue: 3.2.33

rluders commented 2 years ago

Some context:

<script setup>
import { Link } from '@inertiajs/inertia-vue3'
// import { Dropdown } from 'flowbite'
import Flowbite from 'flowbite'
import { Inertia } from '@inertiajs/inertia'

Inertia.on('finish', () => {
  document.querySelectorAll('[data-dropdown-toggle]').forEach(triggerEl => {
    const targetEl = document.getElementById(triggerEl.getAttribute('data-dropdown-toggle'))
    const placement = triggerEl.getAttribute('data-dropdown-placement')

    Flowbite.Dropdown(targetEl, triggerEl, {
      placement: placement ? placement : 'bottom'
    })
  })
})
</script>

tho seems something is happening in the browser devtools

flowbite__WEBPACK_IMPORTED_MODULE_1___default(...).Dropdown is not a function
benoit96 commented 2 years ago

Some context:

<script setup>
import { Link } from '@inertiajs/inertia-vue3'
// import { Dropdown } from 'flowbite'
import Flowbite from 'flowbite'
import { Inertia } from '@inertiajs/inertia'

Inertia.on('finish', () => {
  document.querySelectorAll('[data-dropdown-toggle]').forEach(triggerEl => {
    const targetEl = document.getElementById(triggerEl.getAttribute('data-dropdown-toggle'))
    const placement = triggerEl.getAttribute('data-dropdown-placement')

    Flowbite.Dropdown(targetEl, triggerEl, {
      placement: placement ? placement : 'bottom'
    })
  })
})
</script>

tho seems something is happening in the browser devtools

flowbite__WEBPACK_IMPORTED_MODULE_1___default(...).Dropdown is not a function

You were close to the solution ! Here is what worked for me

<script setup>
import { Link } from '@inertiajs/inertia-vue3'
import Flowbite from 'flowbite'
import { Inertia } from '@inertiajs/inertia'

Inertia.on('finish', () => {
  document.querySelectorAll('[data-dropdown-toggle]').forEach(triggerEl => {
    const targetEl = document.getElementById(triggerEl.getAttribute('data-dropdown-toggle'))
    const placement = triggerEl.getAttribute('data-dropdown-placement')

    const dd = new Dropdown(targetEl, triggerEl, {
      placement: placement ? placement : 'bottom'
    })
  })
})
</script>

1- Dropdown is a class and not a function 2- Looks like when you import the module everything it contains is global so you can use Dropdown directly

I hope this helps !

rluders commented 2 years ago

Nice. The code isn't mine, just bring it from the Discord channel to get some help for a user. Thanks @benoit96

benoit96 commented 2 years ago

You're welcome @rluders

stallyons-developer1 commented 2 years ago

Still having the same issue.

zoltanszogyenyi commented 2 years ago

Hey, everyone!

A new standalone Vue 3 library has been started here and @ralphschuler is the maintainer.

If you'd like to help you can contribute to the repository and reach out to me or @ralphschuler :)

https://github.com/themesberg/flowbite-vue

sagadsalem commented 2 years ago

any workaround? for Vue 3 + inertia js

alorence commented 2 years ago

I have this issue too... The main problem is that JS components registration is attached to DOMContentLoaded event. Unfortunately, in some case (very light page content), the DOM finishes to load BEFORE the JS code is executed.

This is very well explained here: https://stackoverflow.com/a/39993724/1887976

A solution would be to transform this kind of code (ex for modal):

document.addEventListener('DOMContentLoaded', () => {
    let modalInstances = []
    document.querySelectorAll('[data-modal-toggle]').forEach(el => {
        const modalId = el.getAttribute('data-modal-toggle');
        const modalEl = document.getElementById(modalId);
        const placement = modalEl.getAttribute('data-modal-placement')

        if (modalEl) {
            if (!modalEl.hasAttribute('aria-hidden') && !modalEl.hasAttribute('aria-modal')) {
                modalEl.setAttribute('aria-hidden', 'true');
            }
        }

        let modal = null
        if (getModalInstance(modalId, modalInstances)) {
            modal = getModalInstance(modalId, modalInstances)
            modal = modal.object
        } else {
            modal = new Modal(modalEl, {
                placement: placement ? placement : Default.placement
            })
            modalInstances.push({
                id: modalId,
                object: modal
            })
        }

        if (modalEl.hasAttribute('data-modal-show') && modalEl.getAttribute('data-modal-show') === 'true') {
            modal.show();
        }

        el.addEventListener('click', () => {
            modal.toggle()
        })
    })
})

into something similar to this

const initModal = () => {
    let modalInstances = []
    document.querySelectorAll('[data-modal-toggle]').forEach(el => {
        const modalId = el.getAttribute('data-modal-toggle');
        const modalEl = document.getElementById(modalId);
        const placement = modalEl.getAttribute('data-modal-placement')

        if (modalEl) {
            if (!modalEl.hasAttribute('aria-hidden') && !modalEl.hasAttribute('aria-modal')) {
                modalEl.setAttribute('aria-hidden', 'true');
            }
        }

        let modal = null
        if (getModalInstance(modalId, modalInstances)) {
            modal = getModalInstance(modalId, modalInstances)
            modal = modal.object
        } else {
            modal = new Modal(modalEl, {
                placement: placement ? placement : Default.placement
            })
            modalInstances.push({
                id: modalId,
                object: modal
            })
        }

        if (modalEl.hasAttribute('data-modal-show') && modalEl.getAttribute('data-modal-show') === 'true') {
            modal.show();
        }

        el.addEventListener('click', () => {
            modal.toggle()
        })
    })
}

if (document.readyState !== 'loading') {
    // DOMContentLoaded event already fired, let's perform initialization explicitely
    initModal()
} else {
    // DOMContentLoaded event not yet fired, attach initialization process to it
    document.addEventListener('DOMContentLoaded', initModal)
}
zoltanszogyenyi commented 2 years ago

Hey everyone,

So we started working on the Flowbite Vue component library so all Vue and Nuxt-related issues should be addressed there since the main Flowbite Library is based on event listeners and not completely compatible with libraries and frameworks such as React or Vue.js (that's why we started working on actual Vue or React components.)

Feel free to contribute to those repositories!