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

Allow nesting of phx-update="ignore" and phx-update="replace" within a single LiveView #2802

Closed
orsenkucher opened this issue Sep 1, 2023 · 5 comments

Comments

@orsenkucher
Copy link

Environment

  • Phoenix version: phoenix 1.7.7, phoenix_live_view 0.19.5
  • Elixir version: Elixir 1.15.2 (compiled with Erlang/OTP 26)
  • NodeJS version: v20.3.1

Issue

We are integrating raw HTML, CSS, and JS exported from Webflow into our Phoenix application. This allows us to utilize designs, animations, and form structures directly from the Webflow export by adding them into the priv/static folder. We subsequently convert specific pages into LiveViews to populate with data from our backend.

However, when a page is converted to a LiveView, Phoenix LiveView takes over DOM diffing, causing the original JS functionality from the Webflow export to be bypassed. This affects our animations and other JS-based functionalities. To address this, we've been using a combination of phx-update="ignore" to preserve animations and JS behaviors, and phx-update="replace" by creating nested LiveViews and communicating between them using the send function.

It would significantly simplify our integration process if we could directly nest phx-update="ignore" and phx-update="replace" within a single LiveView. This would allow us to selectively disable and enable DOM diffing in specific regions of the DOM as per our requirements.

Expected behavior

Allow the nesting of phx-update="ignore" and phx-update="replace" directives within a single LiveView, giving developers more granular control over DOM diffing on a per-region basis.

Actual behavior

Currently, we need to use nested LiveViews to achieve this functionality, adding unnecessary complexity to our codebase.

@orsenkucher
Copy link
Author

Additionally, while searching for solutions to integrate LiveView with JS logic-rich pages, I came across a dom.js workaround that provides a way to preserve specific attributes of an element throughout DOM patches:

export const morphdomOptions = {
  onBeforeElUpdated(from, to) {
    // Keep element attributes starting with data-js-
    // which we set on the client.
    for (const attr of from.attributes) {
      if (attr.name.startsWith("data-js-")) {
        to.setAttribute(attr.name, attr.value);
      }

      if (attr.name === "data-keep-attribute") {
        if (from.hasAttribute(attr.value)) {
          to.setAttribute(attr.value, from.getAttribute(attr.value));
        } else {
          to.removeAttribute(attr.value);
        }
      }
    }
  },

  onNodeAdded(node) {
    // Mimic autofocus for dynamically inserted elements
    if (node.nodeType === Node.ELEMENT_NODE && node.hasAttribute("autofocus")) {
      node.focus();

      if (node.setSelectionRange && node.value) {
        const lastIndex = node.value.length;
        node.setSelectionRange(lastIndex, lastIndex);
      }
    }
  },
};

While this approach helps in certain scenarios, especially for preserving specific attributes and mimicking autofocus, I'd be interested in recommendations from the team:

  1. Is this a recommended approach for handling JS-rich interactions with LiveView?
  2. Are there potential pitfalls with this workaround that we should be aware of?
  3. How can such methods be further optimized or integrated into the core LiveView functionality to make JS integration smoother?

Looking forward to insights and suggestions.

@Gazler Gazler transferred this issue from phoenixframework/phoenix Sep 12, 2023
@josevalim
Copy link
Member

We have no plans to support this at the moment because it opens up a pandora box of complexity, as we now have to perform diffs anywhere in the page. :(

What you could try is to use a separate LiveViews. For example, you render a LiveView, then you phx-update=ignore some content, and inside that content, you live_render another LiveView. This will allow you to mix and match LiveViews with more control.

I came across a dom.js workaround that provides a way to preserve specific attributes of an element throughout DOM patches:

Yup, this is very common (at least in my apps). We have discussed providing something along those lines out of the box but we never officially taken any action because some additional handling is always required anyway (like your autofocus feature).

In any case, closing this as unplanned, thank you. :)

@josevalim josevalim closed this as not planned Won't fix, can't repro, duplicate, stale Sep 12, 2023
@orsenkucher
Copy link
Author

Hey @josevalim,

Thank you, I truly appreciate the response directly from elixir's father :)

I've set up a minimal reproducible example that illustrates the redirect to the /null route issue when using phx-update="ignore" on parent LV with nested child LiveViews. It's a peculiar behavior after a server restart, which could be problematic especially in production environments 💀

For a quick code overview, you can check out the commit diff.

I'd greatly appreciate any help on this further. I think making Phoenix work seamlessly with static site generators have possibility to boost development process for many teams.

Got your point about the dom.js workaround too. Though it can be tricky to pinpoint every attribute that must be preserved and it seem not to work for attached callback listeners.

Can say that making LV sticky: true fixes this problem, but introduces another problem of using sticky component after all.

Lastly, here are a few lines of related logs from the browser:

error: unauthorized live_redirect. Falling back to page request {reason: 'stale'}
socket: disconnected for page nav

@josevalim
Copy link
Member

Hi @orsenkucher, can you please open up a bug report with the issue and the link to the app? Thank you! The tokens are indeed stale but it should not redirect to null. :)

@orsenkucher
Copy link
Author

@josevalim yep, sure!
Done Issue #2806.

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

No branches or pull requests

2 participants