Sencha Inc. | HTML5 Apps

Building a grid with a livesearch form

Published May 05, 2008 | Fransjo Leihitu | Tutorial | Medium
Last Updated Jul 11, 2011

This Tutorial is most relevant to Ext JS, 2.x, 3.x.

First of all, I want to give big ups for the developers of ExtJS. I've discoverd ExtJS just recently and I was really impressed and started to play around with the components especially with the Grid component. In the past I've had my share of Javascript frameworks/libs but ExtJS was really an eyeopener.

Also what I like about ExtJS is that it's almost similar like Adobe Flex (wich I also like).

I've also set up a forum topic here: http://extjs.com/forum/showthread.php?t=26990

Project files

I've set up a working example on my server : http://www.leihitu.nl/ext-2.0.1/examples/tutorial_final/

If you want I have also got the final example to download at:

http://www.leihitu.nl/ext-2.0.1/examples/tutorial_final/tutorial_files.zip

I've setup the folder structure just like the /ext-2.0.1/examples you get when you download the ExtJS files. Just drop the folder tutorial_final in your /examples/ folder. Also drop the connection.php and JSON.php in the /example/ folder.

Make sure you edit connection.php and /tutorial_final/search.php with your mysql details

Object, objects, objects

The goal of this tutorial is to make a page where you can look up some people. The user will type in the name in a inputfield and as the user is typing the name, the search results will be displayed in a grid.

In order to do this you need to understand how the objects interact with each other. First a diagram to illustrate:

So some explanation

1) Formfield

The formfield is where the user types in the name. On each stroke, the input will be given to the datastore.

2) Datastore

For those who uses Adobe Flex, they will see a familiar concept. Datastore basically holds the connection to your server-side script/xml/array data. The Datastore also has a reader. The reader maps the incoming data from your datasource (server-side script/xml/array) to objects so other components can access these objects.

3) Grid

The grid component basically just binds itself to the Datastore, meaning every time the Datastore receives new data and is rendered through the reader, the grid just updates itself (Adobe Flex anyone?). The grid wil get the data from the reader, but the grid will decide which fields will be exposed to the visitor. For example, the reader can have the following fields: FIELD_1, FIELD_2, FIELD_3, but the grid just shows FIELD_1 and FIELD_2.

4) Serverside script

When the formfield passes data to the Datastore, the Datastore performs a HTTP GET or POST (depending on what you want) to your server-side script. The server-side script then queries the database and results are returned to the Datastore. Before the server-side returns the results, it converts the data to JSON data. When the Datastore has received the JSON data, it will pass it to the reader and the Datastore has new data so all the components that are bound to the Datastore will be updated.

That's basically your livesearch flow ;-).

Ok , let's start this journey - let's create the HTML and JS file:

HTML

Okay I assume you have read part 1 and downloaded my final project file. First we create a simple HTML file. Here's one to start with:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
	"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
    <title>Tutorial Final</title>
    <link rel="stylesheet" type="text/css" href="../../resources/css/ext-all.css" />
    <link rel="stylesheet" type="text/css" href="styles.css" />
 
    <!-- GC -->
 	<!-- LIBS -->
 	<script type="text/javascript" src="../../adapter/ext/ext-base.js"></script>
 	<!-- ENDLIBS -->
 
    <script type="text/javascript" src="../../ext-all.js"></script>
 
    <script type="text/javascript" src="RowExpander.js"></script>
    <script type="text/javascript" src="tutorial.js"></script>
 
</head>
<body>
 
<div id='wrapper'>
	<h1>Tutorial Final</h1>
 
	People in the database: George,Melanie,Brad,Sandra,Jasper,Jim
 
	<div id='searchWrapper'></div>
	<div id='gridWrapper'></div>
</div>
 
 
</body>
</html>

As you can see I've included a js file called tutorial.js. This is where we'll define the Grid/Datastore etc.

Also you'll see that I've created two empty divs : searchWrapper (the formfield wil be rendered here) and gridWrapper(the grid component will be rendered here).

JS file

And to start the js file (tutorial.js)

/*
 * Ext JS Library 2.0.1
 * Copyright(c) 2006-2008, Ext JS, LLC.
 * licensing@extjs.com
 * 
 * http://extjs.com/license
 */
 
Ext.onReady(function()
{
 
 
 
});

Datastore

So first we'll set up the Datastore :

Ext.onReady(function()
{
	 Ext.QuickTips.init();
 
	// ----------------
	//	vars
	// ----------------
	var ds;	// datasource var
 
	// ----------------
	//	Create datasource
	// ----------------
	function createDS()
	{
		ds = new Ext.data.Store({
        	proxy: new Ext.data.HttpProxy({
            	url: 'search.php', // serverside script to post to
            	method: 'POST' // method of posting .. GET or POST .. I've used POST
        	})			
		});				
	}
 
 
	createDS();	    
});

Reader

At this point we've only set up the connection TO the server-side script. The script will return a JSON object, but the Datastore doesn't know what is what so now enters the reader:

Ext.onReady(function()
{
	 Ext.QuickTips.init();
 
	// ----------------
	//	vars
	// ----------------
	var ds;	// datasource var
 
	// ----------------
	//	Create datasource
	// ----------------
	function createDS()
	{
		ds = new Ext.data.Store({
        	proxy: new Ext.data.HttpProxy({
            	url: 'search.php', // serverside script to post to
            	method: 'POST' // method of posting .. GET or POST .. I've used POST
        	}),
 
        	// the reader
            reader:  new Ext.data.JsonReader({
				totalProperty: 'total',
				root: 'results', // the object wich old the records
				id: 'id', // the fieldname wich hold the id ... optional
				fields: ['id','name','email','comments'] // the fields the reader need to render 
			})        	
 
		});				
	}
 
	createDS();	    
});

Creating the database

So, now we have a way to connect to the server, we also need a database so the serverside script can query it. Here's my import script:

--
-- Table structure for table 'people'
--
 
DROP TABLE IF EXISTS `people`;
CREATE TABLE IF NOT EXISTS `people` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  `email` varchar(255) NOT NULL,
  `comments` text NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=7 ;
 
--
-- Gegevens worden uitgevoerd voor tabel `people`
--
 
INSERT INTO `people` (`id`, `name`, `email`, `comments`) VALUES(1, 'George', 'apa@khabar.nl', 'Integer ligula tellus, egestas ac, gravida et, ultrices at, arcu. Praesent auctor diam nec dolor. Sed vehicula, libero a laoreet consequat, dui neque commodo odio, sit amet mattis sem nisi vitae tellus. Etiam et nunc. Mauris vel lorem. Praesent orci. Praesent diam ipsum, elementum nec, posuere vitae, semper eu, turpis. Cras viverra, turpis imperdiet vulputate commodo, ipsum mauris tempus quam, quis congue quam elit eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. Suspendisse at erat non lorem ullamcorper vestibulum.\r\n');
INSERT INTO `people` (`id`, `name`, `email`, `comments`) VALUES(2, 'Melanie', 'blaat@aap.nl', 'Suspendisse in purus at ligula facilisis accumsan. Phasellus a leo. Ut nonummy risus. Proin neque dolor, porttitor nec, iaculis sed, vehicula et, mauris. Nunc ornare aliquet metus. In accumsan laoreet velit. Vivamus mollis ultrices magna. Nam mattis varius diam. Pellentesque fermentum diam nec lacus. Morbi vel metus. Nulla semper, tortor ut rhoncus mollis, nibh diam auctor velit, in convallis augue purus feugiat purus. Praesent justo. Ut est. Mauris sem turpis, tristique ac, euismod et, porta sed, lacus. Integer diam quam, aliquet quis, aliquet sit amet, blandit eu, risus. Etiam facilisis tempus mauris. Nullam non quam. Quisque volutpat dui et libero.\r\n');
INSERT INTO `people` (`id`, `name`, `email`, `comments`) VALUES(3, 'Brad', 'bokke@geit.com', 'Suspendisse in purus at ligula facilisis accumsan. Phasellus a leo. Ut nonummy risus. Proin neque dolor, porttitor nec, iaculis sed, vehicula et, mauris. Nunc ornare aliquet metus. In accumsan laoreet velit. Vivamus mollis ultrices magna. Nam mattis varius diam. Pellentesque fermentum diam nec lacus. Morbi vel metus. Nulla semper, tortor ut rhoncus mollis, nibh diam auctor velit, in convallis augue purus feugiat purus. Praesent justo. Ut est. Mauris sem turpis, tristique ac, euismod et, porta sed, lacus. Integer diam quam, aliquet quis, aliquet sit amet, blandit eu, risus. Etiam facilisis tempus mauris. Nullam non quam. Quisque volutpat dui et libero.\r\n');
INSERT INTO `people` (`id`, `name`, `email`, `comments`) VALUES(4, 'Sandra', 'panne@koek.nl', 'Fusce risus mi, aliquet in, congue eu, lacinia eget, ante. Nulla a nibh ac dolor lobortis vehicula. Phasellus lorem lorem, sodales quis, tincidunt id, posuere ac, magna. Praesent fringilla massa faucibus sem. Duis aliquet scelerisque augue. Praesent libero risus, consectetuer et, dapibus eu, suscipit sit amet, tortor. Fusce enim lorem, pulvinar eget, congue quis, aliquet in, diam. Aenean laoreet sem at quam. Mauris adipiscing pharetra risus. Sed rutrum. Vivamus blandit, lorem ut pharetra interdum, elit justo pretium dolor, rutrum pellentesque tellus nisl eu lectus. Duis urna ipsum, dictum ac, faucibus et, scelerisque ut, est. Maecenas tempor ligula et pede. In at felis. Sed orci lacus, scelerisque at, aliquam nec, accumsan in, mauris. Maecenas id elit sed mi congue nonummy. Sed mauris nisl, faucibus et, porta sit amet, luctus in, augue. Phasellus ut ipsum in neque imperdiet rutrum.\r\n');
INSERT INTO `people` (`id`, `name`, `email`, `comments`) VALUES(5, 'Jasper', 'hoe@dan.com', 'Vestibulum aliquam ante ac tortor. Donec libero justo, facilisis ac, ultrices id, consequat ut, metus. Nunc tincidunt purus eu pede. Vivamus purus sapien, tincidunt a, vehicula et, mattis vel, diam. Etiam elementum, odio molestie placerat commodo, libero ligula ultricies lorem, et scelerisque est lorem id neque. Vestibulum nec erat vitae leo varius laoreet. Proin posuere nisl sed felis. Duis dui libero, blandit eu, nonummy sed, placerat aliquet, ipsum. Donec ornare nisl. Mauris eu orci eget enim pharetra dictum. Maecenas ultrices dictum massa. Donec egestas rutrum risus.\r\n');
INSERT INTO `people` (`id`, `name`, `email`, `comments`) VALUES(6, 'Jim', 'bijna@klaar.com', 'Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Quisque in metus. Mauris varius vulputate mauris. Ut pellentesque lectus nec velit. Etiam non ligula. Curabitur bibendum, tortor at facilisis fringilla, turpis libero interdum tellus, in bibendum risus enim feugiat velit. Etiam pellentesque adipiscing neque. Donec lectus. Fusce lorem nunc, sodales non, lobortis eu, interdum ac, nulla. In iaculis ipsum eu lorem. Integer quis dolor mollis erat dignissim gravida. Donec scelerisque dapibus lectus. Mauris neque erat, fringilla eu, faucibus nec, semper quis, sem. Duis lacinia sapien eu leo. Suspendisse enim sapien, consectetuer eu, dapibus eget, congue ut, purus. Quisque eget tellus. Mauris turpis. Morbi egestas dictum lectus. Praesent vehicula mi at arcu hendrerit blandit.\r\n');

Search script

And last but not least, the search script (search.php)

&lt;?php
	include("../connection.php"); // include the database connection files
	include("../JSON.php"); // my server doesn't have native JSON, so this class will help
 
 
	// basic function to search
	function search($query="")
	{
		$payload=array(); // payload array to convert to JSON
		$payload["total"]=0;
		$payload["results"]=array();
 
		$sql="";
 
		// do we have a empty search query yes or no?
		if(trim($query)!="")
		{
			// search on NAME
			$sql="SELECT * FROM people WHERE (name LIKE '%" . mysql_real_escape_string(trim($query)) . "%')";
 
			// we got results?
			if($rs=mysql_query($sql))
			{
				// yes we do
				$payload["total"]=mysql_num_rows($rs);
				while($data=mysql_fetch_assoc($rs))
				{
					// add each record to the "results" field
					$payload["results"][]=$data;
				}
			}
		}
 
		// setup up a JSON service
		$json = new Services_JSON();
 
		// return the converted payload array
		return $json->encode($payload);		
	}
 
 
 
	$searchResults=search(); // standard search
 
	// let's connect to the database
	if(connectDB("YOUR DATABASE NAME HERE"))
	{
		// is there a search query?
		if(isSet($_POST["query"]))
		{
			// yes there is a search query, so lets try and find the name
			$searchResults=search($_POST["query"]);			
		}
	}
 
	// print out the searchresults
	print $searchResults;
?&gt;

So at this point we have a working Datastore wich can send and recieve data.

Ok, let's create the grid.

The Grid

Lets start with a grid without any data.

var grid; // grid component
	var xg = Ext.grid;
 
	// ----------------
	//	Create grid
	// ----------------	
	function createGrid()
	{
	    grid = new xg.GridPanel(
	    {
 
	        // define the colomns to show
	        cm: new xg.ColumnModel(
		        [
		            {id:'name',header: "Name",  sortable: true, dataIndex: 'name'},
		            {id:'email',header: "E-mail",  sortable: true, dataIndex: 'email'}
		        ]
	        ),        
 
	        // make the columns fit
	        viewConfig: 
	        {
	            forceFit:true
	        },
 
			width:950,
 
			// yeah zebra stripes
	        stripeRows:true,
 
	        title:'Search results',
	        iconCls:'icon-grid',
	        renderTo: "gridWrapper",
	        autoHeight:true
 
	    });
	} 
 
	createGrid();

So now let's add the datastore

function createGrid()
{
    grid = new xg.GridPanel(
    {
        store: ds, // use the datasource
 
        cm: new xg.ColumnModel(
	        [
	            {id:'name',header: "Name",  sortable: true, dataIndex: 'name'},
	            {id:'email',header: "E-mail",  sortable: true, dataIndex: 'email'}
	        ]
        ),        
 
        viewConfig: 
        {
            forceFit:true
        },
 
	width:950,
        stripeRows:true,
        title:'Search results',
        iconCls:'icon-grid',
        renderTo: "gridWrapper",
        autoHeight:true
 
    });
}

Notice the line

store: ds

This is the reference to the datastore. Also when you look at

dataIndex: 'name'

This is how the Grid know wich data (provided by the datastore and mapped by the reader) belongs to what column. So 'name' should be avaible in the reader.

Row expander

Now when you look at the live example [1] then you will notice that each row can be expanded. This is a nice little feature called rowexpander. The best thing about the row expander is that you can use a template to define the expander :

// ----------------
//	Create expander
// ----------------	
var expander = new xg.RowExpander({
    tpl : new Ext.Template(
        '<p>{comments}</p>'
    )
});

The row expander has access to the data the datastore provided and you can access it by putting the fieldname between brackets.

And now we put the expander in the grid:

function createGrid()
{
    grid = new xg.GridPanel(
    {
        store: ds, // use the datasource
 
        cm: new xg.ColumnModel(
	        [
	        	expander,
	            {id:'name',header: "Name",  sortable: true, dataIndex: 'name'},
	            {id:'email',header: "E-mail",  sortable: true, dataIndex: 'email'}
	        ]
        ),        
 
        viewConfig: 
        {
            forceFit:true
        },
 
        // add the expander        
        plugins: expander,
		collapsible: true,
		animCollapse: false,
 
		width:950,
        stripeRows:true,
        title:'Search results',
        iconCls:'icon-grid',
        renderTo: "gridWrapper",
        autoHeight:true
    });
}

Now we have linked the grid to a datastore, but it's still empty because the datastore never retrieves data.

So up to the next chapter, adding the livesearch.

Form

Let's start with a form without any interaction.

var simple;
 
// ----------------
//	Create searchform
// ----------------	
function createSearchForm()
{
	simple = new Ext.FormPanel({
	    width:950,
        labelWidth: 75,
        frame:true,
        title: 'Live search',
        bodyStyle:'padding:5px 5px 0',
        defaults: {width: 230},
        defaultType: 'textfield',
 
        items: [{
                fieldLabel: 'Search',
                name: 'query',
                allowBlank:true  		                            
            }
        ]	
	});
 
	// render the form in the div #searchWrapper
    simple.render("searchWrapper");	
}

Nobody's listening?

Now we want to add listeners to the formfield, because we want to send the formfield value to the server-side script every time we press a key. There's only one problem: there are no listeners attached to a form field (why I don't know). So we need to add those listeners manually.

// ----------------
//	Set listeners on form fields
// ----------------	
Ext.override(Ext.form.Field, {
    fireKey : function(e) {
        if(((Ext.isIE && e.type == 'keydown') || e.type == 'keypress') && e.isSpecialKey()) {
            this.fireEvent('specialkey', this, e);
        }
        else {
            this.fireEvent(e.type, this, e);
        }
    }
  , initEvents : function() {
        this.el.on("focus", this.onFocus,  this);
        this.el.on("blur", this.onBlur,  this);
        this.el.on("keydown", this.fireKey, this);
        this.el.on("keypress", this.fireKey, this);
        this.el.on("keyup", this.fireKey, this);
        this.originalValue = this.getValue();
    }
});

So now that every formfield has listeners we can use:

function createSearchForm()
{
	simple = new Ext.FormPanel({
	    width:950,
        labelWidth: 75,
        frame:true,
        title: 'Live search',
        bodyStyle:'padding:5px 5px 0',
        defaults: {width: 230},
        defaultType: 'textfield',
 
        items: [{
                fieldLabel: 'Search',
                name: 'query',
                allowBlank:true,
 
                // now ad listener
				listeners: 
				{
					// call this function on event keyup
					keyup: function(el,type)
					{
						var theQuery=el.getValue();
					}				
				}
            }
        ]	
	});
 
    simple.render("searchWrapper");	
}

So every time we press something while the focus is on the formfield, the 'keyup' event is fired and our function has 2 arguments: el and type. The 'el' argument holds the object that fired the keyup event. So with el.getValue() we extract the current value in the formfield.

Beam me up Scotty

Now we need to pass that value to the Datastore so it can send it to the server-side script.

To make the datastore setup a HTTP request to the server-side script we need to fire the load() function with the right params for example:

ds.load(
{
	params: 
	{	
		key:value
	}
});

Now we implement it in the formfield:

function createSearchForm()
	{
    	simple = new Ext.FormPanel({
		    width:950,
	        labelWidth: 75,
	        frame:true,
	        title: 'Live search',
	        bodyStyle:'padding:5px 5px 0',
	        defaults: {width: 230},
	        defaultType: 'textfield',
 
	        items: [{
	                fieldLabel: 'Search',
	                name: 'query',
	                allowBlank:true,
 
					listeners: 
					{
						keyup: function(el,type)
						{
							var theQuery=el.getValue();
 
							// set up the datastore to make a request with the params
							ds.load(
							{
								params: 
								{	
									query: theQuery
								}
							});
 
						}				
					}
	            }
	        ]	
    	});
 
	    simple.render("searchWrapper");	
	}

That's a wrap

So there you have it, the formfield has a listener to pass the value to the Datastore. The Datastore will setup a HTTP request to the server-side script. The server-side script will handle the request and return a JSON string. The reader will extract the rows and convert them into objects. The grid will get the objects from the reader and display the rows. And a livesearch is born.


So in short this was my tutorial, I hope it was useful to you.

Here's my complete JS code again:

/*
 * Ext JS Library 2.0.1
 * Copyright(c) 2006-2008, Ext JS, LLC.
 * licensing@extjs.com
 * 
 * http://extjs.com/license
 */
 
Ext.onReady(function()
{
 
	// ----------------
	//	Set listeners on form fields
	// ----------------	
    Ext.override(Ext.form.Field, {
        fireKey : function(e) {
            if(((Ext.isIE && e.type == 'keydown') || e.type == 'keypress') && e.isSpecialKey()) {
                this.fireEvent('specialkey', this, e);
            }
            else {
                this.fireEvent(e.type, this, e);
            }
        }
      , initEvents : function() {
            this.el.on("focus", this.onFocus,  this);
            this.el.on("blur", this.onBlur,  this);
            this.el.on("keydown", this.fireKey, this);
            this.el.on("keypress", this.fireKey, this);
            this.el.on("keyup", this.fireKey, this);
            this.originalValue = this.getValue();
        }
    });
 
 
	 Ext.QuickTips.init();
 
	// ----------------
	//	vars
	// ----------------
	var ds;	// datasource
	var grid; // grid component
	var xg = Ext.grid;
	var expander;
	var simple;
 
	// ----------------
	//	Create searchform
	// ----------------	
	function createSearchForm()
	{
    	simple = new Ext.FormPanel({
		    width:950,
	        labelWidth: 75,
	        frame:true,
	        title: 'Live search',
	        bodyStyle:'padding:5px 5px 0',
	        defaults: {width: 230},
	        defaultType: 'textfield',
 
	        items: [{
	                fieldLabel: 'Search',
	                name: 'query',
	                allowBlank:true,
 
					listeners: 
					{
						keyup: function(el,type)
						{
							var theQuery=el.getValue();
 
							ds.load(
							{
								params: 
								{	
									query: theQuery
								}
							});
 
						}				
					}
	            }
	        ]	
    	});
 
	    simple.render("searchWrapper");	
	}	
 
	// ----------------
	//	Create datasource
	// ----------------
	function createDS()
	{
		ds = new Ext.data.Store({
            proxy: new Ext.data.HttpProxy({
                url: 'search.php',
                method: 'POST'
            }),                        
 
            reader:  new Ext.data.JsonReader({
				totalProperty: 'total',
				root: 'results',
				id: 'id',
				fields: ['id','name','email','comments']
			})					    
 
		});				
	}
 
 
	// ----------------
	//	Create expander
	// ----------------	
    var expander = new xg.RowExpander({
        tpl : new Ext.Template(
            '<p>{comments}</p>'
        )
    });	
 
 
 
	// ----------------
	//	Create grid
	// ----------------	
	function createGrid()
	{
	    grid = new xg.GridPanel(
	    {
	        store: ds, // use the datasource
 
	        cm: new xg.ColumnModel(
		        [
		        	expander,
		            {id:'name',header: "Name",  sortable: true, dataIndex: 'name'},
		            {id:'email',header: "E-mail",  sortable: true, dataIndex: 'email'}
		        ]
	        ),        
 
	        viewConfig: 
	        {
	            forceFit:true
	        },
 
	        plugins: expander,
			collapsible: true,
			animCollapse: false,
 
			width:950,
	        stripeRows:true,
	        title:'Search results',
	        iconCls:'icon-grid',
	        renderTo: "gridWrapper",
	        autoHeight:true
 
	    });
	} 
 
 
	createDS();
	createSearchForm();
    createGrid();	
 
});
Share this post:
Leave a reply

Written by Fransjo Leihitu

1 Comment

Isabella

3 years ago

Gosh, I wish I would have had that information elriaer!

Leave a comment:

Commenting is not available in this channel entry.