This article demonstrates how to create a live search application (an AJAX based application that returns search results as you type) with CodeIgniter and Mootools JSON. You can take a look at the final product here. The application is build using JavaScript in non obtrusive ways, so it will function in just about any browser, even if they don’t support JavaScript at all.
The article consists of five parts: model, view, controller, CodeIgniter JSON library and Mootools front end scripting, each with extensively commented source code.

Model

The SQL here is based on WP Search Reloaded plug-in. It is a fairly sophisticated way of searching through text, but requires FULLTEXT index. To simplify the example, this query is designed for search with a single term.

/*     

CREATE TABLE `posts` (
  `id` int(11) unsigned NOT NULL auto_increment,
  `title` varchar(255) NOT NULL,
  `body` text NOT NULL,
  `date` timestamp NOT NULL default CURRENT_TIMESTAMP,
  PRIMARY KEY  (`id`),
  UNIQUE KEY `id` (`id`),
  FULLTEXT KEY `title` (`title`,`body`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

*/

//search posts table for matching terms - the method will produce an associative array
public function search($term) {
	$term = $this->db->escape($term);

	$sql = "SELECT posts.*,
			CASE WHEN posts.title REGEXP $term THEN 1 ELSE 0 END AS keyword_in_title,
			MATCH ( posts.title, posts.body ) AGAINST ($term) AS mysql_score
			FROM posts
			WHERE ( posts.title REGEXP $term OR posts.body REGEXP $term )
			ORDER BY keyword_in_title DESC, mysql_score DESC, posts.date DESC LIMIT 0, 10";

	$res = $this->db->query($sql);	

	$items = array();

	if ($res->num_rows() > 0) {

		$count = 0;
		foreach ($res->result() as $item) {

			foreach ($item as $key => $value) {
				$items[$count][$key] = $value;
			}

			$count++;
		}
	} 

	return $items;
}

View

This part is straight forward - a form for submitting search requests and a for loop to display the results.

<!-- header -->

<div id="form-container-wrapper">
	<div id="form-container">
		<form method="post" id="ls-form" action="<?php site_url('ajax'); ?>">
			<p>
				<label for="searchterm" class="heading">Start typing in the bessage box. Try <strong>CodeIgniter</strong>, for example.</label>
			</p>
			<p>
				<input type="text" name="searchterm" id="searchterm" value="" />
				<input type="submit" name="Submit" id="submit" value="Submit" />
			</p>

		</form>
	</div><!-- form-container -->
</div><!-- form-container-wrapper -->

<div id="search-results-wrapper">
	<div id="search-results">

	<?php if($posts): ?>
		<h2>Search results:</h2>
		<?php $count = 1; foreach($posts as $key => $post) : ?>

		<h3><?php echo $count; ?>. <?php echo $post['title']; ?></h3>
		<p><?php echo $post['body']; ?></p>

		<?php $count++; endforeach; ?>
	<?php else: ?>	

		<h2>Search results:</h2>
		<p>Your query did not match any posts.</p>

	<?php endif; ?>	

	</div><!-- end search-results -->
</div><!-- end search-results-wrapper -->

<!-- footer-->

Controller

Controller contains one key method that will render the related view upon a standard post request or dump a JSON encoded string upon an AJAX request. You will need the JSON library for CodeIgniter to encode/decode that notation - the lib is provided further down the page.

public function index(){
	$this->search('standard');
}

public function json(){
	$this->search('json');
}

public function search($transport = null){
	//load database
	$this->load->database();
	//load posts model
	$this->load->model('Posts', 'posts');
	//load JSON lib
	$this->load->library('JSON', 'json');

	//init posts array
	$posts = array();

	//validate and filter the input
	if(isset($_POST['searchterm']) && !empty($_POST['searchterm'])){
		//clean the string, just in case
		$searchterm = strip_tags($_POST['searchterm']);
		//get the results if any
		$posts = $this->posts->search($searchterm);
	}

	//if request came from a JSON application
	if($transport == 'json'){
		//output the encoded string
		echo $this->json->encode($posts);
		//and exit, no reason to continue
		exit;
	}

	//assign post array
	$this->data['posts'] = $posts;
	//display the view
	$this->render('livesearch');
}

JSON Library for CodeIgniter

This is a wrapper for Services_JSON class from JSON’s official website. The library has two methods encode and decode, simple. You can download this library from CodeIgniter’s WIKI. To use the library, simply drop the contents of the zip file into the library folder in the application folder (not the library folder located in system).
If you have PHP 5.2.0 or older, the above library will not be needed. As of PHP 5.2.0, the JSON extension is bundled and compiled into PHP by default.

JavaScript

Finally after the application is completely functional, we can look into the fun part - Mootools :) The JS part of the application sends an AJAX post request containing the search term(s) and in return receives an encoded JSON string. The string is then decoded using Mootools JSON parser. The resulting JS object is then used to build and display the search results.

<script type="text/javascript">
//<![CDATA[
	var Site = {

		start: function(){
			Site.liveSearch();
		},

		liveSearch: function(){
			//set focus on the search form
			$('searchterm').focus();

			//attach a keyup event to the searchfield
			$('searchterm').addEvent('keyup', function(event){

				//controller's URI
				var url = '<?php echo site_url('livesearch/json'); ?>';

				//AJAX request
				new Ajax(url, {

					method: 'post',
					onComplete: function(request){
						//prepare search container for new results
						$('search-results').empty();

						//if controller returned some results decode them
						if(request) var results = Json.evaluate(request);
						//pass results object to the output method
						if(results) Site.buildSearchResults(results);
					},
					//post search form
					postBody: $('ls-form')				

				}).request();

				//return false;
			});
		},

		buildSearchResults: function(results){
			//prepare the container for newly fetched results
			var container = $('search-results');
			var heading = new Element('h2').setHTML('Search results:').injectInside(container);

			//output the results
			results.each(function(result, i) {
				var title = new Element('h3').injectInside(container);
				var link = new Element('a', {'href': '#post-'+result.id}).setHTML((i+1)+'. '+result.title).injectInside(title);
				var body = new Element('p').setHTML(result.body).injectAfter(title);
				var br = new Element('br').injectAfter(body);
			});
		}

	};
	window.addEvent('domready', Site.start);
//]]>
</script>

Further reading


7 Responses to “Live search with CodeIgniter and Mootools”

  • Hi, Thanks for posting this. Do you have a package of the files I can download and play with (sql file, etc)?

  • Hi Michael,

    I’ve uploaded the application - you can get it here.

    Just unzip the “app” folder next to the system folder (or whatever directory you keep CI in), edit database.php and config.php to match your settings and you’re good to go…

    SQL file, for the live search app, is located in models directory.

  • THANKS. Very interesting and inspiring article.
    Do you happen to have some advice for further reading on some even complexer search for SQL? You wrote “To simplify the example, this query is designed for search with a single term.”
    I’d like to learn in advance how to build a search for more than one term and for example allow Booleans etc..
    - I think I have an idea how to achieve it in PHP, but something tells me, that my way isn’t the most sophisticated and after all not the simplest.
    I had a look at the WP-plugin you mentioned and I gonna go deeper into it, when I’ve some time - just thought you might maybe now some interesting article on this matter.
    Cheers for your work anyways - I’ll make sure to come back here more often (there are way not enough CI-related blogs out there!).

    -Florian

  • Florian, I’m gland you found the article helpful.

    In this example, your complete search query equals to one single term. So query like “CodeIgniter Mootools” will be interpreted as one word (string’s case doesn’t matter), and most likely won’t yield any results, even though there are many “codeigniter” and “mootools” in the table. WP Reloaded plugin splits your query into multiple search terms, so the query becomes “CodeIgniter” + “Mootools” - WHERE ( posts.title REGEXP ‘codeigniter|mootools’ OR posts.post_content REGEXP ‘codeigniter|mootools’ ) - that would return everything with CodeIgniter OR Mootools.

    The above approach is better that the original WP search, but still quite simple. If you’re interested in something more sophisticated, you should definitely try Zend Lucene.

  • Hi there m8, thank you for your great documentation.

    However, i am having difficulty to adapt it to my database.

    Here some info about my database:

    table: portfolio
    `pid` int(11) unsigned NOT NULL auto_increment,
    `name` varchar(255) NOT NULL,
    `text` text NOT NULL,
    `date` varchar(255) NOT NULL,
    PRIMARY KEY (`pid`),
    UNIQUE KEY `pid` (`pid`),
    FULLTEXT KEY `title` (`name`,`text`)

    How do i need to fill in your code correctly now?

    I’m sorry for the stupidness but um just beginning to understand PHP/CI :)

  • Hmm, i am also having difficulty getting the AJAX part to work, ive got the search working with your table structure (i dont want to use that, id like to use the structure i mentioned above), but i still have to hit the search button to get results.

  • Gerben, everything is explained in the tutorial, everything is working, as you can see from the example. Moreover, I can’t help you, when you don’t supply any particular example or error.