wire-elements / wire-extender

Wire Extender allows you to embed any Livewire component on any website or even within a static HTML file.
https://wire-elements.dev/blog/embed-livewire-components-using-wire-extender
MIT License
246 stars 9 forks source link

Complex SPA #10

Closed MasterZydra closed 5 months ago

MasterZydra commented 5 months ago

Hi @PhiloNL,

I want to use WireExtender to embed a more complex Laravel application into another page.

For this case I want to hook into the WireExtender:

  1. I want to call renderComponents() multiple times. The use case is a custom attribute that allows me to replace the loaded component: e.g. <a href="" component:navigate data-component="PageN" data-params='{page: 1}'>Page 1</a> Similar like the wire:navigate attribute works for Livewire, I want to add this logic to WireExtender.

  2. I need an event that tells me when Livewire is loaded and started The use case is to have an event that I can use to call my JavaScript

If you are OK with that kind of changes I would create a PR.

The changes would look like this: image

PhiloNL commented 5 months ago

I'm open for a PR, but I'm curious if you can achieve this using Livewire's initialize?

document.addEventListener('livewire:initialized', () => {
      // Runs immediately after Livewire has finished initializing
      // on the page...
})
MasterZydra commented 5 months ago

Ok. I will test the initialization. And open a PR.

MasterZydra commented 5 months ago

The event livewire:initialized worked. Thanks. I created a PR for the logic allow multiple calls for renderComponents in order to build an. "component:navigate" attribute.

14

MasterZydra commented 5 months ago

If you are interested the "component:navigate" is working 😄. It would be nice to integrate that into the WireExtender itself as a feature. (See #15)

Usage

<a href="" component:navigate data-component="MyComponent" data-params='{someParam: 42}'>Page 2</a>

component-navigate.js

// Trigger if livewire is started
document.addEventListener('livewire:initialized', (e) => {
    // If a "component:navigate" anker is clicked, load the given component
    const componentNavigateClick = (event) => {
        event.preventDefault();

        let component = event.target.getAttribute('data-component');
        let params = event.target.getAttribute('data-params');

        let livewireElem = event.target.closest("livewire");
        livewireElem.setAttribute('data-component', component);
        livewireElem.setAttribute('data-params', params ?? '');

        renderComponents([{name: component, params: params}]);
    };

    // Register click event for all elements with attribute "component:navigate"
    const componentNavigateElementUpdated = ({el}) => {
        if (el == undefined) {
            document.querySelectorAll('[component\\:navigate]').forEach(
                (elem) => elem.addEventListener('click', componentNavigateClick)
            );
            return;
        }

        if (el.hasAttribute('component:navigate')) {
            el.addEventListener('click', componentNavigateClick);
            return;
        }
    };

    // Trigger event for every livewire update cycle
    window.Livewire.hook('morph.updated', componentNavigateElementUpdated);

    // Use observer to detect if the component has been replaced
    const componentNavigateObserver = new MutationObserver( () => {
        componentNavigateElementUpdated({ undefined });
    });
    document.querySelectorAll('livewire').forEach((elem) => {
        componentNavigateObserver.observe(elem, {attributes: false, childList: true, subtree: true});
    });

    // Initial call to register click events
    componentNavigateElementUpdated({ undefined });
});
PhiloNL commented 5 months ago

Thanks for the code snippet, I'll take a look and see if something like this can be added :)