Consultez la FAQ sur le ZF avant de poster une question
Vous n'êtes pas identifié.
Bonjour,
Je me retrouve face à un problème que je ne pensais pas rencontrer.
Lorsque je fais appel à un Ajax.updater (avec prototype) pour afficher une page dans une div, et que dans cette même page j'ai un formulaire que j'envoie par un Ajax.request en post (vers la même action/page), je ne rentre pas dans l'action du controller.
Exemple tout bête:
Controller :
function friendAction() { if ($this->_request->isPost()) { $this->_helper->viewRenderer->setNoRender(); echo 'test'; } else { $this->_helper->layout->setLayout('layout-ajax'); } }
Requête ajax qui affiche la page :
var myAjax = new Ajax.Updater ( this.container, this.urlRequest, { method: 'get', evalScripts: true, onCreate : function () { if (objWindow.options.showLoading) { this.showLoading(); } }, onComplete: function() { if (objWindow.options.showLoading) { this.hideLoading(); } }, onSuccess: function() { if (objWindow.options.isModal) { Effect.Appear($('BDAjaxWindowOverlay'), { duration:0.1, from:0.0, to:0.3, queue: {position:'end', scope: 'BDajaxwindowscope'} }); } Effect.Appear($('BDAjaxWindowDiv'), { duration:objWindow.options.effectDuration, from:0.0, to:1.0, queue: {position:'end', scope: 'BDajaxwindowscope'}, afterFinish: function() { if (objWindow.options.externalControl){ var externalControl = objWindow.options.externalControl; for(var i=0; i < (externalControl.length); i++) { Event.observe($(externalControl[i]), 'click', objWindow.hideBox.bindAsEventListener(objWindow)); } } } }); }, onFailure: function(pResponse) { } } );
Requête qui envoie le formulaire:
function jsSendMail(pUrl, pParams, pForm, pContext, pRedirect) { var eval; var context; var redirect; redirect = pRedirect || false; context = (pContext) ? '?format='+pContext : ''; var myAjax = new Ajax.Request ( pUrl + context, { method: 'post', evalScripts: true, parameters : pForm.serialize(true), onLoading : function () { new LoadingIndicator.base({pic:'ajax-loading.gif'}); }, onComplete: function transResult (response) { LoadingIndicator.hideAll(); if (!pRedirect) { if (response.responseText == 'OK') { //pForm.reset(); } else { } } else { //top.location.replace(response.responseText); } } } ); return false; }
Donc c'est la même action qui affiche la page et gère l'envoie du formulaire.
Le problème est que lorsque je rappelle l'action depuis le request je ne rentre pas dedans.
Pourtant en mettant une trace je vois bien que je passe dans le onComplete du Ajax.request.
Une idée ?
Merci,
A+ benjamin.
Dernière modification par Delprog (21-11-2008 12:10:10)
Hors ligne
Je ne comprends rien à ton problème.
Par contre, ce que je peux te dire la dessus :
function friendAction() { if ($this->_request->isPost()) { //toto } else { $this->_helper->layout->setLayout('layout-ajax'); } }
Ce n'est pas 'isPost' qui détermine si tu es face à une requète ajax ou non !
C'est isXmlHttpRequest();
Hors ligne
Salut nORKy,
Je ne cherche pas détecter si je viens d'une requête XmlHttpRequest, mon controller ne sert qu'à ça. Il ne sera jamais utilisé dans un contexte normal.
Le isPost me sert simplement à tester si les données du formulaire ont été envoyées par mon Ajax.request (avec method: 'post'). Je suppose que c'est le nom du layout utilisé (layout-ajax) qui t'as fait dire ça.
Si je reçois des données en post je n'utilise pas de layout, sinon je sais que je viens de mon Ajax.updater et que je dois afficher mon layout et ma vue. Seulement j'ai épuré tout mon code pour poster sur le fofo, donc c'est pas très clair.
Tu veux dire que tu ne sais pas d'où viens le problème, ou est-ce que tu n'as rien pigé à mon explication ?
A+ benjamin.
Hors ligne
Bonjour Delprog,
Quand tu fais ta 2e requête, tu peux nous envoyer les contenus de la requête et de la réponse (ce qui s'affiche dans l'onglet réseau de firebug, quand tu regarde ce qui se passe à l'intérieur de ta requête).
A+, Philippe
Hors ligne
Salut,
Alors dans firebug, j'avais regardé mais je pense qu'il ne reproduit pas correctement le comportement du post.
Requête : POST friend?format=xml http://bohort:8081/tpl1/mail/friend?format=xml
Paramètres : format xml
Post :
frdCiv
frdDestMail
frdMail
frdNom
frdPrenom
Donc bien les données que je veux récupérer.
Par contre en réponse (et c'est là que je doute de Firebug), j'ai le contenu de la page en top, c'est à dire celle qui contient elle même la div que je met à jour avec le Ajax.update (donc "friend.phtml"), dans laquelle il m'affiche le résultat de mon echo de test. (je met à jour le code du premier post pour mieux comprendre)
Je pense que dans ce cas je passe bien dans mon action et dans mon test isPost() puisque firebug doit envoyer une requête Post correcte qui n'est pas forcément identique à celle envoyée par Ajax.request.
Pour info, je ne l'ai pas foutu dans le fofo, mais dans le cas du isPost() j'ai un setNoRender().
Dernière modification par Delprog (21-11-2008 12:14:14)
Hors ligne
Bon, j'ai simplifié les choses.
Dans ma vue "friend.phtml" contenant le formulaire j'ai ajouté :
<div id="test"><?php echo $this->test; ?></div>
Dans le controller :
function friendAction() { $this->_helper->layout->setLayout('layout-ajax'); if ($this->_request->isPost()) { $this->view->test = 'toto'; } }
La requête Ajax :
function jsSendMail(pUrl, pForm, pContext, pRedirect) { var eval; var context; var redirect; redirect = pRedirect || false; context = (pContext) ? '?format='+pContext : ''; var myAjax = new Ajax.Request ( pUrl + context, { method: 'post', evalScripts: true, parameters : pForm.serialize(true), onLoading : function () { //new LoadingIndicator.base({pic:'ajax-loading.gif'}); }, onComplete: function transResult (response) { //LoadingIndicator.hideAll(); } } ); return false; }
L'appel à jsSendMail() :
jsSendMail('<?=$this->_httpRoot?>mail/friend', $('fmfriend'));
Donc j'ai viré le paramètre de contexte au cas où (format=xml), et mon redirect pour épurer.
Et en résultat aucun "toto"
En rappelant que l'action "friend" est appelée une première fois avec un Ajax.updater() qui charge dans une div le contenu de la vue friend.phtml.
La vue contient un formulaire qui est envoyé par un Ajax.request() et là début des problèmes.
Je suppose que la solution repose sur une infime et vile connerie, et surement du fait que je viens déjà d'un contexte xml, mais je ne vois vraiment pas.
J'espère que c'est plus clair.
A+ benjamin.
Dernière modification par Delprog (21-11-2008 12:39:49)
Hors ligne
tu as montré plus haut ce que firebug envoit, tu ne peux pas voir ce que tu recois ??
Car, si je regarde ton code :
onComplete: function transResult (response) { //LoadingIndicator.hideAll(); }
tu ne te sert pas du résultat..
au pire tu fais ca pour debuger :
onComplete: function transResult (response) { alert(response); }
et tu mets des lignes de debug dans ton code php :
function friendAction() { $this->_helper->layout->setLayout('layout-ajax'); var_dump($this->_request); if ($this->_request->isPost()) { $this->view->test = 'toto'; } }
Ou alors, c'est que j'ai toujours pas compris ce que tu veux faire
Hors ligne
@nORKy
Ce n'est pas que je n'utilise pas la réponse, mais j'ai vidé les traitements que je faisais sur le onComplete pour poster la fonction sur le fofo
Mais ce que tu m'as dis m'a mis sur la piste. Effectivement en faisant un alert(response.responseText), je me suis rendu compte qu'il me rechargeait la vue. Et j'en déduis tout de suite que je suis un pauvre débile puisque je le force à utiliser un layout quelque soit le contexte.
Donc, résultat, j'ai remis en place l'utilisation des contextes.
Si je passe "format=xml" à l'appel de la requête il me charge "friend.xml.phtml" et sinon "friend.html".
Ca me permet donc lors de l'appel de Ajax.updater de m'afficher la vue avec le formulaire. Et ensuite avec un Ajax.request dans lequel j'envoie format=xml de ne charger que la vue du contexte xml.
C'est très bien, sauf que :
Quelle est la bonne méthode pour faire la différence entre un updater et une request ? Sachant que dans mon action j'arrive forcément depuis un XmlHttpRequest().
J'utilise :
if ($this->_helper->contextSwitch->getCurrentContext() != 'xml') { $this->_helper->layout->setLayout('layout-ajax'); }
Dans mon cas, puisque lors du Ajax.request j'envoie "format=xml".
Mais je ne sais pas si c'est la bonne pratique. En tout cas ça fonctionne
Au final, ce que je voulais, c'était ne pas utiliser deux actions différentes.
Dans la même action la vue 'friend.phtml' est rendue avec le layout 'layout-ajax' si je viens d'un Ajax.updater, et si je viens d'un Ajax.request la vue 'friend.xml.phtml' est rendue sans layout en faisant un simple echo de mes erreurs de formulaire (que j'interprête ensuite avec ajax dans le onComplete).
Pour info, mon code final (très épuré):
Controller :
function friendAction() { if ($this->_helper->contextSwitch->getCurrentContext() != 'xml') { $this->_helper->layout->setLayout('layout-ajax'); } if ($this->_request->isPost()) { //traitement des erreurs $fm_errors = ....... //puis $this->view->errors = $fm_errors; } }
Javascript (très épuré là aussi, c'est pour le principe) :
function jsSendMail(pUrl, pForm, pContext, pRedirect) { var eval; var context; var redirect; redirect = pRedirect || false; context = (pContext) ? '?format='+pContext : ''; var myAjax = new Ajax.Request ( pUrl + context, { method: 'post', evalScripts: true, parameters : pForm.serialize(true), onLoading : function () { //new LoadingIndicator.base({pic:'ajax-loading.gif'}); }, onComplete: function transResult (response) { //LoadingIndicator.hideAll(); // Les erreurs sont dans response.responseText } } ); return false; }
et donc l'appel :
onclick="jsSendMail('<?=$this->_httpRoot?>mail/friend', $('fmfriend'), 'xml', false);"
Quelles sont vos pratiques dans ce genre de cas ?
Merci,
A+ benjamin
Dernière modification par Delprog (21-11-2008 14:57:51)
Hors ligne
N'oublie pas qu'un requète ajax, c'est pareil qu'une requète dans ton navigateur
Tu veux rajouter une variable dans ta requète ajax, genre type=type1
et dans ton action : if ($this->_getParam('type') == 'type1') { ... } else { ... }
ou alors, tu peux aussi inventer un contexte 'type1' et tu envois format=type1 dans ta requête.
Hors ligne
Oui biensur, avec mes contextes je fais ce que je veux.
Ce ne sont ni plus ni moins que des paramètres gérés en natif par Zend.
Hors ligne
Une petite question complémentaire
Du coup je vais utiliser JSON pour transmettre les réponse au JS.
Je fais donc dans mon controller:
if ($input->hasInvalid()) { $view_errors = array(); $view_errors = $input->getErrors(); Zend_Loader::loadClass('Zend_Json'); $this->view->errors = Zend_Json::encode($view_errors); }
Puis un echo de $this->$errors dans la vue.
Dans le JS :
onComplete: function transResult (response) { //LoadingIndicator.hideAll(); // avec un alert(response.responseText) j'ai bien un truc de la forme // {"frdNom":["isEmpty"],"frdMail":["isEmpty","emailAddressInvalid"],"frdDestMail":["isEmpty","emailAddressInvalid"]} var TextResponse = response.responseText; var JSONResponse = eval('(' + TextResponse + ')'); // eval temporaire, je vais utiliser une classe pour parser au final alert(JSONResponse.frdNom[0]); }
Sauf que l'alert ne me renvoie rien. On dirait qu'il bloque sur le "eval".
Une idée ?
A+ benjamin.
Dernière modification par Delprog (21-11-2008 16:03:24)
Hors ligne
eval('var JSONResponse = (' + TextResponse + ')');
?
Hors ligne
il y a un truc que je ne saisis pas dans ta façon de faire.
moi mon action si j'ai un post el sauve les donnée et retourne un json d'acquittement
soit j'ai pas de post c'est donc qu'on me demande les données et donc j'envoie un json de donnée
Une action invoqué pour de l'ajax n'a pas à envoyer de vue.
A+JYT
Hors ligne
Bonsoir sekaijin,
Mon action est utilisée pour deux choses différentes mais liées.
Dans un premier cas elle renvoie une vue (contenant un formulaire ici, mais peu importe) qui se charge dans une div grâce à un Ajax.updater. En fait je me suis créé un composant Javascript basé sur prototype, qui me permet de créer en dynamique une div paramétrable chargeant un contenu au centre de l'écran. J'appelle donc une action de controller et la laisse rendre la vue.
Dans le 2eme, j'envoie mon form à la même action avec un Ajax.request. J'ai laissé faire les contextes et je fais simplement le echo dans la vue friend.xml.phtml au lieu de le faire dans l'action et de sortir par un exit();
Au final c'est la même chose.
Lorsque j'envoie du post, donc par le Ajax.request, je renvoie les messages d'erreur en JSON que j'eval dans mon onComplete de la requête. Puis dans la vue friend.phtml (contenant le formulaire) j'affiche les erreurs liées à chaque champs du form.
En fait j'ai fait ça instinctivement parce que si je valide mon form grâce à un submit, il me recharge la page dans le navigateur et évidemment pas l'intérieur de ma div. Dans ce cas il faudrait que je ré-exécute un Ajax.Updater.
Donc autant faire un Ajax.request, ne rien recharger, et gérer les erreurs de saisies du formulaire par des retours JSON. Après je suis peut-être dans un délire et j'ai peut-être raté une méthode simple et évidente. Mais je me suis naturellement dirigé vers cette solution.
A+ benjamin.
Dernière modification par Delprog (21-11-2008 20:07:27)
Hors ligne
pourquoi ne pas utiliser deux action d'un même contrôleur ?
ClientController::getFromFragmentAction
et
ClientController::performDataAction
ainsi ton action getFormFragment retourne toujours une vue
et ton action performData toujours du JSON
pour ma part je fais toujours ainsi mais en plus ma vue ne contient jamais les données de formulaire juste le formulaire vide
ClientController::getFromDataAction fournis les données (ainsi le client ne demande qu'une seule fois le formulaire)
mon processus est le suivant
ClientController::getFromFragmentAction
qui envoie le formulaire à mon client
le client et l'instancie et appelle ensuite le serveur pour obtenir les données
ClientController::getFromDataAction retourne les données en JSON
le client place les données dans le formulaire
lorsque le l'utilisateur renregistre les données
le client poste en ajax les donnée
ClientController::performDataAction vérifie que les donnée sont bien passées puis vérifie la consistance des données (vérification métier CF MVC)
et enfin effectue l'opération. le performData retourne toujours un objet d'acquittement
tout c'est bien passé :
$response = array( 'success' => true, 'record' => $client->getData() );
en cas d'erreur je retourne aussi un acquittement
pas de donnée dans le post
$response = array( 'success' => false, 'error' => Ajax::NO_DATA_FOUND );
vérification métier données incorrectes
$response = array( 'success' => false, 'error' => Model::INVALID_DATA, 'record' => $formData, 'msgs' => array( 'cli_mail => Model_Client::INVALID_MAIL_FORMAT ) );
Objet introuvable en base (par exemple un update sur un id inexistant)
$response = array( 'success' => false, 'error' => Fast_Table::NOT_FOUND, 'record' => $formData );
je ne construit pas mes objet ainsi chaque couche retournant les éléments nécessaire le contrôleur lui se cantonne d'assembler le tout
de même getFromDataAction retourne un Json d'acquittement
$response = array( 'success' => false, 'error' => Fast_Table::NOT_FOUND, 'record' => $id );
ou
$response = array( 'success' => true, 'result' => 1, 'rows' => $formData );
par habitude je retourne toujours un reslut qui donne le nombre d'élément retourné et rows est un tableau d'objet
ainsi ClientController::getListAction qui retourne la liste des clients retournera
$response = array( 'success' => true, 'result' => count($clientList), 'rows' => $clientList );
si ma liste est paginée
$response = array( 'success' => true, 'result' => count($clientList), 'count' -> $allClientCount, 'rows' => $clientList );
Je n'ai ainsi jamais de collision entre les actions de mes contrôleurs.
A+JYT
Hors ligne
grandlap a écrit:
Code:
eval('var JSONResponse = (' + TextResponse + ')');?
Nop
Hors ligne
Bonjour,
@sekaijin
Merci de partager tes méthodes.
Un détail: dans l'action qui gère la validation des données, tu sors par un echo puis exit(), ou tu utilises un
$this->_helper->viewRenderer->setNoRender();
?
Je suis un train de démarrer un projet full Ajax (ou pratiquement) et j'avais imaginé un système similaire pour les actions.
La différence avec la question que j'ai posé c'est que je vais certainement utiliser une librairie comme Ext.js pour gérer l'appli. Donc mes controlleurs ne vont traiter et renvoyer que de la donnée en JSON, et ne renverrons jamais de vues.
Un peu comme tu l'expliques ici : http://www.z-f.fr/forum/viewtopic.php?pid=11758#p11758
Et dans le cas ici, je me demandais si ce n'était pas justement l'intérêt des contextes. Mais effectivement ce n'est pas forcément l'idéal. L'éclatement et le fait de donner un rôle spécifique à chaque controlleur (traitement de la vue, des données, de la validation), est effectivement certainement plus facile à maintenir.
Maintenant, est-ce qu'en général tu uniformise tout ça ? Que ce soit côté client en Ajax ou au niveau des controlleurs ? C'est à dire de n'utiliser qu'un unique controller et/ou une unique classe Ajax quelque soit la vue ou les données que tu vas traiter ?
J'ai cette sale manie de vouloir tout rendre générique, et je sais que parfois c'est me prendre la tête pour pas grand chose.
A+ benjamin.
Dernière modification par Delprog (24-11-2008 09:32:22)
Hors ligne
j'ai une classe dont tous mes controllers ajax dérivent
<?php Zend_Loader::loadClass("Application_Controllers_Action"); class Application_Controllers_Ajax extends Zend_Controllers_Action { /** * initialise le controleur * tout les controleur ajax ne retourne pas de vue * ils envoient du json au client */ public function init() { parent::init(); $this->getFrontController()->setParam('noViewRenderer', true); if ($this->_request->isXmlHttpRequest()) { $this->_response->setHeader('Content-Type', 'application/json; charset=UTF-8', true); } else { // on envoie la reponse en texte lors d'une invocation via le navigateur // cela permet de tester la réponse. $this->_response->setHeader('Content-Type', 'text/plain; charset=UTF-8', true); } $this->response = array(); } public function postDispatch() { Zend_Loader::loadClass('Zend_Json_Encoder'); echo utf8_encode(ereg_replace('"__className":"[^\"]+",', '',Zend_Json_Encoder::encode($this->response))); parent::postDispatch(); } }
utilisé ainsi
<?php Zend_Loader::loadClass("Application_Controllers_Ajax"); class AccessController extends Application_Controllers_Ajax { ... public function getListAction() { $user = Zend_Auth::getInstance()->getIdentity(); #Fast_Debug::show('$user', $user); $userid = $user->usr_id; $data = $this->model->getRequestListByUserId($userid); #Fast_Debug::show('$data',$data); if (is_array($data)) { $this->logOk(array()); $this->response = array( 'success' => true, 'results' =>count($data), 'rows'=> $data ); } else { $this->response = array( 'success' => false, 'error' => FORBIDEN ); } } }
Hors ligne