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 :
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
La fonction de trie reste active dans la page, et nous voyons bien le changement dans l’url avec les paramètres de trie :
LA fonction de filtre modifie l’url dans le navigateur mais ne fonctionne pas :
Nous testons une valeur négative, le contrôle fonctionne et affiche bien un message d’alerte :
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 :
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 :
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.