zporter

zporter

I have a LiveView page in which I would like to render a stream of items differently depending on viewport size. Currently, I use Tailwind classes like hidden and lg:inline-table to render a <table> on wider viewports and lg:hidden and block to render a list of cards on narrow viewports. In order to render both lists within the same DOM, I need to give each element a unique identifier. This requires me to setup two streams pointed to the same data source and configured to generate DOM identifiers differently: stream(:rows) and stream(:cards).

Using two streams in this way works well until I want to use the InfiniteScroll hook. On LiveView version 0.19.3, using the phx-viewport-bottom attribute on both of the stream elements results in triggering more rows to be added as soon as a scroll action is detected. This is due to the hidden list element’s last child being higher up in the viewport. A small change to the onScroll event handler in the InfiniteScroll hook to exit early if the element is hidden (if (this.el.offsetParent === null) { return };) fixes the scroll behavior for the page and items aren’t loaded until the last child of the visible element is on the screen.

Have others run into similar issues? Does the proposed solution make sense or should I be considering other options? I’m happy to open a Pull Request with the aforementioned change, but I wanted to solicit ideas here first. Thank you!

Showing Posts 1 to 2

jason.o

jason.o

I know this is an old post but I am implementing something very similar, and my solution is basically rendering the table or the card view depending on the screen size. (e.g., <table :if={@screen_layout == :desktop} />) It’s not perfect, but works decently.

byhemechi

byhemechi

Just for anyone landing on this thread from search i figured i’d put this here

Instead of using phx-viewport-bottom you could use an Intersection Observer positioned after the streamed elements.

What I usually do is create a custom element that “clicks” a child button once it is in view. Here’s an example of how that could be implemented

class InfiniteScrollTrigger extends HTMLElement {
  observer = new IntersectionObserver(([entry]) => this.trigger(entry), {
    threshold: 1,
  });

  trigger(entry) {
    this.querySelector("button")?.click();
  }

  connectedCallback() {
    this.observer.observe(this);
    this.style.display = "contents";
  }

  disconnectedCallback() {
    this.observer.disconnect();
  }
}


customElements.define("infinite-scroll-trigger", InfiniteScrollTrigger);
  <infinite-scroll-trigger event="load_more" value={@cursor}>
    <.button phx-click="load_more" value={@cursor} phx-disable-with="Loading...">
      Load more
    </.button>
  </infinite-scroll-trigger>

Alternatively, you could make a hook which you apply to a div or something.
I prefer the wrapper method as it makes loading state much simpler, you just use the built in phx attributes


hooks.InfiniteScrollTrigger = {
  mounted() {
    this.observer = new IntersectionObserver(([{ isIntersecting }]) => {
      if (!this.el.hasAttribute("disabled")) {
        this.pushEvent(this.el.getAttribute("phx-intersect"), {
          value: this.el.getAttribute("value"),
        });
      }
    });
    this.observer.observe(this.el);
  },
  beforeDestroy() {
    this.obsever.disconnect();
  },
};
— All posts loaded —

Where Next? Top

Trending in Questions Top

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
kszambelanczyk
Hello! Could someone please give me a help/sample code, how to delete a file from s3 using waffle/waffle_ecto from Phoenix app. I creat...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New
FlyingNoodle
If a change or preparation module uses Ash.Changeset.get_argument/2 or Ash.Query.get_argument/2 (or any of the other get_argument functio...
New
ryanwinchester
apply_graft/2 doesn’t rewrite an add_many sub-workflow’s deps on an add step. Grafted jobs cancel with “upstream job was deleted” Version...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews