Défilement infini de la liste dans une table LiveView

La pagination est une première méthode pour présenter les listes d’item à l’utillisateur. Il existe une seconde méthode, le défilement infini de la liste dans la table LiveView.

Quel environnement est installé sur notre poste de travail ?

Nous réa&lisons le projet sur Windows. Le dossier utilisé est celui du projet meow. Commençons par nous placer dans ce dossier, puis vérifions les outils installés :

  • cd C:\CarbonX1\Phoenix\Public\meow
  • code .

Quelles sont les versions disponibles :

  • elixir -v ‘version d’elixir
  • mix local.hex ‘mise à jour des outils hex
  • mix archive.install hex phx_new ‘mise à jour de phoenix
  • psql -V ‘version de postgres
C:\CarbonX1\Phoenix\Public\meow>elixir -v
Erlang/OTP 26 [erts-14.0.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]

Elixir 1.15.5 (compiled with Erlang/OTP 26)

C:\CarbonX1\Phoenix\Public\meow>mix local.hex
Found existing entry: c:/Users/broussel/.mix/archives/hex-2.0.6
Are you sure you want to replace it with "https://builds.hex.pm/installs/1.14.0/hex-2.0.6.ez"? [Yn] n

C:\CarbonX1\Phoenix\Public\meow>mix archive.install hex phx_new
Resolving Hex dependencies...
Resolution completed in 0.083s
New:
  phx_new 1.7.10
* Getting phx_new (Hex package)
All dependencies are up to date
Compiling 11 files (.ex)
Generated phx_new app
Generated archive "phx_new-1.7.10.ez" with MIX_ENV=prod
Found existing entry: c:/Users/broussel/.mix/archives/phx_new-1.7.10
Are you sure you want to replace it with "phx_new-1.7.10.ez"? [Yn] n

C:\CarbonX1\Phoenix\Public\meow>psql -V
psql (PostgreSQL) 16.1

C:\CarbonX1\Phoenix\Public\meow>

Préparation pour le défilement infini de la liste dans la table LiveView

Le défilement infini permet de chargfer les donneées de la base de onnées en bloc sans avoir besoin d’intervention de l’utilisateur. La liste se charge lorsque l’utilisateur arrive dans la fin de la liste aussi bien en bas de dliste qu’en haut de liste. Ce sont ces eveenements qui déclenche le chargement.

Du point de vue technique vis à vis de la base de données, cela permet d’éviter la manipulation d’un trop gros volumen de données, ce qui aurait pour conséquence de ralentir l’application.

Du point de vue de l’utilisateur, cela permet de voir la liste sans intervention particulière, grâce a des évènements declenchés automatiquement en fin de liste.

Le défilement infini de la liste dans une table LiveView est à la fois un confort pour l’utilisateur et une solution technique élégante.

L’utilisation du défilement infini est réalisé dans une table sans trie ni filtre activable par l’utilisateur.

Défilement infini pour la liste LiveView

Modification du contexte du projet

DAns le fichier Context du projet, nous ajoutons deux fonctions :

  • meerkat_count ‘pour avoir le nombre d’item total
  • list_meerkats_with_pagination ‘pour obtenir les items d’un bloc
	def meerkat_count(), do: Repo.aggregate(Meerkat, :count)
	
	def list_meerkats_with_pagination(offset, limit) do
		from(m in Meerkat)
		|> limit(^limit)
		|> offset(^offset)
		|> Repo.all()
	end

La fonction meerkat_count va nous permettre de savoir lorsque nous avosn atteind la fin de la liste dans notre défilement infini de la liste de la table LiveView.

La fonction list_meerkats_with_pagination utilise deiux parametre, offset pour indiquer le début du bloc d’item à choisir et limit pour donner le nombre d’item à prendre dnas le bloc.

Création de la vue LiveView pour defilement infini de la liste

Nous créons un vue LiveView qui contient la génération de la partie html composée d’une table affichant une ligne par meerkat avec son id et son name :

defmodule MeowWeb.InfinityLive do
  use MeowWeb, :live_view
  
  alaias Meow.Meerkats
  
  def render(assigns) do
    ~H"""
      <table>
        <tbody  id="meerkats"
                phx-update="append"
                phx-hook="InfinityScroll"
        >
          <%= for meerkat <- @meerkats do %>
            <tr id={"meerkat-#{meerkat.id}"}>
            <td><%= meerkat.id %></td>
            <td><%= meerkat.name %></td>
            </tr>
          <% end %>
        </tbody>
      </table>
    """
  end
  
end

Ce qui caractérise cette table ce sont les deux tag :

  • phx-update ‘parametre sur la façon de mettre à jour la table lorsque les données ont changées dans la variable @meerkats (par defaut c’est replace et non append)
  • phx-hook ‘défini l’évenement à déclencher lorsque l’utilisateur arrive en bas de la liste

Quelles sont les compôrtement possibles avec les valeurs de phx-update :

  • ignore ‘ne pas faire de mise à jour automatique lorsque les données change, permet une gestion par javascript des changements de la table
  • prepend ‘ajoute les element au debut de la table et non à la fin
  • append ‘ajoute les ellements à la fin de la table
  • replace ‘remplace toute la table avec les nouveaux elements

Avec append et prepend, chaque ligne de la table doit disposer d’un identifiant unique afin de ne pas créer de doublon.

Initialisation de la liste

La fonction mount initialise la liste dans InfinityLive. Nous accédons à la base de données à travers le contexte Meerkats pour :

  • connaitre le nombre d’item avec meerkat_count,
  • charger le premier lot d’item grâce à list_meerkats_with_pagination.

Pour simplifier le code, nous avons au début de InfinityLive défini l’alias Meow.Meerkats.

La fonction mount défini les valeurs initialle avec assign(socket, offset: 0, limit: 25, count: count) en ligne 6 ci-dessous. Rappel : le pipe ‘|>‘ ajoute le résultat de la fonction précédente en premier argument de la fonction suivante.

  def mount(_params, _session, socket) do
    count = Meerkats.meerkat_count()
    
    socket = 
      socket
      |> assign(offset: 0, limit: 25, count: count)
      |> load_meerkats()
      
    {:ok, socket, temporary_assigns: [meerkats: []]}
  end

  defp load_meerkats(socket) do
    %{offset: offset, limit: limit} = socket.assigns
    meerkats = Meerkats.list_meerkats_with_pagination(offset, limit)
    assign(socket, :meerkats, meerkats)
  end

La ligne 9 ci-dessus indique temporary_assigns: [meerkats: []]. Cette option permet à Elixir de vider la variable @meerkats après l’affichage avec LiveView. Cela évite de surcharger la mémoire du serveur. Nous pouvons aussi jouer sur la variable limit qui donne le nombre d’item de chaque lot. Si le nombre d’item est trop faible la page va demander trop souvent le lot suivant. Ajuster la valeur de limit se fait par essai-erreur grâce à des outils comme : wrt, k6, Jmeter.

Le navigateur déclenche les chargements de la table grâce aux hoock

Les évènement déclenchant le chargement du lot suivant proviennent du navigateur. LiveView permet de créer des évènement à partir de code javascript installés dans la page. Ce sont les hooks. L’évènement utilise le websocket pour communiquer avec le serveur.

Pour gérer la demande de chargement, nous créons l’évènement load_more. Nous créons dans la page InfinityLive, la fonction handle_event pour prendre en charge cet évènement :

  def handle_event("load-more", _params, socket) do
    %{offset: offset, limit: limit, count: count} = socket.assigns
    
    socket = 
      if offset < count do
        socket
        |> assign(offset: offset + limit)
        |> load_meerkats()
      else
        socket
      end
      
      {:noreply, socket}
  end

LE traitement de l’évènement est assez classique. Nous prenons les parametres dans le socket.assigns. Si il reste des items à afficher, nous modifions le pointeur de début de lot offset, et demandons l’affichage du lot ainsi défini.

Mise en place du défilement infini pour la liste dans la table LiveView

Nous devons maintenant déclarée la route pour pouvoir accéder à notre page :

MEOW/lib/meow_web/router.ex :

defmodule MeowWeb.Router do
  use MeowWeb, :router

  pipeline :browser do
    plug(:accepts, ["html"])
    plug(:fetch_session)
    plug(:fetch_live_flash)
    plug(:put_root_layout, {MeowWeb.LayoutView, :root})
    plug(:protect_from_forgery)
    plug(:put_secure_browser_headers)
  end

  pipeline :api do
    plug(:accepts, ["json"])
  end

  scope "/", MeowWeb do
    pipe_through(:browser)
    live("/infinity", InfinityLive)
    live("/", MeerkatLive)
  end
end

La page InfinityLive est accessible par l’url :

  • http://localhost:4000/infinity

Démarrons le serveur et vérifions le fonctionnement de notre page :

  • cd C:\CarbonX1\Phoenix\Public\meow
  • mix phx.server
  • http://localhost:4000/infinity

Nous avons bien la table qui apparait avec le premier jeux de données initial. Nous devons maintenant ajouter la partie javascript dans la page. C’est l’évènement load_more qui demande au serveur d’envoyer les données suivantes.

Mettre en place dans la page le hook client en javascript

La communication du navigateur vers le serveur suppose la mise en place d’un code projet en javascript.

Pour mettre en place un hoock nous devons :

  • définir le hook
  • ajouter ce hook au LiveSocket
  • ajouter le phx-hook tag dans la page pour envoyer une action au serveur

Nous créons le fichier javascript.

MEOW/assets/js/infinity-scroll.js :

export default{
    rootElement(){
        return (
            document.documentElement || document.body.parentNode || document.body
        );
    },
    scrollPosition(){
        const {scrollTop, clientHeight, scrollHeight} = this.rootElement();
        return ( (scrollTop + clientHeight ) / scrollHeight ) * 100;
    },
    mounted(){
        this.threshold = 90;
        this.lastScrollPosition = 0;

        window.addEventListener(
            "scroll", () => {
                const currentScrollPosition = this.scrollPosition();

                const isCloseToBottom =
                    currentScrollPosition > this.threshold 
                    this.lastScrollPosition <= this.threshold ;

                if (isCloseToBottom) this.pushEvent ("load-more", {});

                this.lastScrollPosition = currentScrollPosition;
            }
        );
    }
}

Le principe est le suivant :

on défin deux fonctions :

  • prendre l’élement html de la fenetre avec rootElement()
  • obtenir la position courante de l’ascenceur avec scrollPosition()

Puis on ajoute l’évènement sur « scroll » à la fenêtre window pour calculer le déclenchement de l’évènement phx-hook appelé « load_more » à envoyer au serveur. Le serveur va recevoir cet evènement et va raffraichir la liste des items dans la table LiveView.

Relançons le serveur, malheureusement cela ne fonctionne pas. Nous avons l’erreur :

  • unknown hook found for « InfinityScroll »
  • <tboby id= »meerkats » phx-update= »append » phx-hook= »InfinityScroll »>…</tbody>

La solution consiste à ne pas oublier de déclarer notre hook dans le fichier app.js :

MEOW/assets/js/app.js :

// The below line isn't mentioned in the book but needed in Chapter 5
import InfinityScroll from "./infinity-scroll";

let Hooks = {};
// The below line isn't mentioned in the book but needed in Chapter 5
Hooks.InfinityScroll = InfinityScroll;
  • cd C:\CarbonX1\Phoenix\Public\meow
  • mix phx.server
  • http://localhost:4000/infinity
Défilement infini de la liste dans une table LiveView#1-Page avec le défilement et le chargement du javascript InfiniteScroll hook
Défilement infini de la liste dans une table LiveView#1-Page avec le défilement et le chargement du javascript InfiniteScroll hook

Conclusion, le défilement infini fonctionne

Nous avons bien le défilement infini en place pour une liste d’item dans une table LiveView.

Pour ce faire, nous avons utilisé le Hook qui permet de créer la communication entre le navigateur et le serveur. Cela suppose de créer le javascript dans un fichier dédié au nom du hook, et de rajouter 2 lignes dans le fichier app.js pour permettre le téléchargement du code inifity_scroll.js.

l’évenement est traité au niveau de la page inifinty_live.ex sur le serveur avec un évenement géré par la fonction handle_event(« load-more », _params, socket), qui va demander le chargement des données.

Pour créer cette communication navigateur vers serveur nous avons:

  • créer le code javascript infinity-scroll.js
  • déclaré le code javascript dans app.js
  • ajouter l’évènement dans le html de la page infinity_live.ex avec phx-hook
  • créer le handle_event dans la page infinity_live.ex
Si vous avez aimé l'article vous êtes libre de le partager :-)

Laisser un commentaire