Trier la liste d’une table LiveView

Nous souhaitons voir comment trier la liste des données d’une vue table LiveView affichant le contenu d’une base de données. Pour ce faire, nous allons lire et tester l’application proposée par Peter Ullrich dans son ouvrage : Building Table Views with Phoenix LiveView.

Trier la liste d une table liveview#1-Peter Ullrich Building Table View
Trier la liste d une table liveview#1-Peter Ullrich Building Table View

Nous allons suivre pas à pas ce qui est demandé afin de bien comprendre les techniques proposées et pouvoir les appliquer dans nos propre projets.

Concernant la pagination nous pourrons consulter un article de fullstackphoenix qui utilise la librairie scrivener_ecto.

Définition des objectifs : trier la liste d’une table LiveView.

L’objectif est de pouvoir trier la liste d’une table LiveView qui affiche les données provenant de la base de données. Nous souhaitons pouvoir filtrer, trier et créer une pagination de ces listes. Dans cet article, nous verrons la mise en place du projet et la réalisation du trie.

La réalisation se fera avec des LiveComponents et Ecto connecté à la base de données PostgreSQL.

Nous avons un lien pour télécharger le dossier de l’application depuis github :

Nous avons aussi la possibilité de télécharger la version initiale permettant d’ajouter au fur et à mesure de la lecture de l’ouvrage les développements réalisés :

  • https://github.com/PJUllrich/pragprog-book-tables/blob/main/pragprog-book-tables-starter-app.zip

Par un double-click, nous arrivons sur le dossier Github qui permet de télécharger le fichier zip que nous recopions du dossier téléchargement vers le dossier :

  • C:\CarbonX1\Phoenix\Public\

Après avoir renommé le zip du nom du projet : meow.zip, nous installons le projet dans le dossier par une extraction du fichier zip. Nous déplaçons le contenu dans le dossier pour avoir l’arborescence du projet dans :

  • C:\CarbonX1\Phoenix\Public\meow
Trier la liste  d'une table  LiveView#1-installation du projet meow
Trier la liste d’une table LiveView#1-installation du projet meow

Nous pouvons maintenant explorer le projet dans son état initial.

Découverte du projet Meow : la vue en mode liste

Nous commençons par ouvrir le projet dans VS Code. Vous pouvez consulter notre article qui explique comment installer le poste Windows avec VS Code Elixir et Phoenix.

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

La vue du projet avec le fichier lib/meow/meerkats.ex :

Trier la liste  d'une table  LiveView#2-projet initial meow
Trier la liste d’une table LiveView#2-projet initial meow

Nous pouvons executer le projet selon les instructions données dans le readme sur github :

  • installer les dépendances avec : mix deps.get
  • créer et migrer la base de données : mix ecto.setup
  • démarrer Phoenix : mix phx.server
  • visiter l’application sur : http://localhost:4000/

Nous chargeons les dépendances :

C:\CarbonX1\Phoenix\Public\meow>mix deps.get
Resolving Hex dependencies...
Resolution completed in 0.182s
Unchanged:
  castore 0.1.13
  connection 1.1.0
  cowboy 2.9.0
  cowboy_telemetry 0.4.0
  cowlib 2.11.0
  db_connection 2.4.1
  decimal 2.0.0
  ecto 3.7.1
  ecto_sql 3.7.1
  esbuild 0.4.0
  file_system 0.2.10
  floki 0.32.0
  html_entities 0.5.2
  jason 1.2.2
  mime 2.0.2
  phoenix 1.6.2
  phoenix_ecto 4.4.0
  phoenix_html 3.1.0
  phoenix_live_reload 1.3.3
  phoenix_live_view 0.17.5
  phoenix_pubsub 2.0.0
  phoenix_view 1.0.0
  plug 1.12.1
  plug_cowboy 2.5.2
  plug_crypto 1.2.2
  postgrex 0.15.13
  ranch 1.8.0
  telemetry 1.0.0
  telemetry_metrics 0.6.1
  telemetry_poller 1.0.0
* Getting phoenix (Hex package)
* Getting phoenix_ecto (Hex package)
* Getting ecto_sql (Hex package)
* Getting postgrex (Hex package)
* Getting phoenix_html (Hex package)
* Getting phoenix_live_reload (Hex package)
* Getting phoenix_live_view (Hex package)
* Getting floki (Hex package)
* Getting esbuild (Hex package)
* Getting telemetry_metrics (Hex package)
* Getting telemetry_poller (Hex package)
* Getting jason (Hex package)
* Getting plug_cowboy (Hex package)
* Getting cowboy (Hex package)
* Getting cowboy_telemetry (Hex package)
* Getting plug (Hex package)
* Getting mime (Hex package)
* Getting plug_crypto (Hex package)
* Getting telemetry (Hex package)
* Getting cowlib (Hex package)
* Getting ranch (Hex package)
* Getting castore (Hex package)
* Getting html_entities (Hex package)
* Getting file_system (Hex package)
* Getting connection (Hex package)
* Getting db_connection (Hex package)
* Getting decimal (Hex package)
* Getting ecto (Hex package)
* Getting phoenix_pubsub (Hex package)
* Getting phoenix_view (Hex package)
You have added/upgraded packages you could sponsor, run `mix hex.sponsor` to learn more

Création de la base de données

Une erreur s’affiche lors de la génération de la base de données :

C:\CarbonX1\Phoenix\Public\meow>mix ecto.setup
==> file_system
Compiling 7 files (.ex)
Generated file_system app
==> connection
Compiling 1 file (.ex)
Generated connection app
==> decimal
Compiling 4 files (.ex)
Generated decimal app
==> mime
Compiling 1 file (.ex)
Generated mime app
===> Analyzing applications...
===> Compiling telemetry
==> telemetry_metrics
Compiling 7 files (.ex)
Generated telemetry_metrics app
===> Analyzing applications...
===> Compiling telemetry_poller
==> jason
Compiling 8 files (.ex)
Generated jason app
==> db_connection
Compiling 14 files (.ex)
Generated db_connection app
==> phoenix_pubsub
Compiling 11 files (.ex)
Generated phoenix_pubsub app
==> plug_crypto
Compiling 5 files (.ex)
warning: use Bitwise is deprecated. import Bitwise instead
  lib/plug/crypto/key_generator.ex:17: Plug.Crypto.KeyGenerator (module)

warning: use Bitwise is deprecated. import Bitwise instead
  lib/plug/crypto.ex:9: Plug.Crypto (module)

Generated plug_crypto app
===> Analyzing applications...
===> Compiling ranch
==> ecto
Compiling 56 files (.ex)
warning: missing parentheses for expression following "do:" keyword. Parentheses are required to solve ambiguity inside keywords.

This error happens when you have function calls without parentheses inside keywords. For example:

    function(arg, one: nested_call a, b, c)
    function(arg, one: if expr, do: :this, else: :that)

In the examples above, we don't know if the arguments "b" and "c" apply to the function "function" or "nested_call". Or if the keywords "do" and "else" apply to the function "function" or "if". You can solve this by explicitly adding parentheses:

    function(arg, one: if(expr, do: :this, else: :that))
    function(arg, one: nested_call(a, b, c))

Ambiguity found at:
  lib/ecto/association.ex:445

warning: missing parentheses for expression following "do:" keyword. Parentheses are required to solve ambiguity inside keywords.

This error happens when you have function calls without parentheses inside keywords. For example:

    function(arg, one: nested_call a, b, c)
    function(arg, one: if expr, do: :this, else: :that)

In the examples above, we don't know if the arguments "b" and "c" apply to the function "function" or "nested_call". Or if the keywords "do" and "else" apply to the function "function" or "if". You can solve this by explicitly adding parentheses:

    function(arg, one: if(expr, do: :this, else: :that))
    function(arg, one: nested_call(a, b, c))

Ambiguity found at:
  lib/ecto/changeset.ex:568

warning: missing parentheses for expression following "do:" keyword. Parentheses are required to solve ambiguity inside keywords.

This error happens when you have function calls without parentheses inside keywords. For example:

    function(arg, one: nested_call a, b, c)
    function(arg, one: if expr, do: :this, else: :that)

In the examples above, we don't know if the arguments "b" and "c" apply to the function "function" or "nested_call". Or if the keywords "do" and "else" apply to the function "function" or "if". You can solve this by explicitly adding parentheses:

    function(arg, one: if(expr, do: :this, else: :that))
    function(arg, one: nested_call(a, b, c))

Ambiguity found at:
  lib/ecto/changeset.ex:2904

warning: missing parentheses for expression following "do:" keyword. Parentheses are required to solve ambiguity inside keywords.

This error happens when you have function calls without parentheses inside keywords. For example:

    function(arg, one: nested_call a, b, c)
    function(arg, one: if expr, do: :this, else: :that)

In the examples above, we don't know if the arguments "b" and "c" apply to the function "function" or "nested_call". Or if the keywords "do" and "else" apply to the function "function" or "if". You can solve this by explicitly adding parentheses:

    function(arg, one: if(expr, do: :this, else: :that))
    function(arg, one: nested_call(a, b, c))

Ambiguity found at:
  lib/ecto/changeset.ex:2906

warning: atom ::: must be written between quotes, as in :"::", to avoid ambiguity
  lib/ecto/query/builder.ex:255:8


== Compilation error in file lib/ecto/query.ex ==
** (Kernel.TypespecError) lib/ecto/query.ex:428: type dynamic/0 is a built-in type and it cannot be redefined
    (elixir 1.15.5) lib/kernel/typespec.ex:961: Kernel.Typespec.compile_error/2
    (stdlib 5.0.2) lists.erl:1599: :lists.foldl_1/3
    (elixir 1.15.5) lib/kernel/typespec.ex:226: Kernel.Typespec.translate_typespecs_for_module/2
could not compile dependency :ecto, "mix compile" failed. Errors may have been logged above. You can recompile this dependency with "mix deps.compile ecto --force", update it with "mix deps.update ecto" or clean it with "mix deps.clean ecto"

C:\CarbonX1\Phoenix\Public\meow>

Mise à jour du projet initial

Nous allons faire un update avec la préconisation : mix deps.update ecto

C:\CarbonX1\Phoenix\Public\meow>mix deps.update ecto
Resolving Hex dependencies...
Resolution completed in 0.111s
Unchanged:
  castore 0.1.13
  connection 1.1.0
  cowboy 2.9.0
  cowboy_telemetry 0.4.0
  cowlib 2.11.0
  db_connection 2.4.1
  ecto_sql 3.7.1
  esbuild 0.4.0
  file_system 0.2.10
  floki 0.32.0
  html_entities 0.5.2
  mime 2.0.2
  phoenix 1.6.2
  phoenix_ecto 4.4.0
  phoenix_html 3.1.0
  phoenix_live_reload 1.3.3
  phoenix_live_view 0.17.5
  phoenix_pubsub 2.0.0
  phoenix_view 1.0.0
  plug 1.12.1
  plug_cowboy 2.5.2
  plug_crypto 1.2.2
  postgrex 0.15.13
  ranch 1.8.0
  telemetry_metrics 0.6.1
  telemetry_poller 1.0.0
Upgraded:
  decimal 2.0.0 => 2.1.1
  ecto 3.7.1 => 3.7.2
  jason 1.2.2 => 1.4.1
  telemetry 1.0.0 => 1.2.1
* Updating jason (Hex package)
* Updating telemetry (Hex package)
* Updating decimal (Hex package)
* Updating ecto (Hex package)

C:\CarbonX1\Phoenix\Public\meow>

Relance de la création de la base de données

Nous relançons la création de la base de données : mix ecto.setup

La même erreur apparait :

C:\CarbonX1\Phoenix\Public\meow>mix ecto.setup
==> decimal
Compiling 4 files (.ex)
Generated decimal app
===> Analyzing applications...
===> Compiling telemetry
==> telemetry_metrics
Compiling 7 files (.ex)
Generated telemetry_metrics app
===> Analyzing applications...
===> Compiling telemetry_poller
==> jason
Compiling 10 files (.ex)
Generated jason app
==> db_connection
Compiling 14 files (.ex)
Generated db_connection app
==> ecto
Compiling 56 files (.ex)
warning: missing parentheses for expression following "do:" keyword. Parentheses are required to solve ambiguity inside keywords.

This error happens when you have function calls without parentheses inside keywords. For example:

    function(arg, one: nested_call a, b, c)
    function(arg, one: if expr, do: :this, else: :that)

In the examples above, we don't know if the arguments "b" and "c" apply to the function "function" or "nested_call". Or if the keywords "do" and "else" apply to the function "function" or "if". You can solve this by explicitly adding parentheses:

    function(arg, one: if(expr, do: :this, else: :that))
    function(arg, one: nested_call(a, b, c))

Ambiguity found at:
  lib/ecto/association.ex:445

warning: missing parentheses for expression following "do:" keyword. Parentheses are required to solve ambiguity inside keywords.

This error happens when you have function calls without parentheses inside keywords. For example:

    function(arg, one: nested_call a, b, c)
    function(arg, one: if expr, do: :this, else: :that)

In the examples above, we don't know if the arguments "b" and "c" apply to the function "function" or "nested_call". Or if the keywords "do" and "else" apply to the function "function" or "if". You can solve this by explicitly adding parentheses:

    function(arg, one: if(expr, do: :this, else: :that))
    function(arg, one: nested_call(a, b, c))

Ambiguity found at:
  lib/ecto/changeset.ex:568

warning: missing parentheses for expression following "do:" keyword. Parentheses are required to solve ambiguity inside keywords.

This error happens when you have function calls without parentheses inside keywords. For example:

    function(arg, one: nested_call a, b, c)
    function(arg, one: if expr, do: :this, else: :that)

In the examples above, we don't know if the arguments "b" and "c" apply to the function "function" or "nested_call". Or if the keywords "do" and "else" apply to the function "function" or "if". You can solve this by explicitly adding parentheses:

    function(arg, one: if(expr, do: :this, else: :that))
    function(arg, one: nested_call(a, b, c))

Ambiguity found at:
  lib/ecto/changeset.ex:2904

warning: missing parentheses for expression following "do:" keyword. Parentheses are required to solve ambiguity inside keywords.

This error happens when you have function calls without parentheses inside keywords. For example:

    function(arg, one: nested_call a, b, c)
    function(arg, one: if expr, do: :this, else: :that)

In the examples above, we don't know if the arguments "b" and "c" apply to the function "function" or "nested_call". Or if the keywords "do" and "else" apply to the function "function" or "if". You can solve this by explicitly adding parentheses:

    function(arg, one: if(expr, do: :this, else: :that))
    function(arg, one: nested_call(a, b, c))

Ambiguity found at:
  lib/ecto/changeset.ex:2906

warning: atom ::: must be written between quotes, as in :"::", to avoid ambiguity
  lib/ecto/query/builder.ex:260:8


== Compilation error in file lib/ecto/query.ex ==
** (Kernel.TypespecError) lib/ecto/query.ex:428: type dynamic/0 is a built-in type and it cannot be redefined
    (elixir 1.15.5) lib/kernel/typespec.ex:961: Kernel.Typespec.compile_error/2
    (stdlib 5.0.2) lists.erl:1599: :lists.foldl_1/3
    (elixir 1.15.5) lib/kernel/typespec.ex:226: Kernel.Typespec.translate_typespecs_for_module/2
could not compile dependency :ecto, "mix compile" failed. Errors may have been logged above. You can recompile this dependency with "mix deps.compile ecto --force", update it with "mix deps.update ecto" or clean it with "mix deps.clean ecto"

C:\CarbonX1\Phoenix\Public\meow>

L’anomalie est identifiée sur stackoverflow.

Mise à jour des dépendances vers les dernières versions

Nous devons passer à une nouvelle version d’Ecto {:ecto, « ~> 3.10 »}.

Dans le fichier mix.exs, nous avons :

  defp deps do
    [
      {:phoenix, "~> 1.6.2"},
      {:phoenix_ecto, "~> 4.4"},
      {:ecto_sql, "~> 3.6"},
      {:postgrex, ">= 0.0.0"},
      {:phoenix_html, "~> 3.0"},
      {:phoenix_live_reload, "~> 1.2", only: :dev},
      {:phoenix_live_view, "~> 0.17.5"},
      {:floki, ">= 0.30.0", only: :test},
      {:esbuild, "~> 0.4", runtime: Mix.env() == :dev},
      {:telemetry_metrics, "~> 0.6"},
      {:telemetry_poller, "~> 1.0"},
      {:jason, "~> 1.2"},
      {:plug_cowboy, "~> 2.5"}
    ]
  end

Modification du fichier mix.exs vers les dernières versions

Nous modifions mix.exs pour utiliser les dernières versions :

  # Specifies your project dependencies.
  #
  # Type `mix help deps` for examples and options.
  defp deps do
    [
      {:phoenix, "~> 1.6.2"},
      {:phoenix_ecto, "~> 4.4"},
      {:ecto, "~> 3.10"},
      {:ecto_sql, "~> 3.10"},
      {:postgrex, ">= 0.0.0"},
      {:phoenix_html, "~> 3.3.1"},
      {:phoenix_live_reload, "~> 1.4.1", only: :dev},
      {:phoenix_live_view, "~> 0.17.5"},
      {:floki, ">= 0.30.0", only: :test},
      {:esbuild, "~> 0.4", runtime: Mix.env() == :dev},
      {:telemetry_metrics, "~> 0.6"},
      {:telemetry_poller, "~> 1.0"},
      {:jason, "~> 1.4.1"},
      {:plug_cowboy, "~> 2.6"}
    ]
  end

Nous n’avons pas utilisé Phoenix 1.7, car cela suppose de modifier la structure de l’appplication. Avec le passage de 1.6 à 1.7, toute l’arborescence des fichiers du projet a été modifié. Notre objectif est de voir le focntionnement de l’application avant de modifier l’ensemble du projet.

Vérification du chargement des dépendances

Nous avons la résolution des dépendances suivantes mix deps.get :

C:\CarbonX1\Phoenix\Public\meow>mix deps.get
Resolving Hex dependencies...
Resolution completed in 0.118s
New:
  phoenix_template 1.0.3
Unchanged:
  castore 0.1.13
  cowboy_telemetry 0.4.0
  decimal 2.1.1
  esbuild 0.4.0
  file_system 0.2.10
  floki 0.32.0
  html_entities 0.5.2
  jason 1.4.1
  phoenix_ecto 4.4.0
  phoenix_live_view 0.17.5
  ranch 1.8.0
  telemetry 1.2.1
  telemetry_metrics 0.6.1
  telemetry_poller 1.0.0
Upgraded:
  cowboy 2.9.0 => 2.10.0
  cowlib 2.11.0 => 2.12.1
  db_connection 2.4.1 => 2.5.0
  ecto 3.7.2 => 3.10.3
  ecto_sql 3.7.1 => 3.10.2
  mime 2.0.2 => 2.0.5
  phoenix 1.6.2 => 1.6.16
  phoenix_html 3.1.0 => 3.3.2
  phoenix_live_reload 1.3.3 => 1.4.1
  phoenix_pubsub 2.0.0 => 2.1.3
  phoenix_view 1.0.0 => 2.0.2 (major)
  plug 1.12.1 => 1.15.0
  plug_cowboy 2.5.2 => 2.6.1
  plug_crypto 1.2.2 => 1.2.5
  postgrex 0.15.13 => 0.17.3 (minor)
* Updating phoenix (Hex package)
* Updating ecto (Hex package)
* Updating ecto_sql (Hex package)
* Updating postgrex (Hex package)
* Updating phoenix_html (Hex package)
* Updating phoenix_live_reload (Hex package)
* Updating plug_cowboy (Hex package)
* Updating cowboy (Hex package)
* Updating plug (Hex package)
* Updating mime (Hex package)
* Updating plug_crypto (Hex package)
* Updating cowlib (Hex package)
* Updating db_connection (Hex package)
* Updating phoenix_pubsub (Hex package)
* Updating phoenix_view (Hex package)
* Getting phoenix_template (Hex package)
You have added/upgraded packages you could sponsor, run `mix hex.sponsor` to learn more

C:\CarbonX1\Phoenix\Public\meow>

Création de la base de données

la création de la base de données avec mix ecto.setup :

C:\CarbonX1\Phoenix\Public\meow> mix ecto.setup
==> mime
Compiling 1 file (.ex)
Generated mime app
==> db_connection
Compiling 15 files (.ex)
Generated db_connection app
==> phoenix_pubsub
Compiling 11 files (.ex)
Generated phoenix_pubsub app
==> plug_crypto
Compiling 5 files (.ex)
Generated plug_crypto app
==> ecto
Compiling 56 files (.ex)
warning: Logger.warn/1 is deprecated. Use Logger.warning/2 instead
  lib/ecto/repo/preloader.ex:208: Ecto.Repo.Preloader.fetch_ids/4

warning: Logger.warn/1 is deprecated. Use Logger.warning/2 instead
  lib/ecto/changeset/relation.ex:474: Ecto.Changeset.Relation.process_current/3

warning: Logger.warn/1 is deprecated. Use Logger.warning/2 instead
  lib/ecto/changeset.ex:3156: Ecto.Changeset.optimistic_lock/3

Generated ecto app
==> plug
Compiling 1 file (.erl)
Compiling 40 files (.ex)
Generated plug app
==> phoenix_html
Compiling 9 files (.ex)
Generated phoenix_html app
==> phoenix_template
Compiling 4 files (.ex)
Generated phoenix_template app
==> phoenix_view
Compiling 1 file (.ex)
Generated phoenix_view app
==> postgrex
Compiling 68 files (.ex)
Generated postgrex app
==> ecto_sql
Compiling 25 files (.ex)
warning: Logger.warn/1 is deprecated. Use Logger.warning/2 instead
  lib/ecto/migrator.ex:271: Ecto.Migrator.up/4

Generated ecto_sql app
==> castore
Compiling 1 file (.ex)
Generated castore app
==> esbuild
Compiling 3 files (.ex)
warning: Logger.warn/1 is deprecated. Use Logger.warning/2 instead
  lib/esbuild.ex:66: Esbuild.start/2

warning: Logger.warn/1 is deprecated. Use Logger.warning/2 instead
  lib/esbuild.ex:80: Esbuild.start/2

Generated esbuild app
===> Analyzing applications...
===> Compiling cowlib
===> Analyzing applications...
===> Compiling cowboy
===> Analyzing applications...
===> Compiling cowboy_telemetry
==> plug_cowboy
Compiling 5 files (.ex)
warning: Logger.warn/1 is deprecated. Use Logger.warning/2 instead
  lib/plug/cowboy.ex:352: Plug.Cowboy.to_args/5

Generated plug_cowboy app
==> phoenix
Compiling 69 files (.ex)
warning: missing parentheses for expression following "do:" keyword. Parentheses are required to solve ambiguity inside keywords.

This error happens when you have function calls without parentheses inside keywords. For example:

    function(arg, one: nested_call a, b, c)
    function(arg, one: if expr, do: :this, else: :that)

In the examples above, we don't know if the arguments "b" and "c" apply to the function "function" or "nested_call". Or if the keywords "do" and "else" apply to the function "function" or "if". You can solve this by explicitly adding parentheses:

    function(arg, one: if(expr, do: :this, else: :that))
    function(arg, one: nested_call(a, b, c))

Ambiguity found at:
  lib/phoenix/controller.ex:998

warning: Logger.warn/1 is deprecated. Use Logger.warning/2 instead
  lib/phoenix/code_reloader/server.ex:50: Phoenix.CodeReloader.Server.handle_call/3

warning: Logger.warn/1 is deprecated. Use Logger.warning/2 instead
  lib/phoenix/endpoint/supervisor.ex:34: Phoenix.Endpoint.Supervisor.init/1

warning: Logger.warn/1 is deprecated. Use Logger.warning/2 instead
  lib/phoenix/endpoint/supervisor.ex:57: Phoenix.Endpoint.Supervisor.init/1

warning: Logger.warn/1 is deprecated. Use Logger.warning/2 instead
  lib/phoenix/endpoint/supervisor.ex:78: Phoenix.Endpoint.Supervisor.pubsub_children/2

warning: Logger.warn/1 is deprecated. Use Logger.warning/2 instead
  lib/phoenix/endpoint/supervisor.ex:264: Phoenix.Endpoint.Supervisor.build_url/2

warning: Logger.warn/1 is deprecated. Use Logger.warning/2 instead
  lib/phoenix/controller.ex:1009: Phoenix.Controller.warn_if_ajax/1

warning: missing parentheses for expression following "do:" keyword. Parentheses are required to solve ambiguity inside keywords.

This error happens when you have function calls without parentheses inside keywords. For example:

    function(arg, one: nested_call a, b, c)
    function(arg, one: if expr, do: :this, else: :that)

In the examples above, we don't know if the arguments "b" and "c" apply to the function "function" or "nested_call". Or if the keywords "do" and "else" apply to the function "function" or "if". You can solve this by explicitly adding parentheses:

    function(arg, one: if(expr, do: :this, else: :that))
    function(arg, one: nested_call(a, b, c))

Ambiguity found at:
  lib/phoenix/router/resource.ex:71

warning: Logger.warn/1 is deprecated. Use Logger.warning/2 instead
  lib/phoenix/socket.ex:610: Phoenix.Socket.handle_in/4

Generated phoenix app
==> phoenix_live_reload
Compiling 4 files (.ex)
warning: Logger.warn/1 is deprecated. Use Logger.warning/2 instead
  lib/phoenix_live_reload/application.ex:32: Phoenix.LiveReloader.Application.start_link/0

Generated phoenix_live_reload app
==> phoenix_live_view
Compiling 29 files (.ex)
warning: Plug.Conn.Query.decode_pair/2 is deprecated. Use decode_init/0, decode_each/2, and decode_done/2 instead
Invalid call found at 4 locations:
  lib/phoenix_live_view/test/client_proxy.ex:1078: Phoenix.LiveViewTest.ClientProxy.form_defaults/3
  lib/phoenix_live_view/test/client_proxy.ex:1083: Phoenix.LiveViewTest.ClientProxy.form_defaults/3
  lib/phoenix_live_view/test/client_proxy.ex:1093: Phoenix.LiveViewTest.ClientProxy.form_defaults/3
  lib/phoenix_live_view/test/client_proxy.ex:1102: Phoenix.LiveViewTest.ClientProxy.form_defaults/3

Generated phoenix_live_view app
==> phoenix_ecto
Compiling 7 files (.ex)
Generated phoenix_ecto app
==> meow
Compiling 19 files (.ex)
Generated meow app
The database for Meow.Repo has been created

16:44:33.223 [info] == Running 20211105111549 Meow.Repo.Migrations.CreateMeerkats.change/0 forward

16:44:33.223 [info] create table meerkats

16:44:33.234 [info] create index meerkats_name_index

16:44:33.239 [info] create index meerkats_weight_index

16:44:33.241 [info] create index meerkats_gender_index

16:44:33.244 [info] == Migrated 20211105111549 in 0.0s
[debug] QUERY OK db=6.8ms queue=2.2ms idle=26.2ms
INSERT INTO "meerkats" ("name","inserted_at","updated_at") VALUES ($1,$2,$3),($4,$5,$6),($7,$8,$9),($10,$11,$12),($13,$14,$15),($16,$17,$18),($19,$20,$21),($22,$23,$24),($25,$26,$27),($28,$29,$30),($31,$32,$33),($34,$35,$36),($37,$38,$39),($40,$41,$42),($43,$44,$45),($46,$47,$48),($49,$50,$51),($52,$53,$54),($55,$56,$57),($58,$59,$60),($61,$62,$63),($64,$65,$66),($67,$68,$69),($70,$71,$72),($73,$74,$75),($76,$77,$78),($79,$80,$81),($82,$83,$84),($85,$86,$87),($88,$89,$90),($91,$92,$93),($94,$95,$96),($97,$98,$99),($100,$101,$102),($103,$104,$105),($106,$107,$108),($109,$110,$111),($112,$113,$114),($115,$116,$117),($118,$119,$120),($121,$122,$123),($124,$125,$126),($127,$128,$129),($130,$131,$132),($133,$134,$135),($136,$137,$138),($139,$140,$141),($142,$143,$144),($145,$146,$147),($148,$149,$150),($151,$152,$153),($154,$155,$156),($157,$158,$159),($160,$161,$162),($163,$164,$165),($166,$167,$168),($169,$170,$171),($172,$173,$174),($175,$176,$177),($178,$179,$180),($181,$182,$183),($184,$185,$186),($187,$188,$189),($190,$191,$192),($193,$194,$195),($196,$197,$198),($199,$200,$201),($202,$203,$204),($205,$206,$207),($208,$209,$210),($211,$212,$213),($214,$215,$216),($217,$218,$219),($220,$221,$222),($223,$224,$225),($226,$227,$228),($229,$230,$231),($232,$233,$234),($235,$236,$237),($238,$239,$240),($241,$242,$243),($244,$245,$246),($247,$248,$249),($250,$251,$252),($253,$254,$255),($256,$257,$258),($259,$260,$261),($262,$263,$264),($265,$266,$267),($268,$269,$270),($271,$272,$273),($274,$275,$276),($277,$278,$279),($280,$281,$282),($283,$284,$285),($286,$287,$288),($289,$290,$291),($292,$293,$294),($295,$296,$297),($298,$299,$300),($301,$302,$303),($304,$305,$306),($307,$308,$309),($310,$311,$312),($313,$314,$315),($316,$317,$318),($319,$320,$321),($322,$323,$324),($325,$326,$327),($328,$329,$330),($331,$332,$333),($334,$335,$336),($337,$338,$339),($340,$341,$342),($343,$344,$345),($346,$347,$348),($349,$350,$351) ["Miri Tink", ~N[2023-10-03 14:44:33], ~N[2023-10-03 14:44:33], "Rusty Boots", ~N[2023-10-03 14:44:33], ~N[2023-10-03 14:44:33], "Kattie Kermit", ~N[2023-10-03 14:44:33], ~N[2023-10-03 14:44:33], "Mean Cheddar", ~N[2023-10-03 14:44:33], ~N[2023-10-03 14:44:33], "Rusty Jelly", ~N[2023-10-03 14:44:33], ~N[2023-10-03 14:44:33], "Cat Bubbles", ~N[2023-10-03 14:44:33], ~N[2023-10-03 14:44:33], "Kat Cheerio", ~N[2023-10-03 14:44:33], ~N[2023-10-03 14:44:33], "Cat Buttons", ~N[2023-10-03 14:44:33], ~N[2023-10-03 14:44:33], "Meery Cindy Clawford", ~N[2023-10-03 14:44:33], ~N[2023-10-03 14:44:33], "Rusty Marshmallow", ~N[2023-10-03 14:44:33], ~N[2023-10-03 14:44:33], "Cat Puddy Tat", ~N[2023-10-03 14:44:33], ~N[2023-10-03 14:44:33], "Slim Fishbait", ~N[2023-10-03 14:44:33], ~N[2023-10-03 14:44:33], "Kat Baloo", ~N[2023-10-03 14:44:33], ~N[2023-10-03 14:44:33], "Cute Miss Piggy", ~N[2023-10-03 14:44:33], ~N[2023-10-03 14:44:33], "Kattie Frodo", ~N[2023-10-03 14:44:33], ~N[2023-10-03 14:44:33], "Kat Pikachu", ~N[2023-10-03 14:44:33], ~N[2023-10-03 14:44:33], "Miri Wasabi", ~N[2023-10-03 14:44:33], ...]

C:\CarbonX1\Phoenix\Public\meow>

L’application compile malgrès les Warning. Nous pouvons passer à l’étape suivante.

Activation du projet initial Moew

Nous lançons le serveur avec : mix phx.server

L’application ne fonctionne pas :

C:\CarbonX1\Phoenix\Public\meow>mix phx.server
[warning] Phoenix is unable to create symlinks. Phoenix' code reloader will run considerably faster if symlinks are allowed. On Windows, the lack of symlinks may even cause empty assets to be served. Luckily, you can address this issue by starting your Windows terminal at least once with "Run as Administrator" and then running your Phoenix application.
[info] Running MeowWeb.Endpoint with cowboy 2.10.0 at 127.0.0.1:4000 (http)
[info] Access MeowWeb.Endpoint at http://localhost:4000
[debug] Downloading esbuild from https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.12.18.tgz
[error] Task #PID<0.442.0> started from MeowWeb.Endpoint terminating
** (UndefinedFunctionError) function :http_util.timestamp/0 is undefined (module :http_util is not available)
    (inets 9.0.1) :http_util.timestamp()
    (inets 9.0.1) httpc.erl:750: :httpc.handle_request/9
    (esbuild 0.4.0) lib/esbuild.ex:283: Esbuild.fetch_body!/1
    (esbuild 0.4.0) lib/esbuild.ex:204: Esbuild.install/0
    (esbuild 0.4.0) lib/esbuild.ex:185: Esbuild.install_and_run/2
    (phoenix 1.6.16) lib/phoenix/endpoint/watcher.ex:19: Phoenix.Endpoint.Watcher.watch/2
    (elixir 1.15.5) lib/task/supervised.ex:101: Task.Supervised.invoke_mfa/2
Function: &Phoenix.Endpoint.Watcher.watch/2
    Args: ["esbuild", {Esbuild, :install_and_run, [:default, ["--sourcemap=inline", "--watch"]]}]
[debug] Downloading esbuild from https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.12.18.tgz
[error] Task #PID<0.443.0> started from MeowWeb.Endpoint terminating
** (UndefinedFunctionError) function :http_util.timestamp/0 is undefined (module :http_util is not available)
......

L’application ne fonctionne pas et l’erreur concerne http_util.timestamp().

L’erreur référencée dans GitHub :  

La solution préconisée par Peter Ullrich :

  • add :inets to your :extra_applications

Un exemple d’utilisation de :inets :

  • https://tylerpachal.medium.com/creating-an-http-server-using-pure-otp-c600fb41c972

Nous ajoutons :inets à extra_applications dans le fichier mix.exs (ligne 7) :

  # Configuration for the OTP application.
  #
  # Type `mix help compile.app` for more information.
  def application do
    [
      mod: {Meow.Application, []},
      extra_applications: [:logger, :runtime_tools, :inets]
    ]
  end

Puis nous relançons l’application avec : mix phx.server, qui affiche bien la liste initiale

Trier la liste  d'une table  LiveView#3-la vue liste initiale meow
Trier la liste d’une table LiveView#3-la vue liste initiale meow

C’est cette liste de données que nous souhaitons trier au niveau de chaque colonnes de la vue en mode liste affichée avec LiveView

Analyse de la version initiale

Afin de comprendre le projet, nous allons analyser le code initial pour comprendre d’où nous partons.

Activons VS Code dans la dossier du projet :

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

Décomposition du projet

L’organisation du projet est conforme aux préconisations Phoenix avec une partir traitement et une partie web :

  • dossier lib/meow/ : les traitements et accès à la base de données
  • dossier lib/meow_web/ : la gestion de la partie affichage web

Le dossier gestion de la base de données

Nous pouvons lire l’article de présentation d’Ecto qui permettra de comprendre la suite.

Dans le dossier meow, nous avons les modules de gestion de la base de données :

  • meow/meerkats/meerkat.ex : le module de description de la table meerkats
  • meow/meerkats.ex : module qui contient une fonction pour accéder à la liste des articles de la table Meerkat

Création de la table Meerkat dans meow/meerkats/meerkat.ex :

defmodule Meow.Meerkats.Meerkat do
  use Ecto.Schema
  import Ecto.Changeset

  schema "meerkats" do
    field(:name, :string)

    timestamps()
  end

  @doc false
  def changeset(meerkat, attrs) do
    meerkat
    |> cast(attrs, [:name])
    |> validate_required([:name])
  end
end

Utilisation de la table Meerkat dans meow/meerkats.ex :

defmodule Meow.Meerkats do
  @moduledoc """
  The Meerkats context.
  """

  import Ecto.Query, warn: false
  alias Meow.Repo
  alias Meow.Meerkats.Meerkat

  def list_meerkats do
    Repo.all(Meerkat)
  end
end

La partie web du projet : trier la liste d’une table LiveView

Dans le dosssier web, nous avons le fichier lib/meow_web/router.ex, qui nous donne le point d’entré des requêtes web. Dans le scope « / » nous voyons que nous adressons toutes les requêtes vers MeekatLive.

  scope "/", MeowWeb do
    pipe_through(:browser)

    live("/", MeerkatLive)
  end

Le fichier contenant le module MeerkatLive est rangé dans meow_web/live/meerkat_live.ex. La fonction handle_params ajoute par assign la liste des articles par appel de la fonction créé dans le Module Meow.Meerkats (meerkats.ex) dans une variable appelée :meerkats (ligne 14 ci-dessous).

Le module MeerkatLive dans meow_web/live/meerkat_live.ex :

defmodule MeowWeb.MeerkatLive do
  use MeowWeb, :live_view
  alias Meow.Meerkats

  @impl true
  def mount(_params, _session, socket), do: {:ok, socket}

  @impl true
  def handle_params(_params, _url, socket) do
    {:noreply, assign_meerkats(socket)}
  end

  defp assign_meerkats(socket) do
    assign(socket, :meerkats, Meerkats.list_meerkats())
  end
end

Dans le fichier meow_web/live/meerkat_live.html.heex nous avons le html gérant l’affichage de la table. Le contenu de la variable @meerkats est utilisé pour afficher les différentes lignes de la base de données.

<div id="table-container">

  <table>
    <tbody>
      <%= if assigns[:error_message] do %>
        <tr>
          <td colspan="6"><%= @error_message %></td>
        </tr>
      <% else %>
        <%= for meerkat <- @meerkats do %>
          <tr data-test-id={meerkat.id}>
            <td><%= meerkat.id %></td>
            <td><%= meerkat.name %></td>
          </tr>
        <% end %>
      <% end %>
    </tbody>
  </table>

</div>

Nous constatons que notre module MeerkatLive ne contient pas de fonction render. L’article utiliser phoenix liveview présente les méthodes de rendu utilisables avec LiveView. Explication, nous avons 2 options pour LiveView :

  • ajouter la fonction render dans le module MeerkatLive avec du code ~H dans cette fonction render.
  • créer un fichier meerkat_live.html.heex situé juste en dessous du fichier meerkat_live.ex utilisé pour le module MeerkatLive. Dans ce dernier cas, le contenu du fichier meerkat_live.html.heex remplace l’usage de la fonction render du module MeerkatLive . Si nous ajoutons une fonction render, le fichier n’est plus utilisé. La fonction render est prioritaire sur l’utilisation d’un fichier de rendu.

L’article utiliser phoenix liveview présente une troisième méthode utilisant render pour appeler une page template de type « my_page.html.heex ».

Présentation du travail à faire

Dans le fichier lib/meow/meekats.ex, nous avons le module Meerkats qui présente la fonction list_meerkats.

Cette fonction retourne toutes les lignes de la table :

defmodule Meow.Meerkats do
  @moduledoc """
  The Meerkats context.
  """

  import Ecto.Query, warn: false
  alias Meow.Repo
  alias Meow.Meerkats.Meerkat

  def list_meerkats do
    Repo.all(Meerkat)
  end
end

Lorsque la liste est très longue cela n’est pas fonctionnel.

[Ouvrage page 13]

La vue présentée à l’utilisateur utilise LiveView. Le fichier meerkat_live.ex présente la vue :

defmodule MeowWeb.MeerkatLive do
  use MeowWeb, :live_view
  alias Meow.Meerkats

  @impl true
  def mount(_params, _session, socket), do: {:ok, socket}

  @impl true
  def handle_params(_params, _url, socket) do
    {:noreply, assign_meerkats(socket)}
  end

  defp assign_meerkats(socket) do
    assign(socket, :meerkats, Meerkats.list_meerkats())
  end
end

La fonction handle_params permet de gérer les mises à jour de la vue, contrairement à la fonction d’initialisation mount. C’est pourquoi c’est dans cette fonction que nous mettons le chargement des données de la base de données par l’appel de la fonction Meerkats.list_meerkats.

Le rendu dans la page est créé avec le fichier heex meerkat_live.html.heex :

<div id="table-container">

  <table>
    <tbody>
      <%= if assigns[:error_message] do %>
        <tr>
          <td colspan="6"><%= @error_message %></td>
        </tr>
      <% else %>
        <%= for meerkat <- @meerkats do %>
          <tr data-test-id={meerkat.id}>
            <td><%= meerkat.id %></td>
            <td><%= meerkat.name %></td>
          </tr>
        <% end %>
      <% end %>
    </tbody>
  </table>

</div>

La table est construite avec une ligne par meerkat présentant l’id et le nom.

Trier les données des colonnes d’une vue en mode liste affichant le contenu d’une base de données avec LiveView

Nous souhaitons trier les données des colonnes d’une vue en mode liste avec un trie des données soit par ordre ascendant soit par ordre descendant.

Création de la requête Ecto avec order_by pour ajouter la fonction de trie

[Ouvrage page 17]

Le trie se fera dans la base de données, et nous utilisons Ecto pour gérer la demande de trie.

Nous modifions l’appel à la base de données de list_meerkats :

defmodule Meow.Meerkats do
  @moduledoc """
  The Meerkats context.
  """

  import Ecto.Query, warn: false
  alias Meow.Repo
  alias Meow.Meerkats.Meerkat

	def list_meerkats(opts) do
		from(m in Meerkat)
		|> sort(opts)
		|> Repo.all()
	end
    
	defp sort(query, %{sort_by: sort_by, sort_dir: sort_dir})
		when sort_by in [:id, :name] and
			sort_dir in [:asc, :desc] do
		order_by(query, {^sort_dir, ^sort_by})
	end
    
	defp sort(query, _opts), do: query

end

Nous avons changé list_meerkats pour prendre en paramètre les options souhaitées par l’utilisateur ;

  • choix de la colonne de trie : sort_by (ligne 17)
  • choix de l’ordre de trie : sort_dir (ligne 18)

La fonction from(m in Meerkat) [ligne 11], retourne un query qui est passé en premier paramètre grâce au pipe |>.

Les options en provenance de l’utilisateur sont passées par une structure avec opts. Nous utilisons le pattern-matching pour récupérer les valeurs passées en paramètre par l’utilisateur (ligne 16).

On vérifie avec le guard when que les valeurs fournies sont conformes aux valeurs attendues :

  • sort_by doit être :id ou :name (ligne 17)
  • sort_dir doit être :asc ou :desc (ligne 18)

Nous utilisons le pin operator : ‘ ^ ‘ pour accéder aux valeurs de sort_by et sort_dir à l’interieure de la fonction Ecto order_by :

  • When writing a query, you are inside Ecto’s query syntax. In order to access params values or invoke Elixir functions, you need to use the ^ operator, which is overloaded by Ecto. Voir la documentation d’Ecto.

La lligne 22 défini la fonction sort par défaut au cas ou la première fonction sort (ligne 16) n’accepte pas les valeurs passées en paramètre. Nous obtenons alors la liste complète des valeurs obtenues de la base de données sans aucun trie.

Permettre aux utilisateurs de trier les données des colonnes d’une vue en mode liste avec LiveView

[Ouvrage page 21]

Pour trier la liste d’une table LiveView, nous allons créer des composant de trie LiveComponent. Ces composants permettent de gérer des évènements internes au composant et d’adresser des évenements à la page qui accueille le composant. Le composant est défini dans la page par :

  • le nom du composant : module={nom.complet.du.composant}
  • l’id du composant, identifiant unique : id={id_pour_le_composant}
  • des propriétés key/value définies par : key={value}

La création d’un composant reprend les fonctions :

  • render(assigns) : le rendu du composant
  • handle_event/3 : pour traiter les évènements qui arrivent dans le composant depuis la page web avec phx-target={@myself} et phx-click= »event_name »

Le composant pour gérer l’ordre de trie :

defmodule MeowWeb.MeerkatLive.SortingComponent do
	use MeowWeb, :live_component
	def render(assigns) do
		~H"""
			<div phx-click="sort" phx-target={@myself} >
				<%= @key %> <%= chevron(@sorting, @key) %>
			</div>
		"""
	end
	def handle_event("sort", _params, socket) do
		%{sorting: %{sort_dir: sort_dir}, key: key} = socket.assigns
		sort_dir = if sort_dir == :asc, do: :desc, else: :asc
		opts = %{sort_by: key, sort_dir: sort_dir}
		send(self(), {:update, opts})
		{:noreply, assign(socket, :sorting, opts)}
	end
	defp chevron(%{sort_by: sort_by, sort_dir: sort_dir}, key)
		when sort_by == key do
		if sort_dir == :asc, do: "⇧", else: "⇩"
	end
	defp chevron(_opts, _key), do: ""

end

L’affichage du composant est défini dans render. Nous avons un <div> avec un évènement « sort » sur phx-click adressé au composant par phx-target={@myself}. [ligne 6]

Lorsque l’évènement est reçu par handle-event(« sort », _params, socket), on récupère les valeurs passées dans socket.assigns [ligne 11] par pattern-matching : sort_dir et key.

On permutte (toggle) la valeur de sort_dir [ligne 12], puis on construit les options [ligne 13] à passer en parametre à la page d’accueil du composant qui les transférera à Ecto pour obtenir la liste des données triées depuis la base de données.

Nous avons deux messages :

  • send self(), qui s’adresse à la page liveView d’accueil du composant. Le message :update envoyé contient les nouvelles options opts avec sort_dir inversé (toggle sur :asc et :desc).
  • le retour de handle_event raffraichit le composant, ce qui va changer le sens de la flèche dessinée par fonction privée chevron.

En résumé, le composant contient 2 valeurs :

  • la direction : sort_dir
  • l’identifiant de la colonne sur laquelle est utilisé le composant : key qui devient sort_by.

L’utilisation dans la page de notre composant de trie se fait dans le header de la table, avec un composant de trie pour chacune des colonnes.

<table>
	<thead>
		<tr>
			<th>
				<.live_component
					module={MeowWeb.MeerkatLive.SortingComponent}
					id={"sorting-id"}
					key={:id}
					sorting={@sorting} />
			</th>
			<th>
				<.live_component
					module={MeowWeb.MeerkatLive.SortingComponent}
					id={"sorting-name"}
					key={:name}
					sorting={@sorting} />
			</th>
		</tr>
	</thead>
	<!-- Table body -->
</table>

Comment traitons-nous le message :update dans la page d’accueil du composant ?

Le message est reçu dans une fonction handle_info ({:update, opts], socket). En effet, le message provient du composant qui est dans la page et non directement de l’action de l’utilisateur. Dans le cas contraire le message serait reçu par handle_event.

Comment récupérer les options et mettre à jour la table ?

La page qui accueille les composants de trie reçoit le message dans handle_info({:update, opts], socket). Nous devons maintenant traiter ce message, ce qui suppose de raffraichir la page pour relancer la requête vers la base de données.

Si nous reprenons la page LiveView, nous avons :

defmodule MeowWeb.MeerkatLive do
  use MeowWeb, :live_view
  alias Meow.Meerkats

  @impl true
  def mount(_params, _session, socket), do: {:ok, socket}

  @impl true
  def handle_params(_params, _url, socket) do
    {:noreply, assign_meerkats(socket)}
  end

  defp assign_meerkats(socket) do
    assign(socket, :meerkats, Meerkats.list_meerkats())
  end
end

La fonction handle_params charge les données avec la methode privée assign_meerkats(socket). La fonction Mererkats.list_meerkats() ne reçoit actuellement aucune information et charge en bloc toute la table.

Nous avons déjà construit notre nouvelle fonction list_meerkats(opts) permettant de recevoir les options de l’utilisateur, avec les options opts dans une structure :

  • opts = %{sort_by: sort_by, sort_dir: sort_dir}

Il nous reste à appeler cette fonction dans assign_meerkats(socket) [ligne 13] :

  • assign(socket, :meerkats, Meerkats.list_meerkats(opts))

La fonction handle_param/3 est appelé lorsque nous arrivons sur la page, ou lorsque les paramétres de la requête URL changent. C’est ainsi que nous allons procéder. Nous allons changer les paramètres de la requête URL avec les données choisies par l’utilisateur. Pour ce faire, dans handle_info/2, nous changeons dans l’url les paramètres des options opts :

def handle_info({:update, opts}, socket) do
	path = Routes.live_path(socket, __MODULE__, opts)
	{:noreply, push_patch(socket, to: path, replace: true)}
end

Nous avons push_patch qui force le changement d’URL dans le navigateur.

Nous pouvons consulter un autre article qui utilise la même technique de changement des paramètres de l’URL pour rafraichir les données dans la page.

Le changement d’URL en restant dans la même page appelera handle_param qui rechargera les données. Nous devons maintenant récupérer les paramètres dans handle_param et vérifier leur conformité avec ce qui est attendu.

Contrôle des valeurs passées en paramètre dans une url

[Ouvrage page 25]

Pour trier la liste d’une table LiveView, nous devons passer en paramètre les valeurs et contrôler ces données à la réception côté serveur. Dans notre cas, nous avons exclusivement des valeurs de type Enum :

  • sort_name {:id, :name}
  • sort_order {:asc, :desc}

Nous souhaitons utiliser un changeset schemaless, c’est à dire non relié à la création d’une table de base de données pour vérifier les valeurs fournies par l’utilisateur.

Un article montre comment déclarer les fields de type enum dans un changeset de type schemaless. C’est la même technique qui est présenté page 26 de l’Ouvrage :

  • avec shema : field :sort_by, Ecto.Enum, values: [:id, :name]
  • pour shemaless : sort_by: {:parameterized, Ecto.Enum, Ecto.Enum.init(values: [:id, :name])}

[Ouvrage page 27]

Dans l’ouvrage, nous avons la création d’un module Helper qui crée une fonction simplifiant la création des Enum en mode schemaless :

defmodule Meow.EctoHelper do
	def enum(values) do
		{:parameterized, Ecto.Enum, Ecto.Enum.init(values: values)}
	end
end

Cette fonction simplifie la déclaration des fields sort_by et sord_dir de notre formulaire :

defmodule MeowWeb.Forms.SortingForm do
	import Ecto.Changeset
	alias Meow.EctoHelper
	@fields %{
		sort_by: EctoHelper.enum([:id, :name]),
		sort_dir: EctoHelper.enum([:asc, :desc])
	}
	@default_values %{
		sort_by: :id,
		sort_dir: :asc
	}
	def parse(params) do
		{@default_values, @fields}
		|> cast(params, Map.keys(@fields))
		|> apply_action(:insert)
	end
	def default_values(), do: @default_values
end

Le formulaire SortingForm ci-dessus est utilisé pour contrôler les données obtenues à travers l’URL, car l’URL peut être changé par l’utilisateur facilement. Grâce à la fonction parse/1, nous convertissons les valeurs obtenue sous la forme de String {« sort_by » => « name »}) en Atom %{sort_by: :name}). Deplus, lorsqu’une valeur n’est pas présente, nous prenons la valeur par défaut. Enfin, en cas d’erreur, nous retournons une erreur avec le contenu de l’erreur dans le changeset :

  • parse success : {:ok, %{sort_by: :name_value, sort_dir: :order_value}
  • parse error : {:error, %Ecto.Changeset{}}

Pour trier la liste d’une table LiveView, nous avons besoin de normaliser les paramètres de trie.

La page LiveView complète permettant de trier la liste d’une table

MeowWeb.MeerkatLive devient :

defmodule MeowWeb.MeerkatLive do
	use MeowWeb, :live_view
	alias Meow.Meerkats
	# Add this alias:
	alias MeowWeb.Forms.SortingForm
    
	def mount(_params, _session, socket), do: {:ok, socket}
    
	# Update handle_params/3 like this:
	def handle_params(params, _url, socket) do
		socket =
			socket
			|> parse_params(params)
			|> assign_meerkats()
		{:noreply, socket}
	end
    
    # Add this function:
	def handle_info({:update, opts}, socket) do
		path = Routes.live_path(socket, __MODULE__, opts)
		{:noreply, push_patch(socket, to: path, replace: true)}
	end
    
	# Add this function:
	defp parse_params(socket, params) do
		with {:ok, sorting_opts} <- SortingForm.parse(params) do
			assign_sorting(socket, sorting_opts)
		else
			_error ->
				assign_sorting(socket)
		end
	end
    
	# Add this function:
	defp assign_sorting(socket, overrides \\ %{}) do
		opts = Map.merge(SortingForm.default_values(), overrides)
		assign(socket, :sorting, opts)
	end
    
	# Update assign_meerkats/1 like this:
	defp assign_meerkats(socket) do
		%{sorting: sorting} = socket.assigns
		assign(socket, :meerkats, Meerkats.list_meerkats(sorting))
	end
end

Test de l’application permettant de trier la liste d’une table LiveView

Testons maintenant l’application pour laquelle nous avons ajouté des entêtes de colonne à notre tableau, entêtes qui contiennent un bouton donnant le sens du trie.

cd C:\CarbonX1\Phoenix\Public\meow
mix phx.server

L’accés à la page avec :

  • http://localhost:4000/

Nous voyons bien le bouton de trie sur la colonne index :

Trier la liste  d'une table  LiveView#4-la vue liste avec le bouton de trie
Trier la liste d’une table LiveView#4-la vue liste avec le bouton de trie

En cliquant sur le nom de la colonne, nous faisons apparaitre la flêche de trie. Le bouton de trie n’est présent que sur la collonne utilisée pour le trie.

Trier la liste  d'une table  LiveView#5-la vue liste avec le bouton de trie sur la 2nd colonne
Trier la liste d’une table LiveView#5-la vue liste avec le bouton de trie sur la 2nd colonne

Par contre, nous ne voyons pas les paramètres dans l’URL. Lorsque nous voulons trier la liste d’une table LiveView, nous souhaitons aussi que les paramètres utiliés pour le trie s’affiche dans l’URL. Il s’agit d’une anomalie identifiée sur stackoverflow : push patch won’t update url. La correction se fait en suivant la description de l’anomalie.

Choix d’une autre version pour Phoenix

Nous allons tester la version Phoenix 1.6.16. Cela se fait en changeant les dépendances.

Dans mix.exs, nous remplaçons :

  •   {:phoenix, « ~> 1.6.2 »}, par {:phoenix, « ~> 1.6.16 »},
  • {:phoenix_live_view, « ~> 0.17.5 »}, par {:phoenix_live_view, « ~> 0.18.4 »},

Nous avons une anomalie, l’application ne compile pas après la mise à jour des dépendances :

C:\CarbonX1\Phoenix\Public\meow>mix deps.get
Resolving Hex dependencies...
Resolution completed in 0.096s
Unchanged:
  cowboy 2.10.0
  cowboy_telemetry 0.4.0
  cowlib 2.12.1
  db_connection 2.5.0
  decimal 2.1.1
  ecto 3.10.3
  ecto_sql 3.10.2
  esbuild 0.4.0
  file_system 0.2.10
  floki 0.32.0
  html_entities 0.5.2
  jason 1.4.1
  mime 2.0.5
  phoenix 1.6.16
  phoenix_ecto 4.4.0
  phoenix_live_reload 1.4.1
  phoenix_pubsub 2.1.3
  phoenix_template 1.0.3
  phoenix_view 2.0.2
  plug_cowboy 2.6.1
  plug_crypto 1.2.5
  postgrex 0.17.3
  ranch 1.8.0
  telemetry 1.2.1
  telemetry_metrics 0.6.1
  telemetry_poller 1.0.0
Upgraded:
  castore 0.1.13 => 1.0.4 (major)
  phoenix_html 3.3.2 => 3.3.3
  phoenix_live_view 0.17.5 => 0.18.18 (minor)
  plug 1.15.0 => 1.15.1
* Updating phoenix_html (Hex package)
* Updating phoenix_live_view (Hex package)
* Updating plug (Hex package)
* Updating castore (Hex package)

C:\CarbonX1\Phoenix\Public\meow>

Affichage de l’erreur à la compilation avec phoenix_live_view_0-18

L’erreur à la compilation :

C:\CarbonX1\Phoenix\Public\meow>mix phx.server
==> plug
Compiling 1 file (.erl)
Compiling 40 files (.ex)
Generated plug app
==> phoenix_html
Compiling 9 files (.ex)
Generated phoenix_html app
==> phoenix_template
Compiling 4 files (.ex)
Generated phoenix_template app
==> phoenix_view
Compiling 1 file (.ex)
Generated phoenix_view app
==> castore
Compiling 1 file (.ex)
Generated castore app
==> esbuild
Compiling 3 files (.ex)
warning: Logger.warn/1 is deprecated. Use Logger.warning/2 instead
  lib/esbuild.ex:66: Esbuild.start/2

warning: Logger.warn/1 is deprecated. Use Logger.warning/2 instead
  lib/esbuild.ex:80: Esbuild.start/2

Generated esbuild app
==> plug_cowboy
Compiling 5 files (.ex)
warning: Logger.warn/1 is deprecated. Use Logger.warning/2 instead
  lib/plug/cowboy.ex:352: Plug.Cowboy.to_args/5

Generated plug_cowboy app
==> phoenix
Compiling 69 files (.ex)
warning: missing parentheses for expression following "do:" keyword. Parentheses are required to solve ambiguity inside keywords.

This error happens when you have function calls without parentheses inside keywords. For example:

    function(arg, one: nested_call a, b, c)
    function(arg, one: if expr, do: :this, else: :that)

In the examples above, we don't know if the arguments "b" and "c" apply to the function "function" or "nested_call". Or if the keywords "do" and "else" apply to the function "function" or "if". You can solve this by explicitly adding parentheses:

    function(arg, one: if(expr, do: :this, else: :that))
    function(arg, one: nested_call(a, b, c))

Ambiguity found at:
  lib/phoenix/controller.ex:998

warning: Logger.warn/1 is deprecated. Use Logger.warning/2 instead
  lib/phoenix/code_reloader/server.ex:50: Phoenix.CodeReloader.Server.handle_call/3

warning: Logger.warn/1 is deprecated. Use Logger.warning/2 instead
  lib/phoenix/endpoint/supervisor.ex:34: Phoenix.Endpoint.Supervisor.init/1

warning: Logger.warn/1 is deprecated. Use Logger.warning/2 instead
  lib/phoenix/endpoint/supervisor.ex:57: Phoenix.Endpoint.Supervisor.init/1

warning: Logger.warn/1 is deprecated. Use Logger.warning/2 instead
  lib/phoenix/endpoint/supervisor.ex:78: Phoenix.Endpoint.Supervisor.pubsub_children/2

warning: Logger.warn/1 is deprecated. Use Logger.warning/2 instead
  lib/phoenix/endpoint/supervisor.ex:264: Phoenix.Endpoint.Supervisor.build_url/2

warning: Logger.warn/1 is deprecated. Use Logger.warning/2 instead
  lib/phoenix/controller.ex:1009: Phoenix.Controller.warn_if_ajax/1

warning: missing parentheses for expression following "do:" keyword. Parentheses are required to solve ambiguity inside keywords.

This error happens when you have function calls without parentheses inside keywords. For example:

    function(arg, one: nested_call a, b, c)
    function(arg, one: if expr, do: :this, else: :that)

In the examples above, we don't know if the arguments "b" and "c" apply to the function "function" or "nested_call". Or if the keywords "do" and "else" apply to the function "function" or "if". You can solve this by explicitly adding parentheses:

    function(arg, one: if(expr, do: :this, else: :that))
    function(arg, one: nested_call(a, b, c))

Ambiguity found at:
  lib/phoenix/router/resource.ex:71

warning: Logger.warn/1 is deprecated. Use Logger.warning/2 instead
  lib/phoenix/socket.ex:610: Phoenix.Socket.handle_in/4

Generated phoenix app
==> phoenix_live_reload
Compiling 4 files (.ex)
warning: Logger.warn/1 is deprecated. Use Logger.warning/2 instead
  lib/phoenix_live_reload/application.ex:32: Phoenix.LiveReloader.Application.start_link/0

Generated phoenix_live_reload app
==> phoenix_live_view
Compiling 35 files (.ex)
warning: Plug.Conn.Query.decode_pair/2 is deprecated. Use decode_init/0, decode_each/2, and decode_done/2 instead
Invalid call found at 5 locations:
  lib/phoenix_live_view/test/client_proxy.ex:1128: Phoenix.LiveViewTest.ClientProxy.form_defaults/3
  lib/phoenix_live_view/test/client_proxy.ex:1133: Phoenix.LiveViewTest.ClientProxy.form_defaults/3
  lib/phoenix_live_view/test/client_proxy.ex:1137: Phoenix.LiveViewTest.ClientProxy.form_defaults/3
  lib/phoenix_live_view/test/client_proxy.ex:1147: Phoenix.LiveViewTest.ClientProxy.form_defaults/3
  lib/phoenix_live_view/test/client_proxy.ex:1156: Phoenix.LiveViewTest.ClientProxy.form_defaults/3

Generated phoenix_live_view app
==> phoenix_ecto
Compiling 7 files (.ex)
Generated phoenix_ecto app
==> meow
Compiling 7 files (.ex)
warning: passing a string as a layout template in use options is deprecated, please pass {MeowWeb.LayoutView, :live} instead of {MeowWeb.LayoutView, "live.html"}
  (phoenix_live_view 0.18.18) lib/phoenix_live_view/utils.ex:204: Phoenix.LiveView.Utils.normalize_layout/2
  (phoenix_live_view 0.18.18) lib/phoenix_live_view.ex:505: Phoenix.LiveView."MACRO-__before_compile__"/2
  (elixir 1.15.5) src/elixir_dispatch.erl:224: :elixir_dispatch.expand_macro_fun/7
  (elixir 1.15.5) src/elixir_dispatch.erl:211: :elixir_dispatch.expand_require/6
  (elixir 1.15.5) src/elixir_dispatch.erl:135: :elixir_dispatch.dispatch_require/7
  (elixir 1.15.5) src/elixir_module.erl:441: :elixir_module.expand_callback/6

error: undefined function live_flash/2 (expected MeowWeb.LayoutView to define such a function or for it to be imported, but none are available)
  lib/meow_web/templates/layout/live.html.heex:4: MeowWeb.LayoutView."live.html"/1

error: undefined function live_flash/2 (expected MeowWeb.LayoutView to define such a function or for it to be imported, but none are available)
  lib/meow_web/templates/layout/live.html.heex:8: MeowWeb.LayoutView."live.html"/1


== Compilation error in file lib/meow_web/views/layout_view.ex ==
** (CompileError) lib/meow_web/views/layout_view.ex: cannot compile module MeowWeb.LayoutView (errors have been logged)


C:\CarbonX1\Phoenix\Public\meow>

Retour en arrière pour la version de phoenix_live_view

Nous revenons en arrière sur la version de live_view dans mix.exs :

  • {:phoenix_live_view, « ~> 0.18.4 »}, remplacé par {:phoenix_live_view, « ~> 0.17.5 »},

avec mix deps get :

C:\CarbonX1\Phoenix\Public\meow>mix deps.get
Resolving Hex dependencies...
Resolution completed in 0.173s
Unchanged:
  castore 1.0.4
  cowboy 2.10.0
  cowboy_telemetry 0.4.0
  cowlib 2.12.1
  db_connection 2.5.0
  decimal 2.1.1
  ecto 3.10.3
  ecto_sql 3.10.2
  esbuild 0.4.0
  file_system 0.2.10
  floki 0.32.0
  html_entities 0.5.2
  jason 1.4.1
  mime 2.0.5
  phoenix 1.6.16
  phoenix_ecto 4.4.0
  phoenix_html 3.3.3
  phoenix_live_reload 1.4.1
  phoenix_pubsub 2.1.3
  phoenix_template 1.0.3
  phoenix_view 2.0.2
  plug 1.15.1
  plug_cowboy 2.6.1
  plug_crypto 1.2.5
  postgrex 0.17.3
  ranch 1.8.0
  telemetry 1.2.1
  telemetry_metrics 0.6.1
  telemetry_poller 1.0.0
Downgraded:
  phoenix_live_view 0.18.18 => 0.17.14 (minor)
* Updating phoenix_live_view (Hex package)

C:\CarbonX1\Phoenix\Public\meow>mix phx.server

L’application compile et nous avons bien la mise à jour d’url :

Trier la liste  d'une table  LiveView#6-la vue liste, le bouton de trie et la maj de l url
Trier la liste d’une table LiveView#6-la vue liste, le bouton de trie et la maj de l url

Nous ne passons pas en version 1.7 car cela suppose de changer la structure du projet. Ce que nous ne souhaitons pas faire tant que nous n’avons pas réalisé l’intégralité des exemples de l’ouvrage.

Pour mettre à jour les dépendances, nous nous plaçons dans le dossier puis nous lançons la commande :

  • cd C:\CarbonX1\Phoenix\Public\meow
  • mix deps.get
  • mix phx.server

Nous accedons à la page avec :

  • http://localhost:4000/

Conclusion

L’application fonctionne, et nous pouvons trier la liste d’une table LiveView. Le trie se fait par colonne dans la vue en mode liste qui affiche le contenu d’une base de données avec LiveView. Les choix de l’utilisateur sont affichés dans l’url, ce qui permet de copier l’url pour la coller dans une autre session afin d’obtenir la même vue.

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

Laisser un commentaire