Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

762 lines (574 sloc) 32.588 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Geddy - A Structured Node.js Framework</title>
<meta name="description" content="">
<meta name="author" content="">
<meta name="viewport" content="width=device-width">
<!-- Le HTML5 shim, for IE6-8 support of HTML elements -->
<!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script src="js/bootstrap.min.js"></script>
<link rel="stylesheet" href="css/bootstrap.min.css">
<link rel="stylesheet" href="css/bootstrap.min.responsive.css">
<link rel="stylesheet" href="js/google-code-prettify/prettify.css">
<script src="js/google-code-prettify/prettify.js"></script>
<!-- Le styles -->
<style>
@media (min-width: 980px) {
body {
padding-top: 60px;
}
}
code {
color: #777;
}
p, li {
line-height: 200%;
}
h2, h3 {
line-height: 200%;
}
.tutorial pre.prettyprint {
margin: 20px 0;
}
.navbar {
box-shadow: 0px 0px 3px #000;
border-bottom: 1px solid #999;
}
.code-box {
padding-top: 28px;
}
.code-box .syntaxhighlighter {
box-shadow: 0px 0px 3px #000;
border-radius: 3px;
}
.container > .hero-unit {
background-color: rgba(200, 200, 200, .5);
box-shadow: 0px 0px 3px #bababa;
border: 1px solid #EDEDED;
}
.tutorial {
padding: 20px 40px;
line-height: 150%;
}
@media (max-width: 480px) {
.hero-unit {
padding: 20px 20px 30px 20px;
}
.hero-unit h1 {
font-size: 32px;
}
.hero-unit p {
font-size: 12px;
}
}
</style>
<!-- Le fav and touch icons -->
<link rel="shortcut icon" href="images/favicon.ico">
<link rel="apple-touch-icon" href="images/apple-touch-icon.png">
<link rel="apple-touch-icon" sizes="72x72" href="images/apple-touch-icon-72x72.png">
<link rel="apple-touch-icon" sizes="114x114" href="images/apple-touch-icon-114x114.png">
</head>
<body style="background-image: url(http://subtlepatterns.com/patterns/whitey.png); background-attachment: initial; background-origin: initial; background-clip: initial; background-color: initial; background-position: initial initial; background-repeat: initial initial; " onload="prettyPrint()">
<div class="navbar navbar-fixed-top">
<div class="navbar-inner">
<div class="container">
<a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
<span class="i-bar"><i class="icon-chevron-down icon-white"></i></span>
</a>
<a class="brand" href="index.html">GeddyJS</a>
<div class="nav-collapse">
<ul class="nav">
<li><a href="index.html">Home</a></li>
<li><a href="features.html">Features</a></li>
<li class="active"><a href="tutorial.html">Tutorial</a></li>
<!--<li><a href="docs.html">Docs</a></li>-->
</ul>
<ul class="nav pull-right">
<li><a href="http://github.com/mde/geddy">Get it on Github</a></li>
</ul>
</div><!--/.nav-collapse -->
</div>
</div>
</div>
<div class="container">
<div class="hero-unit">
<h1>Tutorial</h1>
<p class="pull-right">A quick guide to Geddy</p>
</div>
<div class="row">
<div class="span3">
<div class="well">
<h5>Introduction</h5>
<ul>
<li><a href="#into">Goal</a></li>
</ul>
<h5>Installing Geddy</h5>
<ul>
<li><a href="#install">Install via npm</a></li>
</ul>
<h5>Executable</h5>
<ul>
<li><a href="#generate-app">Initial app generation</a></li>
<li><a href="#start-app">Starting a geddy app</a></li>
<li><a href="#view-app">View your app</a></li>
<li><a href="#gen-resource">Generate a resource</a></li>
</ul>
<h5>Models and Adapters</h5>
<ul>
<li><a href="#models">Models</a></li>
<li><a href="#model-adapter">Model Adapters</a></li>
</ul>
<h5>Building the App</h5>
<ul>
<li><a href="#saving-todos">Creating To Do Items</a></li>
<li><a href="#list-todos">Listing the Items</a></li>
<li><a href="#show-todo">Showing a To Do</a></li>
<li><a href="#update-todo">Updating an Item</a></li>
</ul>
</div>
</div>
<div class="span9" style="background: #efefef; border-radius: 3px; box-shadow: 0px 0px 2px #ccc;">
<div class="tutorial">
<h2 id="intro">Welcome to the Geddy Tuorial</h2>
<p>In this tutorial we'll go over how to get Geddy installed and how to create an example to do list manager app. Check out the finished product here: <a href="https://github.com/mde/geddy/tree/master/examples/todo_app">Example App</a>.</p>
<p>We'll cover:</p>
<ul>
<li>How to generate an app</li>
<li>Setting up a RESTful resource for your app</li>
<li>Review how to use Bootstrap to make a mobile version of your app, automatically</li>
<li>Using Geddy Models</li>
<li>Using a model-adapter to interact with your data</li>
<li>How to use init.js in your app's startup</li>
<li>How views in Geddy work</li>
<li>How to use controllers to tie everything together</li>
</ul>
<h2 id="install">Installing Geddy:</h2>
<p>First, make sure that you have <a href="http://nodejs.org">node</a> installed on your machine.</p>
<p>Next, install Jake and Geddy. Jake is a Javascript build-tool similar to Ruby's Rake.</p>
<pre class="prettyprint">$&gt; npm install -g jake geddy</pre>
<blockquote class="pull-right">(Note running this command may require super-user access, i.e., `sudo`.)</blockquote>
<h2 id="generate-app">Create Your first app</h2>
<p>Now use the <code>geddy</code> executable to generate a basic app-structure.</p>
<pre class="prettyprint">$&gt; geddy app todo_app</pre>
<p>The <code>geddy</code> executable can do many things, to generate an app just type <code>geddy app {{app_name}}</code> where <code>{{app_name}}</code> is the name of the directory you want to put your app in. Geddy will create the directory and create a basic app structure for you.</p>
<h2 id="start-app">Start Your App</h2>
<p>To start your app, all you need to do is <code>cd</code> into your app's directory and run <code>geddy</code>.</p>
<pre class="prettyprint">
$&gt; cd todo_app
$&gt; geddy
</pre>
<p>Geddy will default to running in development mode, which means your server directs all its output to the console, instead of to logs.</p>
<h2 id="view-app">Check out your app</h2>
<p>After running the <code>geddy</code> command, your app should be running on port 4000. Visit <a href="http://localhost:4000">http://localhost:4000</a> in your browser to see your app.</p>
<h3>optional: check out your app on a mobile phone</h3>
<ul>
<li>point your mobile phone's browser to your computer's port 4000</li>
<li>OR open up your favorite phone simulator and go to <a href="http://localhost:4000">http://localhost:4000</a></li>
<li>OR resize your browser to at least 480px wide</li>
</ul>
<h2 id="gen-resource">Generate a resource</h2>
<p>Now, lets actually get started building our To Do list manager. First, we'll need to generate the <code>todo</code> resource. We do this using the <code>geddy</code> executable as well:</p>
<pre class="prettyprint">geddy resource todo</pre>
<p>What did that do?</p>
<ul>
<li>It generated a <code>todo</code> model</li>
<li>It generated a <code>todos</code> controller</li>
<li>It generated views for:
<ul><li>an index of <code>todo</code>'s</li>
<li>a single <code>todo</code></li>
<li>creating a <code>todo</code></li>
<li>editing a <code>todo</code></li></ul></li>
<li>It generated these routes:
<ul><li><code>/todos</code> (GET)</li>
<li><code>/todos</code> (POST)</li>
<li><code>/todos/add</code> (GET)</li>
<li><code>/todos/:id/edit</code> (GET)</li>
<li><code>/todos/:id</code> (GET)</li>
<li><code>/todos/:id</code> (PUT)</li>
<li><code>/todos/:id</code> (DELETE)</li></ul></li>
</ul>
<p>Now, lets run the app again: </p>
<pre class="prettyprint">geddy</pre>
<p>You should be able to see your new resource's index veiw at <a href="http://localhost:4000/todos">http://localhost:4000/todos</a></p>
<h2 id="models">Creating the Todo model</h2>
<p>We're ready to start in on modeling our data. Geddy provides us with some pretty cool tools to do this:</p>
<ul>
<li>Validation</li>
<li>Typed Data</li>
<li>Instance Methods</li>
<li>Static Methods</li>
</ul>
<p>These tools should look familiar to anyone who's used an ORM-system like Ruby's ActiveRecord, or DataMapper.</p>
<p>Go ahead and open up <code>app/models/todo.js</code>. Read through the commented out code there for some ideas on what you can do with models. We'll be writing our model from scratch for this tutorial, so lets leave that commented out. (It's been deleted in the example app.)</p>
<p>So, minus the commented out code, you should have a file that looks like this:</p>
<pre class="prettyprint">
var Todo = function () {
};
Todo = geddy.model.register('Todo', Todo);
</pre>
<p>Let's add three properties onto the <code>todo</code> model:</p>
<ul>
<li>title</li>
<li>status</li>
<li>id</li>
</ul>
<p>To do this all we have to do is define some properties using the `defineProperties` method:</p>
<pre class="prettyprint">
var Todo = function () {
this.defineProperties({
title: {type: 'string', required: true}
, status: {type: 'string', required: true}
, id: {type: 'string', required: true}
});
};
Todo = geddy.model.register('Todo', Todo);
</pre>
<p>The `defineProperties` method takes any number of properties to be added to the model. The keys in the object will be added as properties on the model. The values are just objects that describe the properties. To define the type of the property, use the type option, we want ours to be strings. These properties should all be required, so set `required` to `true`.</p>
<p>To learn more, check out the <a href="https://github.com/mde/geddy/blob/master/README.md">readme</a>.</p>
<p>There's also more detailed validation API. While we're here, lets use that API to set up some more validations:</p>
<pre class="prettyprint">
this.validatesPresent('title');
this.validatesLength('title', {min: 5});
this.validatesWithFunction('status', function (status) {
return status == 'open' || status == 'done';
});
</pre>
<p>For the <code>title</code> property, we made sure that the property is always present and we made sure that the <code>title</code> property is a minimum of 5 characters long.</p>
<p>For the 'status' property, we used a function to validate that the property is always set to either <code>open</code> or <code>done</code>.</p>
<p>For more information about Geddy's Models, you can check out the <a href="https://github.com/mde/geddy/wiki/Models">Model wiki page</a>.</p>
<h2 id="model-adapter">Creating a Todo model-adapter</h2>
<p>Now that we've set up our <code>todo</code> 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 <code>todos</code> array off of our global <code>geddy</code> object to stick the data in. If you want to make this app more robust after the tutorial, you could write an adapter to put your data in a SQL database, or Redis, or even just write it to a text-file.</p>
<h3>Editing your init.js file</h3>
<p>Open up your <code>config/init.js</code> file. All that should be in there now is a global uncaught exception handler.</p>
<pre class="prettyprint">
if (geddy.config.environment != 'development') {
process.addListener('uncaughtException', function (err) {
geddy.log.error(JSON.stringify(err));
});
}
</pre>
<p>Right after that block of code, lets hang our array off the <code>geddy</code> global:</p>
<pre class="prettyprint">geddy.todos = [];</pre>
<p>There, now we've got a place to store our <code>todo</code>'s. This is in your application-memory, so it will disappear when you restart the server.</p>
<h3>Creating the model adapter</h3>
<p>A model-adapter provides the basic CRUD methods a model needs. Our data source is pretty simple (just an array!), so writing our model adapter should be pretty simple too.</p>
<p>Create a directory in <code>lib</code> called <code>model_adapters</code></p>
<pre class="prettyprint">$&gt; mkdir lib/model_adapters</pre>
<p>And create a file in <code>lib/model_adapters</code> called <code>todo.js</code></p>
<pre class="prettyprint">$&gt; touch lib/model_adapters/todo.js</pre>
<p>Lets open up that file and add in some boilerplate code:</p>
<pre class="prettyprint">
var Todo = new (function () {
})();
exports.Todo = Todo;
</pre>
<h3>Require the model adapter in init.js</h3>
<p>So we set up a new <code>Todo</code> 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 <code>init.js</code> and add some code so that it's loaded into our app when it starts up. After the <code>geddy.todos = [];</code> in <code>config/init.js</code> add these two lines:</p>
<pre class="prettyprint">
geddy.model.adapter = {};
geddy.model.adapter.Todo = require(process.cwd() + '/lib/model_adapters/todo').Todo;
</pre>
<blockquote class="pull-right">We're working on making this nicer.</blockquote>
<p>We created a blank model-adapter object, and add the <code>Todo</code> model adapter onto it.</p>
<h2 id="saving-todos">Saving todos</h2>
<p>Now that we have our model and model adapter in place, we can start in on the app logic. Lets start with adding to do items to our to do list.</p>
<h3>Edit the save method on the adapter to save a todo instance</h3>
<p>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 <code>Todo</code> model to our <code>geddy.todos</code> array. So open up <code>lib/model_adapters/todo.js</code> and add in a <code>save</code> method:</p>
<pre class="prettyprint">
this.save = function (todo) {
todo.saved = true;
geddy.todos.push(todo);
};
</pre>
<p>All we have to do is set the instance's <code>saved</code> property to true and push the item into the <code>geddy.todos</code> array. Now lets move on to the controller <code>create</code> action.</p>
<h3>Edit the create action to save a todo instance</h3>
<p>Go ahead and take a look at the <code>create</code> action in <code>app/controllers/todos.js</code></p>
<pre class="prettyprint">
this.create = function (req, resp, params) {
// Save the resource, then display index page
this.redirect({controller: this.name});
};
</pre>
<p>Pretty simple, right? It's stubbed it out for you. So let's modify it a little bit:</p>
<pre class="prettyprint">
this.create = function (req, resp, params) {
var todo = geddy.model.Todo.create({title: params.title, id: geddy.string.uuid(10), status: 'open'});
if (todo.isValid()) {
todo.save();
this.redirect({controller: this.name});
} else {
this.redirect({controller: this.name, action: 'add?error=true'});
}
};
</pre>
<p>First, we create a new instance of the <code>Todo</code> model with <code>geddy.model.Todo.create</code>, passing in the title that our form will post up to us, and setting up the defaults for the id and status.</p>
<p>Then we check to see if the model passed validation, if it did, we call the <code>save</code> method that we created on the model adapter and redirect the user back to the <code>/todos</code> route. If it didn't pass validation, we redirect the user back to the </code>/todos/add</code> route and pass an error as a query parameter.</p>
<p>Geddy has built-in sessions too, so this might be another good spot to improve your app after you finish the tutorial.</p>
<h3>Edit add.html.ejs</h3>
<p>Now it's time for us to set up the <code>add</code> template. Take a look at <code>app/views/todos/add.html.ejs</code>, it should look like this:</p>
<pre class="prettyprint">
&lt;div class=&quot;hero-unit&quot;&gt;
&lt;h3&gt;Params&lt;/h3&gt;
&lt;ul&gt;
&lt;% for (var p in params) { %&gt;
&lt;li&gt;&lt;%= p + ': ' + params[p]; %&gt;&lt;/li&gt;
&lt;% } %&gt;
&lt;/ul&gt;
&lt;/div&gt;
</pre>
<p>We won't be needing that <code>ul</code> for our use case, so lets get rid of it for now. Make your <code>add.html.ejs</code> look like this:</p>
<pre class="prettyprint">
&lt;div class=&quot;hero-unit&quot;&gt;
&lt;h2&gt;Add a ToDo:&lt;/h2&gt;
&lt;form action=&quot;/todos&quot; method=&quot;POST&quot;&gt;
&lt;% if (params.error) {
var title = 'title not long enough, must be 5 characters or more.';
} else {
var title = 'enter title';
}
%&gt;
&lt;input type=&quot;text&quot; class=&quot;span6&quot; placeholder=&quot;&lt;%= title %&gt;&quot; name=&quot;title&quot;&gt;
&lt;input type=&quot;submit&quot; class=&quot;btn btn-primary&quot;&gt;
&lt;/form&gt;
&lt;/div&gt;
</pre>
<p>All we're doing here is adding in a heading and a form. We set the form's <code>action</code> to <code>/todos</code> and it's <code>method</code> to <code>POST</code>. When the server receives a <code>POST</code> to <code>/todos</code>, it routes the request over to the <code>Todos</code> controller's <code>create</code> action.</p>
<blockquote class="pull-right"><h4>Sidenote:</h4> If you want to make this a little prettier, you can copy the <a href="https://github.com/mde/geddy/blob/master/examples/todo_app/public/css/style.css">example app's style.css file</a> into your app.</blockquote>
<p>The other stuff is pretty self explanatory - check to see if the error param is there, and if it is, display an error message. If not, show some placeholder text.</p>
<p>Go ahead and visit <a href="http://localhost:4000/todos/add">http://localhost:4000/todos/add</a> to see your template in action. Create a To Do item while you're at it.</p>
<h2 id="list-todos">Listing all todos</h2>
<p>Now that we have user input To Do items being added into our <code>geddy.todos</code> array, we should probably list them somewhere. Lets start in on the <code>index</code> action in the <code>todos</code> controller.</p>
<h3>Edit the index action to show all todos</h3>
<p>Open up <code>/app/controllers/todos.js</code> again and take a look at the <code>index</code> action. It should look something like this:</p>
<pre class="prettyprint">
this.index = function (req, resp, params) {
this.respond({params: params});
};
</pre>
<p>This part is really simple, just replace the <code>params</code> property in the template variable object with a <code>todos</code> property and set it to <code>geddy.todos</code>, to pass that list down into your view.</p>
<pre class="prettyprint">
this.index = function (req, resp, params) {
this.respond({todos: geddy.todos});
};
</pre>
<p>That's it for the controller, now onto the view.</p>
<h3>edit index.html.ejs</h3>
<p>Take a look at <code>/app/views/todos/index.html.ejs</code>, it should look like this:</p>
<pre class="prettyprint">
&lt;div class=&quot;hero-unit&quot;&gt;
&lt;h3&gt;Params&lt;/h3&gt;
&lt;ul&gt;
&lt;% for (var p in params) { %&gt;
&lt;li&gt;&lt;%= p + ': ' + params[p]; %&gt;&lt;/li&gt;
&lt;% } %&gt;
&lt;/ul&gt;
&lt;/div&gt;
</pre>
<p>Looks a lot like the <code>add.html.ejs</code> template doesn't it. Again, we won't need the params boilerplate here, so take that out, and make your <code>index.html.ejs</code> template look like this:</p>
<pre class="prettyprint">
&lt;div class=&quot;hero-unit&quot;&gt;
&lt;h2&gt;To Do List&lt;/h2&gt;
&lt;a href=&quot;/todos/add&quot; class=&quot;btn pull-right&quot;&gt;Create a new To Do&lt;/a&gt;
&lt;/div&gt;
&lt;% if (todos.length) { %&gt;
&lt;% for (var i in todos) { %&gt;
&lt;div class=&quot;row todo-item&quot;&gt;
&lt;div class=&quot;span8&quot;&gt;
&lt;h3&gt;
&lt;a href=&quot;/todos/&lt;%= todos[i].id; %&gt;&quot;&gt;&lt;%= todos[i].title; %&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;/div&gt;
&lt;div class=&quot;span4&quot;&gt;
&lt;h3&gt;
&lt;i class=&quot;icon-list-alt&quot;&gt;&lt;/i&gt;&lt;%= todos[i].status; %&gt;
&lt;/h3&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;% } %&gt;
&lt;% } %&gt;
</pre>
<p>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 <code>todo</code>'s. Inside the loop we're generating a row for each <code>todo</code>, displaying it's title (as a link to it's <code>show</code> page), and it's status.</p>
<p>To check it out, go to <a href="http://localhost:4000/todos">http://localhost:4000/todos</a>.</p>
<h2 id="show-todo">Showing a todo</h2>
<p>Now that we have a link to the <code>show</code> page, we should probably work on the <code>show</code> page.</p>
<h3>Create a load method in the model adapter</h3>
<p>Open up your model adapter again (<code>/lib/model_adapters/todo.js</code>) - by now, it should look something like this: </p>
<pre class="prettyprint">
var Todo = new (function () {
this.save = function (todo) {
todo.saved = true;
geddy.todos.push(todo);
};
})();
exports.Todo = Todo;
</pre>
<p>Lets define a <code>load</code> method in this adapter, for getting one of the todos from the list:</p>
<pre class="prettyprint">
var Todo = new (function () {
this.load = function (id, callback) {
for (var i in geddy.todos) {
if (geddy.todos[i].id == id) {
return callback(geddy.todos[i]);
}
}
callback({});
};
this.save = function (todo) {
todo.saved = true;
geddy.todos.push(todo);
};
})();
exports.Todo = Todo;
</pre>
<p>This <code>load</code> method takes an <code>id</code> and a <code>callback</code>. It loops through the items in <code>geddy.todos</code> and checks to see if the current item's <code>id</code> matches the passed in <code>id</code>. If it does, it calls the callback, passing the <code>todo</code> item back. If it doesn't find a match, it calls the callback with a blank object. Now we need to use this method in the <code>todos</code> controller's show action.</p>
<p>This is a simple example that finds a single item by iterating the entire collection, but you could write an adapter that does a SQL-call to a database, or makes an API-call to a Web service.</p>
<h3>Edit the show action to find a todo</h3>
<p>Open up your <code>todos</code> controller again and take a look at it's <code>show</code> action. It should look something like this:</p>
<pre class="prettyprint">
this.show = function (req, resp, params) {
this.respond({params: params});
};
</pre>
<p>Lets use the load method that we just created:</p>
<pre class="prettyprint">
this.show = function (req, resp, params) {
var self = this;
geddy.model.adapter.Todo.load(params.id, function (todo) {
self.respond({todo: todo});
});
};
</pre>
<p>All we're doing here is loading the <code>todo</code> and sending it down to the template to be rendered. So let's take a look at the template.</p>
<h3>Edit show.html.ejs</h3>
<p>Open up <code>/app/views/todos/show.html.ejs</code>, it should look like this:</p>
<pre class="prettyprint">
&lt;div class=&quot;hero-unit&quot;&gt;
&lt;h3&gt;Params&lt;/h3&gt;
&lt;ul&gt;
&lt;% for (var p in params) { %&gt;
&lt;li&gt;&lt;%= p + ': ' + params[p]; %&gt;&lt;/li&gt;
&lt;% } %&gt;
&lt;/ul&gt;
&lt;/div&gt;
</pre>
<p>Once again we're not going to need the params boilerplate, so lets remove it. Make your <code>show.html.ejs</code> look like this:</p>
<pre class="prettyprint">
&lt;div class=&quot;hero-unit&quot;&gt;
&lt;h3&gt;&lt;%= todo.title; %&gt;&lt;/h3&gt;
&lt;div class=&quot;pull-right&quot;&gt;
&lt;% if (todo.status == 'open') { %&gt;
&lt;form id=&quot;finish-todo&quot; class=&quot;hidden&quot; action=&quot;/todos/&lt;%= todo.id; %&gt;&quot;&gt;
&lt;input type=&quot;hidden&quot; name=&quot;status&quot; value=&quot;done&quot;&gt;
&lt;input type=&quot;hidden&quot; name=&quot;id&quot; value=&quot;&lt;%= todo.id; %&gt;&quot;&gt;
&lt;input type=&quot;hidden&quot; name=&quot;title&quot; value=&quot;&lt;%= todo.title; %&gt;&quot;&gt;
&lt;/form&gt;
&lt;span&gt;
&lt;a href=&quot;#&quot; class=&quot;btn btn-primary btn-large&quot; id=&quot;finish-btn&quot;&gt;Finish To Do&lt;/a&gt;
&lt;/span&gt;
&lt;script&gt;
var form = $('#finish-todo');
$('#finish-btn').click(function (e) {
e.preventDefault();
$.ajax({
type: 'PUT',
url: form.attr('action'),
data: form.serialize()
}).done(function (msg) {
$(e.target).replaceWith('&lt;p&gt;This todo is finished!&lt;/p&gt;');
});
});
&lt;/script&gt;
&lt;% } else { %&gt;
&lt;p&gt;This to do is finished!&lt;/p&gt;
&lt;% } %&gt;
&lt;/div&gt;
&lt;/div&gt;
</pre>
<p>We're doing a few more complicated things now. First off, you'll notice that we've got a script block in there. It's nice to see that you can do all this with Ajax as well.</p>
<p>This template is basically a big if statement. If the status is <code>open</code>, display the title of the <code>todo</code> and a button to finish it. It's got a hidden form in there as well, this is where the <code>PUT</code> request to <code>/todos/:id</code> get's it's data.</p>
<h2 id="update-todo">Updating a todo</h2>
<p>Now that we have a button to update our <code>todo</code>, lets make the feature work.</p>
<h3>Edit the save method in the model adapter to save over existing todos</h3>
<p>Open up your model adapter again. We're going to want to change the save method to allow for saving over existing model instances. You're <code>save</code> method should look something like this:</p>
<pre class="prettyprint">
this.save = function (todo, callback) {
todo.saved = true;
geddy.todos.push(todo);
};
</pre>
<p>Lets edit it to look like this:</p>
<pre class="prettyprint">
this.save = function (todo, callback) {
for (var i in geddy.todos) {
// if it's already there, save it
if (geddy.todos[i].id == todo.id) {
geddy.todos[i] = todo;
return;
}
}
todo.saved = true;
geddy.todos.push(todo);
};
</pre>
<p>This loops over all the <code>todo</code>'s in <code>geddy.todos</code> and if the id is already there, replace that <code>todo</code> with the new <code>todo</code> instance. If you were hooked up to a real DB here, you'd do a SQL UPDATE or similar here instead.</p>
<h3>Edit the update action to find a todo, change the status, and save it</h3>
<p>Alright, now that we have our <code>save</code> method in order, lets edit the <code>update</code> action in the <code>todos</code> controller. It should look something like this right now:</p>
<pre class="prettyprint">
this.update = function (req, resp, params) {
// Save the resource, then display the item page
this.redirect({controller: this.name, id: params.id});
};
</pre>
<p>You'll want to edit it to make it look like this:</p>
<pre class="prettyprint">
this.update = function (req, resp, params) {
var self = this;
geddy.model.adapter.Todo.load(params.id, function (todo) {
todo.status = params.status;
todo.save();
self.redirect({controller: this.name, id: params.id});
});
};
</pre>
<p>We're taking the id that we sent up via the ajax <code>PUT</code> request on the <code>show</code> page and using the <code>load</code> method that we created earlier to find a <code>todo</code> item. Then we're setting its <code>status</code> to be what we sent up in the params ('done'). Then we use the <code>save</code> method that we just updated to save over the existing <code>todo</code> item. Then, in case this isn't coming from an ajax request, we're redirecting the request over to the <code>show</code> action (hooray for progressive enhancment).</p>
<h2 id="conclusion">Conclusion</h2>
<p>At this point you should have a working To Do List app!</p>
<h3>Oh, did we mention that you have a JSON and JSONP api now too?</h3>
<p>Check it out:</p>
<ul>
<li>GET: <code>/todos.json</code></li>
<li>GET: <code>/todos/:id.json</code></li>
<li>POST: <code>/todos</code></li>
<li>PUT: <code>/todos/:id</code></li>
</ul>
<p>If you want to explore a little more, here are some other things you could do:</p>
<ul>
<li>Change the <code>Main#index</code> route to point to the <code>Todos#index</code> action (hint, check out <code>config/router.js</code>)</li>
<li>Add some logging with <code>geddy.log</code></li>
<li>Set up metrics by running <code>npm install metrics</code>, and uncomment the metrics entry (<code>metrics: { port: 4001 }</code>) in your <code>config/environment.js</code> file</li>
</ul>
</div>
</div>
</div>
<footer>
<p>&copy; GeddyJS.org 2112</p>
</footer>
</div> <!-- /container -->
<script>
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-5555148-4']);
_gaq.push(['_setDomainName', '.geddyjs.org']);
_gaq.push(['_trackPageview']);
(function () {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
</body>
</html>
Jump to Line
Something went wrong with that request. Please try again.