Ich besuchte kürzlich die ARTIFACT Konferenz in Austin, TX, und war inspiriert von einigen Vorträgen über Barrierefreiheit aus der Perspektive der Website-Performance. Mir wurde klar, dass die Tendenz besteht, sich auf große JavaScript-Frameworks zu verlassen, um die Arbeit zu erledigen – wie React, Vue und Angular –, aber das kann in manchen Fällen übertrieben sein. Das heißt, die Website-Performance und somit die Barrierefreiheit negativ beeinflussen. Gleichzeitig können diese Frameworks die Entwicklung für Entwickler einfacher und effizienter machen. Meine wichtigste Erkenntnis von der Konferenz war, wie eine schnelle, performante Erfahrung mit meinem eigenen Entwicklungsprozess in Einklang gebracht werden kann.
Das behielt ich im Hinterkopf, als ich ein paar Tage nach der Konferenz eine Listenfilterfunktion für ein Projekt entwickelte. Ziemlich Standardkram: Ich brauchte eine Liste von Beiträgen und einige Kategoriefilter. Ich verwendete CraftCMS für Frontend-Routen und Templating sowie hier und da einige Vue-Komponenten für etwas zusätzlichen JavaScript-Schwung. Keine vollwertige "Single Page App", sondern eher ein Hauch von Vue.
Der typische Weg, wie man dies angehen könnte, ist,
- die Seite mit einem leeren Div mit Craft/Twig zu rendern
- eine Vue-Komponente in dieses Div zu mounten
- einen Ajax-Aufruf von der Vue-Komponente an eine API zu machen, um die Beiträge als JSON zu sammeln
- die Beiträge zu rendern und die Filterung einzubinden.
Da die Beiträge als Array innerhalb von Vue gespeichert sind, ist dynamisches Rendern von Listen eine ziemlich einfache Aufgabe.
Einfach. Erledigt, richtig? Nun... dieser zusätzliche Ajax-Aufruf bedeutet, dass dem Benutzer beim initialen Laden je nach Netzwerk des Benutzers kein Inhalt angezeigt wird, was einige Zeit dauern kann. Wir könnten eine Ladeanzeige hinzufügen, aber vielleicht geht es besser?
Vorzugsweise werden die Beiträge beim initialen Seitenaufruf vom CMS gerendert.
Aber wie wird der statische HTML-Code mit Vue für die Filterung "verknüpft"?
Nachdem ich einen Schritt zurückgetreten bin, um das Problem zu überdenken, erkannte ich, dass ich `v-if`-Direktiven verwenden könnte, um dasselbe mit etwas Inline-JavaScript von Twig ("im Loop") zu erreichen. Unten zeige ich, wie ich vorgegangen bin.
Mein ursprüngliches Projekt verwendete CraftCMS, aber ich werde die Demos unten in WordPress durchführen. Dies ist nur ein Konzept. Es kann auf CraftCMS/Twig oder jede andere CMS/Templating-Engine-Kombination angewendet werden.
Zuerst brauchen wir eine Filter- UI. Dies wird wahrscheinlich über dem Beginn des Loops in einer Archiv-Vorlage platziert.
<?php $terms = get_terms( [
'taxonomy' => 'post_tag', // I used tags in this example, but any taxonomy would do
'hide_empty' => true,
'fields' => 'names'
] );
if(!empty($terms)): ?>
<div>
Filter:
<ul class="filters">
<li class="filters__item"><button class="filters__button" :class="{'filters__button--active': tag === ''}" @click="tag = ''">All</button></li>
<?php foreach($terms as $term): ?>
<li class="filters__item">
<button class="filters__button" :class="{'filters__button--active': tag === '<?php echo $term; ?>'}" @click="tag = '<?php echo $term; ?>'"><?php echo $term; ?></button>
</li>
<?php endforeach; ?>
</ul>
<p aria-live="polite">Showing posts tagged {{ tag ? tag : 'all' }}.</p>
</div>
<?php endif; ?>
Wenn wir dem Code folgen, erhalten wir einige Tags von WordPress mit `get_terms()` und geben sie in einer `foreach`-Schleife aus. Sie werden bemerken, dass der Button für jeden Tag einige Vue-Direktiven hat, die wir später verwenden werden.
Wir haben unseren Loop!
<div class="posts">
<?php
// Start the Loop.
while ( have_posts() ) : the_post();
<article id="post-<?php the_ID(); ?>"
<?php post_class(); ?>
v-if='<?php echo json_encode(wp_get_post_tags(get_the_ID(), ['fields' => 'names'])); ?>.includes(tag) || tag === ""'
>
<header class="entry-header">
<h2><?php the_title(); ?></h2>
</header>
<div class="entry-content">
<?php the_excerpt(); ?>
</div>
</article>
// End the loop.
endwhile; ?>
</div>
Dies ist eine ziemlich Standard-WordPress-Schleife für Beiträge. Sie werden einige Vue-Direktiven bemerken, die PHP zur Ausgabe von CMS-Inhalten verwenden.
Abgesehen von etwas Styling ist alles, was übrig bleibt, die Vue "App". Bist du bereit dafür? Hier ist sie
new Vue({
el: '#filterablePosts',
data: {
'tag': ''
}
});
Ja, wirklich, das ist alles, was in der JavaScript-Datei benötigt wird, damit dies funktioniert!
Also, was passiert hier?
Nun, anstatt eines JSON-Arrays von Beiträgen, das in Vue eingespeist wird, geben wir die Beiträge beim initialen Seitenaufruf mit WordPress aus. Der Trick besteht darin, PHP zu verwenden, um das auszugeben, was in den Vue-Direktiven benötigt wird: `v-if` und `:class`.
Was bei den Filter-Buttons passiert, ist ein `onclick`-Ereignisbehandler (`@click`), der die Vue-Variable "tag" auf den Wert des WordPress-Post-Tags setzt.
@click="tag = '<?php echo $term; ?>'"
Außerdem, wenn diese Vue-Variable mit dem Wert des Buttons übereinstimmt (in der `:class`-Direktive), wird ein aktiver Klassenname für den Button hinzugefügt. Das dient nur dem Styling.
:class="{'filters__button--active': tag === '<?php echo $term; ?>'}"
Für die Liste der Artikel zeigen wir sie bedingt basierend auf dem Wert der Vue-Variable "tag" an
v-if='<?php echo json_encode(wp_get_post_tags(get_the_ID(), ['fields' => 'names'])); ?>.includes(tag) || tag === ""'
Die PHP-Funktion `json_encode` ermöglicht es uns, ein Array von Post-Tags als JavaScript auszugeben, was bedeutet, dass wir `.includes()` verwenden können, um zu sehen, ob die Vue-Variable "tag" in diesem Array enthalten ist. Wir möchten den Artikel auch anzeigen, wenn kein Tag ausgewählt ist.
Hier ist es zusammengefügt, basierend auf der Vorlage `archive.php` des Twenty Nineteen Themes als Basis
<?php get_header(); ?>
<section id="primary" class="content-area">
<main id="main" class="site-main">
<?php if ( have_posts() ) : ?>
<header class="page-header">
<?php the_archive_title( '<h1 class="page-title">', '</h1>' ); ?>
</header>
<div class="postArchive" id="filterablePosts">
<?php $terms = get_terms( [
'taxonomy' => 'post_tag',
'hide_empty' => true,
'fields' => 'names'
] );
if(!empty($terms)): ?>
<div class="postArchive__filters">
Filter:
<ul class="postArchive__filterList filters">
<li class="filters__item"><button class="filters__button" :class="{'filters__button--active': tag === ''}" @click="tag = ''" aria-controls="postArchive__posts">All</button></li>
<?php foreach($terms as $term): ?>
<li class="filters__item">
<button class="filters__button" :class="{'filters__button--active': tag === '<?php echo $term; ?>'}" @click="tag = '<?php echo $term; ?>'" aria-controls="postArchive__posts"><?php echo $term; ?></button>
</li>
<?php endforeach; ?>
</ul>
<p aria-live="polite">Showing {{ postCount }} posts tagged {{ tag ? tag : 'all' }}.</p>
</div>
<?php endif; ?>
<div class="postArchive__posts">
<?php
// Start the Loop.
while ( have_posts() ) : the_post(); ?>
<article
id="post-<?php the_ID(); ?>"
<?php post_class(); ?>
v-if='<?php echo json_encode(wp_get_post_tags(get_the_ID(), ['fields' => 'names'])); ?>.includes(tag) || tag === ""'
>
<header class="entry-header">
<h2><?php the_title(); ?></h2>
</header>
<div class="entry-content">
<?php the_excerpt(); ?>
</div>
</article>
<?php endwhile; // End the loop. ?>
</div>
</div>
<?php
// If no content, include the "No posts found" template.
else :
get_template_part( 'template-parts/content/content', 'none' );
endif; ?>
</main>
</section>
<?php
get_footer();
Hier ist ein funktionierendes Beispiel auf CodePen
Sehen Sie den Pen
Dynamische Listenfilterung in Vue mit serverseitiger Datenabfrage von Dan Brellis (@danbrellis)
auf CodePen.
Bonuszeit!
Sie haben vielleicht bemerkt, dass ich unter der Liste der Filter-Buttons einen `aria-live="polite"`-Notifier hinzugefügt habe, um Benutzer von assistiver Technologie zu informieren, dass sich der Inhalt geändert hat.
<p aria-live="polite">Showing {{ postCount }} posts tagged {{ tag ? tag : 'all' }}.</p>
Um die Vue-Variable `postCount` zu erhalten, fügen wir zusätzlichen JavaScript-Code zu unserer Vue-Komponente hinzu
new Vue({
el: '#filterablePosts',
data: {
'tag': '',
'postCount': ''
},
methods: {
getCount: function(){
let posts = this.$el.getElementsByTagName('article');
return posts.length;
}
},
beforeMount: function(){
this.postCount = this.getCount();
},
updated: function(){
this.postCount = this.getCount();
}
});</p>
Die neue Methode `getCount` wird verwendet, um die Artikel-Elemente im Div unserer Komponente auszuwählen und die Länge zurückzugeben. Bevor die Vue-Komponente gemountet wird, erhalten wir die Anzahl, um sie unserer neuen Vue-Variable `postCount` hinzuzufügen. Dann, wenn die Komponente nach der Auswahl eines Tags aktualisiert wird, erhalten wir die Anzahl erneut und aktualisieren unsere Variable.