Création de vues filtrées

Dans cette leçon, nous allons parler du filtrage d'une liste d'éléments sur un modèle via la navigation.

 

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

 

Dans cette leçon, nous allons parler du filtrage d’une liste d’éléments sur un modèle. Dans un précédent tutoriel, nous avons examiné le filtrage par les entrées de formulaire. Maintenant, nous allons voir comment faire cela via la navigation.

 

Ce que nous allons couvrir

  • Mise en place de nouvelles relations
  • Ajout de listes de liens de filtres
  • Mise en place des itinéraires filtrés
  • Ajout de méthodes de liens filtrés à DataObjects
  • Filtrage à travers les actions du contrôleur
  • Ajouter des en-têtes de filtres

Il est primordial de pouvoir exploiter cette quantité de données riche pour tirer le meilleur parti de cette leçon. Par conséquent, une fois les modifications de code apportées, il est judicieux d’importer le database.sqlfichier inclus dans la version finale de cette leçon. Il vous fournira une centaine d’ ArticlePageenregistrements composés au hasard que nous filtrerons.

 

Mise en place de nouvelles relations

En consultant notre page Guides de voyage, nous constatons qu’il existe différents filtres que nous pouvons appliquer dans la barre latérale. Nous avons une liste de catégories, une archive des mois et des années précédents, ainsi que des tags. Nous n’utiliserons pas de balises pour ces articles, alors supprimons-le. Nous allons le remplacer par un filtre pour les régions, car chacun de ces guides de voyage peut éventuellement concerner une région.

Avant d’ajouter le filtre, définissons cette relation. Nous allons ajouter un has_oneà Regionsur ArticlePageet has_manyde Regionrevenir à ArticlePage.

app / src / ArticlePage.php

 

//...
    private static $has_one = [
        'Photo' => Image::class,
        'Brochure' => File::class,
        'Region' => Region::class,
    ];
//...

    public function getCMSFields()
    {
        //...
        $fields->addFieldToTab('Root.Main', DropdownField::create(
            'RegionID',
            'Region',
            Region::get()->map('ID','Title')
        )->setEmptyString('-- None --'), 'Content');

        return $fields;

    }

 

app / src / Region.php

//...
    private static $has_many = [
        'Articles' => ArticlePage::class,
    ];
//...

 

Run a dev/build?flush. Si vous avez déjà effectué l’importation de la base de données, la base de données ne devrait pas être modifiée, mais il est toujours essentiel de mettre à jour le modèle.

Ajout de listes de liens de filtres

Maintenant, nous pouvons entrer dans le vif du sujet et commencer à créer des filtres. Voyons notre ArticleHolderControlleret assurez-vous qu’il peut insérer des régions dans sa barre latérale. Ajouter une méthode appelée Regions()à ArticleHoldercela supprime simplement toutes les régions de la page Régions.

app / src / ArticleHolder.php

 

//...
class ArticleHolder extends Page {

  //...
    public function Regions ()
    {
        $page = RegionsPage::get()->first();

        if($page) {
            return $page->Regions();
        }
    }
  //...

 

En pratique, vous voudrez probablement ajouter un sort()et / ou limit()à cette liste, mais à des fins de démonstration, une requête générique suffira.

Notez que nous ne voulons pas simplement vider notre mémoire Region::get(). C’est une erreur commune. Si la page Régions était supprimée et remplacée, vous vous retrouveriez avec des régions orphelines qui se retrouveraient dans cette liste.

Nous devrons les ajouter au modèle. Remplacez la section “tags” par la liste des régions.

app / templates / SilverStripe / Lessons / Layout / ArticleHolder.ss

 

    </div>
    <!-- END  ARCHIVES ACCORDION -->

    <h2 class="section-title">Regions</h2>
    <ul class="categories">
    <% loop $Regions %>
        <li><a href="$ArticlesLink">$Title <span>($Articles.count)</span></a></li>
    <% end_loop %>
    </ul>

    <!-- BEGIN LATEST NEWS -->
    <h2 class="section-title">Latest News</h2>

 

Obtenir le nombre d’articles associés à la région est un simple appel à la Articlesrelation. Chaque liste dans SilverStripe (l’ SS_Listinterface) expose une count()méthode.

Notez que nous appelons une méthode non existante sur Regionappelé $ArticlesLink. Cela renverra une URL à la section Guides de voyage avec le filtre de région approprié appliqué. Ne vous inquiétez pas pour ça pour l’instant. Nous le créerons plus tard dans cette leçon. C’est juste un espace réservé pour l’instant.

Pendant que nous sommes dans cette section, nous devrions éclairer la liste des catégories dans la barre latérale. C’est en fait un peu plus facile que la liste des régions, car a ArticleHolderdéjà un has_manypour Categories.

app / templates / SilverStripe / Lessons / Layout / ArticleHolder.ss

 

    <!-- BEGIN SIDEBAR -->
    <div class="sidebar gray col-sm-4">

        <h2 class="section-title">Categories</h2>
        <ul class="categories">
        <% loop $Categories %>
            <li><a href="$Link">$Title <span>($Articles.count)</span></a></li>
        <% end_loop %>
        </ul>

 

De nouveau, nous appelons la méthode d’agrégation count()contre l’ ArticleCategoryobjet pour obtenir le nombre d’articles auxquels elle se rapporte. Nous pouvons le faire grâce au belongs_many_manyon défini sur ArticleCategory.

Jetez un coup d’oeil dans ArticleCategory.php . Rappelez-vous ceci?

 

    private static $belongs_many_many = [
        'Articles' => ArticlePage::class,
    ];

 

Cela vient maintenant vraiment utile!

Comme nous l’avons fait auparavant, nous avons appelé une Link()méthode non existante sur l’objet catégorie que nous définirons plus tard.

Actualisez la page et vérifiez que nos catégories apparaissent correctement.

Si vous vous interrogez sur la différence de sémantique ( ArticlesLink()vs Link()), c’est simplement une question de contexte. ArticleCategoryLe lien canonique d’ un objet doit être une liste d’articles. Il n’est vraiment utilisé nulle part ailleurs. Cependant, une région a sa propre page de détails, elle Link()est donc déjà revendiquée pour cet état canonique. Utiliser une région pour filtrer une liste d’articles est une utilisation particulière d’une région. Nous devrions donc utiliser une méthode nommée spécialement.

Nous allons enregistrer les liens d’archives des dates jusqu’à la leçon suivante, car cela introduit une certaine complexité, mais nous en établirons les bases. Faisons en sorte que certaines choses fonctionnent avant de plonger dans ce sujet.

 

Mise en place des itinéraires filtrés

Pensons à ce que nous voulons sortir de ces liens. Nous avons essentiellement quatre états:

  • L’état par défaut (pas de filtres)
  • Filtré par région
  • Filtré par catégorie
  • Filtré par date

Un bon point de départ est d’envisager à quoi ressemblent les itinéraires pour chacun de ces États. Nous pouvons facilement imaginer quelque chose comme ceci:

  • travel-guides/region/123 (Afficher les articles relatifs à la région n ° 123)
  • travel-guides/category/123 (voir les articles liés à la catégorie n ° 123)
  • travel-guides/date/2017/05 (voir articles de mai 2017)

Optionnellement, cela peut être bien si nous permettions à l’utilisateur d’omettre le mois pour afficher une année entière. Les itinéraires d’URL intuitifs et sémantiquement corrects rapportent toujours de gros points.

La première chose dont nous aurons besoin dans notre contrôleur est une liste d’actions autorisées.

app / src / ArticleHolderController.php

 

//...
class ArticleHolderController extends PageController
{

    private static $allowed_actions = [
        'category',
        'region',
        'date'
    ];

 

Puisque nous avons mis à jour une variable statique privée, assurez-vous de l’exécuter ?flushà ce stade.

Ajout de méthodes de liens filtrés à DataObjects

Maintenant que nous savons à quoi nos routes ressembleront, revenons aux classes Regionet ArticleCategorypour définir ces méthodes de liens.

app / src / Region.php

 

  //...
    public function ArticlesLink()
    {
        $page = ArticleHolder::get()->first();

        if($page) {
            return $page->Link('region/'.$this->ID);
        }
    }
  //...

 

C’est toujours une bonne idée de placer un garde autour de la page existante. Ne supposez jamais que l’arborescence du site sera toujours la même. Cela peut entraîner de réels problèmes lorsqu’un utilisateur a installé le projet à partir du référentiel de code mais qu’il n’a pas encore importé de base de données.

Il est intéressant de noter que la LinkingMode()méthode de notre Regionobjet est suffisamment agnostique pour continuer à fonctionner sur notre ArticleHolderpage:

 

    public function LinkingMode()
    {
        return Controller::curr()->getRequest()->param('ID') == $this->ID ? 'current' : 'link';
    }

 

Ajoutons maintenant la Link()méthode aux catégories.

app / src / ArticleCategory.php

 

   //...
    public function Link()
    {
        return $this->ArticleHolder()->Link(
            'category/'.$this->ID
        );
    }
  //...

 

Actualisez la page et vérifiez que les catégories et les régions sont liées aux URL correctes.

Filtrage à travers les actions du contrôleur

Commençons par définir la liste de base des articles. Nous savons au minimum que nous ne voulons que les articles qui sont des enfants de cette page, triés par ordre chronologique inverse. ArticlePage::get()->sort('Date DESC')peut donner la même chose, mais à long terme, ce n’est pas une bonne solution. Nous aurons peut-être un jour plusieurs ArticleHolderpages.

En fin de compte, nous souhaitons que le contrôleur commence par cette liste de base et chaque action du contrôleur la filtrera davantage. C’est un excellent cas d’utilisation pour la init()méthode, car elle est exécutée avant toute action.

app / src / ArticleHolderController.php

 

//...
class ArticleHolderController extends PageController
{

    //...

    protected $articleList;

    protected function init ()
    {
        parent::init();

        $this->articleList = ArticlePage::get()->filter([
            'ParentID' => $this->ID
        ])->sort('Date DESC');
    }

 

Si vous vous demandez pourquoi nous n’utilisons pas simplement Children(), ce qui fait effectivement la même chose, c’est qu’il Children()s’agit d’une méthode spéciale qui modifie sa liste après la requête. En fait, il renvoie un ArrayList, pas un DataList, ce qui nous empêcherait d’ajouter des filtres.

Nous allons vouloir que les articles soient paginés, alors créons une méthode qui applique une variable PaginatedListà la $articleListvariable membre. Ce sera notre unique point d’accès à la liste des articles que nous construisons.

app / src / ArticleHolderController.php

 

//...
use SilverStripe\ORM\PaginatedList;

class ArticleHolderController extends PageController
{
  //...
    public function PaginatedArticles ($num = 10)
    {       
        return PaginatedList::create(
            $this->articleList,
            $this->getRequest()
        )->setPageLength($num);
    }

 

De retour dans le modèle, modifiez le <% loop %>bloc pour utiliser la $PaginatedArticlesméthode.

app / templates / SilverStripe / Lessons / Layout / ArticleHolder.ss

 

    <div id="blog-listing" class="list-style clearfix">
        <div class="row">
            <% loop $PaginatedArticles %>
            <div class="item col-md-6">
            <!-- .... -->
            </div>
            <% end_loop %>

 

Pour l’instant, empruntons le code HTML de pagination au PropertySearchResults.ssfichier. Si vous êtes bouleversé par les violations de DRY, détendez-vous. Nous aborderons cette duplication dans une prochaine leçon.

Prenez une profonde respiration et copiez et collez. Personne ne saura. Assurez-vous simplement de changer $Resultspour $PaginatedArticles.

app / templates / SilverStripe / Lessons / Layout / ArticleHolder.ss

 

    <!-- BEGIN PAGINATION -->
    <% if $PaginatedArticles.MoreThanOnePage %>
    <div class="pagination">
        <% if $PaginatedArticles.NotFirstPage %>
        <ul id="previous col-xs-6">
            <li><a href="$PaginatedArticles.PrevLink"><i class="fa fa-chevron-left"></i></a></li>
        </ul>
        <% end_if %>
        <ul class="hidden-xs">
            <% loop $PaginatedArticles.PaginationSummary %>
                <% if $Link %>
                    <li <% if $CurrentBool %>class="active"<% end_if %>>
                        <a href="$Link">$PageNum</a>
                    </li>
                <% else %>
                    <li>...</li>
                <% end_if %>
            <% end_loop %>
        </ul>
        <% if $PaginatedArticles.NotLastPage %>
        <ul id="next col-xs-6">
            <li><a href="$PaginatedArticles.NextLink"><i class="fa fa-chevron-right"></i></a></li>
        </ul>
        <% end_if %>
    </div>
    <% end_if %>
    <!-- END PAGINATION -->

 

Nous sommes maintenant prêts à ajouter notre premier filtre, par catégorie. Définissons l’ category()action.

app / src / ArticleHolderController.php

 

use SilverStripe\Control\HTTPRequest;

class ArticleHolderController extends PageController
{

    //...
    public function category (HTTPRequest $r)
    {
        $category = ArticleCategory::get()->byID(
            $r->param('ID')
        );

        if(!$category) {
            return $this->httpError(404,'That category was not found');
        }

        $this->articleList = $this->articleList->filter([
            'Categories.ID' => $category->ID
        ]);

        return [
            'SelectedCategory' => $category
        ];
    }

 

Nous commençons d’abord par vérifier si la catégorie existe. Sinon, nous lançons un 404. Ensuite, nous mettons à jour la liste d’articles en filtrant l’actuel en fonction de la many_manyrelation Categories,. C’est un merveilleux exemple du pouvoir de l’ORM. Sa couche d’abstraction nous permet de filtrer selon un paramètre qui n’est pas nécessairement un champ dans la base de données, mais plutôt une relation nommée dans notre code. Le filtre Categories.ID => $category->IDdemande simplement tous les articles contenant l’ID de catégorie donné dans la liste des ID de catégorie associés.

À la fin de la fonction, nous renvoyons la catégorie sélectionnée au modèle. Cela sera important lors de l’ajout de texte à la page indiquant l’état filtré.

Notez également que nous ne renvoyons pas de liste d’articles dans l’action du contrôleur. Les articles vont être paginés, et nous voulons éviter la redondance de créer une PaginatedListaction dans chaque contrôleur. Nous allons créer un lieu central pour que cela se produise dans un instant.

Testez-le et vérifiez que le nouvel état de la catégorie filtrée fonctionne comme prévu.

Ajoutons notre prochain filtre pour les régions. Cela fonctionnera à peu près de la même manière.

app / src / ArticleHolderController.php

 

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

class ArticleHolderController extends PageController
{
    //...

    public function region (HTTPRequest $r)
    {
        $region = Region::get()->byID(
            $r->param('ID')
        );

        if(!$region) {
            return $this->httpError(404,'That region was not found');
        }

        $this->articleList = $this->articleList->filter([
            'RegionID' => $region->ID
        ]);

        return [
            'SelectedRegion' => $region
        ];
    }

 

Testez le filtre de régions dans votre navigateur et vérifiez qu’il fonctionne.

 

Ajouter des en-têtes de filtres

La dernière chose à faire est d’insérer nos en-têtes de filtre dans les listes pour montrer à l’utilisateur l’état de la liste. Chaque action du contrôleur renvoie ses propres variables de modèle personnalisées que nous pouvons vérifier.

app / templates / SilverStripe / Lessons / Layout / ArticleHolder.ss

 

    <div id="blog-listing" class="list-style clearfix">
        <div class="row">
            <% if $SelectedRegion %>
                <h3>Region: $SelectedRegion.Title</h3>
            <% else_if $SelectedCategory %>
                <h3>Category: $SelectedCategory.Title</h3>
            <% end_if %>

 

Essayez-le et voyez que vous avez maintenant quelques belles rubriques indiquant l’état de la liste.

Vous remarquerez qu’une pièce manquante est que la page de détail a toujours une barre latérale statique. Pour que cela fonctionne, il faut enseigner un nouveau concept, que nous aborderons dans les prochaines leçons.

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