Ajax facile avec Ruby on Rails, Prototype, script.aculo.us et les RJSDate de publication : 21 Mai 2007 , Date de mise à jour : 10 Octobre 2007
Par
Pierre-Baptiste Naigeon (Tutos, tests et articles web, Ruby et Rails)
Nous allons voir au cours de cet article la facilité de mise en place qu'offre Rails pour faire de l'ajax, les superbes effets que cela permet, et tout cela sans une ligne de Javascript ou presque, grâce aux librairies Prototype et script.aculo.us.
Nous verrons également l'intérêt des RJS.
I. Introduction
II. Rappels
II-A. Génération d'un contrôleur
II-B. Fichier de configuration de la base de données
II-C. Génération d'un modèle
II-D. Utilisation des fichiers de migration
II-E. Utilisation des fixtures
utilisateurs.yml
utilisateurs.csv
Déploiement des fixtures
III. Les RJS (Remote JavaScript)
IV. Quelques petits effets graphiques
IV-A. Effets combinés
Fade / Appear / ToggleAppear
Highlight
SlideUp / SlideDown / ToggleSlide
Grow / Shrink
BlindUp / BlindDown / ToggleBlind
Pulsate
Fold
Puff
Squish
SwitchOff
DropOut
IV-B. Effets 'de base'
Insérer / Remplacer / Supprimer un élément
Cacher / Afficher un élément / alterner entre les deux
Agrandir / Réduire un élément
Modifier le style d'un élément
Retarder un effet
Exécuter un effet au chargement de la page
Récapitulatif
V. Manipulation de formulaires
V-A. Ajouter dynamiquement un champ à un formulaire en fonction de la valeur d'un autre
V-B. Mettre à jour dynamiquement un champ
V-C. Validation dynamique d'un formulaire
form_remote_tag
submit_to_remote
VI. Auto-complétion
VII. Tri d'une liste d'éléments
VIII. Drag & Drop is easy
IX. Petit mélange des deux
X. Allons plus loin
X-A. Créer ses propres helpers
X-B. Créer ses propres fonctions Javascript
XI. Conclusion, remerciements et liens complémentaires
I. Introduction
AJAX est une technologie extrêmement intéressante à mettre en place, puisqu'elle permet une interaction forte avec le client. L'idée est de pouvoir mettre à jour des éléments de la page sans avoir à recharger la page complète.
Qui dit AJAX dit Javascript, mais Rails va nous permettre de faire tout cela sans en faire une ligne, ou presque.
Nous commencerons par un petit rappel pour les moins à l'aise d'entre vous, les autres peuvent passer directement au chapitre III (si vous êtes débutant avec Rails, vous pouvez aller consulter le tutorial " Initiation à Rails", par Yann Marec).
Dans ce cours, nous allons utiliser les différentes librairies Javascript de Rails.
Afin que ces librairies soient déclarées dans toutes nos pages, nous allons définir un fichier "app/views/layouts/application.rhtml" comme suit :
| application.rhtml |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<%= javascript_include_tag :defaults %>
<title><%= @body_title || "titre par défaut" %></title>
</head>
<body<%= @body_extras %>>
<%= yield %>
</body>
</html>
|
Ce fichier se charge d'inclure les librairies Javascript dont nous aurons besoin, et nous permettra de stipuler pour chaque vue une valeur pour la balise title, et éventuellement des options à passer à la balise body.
II. Rappels
II-A. Génération d'un contrôleur
Pour générer un contrôleur, nous taperons la ligne suivante à la racine de notre application Rails : "ruby script/generate controller nom_du_contrôleur".
Par exemple, nous souhaitons créer un contrôleur "test", ce qui nous donnera : "ruby script/generate controller test". Voici ce que Rails vous renverra :
| Retour lors de la création d'un contrôleur |
exists app/controllers/
exists app/helpers/
create app/views/test
exists test/functional/
create app/controllers/test_controller.rb
create test/functional/test_controller_test.rb
create app/helpers/test_helper.rb
|
Ruby nous a donc créé :
- create app/controllers/test_controller.rb : le contrôleur lui-même
- create app/views/test : le répertoire dans lequel nous placeront les vues associées au contrôleur
- create test/functional/test_controller_test.rb : un fichier dans lequel nous écrirons nos tests fonctionnels
- create app/helpers/test_helper.rb : un fichier qui contiendra les helpers (assistants) de notre contrôleur
II-B. Fichier de configuration de la base de données
Le fichier qui sert à configurer votre base de données est le suivant : "config/database.yml".
Il vous permet de configurer quelle base de données utiliser dans les trois modes de ruby : développement, test et production.
Voici un exemple pour le mode développement :
| Contenu du fichier 'database.yml' pour le mode développement |
development:
adapter: mysql
database: mon_application
username: root
password:
host: localhost
|
Configurez ce fichier en fonction de votre environnement de travail, et passons à la suite.
 |
Il faut tout de même créer la base de données à la main.
|
II-C. Génération d'un modèle
Pour générer un modèle, nous taperons la ligne suivante à la racine de notre application Rails : "ruby script/generate model nom_du_modele_au_singulier".
Par exemple, nous souhaitons créer un modèle "utilisateur", ce qui nous donnera : "ruby script/generate model utilisateur". Voici ce que Rails vous renverra :
| Retour lors de la création d'un modèle |
exists app/models/
exists test/unit/
exists test/fixtures/
create app/models/utilisateur.rb
create test/unit/utilisateur_test.rb
create test/fixtures/utilisateurs.yml
create db/migrate
create db/migrate/001_create_utilisateurs.rb
|
Ruby nous a donc créé :
- create app/models/utilisateur.rb : le modèle lui-même
- create test/unit/utilisateur_test.rb : un fichier dans lequel nous écrirons nos tests unitaires
- create test/fixtures/utilisateurs.yml : un fichier qui nous servira à remplir notre base lors des tests (je l'utiliserai pour remplir la base avec un jeu de données initial)
- create db/migrate/001_create_utilisateurs.rb : un fichier de migration, qui va nous servir à créer la table au départ
II-D. Utilisation des fichiers de migration
Les fichiers de migration sont situés dans le répertoire "db/migrate/" de votre application.
Ils peuvent servir à créer la structure de vos tables, à la modifier ou à les supprimer.
Les fichiers de migration sont extrêmement puissants, puisqu'ils permettent via une simple ligne de commande de migrer entre les différentes versions de vos bases de données, tout en gardant une traçabilité totale, et ce quel que soit le type de base de données que vous utilisez.
Voici le fichier de migration généré en même temps que le modèle :
| fichier de migration '001_create_utilisateurs.rb' |
class CreateUtilisateurs < ActiveRecord::Migration
def self.up
create_table :utilisateurs do |t|
end
end
def self.down
drop_table :utilisateurs
end
end
|
Supposons maintenant que nous souhaitions définir un nom et un âge pour un utilisateur.
Modifions le fichier comme suit :
| fichier de migration '001_create_utilisateurs.rb' |
class CreateUtilisateurs < ActiveRecord::Migration
def self.up
create_table :utilisateurs do |t|
t.column "nom", :string, :null => false
t.column "age", :integer, :null => true
end
end
def self.down
drop_table :utilisateurs
end
end
|
Il ne nous reste plus qu'à exécuter ce fichier de migration pour avoir notre table créée à l'aide de la commande suivante : "rake db:migrate VERSION=1" (le numéro de version correspond aux trois premiers chiffres de votre fichier de migration).
(in mon/path)
== CreateUtilisateurs: migrating ==============================================
-- create_table(:utilisateurs)
-> 0.1130s
== CreateUtilisateurs: migrated (0.1140s) =====================================
|
Si vous allez voir la structure de votre table récemment créée, vous vous apercevrez que Rails s'est permis de rajouter un champ "id" auto-incrémenté. Ceci est parfaitement normal, Rails a besoin d'un champ nommé id auto-incrémenté dans toutes les tables (en respectant les conventions).
Il ne nous reste plus qu'à remplir notre base avec des valeurs par défaut.
II-E. Utilisation des fixtures
Il existe deux types de fichiers pour remplir votre base. Par défaut, rails vous a créé un fichier yml. Il est également possible de le renommer en csv.
Nous allons voir comment remplir ces deux types de fichier :
utilisateurs.yml
Voici le fichier tel que je l'ai modifié pour remplir ma base avec trois utilisateurs :
| utilisateurs.yml |
utilisateur1:
nom: Riri
age: 12
utilisateur2:
nom: Fifi
age: 13
utilisateur3:
nom: Loulou
age: 14
|
Ici, je n'ai pas pris la peine de préciser les id. Il peut tout de fois être judicieux de le faire, surtout au cours de tests où vous souhaitez connaître l'id d'un utilisateur précis (cas de jointures par exemple).
 |
Si vous donnez deux fois le même identifiant à deux enregistrements différents, Rails remplacera le premier par le second.
|
utilisateurs.csv
Voici cette fois un fichier faisant exactement la même chose, mais au format csv :
| utilisateurs.csv |
nom, age
Riri, 12
Fifi, 13
Loulou, 14
|
Une fois de plus, je n'ai pas précisé les id.
La première ligne reprend simplement le nom des champs à remplir. Les lignes d'après contiennent les enregistrements, en suivant le "modèle" défini sur la première ligne.
Déploiement des fixtures
Il ne nous reste plus qu'à exécuter ce fichier pour remplir notre table.
Tapez, toujours à la racine de notre application, la ligne de commande suivante : "rake db:fixtures:load FIXTURES=utilisateurs".
Ruby ne vous affichera rien, mais les données sont bien insérées.
 |
Il est possible d'exécuter plusieurs fixtures en même temps. Pour cela, tapez "rake db:fixtures:load FIXTURES=nom_fixture1,nom_fixture2,...".
Cependant, vous ne devez pas avoir d'espaces ni à droite ni à gauche de la virgule, sous peine d'erreur.
|
III. Les RJS (Remote JavaScript)
Les RJS sont des modèles Javascript destinés à mettre dynamiquement à jour une page.
Ils se placent dans le répertoire "views" de notre application (dans le sous-répertoire du contrôleur correspondant), et prennent simplement l'extension RJS.
Il faut considérer les RJS comme une vue qui met à jour les éléments de la page.
Une action du contrôleur et un RJS peuvent (et doivent dans le cas où le RJS est spécifique à une action) porter exactement le même nom. Dans ce cas, ils s'exécuteront l'un après l'autre, d'abord l'action puis le RJS.
Le fait de passer par le contrôleur peut permettre d'affiner les actions qui seront exécutées dans le RJS.
Voyons tout de suite un petit exemple d'utilisation d'un RJS (ne vous attardez pas sur le code, nous y reviendrons à la fin).
Je me suis basé sur le modèle et le contrôleur créés au chapitre II.
| test_controller.rb |
class TestController < ApplicationController
def index
@users = Utilisateur.find(:all)
end
def make_bigger
@users = Utilisateur.find(:all, :conditions=>"age=14")
end
end
|
| index.rhtml |
<% @body_title = "Premier exemple de RJS"%>
<% @users.each do |user| %>
<span id="element_<%= user.id %>"><%= user.nom %></span>
<br />
<% end %>
<hr />
<%= link_to_remote "Faire grossir les items (age = 14)",
:url=>{:action=>"make_bigger"},
:before => "Element.show('wait_icon')",
:complete => "Element.hide('wait_icon')"
%>
<br />
<%= image_tag "ajax-loader.gif", :id=>"wait_icon", :style=>"display:none" %>
|
Notez simplement la présence de la variable "@body_title" définie dans notre vue qui va nous permettre d'affecter à la vue en cours un titre.
| make_bigger.rjs |
@users.each { |user|
page.call "new Effect.Scale", "element_#{user.id}", 200
}
|
Vous n'avez plus qu'à lancer votre serveur, à cliquer sur le lien et à admirer le résultat.
 |
Attention : un appel à un RJS est un appel serveur. Qui dit appel serveur dit temps de latence, utilisez-les donc en connaissance de cause.
|
Dans l'état actuel des choses, si la personne a désactivé Javascript, rien ne ne produira lors du click sur le lien. Pour comprendre pourquoi, il suffit de regarder le code source généré :
<a href="#" onclick="Element.show('wait_icon'); new Ajax.Request('/test/make_bigger', {asynchronous:true, evalScripts:true, onComplete:function(request){Element.hide('wait_icon')}}); return false;">Faire grossir les items</a>
<a href="/test/make_bigger">coucou</a>
|
Le href ne pointant sur rien, rien en se passera dans le cas où javascript est désactivé.
Mais il est possible de gérer ce problème.
Remplacez votre "link_to_remote" par le code suivant :
<%= link_to_remote "Faire grossir les items (age = 14)",
{
:url=>{:action=>"make_bigger"},
:before => "Element.show('wait_icon')",
:complete => "Element.hide('wait_icon')"
},
:href=>(url_for :action=>"make_bigger")
%>
|
Ici, nous lui précisons quelle valeur mettre dans l'attribut href de notre lien.
Il ne nous reste plus qu'à gérer dans notre contrôleur le cas ou la requête est une requête AJAX, ou bien une simple requête HTML.
Modifiez donc votre action "make_bigger" comme suit :
def make_bigger
respond_to do |requete|
requete.html {redirect_to :action=>"index"}
requete.js {
@users = Utilisateur.find(:all, :conditions=>"age=14")
render :action=>"make_bigger.rjs"
}
end
end
|
Nous gérons désormais dans notre contrôleur les différentes actions à effectuer en fonction du type de requête.
Pour en savoir plus sur comment gérer le cas où Javascript est désactivé, regardez du coté du plugin UJS (Unobtrusive JavaScript).
IV. Quelques petits effets graphiques
Nous allons voir dans ce chapitre quelques effets graphiques forts sympathiques, que nous apporte la librairie script.aculo.us.
IV-A. Effets combinés
Fade / Appear / ToggleAppear
Disparition par fondu, apparition par fondu, et alternance entre les deux.
Créons un nouveau contrôleur nommé "effets", puis créez le fichier "index.rhtml" comme suit :
| Fade / Appear / ToggleAppear |
<%= link_to_function("Fade") do |page|
page.visual_effect(:fade, "bout_de_page")
end
%>
<%= link_to_function("Appear") do |page|
page.visual_effect(:appear, "bout_de_page")
end
%>
<%= link_to_function("ToggleAppear") do |page|
page.visual_effect(:toggle_appear, "bout_de_page")
end
%>
<br /><br />
<div id="bout_de_page" style="background-color:#1188FF; width:350px; height:150px;">
<div>
<%= image_tag "rails.png"%>
<p>Bonjour à tous, ceci n'est qu'un petit exemple des possibilités de <b>script.aculo.us</b></p>
</div>
</div>
|
Je sais, les styles définis directement dans le code, on a déjà vu mieux, mais pour l'exemple ce sera amplement suffisant ;)
Il ne vous reste plus qu'à cliquer et à admirer.
Bien entendu, pour faire apparaitre un élément, il faut déjà l'avoir fait disparaitre. Commencez-donc par cliquer sur "Fade" avant de cliquer sur "Appear".
Pour tous les exemples suivants, nous contenterons d'ajouter de nouveaux liens sous le dernier "link_to_function" trouvé.
Le div imbriqué dans le premier n'est là que pour que certains effets passent parfaitement.
Juste à titre d'information, il est également possible d'écrire un "link_to_function" comme suit (reprenons l'exemple du ToggleAppear) :
<%= link_to_function "Hello (local)", update_page { |page|
page.visual_effect(:toggle_appear, "bout_de_page")
}
%>
|
Ce n'est qu'une question de choix.
Highlight
Mise en valeur visuelle d'un élément.
| Highlight |
<%= link_to_function("Highlight") do |page|
page.visual_effect(:highlight, "bout_de_page")
end
%>
|
SlideUp / SlideDown / ToggleSlide
Disparition vers le haut, apparition vers le bas, et alternance entre les deux.
| SlideUp / SlideDown / ToggleSlide |
<%= link_to_function("SlideUp") do |page|
page.visual_effect(:slide_up, "bout_de_page")
end
%>
<%= link_to_function("SlideDown") do |page|
page.visual_effect(:slide_down, "bout_de_page")
end
%>
<%= link_to_function("ToggleSlide") do |page|
page.visual_effect(:toggle_slide, "bout_de_page")
end
%>
|
Notez simplement ici la séparation des deux mots qui s'est transformée en underscore (valable à chaque fois que vous rencontrerez le cas).
Grow / Shrink
Apparition en zoom, disparition en zoom.
| Grow / Shrink |
<%= link_to_function("Grow") do |page|
page.visual_effect(:grow, "bout_de_page")
end
%>
<%= link_to_function("Shrink") do |page|
page.visual_effect(:shrink, "bout_de_page")
end
%>
|
BlindUp / BlindDown / ToggleBlind
Disparition vers le haut, apparition vers le bas, et alternance entre les deux.
| BlindUp / BlindDown / ToggleBlind |
<%= link_to_function("BlindUp") do |page|
page.visual_effect(:blind_up, "bout_de_page")
end
%>
<%= link_to_function("BlindDown") do |page|
page.visual_effect(:blind_down, "bout_de_page")
end
%>
<%= link_to_function("ToggleBlind") do |page|
page.visual_effect(:toggle_blind, "bout_de_page")
end
%>
|
Pulsate
Faire clignoter un élément.
| Pulsate |
<%= link_to_function("Pulsate") do |page|
page.visual_effect(:pulsate, "bout_de_page")
end
%>
|
Fold
Disparition vers le haut puis vers la gauche.
| Fold |
<%= link_to_function("Fold") do |page|
page.visual_effect(:fold, "bout_de_page")
end
%>
|
Puff
Disparition par zoom.
| Puff |
<%= link_to_function("Puff") do |page|
page.visual_effect(:puff, "bout_de_page")
end
%>
|
Squish
Disparition vers le haut et vers la gauche.
| Squish |
<%= link_to_function("Squish") do |page|
page.visual_effect(:squish, "bout_de_page")
end
%>
|
SwitchOff
Disparition à la manière de l'extinction des vieux téléviseurs.
| SwitchOff |
<%= link_to_function("SwitchOff") do |page|
page.visual_effect(:switch_off, "bout_de_page")
end
%>
|
DropOut
Disparition vers le bas.
| DropOut |
<%= link_to_function("DropOut") do |page|
page.visual_effect(:drop_out, "bout_de_page")
end
%>
|
IV-B. Effets 'de base'
Tout ces effets sont très agréables, mais il est des moments où l'on aimerait se contenter de quelque chose de simple, ou tout simplement qui n'est pas proposé dans ces effets.
Voici comment en réaliser certains :
Insérer / Remplacer / Supprimer un élément
| insert_html / update_html / remove |
<%= link_to_function("Insérer") do |page|
page.insert_html :bottom, "bout_de_page", "coucou<br />"
end
%>
<%= link_to_function("Remplacer") do |page|
page.replace_html "bout_de_page", "<p>Le contenu a été remplacé entièrement par du HTML et une image</p>" + image_tag("rails.png")
end
%>
<%= link_to_function("Supprimer") do |page|
page.remove "bout_de_page"
end
%>
|
Pour l'insertion, quatre paramètres sont disponibles, et vont définir l'endroit de l'ajout :
- :top : ajoute le paramètre dans la balise, tout au début
- :bottom : ajoute le paramètre dans la balise, tout à la fin
- :before : ajoute le paramètre immédiatement avant la balise
- :after : ajoute le paramètre immédiatement après la balise
 |
Pour l'insertion et le remplacement de contenu, il est également possible de passer un partial en paramètre :
|
<%= link_to_function("Remplacer") do |page|
page.replace_html "bout_de_page", :partial => 'autre_contenu'
end
%>
|
Il vous suffit de créer le partial "_autre_contenu.rhtml".
Vous pouvez également passer un objet au partial via le paramètre ":object".
 |
Attention : Si vous regardez le code source généré, vous remarquerez que le partial est "embarqué" dans le code JavaScript (puisqu'aucun appel serveur n'est fait).
Cela peut donc considérablement alourdir le poids de vos pages.
De plus, imaginez que vous faisiez ainsi appel à un partial, qui lui-même à une fonction de ce type pour rappeler le premier partial (par exemple pour changer de formulaire). Et bien vous venez de créer une jolie boucle infinie qui se fera un plaisir de faire exploser votre serveur web. Prudence donc !
|
Cacher / Afficher un élément / alterner entre les deux
| show / hide / toggle |
<%= link_to_function("Show") do |page|
page.show "bout_de_page"
end
%>
<%= link_to_function("Hide") do |page|
page.hide "bout_de_page"
end
%>
<%= link_to_function("Toggle") do |page|
page.toggle "bout_de_page"
end
%>
|
Agrandir / Réduire un élément
| Scale |
<%= link_to_function("Scale 200%") do |page|
page.call "new Effect.Scale", "bout_de_page", 200
end
%>
<%= link_to_function("Scale 50%") do |page|
page.call "new Effect.Scale", "bout_de_page", 50
end
%>
|
Modifier le style d'un élément
| Morph |
<%= link_to_function("Morph bg-color") do |page|
page.call "new Effect.Morph", "bout_de_page", :style=>'background-color:#CC55DD;'
end
%>
|
Retarder un effet
| Scale |
<%= link_to_function("Highlight avec deux secondes de décalage") do |page|
page.delay(2.0) do
page.visual_effect(:highlight, "bout_de_page")
end
end
%>
|
L'effet de highlight se produira deux secondes après le clic sur le lien.
Pour effectuer des retards en cascade, il suffit d'imbriquer les page.delay.
Exécuter un effet au chargement de la page
Pour exécuter un effet au chargement de la page, il faut utiliser update_page_tag dans votre vue :
| Scale |
<%= update_page_tag do |page|
page.visual_effect(:highlight, "bout_de_page")
end
%>
|
 |
Il faut impérativement que le update_page_tag soit placé après l'élément auquel s'applique l'effet, sous peine de générer une erreur javascript.
L'idéal est donc de le placer en dernier, immédiatement avant la fermeture de la balise BODY.
|
Récapitulatif
Dans tout ce chapitre, nous n'avons pas fait une ligne d'AJAX, l'intérêt étant inexistant en l'état. En effet, qui dit appel AJAX dit appel serveur, totalement inutile pour simplement changer l'aspect d'un élément.
Mais notez que ces effets pourront être utilisés exactement de la même façon depuis un RJS (page.XXX).
Tous ces effets vous serviront lorsque nous attaquerons AJAX, pour agrémenter de jolis effets vos transitions.
Maintenant que vous avez tout ces beaux effets dans vos bagages, vous devriez être capable d'imaginer les combinaisons les plus folles.
Voyons maintenant comment modifier dynamiquement des formulaires.
V. Manipulation de formulaires
V-A. Ajouter dynamiquement un champ à un formulaire en fonction de la valeur d'un autre
Créons un nouveau contrôleur "my_form"". Remplissons le fichier "index.rhtml" comme suit :
| index.rhtml |
<%= form_tag :action=>'index' %>
<label>Sexe :</label>
<%= text_field('profile', 'sexe') %>
<div id="celibataire">-</div>
<%= observe_field 'profile_sexe',
:url=>{:action=>'check_ajout_form'},
:update=>"celibataire",
:with => "'sexe=' + escape(value)"
%>
<%= end_form_tag %>
|
Dans la première partie de ce code, nous nous contentons de définir un formulaire, et d'insérer un div qui fera office de receveur pour notre appel AJAX.
Le point important ici est le "observe_field", qui va nous créer un observateur, chargé de réagir quand un évènement se produira sur notre champ "profile_sexe" (ici, la perte de focus).
Nous lui passons en premier paramètre l'id du champ à observer.
Puis, nous définissons l'action à laquelle se référer lors d'un évènement, ainsi que l'id de l'élément à modifier au retour de l'appel AJAX.
Enfin, nous lui passons en paramètre la valeur du champ observé.
Dans notre contrôleur, définissons l'action "check_ajout_form" :
| check_ajout_form |
def check_ajout_form
user_sexe = params[:sexe]
if user_sexe == "femme"
render :partial => "celibataire_form"
else
render :text => "Les hommes ne m'interessent pas !"
end
end
|
Enfin, définissons le partiel "_celibataire_form.rhtml" :
| _celibataire_form.rhtml |
<label>Etes-vous célibataire ?</label>
<%= text_field('profile', 'celibataire') %>
|
Testez à présent votre contrôleur.
Si vous saisissez "femme" dans votre champ "sexe", un nouveau champ apparait.
Sinon, un 'message d'erreur' vous informe ;)
Le point important ici est le "render :partial". Comme l'élément à mettre à jour a déjà été défini dans la vue, nous pouvons donc nous comporter comme si de rien n'était.
V-B. Mettre à jour dynamiquement un champ
Pour cet exemple, nous allons créer deux listes déroulantes, la première se mettant à jour en fonction de la seconde.
Créons deux modèles : region et departement :
| 002_create_regions.rb |
class CreateRegions < ActiveRecord::Migration
def self.up
create_table :regions do |t|
t.column :nom, :string
end
end
def self.down
drop_table :regions
end
end
|
| 003_create_departements.rb |
class CreateDepartements < ActiveRecord::Migration
def self.up
create_table :departements do |t|
t.column :region_id, :integer
t.column :num_dept, :string
t.column :nom, :string
end
end
def self.down
drop_table :departements
end
end
|
Il ne nous reste plus qu'à exécuter notre migration.
Remplissons maintenant nos deux tables à l'aide des deux fichiers de fixtures suivant :
| regions.yml |
alsace:
id: 1
nom: Alsace
aquitaine:
id: 2
nom: Aquitaine
auvergne:
id: 3
nom: Auvergne
bassenormandie:
id: 4
nom: Basse-Normandie
bourgogne:
id: 5
nom: Bourgogne
bretagne:
id: 6
nom: Bretagne
centre:
id: 7
nom: Centre
champagneardenne:
id: 8
nom: Champagne-Ardenne
corse:
id: 9
nom: Corse
dom:
id: 10
nom: Départements d'Outre-Mer
franchecomte:
id: 11
nom: Franche-Comté
hautenormandie:
id: 12
nom: Haute-Normandie
idf:
id: 13
nom: Ile-de-France
languedocroussillon:
id: 14
nom: Languedoc-Roussillon
limousin:
id: 15
nom: Limousin
lorraine:
id: 16
nom: Lorraine
midipyrenees:
id: 17
nom: Midi-Pyrénées
nordpasdecalais:
id: 18
nom: Nord-Pas-de-Calais
paysdelaloire:
id: 19
nom: Pays de la Loire
picardie:
id: 20
nom: Picardie
poitoucharentes:
id: 21
nom: Poitou-Charentes
paca:
id: 22
nom: Provence-Alpes-Côte-d'Azur
rhonealpes:
id: 23
nom: Rhône-Alpes
tom:
id: 24
nom: Territoires d'Outre-Mer
|
| departements.csv |
region_id, num_dept, nom
1, 67, Bas-Rhin
1, 68, Haut-Rhin
2, 24, Dordogne
2, 33, Gironde
2, 40, Landes
2, 47, Lot-et-Garonne
2, 64, Pyrénées-Atlantiques
3, 03, Allier
3, 15, Cantal
3, 43, Haute-Loire
3, 63, Puy-de-Dôme
4, 14, Calvados
4, 50, Manche
4, 61, Orne
5, 21, Côte-d'Or
5, 58, Nièvre
5, 71, Saône-et-Loire
5, 89, Yonne
6, 22, Côtes-d'Armor
6, 29, Finistère
6, 35, Ille-et-Vilaine
6, 56, Morbihan
7, 18, Cher
7, 28, Eure-et-Loir
7, 36, Indre
7, 37, Indre-et-Loire
7, 41, Loir-et-Cher
7, 45, Loiret
8, 08, Ardennes
8, 10, Aube
8, 51, Marne
8, 52, Haute-Marne
9, 2A, Corse-du-Sud
9, 2B, Haute-Corse
10, 971, Guadeloupe
10, 972, Martinique
10, 973, Guyane
10, 974, La Réunion
10, 975, Saint-Pierre-et-Miquelon
10, 976, Mayotte
11, 25, Doubs
11, 39, Jura
11, 70, Haute-Saône
11, 90, Territoire de Belfort
12, 27, Eure
12, 76, Seine-Maritime
13, 75, Paris
13, 77, Seine-et-Marne
13, 78, Yvelines
13, 91, Essonne
13, 92, Hauts-de-Seine
13, 93, Seine-Saint-Denis
13, 94, Val-de-Marne
13, 95, Val-d'Oise
14, 11, Aude
14, 30, Gard
14, 34, Hérault
14, 48, Lozère
14, 66, Pyrénées-Orientales
15, 19, Corrèze
15, 23, Creuse
15, 87, Haute-Vienne
16, 54, Meurthe-et-Moselle
16, 55, Meuse
16, 57, Moselle
16, 88, Vosges
17, 09, Ariège
17, 12, Aveyron
17, 31, Haute-Garonne
17, 32, Gers
17, 46, Lot
17, 65, Hautes-Pyrénées
17, 81, Tarn
17, 82, Tarn-et-Garonne
18, 59, Nord
18, 62, Pas-de-Calais
19, 44, Loire-Atlantique
19, 49, Maine-et-Loire
19, 53, Mayenne
19, 72, Sarthe
19, 85, Vendée
20, 02, Aisne
20, 60, Oise
20, 80, Somme
21, 16, Charente
21, 17, Charente-Maritime
21, 79, Deux-Sèvres
21, 86, Vienne
22, 04, Alpes-de-Haute-Provence
22, 05, Hautes-Alpes
22, 06, Alpes-Maritimes
22, 13, Bouches-du-Rhône
22, 83, Var
22, 84, Vaucluse
23, 01, Ain
23, 07, Ardèche
23, 26, Drôme
23, 38, Isère
23, 42, Loire
23, 69, Rhône
23, 73, Savoie
23, 74, Haute-Savoie
24, 984, Terres Australes et Antarctiques
24, 986, Wallis et Futuna
24, 987, Polynésie Française
24, 988, Nouvelle-Calédonie
|
Exécutons maintenant ces deux fichiers, et allez vérifier que vos tables sont bien remplies.
Notez que j'ai pris la peine de préciser l'id dans "regions.yml", afin d'être sûr de ma cohérence avec "departements.csv".
Créons maintenant un contrôleur "maj_liste".
Il ne nous reste plus qu'a éditer les fichiers suivants :
| maj_liste_controller.rb |
class MajListeController < ApplicationController
def index
@liste_region = Region.find :all
@liste_dept = Departement.find :all, :order=>"nom asc"
end
def update
choix_region = params['id_region']
@liste_dept = Departement.find_all_by_region_id(choix_region, :order => "nom asc")
end
end
|
| index.rhtml |
<%= form_tag :action=>"index" %>
<label>Région :</label>
<%= select("profile", "region", @liste_region.collect {|elt| [ elt.nom, elt.id ] }) %>
<%= image_tag "ajax-loader.gif", :id=>"wait_icon", :style=>"display:none" %>
<br />
<label>Département :</label>
<%= select("profile", "departement", @liste_dept.collect {|elt| [ elt.nom, elt.id ] }) %>
<br />
<%= submit_tag "valider"%>
<%= end_form_tag%>
<div id="result_validation"></div>
<%= observe_field 'profile_region',
:url=>{:action=>'update'},
:before => "Element.toggle('wait_icon')",
:complete => "Element.toggle('wait_icon')",
:with => "'id_region=' + escape(value)"
%>
|
| update.rjs |
page.replace_html "profile_departement", ""
@liste_dept.each { |dept|
page.insert_html :bottom,
"profile_departement",
content_tag('option', dept.nom, :value => dept.id )
}
|
Il ne vous reste plus qu'à tester votre contrôleur. Le fait de changer la région dans la liste met automatiquement à jour la liste déroulante contenant les départements.
Cette fois encore, notez l'utilisation du "observe_field" qui nous permet de détecter un changement de valeur dans la liste des départements et de mettre à jour nos départements.
Certaines des choses présentes dans le fichier "index.rhtml" ne nous ont pas encore servi, mais comme elles vont nous servir dans le prochain chapitre, j'ai préféré les intégrer dès à présent.
Notez dans le "observe_field" l'utilisation des paramètres ":before" et ":complete", qui permettent d'effectuer des actions à différents moments.
Ce ne sont pas les deux seuls, voici donc une liste complète :
- :before : juste avant l'appel AJAX
- :after : juste après le déclenchement de l'appel AJAX
- :loading : pendant qu'XHR reçoit les données
- :loaded : quand XHR a fini re recevoir les données
- :interactive : pendant qu'XHR traite les données
- :success : les données sont finies de traiter, et il n'y a pas d'erreur
- :failure : les données sont finies de traiter, et il y a une erreur
- :complete : les données ont été renvoyées au navigateur, et :success ou :failure ont été appelés
Il est agréable de mettre à jour la liste dynamiquement, mais si en plus elle pouvait se mettre à jour automatiquement au chargement de la page, ce serait le paradis.
| |