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)
<?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; ?>
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(); });

1 Comment
Isabella
2 years agoGosh, I wish I would have had that information elriaer!
Leave a comment:
Commenting is not available in this channel entry.