Creating a Todo List App With Node.js and Geddy

Discussion in 'Design & Development' started by Samuel, Mar 27, 2012.

  1. Samuel

    Samuel
    Expand Collapse
    Admin

    Joined:
    Dec 20, 2011
    Messages:
    5,576
    Likes Received:
    71
    [​IMG]
    In this three part tutorial, we’ll be diving deep into creating a to do list management app in Node.js and Geddy. This is the second part in the series, where we’ll be creating a simple to do list management app.
    Recap

    As a quick refresher, last time we installed Node and Geddy, generated a new app, and learned how to start up the server. In this tutorial we’ll build upon what we did last time, so make sure you’ve completed that one before continuing.
    Generating the Todo Resource

    Geddy has a built in resource generator; this will allow us to automatically generate a model, controller, views, and routes for a specific resource. Our to do list app will only have one resource: todo. To generate it, just cd into your app’s directory (cd path/to/your/todo_app) and run:
    geddy resource todo1
    You should now have these files added to your app:
    • app/models/todo.js
    • app/controllers/todos.js
    • app/views/todos/
      • index.html.ejs
      • show.html.ejs
      • edit.html.ejs
      • add.html.ejs
    Your config/router.js should also have this appended to it:
    router.resource('todos');
    What it all does

    If you’re new to MVC this all might seem a little daunting to you. Don’t worry though, it’s really simple once you figure it out.
    models/todo.js: This file is where we’ll define our todo model. We’ll define a number of properties that all todo’s have. We’ll also write some data validations here.
    controllers/todos.js: This file is where all the /todos/ routes end up. Each action in this controller has a corresponding route:
    GET /todos/ => index
    POST /todos/ => create
    GET /todos/:id => show
    PUT /todos/:id => update
    DELETE /todos/:id => remove
    GET /todos/:id/add => add
    GET /todos/:id/edit => edit
    views/todos/: Each file in here corresponds to one of the GET routes that we showed you above. These are the templates that we use to generate the front end of the app. Geddy uses EJS (embedded JavaScript) as it’s templating language. It should look familiar if you’ve ever used PHP or ERB. Basically, you can use any JavaScript that you’d like in your templates.
    Getting a feel for the routes

    Now that we’ve generated a bunch of code, let’s verify that we’ve got all the routes that we need. Start the app again (geddy), and point your browser to http://localhost:4000/todos. You should see something like this
    [​IMG]
    Go ahead and try that for the other GET routes too:
    • http://localhost:4000/todos/something
    • http://localhost:4000/todos/add
    • http://localhost:4000/todos/something/edit
    All good? Alright, let’s continue.
    Creating the Todo Model

    In Geddy (and most other MVC frameworks), you use models to define the kind of data that your app will work with. We just generated a model for our todos, so let’s see what that gave us:
    var Todo = function () {
    // Some commented out code
    };

    // Some more commented out code

    Todo = geddy.model.register('Todo', Todo);
    Models are pretty simple in Geddy. We’re just creating a new constructor function for our todos and registering it as a model in geddy. Let’s define some properties for our todos. Delete all the commented out code and add this to the contructor function:
    var Todo = function () {
    this.defineProperties({
    title: {type: 'string', required: true}
    , id: {type: 'string', required: true}
    , status: {type: 'string', required: true}
    });
    };
    Our todos will have a title, an id, and a status, and all three will be required. Now let’s set some validations for our todos.
    var Todo = function () {

    this.defineProperties({
    title: {type: 'string', required: true}
    , id: {type: 'string', required: true}
    , status: {type: 'string', required: true}
    });

    this.validatesPresent('title');
    this.validatesLength('title', {min: 5});

    this.validatesWithFunction('status', function (status) {
    return status == 'open' || status == 'done';
    });

    };
    We’re validating that the title is present, that the title has a minimum length of 5 characters, and we’re using a function to validate that the status is either open or done. There are quite a few valitation functions that are built in, go ahead and check the project out on http://github.com/mde/geddy to learn more about them.
    Creating the Todo Model Adapter

    Now that we’ve set up our todo model, we can create somewhere to store our models. For the purposes of this tutorial, we’re just going to keep the data in memory. We’ll hang a todos array off of our global geddy object to stick the data in. In the next part of this series, we’ll start to get these persisted in a database.
    Editing Your init.js File

    Open up your config/init.js file. All that should be in there now is a global uncaught exception handler:
    // Add uncaught-exception handler in prod-like environments
    if (geddy.config.environment != 'development') {
    process.addListener('uncaughtException', function (err) {
    geddy.log.error(JSON.stringify(err));
    });
    }
    Right after that block of code, let’s hang our array off the geddy global:
    geddy.todos = [];
    There, now we’ve got a place to store our todos. Remember, this is in your application-memory, so it will disappear when you restart the server.
    Creating the Model-adapter

    A model-adapter provides the basic save, remove, load, and all methods a model needs. Our data source is pretty simple (just an array!), so writing our model adapter should be pretty simple too.
    Create a directory in lib called model_adapters and create a file in lib/model_adapters called todo.js. Let’s open up that file and add in some boilerplate code:
    var Todo = new (function () {
    })();
    exports.Todo = Todo;
    All we’re doing here is setting up a new blank object to be exported out to whatever ends up requiring this file. If you’d like to know a bit more about how Node’s require method works, this article has a pretty good overview. In this case, our init.js file will do the requiring.
    Require the model adapter in init.js

    So we set up a new Todo model-adapter object. It’s pretty barren right now, but we’ll get to that soon. For now, we’ll have to go back to init.js and add some code so that it’s loaded into our app when it starts up. After the geddy.todos = []; in config/init.js add these two lines:
    geddy.model.adapter = {};
    geddy.model.adapter.Todo = require(process.cwd() + '/lib/model_adapters/todo').Todo;
    We created a blank model-adapter object, and added the Todo model adapter onto it.
    Saving Todos

    Now that we have our model and model adapter in place, we can start in on the app logic. Let’s start with adding to do items to our to do list.
    Edit the save method on the adapter to save a todo instance

    When working with data, the first place you should go is the model adapter. We need to be able to save an instance of our Todo model to our geddy.todos array. So open up lib/model_adapters/todo.js and add in a save method:
    var Todo = new (function () {
    this.save = function (todo, opts, callback) {

    if (typeof callback != 'function') {
    callback = function(){};
    }

    todo.saved = true;
    geddy.todos.push(todo);
    return callback(null, todo);

    }
    })();
    All we have to do is set the instance’s saved property to true and push the item into the geddy.todos array. In Node, it’s best to do all I/O in a non-blocking way, so it’s a good idea to get in the habit of using callbacks to pass data around. For this tutorial it doesn’t matter as much, but later on when we start persisting things, it’ll come in handy. You’ll notice that we made sure that the callback is a function. If we don’t do that and use save without a callback, we’d get an error. Now let’s move on to the controller create action.
    Edit the create action to save a todo instance

    Go ahead and take a look at the create action in app/controllers/todos.js:
    this.create = function (req, resp, params) {
    // Save the resource, then display index page
    this.redirect({controller: this.name});
    };
    Pretty simple, right? Geddy has stubbed it out for you. So let’s modify it a little bit:
    this.create = function (req, resp, params) {
    var self = this
    , todo = geddy.model.Todo.create({
    title: params.title
    , id: geddy.string.uuid(10)
    , status: 'open'
    });
    todo.save(function (err, data) {
    if (err) {
    params.errors = err;
    self.transfer('add');
    }
    else {
    self.redirect({controller: self.name});
    }
    });
    };
    First, we create a new instance of the Todo model with geddy.model.Todo.create, passing in the title that our form will post up to us, and setting up the defaults for the id and status.
    Then we call the save method that we created on the model adapter and redirect the user back to the /todos route. If it didn’t pass validation, or we get an error, we use the controller’s transfer method to transfer the request back over to the add action.
    Edit add.html.ejs

    Now it’s time for us to set up the add template. Take a look at app/views/todos/add.html.ejs, it should look like this:
    <div class="hero-unit">
    <h3>Params</h3>
    <ul>
    <% for (var p in params) { %>
    <li><%= p + ': ' + params[p]; %></li>
    <% } %>
    </ul>
    </div>
    We won’t be needing that

    • for our use case, so let’s get rid of it for now. Make your add.html.ejs look like this:

    • <div class="hero-unit">
      <%= partial('_form', {params: params}); %>
      </div>
      An Intro to Partials

      Partials give you an easy way to share code between your templates.​
      You’ll notice that we’re using a partial in this template. Partials give you an easy way to share code between your templates. Our add and edit templates are both going to use the same form, so let’s create this form partial now. Create a new file in the views/todos/ directory called _form.html.ejs. We use an underscore to easily tell if this template is a partial. Open it up and add in this code:
      <%
      var isUpdate = params.action == 'edit'
      , formTitle = isUpdate ? 'Update this To Do Item' : 'Create a new To Do Item'
      , action = isUpdate ? '/todos/' + todo.id + '?_method=PUT' : '/todos'
      , deleteAction = isUpdate ? '/todos/' + todo.id + '?_method=DELETE' : ''
      , btnText = isUpdate ? 'Update' : 'Add'
      , doneStatus = isUpdate ? 'checked' : ''
      , titleValue = isUpdate ? todo.title : ''
      , errors = params.errors;
      %>
      <form id="todo-form" class="form-horizontal" action="<%= action %>" method="POST">
      <fieldset>
      <legend><%= formTitle %></legend>
      <div class="control-group">
      <label for="title" class="control-label">Title</label>
      <div class="controls">
      <input type="text" class="span6" placeholder="enter title" name="title" value='<%= titleValue %>'/>
      <% if (errors) { %>
      <p>
      <% for (var p in errors) { %>
      <div><%= errors[p]; %></div>
      <% } %>
      </p>
      <% } %>
      </div>
      </div>
      <% if (isUpdate) { %>
      <div class="control-group">
      <label for="status">Status</label>
      <div class="controls">
      <select name="status">
      <option>open</option>
      <option>done</option>
      </select>
      </div>
      </div>
      <% } %>
      <div class="form-actions">
      <input type="submit" class="btn btn-primary" value="<%= btnText %>"/>
      <% if (isUpdate) { %>
      <button type="submit" formaction="<%= deleteAction %>" formmethod="POST" class="btn btn-danger">Remove</button>
      <% } %>
      </div>
      </fieldset>
      </form>
      Whoa, that’s a lot of code there! Let’s see if we can walk through it. Since two different templates are going to be using this partial, we’ve got to make sure the form looks right in both of them. Most of this code is actually boilerplate from Twitter’s Bootstrap. It’s what allows this app to look so good right off the bat (and on mobile devices too!).
      To make this app look even better, you can use the CSS file provided in the demo app download.​
      The first thing we did was set up some variables for us to use. In the add action we’re passing a params object down to the template in the respond method call. This gives us a few things – it tells us what controller and action this request has been routed to, and gives us any query parameters that were passed in the url. We set up the isUpdate variable to see if we’re currently on the update action, and then we set up a few more variables to help clean up our view code.
      From there, all we did was make a form. If we’re on the add action, we just render the form as is. If we’re on the edit action, we fill in the form to let the user update the fields.
      Notice that the form will send a POST request to the /todos/ with a _method=PUT parameter. Geddy uses the standard method override parameter to allow you to send PUT and DELETE requests up from the browser without having to use JavaScript. (on the front end at least!)
      The last little detail we need to take a look at is that “Remove” button. We’re using html5’s formaction attribute to change the action for this form. You’ll notice that this button’s formaction sends a POST request up to the /todos/:id route with a _method=DELETE parameter. This will hit the remove action on the controller, which we’ll get to later.
      Restart your server (geddy) and visit http://localhost:4000/todos/add to see your template in action. Create a To Do item while you’re at it.
      Listing all Todos

      Now that we have user input To Do items being added into our geddy.todos array, we should probably list them somewhere. Let’s start in on the all method in the model-adapter.
      Edit the all method on the adapter to list all todos

      Let’s open lib/model_adapters/todo.js again and add an all method right above thesave` method:
      this.all = function (callback) {
      callback(null, geddy.todos);
      }
      This is probably the simplest model-adapter method that we’ll create today, all it does is accept a callback and call it with the an error (which is always null for now, we’ll upgrade this method in the next tutorial), and geddy.todos.
      Edit the index action to show all todos

      Open up /app/controllers/todos.js again and take a look at the index action. It should look something like this:
      this.index = function (req, resp, params) {
      this.respond({params: params});
      };
      This part is really simple, we just use the all method that we just defined on the model-adapter to get all the todos and render them:
      this.index = function (req, resp, params) {
      var self = this;
      geddy.model.adapter.Todo.all(function(err, todos){
      self.respond({params: params, todos: todos});
      });
      };
      That’s it for the controller, now onto the view.
      Edit index.html.ejs

      Take a look at /app/views/todos/index.html.ejs, it should look like this:
      <div class="hero-unit">
      <h3>Params</h3>
      <ul>
      <% for (var p in params) { %>
      <li><%= p + ': ' + params[p]; %></li>
      <% } %>
      </ul>
      </div>
      Looks a lot like the add.html.ejs template doesn’t it. Again, we won’t need the params boilerplate here, so take that out, and make your index.html.ejs template look like this:
      <div class="hero-unit">
      <h2>To Do List</h2>
      <a href="/todos/add" class="btn pull-right">Create a new To Do</a></p>
      </div>
      <% if (todos &amp;&amp; todos.length) { %>
      <% for (var i in todos) { %>
      <div class="row todo-item">
      <div class="span8"><h3><a href="/todos/<%= todos.id; %>/edit"><%= todos.title; %></a></h3></div>
      <div class="span4"><h3><i class="icon-list-alt"></i><%= todos.status; %></h3></div>
      </div>
      <% } %>
      <% } %>
      This one is also pretty simple, but this time we’ve got a loop in our template. In the header there we’ve added a button to add new todo’s. Inside the loop we’re generating a row for each todo, displaying it’s title (as a link to it’s edit page), and it’s status.
      To check it out, go to http://localhost:4000/todos.
      Editing a Todo

      Now that we have a link to the edit page, we should probably make it work!
      Create a load method in the model adapter

      Open up your model adapter again (/lib/model_adapters/todo.js). We’re going to add in a load method so that we can load a specific todo and use it in our edit page. It doesn’t matter where you add it, but for now let’s put it between the all method and the save method:
      this.load = function (id, callback) {
      for (var i in geddy.todos) {
      if (geddy.todos.id == id) {
      return callback(null, geddy.todos);
      }
      }
      callback({message: "To Do not found"}, null);
      };
      This load method takes an id and a callback. It loops through the items in geddy.todos and checks to see if the current item’s id matches the passed in id. If it does, it calls the callback, passing the todo item back. If it doesn’t find a match, it calls the callback with a error. Now we need to use this method in the todos controller’s show action.
      Edit the edit action to find a todo

      Open up your todos controller again and take a look at it’s edit action. It should look something like this:
      this.edit = function (req, resp, params) {
      this.respond({params: params});
      };
      Let’s use the load method that we just created:
      this.edit = function (req, resp, params) {
      var self = this;
      geddy.model.Todo.load(params.id, function(err, todo){
      self.respond({params: params, todo: todo});
      });
      };
      All we’re doing here is loading the todo and sending it down to the template to be rendered. So let’s take a look at the template.
      Edit edit.html.ejs

      Open up /app/views/todos/edit.html.ejs. Once again we’re not going to need the params boilerplate, so let’s remove it. Make your edit.html.ejs look like this:
      <div class="hero-unit">
      <%= partial('_form', {params: params, todo: todo}); %>
      </div>
      This should look very similar to the add.html.ejs file we just edited. You’ll notice that we’re sending a todo object down to the partial as well as the params this time. The cool thing is, since we already wrote the partial, this is all we’ll have to do to get the edit page to show up correctly.
      Restart the server, create a new todo and click the link to see how this works. Now let’s make that update button work!
      Edit the save method in the model-adapter

      Open up the model-adapter again and find the save method. we’re going to be adding a bit to it so that we can save over existing todos. Make it look like this:
      this.save = function (todo, opts, callback) {
      if (typeof callback != 'function') {
      callback = function(){};
      }
      var todoErrors = null;
      for (var i in geddy.todos) {
      // if it's already there, save it
      if (geddy.todos.id == todo.id) {
      geddy.todos = todo;
      todoErrors = geddy.model.Todo.create(todo).errors;
      return callback(todoErrors, todo);
      }
      }
      todo.saved = true;
      geddy.todos.push(todo);
      return callback(null, todo);
      }
      This loops over all the todo’s in geddy.todos and if the id is already there, it replaces that todo with the new todo instance. We’re doing some stuff here to make sure that our validations work on update as well as create – in order to do this we have to pull the errors property off of a new model instance and pass that back in the callback. If it passed validations, it’ll just be undefined and our code will ignore it. If it didn’t pass, todoErrors will be an array of validation errors.
      Now that we have that in place, let’s work on our controller’s update action.
      Edit the update action to find a todo, change the status, and save it

      Go ahead and open up the controller again and find the ‘update’ action, it should look something like this:
      this.update = function (req, resp, params) {
      // Save the resource, then display the item page
      this.redirect({controller: this.name, id: params.id});
      };
      You’ll want to edit it to make it look like this:
      this.update = function (req, resp, params) {
      var self = this;
      geddy.model.adapter.Todo.load(params.id, function (err, todo) {
      todo.status = params.status;
      todo.title = params.title;
      todo.save(function (err, data) {
      if (err) {
      params.errors = err;
      self.transfer('edit');
      }
      else {
      self.redirect({controller: self.name});
      }
      });
      });
      };
      What we’re doing here is loading the requested todo, editing some of it’s properties, and saving the todo again. The code we just wrote in the model-adapter should handle the rest. If we get an error back, that means the new properties didn’t pass validation, so we’ll transfer the request back to the edit action. If we didn’t get an error back, we’ll just redirect the request back over to the index action.
      Go ahead and try it out. Restart the server, create a new todo, click on it’s edit link, change the status to done, and see that it get’s updated in the index. If you want to verify that you have your validations working, try changing the title to something shorter than 5 characters.
      Now let’s get that “Remove” button working.
      Removing a Todo

      By now we’ve got a working to do list application, but if you start using it for a while, it’s going to get tough to find the todo item that you’re looking for on that index page. Let’s make that “Remove” button work so we can keep our list nice and short.
      Create a remove method in the model-adapter

      Let’s open up our model-adapter again, this time we’re going to want to add a remove method in there. Add this right after the save method:
      this.remove = function(id, callback) {
      if (typeof callback != 'function') {
      callback = function(){};
      }
      for (var i in geddy.todos) {
      if (geddy.todos.id == id) {
      geddy.todos.splice(i, 1);
      return callback(null);
      }
      }
      return callback({message: "To Do not found"});
      }
      This one is pretty simple, it should look a lot like the load method. It loops through all the todos in geddy.todos to find the id that we’re looking for. It then splices that item out of the array and calls the callback. If it doesn’t find it in the array, it calls the callback with an error.
      Let’s use this in our controller now.
      Edit the remove action

      Open up your controller again and fing the remove action. It should look something like this:
      this.remove = function (req, resp, params) {
      this.respond({params: params});
      };
      Edit it to make it look like this:
      this.remove = function (req, resp, params) {
      var self = this;
      geddy.model.adapter.Todo.remove(params.id, function(err){
      if (err) {
      params.errors = err;
      self.transfer('edit');
      }
      else {
      self.redirect({controller: self.name});
      }
      });
      }
      We pass the id that we got from the params in the form post into the remove method that we just created. If we get an error back, we redirect back to the edit action (we’re assuming the form posted the wrong info). If we didn’t get an error back, just send the request over to the index action.
      Thats it! We’re done.
      You can test the remove feature by restarting your server, creating a new todo item, clicking on it’s link, then clicking on the “Remove” button. If you did it right, you should be back on the index page with that item removed.
      The Next Steps

      In the next tutorial we’ll use http://i.tv’s awesome mongodb-wrapper module to persist our todo’s into MongoDB. With Geddy, this will be easy; all we’ll have to change is the model-adapter.
      If you have any questions, please leave a comment here, or open up an issue on github.
      [​IMG]

      [​IMG]

    [​IMG]
    [​IMG] [​IMG] [​IMG] [​IMG] [​IMG]

    Continue reading...
     

Share This Page