0
votes

I use Timber for Wordpress and I would like to create a dictionary system like that : https://wordpress.stackexchange.com/questions/119163/displaying-custom-post-type-by-first-letter-through-custom-taxonomy

I wrote this :

lexique.php

$query = get_posts(array('post_type' => 'lexique','posts_per_page' => -1));

But I don't know how to transform this with Timber :

$by_letter = array();
while( $query->have_posts() ) { $query->the_post();
  global $post;
  $letter = substr($post->post_name, 0, 1);
  if ( ! isset($by_letter[$letter]) ) $by_letter[$letter] = array();
  $by_letter[$letter][] = $post;
}
wp_reset_postdata();
1
You should avoid doing a substr() it's intensive, a better way would be to use terms and then with one query using $wpdb you can select all custom posts and order by term.Stubbies
@Erevald, thank you but I can use this loop in Timber ?Jandon
Put the loop in a function in your theme and in your twig file you can show it like this {{function("mytheme_loop_function")}}Stubbies
How to use terms and $wpdb please ?Jandon
I have no idea how introducing terms would make a solution any easier. The way to use timber is not to call a function in your Twig file that uses The Loop again. You use Timber because you want to get rid of The Loop.Gchtr

1 Answers

2
votes

It’s a good idea to get all posts at once. By adding the orderby parameter, you can already put them into the correct order for the letter-sorting. I’d use title and not name for the sorting and to define the first letter, because name/post_name is a URL-safe string (used in permalinks) might be different from the actual title of the post.

You get the posts through Timber::get_posts(), so that you don’t have to rely on The Loop. You get an array with posts that you can work with, before you render it through a Twig file. This is much more direct than the method you linked to, because you don’t have to rely on additional functions and query resets.

lexique.php

$posts = Timber::get_posts( array(
    'post_type' => 'lexique',
    'posts_per_page' => -1,
    'orderby' => 'title',
    'order' => 'ASC',
) );

$posts_by_letter = array();

// Sort posts by letter
foreach ( $posts as $post ) {
    $first_letter = substr( $post->post_title, 0, 1);

    // Create array for letter if it doesn’t exist
    if ( ! isset( $posts_by_letter[ $first_letter ] ) ) {
        $posts_by_letter[ $first_letter ] = array();
    }

    $posts_by_letter[ $first_letter ][] = $post;
}

$context['posts_by_letter'] = $posts_by_letter;

Timber::render( [ 'lexique.twig' ], $context );

Display only letters for existing posts

lexique.twig

<dl>
{% for letter, posts in posts_by_letter %}
    <dt>{{ letter }}</dt>

    {% for post in posts  %}
        <dd><a href="{{ post.link }}">{{ post.title }}</a></dd>
    {% endfor %}
{% endfor %}
</dl>

Because you have a nested array, you have to do two for-loops. The first loop goes through the letters (which are the keys of the outer array). The value assigned to a letter key is another array, containing all the posts starting with that letter. To display the post titles as links, you use the second for-loop.

Display all letters from A to Z

If you want to generate a list of all letters from A to Z and display existing posts, you can use range and check if posts exists for that letter in posts_by_letter.

Additionally you can use range to create a list of anchor links that lets a visitor jump to a specific letter.

lexique.twig

 {# Anchor links to jump to letter #}
 {% for letter in range('A', 'Z') %}
     <a href="#{{ letter }}">{{ letter }}</a>
 {% endfor %}

 <dl>
 {% for letter in range('A', 'Z') %}
     <dt><a id="{{ letter }}">{{ letter }}</a></dt>
     {% if posts_by_letter[letter] is defined %}
         {% for post in posts_by_letter[letter]  %}
             <dd><a href="{{ post.link }}">{{ post.title }}</a></dd>
         {% endfor %}
     {% endif %}
 {% endfor %}
 </dl>