Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / docs / guides / editable_grid / README.md
1 # Doing CRUD with the Editable Grid
2
3 ## Part One: Setting up the Stack
4
5 The {@link Ext.grid.Panel Grid Panel} is a powerful way to display tabular data. It is an ideal solution for displaying dynamic data from a database. It can also allow users to edit the data displayed in the grid. Changes to the dataset can be easily saved back to the server. This guide describes how to create this functionality using Ext's MVC application architecture. If you're not familiar with this, then I recommend you check out [the guide](#!/guide/application_architecture) for more details.  The first part of this tutorial will cover the setting up of the application on the client and server, to the point where we can read data from the database and display it in the grid. Creating, updating, and deleting the records in the grid will be covered in the second part.
6
7 We will be building a database driven application, so we will need:
8
9 1. A web server
10 2. A server-side programming environment
11 3. A database
12
13 Traditionally, this has been done with a LAMP (Linux, Apache, Mysql, PHP) stack, or (on Windows) ASP.NET. For this guide I have decided to use the new kid on the block: [Node.js](http://nodejs.org). In particular, [Express](http://expressjs.com), the Node-based web application framework, provides a quick and easy way both to host static content and a ready-made web application framework. As for the database, [MongoDB](http://mongodb.org) provides a lightweight alternative to relational databases like MySQL or Postgres, which require using SQL. All these technologies are JavaScript based.
14
15 One limitation of this toolchain is that it is currently not supported on Windows. Although Node is currently being ported to Windows, this work is not yet complete. Furthermore the Node Package Manager uses Unix features such as symbolic links, and simply does not work on Windows at present. So if you are using Windows, you will need to use a Virtual Machine running Ubuntu (or other linux distro). [VirtualBox](http://virtualbox.org) and [VMWare](https://help.ubuntu.com/community/VMware) are free virtualization solutions. If you are using Mac OS X, or Linux, you will be fine.
16
17 Express is built on [Connect](https://github.com/senchalabs/connect), which is a SenchaLabs project. This gives some assurance that it is not going to disappear tomorrow (which may be a concern with something so young). Node.js is growing very rapidly in terms of the number of users, the number of libraries (modules) available and the number of hosting providers which support it. The online documentation is excellent, and the IRC community is apparently also quite helpful. There are also a few books in the works, which should be hitting shelves later this year. It seems that MongoDB is taking off as the [most popular NoSQL database](http://css.dzone.com/news/nosql-job-trends).
18
19 ### Installation
20
21 While a full guide to installation, configuration, and use of Node, NPM, and MongoDB, are beyond the scope of this guide, here are the commands which worked for me.
22
23
24 #### Node.js
25 I downloaded the latest *stable* source package, which at the time of writing was [node v0.4.11](http://nodejs.org/dist/node-v0.4.11.tar.gz). Then execute the following commands in the folder where the archive was downloaded:
26
27       tar zxvf node-v0.4.11.tar.gz
28       cd node-v0.4.11
29       ./configure
30       make
31       sudo make install
32
33 The 'make' stage will take a few minutes.
34
35 #### NPM
36
37 The instructions on the [npm website](http://npmjs.org) say to execute this command:
38
39     curl http://npmjs.org/install.sh | sh
40
41 For some reason this does not work for me, even when I add 'sudo' in front of it. So I break the command into the following steps:
42
43     wget http://npmjs.org/install.sh
44     chmod +x install.sh
45     sudo ./install.sh
46
47 'wget' is similar to curl, but a bit simpler to use as it just downloads the URL to a local file. You can install it with 'apt-get install wget' on Ubuntu, or 'brew install wget' on the Mac, if you are using [HomeBrew](http://mxcl.github.com/homebrew/).
48
49 #### MongoDB
50
51 The installation process is well documented on [the MongoDB website](http://www.mongodb.org/display/DOCS/Quickstart). It should be available though your system package manager. The details of creating a database are covered later in this guide.
52
53 #### Express
54
55 Now we can simply use npm to install this:
56
57     sudo npm install -g express
58
59 ## Setting up the Application
60
61 ### Express Yourself!
62
63 The example dataset we will be dealing with is the set of movies which computer geeks like. This is a matter of opinion, hence the editable aspect is important. So we will call the demo app 'geekflicks'. As instructed on the [express website](http://expressjs.com/guide.html), simply create your application using the 'express' command:
64
65     express geekflicks
66
67 Which should output that it created the following files:
68
69     create : geekflicks
70     create : geekflicks/package.json
71     create : geekflicks/app.js
72     create : geekflicks/public/javascripts
73     create : geekflicks/public/stylesheets
74     create : geekflicks/public/stylesheets/style.css
75     create : geekflicks/public/images
76     create : geekflicks/views
77     create : geekflicks/views/layout.jade
78     create : geekflicks/views/index.jade
79
80 Note that the public/ directory is where the Ext JS application will live.  In order to finish setting up the Express app, we need to also execute this command from within the 'geekflicks' directory:
81
82     npm install -d
83
84 This will install the additional Node packages which are required.  The dependencies are defined in the 'package.json' file, which by default looks like this:
85
86     {
87         "name": "application-name",
88         "version": "0.0.1",
89         "private": true,
90         "dependencies": {
91             "express": "2.4.6",
92             "jade": ">= 0.0.1"
93         }
94     }
95
96 For accessing the MongoDB database in our NodeJS app, I've chosen to use [Mongoose](http://mongoosejs.com/) which provides a nice high-level API. Lets add that as an explicit dependency in the package.json file, and change the name of our app while we're at it:
97
98     {
99         "name": "geekflicks",
100         "version": "0.0.1",
101         "private": true,
102         "dependencies": {
103             "express": "2.4.6",
104             "jade": ">= 0.0.1",
105             "mongoose": ">= 2.0.0"
106         }
107     }
108
109 Now if we re-execute the 'npm install -d' command, we see that it picks up the additional dependency, and installs it. What actually happens here is that the dependencies are installed in a folder called 'node_modules'. They are not installed globally, which means that our app has its own copy of these modules, which is a Good Thing, as it makes it independent of the global Node modules on your system.
110
111 To run our webapp, go into the 'geekflicks' directory, and run the following command:
112
113     node app.js
114
115 Which should say that the app is running on port 3000. If you open a browser to [http://localhost:3000](http://localhost:3000), you should see a page with the message: 'Express' and 'Welcome to Express'. How does this work? Well, in the geekflicks/app.js file, there is a definition of a 'route' as follows:
116
117     // Routes
118     app.get('/', function (req, res) {
119         res.render('index', {
120             title: 'Express'
121         });
122     });
123
124 Which says that whenever a request is made to the root of the webserver, it should render the 'index' template in the 'views' directory, using the given data object. This uses the Jade templating system which is bundled into Express. While this is neat, what we want it to do is redirect to the index.html file inside the 'public' folder, which is where we will be building our Ext JS app. We can achieve this using a redirect:
125
126     // Routes
127     app.get('/', function (req, res) {
128         res.redirect('/index.html');
129     });
130
131 ### Creating the Ext JS Application
132
133 Lets create a directory structure for our Ext JS application in the 'public' directory, as described in the [getting started guide](#!/guide/getting_started):
134
135     geekflicks/
136       public/
137         app/
138             controller/
139             model/
140             store/
141             view/
142         extjs/
143       ...
144
145 The extjs/ folder has the ExtJS 4 SDK (or a symlink to it). In the GeekFlicks folder, we create a index.html file with the following:
146
147     <!doctype html>
148     <html>
149     <head>
150       <meta charset="utf-8">
151       <title>Editable Grid</title>
152       <link rel="stylesheet" href="extjs/resources/css/ext-all.css">
153       <script src="extjs/ext-all-debug.js"></script>
154       <script>
155         Ext.Loader.setConfig({
156             enabled: true
157         });
158       </script>
159       <script type="text/javascript" src="app.js"> </script>
160     </head>
161     <body>
162
163     </body>
164     </html>
165
166 I'm using the HTML5 recommended syntax here, though this is not necessary. Also, note that I have included 'ext-all-debug.js' not 'ext.js'. This ensures that all the Ext JS classes are available immediately after the app is loaded, rather than loading each class file dynamically, which is what would occur if you used 'ext.js' (or ext-debug.js). The grid does require a large number of classes, and this tends to slow down the initial page load, and clutter up the class list with a bunch of classes, which makes finding your own classes harder. However the MVC Application class does require the Loader to be enabled, and it is disabled by default when you use the 'ext-all' version. So I've manually re-enabled it here.
167
168 The app.js has this:
169
170     Ext.application({
171         name: "GeekFlicks",
172         appFolder: "app",
173         launch: function () {
174             Ext.create('Ext.container.Viewport', {
175                 layout: 'fit',
176                 items: [{
177                     xtype: 'panel',
178                     title: 'Flicks for Geeks',
179                     html: 'Add your favorite geeky movies'
180                 }]
181             });
182         }
183     });
184
185 So now if you stop the Express webserver (by typing Ctrl-C in the terminal where you issued the 'node app.js' command) and restarting it (by issuing the 'node app.js' command again ) and navigate to [http://localhost:3000](http://localhost:3000) (or refresh the browser), you should see a panel with the title "Flicks for Geeks" and the text "Add your favorite geeky movies" beneath it. If not, check the console for errors.. perhaps something got misplaced. Now we still don't have a Grid panel anywhere in sight, so lets remedy that.
186
187 ### The View & Viewport
188
189 Now lets setup the MVC components of our application, rather than have it all in one file. This will allow us to take advantage of the features of Ext JS 4's Application framework. Create a view for the editable data grid in the 'views' folder, called 'Movies', with the following code:
190
191     Ext.define('GeekFlicks.view.Movies', {
192         extend: 'Ext.grid.Panel',
193         alias: 'widget.movieseditor',
194
195         initComponent: function () {
196
197             // Hardcoded store with static data:
198             this.store = {
199                 fields: ['title', 'year'],
200                 data: [{
201                     title: 'The Matrix',
202                     year: '1999'
203                 }, {
204                     title: 'Star Wars: Return of the Jedi',
205                     year: '1983'
206                 }]
207             };
208
209             this.columns = [{
210                 header: 'Title',
211                 dataIndex: 'title',
212                 flex: 1
213             }, {
214                 header: 'Year',
215                 dataIndex: 'year',
216                 flex: 1
217             }];
218
219             this.callParent(arguments);
220         }
221     });
222
223 This creates a new view class called 'Movies' which extends the grid panel and hardcodes some data in a store, which is simply declared inline. This will be refactored later, but is enough to get us going for now.  Additionally, instead of creating the Viewport manually in the app.js file when the 'launch' event fires, we can create the main viewport of our application as a separate class called 'Viewport.js' in the view/ folder. It includes the Movies view we just created, using its xtype:
224
225     Ext.define('GeekFlicks.view.Viewport', {
226         extend: 'Ext.container.Viewport',
227
228         layout: 'fit',
229
230         items: [{
231             xtype: 'panel',
232             title: 'Top Geek Flicks of All Time',
233             items: [{
234                 xtype: 'movieseditor'
235             }]
236         }]
237     });
238
239 In order for the Viewport to be loaded automatically when the application launches, we set the 'autoCreateViewport' property to true in app.js (see next section for a listing of it).
240
241 ### The Controller
242
243 Now lets create the controller, in the 'controller' folder, as follows:
244
245     Ext.define("GeekFlicks.controller.Movies", {
246         extend: 'Ext.app.Controller',
247
248         views: [
249             'Movies'
250         ],
251
252         init: function () {
253             this.control({
254                 'movieseditor': {
255                     render: this.onEditorRender
256                 }
257             });
258         },
259
260         onEditorRender: function () {
261             console.log("Movies editor was rendered");
262         }
263     });
264
265 This sets up a controller for the view we just created, by including it in the `views` array.  The `init()` method is automatically called by the Application when it starts. The `{@link Ext.app.Controller#method-control control()}` method adds the `onEditorRender` event listener to the movies editor grid which was selected by the {@link Ext.ComponentQuery ComponentQuery} expression `movieseditor` (which selects components with that xtype, or 'alias'). This is nice because it means the view does not have to know anything about the Controller (or the Model) and so it can potentially be reused in more than one context.
266
267 So now we have added a view, and a controller to listen for its events. Now we need to tell the Application about it. We set the `controllers` array to contain the `Movies` controller. Our main app.js file now looks like this.
268
269     Ext.application({
270
271         name: "GeekFlicks",
272         appFolder: "app",
273
274         autoCreateViewport: true,
275
276         controllers: [
277             'Movies'
278         ]
279     });
280
281
282 Now, when you refresh your browser, you should see the actual grid panel show up with the data we hardcoded into the view. Next we will refactor this to make this data load dynamically.
283
284 ### The Model
285
286 The Model element of the MVC trinity consists of a few classes with specific responsibilities. They are:
287
288 * The Model: defines the schema of the data (think of it as a data-model or object-model)
289 * The Store: stores records of data (which are defined by the Model)
290 * The Proxy: loads the Store from the server (or other storage) and saves changes
291
292 These are covered in more detail in the [data package guide](#!/guide/data). So lets define our data, first by creating `Movie.js` in the 'model' folder:
293
294     Ext.define('GeekFlicks.model.Movie', {
295         extend: 'Ext.data.Model',
296
297         fields: [{
298             name: 'title',
299             type: 'string'
300         }, {
301             name: 'year',
302             type: 'int'
303         }]
304     });
305
306 Then, in `store/Movies.js`, add the following:
307
308     Ext.define('GeekFlicks.store.Movies', {
309         extend: 'Ext.data.Store',
310         model: 'GeekFlicks.model.Movie',
311
312         data: [{
313             title: 'The Matrix',
314             year: '1999'
315         }, {
316             title: 'Star Wars: Return of the Jedi',
317             year: '1983'
318         }]
319     });
320
321 Which is just copied from the View, where it was previously set in the `initComponent()` method. The one change is that instead on the fields being defined inline, there is a reference to the Model we just created, where they are defined. Let's now clean up the view to reference our store... change the contents of the `view/Movies.js` to:
322
323     Ext.define('GeekFlicks.view.Movies', {
324         extend: 'Ext.grid.Panel',
325         alias: 'widget.movieseditor',
326
327         store: 'Movies',
328
329         initComponent: function () {
330             // Note: store removed
331             this.columns = [{
332                 header: 'Title',
333                 dataIndex: 'title',
334                 flex: 1
335             }, {
336                 header: 'Year',
337                 dataIndex: 'year',
338                 flex: 1
339             }];
340
341             this.callParent(arguments);
342         }
343     });
344
345 Note that the `store` configuration property was set to 'Movies' which will cause an instance of the Movies store to be instantiated at run time, and assigned to the grid.
346
347 The Controller also needs to know about the Model and Store, so we tell it about them by adding a couple of config items, named (surprise!) `models` and `stores`:
348
349     Ext.define("GeekFlicks.controller.Movies", {
350         extend: 'Ext.app.Controller',
351
352         models: ['Movie'],
353         stores: ['Movies'],
354         views:  ['Movies'],
355
356         init: function () {
357             this.control({
358                 'movieseditor': {
359                     render: this.onEditorRender
360                 }
361             });
362         },
363
364         onEditorRender: function () {
365             console.log("movies editor was rendered");
366         }
367     });
368
369 ## The Proxy and the Server
370
371 We still have hard-coded data in our Store, so lets fix that using a {@link Ext.data.proxy.Proxy Proxy}. The proxy will load the data from our Express app. Specifically, it will access the URL `/movies`. We must define this as a route in `geekflicks/app.js`. The first version of this simply echoes back a JSON representation of the current movies. Here is the new route definition:
372
373       app.get('/movies', function (req, res) {
374           res.contentType('json');
375           res.json({
376               success: true,
377               data: [{
378                   title: "The Matrix",
379                   year: 1999
380               }, {
381                   title: "Star Wars: Return of the Jedi",
382                   year: 1983
383               }]
384           });
385       });
386
387
388 The format here is significant: the JSON response must be a single object, with a `success` property, and a `data` property, which is an array of records matching the definition of the Model. Actually you can customize these properties by configuring your Proxy's `{@link Ext.data.proxy.Proxy#cfg-reader reader}` differently, but the important thing is that the server sends the same names which the {@link Ext.data.reader.Reader Reader} is expecting.  Now this data is still hardcoded - just on the server-side. In the next section we will be reading this out of a database. But at least we can remove the hardcoded data from our store, and replace it with the Proxy definition:
389
390     Ext.define('GeekFlicks.store.Movies', {
391         extend: 'Ext.data.Store',
392
393         autoLoad: true,
394         fields: ['title', 'year'],
395
396         // Data removed, instead using proxy:
397         proxy: {
398             type: 'ajax',
399             url: '/movies',
400             reader: {
401                 type: 'json',
402                 root: 'data',
403                 successProperty: 'success'
404             }
405         }
406     });
407
408 If you restart the server app, and reload the page, you should see the grid with the first two movies in it.
409
410 ### Setting up the Database
411
412 For our app to be truly dynamic and interactive, we'll need a database to store our movies in. Because of its simplicity and JavaScript syntax, we will be using MongoDB for this demo. If you haven't installed it as described at the start of this guide, you should do that now. Once you have the `mongodb` daemon running, typing `mongo` will put you into a MongoDB console where you can create a new database and collection (similar to a table in SQL). You can then insert the default movies using a nice JavaScript API, as follows (you just type the commands on the lines starting with '>' which is the mongo prompt.)
413
414       $ mongo
415       MongoDB shell version: 2.0.0
416       connecting to: test
417       > use example
418       switched to db example
419       > db.movies.insert({title: "The Matrix", year: 1999});
420       > db.movies.insert({title: "Star Wars", year: 1977});
421       > db.movies.find();
422       { "_id" : ObjectId("4e7018a79abdbdfb5d235b6c"), "title" : "The Matrix", "year" : 1999 }
423       { "_id" : ObjectId("4e7018dd9abdbdfb5d235b6d"), "title" : "Star Wars", "year" : 1977 }
424       > quit()
425
426 The find() command is like SQL `select *`. It returns all members of the collection, which confirms that the rows were added as expected. Note that they have also been given a unique id. Now that the data is in the database, we just need to pull it out with our Node JS app. This can be done by using Mongoose to define a model, and then modifying the `/movies` route in `app.js` to query it:
427
428     var mongoose = require('mongoose'),
429
430     db = mongoose.connect('mongodb://127.0.0.1/example'),
431
432     //create the movie Model using the 'movies' collection as a data-source
433     movieModel = mongoose.model('movies', new mongoose.Schema({
434         title: String,
435         year: Number
436     }));
437
438     //...
439
440     app.get('/movies', function (req, res) {
441         movieModel.find({}, function (err, movies) {
442             res.contentType('json');
443             res.json({
444                 success: true,
445                 data: movies
446             });
447         });
448     });
449
450 After restarting the server (this will need to be done to see any change to the server code, but not after changing Ext JS code only) the index page of our app will show the movies we entered into the database above. Note the change to the Star Wars film title and date. This is what the app should look like at this point:
451
452 {@img geekflicks_readonly.png}
453
454 Admittedly, it is not very exciting, as it simply displays the data from the database. However, we have set up a full web stack from the database to the UI, using NodeJS and the Ext JS 4 Application framework. Now we are in a good position to add more functionality to the app, specifically creating new movies and updating or deleting existing ones. This will be described in part two of this tutorial.
455
456 [Download project files](guides/editable_grid/geekflicks.zip)