La pré factorisation

Ainsi, nous allons ajouter les listes paginées avec des commandes de mise en page semblables à celles dans question/templates/_list.php. Nous n'aimons pas nous répéter, ainsi nous extrairons le code de mise en page à partir de celui des helpers personnalisés. Un  est une fonction de PHP rendue accessible aux templates (de la même manière que les aides link_to() et format_date()).

Créez le fichier GlobalHelper.php dans askeet/apps/frontend/lib/helper et ajoutez lui le code suivant:

<?php
function pager_navigation($pager, $uri)
{
  $navigation = '';
  if ($pager->haveToPaginate())
  { 
    $uri .= (preg_match('/\?/', $uri) ? '&' : '?').'page=';
    // First and previous page
    if ($pager->getPage() != 1)
    {
      $navigation .= link_to(image_tag('first.gif', 'align=absmiddle'), $uri.'1');
      $navigation .= link_to(image_tag('previous.gif', 'align=absmiddle'), $uri.$pager->getPreviousPage()).'&nbsp;';
    }
    // Pages one by one
    $links = array();
    foreach ($pager->getLinks() as $page)
    {
      $links[] = link_to_unless($page == $pager->getPage(), $page, $uri.$page);
    }
    $navigation .= join('&nbsp;&nbsp;', $links);
    // Next and last page
    if ($pager->getPage() != $pager->getCurrentMaxLink())
    {
      $navigation .= '&nbsp;'.link_to(image_tag('next.gif', 'align=absmiddle'), $uri.$pager->getNextPage());
      $navigation .= link_to(image_tag('last.gif', 'align=absmiddle'), $uri.$pager->getLastPage());
    }
  }
  return $navigation;
}

L'aide à la navigation dans les pages améliore le code que nous avons précédemment écrit : elle permet n'importe quelle règle de routage, afin de ne pas faire afficher les liens previous pour la première page ni les liens next pour la dernière page. Nous avons également ajouté quatre nouvelles images (first.gif, previous.gif, next.gif et last.gif) pour rendre les liens plus jolis.Vous pouvez utlisier les iconbes du project nuvola. Vous réutiliserez probablement ce helper à l'avenir pour vos propres projets.

Pour employer ce helper dans le fragment de question/templates/_list.php, appelez le helper comme suit:

<?php use_helper('Text', 'Global') ?>
<?php foreach($question_pager->getResults() as $question): ?>
   
   <div class="question">
      
       <div class="interested_block">
       
           <?php include_partial('interested_user', array('question' => $question)) ?>
      
       </div>
     
       <h2><?php echo link_to($question->getTitle(), 'question/show?stripped_title='.$question->getStrippedTitle()) ?></h2>

       <div class="question_body">

           <?php echo truncate_text($question->getBody(), 200) ?>

       </div>

    </div>

<?php endforeach; ?>

<div id="question_pager">

    <?php echo pager_navigation($question_pager, 'question/list') ?>

</div>

Le nom Global se réfère au fichier GlobalHelper.php que vous venez juste de créer.

Vérifiez que tout fonctionne comme avant avec l'adresse:

http://askeet/frontend_dev.php/

Refactorisation de la navigation des pages

Liste des dernières questions

Dans le module question, créez une nouvelle action recent:

public function executeRecent()
{
  $this->question_pager = QuestionPeer::getRecentPager($this->getRequestParameter('page', 1));
}

C'est aussi simple que cela. Nous considérons que la capacité de saisir les dernières questions devrait être une méthode de classe de QuestionPeer. La classe -Peer est consacrée aux listes de retour d'objets d'une classe donnée - La méthode de classe getRecent() doit être créée. Ouvrez la classe askeet/lib/model/QuestionPeer.php et ajoutez lui ceci:

public static function getRecentPager($page)
{
  $pager = new sfPropelPager('Question', sfConfig::get('app_pager_homepage_max'));
  $c = new Criteria();
  $c->addDescendingOrderByColumn(self::CREATED_AT);
  $pager->setCriteria($c);
  $pager->setPage($page);
  $pager->setPeerMethod('doSelectJoinUser');
  $pager->init();

  return $pager;
}


Le tri en ordre décroissant des dates de création choisira les dernières questions. Cette méthode utilise self au lieu de parent parce que c'est une fonction de classe, et pas une fonction d'objet. La raison pour laquelle nous faisons ici un doSelectJoinUser() au lieu d'un simple doSelect(), c'est parce que nous savons que le template aura besoin des coordonnées de l'auteur de la question. Cela signifierait une première demande de la liste des questions, plus une demande par question pour obtenir l'utilisateur concerné. La méthode doSelectJoinUser() fait tout cela dans une seule requête : quand nous demandons

$question->getUser();

...il n'y a qu'une requête envoyée à la base de données. Le joinUser nous permet de réduire le nombre de requête à 1 + le nombre de questions à seulement 1. La base de données nous remerciera de cette optimisation facile.

La documentation Propel vous donnera toutes les explications au sujet de ce grand dispositif.

Le template de la liste des dernières questions ressemblera beaucoup à la liste des questions affichées dans la page d'accueil. Créez askeet/apps/frontend/module/question/templates/recentSuccess.php avec

<h1>recent questions</h1>

<?php include_partial('list', array('question_pager' => $question_pager)) ?>

Vous comprenez maintenant pourquoi nous avons refactorisé la liste des questions dans un fragment . En conclusion, vous avez besoin d'ajouter une règle à recent_questions dans le fichier de configuration frontend/config/routing.yml:

recent_questions:
  url:   /question/recent/:page
  param: { module: question, action: recent, page: 1 }

Mais attendez: le fragment question/_list crée des liens avec la règle de routage question/list, en l'utilisant, cela ne fonctionnera pas pour la liste des dernières questions. Nous avons besoin que la règle de routage soit passée en paramètre au fragment afin qu'elle puisse être réutilisée pour plusieurs pages. Donc changez la dernière ligne de recentSuccess.php par:

<?php include_partial('list', array('question_pager' => $question_pager, 'rule' => 'question/recent')) ?>

Et également changer les dernières lignes du fragment _list.php par:

<div id="question_pager">
  
   <?php echo pager_navigation($question_pager, $rule) ?>

</div>

N'oubliez pas d'ajouter également le paramètre de règle dans l'appel au fragment _list dans modules/question/templates/listSuccess.php.

<h1>popular questions</h1>

<?php echo include_partial('list', array('question_pager' => $question_pager, 'rule' => 'question/list')) ?>

Vider le cache (la configuration a été modifiée), et c'est tout..

Pour afficher la liste des dernières questions, saisissez dans la zone URL de votre navigateur:

http://askeet/question/recent

liste des dernières questions

Liste des dernières réponses

C'est à peu près la même chose que ci-dessus, et nous serons donc assez exhaustif sur celui-ci:

Créez le module answer:

$ symfony init-module frontend answer


Créez une nouvelle action recent:

public function executeRecent()
{
   $this->answer_pager = AnswerPeer::getRecentPager($this->getRequestParameter('page', 1));
}


Etendez la classe AnswerPeer:

public static function getRecentPager($page)
{
   $pager = new sfPropelPager('Answer', sfConfig::get('app_pager_homepage_max'));
   $c = new Criteria();
   $c->addDescendingOrderByColumn(self::CREATED_AT);
   $pager->setCriteria($c);
   $pager->setPage($page);
   $pager->setPeerMethod('doSelectJoinUser');
   $pager->init();

   return $pager;
}


Créez un nouveau template recentSuccess.php:

<?php use_helper('Date', 'Global') ?>

<h1>recent answers</h1>

<div id="answers">

   <?php foreach ($answer_pager->getResults() as $answer): ?>
  
      <div class="answer">

         <h2><?php echo link_to($answer->getQuestion()->getTitle(), 'question/show?stripped_title='.$answer->getQuestion()->getStrippedTitle()) ?></h2>

         <?php echo count($answer->getRelevancys()) ?> points

         posted by <?php echo link_to($answer->getUser(), 'user/show?id='.$answer->getUser()->getId()) ?>
         on <?php echo format_date($answer->getCreatedAt(), 'p') ?>

         <div>

            <?php echo $answer->getBody() ?>

         </div>

   <?php endforeach ?>

</div>

<div id="question_pager">

   <?php echo pager_navigation($answer_pager, 'answer/recent') ?>

</div>


Testez le dans votre navigateur:

http://askeet/answer/recent


liste des dernières réponses

Vous vous habituez à lui, n'est-ce pas ?

Note: vous avez probablement reconnu le morceau de code utilisé pour afficher les détails d'une réponse. Puisque ce code est employé dans au moins deux endroits, nous allons refactoriser et créer un _answer.php partiel, destiné à être utilisé aussi bien dans question/show et dans answer/recent.

Profil utilisateur

Le nom de l'utilisateur dans une réponse sera relié à une action de user/show (qui reste à écrire). Ce sera le profil de l'utilisateur, et il montrera les dernières questions et réponses postées, ainsi que quelques détails au sujet de l'utilisateur.

La première chose à faire est de créer l'action:

public function executeShow()
{
   $this->subscriber = UserPeer::retrieveByPk($this->getRequestParameter('id', $this->getUser()->getSubscriberId()));
   $this->forward404Unless($this->subscriber);
   $this->interests = $this->subscriber->getInterestsJoinQuestion();
   $this->answers = $this->subscriber->getAnswersJoinQuestion();
   $this->questions = $this->subscriber->getQuestions();
}

Les méthodes ->getInterestsJoinQuestion() et ->getAnswersJoinQuestion() sont des méthodes natives de la classe User. Vous pouvez examiner la classe askeet/lib/model/om/BaseUser.php pour voir comment elles fonctionnent.

Le template askeet/apps/frontend/modules/user/templates/showSuccess.php ne devrait pas vous poser de problème:

<h1><?php echo $subscriber ?>?s profile</h1>

<h2>Interests</h2>

<ul>

   <?php foreach ($interests as $interest): $question = $interest->getQuestion() ?>

      <li><?php echo link_to($question->getTitle(), 'question/show?stripped_title='.$question->getStrippedTitle()) ?></li>

   <?php endforeach; ?>

</ul>

<h2>Contributions</h2>

<ul>
  
   <?php foreach ($answers as $answer): $question = $answer->getQuestion() ?>

      <li>
  
         <?php echo link_to($question->getTitle(), 'question/show?stripped_title='.$question->getStrippedTitle()) ?><br />
         <?php echo $answer->getBody() ?>

      </li>
  
   <?php endforeach; ?>

</ul>

<h2>Questions</h2>

<ul>

   <?php foreach ($questions as $question): ?>

      <li><?php echo link_to($question->getTitle(), 'question/show?stripped_title='.$question->getStrippedTitle()) ?></li>

   <?php endforeach; ?>

</ul>

Naturellement, vous pourriez souhaiter limiter le nombre de résultat retourné par chacune des méthodes ->getInterestsJoinQuestion(), ->getAnswersJoinQuestion() et getQuestion() de l'objet User, comme l'ordre de tri. Il est tout simplement fait par une de ces méthodes, dans le fichier de classe askeet/lib/model/User.php, et nous ne dévoilerons pas ici comment le faire.

Il est temps pour l'essai final. Voyons ce que le premier utilisateur a fait:

http://askeet/user/show/id/1

profil utilisateur

Maintenant nous pouvons également lier un profil d'utilisateur à une question. Ajoutez la ligne suivante à question/templates/showSuccess.php et question/templates/_list.php au début du div question_body:

<div>
  
   asked by <?php echo link_to($question->getUser(), 'user/show?id='.$question->getUser()->getId()) ?> on <?php echo format_date($question->getCreatedAt(), 'f') ?>

</div>

N'oubliez pas de déclarer l'utilisation de l'aide Date dans _list.php.

Ajouter une barre de navigation

Nous changerons la disposition globale pour ajouter une barre latérale. Cette barre posséde le contenu dynamique, mais comme nous voulons régler sa position dans la mise en page, il ne peut pas faire partie de chaque template. En outre, en mettant le code de la barre dans le template, cela signifierait le répéter plusieurs fois, et vous savez que nous n'aimons pas faire cela.

C'est pourquoi la barre sera un composant. Un composant est le résultat d'une action (par exemple le code HTML résulte de l'exécution d'un template) mis à disposition dans une variable.

Ajouter le composant dans le gabarit principal

Ouvrez le gabarit global (askeet/apps/frontend/templates/layout.php). Vous souvenez-vous de cette partie du code:

<div id="content_bar">
  
   <!-- Nothing for the moment -->
   <div class="verticalalign"></div>

</div>

Remplacer le commentaire par

<?php include_component_slot('sidebar') ?>

Et c'est tout.

Préciser quelle action va dans le composant

Nous avons décidé d'utiliser quelque chose d'un peu plus puissant qu'un simple composant: un composant slot. C'est un élément dont l'action peut être modifiée en fonction de l'appel de l'action - permettant du contenu contextuel. C'est la configuration de vue (écrite dans le fichier view.yml) qui définit quelle action correspond à un connecteur de composant:

default:
  components:
    sidebar:      [sidebar, default]

Dans cet exemple, le connecteur de composant nommé sidebar est déclaré pour être le résultat de l'action default du module sidebar.

La configuration de vue peut être définie pour l'application entière (dans le répertoire askeet/apps/frontend/config/) ou spécialement pour un module (dans le réertoire askeet/apps/frontend/modules/mymodule/config/). Pour notre cas, nous le définirons pour l'application entière, et nous le redéfinirons, si nécessaire, pour fournir dans un contexte spécifique des liens dans la barre latérale.

Alors ouvrez askeet/apps/frontend/config/view.yml et ajoutez la configuration du connecteur de composant illustrée ci-dessus.

Ecrire l'action sidebar/default et le template

D'abord, nous laisserons symfony initialiser le nouveau module sidebar:

$ symfony init-module frontend sidebar

Ensuite, nous avons besoin d'écrire un composant default. Dans le répertoire askeet/apps/frontend/modules/sidebar/actions/, renommez actions.class.php en components.class.php, et changez son contenu par:

<?php

class sidebarComponents extends sfComponents
{
  public function executeDefault()
  {
  }
}

Une vue de composant est un template, tout comme pour une action. La différence est dans la nomination: Une vue de composant est nommée comme un fragment (commençant par le _) plutôt que comme un template (se terminant par Success). Créez ainsi un fragment askeet/apps/frontend/modules/sidebar/templates/_default.php (et effacez indexSuccess.php qui ne sera pas utilisé) avec le contenu suivant:

<?php echo link_to('ask a new question', 'question/add') ?>

<ul>

   <li><?php echo link_to('popular questions', 'question/list') ?></li>
   <li><?php echo link_to('latest questions', 'question/recent') ?></li>
   <li><?php echo link_to('latest answers', 'answer/recent') ?></li>

</ul>

Nous avons modifié le fichier de configuration view.yml, mais les actions dans l'environnement de production ne les voit pas. Ils utilisent la version en cache - celle qui ne contient pas la configuration des connecteurs de composant. Si vous voulez voir les modifications, soit vous effacez la mémoire cache soit vous naviguez dans l'environnement de développement:

$ symfony clear-cache

ou

http://askeet/frontend_dev.php/

La barre de navigation est affichée correctement sur chaque page

sidebar

Note: C'est un effet général de la configuration d'environnement de production. Ainsi vous devez vous rappeler qu'il faut employer l'environnement de développement pendant la phase de développement (quand vous changez beaucoup la configuration), et effacer la mémoire cache lorsque vous naviguez dans l'environnement de production après chaque changement dans la configuration.

Un peu plus sur la configuration de la vue

Pendant que nous sommes là, nous allons jeter un coup d'œil au fichier de configuration de l'application view.yml dans apps/config/:

default:
  http_metas:
    content-type: text/html; charset=utf-8

  metas:
    title:        symfony project
    robots:       index, follow
    description:  symfony project
    keywords:     symfony, project
    language:     en

  stylesheets:    [main, layout]

  javascripts:    []

  has_layout:     on
  layout:         layout

  components:
    sidebar:      [sidebar, default]

Le section metas contient une configuration pour les metas tags de l'ensemble du site. La clé title définit également que le titre est affiché dans la barre de titre de la fenêtre du navigateur. Ce titre est très important, parce que c'est la première chose que voit un utilisateur du site si elle est trouvée par un moteur de recherche. Il est donc nécessaire de le changer pour quelque chose de plus adapté au site askeet:

  metas:
    title:        askeet! ask questions, find answers
    robots:       index, follow
    description:  askeet!, a symfony project built in 24 hours
    keywords:     symfony, project, askeet, php5, question, answer
    language:     en

Rafraîchissez la page courante. Si vous ne constatez pas de changement, c'est parce que vous êtes dans l'environnement de production, et vous devriez d'abord effacer la mémoire cache, pour obtenir le bon titre de la fenêtre:

window title

Note: En plus de fournir un titre par défaut pour vos pages du projet, symfony par défaut crée un fichier robots.txt et favicon.ico dans le répertoire web racine (askeet/web/). N'oubliez pas de les changer également !

Note: Vous pourriez avoir besoin de changer le titre pour chaque page de votre site. Vous pouvez faire cela en définissant une configuration personnalisée de view.yml pour chaque module, mais cela ne vous permet que de donner des titres statiques. Alternativement, vous pouvez utiliser une valeur dynamique d'une action avec la méthode ->setTitle().

$this->getResponse()->setTitle($title);

Regarder ce que nous avons fait

Il s'agit d'une tradition générale de s'arrêter et de regarder ce que vous avez fait lorsque vous avez atteint le septième jour. C'est une bonne occasion de documenter un certain nombre de choses, y compris le modèle de données courant et les actions disponibles.

En fait, vous devez documenter votre code pendant que vous l'écrivez, par exemple en utilisant PHP doc-style des commentaires pour chaque méthode. Le truc avec un projet symfony est que les noms utilisés dans les méthodes ou les fonctions servent souvent d'explication de leur but et de leur utilisation. Les méthodes sont gardés courtes, et sont donc très lisibles. La plupart du temps, les templates utilisent seulement les instructions foreach et if statements qui sont assez explicites. C'est pourquoi le code symfony ne contiennent pas beaucoup de documentation

Maintenant, jetez un oeil sur le diagramme mis à jour des entités et des relations:

ERD

Ajouter un indicateur au gabarit

Quand une requête asynchrone est en attente, les utilisateurs d'un site utilisant la technologie AJAX ne savent pas si leur requête a bien été prise en compte et si le résultat va s'afficher. C'est pourquoi chaque page contenant une interaction AJAX devrait afficher un indicateur d'activité.
Pour le faire, ajoutez en haut de la balise <body> du gabarit général layout.php:

<div id="indicator" style="display: none"></div>

Caché par défaut, ce <div> s'affichera quand une requête AJAX est en attente. Il est vide, mais la feuille de style main.css (rangé dans le répertoire askeet/web/css/ ) le met en forme et lui donne son contenu

div#indicator
{
  position: absolute;
  width: 100px;
  height: 40px;
  left: 10px;
  top: 10px;
  z-index: 900;
  background: url(/images/indicator.gif) no-repeat 0 0;
}

Ajouter une interaction AJAX pour manifester son intérêt

Une interaction AJAX est faite de trois parties: un appel (un lien, un bouton ou n'importe quel controle qu'un utilisateur peut manipuler pour lancer une action), une action côté serveur, et une zone dans la page où afficher le résultat de l'action de l'utilisateur.


L'appel

Revenons à l'affichage des quesiton. Une question peut être afficher dans les listes de questions et dans le détails d'une question.

list of question

That's why the code for the question title and interest block was refactored into a  fragment. Open this fragment again, add a link to allow users to declare their interest:
C'est pourquoi le code pour le titre de la question et le bloque des intérêts a été refactorisé dans le fragment _interested_user.php. Ouvrez ce fragment, ajoutez un lien pour que l'utilisatuer puisse manifester son intérêt.

<?php use_helper('User') ?>
 
<div class="interested_mark" id="mark_<?php echo $question->getId() ?>">

   <?php
echo $question->getInterestedUsers() ?>

</div>
 
<?php echo link_to_user_interested($sf_user, $question) ?>

 

Ce lien fait plus qu'une simple redirection vers une page. En toute logique, si un utilisateur a déjà manifester son intérêt pour une question données, il ne doit pas pouvoir le manifester à nouveau. Et si l'utilisateur n'est pas authentifié ... nous verrons ce cas plus tard

Le lien est écrit  dans un helper, qui nécessite d'être créé dans un fichier askeet/apps/frontend/lib/helper/UserHelper.php:

<?php
 
use_helper('Javascript');
 
function link_to_user_interested($user, $question)
{
    if ($user->isAuthenticated())
    {
        $interested = InterestPeer::retrieveByPk($question->getId(), $user->getSubscriberId());
        if ($interested)
        {
            // already interested
            return 'interested!';
         }
        else
         {
            // didn't declare interest yet
            return link_to_remote('interested?', array(
                    'url' => 'user/interested?id='.$question->getId(),
                    'update' => array('success' => 'block_'.$question->getId()),
                    'loading' => "Element.show('indicator')",
                    'complete' => "Element.hide('indicator');".visual_effect('highlight', 'mark_'.$question->getId()),
                )
            );
        }
    }
    else
    {
        return link_to('interested?', 'user/login');
    }
}
 
?>
 

La fonction link_to_remote() est la paremière partie de l'interaction AJAX: L'appel. elle mentionne quelle action doit être appelé quand un utilisateur clique sur le lein (ici: : user/interested) et quelle zone de lapage doit être mise à jour avec le résultat de l'action (ici: l'élément ayant pour id block_XX). Deux gestionnaires d'évènements (loading et complete) sont ajoutés et associés à des fonctions javascript  prototype. La librairie prototype mets à disposition des outils javascript très pratique pour mettre en place des effets visuels dans une page webvia un simple appel de fonction. Son seul défaut est le manque de documentation mais le code est très parlant

Nous choisissons d'utiliser un helper plutot qu'un partial car la fonction contient plus de code PHP que de code HTML/

N'oubliez pas d'ajouter le blod ayant pour id id="block_<?php echo $question->getId() ?>"  dans le fragment question/_list

<div class="interested_block" id="block_<?php echo $question->getId() ?>">

    <?php
include_partial('interested_user', array('question' => $question)) ?>

</div>  

Ceci ne marchera que si sf alias est bien configuré dans votre serveur apache

La zone de résultat

L'attribut update du helper javascript link_to_remote() spécifie la zone de résultat. Dans ce cas, le résultat de l'action user/interested remplacera le contenu de l'élément ayant pour id block_XX. Si vous êtes perdu, jetez un oeil à ce que l'intégration de ce fragment dans le template donne:

...
<div class="interested_block" id="block_<?php echo $question->getId() ?>">
    <!-- between here -->
    <?php use_helper('User') ?>
    <div class="interested_mark" id="mark_<?php echo $question->getId() ?>">
        <?php echo $question->getInterestedUsers() ?>
    </div>
    <?php echo link_to_user_interested($sf_user, $question) ?>
    <!-- and there -->
</div>
...
 

La zone de résultat est la partie contenue entre les deux commentaires. L'action, une fois exécutée remplacera le contenu.

L'intérêt du seond id (mark_XX) est purement visuel. le gestionnaire d'évènement complete du helper link_to_remote met en évidence le <div> interested_mark  de l'intérêt cliqué... après que l'action retourne un nombre d'intérêt incréementé.

Action côté serveur

L'appel AJAX pointe sur l'action user/interested. Cette action doit créée un nouvel enregistrement dans la table Interest pour la question courante et l'utilisateur courant.Voilà comment cela se passe avec symfony:

public function executeInterested()
{
    $this->question = QuestionPeer::retrieveByPk($this->getRequestParameter('id'));
    $this->forward404Unless($this->question);
 
    $user = $this->getUser()->getSubscriber();
 
    $interest = new Interest();
    $interest->setQuestion($this->question);
    $interest->setUser($user);
    $interest->save();
} 

Souvenez vous que la méthode ->save() de l'objet  Interest object a été modifié pour incrémenter le champs interested_user du User associé. Donc le nombre d'utilisateurs intéressés par la question courante sera incrémenté à l'acran après l'appl de l'action comme par magie.

Et que devrait être le résultat de l'affichage du template interestedSuccess.php ?

<?php include_partial('question/interested_user', array('question' => $question)) ?> 

Il affiche le fragment _interested_user.php du module question une fois de plus. C'est le grand inétrêt d'avoir écrit les fragments en premier.

Nous devons églement désactivé le rendu pour ce template (modules/user/config/view.yml):

interestedSuccess: 
has_layout: off

Test final

Le développement AJAX pour l'intérêt est maintenant terminé. Vous pouvez tester avec un utilisateur / mot de passe existant dans la page de login, afficher la liste des quesitons et cliquer sur un lien 'interested?'. L'indicateur apparaît pendant que la requête est traitée par le serveur. Ensuite, le nombre est incrémenté et mis en évidence quand le serveur renvoie sa réponse. Notez que le lien intitulé 'interested?' est maintenant  un texte sans lien 'interested!', merci au helper link_to_user_interested :

ajax

Si vous voulez plus d'exemple sur les heper AJAX, vous pouvez lire drag-and-drop shopping cart tutorial.

Ajouter un formulaire d'authentification en ligne

Nous avons dit que seuls les utilisateurs enregistrés pouvaient manifester leur intérêt  pour une question. Cela signifie que si un utilisateur non authentifié clique sur un lien 'interested?", la page de login doit être affiché avant tout.

Mais attendez un peu. Pourquoi un utilisateur devrait chargé une nouvelle page et perdre la question qui suscite son intérêt? Une meilleure idée serait d'avoir un formulaire de login qui apparaisse dynamiquement sur la page. C'est ce que nous allons faire.

Ajouter un formulaire d'authentification caché dans le gabarit

Ouvrez le gabarit global (dans askeet/apps/frontend/templates/layout.php), et ajoutez y (entre le header et le div content ):

<?php use_helper('Javascript') ?>
 
<div id="login" style="display: none">
    <h2>Please sign-in first</h2>
 
    <?php echo link_to_function('cancel', visual_effect('blind_up', 'login', array('duration' => 0.5))) ?>
 
    <?php echo form_tag('user/login', 'id=loginform') ?>
        nickname: <?php echo input_tag('nickname') ?><br />
        password: <?php echo input_password_tag('password') ?><br />
        <?php echo input_hidden_tag('referer', $sf_params->get('referer') ? $sf_params->get('referer') : $sf_request->getUri()) ?>
        <?php echo submit_tag('login') ?>
    </form>

</div>
 

Une fois de plus ce formulaire est cahé par défaut? Le champs caché referer contient le paramètre de requête referer s'il existe, sinon l'url courante.

Avoir un formulaire qui apparaît quand un utilisateur non authentiofié clique sur un lien 'interested?'

Vous vous souvenez du helper User que nous avons écrit? On va maintenant traité le cas où un utilistateur n'est pas authentifié Ouvrez à nouveau le fichier askeet/lib/helper/UserHelper.php et changez la ligne:

return link_to('interested?', 'user/login')

par celle ci:

return link_to_function('interested?', visual_effect('blind_down', 'login', array('duration' => 0.5)))

Quand l'utilisateur n'est pas authentifié, le lien du mot 'interested?' lance un effet javascript prototype (blind_down) qui révèlera l'élémenbt ayant pour id login - et c'est précisémment le formulaire que nous avons ajouté dans le gabarit

Authentifié l'utilisateur

L'action user/login a déjà été écrite et refactorisée. Doit on la modifier à nouveau?

public function executeLogin()
{
    if ($this->getRequest()->getMethod() != sfRequest::POST)
    {
        // display the form
        $this->getRequest()->getParameterHolder()->set('referer', $this->getRequest()->getReferer());
 
        return sfView::SUCCESS;
    }
    else
    {
        // handle the form submission
        // redirect to last page
        return $this->redirect($this->getRequestParameter('referer', '@homepage'));
    }
}
 

Eh bien non! Ca marche trés bien comme ça, la gestion du referer va rediriger l'utilisateur sur la page contenant le lien qu'il a cliqué.

Testez la fonctionnalité AJAX maintenant. A un utilisateur non enregistré  on présentera un forumlaire d'authentification sur la page courante. Si le nom d'utilisateur / mot de passe est reconnu, la page sera rafraichieet l'utilisateur pourra cliquer sur le lien 'intersted?' qu'il visait.

login form revealed

Dans beaucoup d'interaction AJAX comme celle ci, le template de l'action côté serveur est un simple include_partial. C'est parce que un résultat initial est souvent affiché quand la page entière est chargée pour la première fois, et parce que la partie mise à jour par l'action AJAX est aussi une partie du template initial.