1. The Models
Our simple applicaiton will have two model definitions, one for "categories" of todo items (e.g. "personal", "work") and one for the todo items themselves.
Model/todo_category.erl:
-module(todo_category, [Id, Name, Description]). -compile(export_all). -has_many(todo_items).
Model/todo_item.erl:
-module(todo_item, [Id, TodoCategoryId, Name, Description]). -compile(export_all). -belongs_to(todo_category).
OK. A word about "belongs_to" -- you must have a CamelCaseId in the main module definition which corresponds exactly to the corresponding model referenced by "belongs_to". It could have been placed at the end of the list, e.g. "-module(todo_item, [Id, Name, Description, TodoCategoryId])." but I tend to put these "foreign key" bits at the beginning. YMMV.
2. The Controller
There's a lot going on in this controller, I'll explain it later. First, the code:
Controller/todo_controller.erl:
-module(todo_controller). -compile(export_all). item_list(Req) -> Items = boss_db:find(todo_item, [], 100), {ok, [{item_list, Items}]}. items_by_category(Req) -> CategoryId = Req:get_qs_value("category_id"), Category = boss_db:find(CategoryId), Items = boss_db:find(todo_item, [{todo_category_id, CategoryId}], 100), {ok, [{item_list, Items}, {category, Category}]}. new_item_form(Req) -> Categories = boss_db:find(todo_category, [], 100), {ok, [{category_list,Categories}]}. new_item_submit(Req) -> CategoryId = Req:get_post_value("item_category_id"), Name = Req:get_post_value("item_name"), Description = Req:get_post_value("item_description"), Item = todo_item:new(id, CategoryId, Name, Description), SavedItem = Item:save(), {redirect, lists:concat(["item?id=", [SavedItem:id()]])}. item(Req) -> ItemId = Req:get_qs_value("id"), Item = boss_db:find(ItemId), {ok, [{item, Item}]}. category_list(Req) -> Categories = boss_db:find(todo_category, [], 100), {ok, [{category_list,Categories}]}. new_category_submit(Req) -> Name = Req:get_post_value("category_name"), Description = Req:get_post_value("category_description"), Category = todo_category:new(id, Name, Description), SavedCategory = Category:save(), {redirect, "category_list"}. category(Req) -> CategoryId = Req:get_qs_value("id"), Category = boss_db:find(CategoryId), {ok, [{category, Category}]}.
All right. First a word about controllers in general. Chicago Boss automatically (convention, not configuration) routes requests to /some/word to Controller/some_controller.erl:word(Req) if that controller and method exist. For a simple html file, these don't have to exist. We'll see an example of that in a moment.
What I've done here is define a few web entry points to the application: "/todo/item_list", "/todo/items_by_category", "/todo/new_category_submit", etc. Most of these methods return a simple "ok" tuple along with some data. Some of them, the callbacks for create form submission, use "redirect" to send the user along to an appropriate page instead of simply presenting a "OK, thanks for your submission" page.
You also see a few of the methods available: Req:get_qs_value to get a value from a query string, and Req:get_post_value to get a value from a form submit. Of particular interest might be the "boss_db:find" examples. We could add these as ease-of-use functions in our models themselves, but for now, let's go with what we have. (Noting that I'm not showing update or delete paths yet in this snippet: so far just create and read.)
3. The Views
All right. We have defined a model and some controller routes for our web application, but to present data back to the user, we'll need some views. I'm not going to present all the views necessary to implement this application, or present them in full xhtml-strict compliance glory (what would be the fun in that?) but I will give a few of the nicer bits which show off some of the templating goodness available.
View/todo/item_list.html:
<table> <tr> <th>category</th> <th>name</th> <th>description</th> </tr> {% for item in item_list %} <tr> <td><a href="category?id={{ item.todo_category.id }}">{{ item.todo_category.name }}</a></td> <td><a href="item?id={{ item.id }}">{{ item.name }}</a></td> <td>{{ item.description }}</td> </tr> {% endfor %} </table> <a href="new_item_form">create new</a>
All right. A for loop and some inline string data. Whee. Well, actually, that's pretty much all you need to do 90% of what a view should be doing. There's more under the covers, but that will have to wait until a more in-depth article on the view template features.