PageRenderTime 21ms CodeModel.GetById 32ms RepoModel.GetById 1ms app.codeStats 0ms

/_posts/2012-12-20-backbone-tutorial-4.md

http://github.com/alexyoung/dailyjs
Markdown | 272 lines | 204 code | 68 blank | 0 comment | 0 complexity | bfe01c9f51e8a067f0db6aaacd905a00 MD5 | raw file
Possible License(s): CC-BY-SA-3.0
  1. ---
  2. layout: post
  3. title: "Backbone.js Tutorial: Backbone.sync"
  4. author: Alex Young
  5. categories:
  6. - backbone.js
  7. - mvc
  8. - node
  9. - backgoog
  10. ---
  11. <ul class="parts">
  12. <li><a href="http://dailyjs.com/2012/11/29/backbone-tutorial-1/">Part 1: Build Environment</a></li>
  13. <li><a href="http://dailyjs.com/2012/12/06/backbone-tutorial-2/">Part 2: Google's APIs and RequireJS</a></li>
  14. <li><a href="http://dailyjs.com/2012/12/13/backbone-tutorial-3/">Part 3: Authenticating with OAuth2</a></li>
  15. <li><a href="http://dailyjs.com/2012/12/20/backbone-tutorial-4/"><strong>Part 4: Backbone.sync</strong></a></li>
  16. <li><a href="http://dailyjs.com/2012/12/27/backbone-tutorial-5/">Part 5: List Views</a></li>
  17. <li><a href="http://dailyjs.com/2013/01/03/backbone-tutorial-6/">Part 6: Creating Lists</a></li>
  18. <li><a href="http://dailyjs.com/2013/01/10/backbone-tutorial-7/">Part 7: Editing Lists</a></li>
  19. <li><a href="http://dailyjs.com/2013/01/17/backbone-tutorial-8/">Part 8: Deleting Lists</a></li>
  20. <li><a href="http://dailyjs.com/2013/01/24/backbone-tutorial-9/">Part 9: Tasks</a></li>
  21. <li><a href="http://dailyjs.com/2013/01/31/backbone-tutorial-10/">Part 10: Oh No Not More Tasks</a></li>
  22. <li><a href="http://dailyjs.com/2013/02/07/backbone-tutorial-11/">Part 11: Spies, Stubs, and Mocks</a></li>
  23. <li><a href="http://dailyjs.com/2013/02/14/backbone-tutorial-12/">Part 12: Testing with Mocks</a></li>
  24. <li><a href="http://dailyjs.com/2013/03/07/backbone-tutorial-13/">Part 13: Routes</a></li>
  25. <li><a href="http://dailyjs.com/2013/03/14/backbone-tutorial-14/">Part 14: Customosing the UI</a></li>
  26. <li><a href="http://dailyjs.com/2013/03/28/backbone-tutorial-15/">Part 15: Updates for 1.0, Clear Complete</a></li>
  27. <li><a href="http://dailyjs.com/2013/04/04/backbone-tutorial-16/">Part 16: jQuery Plugins</a></li>
  28. </ul>
  29. ###Preparation
  30. Before starting this tutorial, you'll need the following:
  31. * [alexyoung / dailyjs-backbone-tutorial](https://github.com/alexyoung/dailyjs-backbone-tutorial) at commit `c1d5a2e7cc`
  32. * The API key from part 2
  33. * The "Client ID" key from part 2
  34. * Update `app/js/config.js` with your keys (if you've checked out my source)
  35. To check out the source, run the following commands (or use a suitable Git GUI tool):
  36. {% highlight text %}
  37. git clone git@github.com:alexyoung/dailyjs-backbone-tutorial.git
  38. cd dailyjs-backbone-tutorial
  39. git reset --hard c1d5a2e7cc
  40. {% endhighlight %}
  41. ###Google's Tasks API
  42. To recap: the point of this tutorial series is to build a Backbone.js single page application that uses client-side JavaScript to communicate with Google's authentication and to-do list APIs. Got that? Good!
  43. Google provides access to our to-do lists through two APIs:
  44. * [Tasklists](https://developers.google.com/google-apps/tasks/v1/reference/tasklists#resource)
  45. * [Tasks](https://developers.google.com/google-apps/tasks/v1/reference/tasks)
  46. When loading Google's JavaScript, the browser is bestowed with a global called `gapi` that provides access to various objects and methods. In the last part, I quietly included a call to `gapi.client.load` that loads the `tasks` API:
  47. {% highlight javascript %}
  48. gapi.client.load('tasks', 'v1', function() { /* Loaded */ });
  49. {% endhighlight %}
  50. This can be found in `app/js/gapi.js`. The remaining challenge before building the interface is to implement a new `Backbone.sync` method that uses `gapi` to communicate with the Tasks and Tasklists APIs.
  51. ###Backbone.sync Structure
  52. I've already talked about the overall structure of `Backbone.sync` in [part 2](http://dailyjs.com/2012/12/06/backbone-tutorial-2/). The pattern I'll use in these tutorials is fairly generic, so you could use the same approach to communicate with something other than Google's APIs.
  53. The `sync` method itself takes three arguments, the first of which is the `method` (`create`, `update`, `delete`, and `read`). We need to map `method` to something Google's API can understand.
  54. This is what we've got so far:
  55. {% highlight javascript %}
  56. Backbone.sync = function(method, model, options) {
  57. options || (options = {});
  58. switch (method) {
  59. case 'create':
  60. break;
  61. case 'update':
  62. break;
  63. case 'delete':
  64. break;
  65. case 'read':
  66. break;
  67. }
  68. };
  69. {% endhighlight %}
  70. Google's Tasks API methods map to the Backbone `method` argument like this:
  71. <table class="amy">
  72. <thead>
  73. <tr>
  74. <th>Google Tasks API</th><th>Backbone.sync Method</th><th>Description</th>
  75. </tr>
  76. </thead>
  77. <tbody>
  78. <tr>
  79. <td><code>insert</code></td><td><code>create</code></td><td>Create a new task.</td>
  80. </tr>
  81. <tr>
  82. <td><code>update</code></td><td><code>update</code></td><td>Update an existing task.</td>
  83. </tr>
  84. <tr>
  85. <td><code>delete</code></td><td><code>delete</code></td><td>Delete a task.</td>
  86. </tr>
  87. <tr>
  88. <td><code>list</code></td><td><code>read</code></td><td>Get a list of tasks.</td>
  89. </tr>
  90. </tbody>
  91. </table>
  92. Even though Google's API doesn't look like the Rails 3-based RESTful API that Backbone.js is designed for out of the box, it's still very close.
  93. ###Making Requests with `gapi`
  94. The `gapi` object makes requests using this pattern:
  95. * Call one of the `gapi.client.tasks` methods with the _request content_ to get a `request` object
  96. * Call `request.execute` with a callback to send the request
  97. * The callback receives a `response` object, much like a standard Ajax request
  98. Here's what this looks like in reality:
  99. {% highlight javascript %}
  100. var requestContent = {}
  101. , request
  102. , gapiResource;
  103. gapiResource = 'tasks';
  104. requestContent['tasklist'] = tasklistId; // Assuming we have one
  105. requestContent['resource'] = model.toJSON();
  106. // 'insert' is for creating new tasks
  107. request = gapi.client.tasks[gapiResource].insert(requestContent);
  108. // Send the request to the API
  109. request.execute(function(res) {
  110. // Handle the response
  111. });
  112. {% endhighlight %}
  113. Looking at this, it's clear that we need two models: `Task` and `TaskList`. There also need to be two corresponding collections: `Tasks` and `TaskLists`.
  114. Backbone models and collections have URLs -- these are used for making API requests. Similarly, Google's APIs have URLs: `tasks` and `tasklists`, so by using the model URL `Backbone.sync` can determine which API resource is required for a given request.
  115. ###Models
  116. Create a new directory called `app/js/models` and add `task.js`:
  117. {% highlight javascript %}
  118. define(function() {
  119. var Task = Backbone.Model.extend({
  120. url: 'tasks'
  121. });
  122. return Task;
  123. });
  124. {% endhighlight %}
  125. You'll also want to create a `app/js/models/tasklist.js`:
  126. {% highlight javascript %}
  127. define(function() {
  128. var TaskList = Backbone.Model.extend({
  129. url: 'tasklists'
  130. });
  131. return TaskList;
  132. });
  133. {% endhighlight %}
  134. ###Collections
  135. Create another new directory called `app/js/collections` and add `tasklists.js`:
  136. {% highlight javascript %}
  137. define(['models/tasklist'], function(TaskList) {
  138. var TaskLists = Backbone.Collection.extend({
  139. model: TaskList
  140. , url: 'tasklists'
  141. });
  142. return TaskLists;
  143. });
  144. {% endhighlight %}
  145. We're going to use the `TaskList` collection later on to load your task lists.
  146. ###Making API Requests
  147. Open up `app/js/gapi.js` and add a new line after line 36:
  148. {% highlight javascript %}
  149. app.views.auth.$el.hide();
  150. $('#signed-in-container').show();
  151. self.trigger('ready'); // This one
  152. {% endhighlight %}
  153. This `'ready'` event will be used to signify that authentication was successful, and the Tasks API is ready for use. Next, add the following two lines to `Backbone.sync`, inside the `read` switch case:
  154. {% highlight javascript %}
  155. case 'read':
  156. var request = gapi.client.tasks[model.url].list(options.data);
  157. Backbone.gapiRequest(request, method, model, options);
  158. break;
  159. {% endhighlight %}
  160. This code creates a request, and then `Backbone.gapiRequest` will execute it and delegate the response.
  161. Here's the most basic `Backbone.gapiRequest` implementation:
  162. {% highlight javascript %}
  163. Backbone.gapiRequest = function(request, method, model, options) {
  164. var result;
  165. request.execute(function(res) {
  166. if (res.error) {
  167. if (options.error) options.error(res);
  168. } else if (options.success) {
  169. result = res.items;
  170. options.success(result, true, request);
  171. }
  172. });
  173. };
  174. {% endhighlight %}
  175. All it does is run `request.execute`, which is provided by Google, and then maps the result to be compatible with Backbone's API by running the `success` and `error` callbacks.
  176. Just so you can see something is really happening, open `app/js/app.js` and make it load the `TaskLists` collection by changing the `define` invocation at the top:
  177. {% highlight javascript %}
  178. define([
  179. 'gapi'
  180. , 'views/app'
  181. , 'views/auth'
  182. , 'collections/tasklists'
  183. ],
  184. function(ApiManager, AppView, AuthView, TaskLists) {
  185. {% endhighlight %}
  186. Now add this to the `connectGapi` method:
  187. {% highlight javascript %}
  188. this.apiManager.on('ready', function() {
  189. self.collections.lists.fetch({ data: { userId: '@me' }, success: function(res) {
  190. _.each(res.models, function(model) {
  191. console.log(model.get('title'));
  192. });
  193. }});
  194. });
  195. {% endhighlight %}
  196. That code uses Underscore's `each` method to iterate over each "model" returned by `Backbone.sync`, which is called by the `TaskList` collection.
  197. Run the server with `npm start`, and visit `http://localhost:8080`. If you run it in a browser that supports `console`, then you should see your task lists printed out.
  198. <div class="image">
  199. <img src="/images/posts/backbone-tutorial-api-example.png" alt="" />
  200. <small>My task lists.</small>
  201. </div>
  202. If you've got this working then you're not far off building a real world Backbone.js app that communicates with Google's APIs. The same concepts can be applied to other Google JavaScript APIs.
  203. ###Summary
  204. The full source for this tutorial can be found in [alexyoung / dailyjs-backbone-tutorial, commit fcd653ec6](https://github.com/alexyoung/dailyjs-backbone-tutorial/tree/fcd653ec6fa5916246e3f8b9b5f942f4be31d2e7).