Kayak-like filter sliders using jQuery and AJAX pagination in CakePHP

Let’s try to build a nice slider widget to filter your data… kind of like the ones you’d find on kayak.com (do a search for any flight to see a sample).

First we need to enable our data to be paginated via AJAX.
For this, I am relying on an excellent and very simple technique, provided by Casey Dreier. It really only takes a few minutes to setup and works wonderfully well with CakePHP built-in Paginator.

You should probably setup AJAX pagination for your data first, but if you are interested in just adding the slider filter functionality, here’s how to do it…

Let’s say we are building a real estate web site where on a page we show a bunch of listings of various properties for a given town (My Little Town). Now, we’d like to let the user to easily filter the price range by using a cool jQuery slider.

Going a bit backwards, perhaps, we’ll start with the view…

This is how I’ve got everything setup:

<?php
   $html->css('data_grid', null, null, false);
    $html->css('search_filters', null, null, false);
    $html->css('jquery_ui_blue', null, null, false);

    $javascript->link('jquery/jquery.min', false);
    $javascript->link('jquery/jquery_ui.min', false);
    $javascript->link('jquery/ui/jquery.slider.min', false);

    $javascript->link('jquery/page_specific/towns_view', false);
?>

<div class="page-title"><?php echo !empty($searchString) ? Inflector::humanize($searchString) : null; ?></div>

<form>
    <input type="hidden" id="search-string" value="<?php echo $searchString; ?>" />
    <input type="hidden" id="max-price" value="<?php echo $maxPrice['Property']['sale_price']; ?>" />
    <input type="hidden" id="min-price" value="<?php echo $minPrice['Property']['sale_price']; ?>" />
</form>

<p>
   <div class="search-filters">
       <form>
           <?php echo $form->label('amount', 'Filter by price:'); ?>
           <?php echo $form->text('amount', array('id'=>'amount', 'class'=>'amount-holder')); ?>
       </form>
       <p>
           <div id="slider"></div>
       </p>
   </div>
</p>

<div id="listing-data"><?php echo $html->image('icons/loading.gif'); ?></div>

First, we include our relevant CSS files and jQuery, that’s a no-brainier.

Inflector::humanize($searchString) will convert our search string, which usually comes from the URL (i.e. www.example.com/towns/view/my_little_town) into “My little town”.

Then we’ve got a bunch of hidden fields for the JavaScript to get the values from. As you’ve guessed some of these are used as settings for the slider as well as let our JS grab the search string, which will be sent to the server via AJAX.

Then we’ve got our prepared filter with the slider, and finally the div, which is going to hold the listing data (all of the details of the listing data setup are explained in the article linked above).

As you can imagine we set the min and max prices in the controller (as well as the search string)…

Now, let’s take a look at our simple jQuery script that will handle data retrieval and filtration for us:
(That’s the file towns_view.js, which is linked above)

[sourcecode language=”javascript”]
$(document).ready(function(){
var minPrice = $(‘#min-price’).val();
var maxPrice = $(‘#max-price’).val();
var searchString = $(‘#search-string’).val();

minPriceInt = parseInt(minPrice);
maxPriceInt = parseInt(maxPrice);

loadPiece(‘/towns/get_listings/’ + searchString, ‘#listing-data’);

$(“#slider”).slider({
range: true,
min: minPriceInt,
max: maxPriceInt,
values: [minPriceInt, maxPriceInt],
step: 100,
slide: function(event, ui){
$(“#amount”).val(‘$’ + ui.values[0] + ‘ – $’ + ui.values[1]);
},
stop: function(event, ui){
loadPiece(‘/towns/get_listings/’ + searchString + ‘/minPrice:’ + $(“#slider”).slider(“values”, 0) + ‘/maxPrice:’ + $(“#slider”).slider(“values”, 1), ‘#listing-data’);
}
});

$(“#amount”).val(‘$’ + $(“#slider”).slider(“values”, 0) + ‘ – $’ + $(“#slider”).slider(“values”, 1));

});

function loadPiece(href, divName){

$(divName).load(href, {}, function(){
var divPaginationLinks = divName + ‘ .paginator-links a’;

$(divPaginationLinks).click(function(){
var thisHref = $(this).attr(‘href’);
loadPiece(thisHref, divName);
return false;
});

});
[/cc]

First, we prepare our script with the values we’ve gotten from the hidden fields of our view…


var minPrice = $(‘#min-price’).val();
var maxPrice = $(‘#max-price’).val();
var searchString = $(‘#search-string’).val();

The loadPiece(), which was kindly posted at the above article, handles loading paginated data via AJAX into our “listing-data” div (remember the view?)…

So, on the first page load loadPiece() will grab all paginated data for a given model, using the $searchString (which originally came from the URL, and then was set to a hidden field).

The minPrice and maxPrice values are obtained from the controller (in our case it would be the minimum price of some property for sale, or the maximum price).

Without going into too much detail here’s an easy way to grab that info from your database.

This is done and set in some action of the controller, perhaps view(), following our example…

$this->set('maxPrice', $this->Property->find('first', array('fields'=>'Property.sale_price',
                                                                             'order'=>array('Property.sale_price DESC'),
                                                                             'conditions'=>array('Property.city LIKE' => '%'.$searchString.'%'))));

$this->set('minPrice', $this->Property->find('first', array('fields'=>'Property.sale_price',
                                                                            'order'=>array('Property.sale_price ASC'),
                                                                            'conditions'=>array('Property.city LIKE' => '%'.$searchString.'%'))));

I hope this code is self-explanatory.

Now that we know and have set our $minPrice and $maxPrice, we are ready to go ahead and setup the slider…

Note:

minPriceInt = parseInt(minPrice); and maxPriceInt = parseInt(maxPrice); this was necessary for the slider to work, because otherwise the values were treated as a string and broke the slider…

Alright, I’m not really going to go in depth about the setup of the slider as it is quite nicely covered in the jQuery examples and the manual, but will point out a few things:

  1. values: [minPriceInt, maxPriceInt] – means where the slider handles are originally positioned, in this case I want them both to be at the min and max coordinates respectively
  2. step: 100 – means how many units the slider handle jumps, so in case of our sale prices… it means 100 bucks on each slide
  3. slide – provides the user with the feedback on what price range is currently selected. You can see this code and how it looks in action right here
  4. stop – is our main “filtering” function it takes the values of the price range from the slider handle’s position and, again, using the loadPiece() function gets the paginated data from the server. Only this time, of course, the prices are passed to the ‘conditions’ array of the find() method and therefore the list becomes filtered out

Our method, the one triggered by an AJAX request, might look something like this:

function get_listings($searchString = null) {

Configure::write('debug', 0);

if($searchString) {

 $searchConditions = array('Property.city LIKE' => '%'.$searchString.'%');

 if($this->params['named']['maxPrice'] || $this->params['named']['minPrice']) {
    $searchConditions = Set::merge($searchConditions, array('Property.sale_price <=' => $this->params['named']['maxPrice'],
                                                                            'Property.sale_price >=' => $this->params['named']'minPrice']));
  }

  $this->set('listings', $this->paginate('Property', $searchConditions));
  }
}

Well, that’s all there is to it. I know this post doesn’t go into a lot of detail and code specifics, but it should give you a good general idea on how to setup such a filter with AJAX. Definitely try out the method described above to do an AJAX pagination, it is simple, effective and allows you to create really cool interactive widgets like this one for your data filters.

Plus working with AJAX and jQuery is always “fun” :)

Related Posts

%d bloggers like this: