Alright, on to the actual Falcon tutorial. As you can see, the code written with Knockout, although very dynamic, doesn't have much structure or organization around it. This can lead to a large mess of code as a project grows from being a very simple application. This is where Falcon steps in. It adds structure to Knockout so that you can have both well organized and easily maintainable code with the dynamics of Knockout.
To demonstrate how Falcon adds better organization to Knockout, we'll start with a traditional todo list example. Here you'll see how Falcon can be used to add, remove, and complete todos on a todo list. This also allows us to edit the title and description of the todo list. In these examples, I've written comments around areas of interest describing how everything is working in the code. Be sure to look through all of the tabs in the jsFiddle to get a full understanding of everything.
A few key things to pay attention for are Models, Collections, Views, the Falcon.apply() method, and the $view binding context (in the html template).
- Model - Represents a single object (such as an Animal, Automobile, or a single Todo)
- Collection - Represents a list of models (such as Animals, Automobiles, or multiple Todos)
- View - Handles the interaction between models, collections and other application logic and binds everything to a specified Knockout template.
-
Falcon.apply() - This is the single method that must be called to initialize Falcon and your app. The first argument must be a Falcon View that will be bound against, by default, the
<body>
of the document. Optionally, you may pass in a second parameter to specify what element on the page we would like to initialize our application in. In this example we use the#application
element. This will trigger the specified view to load its html template and inject it into our app's container element. - $view - This is a new binding context (like Knockout's $root, $parent, and $data) added by Falcon to give your templates direct access to a view's methods and variables regardless of what scope you're currently in. By default, the root level scope of a view's template is the view itself. However, if you're within a 'foreach' binding, then instead of having to call $parent to access the view, you can simply us $view itself. To get a better understanding of how this is implemented, look at the html tab in the jsFiddle below.
1 var Todo = Falcon.Model.extend({
2 url: 'todo',
3
4 observables: {
5 'text': '',
6 'is_complete': ''
7 }
8 });
9
10 var Todos = Falcon.Collection.extend({
11 model: Todo
12 });
13
14 var TodoListView = Falcon.View.extend({
15 url: '#todo_list_tmpl',
16
17 defaults: {
18 'todos': function() { return new Todos; }
19 },
20
21 observables: {
22 'title': 'Untitled List',
23 'is_editting_title': false,
24 'new_todo_text': ''
25 },
26
27 initialize: function(){},
28
29 addTodo: function()
30 {
31 var todo = new Todo({ text: this.new_todo_text() });
32
33 // Append the todo to the end of the colleciton. Note: We didn't need
34 // to define a new Todo model, rather we could have instead passed in
35 // the todo model's data and the collection would have created the todo
36 // for us.
37 this.todos.append( todo );
38
39 // Reset the todo text.
40 this.new_todo_text('');
41 },
42
43 // Method used to remove a specific todo. Because this will be bound by Knockout's
44 // 'click' binding, the specific todo is passed in as the first argument for us.
45 removeTodo: function(todo)
46 {
47 this.todos.remove( todo );
48 },
49
50 // Method used to mark a todo as complete
51 completeTodo: function(todo)
52 {
53 // The set method is used on this model to set the value of 'is_complete'.
54 // This is useful in the scenario that we might not know if the is_complete
55 // property is an observable or a primitive value. In this instance calling
56 // todo.is_complete( true ) would yield the same result.
57 todo.set('is_complete', true);
58 },
59
60 // Method used to start editting the list title
61 editTitle: function()
62 {
63 this.is_editting_title( true );
64 },
65
66 // Method used to 'save' the list's title
67 saveTitle: function()
68 {
69 this.is_editting_title( false );
70 }
71 });
72
73 //Initialize our app and the initial view
74 view = new TodoListView;
75 //This method is used to apply the bindings to a specific element (in this example
76 //the div with the id 'application'). If an element isn't given, Falcon will default
77 //to the body element.
78 Falcon.apply(view, "#application");
79
80 //Lastly, add some initial mock todos
81 view.todos.append([
82 {'text': 'My First Todo'}, //Same as 'new Todo({...})'
83 {'text': 'My Second Todo'},
84 {'text': 'My Third Todo'}
85 ]);
1 <!DOCTYPE html>
2 <html>
3 <head>
4 <script src="knockout-3.1.0.js"></script>
5 <script src="falcon.min.js"></script>
6 <script src="application.js"></script>
7 </head>
8 <body>
9 <div id="application"></div>
10 <template id="todo_list_tmpl">
11 <!-- ko if: $view.is_editting_title -->
12 <input type="text" data-bind="value: $view.title" />
13 <button data-bind="click: $view.saveTitle">Save</button>
14 <!-- /ko -->
15 <!-- ko ifnot: $view.is_editting_title -->
16 <h3>
17 <!-- ko text: $view.title --><!-- /ko -->
18 <a data-bind="click: $view.editTitle">Edit</a>
19 </h3>
20 <!-- /ko -->
21 <ul class="todo-list" data-bind="foreach: $view.todos">
22 <li data-bind="css: {'completed': is_complete}">
23 <!-- ko text: text --><!-- /ko -->
24 <a data-bind="click: $view.completeTodo">[Complete]</a>
25 <a data-bind="click: $view.removeTodo">[X]</a>
26 </li>
27 </ul>
28 <input type="text" data-bind="value: $view.new_todo_text" />
29 <button data-bind="click: $view.addTodo">Add</button>
30 </template>
31 </body>
32 </html>