Sort posts by custom field numeric value using dropdown

'meta_value_num' is not magic, it casts a value as numeric, however, to be properly cast as numeric, a value must be numeric compatible.

In OP it’s said that the field values are “numeric” but I bet there’s something not numeric, e.g. a currency symbol like “10 $” or “$ 10.00”, or other symbol like “10-“.

Also consider that using a thousands separator most probably makes the casting fail, because MySQL can mistake it as a decimal separator.

When you want to use a meta field for order, follow these rules:

  • Do not use any other char that is not numeric (no currency or other symbols, no spaces..)
  • Do not use any thousands separators
  • When a value is an integer (no decimal) do not use a decimal, e.g. if a value is 10, do not use 10.00
  • Use the dot as a decimal separator.

If you follow previous rules, the order by 'meta_value_num' will work as expected.

If you are asking yourself how to properly format a price entered using previous rules, the answer is number_format.

As example, in Italy, a properly formatted price is € 1.000,50 (one thousand euros and fifty cents).

Following previous rules, one should enter that price as 1000.50

To output the proper formatted price it’s possible to use a custom function like this:

function formatted_price( $number ) {
    // force 2 decimals, comma as decimal separator and dot as thousands separator
    return '€ ' . number_format ( (float) $number, 2, ",", "." );
}

And use it like so:

while ( have_posts() ) :
    the_post();

    if ( $price = get_post_meta( get_the_ID(), 'property_price', TRUE ) ) {
        // assuming the price is stored in the 'property_price' custom field
        echo formatted_price( $price );
    }

That said, I strongly suggest you avoid the usage of query_posts and replace it with a function hooked into 'pre_get_posts'.

From Codex:

… It is inefficient (re-runs SQL queries) and will outright fail in some
circumstances (especially often when dealing with posts pagination).
Any modern WP code should use more reliable methods, like making use
of pre_get_posts hook, for this purpose.

So, remove any code from your category.php and in your functions.php write:

add_action( 'pre_get_posts', 'order_properties_by_price' );

function order_properties_by_price( $query ) {
    if ( is_admin() || ! $query->is_main_query() || ! $query->is_category( 'property' ) )
        return;
    $byprice = filter_input( INPUT_GET, 'orderby', FILTER_SANITIZE_STRING ); 
    if ( empty( $byprice ) )
        return;
    if ( $byprice === 'price' ) {
        $order = strtoupper( filter_input( INPUT_GET, 'order', FILTER_SANITIZE_STRING ) ); 
        if ( $order !== 'DESC' ) $order="ASC";
        $query->set( 'meta_key', 'property_price' );
        $query->set( 'orderby', 'meta_value_num' );
        $query->set( 'order', $order );
    }
}

Now, assuming that your ‘property’ archive URL is

http://www.example.com/category/property/

it will, of course, show posts in category ‘property’ ordered by date (newer to older).

Using the code posted above, the URL

http://www.example.com/category/property/?orderby=price

will show posts in category ‘property’ ascending, ordered by price, and the URL

http://www.example.com/category/property/?orderby=price&order=DESC

will show posts in category ‘property’ descending, ordered by price.

So, what you need to do, is to make a dropdown that redirects the page to the proper URLs.

You can write a custom function to output the dropdown:

function order_properties_menu() {
    if ( ! is_category('property') ) return;
    $url = get_category_link( get_term_by( 'slug', 'property', 'category' ) );
    $form = '<form id="orderbyprice" action="#" method="GET">'
        . '<label>Order by:</label><select name="order">';
    $htl = esc_url( add_query_arg( array( 'orderby' => 'price', 'order' => 'DESC' ), $url ) ) ;
    $lth = esc_url( add_query_arg( array('orderby' => 'price', 'order' => 'ASC' ), $url ) );
    $options = array(
        'latest' => array( 'Latest', $url ),
        'htl'    => array( 'Price High to Low', $htl ),
        'lth'    => array( 'Price Low to High', $lth ) 
    );
    $format="<option value="%s"%s>%s</option>";
    foreach( $options as $id => $option ) {
        $sel="latest";
        if ( get_query_var( 'orderby' ) === 'meta_value_num' )
            $sel = get_query_var( 'order' ) === 'DESC' ? 'htl' : 'lth';
        $selected = selected( $sel, $id, FALSE );
        $form .= sprintf( $format, esc_url( $option[1] ), $selected, esc_html( $option[0] ) );
    }
    echo $form . '</select></form>';
    ?>
    <script>
        jQuery(document).on( 'change', '#orderbyprice select', function() {
            window.location.href = jQuery(this).val();
        });
    </script>
    <?php
}

This function outputs a dropdown menu that allow users to choose from the 3 ordering options, and when one is selected, the page is redirected accordingly.

After having added this function in functions.php, put it everywhere you want in your category.php

order_properties_menu();

and the select will be shown.

Note that the order_properties_menu() function requires jQuery.

Leave a Comment