swup / fragment-plugin

A swup plugin for dynamically replacing containers based on rules 🧩
https://swup-fragment-plugin.netlify.app
MIT License
15 stars 1 forks source link

[Feature Request]: matching rules by name #66

Open lubomirblazekcz opened 8 months ago

lubomirblazekcz commented 8 months ago

Describe the problem 🧐

Currently it's possible to define rules only by path, I think this is kind of tricky for lang mutations or url can be changed via cms etc.

Describe the propsed solution 😎

Definition by data attributes to match name of the rule, eg. data-swup-route-name="my-route" for routes, or data-swup-fragment-name="my-fragment-rule" for fragments

Alternatives considered 🤔

I am already using this via link:click hook for the RouteNamePlugin, but I don't think it's possible to do similiar to fragments, or am I wrong?

    if (event.target.dataset.swupRoute) {
        visit.to.route = event.target.dataset.swupRoute
    }

How important is this feature to you? 🧭

Nice to have

Checked all these? 📚

daun commented 8 months ago

Right, that can get tricky. In current projects, I'm handling this by sharing those routes between the backend and frontend. Note that this is just the final output as they're actually auto-generated from the current slugs of pages as defined in the backend.

<script>
    window.__routes__ = {
      'home': ['/de', '/en', '/fr'],
      'project-list': ['de/projects', 'en/projects', 'fr/projects'],
      'project': ['de/project/:slug', 'en/project/:slug', 'fr/project/:slug']
    };
</script>

Your solution should work for the route name plugin, yes, but indeed the fragment plugin currently requires actual route definitions to work properly. There was a discussion about other ways of selecting fragment rules, but routes came out on top 🤠

daun commented 8 months ago

@hirasso Do you remember the actual reasoning behind going for routes-only for the fragments instead of dom attributes? I remember our first trials were with dom attributes but I don't remember where exactly that approach broke down...

lubomirblazekcz commented 8 months ago

@daun yeah your solution came in mind too, but I found the data-swup-route attribute much easier to use for my use case (I am using it to append skeleton animations before loading the new page).

I figured that fragments would be trickier, but current solution adds a lot overhead to configuring if the route name is dynamic.

My idea was that upon navigation with data-swup-fragment-name="my-fragment-rule" or similiar swup detects if that fragment with that rule name is on the page, and if so it would apply it on the rule containers.

hirasso commented 8 months ago

Yes, at first we tried to use dom attributes - similar to what hotwire/turbo is doing with frames. But that proved to be problematic for history and programmatic navigation using swup.navigate(). During these visits, it's unclear what fragment rules should be applied, as the visit wouldn't be triggered by a link (or form).

lubomirblazekcz commented 8 months ago

I see. Can the fragment name be saved into state object in history? And programmatic navigation would have to be necessary manually as an option to work. I think the best solution would be have both approaches possible, and it would depend on the situation what would be more suitable to use.

hirasso commented 8 months ago

We are very open to contributions and additional brain power to improve fragment plugin! I'm personally a bit limited in resources these days, but would happily review and test-drive a PR for this feature @lubomirblazekcz .

hirasso commented 8 months ago

I'm starting to remember more problems with the attribute-based approach. If you go to the fragment plugin demo site, then execute the following navigation:

  1. Navigate to "Characters":

CleanShot 2023-12-06 at 14 05 21@2x

  1. click on one of the filters, for example "Princesses":

CleanShot 2023-12-06 at 14 05 58@2x

  1. click on one of the characters, for example "Princess Daisy":

CleanShot 2023-12-06 at 14 07 01@2x

  1. On the overlay, there are links back to the filters:

CleanShot 2023-12-06 at 14 01 16@2x

Something like this would be impossible to control using attributes, as on the server there is no way of knowing in what context the overlay would be rendered.

lubomirblazekcz commented 8 months ago

@hirasso right now I am limited too, but once I have more time I could take a look at this and do a PR. 👍

Regarding your example, I think it could be possible - I would add an attribute to links in filter with a close-character name (only if the filers are in modal, otherwise it would be characters-list), that would match the correct rule and replace the containers. But that's my simplified version, I might be missing something.

hirasso commented 8 months ago

Actually, that sounds very interesting! Looking forward to what you come up with!

hirasso commented 8 months ago

Another thing to keep in mind would be multiple history steps at once. You know, when you right-click on the browser's back button and jump multiple visits back in history.

daun commented 8 months ago

@hirasso Right, I remember now. That was our nemesis -- history visits which skip intermediate history entries.

@lubomirblazekcz Swup (and fragment plugin) by default start the out animation while the new page is still loading, mainly for perceived performance. Awaiting the new page html to determine the type of visit should work, but at the cost of user experience. Unless such a feature was optional, of course.

Looking forward to what you come up with! Let us know if you need a helping hand along the way.

hirasso commented 8 months ago

Actually, I never thought of the idea to define rules with names beforehand, and then reference these rules in dom attributes. Combined with a mechanism to store the matched rule (if any) in the history state this all sounds quite promising to me.

lubomirblazekcz commented 8 months ago

@daun I don't think it's needed to wait for new page load, you know beforehand which rules to apply and which containers to replace from the name in the attribute

@hirasso yeah that's the idea

daun commented 8 months ago

@lubomirblazekcz Even better then. Looks like I misread one of your previous comments to that effect.

daun commented 8 months ago

@lubomirblazekcz Just making sure — could the first of your scenarious be covered by swup's built-in animation attribute? Adding data-swup-animation to a link will add a class name to the html element with that value. You can use that to act as a route name:

<a href="/" data-swup-animation="my-route">

Will produce these classnames during the animation:

<html class="is-changing is-animating to-my-route">
hirasso commented 8 months ago

One more consideration why we went with routes for fragment rules: on larger websites you might not always be fully in control of your link's markup (think content from WYSIWYG editors, for example). With attributes, there could be considerable overhead be involved in making sure every last link has the necessary attributes. Not unsolvable of course, but a bit tricky.

lubomirblazekcz commented 8 months ago

@daun that sounds good, but the name is not included in visit.to, but if it would that would work great

Here is an example how am I handling the loading skeletons, I might do a plugin out of this at some time. I'm doing a php eshop project now with Swup and it looks really cool, just like SPA. Swup got really powerful in these last versions, so great work guys!

const routeSkeleton = async name => (await import('../Utils/skeletons.js'))[name]

LibSwup.hooks.on('animation:out:end', async (visit) => {
    const { route } = visit.to
    const fragments = visit?.fragmentVisit?.containers

    if (fragments) {
        for (const container in fragments) {
            const skeleton = await routeSkeleton(fragments[container].replace('#swup-', ''))

            if (skeleton) {
                document.querySelector(fragments[container]).innerHTML = skeleton
                document.querySelector(fragments[container]).style.opacity = '1'
            }
        }
    }

    if (document.documentElement.classList.contains('is-leaving')) {
        document.getElementById('l-main').innerHTML = await routeSkeleton(route ?? 'any')
        document.getElementById('l-main').style.opacity = '1'
    }

    if (!window.location.hash && visit.scroll.reset && !fragments) {
        document.documentElement.scroll({ top: 0, behavior: 'instant' })
    }
})

@hirasso yeah true, that make sense

daun commented 8 months ago

@lubomirblazekcz You'll find the animation name in visit.animation.name :)

Your use case is super fascinating! i would love to see a plugin for that kind of thing. These things always seemed a bit far off in Vue and React land, but you're right that with swup 4 there's a lot more to customize and extend.

lubomirblazekcz commented 8 months ago

@daun great, that's it thanks!

lubomirblazekcz commented 8 months ago

Here is a quick example of the skeletons in action, so yeah definitely doing a plugin when I got the time! There is a longer delay for testing purposes, the idea though is to show the skeleton only if the navigation is slow, if it's fast swup replaces the content and normal animation is shown.

https://github.com/swup/swup/assets/6872956/370c0623-551f-4e2e-bf1c-61b222064630

daun commented 8 months ago

I'll move this request to the Fragment Plugin as it looks like the first use case is solved by visit.animation.name.