Support

Account

Home Forums General Issues List posts by date picker with pagination

Solving

List posts by date picker with pagination

  • Hello, I’m trying to solve a problem I have with a post page with events that have a certain date as a datepicker field. I would like to query these posts (events) and show the posts sorted by dates with the current month at the top.

    Right now I have been able to fetch the posts and sort them in the correct order. But I need some help with how I can paginate so that you see this month first and be able to go backwards for earlier months and forward for next months.

    I also need some help to group the posts by month so that I can put a label above the events for march for example.

    Maybe someone has done this before, or maybe it’s impossible or maybe someone could point me in the right direction? Because I’m not entirely sure of my next move.

    Thanks in advance.

    This is my current post page with loop:

    <?php
    	$args = array(
    		'post_type' => 'match',
    		'meta_key'  => 'date',
    		'orderby'   => 'meta_value_num',
    		'order'     => 'ASC',  
    	);
    	$loop = new WP_Query($args);
    	if ( $loop->have_posts() ) { ?>
    		<?php while ( $loop->have_posts() ) { $loop->the_post(); ?>
    			<p><?php the_title(); ?> - Event Date: <?php the_field('date'); ?></p>
    		<?php } ?>
    	<?php } ?>
    <?php wp_reset_postdata(); ?>
  • I am working on similar situation. Sorry, this got long. I thought that explaining it would lead me to a simpler solution but I’m still not seeing one.

    There is no easy way that I know of to have a url like http://yoursite.com/events/ and have a query the sorts all of the events in order and then jumps into the middle of the list somewhere where the current and future events are listed and then paginate both into the future and into the past.

    Sorry, I don’t really have any simple code that I can share.

    One option is to have a pre_get_posts filter. In this filter if the “page” query argument is not set then you attempt to find the page where the first current or future event is listed. Basically you have to do a query to get one post with a date >= today. Then you do a second query to get all of the posts sorted in the right order but return only an array of post IDs 'fields' => 'ids'. Then you get the index of this post in the array and set “page” query argument based on the results.

    
    $index = array_search($post_id, $query->posts);
    $page = intval($index/$posts_per_page)+1;
    

    I thought about this and discarded it. The reason why is that this only gives the page to show. The actual event that is current or future might be at the top, bottom or anywhere in between on this page depending on how many events there are and how many you are showing per page. Knowing the client I am working for this would not be acceptable. Not to mention that all of this additional querying will cause a performance issue if there are many events, which is likely to happen.

    Another option is the direction that I am going in, which is even more complicated.
    The first page is only going to show current and future events. I am adding a rewrite tag and rewrite rules for past events, in addition to all the date rewrite rules that I still need to work out because there will be date archive pages. In essence this will create two different urls for future vs past.

    
    add_rewrite_tag('%past%', '([^/]+)', 'past-events=');
    $rules = array(
      array(
        'rule' => 'event/past/?$',
        'rewrite' => 'index.php?post_type=event&past-events=1'
      ),
      array(
        'rule' => 'event/past/page/?([0-9]{1,})/?$',
        'rewrite' => 'index.php?post_type=event&past-events=1&paged=$matches[1]'
      )
    );
    foreach ($rules as $rule) {
      add_rewrite_rule($rule['rule'], $rule['rewrite'], 'top');
    }
    

    Then I am going to have a pre_get_posts filter that looks for this argument and causes WP to do a different query based on if this is set or not. Future events will be shown in ASC order and past events will be shown in DESC order.

    Then I’m going to build a custom pagination function where if you are on the first page of the upcoming events then “Older Events” will be a link to /event/past/ and if you are on the first page of past events “Newer Events” will be a link to /event/ (the main archive) to show upcoming events.

    I’m still in the middle of this and my client is complicating things further because they want a flag so they can decide what events actually show in the past. For example they have webinars so they want these to appear to make it easy for visitors to find old webinars. They also want a month (year) select field so this needs to be able to search all events past and future by date (year/month/day), but only showing past events that are flagged in the past while showing all future events. In addition to this they will have events that span multiple months so if say an event goes from March to May and they choose April, then this event also needs to appear in the results. And on top of this they want to be able to search by keyword, which should also show past and future events but past events must be limited to those that are flagged while all current and future events should be shown. I’m still in the process of working out all of these different queries and it’s making my head hurt.

    Luckily I already had all the other framework for events built for another client, only in that case they never wanted to show past events so I didn’t need to figure this out.

  • Thank you very much for your informative thoughts! Glad to see I’m not alone in this. Fortunately my client hasn’t (yet) complicated things further. But interesting to see what different ideas you’ve tried, the last one I thought about as well but unfortunately I don’t have the time nor the budget.

    But thanks to your message I came to think of another way to go which is a little bit different design-wise but I think it will work rather well. I will just load all posts and only show the ones with a date past today (or maybe a few days back as well). And then I will use Ajax to filter between older posts and newer posts based on dates. It’s just a thought right now but I’m guessing it might be able to work.

    I’m afraid it might not help you though 🙁

  • I stumbled on this discussion while looking for a similar solution. @hube2 would you be so kind as to share your already-created framework that doesn’t show past events? That is exactly what I’m looking for and I’m hoping your work can save me some time. Thanks in advance!

  • @bkmacdaddy the event framework I have is quite extensive and spans many, many files, classes and functions. It’s something that I’ve been working on for a couple of years and modified over time to meet client requirements. There are at least 3 different version now. It really is an entire application built into custom themes. It would be impossible for me to share the entire thing though this forum.

    If there are specific things that you need help with I can possibly help with them.

  • @hube2 I appreciate any help you can provide and totally understand your position regarding your framework and all the work that has gone into it.

    I have an “event” custom post type with the date picker field included. I’m using the following code in a page template to pull in the current month’s events, which is working fine. But I have no clue how to get this to paginate from month to month. What is needed is for the user to arrive on the page showing the current month’s events, then be able to click through to the next month’s events, and so on, including clicking back to the previous month but not past the current month. My guess is that I need to use WP_Query instead of get_posts, but still not sure how to get that to work.

    Again, any help you can provide is greatly appreciated!

    <?php
            $year = date('Y');
            $month = date('m');
            $posts = get_posts(array(
                'post_type' => 'event',
                'posts_per_page' => -1,
                'order' => 'ASC',
                'orderby' => 'meta_value',
                'meta_key' => 'date',
                'meta_query' => array(
                    'relation' => 'AND',
                    array(
                        'key' => 'date',
                        'value' => $year.''.$month.'01',
                        'compare' => '>=',
                    ),
                    array(
                        'key' => 'date',
                        'value' => $year.''.$month.'31',
                        'compare' => '<=',
                    )       
                )
            ));
            if( $posts ) : foreach( $posts as $p ) :
            $date_string = get_field('date', $p->ID);
            $date = DateTime::createFromFormat('Ymd', $date_string);         
            endforeach;
            ?>
            <div class="small-12 columns text-center">
                <h2><?php echo $date->format('F Y'); ?></h2>
            </div>
            <?php
            foreach( $posts as $p ) :
            $background_color = get_field('background_color', $p->ID);
                if($background_color == 'terracotta') {
                    $bkg = '#AD7A67';
                } elseif($background_color == 'sand') {
                    $bkg = '#C3AD93';
                } elseif($background_color == 'dark-blue') {
                    $bkg = '#2D4B62';
                } elseif($background_color == 'seafoam-green') {
                    $bkg = '#91ACA9';
                }
            $date_string = get_field('date', $p->ID); 
            $date = DateTime::createFromFormat('Ymd', $date_string);
            $name = get_the_title($p->ID);
            $start_time = get_field('start_time', $p->ID);
            $description = get_field('description', $p->ID);
            $event_page_link = get_field('event_page_link', $p->ID);
            $register_link = get_field('register_link', $p->ID);
            ?>
            <div class="large-3 small-12 columns">
                <div class="event section--one__content__event" <?php echo 'style="background-color:'.$bkg.';"'; ?>>
                    <?php
                    if($date) echo '<div class="name section--one__content__event__day">'.$date->format('j').'</div>';
                    if($name) echo '<div class="name section--one__content__event__name">'.$name.'</div>';
                    if($start_time) echo '<div class="name section--one__content__event__time">'.$start_time.'</div>';
                    if($description) echo '<div class="position section--one__content__event__description">'.$description.'</div>';
                    if($event_page_link) echo '<a href="'.$event_page_link.'" target="_blank" class="link section--one__content__event__link">EVENT PAGE</a>'; 
                    if($event_page_link && $register_link) echo ' <span class="link section--one__content__event__link">|</span> ';
                    if($register_link) echo '<a href="'.$register_link.'" target="_blank" class="link section--one__content__event__link">REGISTER</a>';
                    ?>
                </div>
            </div>
            <?php 
            endforeach; 
            
            endif; 
            ?>
    
  • It’s not that I’m apposed to sharing, not really anything super secret, but the scope of what I have is too huge to share on a forum.

    In order to get pagination to work correctly you need to use a pre_get_posts filter on on the archive. Or at least that is the easiest way for me to do it. I suppose that you can do it with get_posts(), but it would be more difficult. When you modify the main query then you can use built in WP functions for the pagination links.

    For example, here is my pagination code on one site that uses a simple previous/next link, I use my own function because I wrap the links in bootstrap columns.

    
    function events_nav_links() {
      if (!get_previous_posts_link() && !get_next_posts_link()) {
        return;
      }
      ?>
        <div class="row events-archive-nav" style="margin-bottom: 2em;">
          <div class="col-6 text-left previous">
            <?php previous_posts_link("&laquo; Earlier Events"); ?>
          </div>
          <div class="col-6 text-right next">
            <?php next_posts_link("Later Events &raquo;"); ?>
          </div>
        </div>
      <?php 
    }
    

    This one uses pagination links to show page numbers

    
    // maybe page links
    $current_page = max(1, get_query_var('paged'));
    $args = array(
      'base' => get_pagenum_link(1).'%_%',
      'format' => 'page/%#%/',
      'prev_text' => '<i class="fal fa-chevron-left"></i>',
      'next_text' => '<i class="fal fa-chevron-right"></i>',
      'current' => $current_page,
      'mid_size' => 5,
      'type' => 'array'
    );
    $page_links = paginate_links($args);
    if (!empty($page_links)) {
      ?>
        <div class="row">
          <div class="col-12 event-page-links number-page-links">
            <div class="row justify-content-center">
              <?php 
                foreach ($page_links as $link) {
                  //$link = str_replace('//', '/', $link);
                  $extra_classes = array();
                  if (preg_match('/(prev|next)/', $link)) {
                    $extra_classes[] = 'icon';
                  } elseif (preg_match('/current/', $link)) {
                    $extra_classes[] = 'current';
                  } else {
                    $extra_classes[] = 'page';
                  }
                  $class = '';
                  if (!empty($extra_classes)) {
                    $class = ' '.implode(' ', $extra_classes);
                  }
                  ?>
                    <div class="col-auto link<?php echo $class; ?>">
                      <?php echo $link; ?>
                    </div>
                  <?php 
                }
              ?>
            </div>
          </div>
        </div>
      <?php 
    } // end if page links
    

    Like I said, I don’t know if it’s possible to do something similar using get_posts() in the template, but it will be more difficult. I try to always use built in WP functions when I can and using WP’s built in pagination functions are easier than trying to build something yourself. Altering the main query through a pre_get_posts_filter is just easier.

    the following is a pre_get_psots filter from a site that only shows current and future events in the archive

    
    function events_archive_only_children($query) {
      if (is_admin() || !$query->is_main_query()) {
        return;
      }
      // tests to make sure that we are displaying an event page
      if (!isset($query->query_vars) ||
          !isset($query->query_vars['post_type']) ||
          $query->query_vars['post_type'] != $this->post_type) {
        // test for event-category tax
        if (!isset($query->query_vars) ||
            !isset($query->query_vars['event-category'])) {
          // test for event-tag tax
          if (!isset($query->query_vars) ||
              !isset($query->query_vars['event-tag'])) {
            // test for event-type
            if (!isset($query->query_vars) ||
                !isset($query->query_vars['event-type'])) {
              // text for event-visitor-type
              if (!isset($query->query_vars) ||
                  !isset($query->query_vars['event-visitor-type'])) {
                // not of the possible query vars for events is set
                return;
              }
            }
          }
        }
      }
      // double check we are not on a single page
      if (is_single()) {
        return;
      }
      
      // custom query parameters (custom search form) that might be set
      // if visitor's browser does not support JS
      // redirect to correct URL based on event rewrite rules
      // custom taxonomies include event type and visitor type
      $type = false;
      $visitor = false;
      if (isset($_GET['e-type']) && !empty($_GET['e-type'])) {
        $type = $_GET['e-type'];
      }
      if (isset($_GET['e-visitor']) && !empty($_GET['e-visitor'])) {
        $visitor = $_GET['e-visitor'];
      }
      if ($type || $visitor) {
        $url = '/event/';
        if ($type) {
          $url .= 'type/'.$type.'/';
        }
        if ($visitor) {
          $url .= 'visitor/'.$visitor.'/';
        }
        wp_redirect($url, 301);
        exit;
      }
      
      // posts_per_page
      // setting on options page allows client to set
      if (function_exists('get_field')) {
        $post_id = $this->options_page;
        $posts_per_page = get_field('events_per_page', 'events_options');
        if ($posts_per_page === 0) {
          $posts_per_page = -1;
        }
        $query->set('posts_per_page', $posts_per_page);
      }
      
      // this framework uses parent/child events
      // to allow multiple event start and and dates
      // and recurring events
      // only show child events on the admin
      $query->set('post_parent__not_in', array('0'));
      
      
      if (isset($query->query['year'])) {
        // doing a date query
        // The following converts standard WP date searches
        // into a meta query on start & end dates ACF date/time fields
        // and removes the WP date query
        // shows any event starting in the selected year/month/day
        $date = $query->query['year'].'-';
        unset($query->query['year']);
        $query->query_vars['year'] = '';
        if (isset($query->query['monthnum'])) {
          $date .= $query->query['monthnum'].'-';
          unset($query->query['monthnum']);
          $query->query_vars['monthnum'] = '';
        };
        if (isset($query->query['day'])) {
          $date .= $query->query['day'];
          unset($query->query['day']);
          $query->query_vars['day'] = '';
        };
        $meta_query = array(
          'relation' => 'AND',
          'start_date_clause' => array(
            'key' => 'start_date',
            'value' => $date,
            'compare' => 'LIKE',
          ),
          'end_date_clause' => array(
            'key' => 'end_date',
            'value' => date('Y-m-d H:i:s'),
            'compare' => '>',
            'type' => 'DATETIME'
          )
        );
      } else {
        // not doing a date query
        // add standard meta query to only show current or upcoming events
        $meta_query = array(
          'relation' => 'AND',
          'start_date_clause' => array(
            'key' => 'start_date',
            'compare' => 'EXISTS',
          ),
          'end_date_clause' => array(
            'key' => 'end_date',
            'value' => date('Y-m-d H:i:s'),
            'compare' => '>',
            'type' => 'DATETIME'
          )
        );
      }
      $query->set('meta_query', $meta_query);
      
      // order by start/end date acf fields
      $order = array(
        'start_date_clause' => 'ASC',
        'end_date_clause' => 'ASC',
        'title' => 'ASC'
      );
      $query->set('orderby', $order);
    }
    
  • @hube2 Wow! Thanks for all these examples and explanations!

    Here’s what I have tried on a CPT archive page using pre_get_posts. It works fine for the first page but fails on any subsequent pages (example.com/events/page/2). My guess is that it has to do with my conditional statement failing for any pages other than page 1. Any ideas/suggestions/fixes of my mess?

    I’m trying to get each page of the archive show only events for a specific month, starting with the current month. So page 1 = May 2021, page 2 = June 2021, and so on…

    function custom_events_query( $query ) {
    
        if (  $query->is_main_query() && !is_admin() && is_post_type_archive('event')) {
            $paged = (get_query_var('paged')) ? get_query_var('paged') : 1;
            $query->set( 'post_type', 'event' );
            $query->set( 'meta_key', 'event_date' );
            $query->set( 'orderby', 'meta_value' );
            $query->set( 'order', 'ASC' );
            $query->set( 'posts_per_page', -1 );
            $query->set( 'paged', $paged );
            if($paged == 1) {
                $datequery = date('Ymd');
                $year = date('Y');
                $month = date('m');
                $meta_query_two = array(
                    'relation' => 'AND',
                    array(
                        'key' => 'event_date',
                        'value' => $datequery,
                        'compare' => '>='
                    ),
                    array(
                        'key' => 'event_date',
                        'value' => $year.''.$month.'31',
                        'compare' => '<='
                    )       
                );
            } else {
                $today = date('Ymd');
                $thisMonth = date('m', strtotime($today));
                $year = date('Y', strtotime($today));
                $month = date('m', strtotime($today.'+'.$paged.' month'));
                $datequery = date('Y'.$month.'01');
                $meta_query_two = array(
                    'relation' => 'AND',
                    array(
                        'key' => 'event_date',
                        'value' => $datequery,
                        'compare' => '>='
                    ),
                    array(
                        'key' => 'event_date',
                        'value' => $year.''.$month.'31',
                        'compare' => '<='
                    )       
                );
            }
            $query->set( 'meta_query', $meta_query_two );
        }
    
        return $query;
    }
    add_filter( 'pre_get_posts', 'custom_events_query' );
  • You do not need to set paged query arguments if WP is already doing this. It happens automatically when the url includes /page/XX/

    I don’t completely understand your query or why you would do something different on pages>2 verses page 1. but here are some things

    
    $query->set( 'posts_per_page', -1 );
    

    cause there to be no pages and all of the paged stuff you are setting is ignored.

    You want your query for all pages to be identical or you will get different results.

Viewing 9 posts - 1 through 9 (of 9 total)

You must be logged in to reply to this topic.