1 # Doing CRUD with the Editable Grid
3 ## Part One: Setting up the Stack
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.
7 We will be building a database driven application, so we will need:
10 2. A server-side programming environment
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.
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.
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).
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.
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:
27 tar zxvf node-v0.4.11.tar.gz
33 The 'make' stage will take a few minutes.
37 The instructions on the [npm website](http://npmjs.org) say to execute this command:
39 curl http://npmjs.org/install.sh | sh
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:
43 wget http://npmjs.org/install.sh
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/).
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.
55 Now we can simply use npm to install this:
57 sudo npm install -g express
59 ## Setting up the Application
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:
67 Which should output that it created the following files:
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
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:
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:
87 "name": "application-name",
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:
105 "mongoose": ">= 2.0.0"
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.
111 To run our webapp, go into the 'geekflicks' directory, and run the following command:
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:
118 app.get('/', function (req, res) {
119 res.render('index', {
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:
127 app.get('/', function (req, res) {
128 res.redirect('/index.html');
131 ### Creating the Ext JS Application
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):
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:
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>
155 Ext.Loader.setConfig({
159 <script type="text/javascript" src="app.js"> </script>
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.
173 launch: function () {
174 Ext.create('Ext.container.Viewport', {
178 title: 'Flicks for Geeks',
179 html: 'Add your favorite geeky movies'
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.
187 ### The View & Viewport
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:
191 Ext.define('GeekFlicks.view.Movies', {
192 extend: 'Ext.grid.Panel',
193 alias: 'widget.movieseditor',
195 initComponent: function () {
197 // Hardcoded store with static data:
199 fields: ['title', 'year'],
204 title: 'Star Wars: Return of the Jedi',
219 this.callParent(arguments);
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:
225 Ext.define('GeekFlicks.view.Viewport', {
226 extend: 'Ext.container.Viewport',
232 title: 'Top Geek Flicks of All Time',
234 xtype: 'movieseditor'
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).
243 Now lets create the controller, in the 'controller' folder, as follows:
245 Ext.define("GeekFlicks.controller.Movies", {
246 extend: 'Ext.app.Controller',
255 render: this.onEditorRender
260 onEditorRender: function () {
261 console.log("Movies editor was rendered");
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.
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.
274 autoCreateViewport: true,
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.
286 The Model element of the MVC trinity consists of a few classes with specific responsibilities. They are:
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
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:
294 Ext.define('GeekFlicks.model.Movie', {
295 extend: 'Ext.data.Model',
306 Then, in `store/Movies.js`, add the following:
308 Ext.define('GeekFlicks.store.Movies', {
309 extend: 'Ext.data.Store',
310 model: 'GeekFlicks.model.Movie',
316 title: 'Star Wars: Return of the Jedi',
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:
323 Ext.define('GeekFlicks.view.Movies', {
324 extend: 'Ext.grid.Panel',
325 alias: 'widget.movieseditor',
329 initComponent: function () {
330 // Note: store removed
341 this.callParent(arguments);
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.
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`:
349 Ext.define("GeekFlicks.controller.Movies", {
350 extend: 'Ext.app.Controller',
359 render: this.onEditorRender
364 onEditorRender: function () {
365 console.log("movies editor was rendered");
369 ## The Proxy and the Server
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:
373 app.get('/movies', function (req, res) {
374 res.contentType('json');
381 title: "Star Wars: Return of the Jedi",
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:
390 Ext.define('GeekFlicks.store.Movies', {
391 extend: 'Ext.data.Store',
394 fields: ['title', 'year'],
396 // Data removed, instead using proxy:
403 successProperty: 'success'
408 If you restart the server app, and reload the page, you should see the grid with the first two movies in it.
410 ### Setting up the Database
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.)
415 MongoDB shell version: 2.0.0
418 switched to db example
419 > db.movies.insert({title: "The Matrix", year: 1999});
420 > db.movies.insert({title: "Star Wars", year: 1977});
422 { "_id" : ObjectId("4e7018a79abdbdfb5d235b6c"), "title" : "The Matrix", "year" : 1999 }
423 { "_id" : ObjectId("4e7018dd9abdbdfb5d235b6d"), "title" : "Star Wars", "year" : 1977 }
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:
428 var mongoose = require('mongoose'),
430 db = mongoose.connect('mongodb://127.0.0.1/example'),
432 //create the movie Model using the 'movies' collection as a data-source
433 movieModel = mongoose.model('movies', new mongoose.Schema({
440 app.get('/movies', function (req, res) {
441 movieModel.find({}, function (err, movies) {
442 res.contentType('json');
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:
452 {@img geekflicks_readonly.png}
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.
456 [Download project files](guides/editable_grid/geekflicks.zip)