We're going to create a basic cookbook application that lets us create, read, update, and delete (CRUD) recipes. It will look like:

JavaScriptMVC uses generator scripts to assist you in setting up your application's files and folders. They make everything you need to fall into the pit of success!
To create your application, open a console window and navigate to your public directory. Run:
> js jquery\generate\app cookbook
This script creates an application folder and files. Here's what each file does:
cookbook/ // app for your folder
cookbook.css // css for your app
cookbook.html // a page for your app
cookbook.js // app file, loads other files
controllers/ // plugins & widgets
docs/ // documentation
fixtures/ // simulated ajax responses
funcunit.html // functional test page
models/ // model & data layers
qunit.html // unit test page
resources/ // 3rd party scripts
scripts/ // command line scripts
build.html // html for build script
build.js // build script
clean.js // code cleaning / linting
docs.js // create documentation
test/
funcunit // functional tests
cookbook_test.js // functional test
funcunit.js // loads functional tests
qunit/ // unit tests
cookbook_test.js // unit test
qunit.js // loads unit tests
views/ // client side templates
Read Folder and File Organization for more information.
We'll use cookbook.html for our application. If you need to make another page for your app you can generate it:
> js jquery\generate\page cookbook index.html
Generating ... index.html
Or you add the steal script to an existing page
page followed by ?cookbook like:
<script type='text/javascript'
src='../path/to/steal/steal.js?cookbook'>
</script>
If you open cookbook/cookbook.html, you'll see a JavaScriptMVC welcome screen.

The scaffold generator creates all the code you need for simple
Create-Read-Update-Delete (CRUD) functionality.
For our cookbook app, we want to make recipes.
To scaffold recipes run the following in a console:
> js jquery\generate\scaffold Cookbook.Models.Recipe
Here's what each part does:
recipe_controller.jsedit.ejs,init.ejs,list.ejs,show.ejsrecipe_controller_test.jsrecipe.jsrecipes.getrecipe_test.jsAfter generating the scaffolding files, you must steal them in your application file. Open cookbook/cookbook.js and modify the code to steal your recipe controller and model as follows:
steal.plugins(
'jquery/controller',
'jquery/controller/subscribe',
'jquery/view/ejs',
'jquery/controller/view',
'jquery/model',
'jquery/dom/fixture',
'jquery/dom/form_params')
.css('cookbook')
.resources()
.models('recipe')
.controllers('recipe')
.views();
To add your unit and functional tests, include them in your qunit.js and funcunit.js files.
cookbook/test/qunit/qunit.js
steal
.plugins("funcunit/qunit", "cookbook")
.then("cookbook_test","recipe_test")
cookbook/test/funcunit/funcunit.js
steal
.plugins("funcunit")
.then("cookbook_test","recipe_controller_test")
That's it. You've created a simple Cookbook application. Open cookbook/cookbook.html in a browser.

NOTICE: If you are having problems and using Chrome from the filesystem, it's because Chrome has an insanely restrictive AJAX policies on the filesystem.
Essentially, Chrome does not allow AJAX requests to files outside the html page's folder. JavaScriptMVC organizes your files into separate folders.
To fix this, just run JavaScriptMVC from a web server.
Or, you can use another browser. Or you can add
--allow-file-access-from-files to Chrome's start script.
If you're annoyed like we are, star the issue and let google know you'd like Chrome to work on the filesystem!
Continue to Testing Cookbook or continue to read how this code works.
The Cookbook application's functionality can be broken into 4 parts:
Lets see how this gets mapped to files in our Cookbook app.
In cookbook.html, you'll find a script tag like:
<script type='text/javascript'
src='../steal/steal.js?cookbook'>
</script>
This does 2 things:
cookbook/cookbook.js) in development mode.When cookbook/cookbook.js runs, it loads a bunch of
plugins, then loads the generated
controller and model.
When recipe_controller.js is loaded, it creates Cookbook.Controllers.RecipeController.
RecipeController extends controller and describes what events control recipe functionality.
Because RecipeController is a "document" controller
(onDocument: true),
it automatically listens on the document
element for events described by it's prototype methods. The
load method listens for the window onload event
and calls RecipeController's load function.
The load function looks for a '#recipe' element in the page
and creates one if not present. Then uses
the Recipe model to retrieve a list of
recipes and callback the list function.
In Recipe.findAll ....
An Ajax request is made to /recipe,
but because the fixtures plugin
is included, the ajax request is
directed to //cookbook/fixtures/recipes.json.get. After
the content is retrived from the fixture,
new instances of Recipe are created with the
wrapMany function
and passed to the success callback.
The success function is RecipeController's list method.
List replaces
the "#recipe" element's
html with the content rendered
by the template in
cookbook/views/recipe/init.ejs with the
recipe's data.
cookbook/views/recipe/init.ejs draws out the outline of the
recipe table and the recipe form. It uses the partial template
'views/recipe/list' to draw out the individual recipes.
RecipeController listens for "form submit". It's important to note that document controllers only respond to events in an element that has an id that matches the name of the controller. In this case, RecipeController only responds to "form submit" events in "#recipe" element.
When the event happens, the formParams plugin is used to turn the name and description fields into an object like:
{
name: "The entered name",
description : "The entered description"
}
These attributes are passed to create a new recipe. When
save is called, Recipe model's
create function is called with the recipe's attributes.
In Recipe.create a post request is sent to
"/recipes", but intercepted by the fixtures plugin. Instead, fixtures call
back success with a JSON object that looks like:
{
"id": 100,
"name": "The entered name",
"description" : "The entered description"
}
Success is the new recipe instance's created function which updates the attributes of the recipe and publishes an OpenAjax "recipe.created" message.
"recipe.created subscribe" messages are listened for in RecipeController. Here, RecipeController uses the list template to insert the new recipe's html into the page.
When a recipe's html "tr" element created, it is labeled with the recipe instance like this:
<tr <%= recipes[i]%> >
This code adds the following to the recipe element:
cookbookmodelsrecipe_5Inside the tr, the destroy link look like this:
<a class="destroy">destroy</a>
Recipe controller listens for clicks on destroy in the
'.destroy click' action. if the person wants to destroy that
recipe, it uses closest to find the first parent with className=
'recipe' and then gets back the model instance. With that instance, it calls destroy.
jQuery.Model.destroy calls Recipe.destroy with the id of the object to be
destroyed. If successful, jQuery.Model.destroyed publishes a
"recipe.destroyed" OpenAjax event. RecipeController
listens for this event, then removes the element from the page.
Edit starts out similar to destroy - RecipeController listens for ".edit click" and gets
the recipe instance from model(). Then RecipeController replaces the
tr's html with the rendered content of the edit template.
The edit template adds an Update and cancel. RecipeController
listens for ".update click" and ".cancel click".
When ".update click" happens, the model instance is updated
with the values in the input elements. This results in a call to
Recipe.update which tries to send a put request to 'recipe/:id', but instead
uses fixtures.
When the request complates, a "recipe.updated" message is published.
RecipeController listens for these events, and uses the show template to
render the updated content.
When ".cancel click" occurs, the tr's content is replaced using the
show template.
I hate mushrooms. I'd like to know if a recipe is tasty (it doesn't have mushrooms) and list it in the Recipe's table. Here's how to do that:
Add an isTasty method to the prototype object of Recipe model (at the end of recipe.js):
/* @Prototype */
{
isTasty : function(){
return !/mushroom/.test(this.name+" "+this.description)
}
})
In cookbook/views/recipe/init.ejs
add a th like this:
<% for(var attr in Cookbook.Models.Recipe.attributes){%>
<% if(attr == 'id') continue;%>
<th><%= attr%> </th>
<%}%>
<th>Tasty?</th>
<th>Options</th>
In cookbook/views/recipe/show.ejs add a td like this:
<%for(var attribute in this.Class.attributes){%>
<%if(attribute == 'id') continue;%>
<td class='<%= attribute%>'>
<%=this[attribute]%>
</td>
<%}%>
<td><%= this.isTasty() %></td>
<td>
<a href='javascript: void(0)' class='edit'>edit</a>
<a href='javascript: void(0)' class='destroy'>destroy</a>
</td>
Reload your page. You should see the Tasty column. Add a recipe with mushrooms and Tasty? should be false.
Continue to Testing Cookbook.