htmlstreamofficial / preline

Preline UI is an open-source set of prebuilt UI components based on the utility-first Tailwind CSS framework.
https://preline.co
Other
4.57k stars 286 forks source link

Auto init in 2.0.2 #197

Closed hiteshjoshi closed 8 months ago

hiteshjoshi commented 9 months ago

So my html components, including html required for plugin gets build up on the fly (client side).

It used to work perfectly with 1.9.0 but with 2.0.2, I have to manually trigger HSStaticMethods.autoInit();

Can we go back to the same way 1.9.0 used to work? The breaking changes without a changelog or direction are very sad. I understand preline team has spend time writing documentation of implementing in various frameworks etc. but thats mostly the same as 1.9.0. In my case of Astro, its actually same.

But then something fundamentally has changed, and I can no longer generate preline dependent html on the fly.

supermx1 commented 9 months ago

I have the same issue. When I open up a page with a dropdown component(in Svelte), sometimes the dropdown works, sometimes it doesn't. It is so weird. I followed every step of the setup guide, but it is buggy at best. 1.9.0 did act like this

olegpix commented 9 months ago

So my html components, including html required for plugin gets build up on the fly (client side).

It used to work perfectly with 1.9.0 but with 2.0.2, I have to manually trigger HSStaticMethods.autoInit();

Can we go back to the same way 1.9.0 used to work? The breaking changes without a changelog or direction are very sad. I understand preline team has spend time writing documentation of implementing in various frameworks etc. but thats mostly the same as 1.9.0. In my case of Astro, its actually same.

But then something fundamentally has changed, and I can no longer generate preline dependent html on the fly.

You will be able to work with components as standard ES6 classes in version 2, thanks to this you can initialize your newly created component through the constructor. For example: import {HSSelect} from "prelien/preline"; ... btn.on('click', () => { // create some select with id on the fly const newSelect = new HSSelect("#new-added-select-id"); });

jagabs commented 9 months ago

do you guys have any idea how to add initial value to hsslect?

const select = new HSSelect(document.querySelector('#select'), {
      value: this.selectedValues,
      dropdownSpace: 1,
    });
i tried this but no luck

i also tried select.value = this.selectedValues;

it works after i trigger the onchange
armenr commented 8 months ago

We have the same issue. In our Nuxt app, the autoInit() seems to fire at the wrong time.

Our Navbar layout component seems to be interactive (the hs-* stuff works...accordians, drop downs etc work), but on the SAME page, components which require menus etc (that also utilize preline's baked-in JS) do not work.

Currently, our work-around is to have to do this:

// import 'preline/preline'
// import type { IStaticMethods } from 'preline/preline'

// declare global {
//   interface Window {
//     HSStaticMethods: IStaticMethods
//   }
// }

// export default defineNuxtPlugin((nuxtApp) => {
//   nuxtApp.hook('page:finish', () => {
//     window.HSStaticMethods.autoInit()
//   })
// })

declare global {
  interface HSStaticMethods {
    autoInit(): void
  }
  interface Window {
    HSStaticMethods: HSStaticMethods
  }
}

export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.hook('page:finish', () => {
    const router = useRouter()

    watchEffect(() => {
      import('preline/preline')
    })

    watchEffect(() => {
      const _route = router.currentRoute.value

      setTimeout(() => {
        window.HSStaticMethods.autoInit()
      }, 100)

      setTimeout(() => {
        window.HSStaticMethods.autoInit()
      }, 300)

      setTimeout(() => {
        window.HSStaticMethods.autoInit()
      }, 500)

      setTimeout(() => {
        window.HSStaticMethods.autoInit()
      }, 700)

      setTimeout(() => {
        window.HSStaticMethods.autoInit()
      }, 900)

      setTimeout(() => {
        window.HSStaticMethods.autoInit()
      }, 1100)

      setTimeout(() => {
        window.HSStaticMethods.autoInit()
      }, 1300)

      setTimeout(() => {
        window.HSStaticMethods.autoInit()
      }, 1500)

      setTimeout(() => {
        window.HSStaticMethods.autoInit()
      }, 1700)

      setTimeout(() => {
        window.HSStaticMethods.autoInit()
      }, 1900)
    })
  })
})

This is very bad. Please advise.

armenr commented 8 months ago

...any updates or feedback or any acknowledgement or anything? :(

This has really done some harm. We're considering moving away form PreLine entirely.

hiteshjoshi commented 8 months ago

@armenr what alternatives you considering?

olegpix commented 8 months ago

We have the same issue. In our Nuxt app, the autoInit() seems to fire at the wrong time.

Our Navbar layout component seems to be interactive (the hs-* stuff works...accordians, drop downs etc work), but on the SAME page, components which require menus etc (that also utilize preline's baked-in JS) do not work.

Currently, our work-around is to have to do this:

// import 'preline/preline'
// import type { IStaticMethods } from 'preline/preline'

// declare global {
//   interface Window {
//     HSStaticMethods: IStaticMethods
//   }
// }

// export default defineNuxtPlugin((nuxtApp) => {
//   nuxtApp.hook('page:finish', () => {
//     window.HSStaticMethods.autoInit()
//   })
// })

declare global {
  interface HSStaticMethods {
    autoInit(): void
  }
  interface Window {
    HSStaticMethods: HSStaticMethods
  }
}

export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.hook('page:finish', () => {
    const router = useRouter()

    watchEffect(() => {
      import('preline/preline')
    })

    watchEffect(() => {
      const _route = router.currentRoute.value

      setTimeout(() => {
        window.HSStaticMethods.autoInit()
      }, 100)

      setTimeout(() => {
        window.HSStaticMethods.autoInit()
      }, 300)

      setTimeout(() => {
        window.HSStaticMethods.autoInit()
      }, 500)

      setTimeout(() => {
        window.HSStaticMethods.autoInit()
      }, 700)

      setTimeout(() => {
        window.HSStaticMethods.autoInit()
      }, 900)

      setTimeout(() => {
        window.HSStaticMethods.autoInit()
      }, 1100)

      setTimeout(() => {
        window.HSStaticMethods.autoInit()
      }, 1300)

      setTimeout(() => {
        window.HSStaticMethods.autoInit()
      }, 1500)

      setTimeout(() => {
        window.HSStaticMethods.autoInit()
      }, 1700)

      setTimeout(() => {
        window.HSStaticMethods.autoInit()
      }, 1900)
    })
  })
})

This is very bad. Please advise.

The only thing window.HSStaticMethods.autoInit() does is that it automatically initializes the DOM elements, it doesn't do any magic, make sure that when you try to call this method, all the elements are already in the DOM and call the method. I don't understand why you need to call this method every 200 milliseconds... I don’t know the specifics of your project, but I can assume that some components are loaded after event page:finish, which means you need to run this method after this component has loaded. You can also use this method to initialize only some components, for example window.HSStaticMethods.autoInit(['dropdown']). You can use Preline components as regular classes via new, maybe this approach will work better for your case. Post your code on Codesanbox or another service for a better understanding of your case.

armenr commented 8 months ago

@olegpix - thanks for the reply. I'll see if I can find a reproduction-worthy subset of our code ( I can't post our codebase to public). But my issue is also with the fact that significant/breaking changes were made to the library without any warning or consideration of documenting what those changes are, in case something breaks for the end-user (us).

The components and code have not changed - the only thing that changed was the version of preline. When we upgraded preline, these components went from working to broken. Something underneath us changed in a significant way.

If what you're saying is that we should start using the library differently than we used to use the library, than the responsibility of informing the users/consumers of a library that the API or the fundamental usage of the library has changed...falls on the maintainers.

We have an async graphql call that loads data after the page loads. While state for that page is empty (graphql call has not returned data yet), we render skeleton loaders.

When data from the graphql call returns an array of items from our GraphQL API, we use v-if to then render the individual cards for each item in the array, and replace the skeleton loader components.

...this is a totally standard/normal way of doing things. But it seems (even with Nuxt SSR disabled), that when the data-hydrated components render, none of the preline-related JS is initialized or ready.

As a result, the only way we've been able to guarantee that Preline's JS-based interactivity will always work is by firing off the autoInit a bunch of times with setTimeout after each page:finish...which we know is totally absurd, but it's the only thing that seems to work for us, without calling autoInit everywhere, all the time.

This represents a fundamental change in the way preline behaves, or in how you expect us to use it.

Before, we'd just load the preline-dist.js file in the document head of our index.html (per a stackBlitz example provided by Preline team), and no matter what, our components and their preline-related JS would just work, no matter what.

@hiteshjoshi - Considering NuxtUI (just headless-ui + tailwind) OR Radix-Vue (and possibly shadcn-vue on top of radix-vue).

robm94 commented 8 months ago

It seems in v1 the event listeners were attached to the document. so on every click, all event listener were fired and then filtered based on if the target element had the correct class.

In v2 the the event listeners are now added directly to each element on init, so if components are rendered after page load we need to call autoInit again to add new event listeners.

Here is what I've put in my nuxt plugin it watches the <div id="page-container"> I've wrapped around the changing content for dom changes and calls the autoInit function which is debounced using perfect-debounce.

import "preline/preline"
import { type IStaticMethods } from "preline/preline"
import { debounce } from 'perfect-debounce'
declare global {
  interface Window {
    HSStaticMethods: IStaticMethods
  }
}

export default defineNuxtPlugin((nuxtApp) => {
  onNuxtReady(() => {
    const targetNode = document.getElementById("page-container")
    if (targetNode) {
      const config = { childList: true, subtree: true }
      const callback = debounce(async () => {
        window.HSStaticMethods.autoInit()
      }, 100)
      const observer = new MutationObserver(callback)
      observer.observe(targetNode, config)
    }
  })
})
hiteshjoshi commented 8 months ago

Yeah, I figured that too. I am now using Plugin.autInit(); like HSTabs.autoInit(); onMount of svelte. Closing this.

ode96-dev commented 6 months ago

I had the same issue. I resolved it by maintaining the guidelines...Preline maintains that: "projects_root_directory/app/components/PrelineScript.tsx", meaning the PrelineScript.tsx component must be housed within a components folder in the app directory.

max-programming commented 3 months ago

I used the MutationObserver liket this. I am using Blazor. Let me know if there's a better way. Or if the autoInit method is expensive

function autoInit() {
    HSStaticMethods.autoInit();

    function mutationCallback(mutationsList, observer) {
        HSStaticMethods.autoInit();
        console.log('autoInit');
    }

    /** @type {MutationObserverInit} */
    const config = {
        attributes: true,
        childList: true,
        subtree: true
    };
    const observer = new MutationObserver(mutationCallback);
    observer.observe(document.body, config);
}