Construire un formulaire de recherche

Dans cette leçon, nous allons créer un formulaire capable de filtrer nos listes selon plusieurs paramètres.

Dépôt de code à télécharger

Ce que nous allons couvrir

  • Définition des attentes: recherche ou filtrage
  • Effectuer des mises à jour mineures à l’objet Property
  • Création d’une nouvelle page de résultats de recherche
  • Construire un formulaire de recherche
  • Application de filtres à une liste de données
  • Tirer les résultats dans le modèle

 

Définition des attentes: recherche ou filtrage

Avant de commencer, il est important de définir une base de référence pour ce que nous cherchons à accomplir dans ce didacticiel, car la recherche, comme vous le savez peut-être, est une véritable boîte de Pandore.

La recherche est une science inexacte. Il cherche à fournir à l’utilisateur les résultats les plus précis possibles. C’est très subjectif, et il est donc toujours à l’écoute pour augmenter les chances de satisfaire le plus grand nombre d’utilisateurs. Ce n’est jamais parfait. À cette fin, de nombreux outils de recherche tiers disponibles effectuent une grande partie du travail à votre place, et vous pouvez les intégrer dans à peu près tout projet piloté par une base de données. Solr , Sphinx et Elastic Search font partie des options les plus populaires . Même MySQL a quelques fonctionnalités de recherche de base qui sont souvent tout ce dont vous avez besoin.

La recherche par mot-clé intelligente et puissante est peut-être un sujet pour un autre tutoriel, car dans cette leçon, bien que nous traitions de la “recherche” au sens académique, nous parlons en réalité de filtrage . Contrairement à la recherche, le filtrage devrait fournir les résultats attendus 100% du temps, car tout ce que vous faites est de faire correspondre objectivement les paires clé / valeur de l’entrée de l’utilisateur aux paires clé / valeur stockées dans des enregistrements individuels faisant partie d’une collection. Comme il s’agit d’une leçon d’introduction, nous traiterons principalement du filtrage simple des enregistrements, mais nous ferons également un peu de correspondance des mots clés. Comprenez simplement que ce ne sera pas la solution optimale de recherche de texte libre pour la plupart des projets.

Sur une note indépendante, augmentez votre niveau de tolérance pour une conception visuelle imparfaite. Les formulaires que nous créerons ne rendront pas de balisage compatible avec notre feuille de style. Nous avons tout un tutoriel sur la personnalisation des formulaires à venir très prochainement, et dans l’intérêt de maintenir un seul focus, cette étape a été omise de ce tutoriel.

 

Mise à jour mineure de l’objet Property

Regardons le formulaire de recherche sur la page d’accueil. Nous avons quatre paramètres que nous pouvons utiliser dans notre recherche:

  • Date d’arrivée
  • Nombre de nuits
  • Chambres (minimum)
  • Mots clés

Les deux derniers sont assez simples, mais en ce qui concerne les deux premiers, nous n’avons prévu aucun logement pour la disponibilité des locations. Cela viendra dans le futur, lorsque nous ajouterons une fonctionnalité permettant aux utilisateurs de réserver des locations pour une période donnée.

Dans l’intérêt d’enseigner le concept au-delà des exigences d’un site Web imaginaire, nous allons créer des champs temporaires sur les Propertyobjets qui stockent ces données de manière native dans l’enregistrement. Une fois que les utilisateurs peuvent réserver des locations, nous allons supprimer ces champs, mais pour l’instant, nous souhaitons simplement que notre formulaire de recherche fonctionne, alors ajoutons ce qui suit:

app / src / Property.php

 

//...
class Property extends DataObject
{

  private static $db = [
        //...
      'AvailableStart' => 'Date',
      'AvailableEnd'=> 'Date'
    ];

 

Nous devrions également ajouter quelques champs au CMS pour pouvoir les éditer.

 

public function getCMSFields()
{
  //...
  DateField::create('AvailableStart', 'Date available (start)'),
  DateField::create('AvailableEnd', 'Date available (end)'),
  //...    

 

Courez dev/buildet voyez que nous obtenons de nouveaux champs.

Nous vous évitons d’avoir à renseigner ces valeurs pour 100 enregistrements. Le moment est donc propice pour exécuter le __assets/set_property_dates.sqlfichier dans cette leçon afin de disposer de quelques données sur lesquelles effectuer des recherches. Il remplit les colonnes AvailableStartet AvailableEndavec une date aléatoire d’ici un an. La date de fin est une valeur aléatoire de 1 à 14 jours après la date de début.

Pour référence, les requêtes sont les suivantes:

 

UPDATE SilverStripe_Lessons_Property SET AvailableStart = FROM_UNIXTIME(
        UNIX_TIMESTAMP(NOW()) + FLOOR(0 + (RAND() * 31536000))
);
UPDATE SilverStripe_Lessons_Property SET AvailableEnd = FROM_UNIXTIME(
        UNIX_TIMESTAMP(AvailableStart) + FLOOR(1 + (RAND() * 1209600))
);

UPDATE SilverStripe_Lessons_Property_Live SET AvailableStart = (
  SELECT AvailableStart
    FROM SilverStripe_Lessons_Property
    WHERE
      SilverStripe_Lessons_Property.ID = SilverStripe_Lessons_Property_Live.ID
);
UPDATE SilverStripe_Lessons_Property_Live SET AvailableEnd = (
  SELECT AvailableEnd
    FROM SilverStripe_Lessons_Property
    WHERE
      SilverStripe_Lessons_Property.ID = SilverStripe_Lessons_Property_Live.ID
);

 

Notre recherche par mot clé devra rechercher une description de la propriété. Nous allons également ajouter ce champ.

app / src / Property.php

 

//...
class Property extends DataObject
{

    private static $db = [
     //...
        'Description' => 'Text',
        'AvailableStart' => 'Date',
        'AvailableEnd'=> 'Date'
    ];

  //...
    public function getCMSFields()
    {
        $fields = FieldList::create(TabSet::create('Root'));
        $fields->addFieldsToTab('Root.Main', array(
            TextField::create('Title'),
            TextareaField::create('Description'),
      //...

 

Courez dev/build, et nous sommes prêts à rouler!

Création d’une page de résultats de recherche

La page qui rend les résultats de la recherche pour la propriété est assez distincte. Dans les ressources de cette leçon, vous trouverez le code HTML d’un nouveau modèle __assets/property-search-results.html. Importons cela dans notre projet.

Copiez le contenu du fichier dans app / templates / SilverStripe / Lessons / Layout / PropertySearchPage.ss .

Ensuite, créez de nouvelles classes pour la page.

app / src / PropertySearchPage.php

 

namespace SilverStripe\Lessons;

use Page;

class PropertySearchPage extends Page
{

}

 

app / src / PropertySearchPageController.php

 

namespace SilverStripe\Lessons;

use PageController;

class PropertySearchPageController extends PageController
{

}

 

Exécuter dev/build?flush, car nous modifions la base de données et introduisons un nouveau modèle.

Ensuite, accédez au CMS et remplacez la page Trouver une location par une page de recherche de propriétés . Naviguez jusqu’à la page sur l’interface et confirmez que notre modèle est affiché.

 

Construire le formulaire de recherche

Notre projet comporte actuellement deux formulaires de recherche: un sur la page d’accueil et un sur notre nouvelle PropertySearchResultspage. Travaillons simplement sur la page de résultats de recherche pour le moment.

Nous allons créer une nouvelle méthode appelée PropertySearchFormet nous ferons de notre mieux pour recréer les champs du modèle.

app / src / PropertySearchPageController.php

 

namespace SilverStripe\Lessons;

use PageController;
use SilverStripe\Forms\Form;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\TextField;
use SilverStripe\Forms\DropdownField;
use SilverStripe\Forms\FormAction;
use SilverStripe\ORM\ArrayLib;

class PropertySearchPageController extends PageController
{

    public function PropertySearchForm()
    {
        $nights = [];
        foreach(range(1,14) as $i) {
            $nights[$i] = "$i night" . (($i > 1) ? 's' : '');
        }
        $prices = [];
        foreach(range(100, 1000, 50) as $i) {
            $prices[$i] = '$'.$i;
        }

        $form = Form::create(
            $this,
            'PropertySearchForm',
            FieldList::create(
                TextField::create('Keywords')
                    ->setAttribute('placeholder', 'City, State, Country, etc...')
                    ->addExtraClass('form-control'),
                TextField::create('ArrivalDate','Arrive on...')             
                    ->setAttribute('data-datepicker', true)
                    ->setAttribute('data-date-format', 'DD-MM-YYYY')
                    ->addExtraClass('form-control'),
                DropdownField::create('Nights','Stay for...')                   
                    ->setSource($nights)
                    ->addExtraClass('form-control'),
                DropdownField::create('Bedrooms')                   
                    ->setSource(ArrayLib::valuekey(range(1,5)))
                    ->addExtraClass('form-control'),
                DropdownField::create('Bathrooms')                  
                    ->setSource(ArrayLib::valuekey(range(1,5)))
                    ->addExtraClass('form-control'),
                DropdownField::create('MinPrice','Min. price')
                    ->setEmptyString('-- any --')
                    ->setSource($prices)
                    ->addExtraClass('form-control'),
                DropdownField::create('MaxPrice','Max. price')
                    ->setEmptyString('-- any --')
                    ->setSource($prices)
                    ->addExtraClass('form-control')             
            ),
            FieldList::create(
                FormAction::create('doPropertySearch','Search')
                    ->addExtraClass('btn-lg btn-fullcolor')
            )
        );

        return $form;
    }
}

 

Nous avons déjà vu des formulaires et la plupart de ces éléments ne devraient pas être nouveaux pour vous. Le nouveau concept que nous avons introduit ici est que nous construisons d’abord quelques tableaux dont nous aurons besoin pour nos champs déroulants. Généralement, exécuter tout cela avant de construire le formulaire est une bonne idée. Si vous avez besoin d’accéder à ces listes au-delà de la portée du formulaire, il est souvent conseillé de les créer dans une méthode distincte.

Ajoutons maintenant le formulaire au modèle. Supprimez la <form>balise entière représentant le formulaire statique et remplacez-la par $PropertySearchForm. Il devrait bien rendre. Ce n’est pas aussi beau que le design, mais au moins c’est utilisable. Nous allons nettoyer le balisage plus tard.

 

Go GET it: Gestion du formulaire de recherche

Vous avez peut-être remarqué que nous avons laissé de côté une étape très importante dans la création d’un formulaire. Nous n’avons jamais mis à jour notre $allowed_actionstableau pour autoriser la PropertySearchFormméthode. C’est parce que nous n’en avons pas besoin!

Pensez à ce que nous voulons dans notre formulaire de recherche. L’utilisateur doit pouvoir créer une vue filtrée des résultats et l’envoyer à un ami, la coller dans un autre navigateur ou actualiser la page. En bref, ces résultats doivent avoir leur propre URL.

Par défaut, les formulaires sont soumis via la POSTméthode, ce qui est très pratique pour gérer les entrées utilisateur menant des données en mutation sur le back-end, mais tout ce que nous souhaitons de notre formulaire ici est un générateur de URL enrichi. Tout ce que le formulaire doit faire est de nous rediriger vers une URL qui transmet tous ses paramètres à une chaîne de requête (requête GET) et permet au contrôleur de le récupérer à partir de là. Par conséquent, pour ce formulaire, nous utiliserons la GETméthode. Mettons à jour l’objet formulaire pour le faire.

app / src / PropertySearchPageController.php

 

public function PropertySearchForm()
{
    //...
    $form->setFormMethod('GET');

    return $form;
}

 

Ensuite, nous devons nous assurer que le formulaire ne soit pas soumis à son gestionnaire doPropertySearch, mais plutôt qu’il redirige simplement vers la vue par défaut du contrôleur.

app / src / PropertySearchPageController.php

 

public function PropertySearchForm()
{
    //...
    $form->setFormMethod('GET')
         ->setFormAction($this->Link());

    return $form;
}

 

Maintenant, testons-le. Mettez des données dans le formulaire et cliquez sur Rechercher . Inutile de dire qu’il ne faut pas mettre à jour les résultats, qui sont toujours statiques, mais noter l’URL. Cela a l’air bien, mais il y a un problème. Le SecurityIDparamètre n’appartient pas à notre URL. Normalement, ceci est utilisé pour contrecarrer les attaques CSRF (Cross-Site Request Forgery), mais comme il s’agit d’un GETformulaire simple , nous n’avons pas besoin de cette mesure de sécurité (cela empêcherait un autre utilisateur d’accéder à l’URL). Supprimons le jeton de sécurité.

app / src / PropertySearchPageController.php

 

public function PropertySearchForm()
{
    //...
    $form->setFormMethod('GET')
         ->setFormAction($this->Link())
         ->disableSecurityToken();

    return $form;
}

 

Actualisez la page et essayez à nouveau la recherche. L’URL devrait avoir l’air un peu plus propre maintenant. Si le SecurityIDparamètre se trouve toujours dans l’URL, essayez de supprimer la chaîne de requête complète de la barre d’adresses, puis essayez à nouveau.

Application de filtres à une liste de données

Maintenant que nous avons des paramètres de recherche entrant dans la requête, nous pouvons commencer à les appliquer en tant que filtres à a SilverStripe\ORM\DataList. Rappelez-vous, dans notre tutoriel sur l’ORM, que ces listes sont chargées paresseux , ce qui le rend très bien pour appliquer un nombre variable de filtres. Plutôt que de construire une clause de filtre en une fois, nous pouvons prendre notre temps et appliquer chaque filtre indépendamment, sans avoir à vous soucier de l’exécution d’une requête.

Nous allons exécuter tout cela dans l’ indexaction de notre contrôleur. Chaque contrôleur déclenche l’ indexaction par défaut, il n’est donc pas nécessaire de l’ajouter à la $allowed_actionsmatrice.

Par défaut, si aucune recherche n’est appliquée, nous voulons renvoyer tous les enregistrements de propriété. Commençons donc avec cela.

Nous n’avons pas encore configuré la pagination, limitons donc le jeu de résultats à 20, de sorte que nous n’ayons pas à extraire les 100 propriétés par défaut.

app / src / PropertySearchPageController.php

 

//...
use SilverStripe\Control\HTTPRequest;

class PropertySearchPageController extends PageController
{

    public function index(HTTPRequest $request)
    {
        $properties = Property::get()->limit(20);

        return [
            'Results' => $properties
        ];
    }

  //...
}

 

Cela devrait être assez simple. Nous obtenons tous les enregistrements et, finalement, nous allons parcourir une variable personnalisée appelée $Resultssur le modèle.

Maintenant, commençons à inspecter la GETdemande. Nous allons vérifier chaque paramètre et appliquer le filtre nécessaire à chacun. Pour cette section, nous utiliserons beaucoup de SearchFilterclasses qui pourraient être nouvelles pour vous. Pour obtenir une liste complète des filtres disponibles, voir framework / src / ORM / Filters ou lisez la documentation de l’ API .

Tout d’abord, utilisons le PartialMatchFilterpour faire correspondre un mot clé dans le titre.

 

if ($search = $request->getVar('Keywords')) {
    $properties = $properties->filter([
        'Title:PartialMatch' => $search             
    ]);
}

 

N’oubliez pas PartialMatchque ne vérifie qu’une séquence de caractères dans le champ (insensible à la casse). Il n’effectuera aucune transformation de langue ni analyse syntaxique. Une recherche de vues sur la mer ne correspond pas à un titre contenant une vue sur la mer .

Ensuite, nous analyserons la date et créerons notre filtre pour AvailableStartet AvailableEnd.

 

if ($arrival = $request->getVar('ArrivalDate')) {
    $arrivalStamp = strtotime($arrival);                        
    $nightAdder = '+'.$request->getVar('Nights').' days';
    $startDate = date('Y-m-d', $arrivalStamp);
    $endDate = date('Y-m-d', strtotime($nightAdder, $arrivalStamp));

    $properties = $properties->filter([
        'AvailableStart:GreaterThanOrEqual' => $startDate,
        'AvailableEnd:LessThanOrEqual' => $endDate
    ]);

}

 

Cela devient un peu délicat. Dans un monde idéal, la date passerait par la requête GET au format Ymd approprié, comme une base de données préfère la traiter, mais notre widget de calendrier ne soumet pas sa valeur de cette façon. Un widget plus configurable pourrait nous permettre de fournir deux formats: un pour l’affichage à l’utilisateur et un pour les données de formulaire, mais malheureusement, cette option n’est pas disponible. Nous devons donc prendre la date en JJ-MM-AAAA formater et analyser.

 

if ($arrival = $request->getVar('ArrivalDate')) {
    $arrivalStamp = strtotime($arrival);                        
    $nightAdder = '+'.$request->getVar('Nights').' days';
    $startDate = date('Y-m-d', $arrivalStamp);
    $endDate = date('Y-m-d', strtotime($nightAdder, $arrivalStamp));

    $properties = $properties->filter([
        'AvailableStart:LessThanOrEqual' => $startDate,
        'AvailableEnd:GreaterThanOrEqual' => $endDate
    ]);

}

 

En utilisant cette strtotime()méthode, nous ajoutons le nombre de nuits au timestamp Unix de la date d’arrivée. Si vous ne l’avez pas utilisé strtotime()auparavant, vous devriez vous en familiariser avec la page de documentation PHP . Il vous permet de transmettre des phrases de manipulation lisibles par l’homme aux horodatages, tels que strtotime('+3 weeks', $timestamp). C’est précieux pour la manipulation des dates.

En utilisant deux dates Ymd calculées pour le début et la fin, nous appliquons a GreaterThanOrEqualFilteret a LessThanOrEqualFilterpour rechercher dans une plage.

Remarque importante sur les dates conviviales

L’utilisation de traits d’union pour séparer les valeurs de date est d’une importance capitale, ici. La strtotime()méthode PHP élimine l’ambiguïté des dates DMY et MDY via le caractère utilisé pour séparer les valeurs. Les traits d’union sont supposés indiquer DMY et les barres obliques sont de type MDY.

 

date('F j Y', strtotime('7/3/2015')); // July 3 2015
date('j F Y', strtotime('7-3-2015')); // 7 March 2015

 

Les quatre filtres suivants sont assez simples. Nous allons utiliser GreaterThanOrEqualFilteret LessThanOrEqualFilterfiltrer par chambres, salles de bains et prix.

if ($bedrooms = $request->getVar('Bedrooms')) {
    $properties = $properties->filter([
        'Bedrooms:GreaterThanOrEqual' => $bedrooms
    ]);
}

 

Un peu plus de ceux-ci, et ici nous avons tous nos filtres:

app/src/PropertySearchPageController.php

 

public function index(HTTPRequest $request)
{
    $properties = Property::get();

    if ($search = $request->getVar('Keywords')) {
        $properties = $properties->filter(array(
            'Title:PartialMatch' => $search             
        ));
    }

    if ($arrival = $request->getVar('ArrivalDate')) {
        $arrivalStamp = strtotime($arrival);                        
        $nightAdder = '+'.$request->getVar('Nights').' days';
        $startDate = date('Y-m-d', $arrivalStamp);
        $endDate = date('Y-m-d', strtotime($nightAdder, $arrivalStamp));

        $properties = $properties->filter([
            'AvailableStart:GreaterThanOrEqual' => $startDate,
            'AvailableEnd:LessThanOrEqual' => $endDate
        ]);

    }

    if ($bedrooms = $request->getVar('Bedrooms')) {
        $properties = $properties->filter([
            'Bedrooms:GreaterThanOrEqual' => $bedrooms
        ]);
    }

    if ($bathrooms = $request->getVar('Bathrooms')) {
        $properties = $properties->filter([
            'Bathrooms:GreaterThanOrEqual' => $bathrooms
        ]);
    }

    if ($minPrice = $request->getVar('MinPrice')) {
        $properties = $properties->filter([
            'PricePerNight:GreaterThanOrEqual' => $minPrice
        ]);
    }

    if ($maxPrice = $request->getVar('MaxPrice')) {
        $properties = $properties->filter([
            'PricePerNight:LessThanOrEqual' => $maxPrice
        ]);
    }

    return [
        'Results' => $properties
    ];
}

 

En regardant ce code, il y a beaucoup de répétition, et nous sommes sur le point de casser nos principes DRY. Essayons de le ranger un peu en créant une carte des filtres et en les parcourant en boucle.

app / src / PropertySearchPageController.php

public function index(HTTPRequest $request)
{
  //...
    if ($arrival = $request->getVar('ArrivalDate')) {
    //...
    }

  $filters = [
    ['Bedrooms', 'Bedrooms', 'GreaterThanOrEqual'],
    ['Bathrooms', 'Bathrooms', 'GreaterThanOrEqual'],
    ['MinPrice', 'PricePerNight', 'GreaterThanOrEqual'],
    ['MaxPrice', 'PricePerNight', 'LessThanOrEqual'],
  ];

  foreach($filters as $filterKeys) {
    list($getVar, $field, $filter) = $filterKeys;
    if ($value = $request->getVar($getVar)) {
      $properties = $properties->filter([
        "{$field}:{$filter}" => $value
      ]);
    }
  }

    return [
        'Results' => $properties
    ];
}

Etat persistant sur le formulaire de recherche

Essayez de chercher en utilisant nos nouveaux filtres appliqués. Nous devrions toujours voir des résultats statiques, mais notez que le formulaire perd son état lors de l’actualisation de la page. Cela ne suffit pas. Nous devrons rendre le formulaire rempli avec les paramètres de filtre de la demande. Étant donné que tous les noms de champs de formulaire correspondent aux paramètres de demande, cette procédure est extrêmement simple.

app / src / PropertySearchPageController.php

public function PropertySearchForm()
{
  //...
    $form->setFormMethod('GET')
         ->setFormAction($this->Link())
         ->disableSecurityToken()
         ->loadDataFrom($this->request->getVars());

    return $form;
}

Nous chargeons toutes les variables dans la GETrequête en utilisant la getVarsméthode de l’ HTTPRequestobjet. Aussi disponible sont postVars()et le tout inclus requestVars(). N’oubliez pas que cette magie ne fonctionne que si vos champs de formulaire partagent des noms avec des paramètres de demande. Si notre champ de formulaire s’appelait MinBedroomset que la demande contenait une variable appelée Chambres, nous devions attribuer explicitement la valeur, en utilisant setValue($this->request->getVar('MinBedrooms')le champ de formulaire.

Actualisez la page et vérifiez que le formulaire enregistre maintenant son état.

Tirer les résultats dans le modèle

Maintenant que notre $Resultsliste est passée au modèle, nous allons parcourir les résultats.

app / templates / SilverStripe / Lessons / Layout / PropertySearchPage.ss (ligne 38)

<% loop $Results %>
<div class="item col-md-4">
    <div class="image">
        <a href="$Link">
            <span class="btn btn-default"><i class="fa fa-file-o"></i> Details</span>
        </a>
        $PrimaryPhoto.Fill(760,670)
    </div>
    <div class="price">
        <span>$PricePerNight.Nice</span><p>per night<p>
    </div>
    <div class="info">
        <h3>
            <a href="$Link">$Title</a>
            <small>$Region.Title</small>
            <small>Available $AvailableStart.Nice - $AvailableEnd.Nice</small>
        </h3>
        <p>$Description.LimitSentences(3)</p>

        <ul class="amenities">
            <li><i class="icon-bedrooms"></i> $Bedrooms</li>
            <li><i class="icon-bathrooms"></i> $Bathrooms</li>
        </ul>
    </div>
</div>
<% end_loop %>

 

Pour faciliter le débogage, nous avons ajouté les valeurs $AvailableStartet $AvailableEndpour confirmer que notre recherche de date fonctionne.

Essayez la recherche et voyez comment fonctionnent les filtres.

TOUT VOIR Ajouter une remarque
VOUS
Ajoutez votre commentaire
 
© Academy EdTech. 2019 All rights reserved.
X