Bonjour à tous,
Bon étant donné qu'on a eu ces derniers jours plusieurs fois la question "Comment on fait pour faire des select box dépendantes l'un de l'autre en symfony ?", je fais un rapide tuto. Et vous allez voir que même si on code avec symfony, ça n'a rien de sorcier.
Avertissement : Je ne prétends absolument pas que mon post présente la meilleure des solutions. C'est un tuto basique, fonctionnel, qui devrait pouvoir fournir à ceux qui ont du mal une base afin d'implémenter cette fonctionnalité. Je n'ai sûrement pas gérer tous les cas possibles, les erreurs possibles, je n'ai pas utilisé de sfForm ce sont juste deux selects basiques mais le principe reste le même avec un sfForm. De plus, ce qui est fait au niveau du modèle est sûrement pas ce qu'il y a de mieux, mais ce n'est pas le sujet. (Le findAll() c'est maaaaaaaaaaaaaal)
Toutes les remarques et commentaires afin d'améliorer l'exemple sont les bienvenus, mais je répète que celui-ci n'a pas pour but de gérer tous les trucs possibles et imaginables pour ce genre de cas. Les questions sont aussi les bienvenues.
Contexte : Aller on va prendre un sujet un peu sympathique, vous imaginez qu'on est dans un jeu, qu'on a des personnages, qui possèdent un ensemble d'objets dans leur inventaire. Et on va faire justement l'écran qui va nous permettre d'utiliser ces objets.
C'est parti !
Pour commencer, je vous laisse le soin d'installer symfony, d'installer votre projet, de générer une application (que j'appellerai frontend pendant ce tuto). D'avoir votre serveur apache configuré et avoir la page par défaut de symfony. Une base de donnée disponible aussi, avec le database.yml qui va bien.
Bon déjà, de quoi va-t-on avoir besoin ? On veut deux listes en ajax (non, pas le produit ménager ...), il nous faut de quoi faire de l'ajax. On va donc commencer par récupérer jQuery. Oui, c'est un choix tout à fait arbitraire, mais c'est comme ça. Et non, je ne rédigerai pas une version du tuto avec un autre framework javascript.
Bon il me faut aussi un model correct pour travailler. Beaucoup d'entre-vous aurons le leur mais je mets le miens au cas où certaines personnes veuillent faire le tuto pas à pas.
Code : Sélectionner tout - Visualiser dans une fenêtre à part ./symfony plugin:install sfJqueryReloadedPlugin
On va avoir besoin d'avoir aussi des données pour travailler, voici donc le fixtures.yml pour ceux qui en auraient besoin :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
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 # /config/doctrine/schema.yml MainCharacter: columns: id: { type: integer(4), unsigned: true, notnull: true, primary: true, autoincrement: true } name: { type: string(50), notnull: true } relations: Items: class: Item refClass: MainCharacterItem local: main_character_id foreign: item_id foreignAlias: Characters Item: columns: id: { type: integer(4), unsigned: true, notnull: true, primary: true, autoincrement: true } name: { type: string(50), notnull: true } MainCharacterItem: columns: main_character_id: { type: integer(4), unsigned: true, notnull: true, primary: true } item_id: { type: integer(4), unsigned: true, notnull: true, primary: true } quantity: {type: integer(4), unsigned: true, notnull: true } relations: MainCharacter: local: main_character_id foreign: id foreignAlias: MainCharacterItems onDelete: CASCADE Item: local: item_id foreign: id foreignAlias: MainCharacterItems onDelete: CASCADE
Donc vous êtes censés avoir votre application qui fonctionne, on va avoir besoin d'un module maintenant :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
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 # /data/fixtures.yml MainCharacter: lightning: name: Lightning serah: name: Serah vanille: name: Vanille Item: potion: name: Potion queue_de_phenix: name: Queue de Phénix elixir: name: Elixir antidote: name: Antidote collyre: name: Collyre MainCharacterItem: potion_lightning: MainCharacter: lightning Item: potion quantity: 5 potion_serah: MainCharacter: serah Item: potion quantity: 2 elixir_serah: MainCharacter: serah Item: elixir quantity: 3 queue_de_phenix_vanille: MainCharacter: vanille Item: queue_de_phenix quantity: 2 antidote_vanille: MainCharacter: vanille Item: antidote quantity: 4 collyre_vanille: MainCharacter: vanille Item: collyre quantity: 1
On fait une petite route de base, obligatoire puisque vous avez supprimé les routes par défaut vu que vous suivez les recommandations
Code : Sélectionner tout - Visualiser dans une fenêtre à part ./symfony generate:module frontend inventory:
Alors, quand on va arriver sur l'écran de l'inventaire, on aura déjà une liste prête avec le nom des personnages. Notre petite action est donc très simple :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4 # /apps/frontend/config/routing.yml inventory_show: url: /inventory.:sf_format param: { module: inventory, action: index, sf_format: html }
Le template qui va avec :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8 // /apps/frontend/modules/inventory/actions/actions.class.php class inventoryActions extends sfActions { public function executeIndex(sfWebRequest $request) { $this->characters = Doctrine::getTable('MainCharacter')->findAll(); } }
C'est peu commenté je sais, mais ça ne devrait pas être bien dur à comprendre. Ici on affiche notre première liste avec les personnages, et on affiche une deuxième liste vide. Ensuite il y a du javascript. Je vous suggère d'apprendre jQuery si vous y comprenez rien
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 <!-- /apps/frontend/modules/inventory/templates/indexSuccess.php --> <?php use_javascript('/sfJqueryReloadedPlugin/js/jquery-1.3.2.min.js'); ?> <h1>Inventory</h1> <select name="character_id" id="characters"> <option>Please select a character</option> <?php foreach ($characters as $character): ?> <option value="<?php echo $character->getId() ?>"><?php echo $character->getName(); ?></option> <?php endforeach; ?> </select> <select name="item_id" id="items"></select> <input type="submit" value="Use this item !" /> <script type="text/javascript"> $(document).ready(function() { $("#characters").change(function(event) { var characterId = $(event.target).val(); $.post('<?php echo url_for('@inventory_update_items_list') ?>', { character_id: characterId }, function(result) { $("#items").html(result); }); }); }); </script>, mais en gros, quand la page est chargée, on surveille l'évènement "onChange" de notre liste de personnage pour faire une requête ajax dès que la valeur sélectionnée change.
On récupère donc la valeur actuellement selectionnée, puis on la post vers la route @inventory_update_items_list
Là dans vos têtes ça doit faire "Ding ! On a une nouvelle route" (Attention, si ça ding trop fort c'est que votre tête est vide). Et ben il reste plus qu'à la rajouter dans le routing.yml :
Ah, maintenant on a donc une nouvelle action qui va se charger de récupérer les éléments de la deuxième liste. Allons-y alors :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6 # /apps/frontend/config/routing.yml inventory_update_items_list: url: /inventory/update-items-list.:sf_format param: { module: inventory, action: updateItemsList, sf_format: html } requirements: sf_method: post
Rien de sorcier là encore : Si ce n'est pas une requête ajax, on renvoit une page blanche. Bon à la place vous auriez pu faire une redirection, une 404, ou ce que vous voulez peu importe. Ensuite on récupère le personnage concerné grâce au paramètre transmis par post.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14 // apps/frontend/modules/inventory/actions/actions.class.php public function executeUpdateItemsList(sfWebRequest $request) { if (!$request->isXmlHttpRequest()) { return sfView::NONE; } $this->character = Doctrine::getTable('MainCharacter')->find($request->getParameter('character_id')); if (!$this->character) { return sfView::NONE; } }
Dans le cas où on ne le trouve pas, là encore j'ai choisi d'afficher une page blanche.
Le template qui va avec cette action :
Alors là, j'ai fait le rendu des options de ma 2ème liste en html directement. Vous pouvez soit faire pareil, soit par exemple faire un rendu en json et utiliser le javascript après le retour de votre requête ajax pour construire les différentes options. Bon, le plus simple je trouve c'est de faire le html directement, j'ai choisi ça par soucis de rapidité à faire. Et si vous avez rien compris à ce que je viens de dire, faites comme moi
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5 <!-- /apps/frontend/modules/inventory/templates/updateItemsListSuccess.php --> <?php foreach ($character->getMainCharacterItems() as $mainCharacterItem): ?> <option value="<?php echo $mainCharacterItem->getItem()->getId() ?>"><?php echo $mainCharacterItem; ?></option> <?php endforeach; ?>
Donc là, toujours rien de sorcier. Cette page va afficher une liste de tags <option> qui contiendront l'id de l'objet en value, et qui en texte affichent directement l'objet MainCharacterItem. MainCharacterItem ? Mais il n'y a que 3 colonnes de type entier dedans. Diantre ! Pourquoi lui ? Alors en effet, j'aurais pu afficher directement le nom de l'objet, mais dans MainCharacterItem on a aussi une donnée qui peut être intéressante : La quantité possédée par le personnage ! Donc on va se faire la petite fonction __toString() qui va bien dans le model :
(Oui, comme vous vous en doutez, ça n'a rien à voir avec le tuto
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8 // /lib/model/doctrine/MainCharacterItem.class.php class MainCharacterItem extends BaseMainCharacterItem { public function __toString() { return $this->getItem()->getName()." (".$this->getQuantity().")"; } }. Mais c'est moi qui rédige, donc "je fais qu'est ce que je veux" comme dirait mon petit cousin)
Et voilà, votre requête ajax renvoit donc du html qui est inséré directement à l'intérieur de la seconde liste.
Voilà, c'est fini. Au final je me rend compte que c'était tellement pas compliqué que peut-être que personne va apprendre quoi que ce soit avec ce truc.
En tout cas, y'a rien de différent parce qu'on est en symfony. La seule chose qui diffère, c'est où vous mettez votre code qui gère la requête ajax en gros.
Partager