jeudi 25 février 2010

Action AJAX avec Symfony et JQuery

Symfony et moi, c'est une réelle histoire d'amour technologique en ce moment.
Voici pourquoi... Tout est d'une simplicité enfantine et instinctive.
Ainsi pour faire quoi que ce soit, il y a toujours une solution évidente et facile.
Voici le tuto pour appeller une action Symfony en Ajax par JQuery.

Tout d'abord, créons un contexte.
1.L'utilisateur clique sur un bouton.
2.Cela déclenche un évenement JQuery et donc une fonction.
3.Cette fonction envoit des données telle adresse (reliée à une action)
4.L'action éffectue une tache quelconque comme un test de valeur et renvoit la réponse

1.L'utilisateur clique sur un bouton.

Voici un code HTML très simple pour simuler ceci.
<input id="mybutton" type="button" value="Valider">
 2.Cela déclenche un évenement JQuery et donc une fonction.
<input id="mybutton" type="button" onclick="myAjaxFunction()" value="Valider">
 #myJSFile.js

function myAjaxFunction(){}

3.Cette fonction envoit des données telle adresse (reliée à une action)
#myJSFile.js
        function myAjaxFunction(){
            var value = $("#myhiddenvalue").val();
            $.post("myModuleName/myAction",
            {myParam : value},
            function(data)
            {
                $("#mybutton").attr("value",data);
            });
        }
4.L'action éffectue une tache quelconque comme un test de valeur et renvoit la réponse

public function executeIndex(sfWebRequest $request)
 {
    if ($request->isXmlHttpRequest())
    {
        $myValue = $request->getParameter('myParam');
        if($myValue==1)
            $message = "super";
        else
            $message = "bof";
        return $this->renderText($message);
    }
}

Ainsi, le texte du bouton va changer grace à un appel AJAX.
Passez plus de paramètres, renvoyez au lieu d'un texte un fichier _myFileToDisplay.php (renderPartial au lieu de renderText)

exemple : 
return $this->renderPartial('myModuleNamle/myFileToDisplay', array('message' => $message,'hello' => "coucou"));

Pensez que lorsqu'on fait un renderPartial, le fichier doit commencer par underscore, soit ici _myFileToDisplay.php

Bonne chance

22 commentaires:

  1. J'ai essayé renderPartial avec un formulaire(que je veux basculerle statut de ses champs entre non required et required en chochant ou décochant un checkbox), mais ça n'a pas l'air fonctionner .. j'ai pu vérifier que tout le traitement précédant renderPartial fonctionne super bien .. j'ai pensé que renderPartial ne fonctionne plus de la même manière dans symfony 1.4 .. mais c à vérifier

    RépondreSupprimer
  2. Les formulaires sont des objets sous symfony, alors si tu veux l'utiliser, fait une page _myForm.php que tu va afficher avec un render_partial en passant ton $form et dans ton render partial tu fait : echo $form;
    Donc essaye ca :
    return $this->renderPartial('myModuleNamle/myForm', array('form' => $form));

    RépondreSupprimer
  3. merci pour ta réponse Leny!
    c'est exactement ça ce que j'ai fait ..
    apparemment j'ai quelque chose qui ne va pas quelques parts .. mais ça me rassure que tu confirmes que c'est ça la méthode qui marche!
    Je continue mes 'investigations'
    encore merci !

    RépondreSupprimer
  4. de rien :) tu n'a pas de message d'erreur ??

    RépondreSupprimer
  5. non .. mais rien ne se passe
    J'ai vérifié qu'avant l'appel à renderPartial tout se passe bien , fin normalement tout est prêt et ne reste que la revalidation du formulaire .. mais renderPartial en sois ne change rien dans l'interface ..
    pour le moment j'ai fait autrement (changer en client si le user coche la case et au submit je vérifie selon la valeur de cette case, ça marche comme solution mais j'en suis pas très convaincu)

    RépondreSupprimer
  6. merci pour votre article

    pour mon cas , j'ai deux model auteur et livres
    je vient d'imbriquer la formulaire du livre dans une formulaire d'auteur en utilisant ajax
    mon problème que je n'arrive pas a récupérer l'objet du form auteur pour que je puisse créer un objet livre dans l'action exécuté lors de l'appel par ajax ??

    pouvez vous SVP me donner quelque détails !!

    RépondreSupprimer
  7. Merci pour ton post qui m'a un peu aidé.

    RépondreSupprimer
  8. je ne comprend pas à quoi sert

    var value = $("#myhiddenvalue").val();

    Je ne vois pas d'élement HTML d'id myhiddenvalue....
    Peut tu clarifier ce morceau de code merci :)

    RépondreSupprimer
  9. Salut "Anonyme",
    Tu es d'accord avec moi j'imagine, le but est d'appeler une méthode. Tu devra souvent passer des paramètres, par exemple si tu veux supprimer un objet, tu créé un bouton et un champ de type text caché avec une valeur assignée : <input type="text" value="$id"/>
    la ligne que tu ne comprends pas existe simplement pour récupérer cet id, la valeur que doit prendre le paramètre que j'appelle ensuite myParam et que donc j'affecte la valeur value... Ensuite dans ta méthode executeIndex (dans l'exemple), tu récupert ce paramètre passé avec cette ligne : $myValue = $request->getParameter('myParam');
    Donc voilà, c'est plutot facile, je veux envoyer un parametre, donc je récupert la valeur que je veux en javascript puis je poste à la bonne adresse en passant en parametre myParam puis je fais le traitement dont j'ai besoin dans ma méthode. J'espère que j'ai été clair.

    RépondreSupprimer
  10. Mon probleme c'est que je veux personnaliser les formulaires alors j'ai essayé en premier lieu d'éditer _edit_form de mon template et j'ai remplacé une zone texte par une liste déroulante le problème que la liste s'affiche mais la valeur choisit s'affiche pas aprés le sauvegarde vous pouvez m'aider svp?!!

    RépondreSupprimer
  11. Tu veux faire quoi au final ? Es tu sur qu'il n'y a pas une autre facon pour faire ca ?
    Si j'ai bien compris, mais je ne suis pas sur d'ou la première question, tu veux remplacer un champs texte par un select... mais ce select va chercher quoi ? ou alors il est statique. S'il doit être dynamique, c'est à dire se charger avec un autre modèle,il faut que tu changes le type de widget dans lib/form/base en remplacant sfWidgetInputText() par sfWidgetFormDoctrineChoice(array('multiple'=>true/false, 'model'=> 'yourModelName'). Normalement, il se génère tout seul avec doctrine lorsque tu fais ton schema et en faisant une liaison entre les 2 modèles.
    soit, si ta liste déroulante est statique, il faut que tu modifie le validateur, de string tu le passe à sfValidatorDoctrineChoice(array('model' => $this->getRelatedModelName('monDeuxiemeModeleName'), 'required' => false))... J'espère que ca t'aidera si c'est ca ton probleme

    RépondreSupprimer
  12. C'est bon Leny j'ai resolu mon probleme merci pour ce tuto qui m'a bien aidé.

    signé Anonyme du 22 avril 2010 02:38 :)

    RépondreSupprimer
  13. Bonjour,

    J'ai pu utiliser ton render partial sur mon formulaire symfony. Mais au submit, les deux champs "ajaxisés" sont soumis avec une valeur nulle. Je me fait donc jeter au bind par el Validator (ça fait super héros formulé comme ceci ^^).

    Pour plus de détails voici comment j'ai procédé :

    1.1/ première action : l'index

    public function executeIndex(sfWebRequest $request)
    {
    $this->form = new myForm();
    }

    1.2/ Le formulaire myForm

    public function configure()
    {
    $this->widgetSchema->setNameFormat('myForm[%s]');

    $this->widgetSchema['start_date'] = new sfWidgetFormSelect(array('choices' => array()));
    $this->widgetSchema['end_date'] = new sfWidgetFormSelect(array('choices' => array()));

    //Year Table
    $year1 = date("Y");
    $year2 = $year1 - 1;
    $year3 = $year1 - 2;
    $year4 = $year1 - 3;

    $choices = array($year1 => $year1,
    $year2 => $year2,
    $year3 => $year3,
    $year4 => $year4);

    $this->widgetSchema['year'] = new sfWidgetFormChoice(array('choices' => $choices,'expanded' => true,));

    $this->setValidators(array('year' => new sfValidatorChoice(array('choices' => array_keys($choices))),
    'start_date' => new sfValidatorDate(array('required' => true)),
    'end_date' => new sfValidatorDate(array('required' => true))));
    }

    Mes champs start_date et end_date sont dépendants du champs year. Ils sont donc instanciés à vide pour être "ajaxisés" après le chois de l'année.

    1.3/ Le template indexSuccess
    Je ne vais pas tout mettre, j'ai peur de te souler avec du code inutile. Je ne met que ce que je juge essentiel : l'appel ajax.

    <|form action="" method="post" class="formulaire">
    <|span class="txt_error">renderGlobalErrors() ?>
    <|fieldset>
    <|legend>
    renderHiddenFields() ?>

    <|span class="txt_error">renderError() ?>
    render(array('onChange'=>jq_remote_function(array(
    'update'=> 'period_end',
    'url'=>'myModule/getPeriodend',
    'method'=>'get',
    'with'=>'year=2010',))
    )
    )
    ?>

    <|fieldset id="period_end">

    RépondreSupprimer
  14. 2.1/ L'appel ajax est fait au choix de l'année, voici l'action correspondante. Note que je fais appel à un formulaire différent du premier : myForm2. Celui-ci héritant de myForm.

    public function executeGetPeriodend(sfWebRequest $request)
    {
    $this->forward404Unless($request->isXmlHttpRequest());

    $form = new myForm2();
    return $this->renderPartial('myModule/periodEnd', array('form'=>$form));
    }

    2.2/ Le formulaire myForm2

    class myForm2 extends myForm
    {

    public function configure()
    {

    unset($this['year']);

    $choices = array("2010-01-01","2010-01-02","2010-01-03","2010-01-04");

    $this->widgetSchema['start_date'] = new sfWidgetFormSelect(array('choices' => $choices));
    $this->widgetSchema['end_date'] = new sfWidgetFormSelect(array('choices' => $choices));

    $this->validatorSchema['start_date'] = new sfValidatorChoice(array('choices' => array_keys($choices)));
    $this->validatorSchema['end_date'] = new sfValidatorChoice(array('choices' => array_keys($choices)));

    }
    }

    2.3/ Le partial _perioEnd :

    <|legend>
    renderHiddenFields() ?>

    renderLabel('Start Date') ?>
    <|span class="txt_error">renderError() ?>

    <|br />
    renderLabel('End Date') ?>
    <|span class="txt_error">renderError() ?>

    <|br />

    RépondreSupprimer
  15. 3/ Résultat avant le submit :
    Les combo box start_date et end_date sont correctement remplis, l'année est bien sélectionnée.

    4/ Submit :

    C'est le drame ! Voici l'action submit

    public function executeSubmit(sfWebRequest $request)
    {
    $this->form = new DebitMemoReportForm();
    sfContext::getInstance()->getLogger()->info("Avant le bind");

    $this->form->bind($request->getParameter($this->form->getName()));
    if ($this->form->isValid())
    {
    //do stuff
    return sfView::SUCCESS;
    }
    $this->setTemplate('Index');
    return sfView::SUCCESS;
    }

    Voici ce que j'obtiens sur ma page web

    Start Date

    * Required.


    End Date

    * Required.

    Je ne sais vraiment pas d'où cela viens. En plus dans la fénêtre debug (je suis en frontend_dev) view/form : j'ai bien tout mes paramètres dans le formulaire...

    Si tu as une petite idée, je suis par avance, éternellement reconnaissant.

    RépondreSupprimer
  16. Aie, c'est assez spécial... C'est vrai que les formulaires sont assez compliqués... J'essaye de reproduire ton problème si je trouve, je te tiens au courant.

    RépondreSupprimer
  17. bonjour,
    j'essaye actuellement de faire un appel ajax afin de récupérer mes données partiellement au chargement de la page grâce à Jquery sous Symfony 1.4.
    Avant toute chose je précise que mon code ne fonctionne pas en environnement de production, mais FONCTIONNE lorsque je suis en dev. Ceci est dû au no_script_name dans settings.yml qui est à true dans l'environnement de prod et pas en dev.
    j'effectue un appel ajax côté template avec du jquery tel que:

    J'ai donc un appel de cette forme côté template:
    $.ajax({
    type : "GET",
    url : "",
    data : "id_item=X&pr_id=Y", //les valeurs X et Y sont des variables php à la base
    success : function(data){
    retour = data;
    $('#mondiv').append(retour);
    }
    });

    Mon url fait donc appel à la route suivante dans mon routing.yml

    recup_poi:
    url: /petitesregions/recup_poi/:id_item/:pr_id
    param: { module: petitesregions, action: recup_poi }

    après je fais un traitement dans l'action recup_poi, puis place le contenu dans un partial afin de le mettre au callback dans mon div.
    s'il y a besoin de plus d explications je suis dispo... et perdu aussi, ça fait 2 jours que je bloque là dessus, je commence à dérailler.

    RépondreSupprimer
  18. Salut,
    Je n'ai pas trop compris ce que tu essaye de faire :S Si j'ai bien compris, tu as des petites régions et tu souhaite récupérer des coordonnées relatives à un poi. C'est ça ? Si tu peux, explique le contexte, quelle est l'erreur que tu obtiens, j'essayerais de t'aider :)

    RépondreSupprimer
  19. Bonjour Lilian,
    J'ai exactement le meme problème que le tien :(
    est ce que vous avez trouvé une solution pour ça?
    Merci d'avance!

    RépondreSupprimer
  20. Bonjour a tous,
    Ce billet date depuis deja quelque temps mais j'ose quand meme laissé mon commentaire ! :)

    Tout d'abord merci car il m'a été tres utile !

    J'ai rencontré un probleme dans le $.post de la fonction javascript. En effet au lieu de mettre simplement $.post("myModuleName/myAction",... j'ai du mettre $.post("http://monsite.truc.fr/myModuleName/myAction", ....

    Est ce que tu aurais une explication a cela ? Est ce que cela vient du routing ? Est ce que de ton coté tu avais mis en place une route pour myModuleName/myAction ? (si oui peux tu me la préciser ?...)


    A bientot et encore merci :)

    RépondreSupprimer
  21. je suis débutant en Symfony et je développe mon premier site le souci que j'ai c'est que j'utilise AJAX pour m'afficher un formulaire d'ajout d'un commentaire, j'utilise la méthode renderPartial() dans l'action concernée (new) alors que dans mon fichier security.yml du module j'ai spécifier
    new:
    is_secure: true

    quand j’accède directement via un URL ça me redirige vers mon formulaire d'authentification mais c'est pas le cas quand je passe via AJAX

    N.B : j'utilise sfDoctrineGuardPlugin

    merci d'avance !!

    RépondreSupprimer

Bonjour,