Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / docs / guides / direct_grid_pt1 / README.md
1 Mapping a Grid to a MySQL table using Direct and PHP Part 1
2 ===========================================================
3
4 I. Introduction
5 ---------------
6 In this tutorial we will be looking at how to build a table, or 'grid', that receives its data from a MySQL database. It's aimed at people who have some familiarity with JavaScript, PHP and MySQL but are new to the Ext JS framework. By the end of the tutorial, we'll have a grid component that looks like this:
7
8 {@img grid-full.png The finished product}
9
10 II. Getting Started
11 -------------------
12 ### 1.1 Requirements
13
14 You will need:
15
16 *   A server with PHP (5.3+) and MySQL (4.1.3+) installed
17 *   A browser compatible with Ext JS 4
18 *   A text editor
19
20 ### 1.2 What is Ext Grid?
21 A grid in Ext JS is "essentially a supercharged `<table>`" to quote [its documentation](#!/api/Ext.grid.Panel). It allows you to manipulate data by sorting and filtering, and to fetch new data in, so it's much more dynamic than your run-of-the-mill table. As you can imagine, this allows you to do some pretty cool things.
22
23 ### 1.3 What is Ext Direct?
24 Ext Direct provides a way to communicate between the browser and server using less code than traditional methods (i.e. PHP) to actually _do_ stuff with your data.
25
26 ### 1.4 What's the Benefit of Doing This?
27 There are a number of benefits to using Ext Direct to handle your data:
28
29  - It's platform agnostic, so it doesn't matter whether you're using PHP, Java or C\# to serve the data.
30  - You can serve _as much_ data as you want, with no negative client-side impacts.
31  - It has [3 types of 'providers'](#!/api/Ext.direct.Manager), that communicate with the server in different ways, we will be using the `RemotingProvider`.
32  - It can bundle your AJAX requests into a single request (by default, all those sent in the first 10ms) and so the server only has to send back one response.
33
34 Now that we've all been persuaded, lets get to building it.
35
36 III. Setting Up
37 ---------------
38 Following the best practices for an Ext application highlighted in the [getting started guide](#/guide/getting_started), we can set up a skeleton directory structure with an index.html file and a blank JavaScript file called grid.js.
39
40 index.html
41
42     <!DOCTYPE html>
43     <html>
44     <head>
45         <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
46         <title>List of Velociraptor Owners</title>
47         <!--Ext JS-->
48         <link rel="stylesheet" type="text/css" href="resources/css/ext-all.css">
49         <script src="extjs/ext-all-debug.js"></script>
50         <!--Application JS-->
51         <script src="grid.js"></script>
52         <script src="php/api.php"></script>
53     </head>
54     <body>
55     </body>
56     </html>
57
58 Because we're using the HTML5 document type we're allowed to omit the type in a script tag. It assumes that all `<script>` tags will be JavaScript which helps cut down our bytes. However, you've probably also noticed the peculiar api.php file, surely that can't be JavaScript? All will be explained.
59
60 Now that the index is pointing to all the right places, unzip your copy of Ext 4 into a folder called 'extjs'. We are now ready to start building the application.
61
62 IV. Writing the Application
63 ---------------------------
64 We'll start by writing the JavaScript portion to give us something to look at when we start trying to debug the PHP side of the app. Within grid.js we first want to declare what parts of the Ext framework we'll be dealing with, this will probably be a familiar process to ActionScript and Java users but for the rest of us, it's very simple. Because I've seen into the future, I know that we'll be using Ext Direct, Ext Data and Ext Grid, to display the data so we require the following:
65
66 ### `grid.js`
67
68     Ext.require([
69         'Ext.direct.*',
70         'Ext.data.*',
71         'Ext.grid.*'
72     ]);
73
74 The asterisk ('`*`') in this context loads all of the classes within those areas of Ext JS, we could optimize it at the end by only requiring the classes that we use. We then want to make a pretty grid to look at, but first, a slight digression.
75
76 ### 4.1 Models and Stores, An Overview
77 (You can skip this section if you're already familiar with the concept of models and stores)
78
79 Models and stores are key to presenting users with dynamic data. A 'model' is a blueprint of what a store will look like. Say you have a menu of beers, the model would define what headings to expect, in this case: type (ale, stout, etc.), name, price, and ABV (alcohol by volume). The 'store' will then contain the individual properties, so, Type: 'Ale', Name: 'Jewel', Price: $4.00, ABV: 5.0%. Stores can be represented in many ways and come from many sources but ultimately end up being converted to JSON for use with Ext.
80
81 ### 4.2 Back to the App
82
83 To create a model we write the following:
84
85 ### `grid.js`
86
87     Ext.define('PersonalInfo', {
88         extend: 'Ext.data.Model',
89         fields: ['id', 'name', 'address', 'state']
90     });
91
92 What we've done here is give it a name (PersonalInfo), told it that this _extends_ Ext.data.Model, (thankfully we don't need to write all of the necessary code to get a model working, we simply tell it that this extends what the Ext JS framework already provides) and told it what fields (headings) we're going to present to it. All exciting stuff, I'm sure you'll agree.
93
94 Now, we don't want the JavaScript that renders the grid to initiate before the framework has loaded, this is increasingly important with browsers running JavaScript at near-native speeds. To get around this, we want to use `Ext.onReady`, this will wait for Ext to be fully loaded and the DOM to be fully initialized before we start trying to put our grid on it.
95
96 ### `grid.js`
97
98     Ext.onReady(function() {
99         // Create the Grid
100         Ext.create('Ext.grid.Panel', {
101             store: {
102                 model: 'PersonalInfo',
103                 autoLoad: true,
104                 proxy: {
105                     type: 'direct',
106                 }
107             },
108             columns: [{
109                 dataIndex: 'id',
110                 width: 50,
111                 text: 'ID'
112             }],
113             height: 450,
114             width: 700,
115             title: 'Velociraptor Owners',
116             renderTo: Ext.getBody()
117         });
118     });
119
120 Once the DOM is ready we use `Ext.create` to make a new grid. A grid requires a store, otherwise it won't have a purpose. We will give it a store that uses the model we defined earlier with the name of 'PersonalInfo' and use the proxy type `direct` to tell it that we'll be using Ext Direct. A proxy tells the application how we'll be communicating with the store. There are many different types which you can find more information [here](#!/api/Ext.data.proxy.Proxy).
121
122 We then gave the grid a single column (wrapped in an array as we'll be adding more later) with the properties of width and text. The only part that may be unfamiliar here is `dataIndex` - this is what binds the column with the store, so it has to have the same name. After that, everything should be self-explanatory apart from `renderTo: Ext.getBody()`. This is a function that gets the body of the document and will attach the grid to it. Remember that we wrap it all in the `onReady` function? That is so that we don't try to attach it to `<body>` before `<body>` exists.
123
124 Hopefully, your efforts will be rewarded with this when you refresh the page:
125
126 {@img grid-bare.png The grid laid bare}
127
128 ### 4.3 Working with MySQL
129 Now that we have a basic grid working, we'll move on to serving up some data. For our example, we'll be listing everyone that owns a Velociraptor in the USA. You'd expect this to be a fairly small dataset - it's not. [Download and execute this .sql file](guides/direct_grid_pt1/grid-tutorial.sql) and you'll know who to steer clear of. Disclaimer, all of this data has been automatically generated by a dummy data script, any correlations with reality is purely coincidental.
130
131 If all went well, you should now have a MySQL table populated with 1,000 records which we'll display in our grid.
132
133 In the root directory of our app, create a folder called `php` followed by another one inside it called `classes`. Within classes create a file called `QueryDatabase.php`.
134
135 We'll be taking advantage of PHP's MySQLi extension which works with MySQL 4.1.3 and above (any version released after mid-2004 will work fine).
136
137 First, we'll make a new class and declare some variables:
138
139 ### `QueryDatabase.php`
140
141     <?php
142     class QueryDatabase
143     {
144         private $_db;
145         protected $_result;
146         public $results;
147
148     }
149
150 _Within_ this class, we want to make a function that will connect to the database, (note that you don't write the ..., it's to denote that this block of code continues on from the last one).
151
152 ### `QueryDatabase.php`
153
154     ...
155     public function __construct()
156     {
157         $_db = new mysqli('host', 'username' ,'password', 'database');
158
159         if ($_db->connect_error) {
160             die('Connection Error (' . $_db->connect_errno . ') ' . $_db->connect_error);
161         }
162
163         return $_db;
164     }
165
166 On line 10, replace 'hostname', 'username', 'password' and 'database' with your own configuration. If this all looks a little alien to you, yet you're used to PHP, it uses a style called 'object-oriented' programming, you can [read more about it online](https://encrypted.google.com/search?q=object+oriented+php). The `->` is called an arrow operator and gets a method (aka a function) from that object. So we're calling the `connect_error` and `connect_errno` functions from the `mysqli` object in this script with the arrow operators.
167
168 We also want to close the database connection once we're done with it which is simply enough done with:
169
170 ### `QueryDatabase.php`
171
172     ...
173     public function __destruct()
174     {
175         $_db = $this->__construct();
176         $_db->close();
177
178         return $this;
179     }
180
181 Notice in the parenthesis we've put `$_db`? This means that this function is going to expect a parameter passed to it, i.e. it's expecting `$_db` otherwise it'll have nothing to close.
182
183 Now we've got a connection to our database opening and closing we can query it. To do this, we'll create a new function called getResults.
184
185 ### `QueryDatabase.php`
186
187     ...
188     public function getResults($params)
189     {
190         $_db = $this->openConnection();
191
192         $_result = $_db->query("SELECT id, name, address, state FROM owners") or die('Connect Error (' . $_db->connect_errno . ') ' . $_db->connect_error);
193
194         $results = array();
195
196         while ($row = $_result->fetch_assoc()) {
197             array_push($results, $row);
198         }
199
200         $this->closeConnection($_db);
201
202         return $results;
203     }
204
205 That's all for our first PHP file. To recap, we declared some variables at the top of the class and then made 3 functions that will help us as we expand our application. The first function defines the database to use with the credentials needed to access it and fails if it cannot connect (hopefully providing a detailed error message). The second is a simple function that closes the database connection.
206
207 The third function uses the first function to open a connection and queries the database for all of the records from the fields: 'id', 'name', 'address' and 'state'. We could have used the wildcard operator (`*`) to do the same, but in larger tables you'll probably only want to reveal a subset of fields so it's better to specify them individually. We then push all of the results into a an array called `$results` in a while statement, close the connection to the database once we're done and return the results.
208
209 ### 4.4 The Complicated Bit
210 Going up a level to the php directory, create a new file called config.php and write the following:
211
212 ### `config.php`
213
214     <?php
215     $API = array(
216         'QueryDatabase'=>array(
217             'methods'=>array(
218                 'getResults'=>array(
219                     'len'=>1
220                 ),
221             )
222         )
223     );
224
225 This exposes what methods (functions) are available to our Ext application to call on the server. At the moment, there's only one that we want to reveal, the 'getResults' method we just created. That's all there is to our config.php file for now.
226
227 To make sure the correct methods are called, we need a router. The router is where the calls from Ext Direct get routed to the correct class using a Remote Procedure Call (RPC).
228
229 ### `router.php`
230
231     <?php
232     require('config.php');
233
234     class Action {
235         public $action;
236         public $method;
237         public $data;
238         public $tid;
239     }
240
241 Here, we've declared a class and required our config file that contains which methods we expose in our API.
242
243 ### `router.php`
244
245     ...
246     $isForm = false;
247     $isUpload = false;
248     if(isset($HTTP_RAW_POST_DATA)) {
249         header('Content-Type: text/javascript');
250         $data = json_decode($HTTP_RAW_POST_DATA);
251     } else if (isset($_POST['extAction'])) { // form post
252         $isForm = true;
253         $isUpload = $_POST['extUpload'] == 'true';
254         $data = new Action();
255         $data->action = $_POST['extAction'];
256         $data->method = $_POST['extMethod'];
257         $data->tid = isset($_POST['extTID']) ? $_POST['extTID'] : null; // not set for upload
258         $data->data = array($_POST, $_FILES);
259     } else {
260         die('Invalid request.');
261     }
262
263     function doRpc($cdata){
264         global $API;
265         try {
266             if(!isset($API[$cdata->action])){
267                 throw new Exception('Call to undefined action: ' . $cdata->action);
268             }
269
270             $action = $cdata->action;
271             $a = $API[$action];
272
273             doAroundCalls($a['before'], $cdata);
274
275             $method = $cdata->method;
276             $mdef = $a['methods'][$method];
277             if(!$mdef){
278                 throw new Exception("Call to undefined method: $method on action $action");
279             }
280             doAroundCalls($mdef['before'], $cdata);
281
282             $r = array(
283                 'type'=>'rpc',
284                 'tid'=>$cdata->tid,
285                 'action'=>$action,
286                 'method'=>$method
287             );
288
289             require_once("classes/$action.php");
290             $o = new $action();
291             if (isset($mdef['len'])) {
292                 $params = isset($cdata->data) && is_array($cdata->data) ? $cdata->data : array();
293             } else {
294                 $params = array($cdata->data);
295             }
296
297             $r['result'] = call_user_func_array(array($o, $method), $params);
298
299             doAroundCalls($mdef['after'], $cdata, $r);
300             doAroundCalls($a['after'], $cdata, $r);
301         }
302         catch(Exception $e){
303             $r['type'] = 'exception';
304             $r['message'] = $e->getMessage();
305             $r['where'] = $e->getTraceAsString();
306         }
307         return $r;
308     }
309
310 The doRpc function will provide important information on our data and responses from the server. Basically, if you refresh the page and have a console open you'll see something that looks like this:
311
312 {@img firebug-post-result.png Sending a request to router.php and getting a response}
313
314 You can see the results of our $r variable clearly laid out. If you've made an error the result is where the PHP error text will be, but when everything's gone to plan you'll see all of the records that we have added to our database stored as JSON. The PHP that converts it to JSON is:
315
316     ...
317     function doAroundCalls(&$fns, &$cdata, &$returnData=null){
318         if(!$fns){
319             return;
320         }
321         if(is_array($fns)){
322             foreach($fns as $f){
323                 $f($cdata, $returnData);
324             }
325         }else{
326             $fns($cdata, $returnData);
327         }
328     }
329
330     $response = null;
331     if (is_array($data)) {
332         $response = array();
333         foreach($data as $d){
334             $response[] = doRpc($d);
335         }
336     } else {
337         $response = doRpc($data);
338     }
339     if ($isForm && $isUpload) {
340         echo '<html><body><textarea>';
341         echo json_encode($response);
342         echo '</textarea></body></html>';
343     } else {
344         echo json_encode($response);
345     }
346
347 Then create a file called 'api.php'. Remember when we pointed our index.html file to a PHP file but told it that it was JavaScript? This is where the magic happens.
348
349 ### `api.php`
350
351     <?php
352     require('config.php');
353     header('Content-Type: text/javascript');
354
355     // convert API config to Ext.Direct spec
356     $actions = array();
357     foreach ($API as $aname=>&$a) {
358         $methods = array();
359         foreach ($a['methods'] as $mname=>&$m) {
360             if (isset($m['len'])) {
361                 $md = array(
362                     'name'=>$mname,
363                     'len'=>$m['len']
364                 );
365             } else {
366                 $md = array(
367                     'name'=>$mname,
368                     'params'=>$m['params']
369                 );
370             }
371             if (isset($m['formHandler']) && $m['formHandler']) {
372                 $md['formHandler'] = true;
373             }
374             $methods[] = $md;
375         }
376         $actions[$aname] = $methods;
377     }
378
379     $cfg = array(
380         'url'=>'php/router.php',
381         'type'=>'remoting',
382         'actions'=>$actions
383     );
384
385     echo 'Ext.ns("Ext.app"); Ext.app.REMOTING_API = ';
386
387     echo json_encode($cfg);
388     echo ';';
389
390 The last two files are taken straight from the example in the Ext Direct directory, made by people much smarter than I (hence the sparse comments).
391
392 It uses the config.php file we made earlier and sets it's header to JavaScript, so any output the browser will expect to be JavaScript. It then proceeds to turn our config and router PHP files into JSON so the right method is called when Ext Direct calls it. Further information can be found on the [Ext Direct specification page](http://www.sencha.com/products/extjs/extdirect).
393
394 ### 4.5 Get it Together
395 With the hard part now over, the final bits to finish our application are found back in grid.js. We need to tell the proxy what function to call to get the results, tell Ext Direct what type of provider we're using and add the other columns to our grid.
396
397 To accomplish this, add the following to the relevant parts of grid.js.
398
399 ### `grid.js`
400
401     Ext.onReady(function() {
402         //add a provider to our grid
403         Ext.direct.Manager.addProvider(Ext.app.REMOTING_API);
404         ...
405
406         //add directFn to our proxy
407         proxy: {
408             type: 'direct',
409             directFn: QueryDatabase.getResults
410         },
411
412         //add the other columns
413         columns: [{
414             dataIndex: 'id',
415             width: 50,
416             text: 'ID'
417         }, {
418             dataIndex: 'name',
419             flex: 1,
420             text: 'Name'
421         }, {
422             dataIndex: 'address',
423             flex: 1.3,
424             text: 'Address'
425         }, {
426             dataIndex: 'state',
427             flex: 1,
428             text: 'State'
429         }],
430
431 We finally tell our application to use the Remoting Provider and `directFn` calls `getResults` as soon as it's run to add our data to the grid.
432
433 The columns are largely the same as we did initially apart from `flex`. This dynamically sizes the field relative to the others so a `flex: 1.3` will be slightly larger than `flex: 1` and, together, fill all of the remaining space left over by our fixed width id column.
434
435 Refresh your browser and you should have a fully populated grid. If you hover over or click any of the headings you will see that you are able to dynamically sort by any of the fields.
436
437 {@img grid-full.png The completed grid in all of it's glory}
438
439 V. Conclusion
440 -------------
441 In this tutorial, we've learnt the basics of how to utilize Ext Direct while getting experience with how to create Ext grids as well as writing some pretty advanced PHP. Take some time to experiment with other configuration options by [looking at the documentation](#!/api) and getting a feel for what can be achieved and what customizations can be made.
442
443 For reference, [here are the working source files](guides/direct_grid_pt1/reference-files.zip).
444
445 In the next tutorial, we'll harness a bit more of Ext Direct's power to run server-side functions to create, update and delete from our MySQL database building on top of our current work.