Tuesday, October 16, 2012

Chained AJAX selects

Chained AJAX selects

In today's tutorial, we will build a set of chained select elements. Selecting an option in one of them will trigger an update on the page, showing you more choices to refine your selection. We will describe the options server side with PHP, so it is easy for you to hook today's example to a database.


The idea for this tutorial was suggested by Casper Hansen from Denmark.

The HTML

As you can see from the screenshot below, the select box is accompanied by a title that explains what the selection is about. The title and the selectbox are enclosed in a li item.

When adding more questions, additional lis are created by jQuery. All of these sit inside an unordered list called #questions. The title and options for these items are served as JSON, as you will see in the PHP part of the tut. Here is the markup that is generated for the li items:

<ul id="questions">
    <!-- Generated by jQuery -->
    <li>
        <p>What would you like to purchase?</p>
        <select data-placeholder="Choose a product category">
            <option data-connection="phoneSelect" value="Phones">Phones</option>
            <option data-connection="notebookSelect" value="Notebooks">Notebooks</option>
            <option data-connection="tabletSelect" value="Tablets">Tablets</option>
        </select>
    </li>
    <!-- The next sections are inserted here depending on the choices above -->
</ul>

You might notice in the demo page that we aren't using the default browser select controls. This is because we are making use of the Chosen jQuery plugin to upgrade our selects into the fancy widgets you see. We simply need to call the chosen() method on the selects and the plugin will handle the rest.

The jQuery code

Here is what our jQuery code does in short – it fetches the select boxes information as JSON from the server, generates their HTML and sets up event listeners for selection changes. If a change in the selection does occur, the process is repeated for the new select item.

In the code, this is achieved using two JavaScript functions:

  • refreshSelects triggers the Chosen plugin and binds event listeners every time an item is added to the page;
  • fetchSelect requests a JSON feed from the server and generates the markup from the response.

You can see them below.

$(function(){

    var questions = $('#questions');

    function refreshSelects(){
        var selects = questions.find('select');

        // Improve the selects with the Chose plugin
        selects.chosen();

        // Listen for changes
        selects.unbind('change').bind('change',function(){

            // The selected option
            var selected = $(this).find('option').eq(this.selectedIndex);
            // Look up the data-connection attribute
            var connection = selected.data('connection');

            // Removing the li containers that follow (if any)
            selected.closest('#questions li').nextAll().remove();

            if(connection){
                fetchSelect(connection);
            }

        });
    }

    var working = false;

    function fetchSelect(val){

        if(working){
            return false;
        }
        working = true;

        $.getJSON('ajax.php',{key:val},function(r){

            var connection, options = '';

            $.each(r.items,function(k,v){
                connection = '';
                if(v){
                    connection = 'data-connection="'+v+'"';
                }

                options+= '<option value="'+k+'" '+connection+'>'+k+'</option>';
            });

            if(r.defaultText){

                // The chose plugin requires that we add an empty option
                // element if we want to display a "Please choose" text

                options = '<option></option>'+options;
            }

            // Building the markup for the select section

            $('<li>\
                <p>'+r.title+'</p>\
                <select data-placeholder="'+r.defaultText+'">\
                    '+ options +'\
                </select>\
                <span class="divider"></span>\
            </li>').appendTo(questions);

            refreshSelects();

            working = false;
        });

    }

    $('#preloader').ajaxStart(function(){
        $(this).show();
    }).ajaxStop(function(){
        $(this).hide();
    });

    // Initially load the product select
    fetchSelect('productSelect');
});

Great! We are now left with generating the actual JSON feed. Notice that the fetchSelect function takes a string argument. This is the key we will be passing back to PHP, denoting which set of items we want.

Here is a sample response from our PHP script:

{
    "items": {
        "Phones": "phoneSelect",
        "Notebooks": "notebookSelect",
        "Tablets": ""
    },
    "title": "What would you like to purchase?",
    "defaultText": "Choose a product category"
}

fetchSelect loops through the items and uses the keys as content of the option elements and the values as connections. Phones and Notebooks would cause the script to generate new select boxes, while Tablets would not.

Chained AJAX selects

The PHP

We need to somehow store the information about the select boxes, the options they contain and the connections between them. With a database this could be done by selecting a specific set of rows. But here we will be storing this data statically as objects. For this purpose, we will define a simple class that will hold the information for a select box:

ajax.php / 1

// Each select box will be an instance of this class

class SelectBox{
    public $items = array();
    public $defaultText = '';
    public $title = '';

    public function __construct($title, $default){
        $this->defaultText = $default;
        $this->title = $title;
    }

    public function addItem($name, $connection = NULL){
        $this->items[$name] = $connection;
        return $this;
    }

    public function toJSON(){
        return json_encode($this);
    }
}

Now we only need to create an instance of this class for every select box and call the addItem() to add options. This method has an optional $connection parameter, that holds the name of a dependent select box.

ajax.php / 2

/* Configuring the selectboxes */

// Product selectbox

$productSelect = new SelectBox('What would you like to purchase?','Choose a product category');
$productSelect->addItem('Phones','phoneSelect')
              ->addItem('Notebooks','notebookSelect')
              ->addItem('Tablets','tabletSelect');

// Phone types

$phoneSelect = new SelectBox('What kind of phone are you interested in?', 'Pick a phone type');
$phoneSelect->addItem('Smartphones','smartphoneSelect')
            ->addItem('Feature phones','featurephoneSelect');

// Smartphones

$smartphoneSelect = new SelectBox('Which is your desired smartphone?','Choose a smartphone model');
$smartphoneSelect->addItem('Samsung Galaxy Nexus')
                 ->addItem('iPhone 4S','iphoneSelect')
                 ->addItem('Samsung Galaxy S2')
                 ->addItem('HTC Sensation');

// Feature phones

$featurephoneSelect = new SelectBox('Which is your desired featurephone?','Choose a feature phone');
$featurephoneSelect->addItem('Nokia N34')
                   ->addItem('Sony Ericsson 334')
                   ->addItem('Motorola');

// iPhone colors

$iphoneSelect = new SelectBox('What color would you like?','Choose a color');
$iphoneSelect->addItem('White')->addItem('Black');

// Notebook select

$notebookSelect = new SelectBox('Which notebook would you like to buy?', 'Choose a notebook model');
$notebookSelect->addItem('Asus Zenbook','caseSelect')
               ->addItem('Macbook Air','caseSelect')
               ->addItem('Acer Aspire','caseSelect')
               ->addItem('Lenovo Thinkpad','caseSelect')
               ->addItem('Dell Inspiron','caseSelect');

// Tablet select

$tabletSelect = new SelectBox('Which tablet would you like to buy?', 'Pick a tablet');
$tabletSelect->addItem('Asus Transformer','caseSelect')
             ->addItem('Samsung Galaxy Tab','caseSelect')
             ->addItem('iPad 16GB','caseSelect')
             ->addItem('iPad 32GB','caseSelect')
             ->addItem('Acer Iconia Tab','caseSelect');

// Case select

$caseSelect = new SelectBox('Buy protective casing?','');
$caseSelect->addItem('Yes')->addItem('No');

// Register all the select items in an array

$selects = array(
    'productSelect'            => $productSelect,
    'phoneSelect'            => $phoneSelect,
    'smartphoneSelect'        => $smartphoneSelect,
    'featurephoneSelect'    => $featurephoneSelect,
    'iphoneSelect'            => $iphoneSelect,
    'notebookSelect'        => $notebookSelect,
    'tabletSelect'            => $tabletSelect,
    'caseSelect'            => $caseSelect
);

The code above defines a number of select items and places them in the $selects array. When this script receives an AJAX request, it will look into this array and return a response:

ajax.php / 3

// We look up this array and return a select object depending
// on the $_GET['key'] parameter passed by jQuery

// You can modify it to select results from a database instead

if(array_key_exists($_GET['key'],$selects)){
    header('Content-type: application/json');
    echo $selects[$_GET['key']]->toJSON();
}
else{
    header("HTTP/1.0 404 Not Found");
    header('Status: 404 Not Found');
}

By calling the toJSON() method we defined in the beginning, we output all the data for the select object as JSON, ready for use by our jQuery frontend.

View Demo


via Tutorialzine by