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

/2011-12-12-cross-domain-backbone-apps.textile

https://github.com/duley666/backbonetutorials
Unknown | 446 lines | 344 code | 102 blank | 0 comment | 0 complexity | 5da03cce88b0c74d140964ccb91af8da MD5 | raw file
  1. ---
  2. layout: post
  3. title: Cross Browser problems when consuming your own API
  4. type: intermediate
  5. posturl: http://backbonetutorials.com/cross-domain-backbone-apps
  6. ---
  7. h2. Cross Browser problems when consuming your own API
  8. p. This tutorial aims to help those who have separated their front-end completely from their back-end by building a restful interface as the mediator between the two.
  9. Consuming your own public api has great benefits and sites such as foursquare have "recently":http://engineering.foursquare.com/2011/12/08/web-sites-are-clients-too/ converted.
  10. Foursquare had the same problem that this tutorial aims to shed light on.
  11. h3. What is the problem?
  12. p. The "same origin policy":http://en.wikipedia.org/wiki/Same_origin_policy restricts websites from executing Javascript from other websites in an attempt to prevent "XSS":http://en.wikipedia.org/wiki/Cross-site_scripting hacks.
  13. Before we continue this tutorial only applies to people who wish to have their API and files served from different sub/domains such as _api.domain.com_.
  14. <img src="http://yuml.me/diagram/scruffy/class/%23%20Cool%20UML%20Diagram,%20%5Bnote:%20This%20won't%20work%20%7Bbg:cornsilk%7D%5D,%20%5BDOMAIN.COM%5D-%3EAjax%20Request-%3E%5BAPI.DOMAIN.COM%5D" />
  15. <div style="clear: both;"></div>
  16. You won't run into any trouble if you serve the index page at _http://domain.com_ and have the API located at _http://domain.com/api_.
  17. <div style="clear: both;"></div>
  18. <img src="http://yuml.me/diagram/scruffy/class/%5Bnote:%20This%20will%20work!%20%7Bbg:cornsilk%7D%5D,%20%5BDOMAIN.COM%5D-%3EAjax%20Request-%3E%5BDOMAIN.COM/API%5D" />
  19. <div style="clear: both;"></div>
  20. h3. How can I overcome the same origin policy?
  21. p. One of the greatest movements I have found in this area is a website called "enable-cors.org":http://enable-cors.org/. The only problem being is that it only works on IE8(flakey) and above. If that is acceptable for your application you shouldn't need to read on.
  22. _JSONP_ has been used in many places across the internet but doesn't actually work to well for a web app using a restful web interface because it does not support PUT/POST/DELETE.
  23. There are a bunch of other solutions that half work but generally you will need a full solution.
  24. h3. EasyXDM - The full solution
  25. p. "EasyXDM":http://easyxdm.net/wp/ is an open source project hosted on "github":https://github.com/oyvindkinsey/easyXDM. It's used by companies such as Twitter, LinkedIn and Disqus.
  26. It has great cross browser support(ie6+) and thorough documentation and examples.
  27. h3. So how does it work?
  28. p.
  29. To easily understand this tutorial you should jump straight into the example code base.
  30. h3. "Example Codebase":https://github.com/thomasdavis/backbonetutorials/tree/gh-pages/examples/modular-backbone
  31. h3. "Example Demo":http://backbonetutorials.com/examples/modular-backbone
  32. p. The tutorial is only loosely coupled with the example and you will find the example to be more comprehensive.
  33. If you would like to see how a particuliar use case would be implemented please visit the Github page and create an issue.(Example Request: How to do nested views).
  34. The example isn't super fleshed out but should give you a vague idea.
  35. h3. Example File Structure
  36. p. There are many different ways to lay out your files and I believe it is actually dependent on the size and type of the project. In the example below views and templates are mirroed in file structure. Collections and Models aren't categorized into folders kind of like an ORM.
  37. {% highlight javascript %}
  38. /* File Structure
  39. ├── imgs
  40. ├── css
  41. │ └── style.css
  42. ├── templates
  43. │ ├── projects
  44. │ │ ├── list.html
  45. │ │ └── edit.html
  46. │ └── users
  47. │ ├── list.html
  48. │ └── edit.html
  49. ├── js
  50. │ ├── libs
  51. │ │ ├── jquery
  52. │ │ │ ├── jquery.min.js
  53. │ │ │ └── jquery.js // jQuery Library Wrapper
  54. │ │ ├── backbone
  55. │ │ │ ├── backbone.min.js
  56. │ │ │ └── backbone.js // Backbone Library Wrapper
  57. │ │ └── underscore
  58. │ │ │ ├── underscore.min.js
  59. │ │ │ └── underscore.js // Underscore Library Wrapper
  60. │ ├── models
  61. │ │ ├── users.js
  62. │ │ └── projects.js
  63. │ ├── collections
  64. │ │ ├── users.js
  65. │ │ └── projects.js
  66. │ ├── views
  67. │ │ ├── projects
  68. │ │ │ ├── list.js
  69. │ │ │ └── edit.js
  70. │ │ └── users
  71. │ │ ├── list.js
  72. │ │ └── edit.js
  73. │ ├── router.js
  74. │ ├── app.js
  75. │ ├── main.js // Bootstrap
  76. │ ├── order.js //Require.js plugin
  77. │ └── text.js //Require.js plugin
  78. └── index.html
  79. */
  80. {% endhighlight %}
  81. p. To continue you must really understand what we are aiming towards as described in the introduction.
  82. h3. Bootstrapping your application
  83. p. Using Require.js we define a single entry point on our index page.
  84. We should setup any useful containers that might be used by our Backbone views.
  85. **Note**: The data-main attribute on our single script tag tells Require.js to load the script located at "js/main.js". It automatically appends the ".js"
  86. {% highlight html %}
  87. <!doctype html>
  88. <html lang="en">
  89. <head>
  90. <title>Jackie Chan</title>
  91. <!-- Load the script "js/main.js" as our entry point -->
  92. <script data-main="js/main" src="js/libs/require/require.js"></script>
  93. </head>
  94. <body>
  95. <div id="container">
  96. <div id="menu"></div>
  97. <div id="content"></div>
  98. </div>
  99. </body>
  100. </html>
  101. {% endhighlight %}
  102. p. You should most always end up with quite a light weight index file. You can serve this off your server and then the rest of your site off a CDN ensuring that everything that can be cached, will be.
  103. h4. What does the bootstrap look like?
  104. p. Our bootstrap file will be responsible for configuring Require.js and loading initially important dependencies.
  105. In the below example we configure Require.js to create shortcut alias to commonly used scripts such as jQuery, Underscore and Backbone.
  106. Due to the nature of these libraries implementations we actually have to load them in order because they each depend on each other existing in the global namespace(which is bad but is all we have to work with).
  107. Hopefully if the AMD specification takes off these libraries will add code to allow themselves to be loaded asynchronously. Due to this inconvience the bootstrap is not as intuitive as it could be, I hope to solve this problem in the near future.
  108. We also request a module called "app", this will contain the entireity of our application logic.
  109. **Note:** Modules are loaded relativly to the boot strap and always append with ".js". So the module "app" will load "app.js" which is in the same directory as the bootstrap.
  110. {% highlight javascript %}
  111. // Filename: main.js
  112. // Require.js allows us to configure shortcut alias
  113. // There usage will become more apparent futher along in the tutorial.
  114. require.config({
  115. paths: {
  116. jQuery: 'libs/jquery/jquery',
  117. Underscore: 'libs/underscore/underscore',
  118. Backbone: 'libs/backbone/backbone'
  119. }
  120. });
  121. require([
  122. // Load our app module and pass it to our definition function
  123. 'app',
  124. // Some plugins have to be loaded in order due to there non AMD compliance
  125. // Because these scripts are not "modules" they do not pass any values to the definition function below
  126. 'order!libs/jquery/jquery-min',
  127. 'order!libs/underscore/underscore-min',
  128. 'order!libs/backbone/backbone-min'
  129. ], function(App){
  130. // The "app" dependency is passed in as "App"
  131. // Again, the other dependencies passed in are not "AMD" therefore don't pass a parameter to this function
  132. App.initialize();
  133. });
  134. {% endhighlight %}
  135. h3. How should we lay out external scripts?
  136. p. Any modules we develop for our application using AMD/Require.js will be asynchronously loaded.
  137. We have a heavy dependency on jQuery, Underscore and Backbone, unfortunatly this libraries are loaded synchronously and also depend on each other existing in the global namespace.
  138. Below I propose a solution(until these libraries allow themselves to be loaded asynchronously) to allow these libraries to be loaded properly(synchronously) and also removing themselves from global scope.
  139. {% highlight javascript %}
  140. // Filename: libs/jquery/jquery.js
  141. define([
  142. // Load the original jQuery source file
  143. 'order!libs/jquery/jquery-min'
  144. ], function(){
  145. // Tell Require.js that this module returns a reference to jQuery
  146. return $;
  147. });
  148. {% endhighlight %}
  149. {% highlight javascript %}
  150. // Filename: libs/underscore/underscore
  151. // As above lets load the original underscore source code
  152. define(['order!libs/underscore/underscore-min'], function(){
  153. // Tell Require.js that this module returns a reference to Underscore
  154. return _;
  155. });
  156. {% endhighlight %}
  157. {% highlight javascript %}
  158. // Filename: libs/backbone/backbone
  159. // Finally lets load the original backbone source code
  160. define(['order!libs/backbone/backbone-min'], function(){
  161. // Now that all the orignal source codes have ran and accessed each other
  162. // We can call noConflict() to remove them from the global name space
  163. // Require.js will keep a reference to them so we can use them in our modules
  164. _.noConflict();
  165. $.noConflict();
  166. return Backbone.noConflict();
  167. });
  168. {% endhighlight %}
  169. h3. A boiler plate module
  170. p. So before we start developing our application, let's quickly look over boiler plate code that will be reused quite often.
  171. For convience sake I generally keep a "boilerplate.js" in my application root so I can copy it when I need to.
  172. {%highlight javascript %}
  173. //Filename: boilerplate.js
  174. define([
  175. // These are path alias that we configured in our bootstrap
  176. 'jQuery', // lib/jquery/jquery
  177. 'Underscore', // lib/underscore/underscore
  178. 'Backbone' // lib/backbone/backbone
  179. ], function($, _, Backbone){
  180. // Above we have passed in jQuery, Underscore and Backbone
  181. // They will not be accesible in the global scope
  182. return {};
  183. // What we return here will be used by other modules
  184. });
  185. {% endhighlight %}
  186. p. The first argument of the define function is our dependency array, we can pass in any modules we like in the future.
  187. h3. App.js Building our applications main module
  188. p. Our applications main module should always remain quite light weight. This tutorial covers only setting up a Backbone Router and initializing it in our main module.
  189. The router will then load the correct dependencies depending on the current URL.
  190. {% highlight javascript %}
  191. // Filename: app.js
  192. define([
  193. 'jQuery',
  194. 'Underscore',
  195. 'Backbone',
  196. 'router', // Request router.js
  197. ], function($, _, Backbone, Router){
  198. var initialize = function(){
  199. // Pass in our Router module and call it's initialize function
  200. Router.initialize();
  201. }
  202. return {
  203. initialize: initialize
  204. };
  205. });
  206. {% endhighlight %}
  207. {% highlight javascript %}
  208. // Filename: router.js
  209. define([
  210. 'jQuery',
  211. 'Underscore',
  212. 'Backbone',
  213. 'views/projects/list',
  214. 'views/users/list'
  215. ], function($, _, Backbone, Session, projectListView, userListView){
  216. var AppRouter = Backbone.Router.extend({
  217. routes: {
  218. // Define some URL routes
  219. '/projects': 'showProjects',
  220. '/users': 'showUsers',
  221. // Default
  222. '*actions": "defaultAction'
  223. },
  224. showProjects: function(){
  225. // Call render on the module we loaded in via the dependency array
  226. // 'views/projects/list'
  227. projectListView.render();
  228. },
  229. // As above, call render on our loaded module
  230. // 'views/users/list'
  231. showUsers: function(){
  232. userListView.render();
  233. },
  234. defaultAction: function(actions){
  235. // We have no matching route, lets just log what the URL was
  236. console.log('No route:', actions);
  237. }
  238. });
  239. var initialize = function(){
  240. var app_router = new AppRouter;
  241. Backbone.history.start();
  242. };
  243. return {
  244. initialize: initialize
  245. };
  246. });
  247. {% endhighlight %}
  248. h3. Modularizing a Backbone View
  249. Backbone views most usually always interact with the DOM, using our new modular system we can load in Javascript templates using Require.js text! plugin.
  250. {% highlight javascript %}
  251. // Filename: views/project/list
  252. define([
  253. 'jQuery',
  254. 'Underscore',
  255. 'Backbone',
  256. // Using the Require.js text! plugin, we are loaded raw text
  257. // which will be used as our views primary template
  258. 'text!templates/project/list.html'
  259. ], function($, _, Backbone, projectListTemplate){
  260. var projectListView = Backbone.View.extend({
  261. el: $('#container'),
  262. render: function(){
  263. // Using Underscore we can compile our template with data
  264. var data = {};
  265. var compiledTemplate = _.template( projectListTemplate, data );
  266. // Append our compiled template to this Views "el"
  267. this.el.append( compiledTemplate );
  268. }
  269. });
  270. // Our module now returns an instantiated view
  271. // Sometimes you might return an un-instantiated view e.g. return projectListView
  272. return new projectListView;
  273. });
  274. {% endhighlight %}
  275. p. Javascript templating allows us to seperate the design from the application logic placing all our html in the templates folder.
  276. h3. Modularizing a Collection, Model and View
  277. p. Now we put it altogether by chaining up a Model, Collection and View which is a typical scenairo when building a Backbone.js application.
  278. First off we will define our model
  279. {% highlight javascript %}
  280. // Filename: models/project
  281. define([
  282. 'Underscore',
  283. 'Backbone'
  284. ], function(_, Backbone){
  285. var projectModel = Backbone.Model.extend({
  286. defaults: {
  287. name: "Harry Potter"
  288. }
  289. });
  290. // You usually don't return a model instantiated
  291. return projectModel;
  292. });
  293. {% endhighlight %}
  294. p. Now we have a model, our collection module can depend on it. We will set the "model" attribute of our collection to the loaded module. Backbone.js offers great benefits when doing this.
  295. "Collection.model: Override this property to specify the model class that the collection contains. If defined, you can pass raw attributes objects (and arrays) to add, create, and reset, and the attributes will be converted into a model of the proper type."
  296. {% highlight javascript %}
  297. // Filename: collections/projects
  298. define([
  299. 'Underscore',
  300. 'Backbone',
  301. // Pull in the Model module from above
  302. 'models/project'
  303. ], function(_, Backbone, projectModel){
  304. var projectCollection = Backbone.Collection.extend({
  305. model: projectModel
  306. });
  307. // You don't usually return a collection instantiated
  308. return new projectCollection;
  309. });
  310. {% endhighlight %}
  311. Now we can simply depend on our collection in our view and pass it to our Javascript template.
  312. {% highlight javascript %}
  313. // Filename: views/projects/list
  314. define([
  315. 'jQuery',
  316. 'Underscore',
  317. 'Backbone',
  318. // Pull in the Collection module from above
  319. 'collections/projects',
  320. 'text!templates/projects/list
  321. ], function(_, Backbone, projectsCollection, projectsListTemplate){
  322. var projectListView = Backbone.View.extend({
  323. el: $("#container"),
  324. initialize: function(){
  325. this.collection = new projectsCollection;
  326. this.collection.add({ name: "Ginger Kid"});
  327. // Compile the template using Underscores micro-templating
  328. var compiledTemplate = _.template( projectsListTemplate, { projects: this.collection.models } );
  329. this.el.html(compiledTemplate);
  330. }
  331. });
  332. // Returning instantiated views can be quite useful for having "state"
  333. return new projectListView;
  334. });
  335. {% endhighlight %}
  336. h3. Conclusion
  337. p. Looking forward to feedback so I can turn this post and example into quality references on building modular Javascript applications.
  338. Get in touch with me on twitter, comments or github!
  339. h3. Relevant Links
  340. "Organizing Your Backbone.js Application With Modules":http://weblog.bocoup.com/organizing-your-backbone-js-application-with-modules
  341. h3. Author
  342. * "Thomas Davis":https://github.com/thomasdavis
  343. h3. Contributors
  344. * "Jakub Kozisek":https://github.com/dzejkej (created modular-backbone-updated containing updated libs with AMD support)