PageRenderTime 13ms CodeModel.GetById 6ms RepoModel.GetById 0ms app.codeStats 0ms

JavaScript | 1 lines | 1 code | 0 blank | 0 comment | 0 complexity | c336ce1b6214afee9c7cce5be7e5a439 MD5 | raw file
  1.{"guide":"<h1>Doing CRUD with the Editable Grid</h1>\n\n<h2>Part One: Setting up the Stack</h2>\n\n<p>The <a href=\"#!/api/Ext.grid.Panel\" rel=\"Ext.grid.Panel\" class=\"docClass\">Grid Panel</a> 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 <a href=\"#!/guide/application_architecture\">the guide</a> 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.</p>\n\n<p>We will be building a database driven application, so we will need:</p>\n\n<ol>\n<li>A web server</li>\n<li>A server-side programming environment</li>\n<li>A database</li>\n</ol>\n\n\n<p>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: <a href=\"\">Node.js</a>. In particular, <a href=\"\">Express</a>, 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, <a href=\"\">MongoDB</a> provides a lightweight alternative to relational databases like MySQL or Postgres, which require using SQL. All these technologies are JavaScript based.</p>\n\n<p>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). <a href=\"\">VirtualBox</a> and <a href=\"\">VMWare</a> are free virtualization solutions. If you are using Mac OS X, or Linux, you will be fine.</p>\n\n<p>Express is built on <a href=\"\">Connect</a>, 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 <a href=\"\">most popular NoSQL database</a>.</p>\n\n<h3>Installation</h3>\n\n<p>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.</p>\n\n<h4>Node.js</h4>\n\n<p>I downloaded the latest <em>stable</em> source package, which at the time of writing was <a href=\"\">node v0.4.11</a>. Then execute the following commands in the folder where the archive was downloaded:</p>\n\n<pre><code> tar zxvf node-v0.4.11.tar.gz\n cd node-v0.4.11\n ./configure\n make\n sudo make install\n</code></pre>\n\n<p>The 'make' stage will take a few minutes.</p>\n\n<h4>NPM</h4>\n\n<p>The instructions on the <a href=\"\">npm website</a> say to execute this command:</p>\n\n<pre><code>curl | sh\n</code></pre>\n\n<p>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:</p>\n\n<pre><code>wget\nchmod +x\nsudo ./\n</code></pre>\n\n<p>'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 <a href=\"\">HomeBrew</a>.</p>\n\n<h4>MongoDB</h4>\n\n<p>The installation process is well documented on <a href=\"\">the MongoDB website</a>. It should be available though your system package manager. The details of creating a database are covered later in this guide.</p>\n\n<h4>Express</h4>\n\n<p>Now we can simply use npm to install this:</p>\n\n<pre><code>sudo npm install -g express\n</code></pre>\n\n<h2>Setting up the Application</h2>\n\n<h3>Express Yourself!</h3>\n\n<p>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 <a href=\"\">express website</a>, simply create your application using the 'express' command:</p>\n\n<pre><code>express geekflicks\n</code></pre>\n\n<p>Which should output that it created the following files:</p>\n\n<pre><code>create : geekflicks\ncreate : geekflicks/package.json\ncreate : geekflicks/app.js\ncreate : geekflicks/public/javascripts\ncreate : geekflicks/public/stylesheets\ncreate : geekflicks/public/stylesheets/style.css\ncreate : geekflicks/public/images\ncreate : geekflicks/views\ncreate : geekflicks/views/layout.jade\ncreate : geekflicks/views/index.jade\n</code></pre>\n\n<p>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:</p>\n\n<pre><code>npm install -d\n</code></pre>\n\n<p>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:</p>\n\n<pre><code>{\n \"name\": \"application-name\",\n \"version\": \"0.0.1\",\n \"private\": true,\n \"dependencies\": {\n \"express\": \"2.4.6\",\n \"jade\": \"&gt;= 0.0.1\"\n }\n}\n</code></pre>\n\n<p>For accessing the MongoDB database in our NodeJS app, I've chosen to use <a href=\"\">Mongoose</a> 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:</p>\n\n<pre><code>{\n \"name\": \"geekflicks\",\n \"version\": \"0.0.1\",\n \"private\": true,\n \"dependencies\": {\n \"express\": \"2.4.6\",\n \"jade\": \"&gt;= 0.0.1\",\n \"mongoose\": \"&gt;= 2.0.0\"\n }\n}\n</code></pre>\n\n<p>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.</p>\n\n<p>To run our webapp, go into the 'geekflicks' directory, and run the following command:</p>\n\n<pre><code>node app.js\n</code></pre>\n\n<p>Which should say that the app is running on port 3000. If you open a browser to <a href=\"http://localhost:3000\">http://localhost:3000</a>, 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:</p>\n\n<pre><code>// Routes\napp.get('/', function (req, res) {\n res.render('index', {\n title: 'Express'\n });\n});\n</code></pre>\n\n<p>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:</p>\n\n<pre><code>// Routes\napp.get('/', function (req, res) {\n res.redirect('/index.html');\n});\n</code></pre>\n\n<h3>Creating the Ext JS Application</h3>\n\n<p>Lets create a directory structure for our Ext JS application in the 'public' directory, as described in the <a href=\"#!/guide/getting_started\">getting started guide</a>:</p>\n\n<pre><code>geekflicks/\n public/\n app/\n controller/\n model/\n store/\n view/\n extjs/\n ...\n</code></pre>\n\n<p>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:</p>\n\n<pre><code>&lt;!doctype html&gt;\n&lt;html&gt;\n&lt;head&gt;\n &lt;meta charset=\"utf-8\"&gt;\n &lt;title&gt;Editable Grid&lt;/title&gt;\n &lt;link rel=\"stylesheet\" href=\"extjs/resources/css/ext-all.css\"&gt;\n &lt;script src=\"extjs/ext-all-debug.js\"&gt;&lt;/script&gt;\n &lt;script&gt;\n Ext.Loader.setConfig({\n enabled: true\n });\n &lt;/script&gt;\n &lt;script type=\"text/javascript\" src=\"app.js\"&gt; &lt;/script&gt;\n&lt;/head&gt;\n&lt;body&gt;\n\n&lt;/body&gt;\n&lt;/html&gt;\n</code></pre>\n\n<p>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.</p>\n\n<p>The app.js has this:</p>\n\n<pre><code>Ext.application({\n name: \"GeekFlicks\",\n appFolder: \"app\",\n launch: function () {\n Ext.create('Ext.container.Viewport', {\n layout: 'fit',\n items: [{\n xtype: 'panel',\n title: 'Flicks for Geeks',\n html: 'Add your favorite geeky movies'\n }]\n });\n }\n});\n</code></pre>\n\n<p>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 <a href=\"http://localhost:3000\">http://localhost:3000</a> (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.</p>\n\n<h3>The View &amp; Viewport</h3>\n\n<p>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:</p>\n\n<pre><code>Ext.define('GeekFlicks.view.Movies', {\n extend: 'Ext.grid.Panel',\n alias: 'widget.movieseditor',\n\n initComponent: function () {\n\n // Hardcoded store with static data:\n = {\n fields: ['title', 'year'],\n data: [{\n title: 'The Matrix',\n year: '1999'\n }, {\n title: 'Star Wars: Return of the Jedi',\n year: '1983'\n }]\n };\n\n this.columns = [{\n header: 'Title',\n dataIndex: 'title',\n flex: 1\n }, {\n header: 'Year',\n dataIndex: 'year',\n flex: 1\n }];\n\n this.callParent(arguments);\n }\n});\n</code></pre>\n\n<p>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:</p>\n\n<pre><code>Ext.define('GeekFlicks.view.Viewport', {\n extend: 'Ext.container.Viewport',\n\n layout: 'fit',\n\n items: [{\n xtype: 'panel',\n title: 'Top Geek Flicks of All Time',\n items: [{\n xtype: 'movieseditor'\n }]\n }]\n});\n</code></pre>\n\n<p>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).</p>\n\n<h3>The Controller</h3>\n\n<p>Now lets create the controller, in the 'controller' folder, as follows:</p>\n\n<pre><code>Ext.define(\"GeekFlicks.controller.Movies\", {\n extend: '',\n\n views: [\n 'Movies'\n ],\n\n init: function () {\n this.control({\n 'movieseditor': {\n render: this.onEditorRender\n }\n });\n },\n\n onEditorRender: function () {\n console.log(\"Movies editor was rendered\");\n }\n});\n</code></pre>\n\n<p>This sets up a controller for the view we just created, by including it in the <code>views</code> array. The <code>init()</code> method is automatically called by the Application when it starts. The <code><a href=\"#!/api/\" rel=\"\" class=\"docClass\">control()</a></code> method adds the <code>onEditorRender</code> event listener to the movies editor grid which was selected by the <a href=\"#!/api/Ext.ComponentQuery\" rel=\"Ext.ComponentQuery\" class=\"docClass\">ComponentQuery</a> expression <code>movieseditor</code> (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.</p>\n\n<p>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 <code>controllers</code> array to contain the <code>Movies</code> controller. Our main app.js file now looks like this.</p>\n\n<pre><code>Ext.application({\n\n name: \"GeekFlicks\",\n appFolder: \"app\",\n\n autoCreateViewport: true,\n\n controllers: [\n 'Movies'\n ]\n});\n</code></pre>\n\n<p>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.</p>\n\n<h3>The Model</h3>\n\n<p>The Model element of the MVC trinity consists of a few classes with specific responsibilities. They are:</p>\n\n<ul>\n<li>The Model: defines the schema of the data (think of it as a data-model or object-model)</li>\n<li>The Store: stores records of data (which are defined by the Model)</li>\n<li>The Proxy: loads the Store from the server (or other storage) and saves changes</li>\n</ul>\n\n\n<p>These are covered in more detail in the <a href=\"#!/guide/data\">data package guide</a>. So lets define our data, first by creating <code>Movie.js</code> in the 'model' folder:</p>\n\n<pre><code>Ext.define('GeekFlicks.model.Movie', {\n extend: '',\n\n fields: [{\n name: 'title',\n type: 'string'\n }, {\n name: 'year',\n type: 'int'\n }]\n});\n</code></pre>\n\n<p>Then, in <code>store/Movies.js</code>, add the following:</p>\n\n<pre><code>Ext.define('', {\n extend: '',\n model: 'GeekFlicks.model.Movie',\n\n data: [{\n title: 'The Matrix',\n year: '1999'\n }, {\n title: 'Star Wars: Return of the Jedi',\n year: '1983'\n }]\n});\n</code></pre>\n\n<p>Which is just copied from the View, where it was previously set in the <code>initComponent()</code> 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 <code>view/Movies.js</code> to:</p>\n\n<pre><code>Ext.define('GeekFlicks.view.Movies', {\n extend: 'Ext.grid.Panel',\n alias: 'widget.movieseditor',\n\n store: 'Movies',\n\n initComponent: function () {\n // Note: store removed\n this.columns = [{\n header: 'Title',\n dataIndex: 'title',\n flex: 1\n }, {\n header: 'Year',\n dataIndex: 'year',\n flex: 1\n }];\n\n this.callParent(arguments);\n }\n});\n</code></pre>\n\n<p>Note that the <code>store</code> 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.</p>\n\n<p>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!) <code>models</code> and <code>stores</code>:</p>\n\n<pre><code>Ext.define(\"GeekFlicks.controller.Movies\", {\n extend: '',\n\n models: ['Movie'],\n stores: ['Movies'],\n views: ['Movies'],\n\n init: function () {\n this.control({\n 'movieseditor': {\n render: this.onEditorRender\n }\n });\n },\n\n onEditorRender: function () {\n console.log(\"movies editor was rendered\");\n }\n});\n</code></pre>\n\n<h2>The Proxy and the Server</h2>\n\n<p>We still have hard-coded data in our Store, so lets fix that using a <a href=\"#!/api/\" rel=\"\" class=\"docClass\">Proxy</a>. The proxy will load the data from our Express app. Specifically, it will access the URL <code>/movies</code>. We must define this as a route in <code>geekflicks/app.js</code>. The first version of this simply echoes back a JSON representation of the current movies. Here is the new route definition:</p>\n\n<pre><code> app.get('/movies', function (req, res) {\n res.contentType('json');\n res.json({\n success: true,\n data: [{\n title: \"The Matrix\",\n year: 1999\n }, {\n title: \"Star Wars: Return of the Jedi\",\n year: 1983\n }]\n });\n });\n</code></pre>\n\n<p>The format here is significant: the JSON response must be a single object, with a <code>success</code> property, and a <code>data</code> property, which is an array of records matching the definition of the Model. Actually you can customize these properties by configuring your Proxy's <code><a href=\"#!/api/\" rel=\"\" class=\"docClass\">reader</a></code> differently, but the important thing is that the server sends the same names which the <a href=\"#!/api/\" rel=\"\" class=\"docClass\">Reader</a> 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:</p>\n\n<pre><code>Ext.define('', {\n extend: '',\n\n autoLoad: true,\n fields: ['title', 'year'],\n\n // Data removed, instead using proxy:\n proxy: {\n type: 'ajax',\n url: '/movies',\n reader: {\n type: 'json',\n root: 'data',\n successProperty: 'success'\n }\n }\n});\n</code></pre>\n\n<p>If you restart the server app, and reload the page, you should see the grid with the first two movies in it.</p>\n\n<h3>Setting up the Database</h3>\n\n<p>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 <code>mongodb</code> daemon running, typing <code>mongo</code> 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.)</p>\n\n<pre><code> $ mongo\n MongoDB shell version: 2.0.0\n connecting to: test\n &gt; use example\n switched to db example\n &gt; db.movies.insert({title: \"The Matrix\", year: 1999});\n &gt; db.movies.insert({title: \"Star Wars\", year: 1977});\n &gt; db.movies.find();\n { \"_id\" : ObjectId(\"4e7018a79abdbdfb5d235b6c\"), \"title\" : \"The Matrix\", \"year\" : 1999 }\n { \"_id\" : ObjectId(\"4e7018dd9abdbdfb5d235b6d\"), \"title\" : \"Star Wars\", \"year\" : 1977 }\n &gt; quit()\n</code></pre>\n\n<p>The find() command is like SQL <code>select *</code>. 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 <code>/movies</code> route in <code>app.js</code> to query it:</p>\n\n<pre><code>var mongoose = require('mongoose'),\n\ndb = mongoose.connect('mongodb://'),\n\n//create the movie Model using the 'movies' collection as a data-source\nmovieModel = mongoose.model('movies', new mongoose.Schema({\n title: String,\n year: Number\n}));\n\n//...\n\napp.get('/movies', function (req, res) {\n movieModel.find({}, function (err, movies) {\n res.contentType('json');\n res.json({\n success: true,\n data: movies\n });\n });\n});\n</code></pre>\n\n<p>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:</p>\n\n<p><p><img src=\"guides/editable_grid/geekflicks_readonly.png\" alt=\"\"></p></p>\n\n<p>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.</p>\n\n<p><a href=\"guides/editable_grid/\">Download project files</a></p>\n","title":"Editable Grid + Node.js"});