Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / docs / guides / direct_grid_pt2 / README.md
1 Mapping a Grid to a MySQL table using Direct and PHP Part 2
2 ===========================================================
3
4 I. Introduction
5 ---------------
6 In [the last tutorial](#!/guide/direct_grid_pt1) we created a grid that pulled information from a MySQL database using Ext Direct. Through the power and simplicity of grids we created what was essentially a glorified table (albeit 'turbocharged'). To add to the dynamism that grids present to us we'll be adding CRUD (Create, Read, Update, Delete) capabilities as well. A typical scenario that benefits from this is backend interfaces where a client might want to do anything from update someone's address or rename a blog post. We've already got it reading from a database so we're a quarter of the way there!
7
8 By the end of this tutorial, we should have something that looks like this.
9
10 {@img grid-full.png The final product}
11
12 II. Getting Started
13 -------------------
14
15 ### 2.1 Requirements
16
17 You will need:
18
19 *   A server with PHP (5.3+) and MySQL (4.1.3+) installed
20 *   A browser compatible with Ext JS 4 and debugging tools
21 *   A text editor
22
23 Personally, I find Firefox with Firebug best for debugging Ext.
24
25 It's highly recommended that you complete Part 1 before embarking on this tutorial to fully understand what we're doing. The base files that we'll be following on from [can be found here](resources/start.zip).
26
27 III. Writing the Application
28 ----------------------------
29
30 ### 3.1 API
31
32 Carrying on from our last tutorial in this series, we wrote a file called grid.js that housed the JavaScript portion of our Ext application. Now, if we're going to move forward to this brave new world of CRUDing we need to make some modifications.
33
34 Previously, we defined the proxy in the store but to save records individually, as we'll be doing, it makes more sense to have it defined in our model, `PersonalInfo`. Remove the `proxy` configuration inside `store` and we'll replace the single Direct function (getting the results) with four functions that will create, read, update and delete. To do this, the following is needed:
35
36 ### `grid.js`
37
38     ...
39     proxy: {
40         type: 'direct',
41         api: {
42             create: QueryDatabase.createRecord,
43             read: QueryDatabase.getResults,
44             update: QueryDatabase.updateRecords,
45             destroy: QueryDatabase.destroyRecord
46         }
47     },
48
49 So there should no longer be a `directFn`, it's been replaced by a more robust API. Writing this tells it that it's looking for a class called QueryDatabase and the method name (e.g. `createRecord`).
50
51 While we're editing the model, a few of you may have noticed in the last tutorial that the ID field, when sorted, displayed some strange behavior. We didn't set a type to the ID so it assumed that it was a string which caused it to not sort sequentially. This can be fixed by adding a type to the ID field like so:
52
53     fields: [{name: 'id', type: 'int'}, 'name', 'address', 'state'],
54
55 Next, we're going to expose these methods in our config.php file, in the first tutorial we added `getResults` to our server-side API, now we're going to add the names of our other PHP functions which we'll be creating shortly.
56
57 ### `config.php`
58
59     <?php
60     $API = array(
61         'QueryDatabase'=>array(
62             'methods'=>array(
63                 'getResults'=>array(
64                     'len'=>1
65                 ),
66                 'createRecord'=>array(
67                     'len'=>1
68                 ),
69                 'updateRecords'=>array(
70                     'len'=>1
71                 ),
72                 'destroyRecord'=>array(
73                     'len'=>1
74                 )
75             )
76         )
77     );
78
79 ### 3.2 Plugin
80
81 Back to grid.js, beneath the `store` variable, we're going to add a plugin for grids called the [Row Editor](http://docs.sencha.com/ext-js/4-0/#!/api/Ext.grid.plugin.RowEditing). This will give an overlay that makes it really simple for users to change a field's content and looks just like this:
82
83 {@img row-editor.png Row Editor in action}
84
85 ### `grid.js`
86
87     ...
88     var rowEditing = Ext.create('Ext.grid.plugin.RowEditing', {
89         clicksToMoveEditor: 1,
90         autoCancel: false
91     });
92
93 By storing the row editor in a variable it'll make it easier to reference later on; as for the configuration options, `clicksToMoveEditor` is how many times the user clicks to move to a different record in our grid. `autoCancel` means that they can't discard changes before moving on and will bring up an arrow pointing to the record saying that they need to commit their changes first. Whenever adding a new component it's worth checking out the [corresponding API documentation on it](http://docs.sencha.com/ext-js/4-0/#!/api/Ext.grid.plugin.RowEditing) to find out all the configuration possibilities.
94
95 We also need to specify a field type for each of the fields we'll want to edit in the grid, simply append the following to the name, address and state columns:
96
97 ### `grid.js`
98
99     ...
100     text: 'Name',
101     field: {
102         type: 'textfield',
103         allowBlank: false
104     }
105
106 For the state column, we're going to do something a little special and add a validation check as well.
107
108 ### `grid.js`
109
110     ...
111     field: {
112         type: 'textfield',
113         allowBlank: false
114         vtype: 'alpha'
115     }
116
117 `vtype` stands for validation type and we're asking to only allow characters from the alphabet to be entered into this field as no state has a number in it.
118
119 To actually be able to edit our grid with the nice Row Editor interface we need to reference the plugin we created earlier. Still within the grid variable write:
120
121 ### `grid.js`
122
123     ...
124     plugins: [
125         rowEditing
126     ],
127
128 However, if you try to add a record and type a state with a space in it, e.g. New York, you'll find that you're not able to type spaces because we've _only_ allowed characters in the alphabet. To get around this we need to write a custom `vtype`.
129
130 Above our grid variable, create a new variable called `alphaSpaceTest`, the funny looking string after this is a ['regular expression'](#!/api/RegExp) which will only allow hyphens, white space and letters.
131
132 ### `grid.js`
133
134     ...
135     var alphaSpaceTest = /^[-\sa-zA-Z]+$/;
136
137     Ext.apply(Ext.form.field.VTypes, {
138         //  vtype validation function
139         alphaSpace: function(val, field) {
140             return alphaSpaceTest.test(val);
141         },
142         // vtype Text property: The error text to display when the validation function returns false
143         alphaSpaceText: 'Not a valid state, must not contain numbers or special characters.',
144         // vtype Mask property: The keystroke filter mask
145         alphaSpaceMask: /^[-\sa-zA-Z]+$/
146     });
147
148 Then change the vtype that previously read `vtype: alpha` to `vtype: alphaSpace` and we'll have the best of both worlds.
149
150 ### 3.3 UI
151
152 So far our grid looks a tad sparse, we're now going to beef it up a bit and add some extra controls with minimal effort. Within our `grid` variable we're going to add a property called `dockedItems` that will hold all of the information for our gorgeous UI.
153
154 ### `grid.js`
155
156     ...
157     dockedItems: [{
158         xtype: 'toolbar',
159         dock: 'bottom',
160     }]
161
162 Setting the `xtype` to a toolbar will house the buttons that we'll create later on. You can also try [experimenting with the dock](http://docs.sencha.com/ext-js/4-0/#!/api/Ext.panel.Panel-cfg-dockedItems) seeing which position you prefer (possible values are `top`, `left`, `right` and `bottom`) and you could even add `frame: true`, it's just a matter of preference.
163
164 ### 3.4 Updating
165
166 We now want to add a function that will update our MySQL table through our QueryDatabase.php file. Because this is a feature that could be used multiple times in one session, we're using the MySQLi prepare statement for it's [security and speed improvements](http://www.linearchat.co.uk/2011/08/why-prepared-statements-in-mysql-are-a-good-thing/).
167
168 ### `QueryDatabase.php`
169
170     ...
171     public function updateRecords(stdClass $params)
172     {
173         $_db = $this->__construct();
174
175         if ($stmt = $_db->prepare("UPDATE owners SET name=?, address=?, state=? WHERE id=?")) {
176             $stmt->bind_param('sssi', $name, $address, $state, $id);
177
178             $name = $params->name;
179             $address = $params->address;
180             $state = $params->state;
181             //cast id to int
182             $id = (int) $params->id;
183
184             $stmt->execute();
185
186             $stmt->close();
187         }
188
189         return $params;
190     }
191
192 When we say `bind_param`, we're telling it the order and type that our variables will appear in, so `sssi` means three strings and an integer. Slightly counter-intuitively, we then say what those variables are and force the `$id` to be an integer, by default it is a string because of the way it's parsed with Ext Direct. We then execute the statement and close it.
193
194 Back to our grid.js file we're going to add the final touch to enable updating. Once we've finished editing a row, the `edit` event is called. We're going to use this to our advantage to save the record once it's been edited. Underneath our `grid` variable write the following:
195
196 ### `grid.js`
197
198     ...
199     grid.on('edit', function(e) {
200         e.record.save();
201     });
202
203 The variable `e` represents an edit event with a [number of properties](http://docs.sencha.com/ext-js/4-0/#!/api/Ext.grid.plugin.RowEditing-event-edit). We're calling the [`save` method](http://docs.sencha.com/ext-js/4-0/#!/api/Ext.data.Model-method-save) which triggers either `createRecord` or `updateRecords` in our Ext Direct proxy.
204
205 You'll see that when you update a record's value a small, red right triangle will appear on the field that you updated, this means that the record is dirty. A dirty record is one that has a different value from it's `originalValue` and needs syncing with the store. Once the save function has been successful you should find that the triangle disappears and is no longer dirty. Note to those using Ext JS 4.0.2a, you'll see that the triangle isn't removed, this is fixed in later versions available to Ext JS subscribers.
206
207 ### 3.5 Creating
208
209 In life, creation is the most complicated process but with Ext we'll get through it without too much sweat. To start, within our `dockedItems` we want to add a button for the user to click to add a record. To do this, we make an array of items.
210
211 ### `grid.js`
212
213     ...
214     items: [
215     {
216         iconCls: 'add',
217         text: 'Add'
218     }]
219
220 The `iconCls` will be referenced in our own CSS file to give it an appropriate icon. This is just the aesthetics, to make it function properly we'll have to add a `handler` and a sprinkling of PHP.
221
222 A `handler` can either be a reference to a function variable or contain the function inline, in this case I've done it inline but you _could_ arrange your file with all of the CRUD functions at the top or bottom and reference it like `handler: addRecord`.
223
224 ### `grid.js`
225
226     ...
227     text: 'Add',
228     handler: function() {
229         rowEditing.cancelEdit();
230         // create a blank record from PersonalInfo
231         var record = Ext.create('PersonalInfo');
232         //insert at top
233         store.insert(0, record);
234         //edit at row 0
235         rowEditing.startEdit(0, 0);
236     }
237
238 This anonymous function first cancels editing if another record is being edited, and then creates a new record from our `PersonalInfo` model, this inserts blank values for our name, address and state fields. We then insert this 'phantom' record into the store using the insert method and then tell the row editor to start editing the top record - where we inserted the record.
239
240 As wonderful as Ext is, it can't deal with the server side logic for us so opening our QueryDatabase.php file we're going to create a new method that will insert records into the database.
241
242 ### `QueryDatabase.php`
243
244     ...
245     public function createRecord(stdClass $params)
246     {
247         $_db = $this->__construct();
248         if($stmt = $_db->prepare("INSERT INTO owners (name, address, state) VALUES (?, ?, ?)")) {
249             $stmt->bind_param('sss', $name, $address, $state);
250
251             $name = $_db->real_escape_string($params->name);
252             $address = $_db->real_escape_string($params->address);
253             $state = $_db->real_escape_string($params->state);
254
255             $stmt->execute();
256
257             $stmt->close();
258         }
259
260         return $params;
261     }
262
263 Our method name corresponds to the name we set when declaring our API in grid.js, i.e. `createRecord`, we then say that 'data that comes from the class stdClass (from router.php) will be assigned to a variable called params, this is for added security so attacks can't be spoofed from another file. The data in question looks like this:
264
265 {@img creating-record.png Firebug showing the JSON that contains the data to create a record}
266
267 This clearly shows us which class and method is being invoked and includes a JSON data object that we access to get the individual fields for our database. The `tid` is the transaction ID of POST requests in this session, so this is the second (the first being when it loaded the data).
268
269 We then prepare our MySQL statement as we did before. The question marks are linked to the next line, where we bind parameters, the `'sss'` denotes that there are three variables which are all strings that we then map afterwards, making sure to escape input into our database as one last security measure before executing our query.
270
271 At this stage, if you add a new record the ID field will stay at 0. If you'd like to remedy this, add `$params->id = $_db->insert_id;` inside of the `createRecord` function, before you `return`. This gets the last inserted ID and sets it as the `id` in `$params` which we then return and is used by the grid. The downside of this is that when the record is saved, because the ID changes, Ext interprets the ID field as dirty.
272
273 ### 3.7 Deleting
274
275 Destruction is always easier than creation and this next section will teach you how to be an Ext Shiva. We already have a button to add so now we're going to do the same process to delete, complete with a handler.
276
277 ### `grid.js`
278
279     ...
280     }, {
281         iconCls: 'delete',
282         text: 'Delete',
283         handler: function() {
284             rowEditing.cancelEdit();
285             var sm = grid.getSelectionModel();
286             store.remove(sm.getSelection());
287             store.sync();
288         }
289     }
290
291 We've seen the first two properties before with adding so I'm going to jump straight to what we're doing with the `handler`. We get rid of the editing overlay if it's open, get which record is being selected in the grid, and then remove the row from the store (using the selection model) and finally sync everything up so our database is up-to-date with our store.
292
293 We then need to write the corresponding snippet of PHP to remove the record from our MySQL database.
294
295 ### `QueryDatabase.php`
296
297     ...
298     public function destroyRecord(stdClass $params)
299     {
300         $_db = $this->__construct();
301
302         $id = (int) $params->id;
303
304         if(is_numeric($id)) {
305             if($stmt = $_db->prepare("DELETE FROM owners WHERE id = ? LIMIT 1")) {
306                 $stmt->bind_param('i', $id);
307                 $stmt->execute();
308                 $stmt->close();
309             }
310         }
311
312         return $this;
313     }
314
315 We grab the `id` variable from the `$params` object and check that it's a number (instead of some nasty SQL injection attack) before passing it to our SQL statement. We're using the same prepare method as we used earlier which works in exactly the same way. Limiting the statement to only one record ensures that only one record will be deleted at a time.
316
317 You should now be able to add, update and remove records but we're not quite finished yet.
318
319 ### 3.8 UX: Bare Buttons and Bad Behavior
320
321 Noticed that the buttons are looking a bit bare? To add an icon we're going to write some old school CSS. The classes aren't arbitrary, when coding our buttons we added an `iconCls` to each one, this adds the class name that we're now using in the CSS. Here are the icons in a [zip file](resources/icons.zip).
322
323 ### `style.css`
324
325     body {
326         background: #ededed;
327     }
328
329     .add {
330         background: url('images/add.png');
331     }
332
333     .delete {
334         background: url('images/delete.png');
335     }
336
337 Of course, we also have to link it in our index.html like so:
338
339 ### `index.html`
340
341     ...
342     <link rel="stylesheet" href="style.css" type="text/css">
343
344 As long as it's beneath the Ext CSS file and in the `<head>` tag it doesn't matter where you place it.
345
346 Something else you may have noticed is how easy it is to delete a record forever and how easily this might be done by mistake. To be less rude to our users we're going to add a dialog box to confirm deletions.
347
348 Dealing with confirming deletions isn't hard at all. First, we want to find the part of our code that deals with deleting things - our handler for the delete button. Second, we want to split the handler into two parts, destructive and non-destructive behavior. The first two lines are non-destructive so I've left them at the top so they get run as soon as the user clicks Delete, but we only want to remove and sync when they _confirm_ that that's what they want to do.
349
350 We invoke `Ext.Msg.show` with some configuration options. The title and message are self-explanatory, the buttons option dictates what buttons the user will be able to click on, in this case, Yes or No. We've also added an icon to the dialog so that the user knows immediately that an action is needed from them to continue. When the user does decide on one of the options and clicks the corresponding button we can check which one was chosen by supplying a callback function with `fn`. This is shorthand for function (no surprises) and works the same way as a handler on our Add and Delete buttons where we simply check if they said yes (always in lowercase) and if so, carry out what needs to be done. If we were being _really_ nice we could add an `else` and resume their editing where they left off.
351
352 ### `grid.js`
353
354     ...
355     handler: function() {
356         rowEditing.cancelEdit();
357         var sm = grid.getSelectionModel();
358
359         Ext.Msg.show({
360              title:'Delete Record?',
361              msg: 'You are deleting a record permanently, this cannot be undone. Proceed?',
362              buttons: Ext.Msg.YESNO,
363              icon: Ext.Msg.QUESTION,
364              fn: function(btn){
365                  if (btn === 'yes'){
366                      store.remove(sm.getSelection());
367                      store.sync();
368                  }
369              }
370         });
371     }
372
373 Voila, now we have a nice, user friendly message that confirms their actions.
374
375 And that's it! You should now have a fully functioning grid that looks similar to this:
376
377 {@img grid-full.png The final product}
378
379 IV. Conclusion
380 --------------
381 In this tutorial we've covered a lot of ground. You should now know how to implement Ext Direct to create, read, update and delete from a database. We've also looked at how easy Ext makes dialogs and alerts that have a direct impact on the application and create a better user experience overall.
382
383 If you're going to integrate this into production code, you could look into how to optimize it using [Ext Loader to only load the classes that we use](http://www.sencha.com/blog/using-ext-loader-for-your-application/) or process actions in batches with a 'Save changes' button so permanent changes aren't immediate.
384
385 Finally, for reference, you can find the [completed source files here](guides/direct_grid_pt2/reference-files.zip).