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 todo
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
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 todo
s, 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 todo
s and registering it as a model in geddy. Let’s define some properties for our todo
s. 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 todo
s will have a title, an id, and a status, and all three will be required. Now let's set some validations for our todo
s.
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 todo
s. 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 the
save` 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 todo
s 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 && todos.length) { %> <% for (var i in todos) { %> <div class="row todo-item"> <div class="span8"><h3><a href="/todos/<%= todos[i].id; %>/edit"><%= todos[i].title; %></a></h3></div> <div class="span4"><h3><i class="icon-list-alt"></i><%= todos[i].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[i].id == id) { return callback(null, geddy.todos[i]); } } 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 todo
s. 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[i].id == todo.id) { geddy.todos[i] = 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[i].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 todo
s 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.
Comments