Support

Account

Home Forums Front-end Issues Sort by one field, and then by another

Solved

Sort by one field, and then by another

  • Hi there,
    I’m trying to sort entries in a custom post type by one field (state), and then by another field (city) within that. Ideally, the state would be output once as an <h3> with city items related to that state listed underneath:

    TENNESSEE
    – Chattanooga
    – Nashville
    – Knoxville

    etc.

    But I’m not having any luck here. My loop will only seem to work if I specify the state instead of outputting for all states and cities.

    <?php $currentState = get_field('state');
    									$args = array(
    										'numberposts' => -1,
    										'post_type' => 'locations',
    										'orderby'   => 'meta_value',
    										'order' => 'ASC',
    										'meta_key' => 'city',
    										'meta_query' => array(
    											array(
    												'key' => 'state',
    												'value' => $currentState,
    											),
    										)
    									);
    										
    									$the_query = new WP_Query( $args );
    									
    									if ( $the_query->have_posts() ) {
    								?>
    								<ul>
    									
    									<?php while ( $the_query->have_posts() ) {
    										$the_query->the_post();
    									?>
    									<li><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></li>
    									<?php } ?>
    								</ul>
    								
    								<?php } ?>
  • Where are the posts for the archive queried? Is this code being used on archive-locations.php? What you need to do is to alter the query that is getting all of the locations posts to sort both of these fields, but how you go about altering the original query depends on where it’s being done.

  • This is in archive-locations.php

  • Look at https://codex.wordpress.org/Class_Reference/WP_Query#Order_.26_Orderby_Parameters and look for ‘orderby’ with multiple ‘meta_key’s and also here https://make.wordpress.org/core/2015/03/30/query-improvements-in-wp-4-2-orderby-and-meta_query/

    This should probably be done with a pre_get_posts filter to alter the main query. This is an example using what you’ve posted to give an example.

    
    $args = array(
      'numberposts' => -1,
      'post_type' => 'locations',
      'meta_query' => array(
        'state_clause' => array(
          'key' => 'state',
          'compare' => 'EXISTS',
        ),
        'city_clause' => array(
          'key' => 'city',
          'compare' => 'EXISTS',
        ),
      ),
      'orderby' => array(
        'state_clause' => 'ASC',
        'city_clause' => 'ASC',
        'title' => 'ASC'
      )
    );
    $the_query = new WP_Query($args);
    

    The above will order all of the posts so that they end up being grouped together by state and then city. The next step is that as you’re looping through the posts you need to look at what the state/city of the post is and compare it to the previous post. This can be a little confusing because of the circular logic of possibly outputting closing tag for elements before outputting the matching opening tags for those elements.

    
    if ($the_query->have_posts()) {
      // variables to hold values from previous post
      $last_state = '';
      $last_city = '';
      
      while ($the_query->have_posts()) {
        // get state and compare it to the previous post
        $this_state = get_field('state');
        if ($this_state != $last_state) {
          // the state has changed
          if ($last_state != '') {
            // close the post UL, city DIV, and state DIV that we'll open next
            // this will be skipped if this is the first post
            // because $last_state will be empty
            echo '</ul></div><!-- .city --></div><!-- .state -->';
          }
          echo '<div class="state"><h2>'.$this_state.'</h2>';
          // clear $last_city
          $last_city = '';
          // set $last_state to $this_state
          $last_state = $this_state;
        } // end if new state
        // get the city and compare to previous post
        $this_city = get_field('city');
        if ($this_city != $last_city) {
          // the city has changed
          if ($last_city != '') {
            // close the post UL, city DIV we'll open next
            // this will be skipped if this is the first post for city
            // because $last_city will be empty
            echo '</ul></div><!-- .city -->';
          }
          echo '<div class="city"><h3>'.$this_city.'</h3><ul>';
          // set $last_city to $this_city
          $last_city = $this_city;
        } // end if new city
        ?>
          <li><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></li>
        <?php 
      } // end while have posts
      // we need to close the last set of elements that were opened
      echo '</ul></div><!-- .city --></div><!-- .state -->';
    } // end if have_posts
    
  • My apologies for taking awhile to get back to this.

    So, the first part works fine. But the 2nd section seems to be causing the page to never fully load. I’m not sure why.

  • well, that’s because I missed something extremely important

    
      while ($the_query->have_posts()) {
        $the_query->the_post(); // this is missing
        // get state and compare it to the previous post
    
  • BLAMMO!!!! Thanks so much, man!!!

  • I was even able to add in another sorting parameter (country) seeing how this worked. Thank you loads!

  • Sooooo…y’all were super helpful before and I was able to apply this filtering to another website as well. All was going swimmingly until the client asked for one more level of sorting, and I’m tearing my hair out trying to figure this out.

    So, this is for a choral group. They’re wanting to display all their past seasons, sorted by year/season, and then by performance date (performances are actually individual products in WooCommerce). Sorting by the ACF meta fields is going fine. But within each year, they also want to break it out to sorting all the concerts assigned the ‘cantata-series’ product category, and the ‘masterworks-series’ product category. So, a typical listing would look something like:

    2017-2018 SEASON
    Cantata Series
    * Concert Date 1
    * Concert Date 2
    Masterworks Series
    * Concert Date 1
    * Concert Date 2

    2016-2017 SEASON
    Cantata Series
    * Concert Date 1
    * Concert Date 2
    Masterworks Series
    * Concert Date 1
    * Concert Date 2

    etc. etc. etc.

    I was doing fine when it was just sorting by season and then by date. But adding in sorting by category is just throwing me. I can get it to display the correct series before each and every date it applies to, but that’s not right.

    Here’s my code right now (not working)

    $season_args = array(
    		'numberposts' => -1,
    		'post_type' => 'product',
    		'product_cat' => 'past, cantata-series, masterworks-series',
    		'meta_query' => array(
    			'season_clause' => array(
    				'key' => 'select_season',
    				'compare' => 'EXISTS',
    			),
    			'date_clause' => array(
    				'key' => 'date',
    				'compare' => 'EXISTS',
    			)
    		),
    		'orderby' => array(
    			'season_clause' => 'ASC',
    			'date_clause' => 'ASC'
    		)
    	);
    	
    	$the_query = new WP_Query($season_args);
    	
    	if ($the_query->have_posts()) {
    		// variables to hold values from previous post
    		$last_season = '';
    		$last_date = '';
    		
    		while ($the_query->have_posts()) {
    			$the_query->the_post();
    			
    			// get season and compare it to the previous post
    			$this_season = get_field('select_season');
    			
    			if ($this_season != $last_season) {
    				// the season has changed
    				if ($last_season != '') {
    					// close the post UL, season DIV, and season DIV that we'll open next
    					// this will be skipped if this is the first post
    					// because $last_season will be empty
    					echo '</div><!-- .season -->';
    				} ?>
    				
    				<div class="season">
    					<h2><?php echo $this_season->post_title; ?></h2>
    				
    				<?php  // clear $last_season
    				$last_season = '';
    				// set $last_season to $this_season
    				$last_season = $this_season; ?>
    				
    				<?php if ( has_term( 'cantata-series', 'product_cat' ) ) { ?>
    						<h3>Cantata Series</h3>
    						
    				<?php } 
    			} // end if new season
    			
    			// get the date and compare to previous post
    			$this_date = get_field('date', false, false);
    			$this_date = new DateTime($this_date); ?>
    			
    				
    							<?php if ( has_term( 'cantata-series', 'product_cat' ) ) { ?>
    							<li><?php echo $this_date->format('M j Y'); ?> - <a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></li> <?php } ?>
    		<?php } // end while have posts
    	} // end if have_posts
    
    wp_reset_query();

    Thanks in advance.

    ~Laura

  • What part of your code is not working? The query or the display? Is it getting the posts?

  • It’s getting the posts. I’m just not having any luck getting the sub-headings with the performance dates that are in that sub-heading category (cantata series, masterworks series). Right now I get the sub-heading above each date, instead of sorted under the sub-heading.

  • WP does not include any way to order posts by category. If you were doing
    – Category
    – – Series
    – – – Date

    I would tell you to get the posts in each term separately, but since the category is the last thing, that won’t help you.

    The only way that I can see to do this is to loop through the posts twice. The first time creating a separate array to store them in and somehow ordering them the way you want and then the second time to show the posts you stored in the array. But I’m not really sure how to do that.

    Another choice would be to somehow do multiple queries, not sure about this either. Maybe do a query for each for each season in each series ordered by date.

    Sorry I can’t be of more help.

  • You were actually wonderfully helpful. You helped me realize that I needed to just add another meta query field for the different series’. Now I’ve got it working perfectly! Thank you!

  • Well, I have another site I’m trying to use this sorting feature on, but for some reason it’s just not working this time, and I’m not sure why. Hoping someone here can help me figure it out.

    Everything displays, except that the <h2> gets added before each item (meaning, for instance, that I get:

    ALABAMA

    • Property Name

    ARKANSAS

    • Property Name

    FLORIDA

    • Property Name

    FLORIDA

    • Property Name

    And it needs to show those last two as

    FLORIDA

    • Property Name
    • Property Name

    Here’s my code:

    $props_args = array(
       'post_type' => 'properties',
       'posts_per_page' => -1,
       'post_status' => 'publish',
       'meta_query' => array(
          'state_clause' => array(
             'key' => 'which_state',
             'compare' => 'EXISTS',
          ),
       ),
       'orderby' => array(
          'state_clause' => 'ASC',
          'title' => 'ASC'
       ),
       'order' => 'DESC',
    );
    
    $props_query = new WP_Query($props_args);
    if ($props_query->have_posts()) { ?>
    
    <main class="container">
       <div class="row">
    
          <?php // variables to hold values from previous post
          $last_state = '';
    
          while ($props_query->have_posts()) {
             $props_query->the_post();
    
             // get state and compare it to the previous post
             $this_state = get_field('which_state');
    
             if ($this_state != $last_state) {
    
                // the state has changed
                if ($last_state != '') {
                   // close the post UL, city DIV, and state DIV that we'll open next
                   // this will be skipped if this is the first post
                   // because $last_state will be empty
                   echo '</tbody></table><!-- list --></div><!-- state -->';
                }
                echo '<div class="state"><h2>'.$this_state.'</h2>';
             } // end if new state
    
             // Vars
             $marker = get_field('address');
             $address = explode( "," , $marker['address']);
             $logo = get_field('property_logo'); ?>
    
             <tr>
                <td class="gold-border"></td>
                <td class="first">
                   <a class="link" href="<?php the_permalink(); ?>" target="_blank"><img src="<?php echo $logo; ?>" class="style-svg" alt="<?php the_title(); ?>"></a>
                </td>
                <td>
                   <?php echo $address[0].''; //street number ?><br />
                   <?php echo $address[1].','.$address[2]; //city, state + zip ?>
                </td>
             </tr>
          <?php  } // end while have posts
    
          // we need to close the last set of elements that were opened
          echo '</tbody></table><!-- list --></div><!-- state -->'; ?>
    
       </div>
    </main>
    
    <?php } // end if have_posts
    
    wp_reset_query();
  • You’re not setting $last_state = $this_state before the end of the while loop.

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

You must be logged in to reply to this topic.

We use cookies to offer you a better browsing experience, analyze site traffic and personalize content. Read about how we use cookies and how you can control them in our Cookie Policy. If you continue to use this site, you consent to our use of cookies.