Créer une application Phoenix avec LiveView

Nous allons créer une application Phoenix avec LiveView.

LiveView est utilisé pour créer des applications web avec un développement côté serveur. Nous définissons des pages avec Elixir/Phoenix/Liveview et ces pages s’affichent dans le navigateur. Chaque événement sur la page communique avec le serveur et exécute une fonction sur le serveur qui renvoie la mise à jour à faire dans la page. Le développement se fait côté serveur uniquement sans avoir à gérer le javascript de communication entre la page et le serveur.

La gestion des requêtes avec LiveView

Nous avons vu dans l’article sur la création d’une page html pour une application Phoenix que les requêtes sont habituellement traitées avec :

  • endpoint.ex : le point d’entrée de la requête
  • router.ex : qui analyse la requête et choisi comment sera traité la requête
  • _controller.ex : les contrôleurs qui exécutent les traitements et dont les noms sont composés avec le suffixe _controller.

Dans le cas de LiveView, les _controller.ex sont remplacés par des _live.ex.

La création de l’application phœnix avec l’option liveview

Pour créer une application phœnix avec les dossiers préparés pour liveview, nous n’avons plus à utiliser l’option –live puisque c’est l’option par defaut. L’option –no-live est utilisé lorsqu’on ne veut pas utiliser liveview.

  • mix phx.new app_name

Nous avons :

  • phx.new pour créer les application phœnix (phx est le diminutif pour phœnix)
  • app_name le nom de l’application. L’ensemble du projet sera créé dans le répertoire dans lequel nous nous trouvons au moment du lancement de la commande mix.
  • –live signifie que nous souhaitons créer les écrans au format liveview.

Nous pouvons voir la liste de toutes les options proposées par phx.new :

Installation de l’environnement de développement

Avant de commencer, vérifions et mettons à jour notre poste de travail si besoin.

Nous vérifions toutes les versions installées :

C:\Users\broussel>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:\Users\broussel>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:\Users\broussel>mix phx.new --version
Phoenix installer v1.7.7

C:\Users\broussel>psql --version
psql (PostgreSQL) 15.4

C:\Users\broussel>node --version
v18.17.1

C:\Users\broussel>npm --version
9.6.7


C:\Users\broussel>

Toutes les versions sont à jour.

Présentation du projet minuteur avec liveview

Nous allons créer un projet très simple : un minuteur pour les œufs à la coque. Le principe, nous décomptons toutes les secondes en partant de 3mn, jusqu’à zéro. Lorsque nous arrivons à zéro, l’alarme se déclenche sous la forme d’un message. Nous affichons le temps qui passe afin de pouvoir suivre la cuissons de nos œufs.

Nous aurons :

  • un affichage du compteur
  • un bouton start pour lancer le minuteur
  • un bouton stop pour arrêter l’alarme

Dans une application avec controller, la description de l’application se fait dans

  • lib/my_app_web/controllers/

Avec liveview, nous avons les pages dans :

  • lib/my_app_web/live/

Génération du projet Phoenix minuteur avec l’option liveview

Nous nous plaçons au préalable dans le répertoire Projets :

C:\Users\broussel>cd C:\CarbonX1\Phoenix\Projets

C:\CarbonX1\Phoenix\Projets>mix phx.new minuteur --live
* creating minuteur/config/config.exs
* creating minuteur/config/dev.exs
* creating minuteur/config/prod.exs
* creating minuteur/config/runtime.exs
* creating minuteur/config/test.exs
* creating minuteur/lib/minuteur/application.ex
* creating minuteur/lib/minuteur.ex
* creating minuteur/lib/minuteur_web/controllers/error_json.ex
* creating minuteur/lib/minuteur_web/endpoint.ex
* creating minuteur/lib/minuteur_web/router.ex
* creating minuteur/lib/minuteur_web/telemetry.ex
* creating minuteur/lib/minuteur_web.ex
* creating minuteur/mix.exs
* creating minuteur/README.md
* creating minuteur/.formatter.exs
* creating minuteur/.gitignore
* creating minuteur/test/support/conn_case.ex
* creating minuteur/test/test_helper.exs
* creating minuteur/test/minuteur_web/controllers/error_json_test.exs
* creating minuteur/lib/minuteur/repo.ex
* creating minuteur/priv/repo/migrations/.formatter.exs
* creating minuteur/priv/repo/seeds.exs
* creating minuteur/test/support/data_case.ex
* creating minuteur/lib/minuteur_web/controllers/error_html.ex
* creating minuteur/test/minuteur_web/controllers/error_html_test.exs
* creating minuteur/lib/minuteur_web/components/core_components.ex
* creating minuteur/lib/minuteur_web/controllers/page_controller.ex
* creating minuteur/lib/minuteur_web/controllers/page_html.ex
* creating minuteur/lib/minuteur_web/controllers/page_html/home.html.heex
* creating minuteur/test/minuteur_web/controllers/page_controller_test.exs
* creating minuteur/lib/minuteur_web/components/layouts/root.html.heex
* creating minuteur/lib/minuteur_web/components/layouts/app.html.heex
* creating minuteur/lib/minuteur_web/components/layouts.ex
* creating minuteur/priv/static/images/logo.svg
* creating minuteur/lib/minuteur/mailer.ex
* creating minuteur/lib/minuteur_web/gettext.ex
* creating minuteur/priv/gettext/en/LC_MESSAGES/errors.po
* creating minuteur/priv/gettext/errors.pot
* creating minuteur/priv/static/robots.txt
* creating minuteur/priv/static/favicon.ico
* creating minuteur/assets/js/app.js
* creating minuteur/assets/vendor/topbar.js
* creating minuteur/assets/css/app.css
* creating minuteur/assets/tailwind.config.js
* creating minuteur/assets/vendor/heroicons/LICENSE.md
* creating minuteur/assets/vendor/heroicons/UPGRADE.md
* extracting minuteur/assets/vendor/heroicons/optimized

Fetch and install dependencies? [Yn] Y
* running mix deps.get
* running mix assets.setup
* running mix deps.compile

We are almost there! The following steps are missing:

    $ cd minuteur

Then configure your database in config/dev.exs and run:

    $ mix ecto.create

Start your Phoenix app with:

    $ mix phx.server

You can also run your app inside IEx (Interactive Elixir) as:

    $ iex -S mix phx.server


C:\CarbonX1\Phoenix\Projets>

Nous nous plaçons dans le répertoire du projet minuteur, puis nous ouvrons le projet dans VS Code par la commande [code .] afin de vérifier les paramètres de la base de données.

C:\CarbonX1\Phoenix\Projets>cd minuteur

C:\CarbonX1\Phoenix\Projets\minuteur>code .

C:\CarbonX1\Phoenix\Projets\minuteur>

La configuration de la base de données pour le projet se trouve dans le fichier MINUTEUR/config/dev.exs :

# Configure your database
config :minuteur, Minuteur.Repo,
  username: "postgres",
  password: "postgres",
  hostname: "localhost",
  database: "minuteur_dev",
  stacktrace: true,
  show_sensitive_data_on_connection_error: true,
  pool_size: 10

Nous avons bien postgres/postgres.

Nous exécutons la création de la base de données :

C:\CarbonX1\Phoenix\Projets\minuteur>mix ecto.create
Compiling 15 files (.ex)
Generated minuteur app
The database for Minuteur.Repo has been created

C:\CarbonX1\Phoenix\Projets\minuteur>

La génération du dossier générique pour le projet Minuteur est maintenant terminée.

Création de la page minuteur

Pour créer la page minuteur, nous devons :

  • rendre la page accessible avec router.ex
  • créer la page /live/minuteur_live.ex

Déclaration de la page dans le routeur

Pour que la page minuteur soit accessible depuis le navigateur, nous devons déclarer dans router.ex le chemin URL vers /minuteur.

on déclare un lien pour L’URL /minuteur :

lib/minuteur_web/router.ex :

scope "/" MyAppWeb do
	pipe_through :browser
    
    get "/", PageController, :home
    live "/minuteur", MinuteurLive
end

Remarquez que nous n’ajoutons pas une action comme sur la ligne précédente qui contient l’action :home

Création de la page minuteur_live

Nous créons un dossier live dans minuteur_web. Ce dossier contiendra notre page minuteur_live.ex :

  • lib/minuteur_web/live/minuteur_live.ex

La page minuteur_live.ex contient 2 fonctions :

  • une fonction d’initialisation de la page : mount
  • une fonction d’affichage de la page : render

lib/minuteur_web/live/minuteur_live.ex :

defmodule MinuteurWeb.MinuteurLive do
	use MinuteurWeb, :live_view
    
    def mount(_param, _session, socket) do
    	{:ok, assign(socket, hello: :world)}
    end
    
    def render(assigns) do
    	~H"""
    	<h1>bonjour <%= @hello %></h1>
        <h2>on placera le minuteur ici</h2>
        """
    end
	
end

La fonction render utilise le paramètre assigns qui est une Map située dans socket. Assigns est utilisé pour ranger sous la form key/value les données spécifiques à la session pour l’application. Dans la fonction mount., nous avons mis le couple key/value [:hello,:world] dans socket.assigns.

Avec assign(socket, hello: :world) nous plaçons dans socket le couple key [:hello], value [:world].

La fonction render défini la page à afficher dans le navigateur. C’est du HTML, parsé avec ~H qui permet d’évaluer puis remplacer le code Elixir inscrit dans les zones délimitées par <%= @variable %>.

~H se lit « sigil H« .

Nous pouvons afficher le message et voir le résultat dans notre navigateur avec :

  • cd dossier de l’application
  • mix phx.server : pour lancer le serveur, et on peut le laisser actif.

dans le navigateur

  • http://localhost:4000/minuteur

Affichage de la page minuteur

Nous avons la page qui s’affiche bien dans notre navigateur :

Créer une application Phoenix avec Liveview#1-Page HelloWorld du projet Liveview
Créer une application Phoenix avec Liveview#1-Page HelloWorld du projet Liveview

Nous pouvons passer à l’étape suivante.

Ajout du compteur dans la page

Le compteur est effectué à partir de timer, qui dispose d’une fonction pour déclencher un évènement à intervalle régulier :

  • :timer.send_interval/3 (delais, pid, messageName)
defmodule MinuteurWeb.MinuteurLive do
	# use Phoenix.LiveView
	use MinuteurWeb, :live_view
    
    def mount(_param, _session, socket) do
    	:timer.send_interval(1000,self(),:tick)
    	{:ok, assign(socket, hello: :world, count: 180)}
    end
    
    def render(assigns) do
    	~H"""
    	<h1>bonjour <%= @hello %></h1>
        <h2>vos oeufs sont prêt dans :</h2>
        <p>décompte : <%= @count %> secondes</p>
        """
    end
	
    def handle_info(:tick, socket) do
    	{:noreply, count(socket)}
    end
    
    defp count(socket) do
    	assign(socket, count: socket.assigns.count - 1)
    end
end

A l’initialisation de la page donc dans mount(), nous ajoutons une variable count qui est notre compteur initialisé à 3mn soit 180 secondes. L’initialisation se fait dans la fonction mount.

La fonction timer.send_interval crée un évènement ici appelé :tick, qui est reçu par la fonction handle_info.

Nous créons une fonction privée count() pour recalculer la valeur de count à chaque évènement :tick. La valeur de count est récupérée dans socket.assigns qui contient les variables de l’application.

Nous voyons ici les 3 étapes :

  • préparer le travail : mount
  • faire le travail : handle_info
  • montrer le travail : render

Affichage de la page avec le compteur

Nous activons l’application :

  • cd dossier de l’application
  • mix phx.server : pour lancer le serveur

dans le navigateur

  • http://localhost:4000/minuteur

La page dans le navigateur avec le compteur :

Créer une application Phoenix avec Liveview#2-La page avec le compteur dans le projet Liveview
Créer une application Phoenix avec Liveview#2-La page avec le compteur dans le projet Liveview

Amélioration de notre minuteur qui doit s’arrêter en arrivant à zéro

Notre compteur ne s’arrête jamais. Regardons dans la documentation si :timer propose des actions pour arrêter le compteur. L’aide en ligne de iex permet d’afficher la documentation avec la fonction help ‘h module_elixir ‘. Nous quitterons iex avec System.halt.

C:\Users\broussel>iex
Erlang/OTP 26 [erts-14.0.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]

Interactive Elixir (1.15.5) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> h :timer

                                     :timer

This module provides useful functions related to time. Unless otherwise stated,
time is always measured in milliseconds. All timer functions return immediately,
regardless of work done by another process.

Successful evaluations of the timer functions give return values containing a
timer reference, denoted TRef. By using cancel/1 (stdlib:timer#cancel/1), the
returned reference can be used to cancel any requested action. A TRef is an
Erlang term, which contents must not be changed.

The time-outs are not exact, but are at least as long as requested.

Creating timers using erlang:send_after/3 (erts:erlang#send_after/3) and
erlang:start_timer/3 (erts:erlang#start_timer/3) is more efficient than using
the timers provided by this module. However, the timer module has been improved
in OTP 25, making it more efficient and less susceptible to being overloaded.
See the Timer Module section in the Efficiency Guide
(system/efficiency_guide:commoncaveats#timer-module).

## Examples

Example 1

The following example shows how to print "Hello World!" in 5 seconds:

    1> timer:apply_after(5000, io, format, ["~nHello World!~n", []]).
    {ok,TRef}
    Hello World!

Example 2

The following example shows a process performing a certain action, and if this
action is not completed within a certain limit, the process is killed:

    Pid = spawn(mod, fun, [foo, bar]),
    %% If pid is not finished in 10 seconds, kill him
    {ok, R} = timer:kill_after(timer:seconds(10), Pid),
    ...
    %% We change our mind...
    timer:cancel(R),
    ...

## Notes

A timer can always be removed by calling cancel/1 (stdlib:timer#cancel/1).

An interval timer, that is, a timer created by evaluating any of the functions
apply_interval/4 (stdlib:timer#apply_interval/4), send_interval/3
(stdlib:timer#send_interval/3), and send_interval/2
(stdlib:timer#send_interval/2) is linked to the process to which the timer
performs its task.

A one-shot timer, that is, a timer created by evaluating any of the functions
apply_after/4 (stdlib:timer#apply_after/4), send_after/3
(stdlib:timer#send_after/3), send_after/2 (stdlib:timer#send_after/2),
exit_after/3 (stdlib:timer#exit_after/3), exit_after/2
(stdlib:timer#exit_after/2), kill_after/2 (stdlib:timer#kill_after/2), and
kill_after/1 (stdlib:timer#kill_after/1) is not linked to any process. Hence,
such a timer is removed only when it reaches its time-out, or if it is
explicitly removed by a call to cancel/1 (stdlib:timer#cancel/1).

iex(2)> System.halt

C:\Users\broussel>

Pour arrêter notre compteur nous utilisons la fonction :timer.kill_after(0). Nous modifions notre code pour arrêter le comptage à 0. Cette solution ne fonctionnera pas car le superviseur relance l’application une fois le message killl reçu. Nous proposerons une deuxième version avec :timer.cancel(refTimer) qui elle fonctionnera parfaitement.

Utilisation du patern-matching pour isoler l’arrivée du compteur à zéro

Le pattern-matching sur count(socket) permet de filtrer et arrêter le compteur lorsque celui-ci est à zéro. La documentation Phoenix donne la structure de données de Phoenix.Socket.

Un article présente un guide pour l’utilisation d’assigns dans Liveview. Nous voyons en particulier comment créer le pattern-matching avec les données d’assigns :

  • socket=%{assigns: %{count: 0}}

lib/minuteur_web/live/minuteur_live.ex (version relancé par le superviseur après arrêt du timer) :

    #si on a le assigns.count à 0
    defp count(socket=%{assigns: %{count: 0}}) do
    	:timer.kill_after(0) #on arrête le timer immédiatement
    	socket #pas de modification de socket
    end
    
    # dans les autres cas
   defp count(socket) do
    	assign(socket, count: socket.assigns.count - 1)
    end

La version ci-dessus ne fonctionne pas car le superviseur relance l’application après réception du message kill. Nous allons changer de méthode. Nous devons garder la référence à notre timer ligne (5) que nous passons dans notre socket.assigns, ligne (6) ; lorsque nous souhaitons stopper le timer, nous récupérons la référence au timer, ligne (22), et nous pouvons appeler :timer.cancel, ligne (23).

lib/minuteur_web/live/minuteur_live.ex (version qui fonctionne ) :

defmodule MinuteurWeb.MinuteurLive do
	use MinuteurWeb, :live_view

    def mount(_param, _session, socket) do
      	{:ok, my_timer}  = :timer.send_interval(1000,self(),:tick)
    	{:ok, assign(socket, hello: :world, count: 180, timer: my_timer)}
    end

    def render(assigns) do
    	~H"""
    	  <h1>bonjour <%= @hello %></h1>
        <h2>vos oeufs sont prêt dans :</h2>
        <p>décompte : <%= @count %> secondes</p>
      """
    end

    def handle_info(:tick, socket) do
    	{:noreply, count(socket)}
    end

    #si on a le assigns.count à 0
    defp count(socket=%{assigns: %{count: 0, timer: my_timer}}) do
      :timer.cancel(my_timer) #on arrete le timer
      socket #pas de modification de socket
    end

    # dans les autres cas
    defp count(socket) do
      assign(socket, count: socket.assigns.count - 1)
    end
end

Le serveur est activé par :

  • cd C:\CarbonX1\Phoenix\Projets\minuteur
  • mix phx.server : pour lancer le serveur

l’application est accessible par : http://localhost:4000/minuteur

Dans la suite de cet article, nous proposons d’ajouter des actions utilisateur pour lancer le minuteur, et remettre le minuteur à zéro. Nous allons aussi améliorer l’affichage de notre minuteur grâce au pattern-matching sur les différents états du timer.

Gestion des actions utilisateurs dans la page

Ajouter les actions utilisateurs avec des boutons

Les boutons sont des actions utilisateurs. Liveview intègre les actions des utilisateurs avec des fonctions placées dans le code serveur écrit en Elixir/Phoenix.

Trois boutons sont utilisés pour faire fonctionner notre minuteur :

  • start : pour lancer le minuteur
  • reset : pour réinitialiser le compteur à 3mn
  • stop : pour arrêter le compteur

Les états correspondant à nos 3 boutons :

  • ready : lorsque le timer est prêt à être lancé
  • running : lorsque le timer est démarré
  • finished : lorsque le compteur est arrivé à zéro

L’affichage est spécialisé pour chacun des états avec des messages spécifiques :

  • ready : « mettez vos œufs dans l’eau bouillante et appuyez sur ‘start' »
  • running : « vos œufs sont prêt dans x secondes »
  • finished : « vos œufs sont prêts, retirez les de l’eau bouillante »
  • stopped : « le compteur est arrêté »

Pour afficher dans render(assigns) les bons messages, nous créons une fonction afficher(), qui filtre selon les timer_status par pattern_matching.

Le lancement du timer se fait dans start. L’initialisation est dans l’état :ready.

Code de la page du minuteur avec les actions utilisateurs

Nous avons donc :

defmodule MinuteurWeb.MinuteurLive do
	# use Phoenix.LiveView
	use MyAppWeb, :live_view
    
    def mount(_param, _session, socket) do
    	{:ok, assign(socket, hello: :world, count: 180, timer_status: :ready )}
    end
    
    def render(assigns) do
    	afficher(assigns) 
    end
    
    # ready
    def afficher(assigns=%{timer_status: :ready}) do
    	~H"""
    	<h1>bonjour <%= @hello %></h1>
        <h2>Mettez vos oeufs dans l'eau bouillante et appuyez sur 'start'</h2>
        <button phx-click="start">start</button>
        """
    end
	    
    # running
    def afficher(assigns=%{timer_status: :running}) do
    	~H"""
    	<h1>bonjour <%= @hello %></h1>
        <h2>Minuteur status : <%= @timer_status %></h2>
        <h2>vos oeufs sont prêt dans :</h2>
        <p>décompte : <%= @count %> secondes</p>
        <button phx-click="stop">stop</button>
        """
    end
    
    # stopped
    def afficher(assigns=%{timer_status: :stopped}) do
    	~H"""
    	<h1>bonjour <%= @hello %></h1>
        <p>décompte : <%= @count %> secondes</p>
        <h2>Le Minuteur a été arrété</h2>
        <h2>Réinitialisez le minuteur avec 'reset'</h2>
        <button phx-click="reset">reset</button>
        """
    end    
    
    # finished
    def afficher(assigns=%{timer_status: :finished}) do
    	~H"""
    	<h1>bonjour <%= @hello %></h1>
        <p>décompte : <%= @count %> secondes</p>
        <h2>Vos oeufs sont prêts : retirez-les de l'eau !</h2>
        <h2>Bonne dégustation</h2>
        <button phx-click="reset">reset</button>
        """
    end
    
    # affichage du cas général lorsque le status ne correspond à aucun cas ci-dessus
    def afficher(assigns) do
    	~H"""
    	<h1>bonjour <%= @hello %></h1>
        <h2>status du minuteur : <%= @timer_status %></h2>
        <button phx-click="reset">reset</button>
        """
    end
    
    # bouton reset : réinitialisation à ready
    def handle_event("reset", _metadata, socket) do
        {:ok, assign(socket, count: 180, timer_status: :ready  )}
    end
    
    # bouton start ! lancement du compteur
    def handle_event("start", _metadata, socket) do
        {:ok, my_timer} = :timer.send_interval(1000,self(),:tick)
        # IO.inspect(my_timer, label: "my_timer")
        {:ok, assign(socket, count: 180, timer_status: :running, timer: my_timer )}
    end
    
    # bouton stop : arrêt du compteur
    def handle_event("stop", _metadata, socket=%{assigns: %{timer: my_timer}}) do
    	:timer.cancel(my_timer) #on arrete le timer
        {:ok, assign(socket, timer_status: :stopped  )}
    end
    
    def handle_info(:tick, socket) do
    	{:noreply, count(socket)}
    end
    
    #si on a le assigns.count à 0
    defp count(socket=%{assigns: %{count: 0, timer: my_timer}}) do
      :timer.cancel(my_timer) #on arrete le timer
      assign(socket, timer_status: :finished) #nous passons le status à :finished
    end
    
    defp count(socket) do
    	assign(socket, count: socket.assigns.count - 1)
    end
end

Nous ajoutons dans afficher(assigns) pour chacun des états du minuteur (ligne 37 et 48), le compteur afin de bien vérifier que le minuteur est arrêté lorsque cela doit être le cas :

  • <p>décompte : <%= @count %> secondes</p>

Nous avons ajouté ligne (72) un moyen de vérifier le retour de la création du timer :

  • IO.inspect(my_timer, label: « my_timer »)

Le bouton contient phx-click= »my_event » qui déclenche un évènement phx-click qu’on retrouve dans la fonction handle_event/3 de notre module, avec « my_event » comme premier argument.

pour lancer le serveur :

  • cd C:\CarbonX1\Phoenix\Projets\minuteur
  • mix phx.server : pour activer le serveur

Autre option, remplacer :timer par Process.send_after

L’application développer ci-dessus fonctionne. Une autre façon de faire aurait été de remplacer :

  • timer.send_interval (1000, self(),:tick) par
  • Process.send_after (self(), :tick, 1000)

Et nous ajoutons une variable timer_status permettant de connaitre l’état de notre minuteur :

  • timer_status : running #minuteur en cours de comptage
  • timer_status : stopped : #minuteur à l’arrêt

Pour comprendre Process, nous devons voir qu’il s’agit de l’équivalent d’un thread en java, avec possibilité d’y accéder par message. On peut aussi demander à Process d’envoyer un message différé dans le temps, pour jouer le rôle de notre Timer : Process.send_after/4 :

  • send_after(dest, msg, time, opts \ [])

Nous pourrions donc utiliser Process.send_after pour remplacer notre Timer.

A l’initialisation, nous indiquons que timer_status est à stopped. le boutons start passe le timer_status à running et déclenche un évènement send_after (:tick). l’évenement tick est géré par handle_info comme précédemment avec count(socket) pour faire avancer le minuteur en relançant un Process.send_after (:tick). Lorsque le minuteur arrive à 0, on arrête le compteur avec timer_status : stopped.

Vous pouvez lire l’article Phoenix LiveView Stopwatch, qui propose cette solution. En outre cet article montre comment créer une horloge partagée entre plusieurs navigateurs.

Nous proposons ici le code de la version avec Process.send_after.

defmodule MinuteurWeb.MinuteurLive do
	# use Phoenix.LiveView
	use MyAppWeb, :live_view
    
    def mount(_param, _session, socket) do
    	# :timer.send_interval(1000,self(),:tick)
    	{:ok, assign(socket, hello: :world, count: 180, timer_status: :stopped  )}
    end
    
    def render(assigns) do
    	~H"""
    	<h1>bonjour <%= @hello %></h1>
        <h2>Minuteur status : <%= @timer_status %></h2>
        <h2>vos oeufs sont prêt dans :</h2>
        <p>décompte : <%= @count %> secondes</p>
        <button phx-click{:start}>start</button>
        <button phx-click{:stop}>stop</button>
        """
    end
	
    def handle_info(:tick, socket) do
        if socket.assigns.timer_status == :running do
      		Process.send_after(self(), :tick, 1000)
      		{:noreply, count(socket)}
    	else
      		{:noreply, socket}
    	end    	
    end
    
    def handle_event("start", _metadata, socket) do
    	Process.send_after(self(), :tick, 1000)
        {:ok, assign(socket, hello: :world, count: 180, timer_status: :running  )}
    end
    
    def handle_event("stop", _metadata, socket) do
        {:ok, assign(socket, hello: :world, timer_status: :stopped  )}
    end
    
    defp count(socket) do
    	assign(socket, count: socket.assigns.count - 1)
        if socket.assigns.count <= 0 do
      		assign(socket, timer_status: :stopped  )
    	end	
    end
end

Conclusion

Nous avons vu que liveview permet de créer une application web en travaillant exclusivement sur la partie Serveur en Phoenix/Elixir.

Pour gérer les évènements générés côté serveur,

  • nous utilisons la fonction handle_info.

Pöur gérer les évènements utilisateurs depuis le navigateur, nous :

  • déclarons l’évènement avec phx-click= »event«  dans les boutons par exemple,
  • et nous traitons l’évènement dans la partie serveur avec handle_event.

Nous avons terminé notre application Phoenix avec liveview. Dans un prochain article nous regarderons comment améliorer le design de l’application en utilisant des composants

Si vous avez aimé l'article vous êtes libre de le partager :-)

Laisser un commentaire