forked from geddy/geddy
-
Notifications
You must be signed in to change notification settings - Fork 0
/
tutorial.html
762 lines (574 loc) · 31.8 KB
/
tutorial.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
<!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">$> 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">$> 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">
$> cd todo_app
$> 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">$> 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">$> 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">
<div class="hero-unit">
<h3>Params</h3>
<ul>
<% for (var p in params) { %>
<li><%= p + ': ' + params[p]; %></li>
<% } %>
</ul>
</div>
</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">
<div class="hero-unit">
<h2>Add a ToDo:</h2>
<form action="/todos" method="POST">
<% if (params.error) {
var title = 'title not long enough, must be 5 characters or more.';
} else {
var title = 'enter title';
}
%>
<input type="text" class="span6" placeholder="<%= title %>" name="title">
<input type="submit" class="btn btn-primary">
</form>
</div>
</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">
<div class="hero-unit">
<h3>Params</h3>
<ul>
<% for (var p in params) { %>
<li><%= p + ': ' + params[p]; %></li>
<% } %>
</ul>
</div>
</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">
<div class="hero-unit">
<h2>To Do List</h2>
<a href="/todos/add" class="btn pull-right">Create a new To Do</a>
</div>
<% if (todos.length) { %>
<% for (var i in todos) { %>
<div class="row todo-item">
<div class="span8">
<h3>
<a href="/todos/<%= todos[i].id; %>"><%= todos[i].title; %></a>
</h3>
</div>
<div class="span4">
<h3>
<i class="icon-list-alt"></i><%= todos[i].status; %>
</h3>
</div>
</div>
<% } %>
<% } %>
</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">
<div class="hero-unit">
<h3>Params</h3>
<ul>
<% for (var p in params) { %>
<li><%= p + ': ' + params[p]; %></li>
<% } %>
</ul>
</div>
</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">
<div class="hero-unit">
<h3><%= todo.title; %></h3>
<div class="pull-right">
<% if (todo.status == 'open') { %>
<form id="finish-todo" class="hidden" action="/todos/<%= todo.id; %>">
<input type="hidden" name="status" value="done">
<input type="hidden" name="id" value="<%= todo.id; %>">
<input type="hidden" name="title" value="<%= todo.title; %>">
</form>
<span>
<a href="#" class="btn btn-primary btn-large" id="finish-btn">Finish To Do</a>
</span>
<script>
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('<p>This todo is finished!</p>');
});
});
</script>
<% } else { %>
<p>This to do is finished!</p>
<% } %>
</div>
</div>
</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>© 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>