Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

proof of concept: phx-portal #3478

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft

proof of concept: phx-portal #3478

wants to merge 2 commits into from

Conversation

SteffenDE
Copy link
Collaborator

This is a proof of concept PR implementing a new phx binding called phx-portal. This binding allows to designate an element to be rendered at another location in the DOM, designated by the ID that phx-portal points to. This can be useful to render things like dialogs at the top layer, outside any containers that might affect their rendering (e.g. overflow: hidden).

Nowadays there is also the Popover API and native <dialog> elements, so this might not be that important any more.

Because of this, I also don't feel like this is something we really want to support. This PR only shows how it could be implemented.

@greven
Copy link

greven commented Oct 21, 2024

Hey @SteffenDE,

I think this is a worthy addition, even with the upcoming Dialog API. Even though the Dialog API usage is already at 95%, there are use cases where a portal is needed outside of a dialog usage, anything that we need to break out of its container layout.

Now, my only doubt is if this should be user-land, but I would be perfectly happy to have it built-in. :)

@tmjoen
Copy link
Contributor

tmjoen commented Oct 24, 2024

I would love this to simplify "forms within forms". Say for instance you have a multi-select component in a form and you want to be able to create options from within the component using another predeclared form (which is our use case). Would this work for that? That's at least how we used Vue Teleport before migrating to Live View.

@AlexKovynev
Copy link

will be great to have this in 1.0 release. This is very necessary feature with which modals can finally will be live! :)

@SteffenDE
Copy link
Collaborator Author

Don't expect to see this in the upcoming 1.0 - maybe we can add it in a later release, but that's up to @chrismccord.

@AlexKovynev
Copy link

AlexKovynev commented Nov 11, 2024

@SteffenDE it is a very useful feature which is exists in most of SPA frameworks. That why i want to see it here. But anyway i want to see 1.0 with or without it :) So many years without a release. Let it be as is but quicker :)

@Gazler
Copy link
Member

Gazler commented Nov 12, 2024

I think given that container-queries are becoming more popular (https://github.com/tailwindlabs/tailwindcss-container-queries), and will be shipped with Tailwind 4 (https://tailwindcss.com/blog/tailwindcss-v4-alpha#designed-for-the-modern-web), this is something that will become increasingly useful.

@greven
Copy link

greven commented Nov 13, 2024

Also, imagine you have a dropdown menu in a header with a fix height. It's non-trivial to make it work without changing a lot of CSS positioning on the dropdown (that you might not want to just for this particular cases) without a portal.

I actually implemented a Portal like solution in a project of mine, but it's not DOM patch aware. :P

@AlexKovynev
Copy link

@chrismccord please push it :) any problems can be fixed in 1.0.1 :) Anyway 1.0 we don't know when will be released

@SteffenDE
Copy link
Collaborator Author

If anyone wants to help out here: try building something with phx-portal, you can use this in your mix.exs:

{:phoenix_live_view, github: "phoenixframework/phoenix_live_view", branch: "sd-phx-portal-assets", override: true}

and report any problems you find. This is very much a proof of concept and needs some exploration with real world and possibly complex use cases.

@Valian
Copy link
Contributor

Valian commented Nov 23, 2024

I wanted to add my 2 use-cases:

  1. Rendering page navigation in root layout. If we do it (eg to optimize the payload on "navigate"), that part can't be updated other than by using JS. Having portals would allow to selectively update parts of the root layout without the need to send it as a whole.

  2. I'm an author of LiveVue, a library bridging LiveView and Vue. There's one unsolved challenge related to slots. Currently we support passing slots from Phoenix LiveView to Vue, but it's a hack - I'm rendering slot content on a server and sending it over the wire. So it's not using optimized diffs, hooks are not evaluated etc. In particular, it makes it impossible to nest LiveVue component inside LiveVue component It's not possible to have nested stateful LiveVue components inside a LiveVue parent component Valian/live_vue#14 because LiveVue components are initialized using Hooks:

~H"""
<.vue v-component="Card">
  <!-- this is a slot. It's rendered on the server side, but Vue won't be initialized in nested -->
  <.vue v-component="Counter">
</.vue>
"""

I have a feeling having portals would make it possible - I could "teleport" each slot to a hidden element somewhere in DOM, and on update sync it with Vue slot content. The only alternative is to patch phoenix JS library to have more control when and how HTML diff is applied, but it would be tricky 😅

@thiagomajesk
Copy link
Contributor

thiagomajesk commented Nov 27, 2024

Thanks @SteffenDE. This will beautifully address the issues I had trying to implement a portal with dynamic hooks over here: #2563. Thanks for your hard work, this is extremely useful 👍

@joshchernoff
Copy link
Contributor

Will this in a sense address this issue #2586?

@greven
Copy link

greven commented Dec 6, 2024

Hey @SteffenDE, I've tested this today in my app, seems to be all working fine!
I could easily replace my ghetto portal implementation and all worked fine. :)

@a3kov
Copy link

a3kov commented Dec 9, 2024

I would love this to simplify "forms within forms". Say for instance you have a multi-select component in a form and you want to be able to create options from within the component using another predeclared form (which is our use case). Would this work for that? That's at least how we used Vue Teleport before migrating to Live View.

Seconding this.
This will be a crucial feature for complex forms, where children components want to render their own forms, but still provide input values for the parent form.

@a3kov
Copy link

a3kov commented Dec 10, 2024

Sadly, this solution doesn't allow us to teleport nested form tags without issues, as browsers see a nested tag and kill it before JS has a chance to unnest it.
Some workarounds are possible (such as renaming the tag before the teleport and renaming back to form after) but I'm not sure if it's worth it, given that the whole "teleport" stuff is already kind of magic.

I still think the portals will be useful - you may render the child form separately and teleport some inputs back to the parent form. In a way this is another mechanism for component interaction without sending messages.

@SteffenDE
Copy link
Collaborator Author

@a3kov thank you very much for testing the form usecase! This is very helpful input. I think if we want to support something like this (portals), they should be able to solve those issues as well. I do have an idea to make it work, but this will require some more work.

@a3kov
Copy link

a3kov commented Dec 10, 2024

I think server-side portals would be a cleaner solution. We could have a portal-entrance component, and portal-exit component. Portal entrance would have a slot that is rendered inside the exit portal, by sending update to the exit component.
But implementing server-side portals doesn't mean we can't have a client-side one as well

@a3kov
Copy link

a3kov commented Dec 10, 2024

@SteffenDE By the way has there been any discussion on implementing component communication without sending actual messages ? If I understand correctly, right now rendering with updates is not atomic, i.e. if I render a button that is interacting with a modal by sending an update to the modal component, there's a moment when client sees the button but not the modal. This is relevant to the idea of server-side portals, as if I were to implement such feature, I would not want the client to see the inconsistent state where content has entered the portal but hasn't exited yet.

@a3kov
Copy link

a3kov commented Dec 11, 2024

I tried to implement server-side portal in my own project, but then realized it would be very inefficient as I would need to copy the assigns to the portal exit. This feature should be implemented inside LiveView, as it has access to all component assigns and can simply render teleported content without copying assigns.

@SteffenDE I think server side portals will be strictly superior to the client-side one, as server can do the same job efficiently without an additional load to the browser (hence why we are doing SSR in the first place), and browser will never see invalid initial state. You could even use the portals for dead views as content would be valid straight from the beginning, even search engines would see the content already teleported to new positions.
In place of sending updates that use messages behind the scenes, LV could do a multi-pass rendering and apply same updates recursively until there's no more outstanding content for teleportation. This way rendering would be completely atomic and clients would get consistent view of the data.

This is a proof of concept commit implementing a new phx binding called
`phx-portal`. This binding allows to designate an element to be rendered
at another location in the DOM, designated by the ID that `phx-portal`
points to. This can be useful to render things like dialogs at the top
layer, outside any containers that might affect their rendering (e.g.
overflow: hidden).

Nowadays there is also the Popover API and native `<dialog>` elements,
so this might not be that important any more.

Because of this, I also don't feel like this is something we really want
to support. This commit only shows how it could be implemented.
@SteffenDE SteffenDE force-pushed the sd-phx-portal branch 4 times, most recently from f782342 to 6595457 Compare December 23, 2024 16:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants