ExtJS Datagrid with CakePHP
Note: I’m in the process of updating this post based on some things I learned here.
To use the extjs datagrid with cake (I’m using 1.2 pre-beta), first you need to download extjs and put it in /webroot/js. I put mine in /webroot/js/ext-1.1/. Now add the following lines to your default layout:
1 2 3 | <?php echo $javascript->link('ext-1.1/adapter/ext/ext-base'); ?> <?php echo $javascript->link('ext-1.1/ext-all-debug'); ?> <?php echo $javascript->link('myScript'); ?> |
Line 1 tells extjs that you are using extjs exclusively. You can also use jquery or yui.
Line 2 loads the extjs library. I used ext-all-debug for development purposes.
Line 3 loads my javascript file. I called it myScript.js and put it in /webroot/js.
Just before the tag, add this line:
1 | <link rel='StyleSheet' href='<?php echo $this->base ?>/js/ext-1.1/resources/css/ext-all.css' /> |
This adds a link to extjs’s css file. This is kind of tricky in cake, since cake expects all css files to be in /webroot/css. You could copy the file there, then just use the html helper to link to the file. I didn’t do that because I want to be able to upgrade my extjs files, and I was afraid I’d forget to copy the new css file.
Put this code in the body of your layout:
1 2 3 | <div id="grid-panel"> <div id="grid-paging" style="border:1px solid #99bbe8;overflow:hidden; width: 665px; height: 00px;"></div> </div> |
Your grid-panel div will hold your datagrid. The grid-paging div will hold the paging bar if you want one (we won’t use it here).
I’m creating a grid to keep track of users, so I made a model called User and a controller called Users. Your Users controller should look something like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | <?php class UsersController extends AppController { var $name = 'Users'; var $helpers = array('Html', 'Form', 'Javascript'); function index() { //blank index method. We'll load data with a json call from myScript.js. } function getAllUsers() { $userArray = array(); //this will hold our data from the database. $this->layout = 'Ajax'; //this tells the controller to use the Ajax layout instead of the default layout. $count = $this->User->findCount(); //counts the number of records in User. $userA = $this->User->findAll(null,'*','last_name ASC'); //gets all the User records and sorts them by last name. /* ************** Use this code if you are using cake 1.1.xx ************************* foreach($userA as $userB) { $temp = array(); foreach($userB['User'] as $key=>$value) { $temp[$key] = $value; } $userArray[] = $temp; } */ $userArray = Set::extract($userA, '{n}.User'); // use this code if you are using cake 1.2.xx $this->set('users','{"total":'.$count.',"users":'.json_encode($userArray).'}'); } } ?> |
Notice that index is blank. We’ll use index as our default controller, and our javascript will call getAllUsers() to fill our grid with json data. If you have ever debugged a cake array, you’ll notice that
$userA = $this->User->findAll(null,'*','last_name ASC');
will return something like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | $users = array ( 0 => array ( 'User' => array ( 'id' => 1, 'last_name' => 'Jones', 'first_name' => 'Paul' ) ), 1 => array ( 'User' => array ( 'id' => 2, 'last_name' => 'Smith', 'first_name' => 'Bill' ) ) ) |
What we really need is an array that looks more like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | $users = array ( 0 => array ( 'id' => 1, 'last_name' => 'Jones', 'first_name' => 'Paul' ), 1 => array ( 'id' => 2, 'last_name' => 'Smith', 'first_name' => 'Bill' ) ) |
In cake 1.2, you can use
1 | $userArray = Set::extract($userA, '{n}.User'); |
to extract all the ‘user’ arrays and put them into userArray. If you are not using cake 1.2, you have to use a for loop. I did things this way to start with, and just commented it out when I learned how to use Set::extract.
There are several different ways to take our database info and turn it into json data. I use php5 and have enabled json, so I used this in the controller:
1 | $this->set('users','{"total":'.$count.',"users":'.json_encode($userArray).'}'); |
In the view, put this:
1 | <?php echo $users; ?> |
You can also do this the cake way (not sure if it works in 1.1). In the controller, put this in place of the above line:
1 2 | $this->set('total', $count); $this->set('users',$userArray); |
In the view, put this:
1 | <?php echo '{"total":'.$total.', "users":'.$javascript->Object($users).'}'; ?> |
This will work with php4 or php5.
Now for the fun stuff. In myScript.js, put the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | Ext.onReady(function(){ /****** ****** host holds the name of my host computer. I did this because of restrictions on making remote data requests. If the ****** host is localhost, things look great from my development machine. If I try to view my page from another computer, ****** though, I get errors and nothing happens. I set my host to tc-mark-lap (the name of my development computer). If I ****** view my page with localhost in the url, I get an error. If I view it with tc-mark-lap, everything works. This way, ****** I can view pages from my laptop or from a remote machine. I'll have to remember to change this when I deploy to my ****** production server, though. I might be able to set this in my controller or in a config table and use the javascript ****** helper to set it at runtime. I'll have to check on that sometime. ******/ var host = 'tc-mark-lap'; //This is the datastore. My json data will be loaded remotely, then stored in the datastore. //I used the HttpProxy because I'm accessing data on my local machine //In my json reader, I define the fields I should get from my json data (id, last_name, first_name, etc. var ds = new Ext.data.Store({ proxy: new Ext.data.HttpProxy({url: 'http://'+host+'/ekklesia3/users/getAllUsers'}), //note that I used host in the url reader: new Ext.data.JsonReader({ root: 'users', totalProperty: 'total', id: 'id' },[ 'id','last_name', 'first_name', 'address1', 'address2', 'city', 'state', 'zip', 'homePhone', 'cellPhone', 'otherPhone', 'birthdate', 'grade', 'school_id', 'school']) }); //This is the column model. This defines the columns in my datagrid. //It also maps each column with the appropriate json data from my database (dataIndex). var cm = new Ext.grid.ColumnModel([ {id: 'grid-paging', header: "ID",dataIndex: 'id', width: 100, hidden: true}, {header: "Last Name", dataIndex: 'last_name', width: 100}, {header: "First Name", dataIndex: 'first_name', width: 100}, {header: "Address1", dataIndex: 'address1', width: 100}, {header: "Address2", dataIndex: 'address2', width: 100}, {header: "City", dataIndex: 'city', width: 100}, {header: "State", dataIndex: 'state', width: 100}, {header: "Zip", dataIndex: 'zip', width: 100} ]); //Here's where we define our datagrid. Notice 'grid-paging' - //that's the div in our layout that we want to render the grid to. //We also have to specify our dataStore and our columnModel. //I have no idea what selModel and enableColLock do :-) var grid = new Ext.grid.EditorGrid('grid-paging', { ds: ds, cm: cm, selModel: new Ext.grid.RowSelectionModel({singleSelect:false}), enableColLock:false }); //This sets a layout for our gridpanel div. I'll try to do a tutorial on //layouts some other time. var layout = Ext.BorderLayout.create({ center: { margins:{left:2,top:3,right:2,bottom:3}, panels: [new Ext.GridPanel(grid)] } }, 'grid-panel'); grid.render(); //This renders our grid ds.load(); //This loads data from the database into the datastore. }) |
The comments should explain everything here. This is more of a “Don’t ask questions, it just works” example, since I’m not quite sure how all the extjs stuff works myself at the moment.
Anyway, we now have a working datagrid. I hope that helps someone :-)
hydra12