Filtrer la liste d’une table LiveView

Nous souhaitons filtrer les données affichées dans la liste d’une table LiveView.

Nous avons déjà vu comment mettre en place le trie des valeurs d’une table LiveView. Nous allons maintenant modifier cette application pour ajouter le filtre des données.

Nous poursuivons avec le chapitre 3 de l’ouvrage de Peter Ullrich.

Mise à jour des dépendances du projet initial

Commençons par vérifier le fonctionnement de notre projet initial.

Nous allons dans le dossier du projet, puis démarrons le projet pour voir si le code fonctionne :

  • cd C:\CarbonX1\Phoenix\Public\meow
  • code .
  • mix phx.server
C:\CarbonX1\Phoenix\Public\meow>mix phx.server
[notice] Application meow exited: Meow.Application.start(:normal, []) returned an error: shutdown: failed to start child: Meow.Repo
    ** (EXIT) an exception was raised:
        ** (UndefinedFunctionError) function Ecto.Adapters.Postgres.init/1 is undefined (module Ecto.Adapters.Postgres is not available)
            (ecto_sql 3.10.2) Ecto.Adapters.Postgres.init([repo: Meow.Repo, telemetry_prefix: [:meow, :repo], otp_app: :meow, timeout: 15000, username: "postgres", password: "postgres", database: "meow_dev", hostname: "localhost", show_sensitive_data_on_connection_error: true, pool_size: 10])
            (ecto 3.10.3) lib/ecto/repo/supervisor.ex:185: Ecto.Repo.Supervisor.init/1
            (stdlib 5.0.2) supervisor.erl:330: :supervisor.init/1
            (stdlib 5.0.2) gen_server.erl:962: :gen_server.init_it/2
            (stdlib 5.0.2) gen_server.erl:917: :gen_server.init_it/6
            (stdlib 5.0.2) proc_lib.erl:241: :proc_lib.init_p_do_apply/3
[notice] Application telemetry_poller exited: :stopped
[notice] Application telemetry_metrics exited: :stopped
[notice] Application esbuild exited: :stopped
[notice] Application phoenix_live_view exited: :stopped
[notice] Application phoenix_live_reload exited: :stopped
[notice] Application file_system exited: :stopped
[notice] Application ecto_sql exited: :stopped
[notice] Application postgrex exited: :stopped
[notice] Application db_connection exited: :stopped
[notice] Application phoenix_ecto exited: :stopped
[notice] Application ecto exited: :stopped
[notice] Application phoenix exited: :stopped
[notice] Application jason exited: :stopped
[notice] Application decimal exited: :stopped
[notice] Application plug_cowboy exited: :stopped
[notice] Application cowboy_telemetry exited: :stopped
[notice] Application cowboy exited: :stopped
[notice] Application ranch exited: :stopped
[notice] Application cowlib exited: :stopped
[notice] Application castore exited: :stopped
[notice] Application phoenix_view exited: :stopped
[notice] Application phoenix_template exited: :stopped
[notice] Application phoenix_html exited: :stopped
[notice] Application phoenix_pubsub exited: :stopped
[notice] Application plug exited: :stopped
[notice] Application telemetry exited: :stopped
[notice] Application plug_crypto exited: :stopped
[notice] Application mime exited: :stopped
[notice] Application eex exited: :stopped
[notice] Application runtime_tools exited: :stopped
** (Mix) Could not start application meow: Meow.Application.start(:normal, []) returned an error: shutdown: failed to start child: Meow.Repo
    ** (EXIT) an exception was raised:
        ** (UndefinedFunctionError) function Ecto.Adapters.Postgres.init/1 is undefined (module Ecto.Adapters.Postgres is not available)
            (ecto_sql 3.10.2) Ecto.Adapters.Postgres.init([repo: Meow.Repo, telemetry_prefix: [:meow, :repo], otp_app: :meow, timeout: 15000, username: "postgres", password: "postgres", database: "meow_dev", hostname: "localhost", show_sensitive_data_on_connection_error: true, pool_size: 10])
            (ecto 3.10.3) lib/ecto/repo/supervisor.ex:185: Ecto.Repo.Supervisor.init/1
            (stdlib 5.0.2) supervisor.erl:330: :supervisor.init/1
            (stdlib 5.0.2) gen_server.erl:962: :gen_server.init_it/2
            (stdlib 5.0.2) gen_server.erl:917: :gen_server.init_it/6
            (stdlib 5.0.2) proc_lib.erl:241: :proc_lib.init_p_do_apply/3

C:\CarbonX1\Phoenix\Public\meow>

Nous devons relancer l’initialisation du projet avec construction de la base de données du projet.

  • psql –version ‘connaitre la version de psql
  • psql -U postgres ‘pour se connecter en tant qu’utilisateur username: postgres
  • \c ‘pour vérifier la connexion
  • \l ‘liste des base de données
C:\CarbonX1\Phoenix\Public\meow>psql -U postgres
Mot de passe pour l'utilisateur postgres :
psql (16.1)
Attention : l'encodage console (850) diffère de l'encodage Windows (1252).
            Les caractères 8 bits peuvent ne pas fonctionner correctement.
            Voir la section « Notes aux utilisateurs de Windows » de la page
            référence de psql pour les détails.
Saisissez « help » pour l'aide.

postgres=# \c
Vous êtes maintenant connecté à la base de données « postgres » en tant qu'utilisateur « postgres ».
postgres=# \l
                                                                        Liste des bases de donnÚes
           Nom            | PropriÚtaire | Encodage | Fournisseur de locale |  Collationnement   |    Type caract.    | Locale ICU | RÞgles ICU : |    Droits d'accÞs
--------------------------+--------------+----------+-----------------------+--------------------+--------------------+------------+--------------+-----------------------
 blogset_dev              | postgres     | UTF8     | libc                  | French_France.1252 | French_France.1252 |            |              |
 blogset_test             | postgres     | UTF8     | libc                  | French_France.1252 | French_France.1252 |            |              |
 live_view_studio_2ed_dev | postgres     | UTF8     | libc                  | French_France.1252 | French_France.1252 |            |              |
 postgres                 | postgres     | UTF8     | libc                  | French_France.1252 | French_France.1252 |            |              |
 template0                | postgres     | UTF8     | libc                  | French_France.1252 | French_France.1252 |            |              | =c/postgres          +
                          |              |          |                       |                    |                    |            |              | postgres=CTc/postgres
 template1                | postgres     | UTF8     | libc                  | French_France.1252 | French_France.1252 |            |              | =c/postgres          +
                          |              |          |                       |                    |                    |            |              | postgres=CTc/postgres
(6 lignes)


postgres=# \q

C:\CarbonX1\Phoenix\Public\meow>

Nous voyons que le projet meow n’est plus présent dans notre base de données.

Relançon l’initialisation du projet avec :

  • 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/
C:\CarbonX1\Phoenix\Public\meow>mix deps.get
Resolving Hex dependencies...
Resolution completed in 0.189s
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_live_view 0.17.14
  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
All dependencies are up to date

C:\CarbonX1\Phoenix\Public\meow>mix ecto.setup
** (UndefinedFunctionError) function Ecto.Adapters.Postgres.__info__/1 is undefined (module Ecto.Adapters.Postgres is not available)
    (ecto_sql 3.10.2) Ecto.Adapters.Postgres.__info__(:attributes)
    (ecto 3.10.3) lib/mix/ecto.ex:151: Mix.Ecto.ensure_implements/3
    (ecto 3.10.3) lib/mix/tasks/ecto.create.ex:51: anonymous fn/3 in Mix.Tasks.Ecto.Create.run/1
    (elixir 1.15.5) lib/enum.ex:984: Enum."-each/2-lists^foreach/1-0-"/2
    (mix 1.15.5) lib/mix/task.ex:447: anonymous fn/3 in Mix.Task.run_task/5
    (mix 1.15.5) lib/mix/task.ex:506: Mix.Task.run_alias/6
    (mix 1.15.5) lib/mix/cli.ex:92: Mix.CLI.run_task/2
    c:/Program Files/Elixir/bin/mix:2: (file)

C:\CarbonX1\Phoenix\Public\meow>

Nous avons mis à jour PostgreSQL vers la version 16 depuis la création du projet Trier la liste d’une table liveview.

Nous forçons la mise à jour de Postgrex :

  • mix deps.update postgrex
C:\CarbonX1\Phoenix\Public\meow>mix deps.update postgrex
Resolving Hex dependencies...
Resolution completed in 0.196s
Unchanged:
  castore 1.0.4
  cowboy 2.10.0
  cowboy_telemetry 0.4.0
  cowlib 2.12.1
  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_live_view 0.17.14
  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
  ranch 1.8.0
  telemetry 1.2.1
  telemetry_metrics 0.6.1
  telemetry_poller 1.0.0
Upgraded:
  db_connection 2.5.0 => 2.6.0
  postgrex 0.17.3 => 0.17.4
* Updating postgrex (Hex package)
* Updating db_connection (Hex package)

C:\CarbonX1\Phoenix\Public\meow>

Puis nous relançons :

  • installer les dépendances avec : mix deps.get
  • créer et migrer la base de données : mix ecto.setup
C:\CarbonX1\Phoenix\Public\meow>mix ecto.setup
==> db_connection
Compiling 15 files (.ex)
Generated db_connection app
==> postgrex
Compiling 68 files (.ex)
Generated postgrex app
==> ecto_sql
Compiling 23 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
==> meow
Compiling 1 file (.ex)

== Compilation error in file lib/meow/repo.ex ==
** (ArgumentError) adapter Ecto.Adapters.Postgres was not compiled, ensure it is correct and it is included as a project dependency
    (ecto 3.10.3) lib/ecto/repo/supervisor.ex:61: Ecto.Repo.Supervisor.compile_config/2
    lib/meow/repo.ex:2: (module)

C:\CarbonX1\Phoenix\Public\meow>

Nous forçons la mise à jour de teoutes les dépendances :

  • mix clean –deps && mix deps.get && mix deps.compile –all
C:\CarbonX1\Phoenix\Public\meow>mix clean --deps && mix deps.get && mix deps.compile --all
Resolving Hex dependencies...
Resolution completed in 0.176s
Unchanged:
  castore 1.0.4
  cowboy 2.10.0
  cowboy_telemetry 0.4.0
  cowlib 2.12.1
  db_connection 2.6.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_live_view 0.17.14
  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.4
  ranch 1.8.0
  telemetry 1.2.1
  telemetry_metrics 0.6.1
  telemetry_poller 1.0.0
All dependencies are up to date
==> file_system
Compiling 7 files (.ex)
Generated file_system 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 10 files (.ex)
Generated jason 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
===> Analyzing applications...
===> Compiling ranch
==> 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 23 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 31 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:1095: Phoenix.LiveViewTest.ClientProxy.form_defaults/3
  lib/phoenix_live_view/test/client_proxy.ex:1100: Phoenix.LiveViewTest.ClientProxy.form_defaults/3
  lib/phoenix_live_view/test/client_proxy.ex:1110: Phoenix.LiveViewTest.ClientProxy.form_defaults/3
  lib/phoenix_live_view/test/client_proxy.ex:1119: Phoenix.LiveViewTest.ClientProxy.form_defaults/3

Generated phoenix_live_view app
==> phoenix_ecto
Compiling 7 files (.ex)
Generated phoenix_ecto app

C:\CarbonX1\Phoenix\Public\meow>

Puis nous relançons le setup :

  • installer les dépendances avec : mix deps.get
  • créer et migrer la base de données : mix ecto.setup
C:\CarbonX1\Phoenix\Public\meow>mix ecto.setup
Compiling 19 files (.ex)

== Compilation error in file lib/meow/repo.ex ==
** (ArgumentError) adapter Ecto.Adapters.Postgres was not compiled, ensure it is correct and it is included as a project dependency
    (ecto 3.10.3) lib/ecto/repo/supervisor.ex:61: Ecto.Repo.Supervisor.compile_config/2
    lib/meow/repo.ex:2: (module)

C:\CarbonX1\Phoenix\Public\meow>

Nous modifions le fichiers mix.exs :

  # Specifies your project dependencies.
  #
  # Type `mix help deps` for examples and options.
  defp deps do
    [
      {:phoenix, "~> 1.6.16"},
      {:phoenix_ecto, "~> 4.4"},
      {:ecto, "~> 3.10.3"},
      {: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 allons modifier la dépendance d’ecto dans le fichier mix.exs :

  • {:ecto, « ~> 3.11 »}, ‘ligne 8
  • {:ecto_sql, « ~> 3.11 »}, ‘ligne 9
C:\CarbonX1\Phoenix\Public\meow>mix deps.update postgrex
Resolving Hex dependencies...
Resolution completed in 0.196s
Unchanged:
  castore 1.0.4
  cowboy 2.10.0
  cowboy_telemetry 0.4.0
  cowlib 2.12.1
  db_connection 2.6.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
  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_live_view 0.17.14
  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.4
  ranch 1.8.0
  telemetry 1.2.1
  telemetry_metrics 0.6.1
  telemetry_poller 1.0.0
Upgraded:
  ecto 3.10.3 => 3.11.1
  ecto_sql 3.10.2 => 3.11.1
* Updating ecto (Hex package)
* Updating ecto_sql (Hex package)

C:\CarbonX1\Phoenix\Public\meow>mix deps.get

Puis nous relançons le setup :

C:\CarbonX1\Phoenix\Public\meow>mix ecto.setup
==> ecto
Compiling 56 files (.ex)
Generated ecto app
==> ecto_sql
Compiling 25 files (.ex)
Generated ecto_sql 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

17:51:10.015 [info] == Running 20211105111549 Meow.Repo.Migrations.CreateMeerkats.change/0 forward

17:51:10.015 [info] create table meerkats

17:51:10.029 [info] create index meerkats_name_index

17:51:10.032 [info] create index meerkats_weight_index

17:51:10.035 [info] create index meerkats_gender_index

17:51:10.038 [info] == Migrated 20211105111549 in 0.0s
[debug] QUERY OK source="meerkats" db=8.6ms queue=1.6ms idle=0.0ms
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) ["Kat Twinky", ~N[2023-12-20 16:51:10], ~N[2023-12-20 16:51:10], "Cat Cindy Clawford", ~N[2023-12-20 16:51:10], ~N[2023-12-20 16:51:10], "Cat Opie", ~N[2023-12-20 16:51:10], ~N[2023-12-20 16:51:10], "Big Sushi", ~N[2023-12-20 16:51:10], ~N[2023-12-20 16:51:10], "Cat Sriracha", ~N[2023-12-20 16:51:10], ~N[2023-12-20 16:51:10], "Cat Frodo", ~N[2023-12-20 16:51:10], ~N[2023-12-20 16:51:10], "Kattie Ricky Ticky Tabby", ~N[2023-12-20 16:51:10], ~N[2023-12-20 16:51:10], "Rusty Boots", ~N[2023-12-20 16:51:10], ~N[2023-12-20 16:51:10], "Kat Puddy Tat", ~N[2023-12-20 16:51:10], ~N[2023-12-20 16:51:10], "Little Porkchop", ~N[2023-12-20 16:51:10], ~N[2023-12-20 16:51:10], "Cat Meowsie", ~N[2023-12-20 16:51:10], ~N[2023-12-20 16:51:10], "Meery Ricky Ticky Tabby", ~N[2023-12-20 16:51:10], ~N[2023-12-20 16:51:10], "Meery Wasabi", ~N[2023-12-20 16:51:10], ~N[2023-12-20 16:51:10], "Little Miss Piggy", ~N[2023-12-20 16:51:10], ~N[2023-12-20 16:51:10], "Meery Clawdia", ~N[2023-12-20 16:51:10], ~N[2023-12-20 16:51:10], "Rusty Bacon", ~N[2023-12-20 16:51:10], ~N[2023-12-20 16:51:10], "Kat Cat Benatar", ~N[2023-12-20 16:51:10], ...]

C:\CarbonX1\Phoenix\Public\meow>

Nous pouvons aller sur la base de données :

  • psql -U postgres ‘pour se connecter en tant qu’utilisateur username: postgres
  • \c ‘pour vérifier la connexion
  • \l ‘liste des base de données
  • \q ‘pour quitter postgreSQL
C:\CarbonX1\Phoenix\Public\meow>psql -U postgres
Mot de passe pour l'utilisateur postgres :
psql (16.1)
Attention : l'encodage console (850) diffère de l'encodage Windows (1252).
            Les caractères 8 bits peuvent ne pas fonctionner correctement.
            Voir la section « Notes aux utilisateurs de Windows » de la page
            référence de psql pour les détails.
Saisissez « help » pour l'aide.

postgres=# \c
Vous êtes maintenant connecté à la base de données « postgres » en tant qu'utilisateur « postgres ».
postgres=# \l
                                                                        Liste des bases de donnÚes
           Nom            | PropriÚtaire | Encodage | Fournisseur de locale |  Collationnement   |    Type caract.    | Locale ICU | RÞgles ICU : |    Droits d'accÞs
--------------------------+--------------+----------+-----------------------+--------------------+--------------------+------------+--------------+-----------------------
 blogset_dev              | postgres     | UTF8     | libc                  | French_France.1252 | French_France.1252 |            |              |
 blogset_test             | postgres     | UTF8     | libc                  | French_France.1252 | French_France.1252 |            |              |
 live_view_studio_2ed_dev | postgres     | UTF8     | libc                  | French_France.1252 | French_France.1252 |            |              |
 meow_dev                 | postgres     | UTF8     | libc                  | French_France.1252 | French_France.1252 |            |              |
 postgres                 | postgres     | UTF8     | libc                  | French_France.1252 | French_France.1252 |            |              |
 template0                | postgres     | UTF8     | libc                  | French_France.1252 | French_France.1252 |            |              | =c/postgres          +
                          |              |          |                       |                    |                    |            |              | postgres=CTc/postgres
 template1                | postgres     | UTF8     | libc                  | French_France.1252 | French_France.1252 |            |              | =c/postgres          +
                          |              |          |                       |                    |                    |            |              | postgres=CTc/postgres
(7 lignes)


postgres=# \q

C:\CarbonX1\Phoenix\Public\meow>

La base de données pour le projet meow_dev est bien visible dans la liste des tables. Nous pouvons maintenant aller voir le projet :

  • démarrer Phoenix : mix phx.server
  • visiter l’application sur : http://localhost:4000/
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
[watch] build finished, watching for changes...
[info] GET /
[debug] Processing with MeowWeb.MeerkatLive.Elixir.MeowWeb.MeerkatLive/2
  Parameters: %{}
  Pipelines: [:browser]
[debug] QUERY OK source="meerkats" db=38.1ms queue=1.3ms idle=538.6ms
SELECT m0."id", m0."name", m0."inserted_at", m0."updated_at" FROM "meerkats" AS m0 ORDER BY m0."id" []
[info] Sent 200 in 659ms
[info] CONNECTED TO Phoenix.LiveView.Socket in 102µs
  Transport: :websocket
  Serializer: Phoenix.Socket.V2.JSONSerializer
  Parameters: %{"_csrf_token" => "MTQXMj0sd3UqAn0RNlInIiYQKR86CSJqZGxwkbEMnk1IldmJtxLPbch_", "_mounts" => "0", "_track_static" => %{"0" => "http://localhost:4000/assets/app.css", "1" => "http://localhost:4000/assets/app.js"}, "vsn" => "2.0.0"}
[debug] QUERY OK source="meerkats" db=4.8ms idle=1519.0ms
SELECT m0."id", m0."name", m0."inserted_at", m0."updated_at" FROM "meerkats" AS m0 ORDER BY m0."id" []
[debug] QUERY OK source="meerkats" db=5.0ms queue=1.4ms idle=1306.6ms
SELECT m0."id", m0."name", m0."inserted_at", m0."updated_at" FROM "meerkats" AS m0 ORDER BY m0."id" DESC []
[debug] QUERY OK source="meerkats" db=7.2ms queue=1.8ms idle=1357.1ms
SELECT m0."id", m0."name", m0."inserted_at", m0."updated_at" FROM "meerkats" AS m0 ORDER BY m0."name" []
[debug] QUERY OK source="meerkats" db=1.5ms queue=0.8ms idle=1809.6ms
SELECT m0."id", m0."name", m0."inserted_at", m0."updated_at" FROM "meerkats" AS m0 ORDER BY m0."name" DESC []

Le projet fonctionne et nous voyons bien la page avec la liste et les fonctions de trie :

Filtrer la liste d'une table LiveView#1-La table LiveView avec le trie actif
Filtrer la liste d’une table LiveView#1-La table LiveView avec le trie actif

Nous pouvons maintenant commencer le chapitre 3 sur la mise en place des filtres.

Mettre en place les filtres de la liste d’une table LiveView

Dans le précédent article, nous avons vu comment ajouter le trie d’une liste dans une table avec LiveView au niveau du context du projet. Nous avons le fichier merkats.ex :

MEOW/lib/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(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

Meerkats.ex défini le contexte au sens de Ecto. C’est dans ce module que nous trouvons le code permettant d’appeler la base de données avec des fonctions construites sur mesure pour le projet. La méthode qui nous intéresse ici est list_meerkats. Cette méthode construit une requête SQL vers PostgreSQL à travers Ecto à partir d’une Query.

  • from (m in Meerkat) ‘ construit la query sur la table meerkats
  • Repo.All(query) ‘applique la requête sur la base grâce au connecteur Repo

Et nous pouvons construire des compléments à cette requête :

  • sort ‘avec ordrer_by
  • filter ‘avec where

Nous avons déjà construit le sort dans l’article précédent. Nous allons ajouter le filtre :

  • defp filter(query, opts)

Comme nous construisons le filtre avec des fonctions, nous pouvons construire cette fonction comme toute autre fonction Elixir. Cela nous permet de décomposer le filtre en créant une fonction par colonne à filtrer soit une fonction pour :name et une fonction pour :id.

Nous pouvons avec profit lire l’article expliquant le principe de Query avec Ecto. Ecto permet de créer des fonctions chaînable avec le pipe ‘|>

Nous allons appliquer la Query de recherche par attribut, et nous obtenons le code suivant :

MEOW/lib/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(opts) do
		from(m in Meerkat)
		|> filter(opts)
		|> 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

	defp filter(query, opts) do
		query
		|> filter_by_id(opts)
		|> filter_by_name(opts)
	end

	defp filter_by_id(query, %{id: id})
			when is_integer(id) do
		where(query, id: ^id)
	end

	defp filter_by_id(query, _opts), do: query

	defp filter_by_name(query, %{name: name})
			when is_binary(name) and name != "" do
		query_string = "%#{name}%"
		where(query, [m], ilike(m.name,  ^query_string))
	end

	defp filter_by_name(query, _opts), do: query

end

Le code proposé est assez simple. Nous ajoutons une fonction de filtre et cette fonction de filtre passe par les filtres spécialisés sur l’id et le name : filter_by_id et filter_by_name (lignes 27 et 28).

Nous utilisons ici les deux constructions de where :

  • where (query, id: ^id) #ligne 33 : nous avons un attribut ‘:id’ et sa valeur associée ^id. Le symbole ^ est l’interpolation permettant de prendre la valeur de la variable id
  • where(query, [m], ilike(m.name, ^query_string)) #ligne 41 : [m] donne la liste des liens (binding) utilisés dans la fonction qui est passée en paramètre, ici ilike().

Le pattern matching permet de prendre dans la variable opts passée en paramètre les valeurs id ou name, en fonction des besoins de l’un ou l’autre filtre :

  • filter_by_id (query, %{id: id}) #ligne 31 : utilise le pattern matching sur l’id : %{id: id}.
  • filter_by_name (query, %{name: name}) #ligne 8 : pattern matching sur name : %{name: name}

Le filtrage sur le nom utilise une fonction spécifique de PostgreSQL : ILIKE. Cette fonction ILIKE permet une comparaison indépendante de la casse (Majuscule/minuscule). Nous avons 2 caractères spéciaux permettant de filtrer avec ILIKE :

  • % ‘ permet de remplacer autant de caractères que nécessaire
  • _ ‘ remplace un seul caractère

Ainsi lorsque nous créons le pattern %marie%, nous trouvons tous les prénoms contenant marie au début au milieu ou à la fin du prénom. Si nous utilisons le pattern _aul%, nous trouvons tous les prénoms contenant aul avec une seule lettre devant, par exemple : Paul, Saul, …

Pour compléter cette explication nous pouvons lire l’article pattern matching avec PostgreSQL.

En ligne 40, nous construisons le pattern PostgreSQL en intégrant la variable avec #{name}, ce qui donne l’expression : « %#{name}% » :

  • % pour remplacer un nombre quelconque de caractères
  • #{name} pour mettre la variable name

Activer les filtres pour la liste d’une table LiveView

Pour activer les filtres, nous allons créer un formulaire dans un LiveComponent spécialisé. Le LiveComposant utilisera un Shemaless changeset pour vérifier la validité des données fournies par l’utilisateur.

Le LiveComponenet permet de structurer la page web avec des composant imbricables et réutilisables, ce qui permet de simplifier le code. Nous avons la possibilité de gérer des évènement à l’intérieure du composant ou entre le composant et la page d’accueil.

Le LiveComponent permet de gérer un bloc visuel intégré dans la page LiveView. Il est créé comme un module, avec un use :live_component en première déclaration.

Le liveComponent contient les fonctions :

  • render ‘partie visuelle du commposant
  • update ‘gérer les changements
  • handle_event ‘pour chaque action déclarée dans la partie render

MEOW/lib/meow_web/live/filter_component.ex :

defmodule MeowWeb.MeerkatLive.FilterComponent do
	use MeowWeb, :live_component

  	alias MeowWeb.Forms.FilterForm

	def render(assigns) do
		~H"""
		<div>
			<.form let={f} for={@changeset} as="filter"
          		phx-submit="search" phx-target={@myself} >
            <div>
              <div>
                <%= label f, :id %>
                <%= number_input f, :id %>
                <%= error_tag f, :id %>
              </div>
              <div>
                <%= label f, :name %>
                <%= text_input f, :name %>
                <%= error_tag f, :name %>
              </div>
              <%= submit "Search" %>
            </div>
            </.form>
      	</div>
		"""
	end

    def update(%{filter: filter}, socket) do
      {:ok, assign(socket, :changeset, FilterForm.change_values(filter))}
    end

	def handle_event("search", %{"filter" => filter}, socket) do
      case FilterForm.parse(filter) do
        {:ok, opts} ->
          send(self(), {:update, opts})
          {:noreply, socket}
        {:error, changeset} ->
          {:noreply, assign(socket, :changeset, changeset)}
      end
	end

end

Nous créons le filtrage du formulaire dans FilterForm :

MEOW/lib/meow_web/forms/filter_form.ex :

defmodule MeowWeb.Forms.FilterForm do
	import Ecto.Changeset

	@fields %{
		id: :integer,
		name: :string
	}
  
	@default_values %{
		id: nil,
		name: nil
	}

	def default_values(overrides \\ %{}) do
    	Map.merge(@default_values, overrides)
  	end
  
	def parse(params) do
		{@default_values, @fields}
		|> cast(params, Map.keys(@fields))
    |> validate_number(:id, greater_than_or_equal_to: 0)
		|> apply_action(:insert)
	end
  
  def change_values(values \\ @default_values) do
    {values, @fields}
    |> cast(%{}, Map.keys(@fields))
  end
  
end

Utiliser les filtres pour la liste d’une table LiveView

Nous ajoutons le formulaire de filtrage au début de la page web.

MEOW/lib/meow_web/live/meerkat_live.html.heex :

<div id="table-container">

  <.live_component
    module={MeowWeb.MeerkatLive.FilterComponent}
    id="filter"
    filter={@filter}
  />

  <table>
  	<thead>

Nous avons ajouté les lignes 3 à 7.

Nous devons maintenant déclarer @filter :

MEOW/lib/meow_web/live/meerkat_live.ex :

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

	# Add this alias:
	alias MeowWeb.Forms.SortingForm

	# Ajoute cet alias chapitre 3
	alias MeowWeb.Forms.FilterForm

	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

	# Modifier cette fonction chapitre 3:
	defp parse_params(socket, params) do
		with 	{:ok, sorting_opts} <- SortingForm.parse(params),
				{:ok, filter_opts} <- FilterForm.parse(params) do
				socket
				|> assign_filter(filter_opts)
				|> assign_sorting(sorting_opts)
		else
			_error ->
				socket
				|> assign_filter()
				|> assign_sorting()
		end
	end

	# Add this function:
	defp assign_sorting(socket, overrides \\ %{}) do
		opts = Map.merge(SortingForm.default_values(), overrides)
		assign(socket, :sorting, opts)
	end

	# Ajoute cette fonction Chapitre 3 :
	defp assign_filter(socket, overrides \\ %{}) do
		assign(socket, :filter, FilterForm.default_values(overrides))
	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

Visualiser le formulaire pour filtrer la liste

Pour tester l’application :

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

Nous voyons bien le formulaire créé avec le LiveComponent

Filtrer la liste d'une table LiveView#2-Vue du formulaire pour filtrer la liste LiveView
Filtrer la liste d’une table LiveView#2-Vue du formulaire pour filtrer la liste LiveView

La fonction de trie reste active dans la page, et nous voyons bien le changement dans l’url avec les paramètres de trie :

Filtrer la liste d'une table LiveView#3-La fonction de trie est active dans LiveView
Filtrer la liste d’une table LiveView#3-La fonction de trie est active dans LiveView

LA fonction de filtre modifie l’url dans le navigateur mais ne fonctionne pas :

Filtrer la liste d'une table LiveView#4-Le filtre modifie l'url du navigateur
Filtrer la liste d’une table LiveView#4-Le filtre modifie l’url du navigateur

Nous testons une valeur négative, le contrôle fonctionne et affiche bien un message d’alerte :

Filtrer la liste d'une table LiveView#5-Le contrôle sur valeur négative fonctionne
Filtrer la liste d’une table LiveView#5-Le contrôle sur valeur négative fonctionne

Nous avons besoin de corriger :

  • le fonctionnement du filtre
  • l’affichage dans l’url qui donne soit les elements du filtre soit les element du trie mais pas les deux
  • les valeurs vide du filtre sont aussi passées en parametre, ce que nous ne souahitons pas

Commençons par modifier le fichier meerkat_live pour prendre en compte le filtre.

MEOW/lib/meow_web/live/meerkat_live.ex :

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

	# Add this alias:
	alias MeowWeb.Forms.SortingForm

	# Ajoute cet alias chapitre 3
	alias MeowWeb.Forms.FilterForm

	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

  # Modifier cette fonction chapitre 3
	def handle_info({:update, opts}, socket) do
		params = merge_and_sanitize_params(socket, opts)
		path = Routes.live_path(socket, __MODULE__, params)
		{:noreply, push_patch(socket, to: path, replace: true)}
	end

	# Modifier cette fonction chapitre 3:
	defp parse_params(socket, params) do
		with 	{:ok, sorting_opts} <- SortingForm.parse(params),
					{:ok, filter_opts} <- FilterForm.parse(params) do
				socket
				|> assign_filter(filter_opts)
				|> assign_sorting(sorting_opts)
		else
			_error ->
				socket
				|> assign_filter()
				|> assign_sorting()
		end
	end

	# Add this function:
	defp assign_sorting(socket, overrides \\ %{}) do
		opts = Map.merge(SortingForm.default_values(), overrides)
		assign(socket, :sorting, opts)
	end

	# Ajoute cette fonction Chapitre 3 :
	defp assign_filter(socket, overrides \\ %{}) do
		assign(socket, :filter, FilterForm.default_values(overrides))
	end

	# Modifier cette fonction chapitre 3
	defp assign_meerkats(socket) do
		params = merge_and_sanitize_params(socket)
		assign(socket, :meerkats, Meerkats.list_meerkats(params))
	end

	# Ajoute cette fonction Chapitre 3 :
	defp merge_and_sanitize_params(socket, overrides \\ %{}) do
		%{sorting: sorting, filter: filter} = socket.assigns
		%{}
		|> Map.merge(sorting)
		|> Map.merge(filter)
		|> Map.merge(overrides)
		|> Enum.reject(fn {_key, value} -> is_nil(value) end)
		|> Map.new()
	end
end

Comme nous avons laissé le serveur actif ainsi que la page web, dès que nous enregistrons les modifications de ce fichier, la page web se met à jour avec le filtre sur la valeur demandée :

Filtrer la liste d'une table LiveView#6-Le filtre fonctionne sur la valeur de l'id
Filtrer la liste d’une table LiveView#6-Le filtre fonctionne sur la valeur de l’id

Nous voyons bien la seule ligne de la liste avec l’id no 2. Par contre, dans l’url nous avons toujours la propriété vide de name qui s’affiche. Si nous jouons maintenant avec les filtres et les tries, nous voyons bien l’application fonctionner avec l’url présentant les seules valeurs utiles :

Filtrer la liste d'une table LiveView#7-Page avec le filtre et le trie actif pour la liste dans la table LiveView
Filtrer la liste d’une table LiveView#7-Page avec le filtre et le trie actif pour la liste dans la table LiveView

Conclusion

Nous avons mis en place le filtre sur les colonnes avec une zone formulaire permettant de réaliser la saisie des colonnes à filtrer.

La mise en place du filtre fonctionne avec le trie toujours actif. Nous avons dans l’url les propriétés de filtrage et de trie, ce qui permet de copier cette url dans un autre navigateur et d’obtenir la même vue.

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

Laisser un commentaire