In this final example, we take the previous multiple todo list example, and add a remote server to the mix. Here we'll be loading the view templates and model/collection data from a remote server. For this example, we're actually using a mock javascript server with local storage. If you're curious to see how that works, the javascript can be found here.
In this example we'll show how Falcon loads and caches view templates. We'll also demonstrate how Falcon's makeUrl method on views, models, and collections generate URLs and what it means for a model or a collection have a 'parent' model.
Show Comments Hide Comments 1 // First we'll begin by configuring the global settings 'baseTemplateUrl' and
2 // 'baseApiUrl'.
3 //
4 // The 'baseTemplateUrl' defines what url should be prepended to all remote view
5 // template ajax requests. This is useful if you have a specific directory or
6 // content server that you would like to serve individual templates from. This
7 // variable is utilized by a View's 'makeUrl' method.
8 //
9 // The 'baseApiUrl' defines what url should be prepended to all remote model and
10 // collection ajax requests. Like the template url, this is useful for defining a
11 // base endpoint for an api that Falcon should use to interact with RESTful Server
12 // APIs
13 Falcon.baseTemplateUrl = "/template";
14 Falcon.baseApiUrl = "/api";
15
16 // Overall, the todo model is the same except for the url attribute which now
17 // points to a remote server resource. How the url attribute is actually used
18 // by Falcon is discussed later in the tutorial
19 var Todo = Falcon.Model.extend({
20 // I didn't have to add the .json to the end of the url attribute but
21 // for the sake of this tutorial, I decided to to show how urls are
22 // generated, even with those that have extensions on them. So just
23 // note that this tutorial would work just the same without the .json
24 // extension. I'll be describing exactly how url's are generated
25 // throughout the tutorial by the 'sync' related methods located within
26 // the tutorial's views (TodoListView, NewTodoListView, EditTodoListView,
27 // LayoutView) for models and collections via their 'sync' methods (fetch,
28 // create, save, destroy)
29 url: 'todo.json',
30
31 observables: {
32 'text': '',
33 'is_complete': ''
34 }
35 });
36
37 // Todos is the same
38 var Todos = Falcon.Collection.extend({
39 model: Todo
40 });
41
42 // TodoList is the same except for the url with it's new extension
43 var TodoList = Falcon.Model.extend({
44 url: 'todo_list.json',
45
46 defaults: {
47 'todos': function() { return new Todos( this ); }
48 },
49
50 observables: {
51 'title': '',
52 'description': ''
53 }
54 });
55
56 // TodoLists is the same
57 var TodoLists = Falcon.Collection.extend({
58 model: TodoList
59 });
60
61
62
63 // The TodoListView has the same functions as before except now it's url is set
64 // up to load a template from a remote server rather than a local <template>
65 // element. Also, the models and collections in this method will be using
66 // sync() methods to retrieve, save, and delete data from a remote server.
67 // The TodoListView is used to display information.
68 var TodoListView = Falcon.View.extend({
69 // Here we changed our url to use a remote resource by getting rid of
70 // the "#" symbol that identifies a local <template> element. Instead,
71 // Falcon will make an ajax request to the server to find this template.
72 //
73 // In this scenario, the view's 'makeUrl' method will combine the
74 // 'url' attribute with the configurable Falcon.baseTemplateUrl variable.
75 // The Falcon.baseTemplateUrl variable is set to '/template' so the url
76 // that will be generated here will be:
77 //
78 // '/template/todo_list.tmpl'.
79 //
80 // This url will be utilized in an ajax request to load up and cache the
81 // view's html template.
82 url: 'todo_list.tmpl',
83
84 defaults: {
85 'todo_list': function(todo_list) { return todo_list; }
86 },
87
88 observables: {
89 'new_todo_text': ''
90 },
91
92 //Stubbed initialize method just to show the input parameters
93 initialize: function(todo_list){},
94
95 // Adds a new todo to the todo list and saves it to the server
96 addTodo: function()
97 {
98 // Initialize the todo with the todo_list as its 'parent'
99 var todo = new Todo({ text: this.new_todo_text() }, this.todo_list);
100
101 // Next we'll create the todo on the server and append it into the
102 // todo_list's collection of todos (todo_list.todos).
103 //
104 // The create method will send a POST ajax request to the server.
105 // Because the Todo was created with this view's TodoList as its
106 // parent, the url that will be generated is:
107 //
108 // /api/todo_list/{todo_list_id}/todo.json
109 //
110 // Let me disect this URL.
111 //
112 // Firstly, the '/api' part is the value of Falcon.baseApiUrl. This
113 // variable is similar to Falcon.baseTemplateUrl except that it
114 // applies to urls generated for Models and Collections.
115 //
116 // Next, the '/todo_list' portion of the url is taken from the TodoList
117 // model which is the 'parent' of this todo. TodoList is defined with a
118 // 'url' of 'todo_list.json'. Falcon's Model 'makeUrl' method takes
119 // everything before the extension (.json) and considers that to be the
120 // appropriate piece for the url
121 //
122 // Next, the '{todo_list_id}' is set to the id of the current todo list.
123 // Hence, if the id of the todo list is 1, then {todo_list_id} will
124 // take the value of '1'. The 'id' attribute of a model is generated
125 // by the server and stored upon a successful response. Here the
126 // response from the server for a todo model will be a json object
127 // that will represent this todo, including it's unique id
128 //
129 // Lastly, the '/todo.json' piece is taken directly from the Todo
130 // Model's url attribute.
131 this.todo_list.todos.create(todo);
132
133 // Reset the todo text
134 this.new_todo_text('');
135 },
136
137 // Method used to remove a todo from a todo list and from a remote server
138 removeTodo: function(todo)
139 {
140 // Here we'll call the todos collection's destroy method to remove the
141 // specified todo. The destroy function on a collection will both
142 // remove all instances of a model (based on equal ids) after it makes
143 // a successful DELETE ajax request to the server. Here the url that
144 // will be generated during the DELETE ajax request is:
145 //
146 // /api/todo_list/{todo_list_id}/todo/{todo_id}.json
147 //
148 // I'll disect this url as well.
149 //
150 // The '/api/todo_list/{todo_list_id}' portion is all the same as the
151 // todos.create() example above.
152 //
153 // The '/todo' part is extracted from the Todo model's url attribute
154 // ('todo.json') and is stripped of the extension. Since we're
155 // attempting to delete a specific Todo, the Todo's id is appended
156 // to the url which makes up the '{todo_id}' portion.
157 //
158 // Lastly, the .json extension is appended from the second half of the
159 // Todo model's url attribute.
160 this.todo_list.todos.destroy( todo );
161 },
162
163 // Method used to mark a todo as complete
164 completeTodo: function(todo)
165 {
166 todo.set('is_complete', true);
167
168 // Here we're going to save the specific Todo to the server. The save
169 // method generates a PUT request to the server. Here the url that will
170 // be generated for the todo is the same as the 'destroy' method above
171 // because we're affecting a specific todo who's parent is a todo list:
172 //
173 // /api/todo_list/{todo_list_id}/todo/{todo_id}.json
174 //
175 todo.save();
176 },
177
178 // Trigger to signal the start of an edit to a specific todo list
179 editTodoList: function()
180 {
181 this.trigger("edit", this.todo_list);
182 },
183
184 // Method used to destroy the internal todo list from the server
185 deleteTodoList: function()
186 {
187 // Here we're going to be destroying a specific todo list from the
188 // server. Once the ajax call is complete, a custom 'delete' event is
189 // triggered.
190 //
191 // In this 'sync' related example the url that will be generated is:
192 //
193 // /api/todo_list/{todo_list_id}.json
194 //
195 // Since we're only affecting a specific todo list with no parent model
196 // or collection the url is generated using the model's 'url' attribute
197 // (todo_list.json) while injecting the model's specific id into the url
198 //
199 // In this method, we pass in a callback method and a context argument
200 // to the destroy method. Underneath the hood, Falcon realizes that the
201 // first argument is a function and assigns that function to be called
202 // when the ajax call is completed (either successful or errornous). The
203 // resultant model (todo_list) is always passed in as the first argument
204 // to any of the 'sync' related callbacks (complete, success, error).
205 // The second argument given to the destroy method is the context in
206 // which to call any of the sync related methods on. In this scenario
207 // it's this view.
208 this.todo_list.destroy(function(todo_list) {
209 this.trigger("delete", todo_list);
210 }, this);
211 }
212 });
213
214
215
216 // Same New Todo List View as before except with a remote template and actual
217 // model and collection server interactions
218 var NewTodoListView = Falcon.View.extend({
219 // Updated template url, will generate to: '/template/edit_todo_list.tmpl'
220 url: 'todo_list_form.tmpl',
221
222 observables: {
223 'title': 'Untitled Todo List',
224 'description': ''
225 },
226
227 // Method to trigger a custom canel event
228 cancelSave: function()
229 {
230 this.trigger("cancel");
231 },
232
233 // Method used to create a new todo list on the server and trigger a custom
234 // 'save' event on completion.
235 saveTodoList: function()
236 {
237 // Initialize the todo list
238 todo_list = new TodoList({
239 'title': this.title(),
240 'description': this.description()
241 });
242
243 // Here we're going to create the TodoList on the server and then, on
244 // complete, we'll trigger a custom 'save' event. On this line, the
245 // create() method creates a POST request to the server and will generate
246 // a url:
247 //
248 // /api/todo_list.json
249 //
250 // Here the url is simply generated from the parentless TodoList's url
251 // attribute and the Falcon.baseApiUrl variable.
252 //
253 // Lastly, this method is set up to call it's callbacks on complete with
254 // the context of this view (hence the second argument of the create
255 // method).
256 todo_list.create(function(todo_list) {
257 this.trigger("save", todo_list);
258 }, this);
259 }
260 });
261
262
263
264 // Again, the EditTodoListView is the same as previous tutorials except that it
265 // now refers to a remote resource to locate its template and utilizes the sync
266 // methods for collections and models.
267 var EditTodoListView = Falcon.View.extend({
268 // Remote url for template, generated url: '/template/edit_todo_list.tmpl'
269 url: 'todo_list_form.tmpl',
270
271 defaults: {
272 'todo_list': function(todo_list){ return todo_list; }
273 },
274
275 observables: {
276 'title': '',
277 'description': ''
278 },
279
280 initialize: function(todo_list) {
281 this.title( todo_list.get('title') );
282 this.description( todo_list.get('description') );
283 },
284
285 // Method used to trigger a custom 'cancel' event on the form
286 cancelSave: function()
287 {
288 this.trigger("cancel", this.todo_list);
289 },
290
291 // Method used to save the todo list to the server and then trigger a custom
292 // 'save' event.
293 saveTodoList: function()
294 {
295 // Update the todo list's data
296 this.todo_list.set({
297 'title': this.title(),
298 'description': this.description()
299 });
300
301 // Save the TodoList to the server and then on complete trigger a custom
302 // 'save' event.
303 //
304 // In this example the save method will send an ajax PUT request to:
305 //
306 // /api/todo_list/{todo_list_id}.json
307 //
308 // This url is generated because we're modifying a specific todo list
309 // that doesn't have a parent Model or Collection
310 this.todo_list.save(function(todo_list) {
311 this.trigger("save", todo_list);
312 }, this);
313 }
314 });
315
316
317 // Lastly, tie everything together with the layout. This is the same as the
318 // the LayoutView in previous examples except for the use of remote resources.
319 var LayoutView = Falcon.View.extend({
320 // Generated url: '/template/layout.tmpl'
321 url: 'layout.tmpl',
322
323 defaults: {
324 'todo_lists': function() { return new TodoLists }
325 },
326
327 observables: {
328 'current_view': null,
329 'selected_todo_list': null
330 },
331
332 // This time we'll initialize the class and fetch any todo lists that have
333 // been saved to the server since our last visit to the application
334 initialize: function()
335 {
336 // The fetch method on a collection will send an ajax GET request to the
337 // url generated by the 'makeUrl' method. The collection will
338 // automatically be populated by the json response from the server.
339 // The url that will be generated is:
340 //
341 // /api/todo_list.json
342 //
343 // This is generated from the TodoList url attribute and the
344 // Falcon.baseApiUrl
345 this.todo_lists.fetch();
346 },
347
348 // Method to display a TodoListView in the content area of the screen, listen
349 // for all related events, and select the currently viewing todo list. All
350 // the same as the previous tutorial
351 selectTodoList: function(todo_list) {
352 var view = new TodoListView( todo_list );
353
354 view.on("edit", function(todo_list) {
355 this.editTodoList(todo_list);
356 }, this );
357
358 view.on("delete", function(todo_list) {
359 this.todo_lists.remove( todo_list )
360 this.selected_todo_list( null );
361 this.current_view( null );
362 }, this);
363
364 this.current_view( view );
365 this.selected_todo_list( todo_list );
366 },
367
368 // Method to display a NewTodoListView in the content area of the screen,
369 // listen for all related events, and select the currently viewing todo list.
370 // All the same as the previous tutorial
371 newTodoList: function() {
372 view = new NewTodoListView();
373
374 view.on("save", function(todo_list) {
375 this.todo_lists.append( todo_list );
376 this.selectTodoList( todo_list );
377 }, this);
378
379 view.on("cancel", function() {
380 this.current_view( null );
381 }, this);
382
383 this.current_view( view );
384 this.selected_todo_list( null );
385 },
386
387 // Method to display a EditTodoListView in the content area of the screen,
388 // listen for all related events, and select the currently viewing todo list.
389 // All the same as the previous tutorial
390 editTodoList: function(todo_list) {
391 view = new EditTodoListView( todo_list );
392
393 view.on("save", function() {
394 this.selectTodoList( todo_list );
395 }, this);
396
397 view.on("cancel", function() {
398 this.selectTodoList( todo_list );
399 }, this);
400
401 this.current_view( view );
402 this.selected_todo_list( todo_list );
403 },
404
405 // Method used determine if a specific list is the one that's currently
406 // being viewed
407 isSelectedList: function(todo_list) {
408 return ( todo_list.equals(this.selected_todo_list) );
409 }
410 });
411
412 //Initialize our app and the initial view
413 view = new LayoutView
414 //This method is used to apply the bindings to a specific element (in this example
415 //the div with the id 'application'). If an element isn't given, Falcon will default
416 //to the body element.
417 Falcon.apply(view, "#application");
1 <!DOCTYPE html>
2 <html>
3 <head>
4 <script src="knockout-3.1.0.js"></script>
5 <script src="falcon.min.js"></script>
6
7 <!--
8 Allows us to use ajax methods through jQuery and
9 request templates and data from a server.
10 -->
11 <script src="jquery-2.1.0.min.js"></script>
12 <script src="falcon.jquery_adapter.min.js"></script>
13
14 <script src="application.js"></script>
15 </head>
16 <body>
17 <div id="application"></div>
18 </body>
19 </html>