Symfony2 - Jobeet - Jour 18 - AJAX

1 1 1 1 1 1 1 1 1 1 Rating 1.75 (2 Votes)
Submit to DeliciousSubmit to DiggSubmit to FacebookSubmit to Google PlusSubmit to StumbleuponSubmit to TechnoratiSubmit to TwitterSubmit to LinkedIn

Hier (Symfony2 - Jobeet - Jour 17 - Recherche), nous avons mis en place un moteur de recherche très puissant pour Jobeet, grâce à la bibliothèque Zend Lucene. Dans les lignes suivantes, afin d'améliorer la réactivité du moteur de recherche, nous allons profiter d'AJAX pour convertir le moteur de recherche pour donner les résultats en temps réel.

Comme le formulaire devrait fonctionner avec et sans Javascript, la fonction de recherche en direct sera mise en œuvre en utilisant JavaScript. L'utilisation de JavaScript non intrusif permet également une meilleure séparation du code client entre HTML, CSS, et les comportements JavaScript.

Installation de jQuery

Allez sur le site jQuery, téléchargez la dernière version (nous utiliserons la version compressée de JQuery 2.1.1 dans ce tutoriel), et mettre le fichier js dans src/Erlem/JobeetBundle/Resources/public/js/.

Après avoir mis le fichier js dans le répertoire js, exécutez la commande suivante pour indiquer à Symfony de le rendre accessible au public :

php app/console assets:install web

Inclure jQuery

Comme nous aurons besoin de jQuery sur toutes les pages, mettez à jour le fichier layout :

  • src/Erlem/JobeetBundle/Resources/views/layout.html.twig
<!-- ... -->
    {% block javascripts %}
        <script type="text/javascript" src={{ asset('bundles/erlemjobeet/js/jquery-2.1.1.min.js') }}></script>
    {% endblock %}
<!-- ... -->

Ajout de comportements

Mettre en œuvre une recherche en direct signifie que chaque fois que l'utilisateur tape une lettre dans la boîte de recherche, un appel vers le serveur doit être déclenché. Le serveur retourne ensuite les informations nécessaires pour mettre à jour certaines régions de la page sans rafraîchir la page entière.

Au lieu d'ajouter le comportement avec l'attribut HTML on*(), le principe de base de jQuery est d'ajouter des comportements au DOM après que la page soit complètement chargée. De cette façon, si vous désactivez le support JavaScript dans votre navigateur, le comportement est inscrit, et la forme fonctionne toujours comme avant.

La première étape consiste à intercepter chaque fois un type de clés dans le moteur de recherche d'un utilisateur :

  • Expliquation du code avant la mise en œuvre
$('#search_keywords').keyup(function(key)
{
    if (this.value.length >= 3 || this.value == '')
    {
        // do something
    }
});

Ne pas ajouter le code pour l'instant, car nous allons le modifier fortement. Le code JavaScript finale sera ajouté dans la section suivante.

Chaque fois que l'utilisateur tape sur une touche, jQuery exécute la fonction anonyme définie dans le code ci-dessus, mais seulement si l'utilisateur a tapé plus de 3 caractères.

Faire un appel AJAX sur le serveur est aussi simple que d'utiliser la méthode load() de l'élément DOM :

  •  Expliquer code avant la mise en œuvre
$('#search_keywords').keyup(function(key)
{
    if (this.value.length >= 3 || this.value == '')
    {
        $('#jobs').load(
            $(this).parents('form').attr('action'), { query: this.value + '*' }
        );
    }
});

Pour gérer l'appel AJAX, la même action que celle "normale" est appelée. Les changements nécessaires dans l'action se feront dans la section suivante.

Last but not least, si JavaScript est activé, nous allons vouloir supprimer le bouton de recherche :

  •  Expliquation code avant la mise en œuvre
$('.search input[type="submit"]').hide();

Retour utilisateur

Chaque fois que vous effectuez un appel AJAX, la page ne sera pas mise à jour immédiatement. Le navigateur va attendre la réponse du serveur avant la mise à jour la page. Dans le même temps, vous devez fournir une rétroaction visuelle à l'utilisateur pour l'informer que quelque chose se passe.

Une convention est d'afficher une icône de chargement lors de l'appel AJAX. Mettre à jour la configuration pour ajouter l'image loader et la cacher par défaut :

  •  src/Erlem/JobeetBundle/Resources/views/layout.html.twig
<!-- ... -->
    <div class="search">
        <h2>Ask for a job</h2>
        <form action={{ path('erlem_job_search') }} method="get">
            <input type="text" name="query" value='{{ app.request.get('query') }}' id="search_keywords" />
            <input type="submit" value="search" />
            <img id="loader" src={{ asset('bundles/erlemjobeet/images/loader.gif') }} style="vertical-align: middle; display: none" />
            <div class="help">
                Enter some keywords (city, country, position, ...)
            </div>
        </form>
    </div>
<!-- ... -->

Maintenant, il faut créer un fichier de search.js que contient le code JavaScript que nous avons expliqué jusqu'ici :

  •  src/Erlem/JobeetBundle/Resources/public/js/search.js
$(document).ready(function()
{
    $('.search input[type="submit"]').hide();

    $('#search_keywords').keyup(function(key)
    {
        if(this.value.length >= 3 || this.value == '') {
            $('#loader').show();
            $('#jobs').load(
                $(this).parent('form').attr('action'),
                { query: this.value ? this.value + '*' : this.value },
                function() {
                    $('#loader').hide();
                }
            );
        }
    });
});

 Exécutez la commande pour dire Symfony pour le rendre accessible au public :

php app/console assets:install web

Vous devez également mettre à jour le layout pour inclure ce nouveau fichier :

  •  src/Erlem/JobeetBundle/Resources/views/layout.html.twig
<!-- ... -->
    {% block javascripts %}
        <script type="text/javascript" src={{ asset('bundles/erlemjobeet/js/jquery-2.0.3.min.js') }}></script>
        <script type="text/javascript" src={{ asset('bundles/erlemjobeet/js/search.js') }}></script>
    {% endblock %}
<!-- ... -->

AJAX dans une action

Si JavaScript est activé, jQuery va intercepter toutes les touches tapées dans le champ de recherche, et appellera l'action de recherche. Sinon, la même recherche action est également appelée lorsque l'utilisateur soumet le formulaire en appuyant sur la touche Entrée.

Ainsi, l'action de recherche doit maintenant déterminer si l'appel est effectué via AJAX ou non. Chaque fois qu'une demande est faite avec un appel AJAX, la méthode isXmlHttpRequest() de l'objet de requête renvoie true.

  •  src/Erlem/JobeetBundle/Controller/JobController.php
use Symfony\Component\HttpFoundation\Response;

class JobController extends Controller
{  
    // ...

    public function searchAction(Request $request)
    {
        $em = $this->getDoctrine()->getManager();
        $query = $this->getRequest()->get('query');

        if(!$query) {
            if(!$request->isXmlHttpRequest()) {
                return $this->redirect($this->generateUrl('erlem_job'));
            } else {
                return new Response('No results.');
            }
        }

        $jobs = $em->getRepository('ErlemJobeetBundle:Job')->getForLuceneQuery($query);

        if($request->isXmlHttpRequest()) {

            return $this->render('ErlemJobeetBundle:Job:list.html.twig', array('jobs' => $jobs));
        }

        return $this->render('ErlemJobeetBundle:Job:search.html.twig', array('jobs' => $jobs));
    }
}

Si la recherche ne retourne aucun résultat, nous devons afficher un message :

  •  src/Erlem/JobeetBundle/Controller/JobController.php
    public function searchAction(Request $request)
    {
        $em = $this->getDoctrine()->getManager();
        $query = $this->getRequest()->get('query');

        if(!$query) {
            if(!$request->isXmlHttpRequest()) {
                return $this->redirect($this->generateUrl('erlem_job'));
            } else {
                return new Response('No results.');
            }
        }

        $jobs = $em->getRepository('ErlemJobeetBundle:Job')->getForLuceneQuery($query);

        if($request->isXmlHttpRequest()) {
            if('*' == $query || !$jobs || $query == '') {
                return new Response('No results.');
            }

            return $this->render('ErlemJobeetBundle:Job:list.html.twig', array('jobs' => $jobs));
        }

        return $this->render('ErlemJobeetBundle:Job:search.html.twig', array('jobs' => $jobs));
    }

Faites maintenant une recherche pour voir le résultat :

01-117-symfony2-jobeet-jour-18-ajax

Test AJAX

Comme Symfony ne peut pas simuler JavaScript, vous avez besoin de l'aider lors de l'essai des appels AJAX. Cela signifie essentiellement que vous devez ajouter manuellement l'en-tête que jQuery et toutes les autres grandes bibliothèques JavaScript envoient à la demande:

  • src/Erlem/JobeetBundle/Tests/Controller/JobControllerTest.php
class JobControllerTest extends WebTestCase
{
    // ...

    public function testSearch()
    {
        $client = static::createClient();

        $crawler = $client->request('GET', '/job/search');
        $this->assertEquals('Erlem\JobeetBundle\Controller\JobController::searchAction', $client->getRequest()->attributes->get('_controller'));

        $crawler = $client->request('GET', '/job/search?query=sens*', array(), array(), array(
            'X-Requested-With' => 'XMLHttpRequest',
        ));
        $this->assertTrue($crawler->filter('tr')->count()== 2);
    }
}

Ensuite, lancez la commande :

phpunit -c app src/Erlem/JobeetBundle/Tests/Controller/JobControllerTest

Au jour 17 (Symfony2 - Jobeet - Jour 17 - Recherche), nous avons utilisé la bibliothèque Zend Lucene pour mettre en œuvre le moteur de recherche. Aujourd'hui, nous avons utilisé jQuery pour le rendre plus souple. Le framework Symfony fournit tous les outils fondamentaux pour construire des applications MVC avec aisance, et joue aussi bien avec les autres composants. Comme toujours, essayez d'utiliser le meilleur outil pour votre travail.

Demain, nous allons vous expliquer comment internationaliser le site Jobeet.


Symfony2 - Jour 17 - Recherche
[Sommaire] Symfony2 - Jour 19 - Internationalisation et localisation >


Pour ce tutoriel, je me suis inspiré du tutoriel Symfony2 Jobeet du site IntelligentBee

Submit to DeliciousSubmit to DiggSubmit to FacebookSubmit to Google PlusSubmit to StumbleuponSubmit to TechnoratiSubmit to TwitterSubmit to LinkedIn