PageRenderTime 60ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 1ms

/www/tutorials.html

https://github.com/anthonybrown/backbone.epoxy
HTML | 575 lines | 449 code | 125 blank | 1 comment | 0 complexity | 4e1901ba471e6059a8337e9b4fa93e91 MD5 | raw file
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="utf-8">
  5. <title>Epoxy.js : Elegant Data Binding for Backbone : Getting Started</title>
  6. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  7. <meta name="description" content="Epoxy is an elegant and extensible data binding library for Backbone.js designed to hook view elements directly to data models.">
  8. <meta name="author" content="Greg MacWilliam">
  9. <meta name="viewport" content="width=device-width, initial-scale=1">
  10. <meta name="apple-mobile-web-app-title" content="Epoxy.js">
  11. <meta name="application-name" content="Epoxy.js">
  12. <link rel="shortcut icon" href="favicon.ico" type="image/x-icon">
  13. <link rel="icon" href="favicon.ico" type="image/x-icon">
  14. <link href="style/reset.css" rel="stylesheet">
  15. <link href="style/screen.css" rel="stylesheet">
  16. <script type="text/javascript">
  17. var _gaq = _gaq || [];
  18. _gaq.push(['_setAccount', 'UA-39215101-1']);
  19. _gaq.push(['_trackPageview']);
  20. (function() {
  21. var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
  22. ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
  23. var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
  24. })();
  25. </script>
  26. </head>
  27. <body>
  28. <div id="page">
  29. <div class="banner clearfix" role="banner">
  30. <p class="title"><span><b>Epoxy</b>.js</span> Elegant Data Binding for Backbone</p>
  31. <div class="download">
  32. <a href="js/backbone.epoxy.min.js" class="download-button">Download Epoxy 0.10.0</a>
  33. <p class="download-info">8k min, 2k gzip <i>|</i> <a href="https://github.com/gmac/backbone.epoxy">GitHub Full Source</a></p>
  34. </div>
  35. </div>
  36. <div class="navigation" role="navigation">
  37. <ul class="nav-main clearfix">
  38. <li><a href="index.html">About Epoxy.js</a></li>
  39. <li class="active"><a href="tutorials.html">Getting Started</a></li>
  40. <li><a href="documentation.html">Documentation</a></li>
  41. </ul>
  42. <div class="nav-toc">
  43. <div class="nav-toc-content">
  44. <b>Tutorials</b>
  45. <ol>
  46. <li><a href="#installation">Installation</a></li>
  47. <li><a href="#simple-bindings">Simple View Bindings</a></li>
  48. <li><a href="#observables-computeds">Model Observables &amp; Computeds</a></li>
  49. <li><a href="#computed-dependencies">Managing Computed Dependencies</a></li>
  50. <li><a href="#computed-get-set">Computed Getters and Setters</a></li>
  51. <li><a href="#binding-operators">View Binding Operators</a></li>
  52. <li><a href="#binding-handlers">Custom Binding Handlers</a></li>
  53. <li><a href="#binding-sources">Binding Collections &amp; Multiple Sources</a></li>
  54. <li><a href="#epoxy-todos">Epoxy ToDos</a></li>
  55. </ol>
  56. </div>
  57. </div>
  58. </div>
  59. <div class="main" role="main">
  60. <h1>Getting Started</h1>
  61. <h2 id="installation">Installation</h2>
  62. <p>Epoxy requires <a href="http://jquery.com/">jQuery</a> 1.7.0+, <a href="http://underscorejs.org/">Underscore</a> 1.4.3+, and <a href="http://backbonejs.org/">Backbone</a> 0.9.9+. To install Epoxy, download the <a href="https://github.com/gmac/backbone.epoxy/blob/master/backbone.epoxy.min.js">Epoxy library</a> (8k min, 3k gzip) and include its script tag in your document after all dependencies:</p>
  63. <pre><code class="html">&lt;script src="js/jquery-min.js"&gt;&lt;/script&gt;
  64. &lt;script src="js/underscore-min.js"&gt;&lt;/script&gt;
  65. &lt;script src="js/backbone-min.js"&gt;&lt;/script&gt;
  66. &lt;script src="js/backbone.epoxy.min.js"&gt;&lt;/script&gt;</code></pre>
  67. <!--p>Note that Epoxy's <tt>Model</tt> and <tt>View</tt> components operate independently of each other, therefore you may choose to just include the <a href="#">model script</a> or <a href="#">view script</a> if you only plan to use one component. Each component script is roughly 4k min, 1.5k gzip.</p-->
  68. <p>Epoxy is open source under the MIT license; you may browse the full library source in its <a href="https://github.com/gmac/backbone.epoxy">GitHub Repo</a>.</p>
  69. <h2 id="simple-bindings">Simple View Bindings</h2>
  70. <p>Let's start by setting up a simple binding between a few DOM elements who's content we want to update when their underlying model data changes:</p>
  71. <div class="example">
  72. <ul class="tabs">
  73. <li data-tab="js">JavaScript</li>
  74. <li data-tab="html">HTML</li>
  75. </ul>
  76. <pre><code class="js">var bindModel = new Backbone.Model({
  77. firstName: "Luke",
  78. lastName: "Skywalker"
  79. });
  80. var BindingView = Backbone.Epoxy.View.extend({
  81. el: "#app-luke",
  82. bindings: {
  83. "input.first-name": "value:firstName,events:['keyup']",
  84. "input.last-name": "value:lastName,events:['keyup']",
  85. "span.first-name": "text:firstName",
  86. "span.last-name": "text:lastName"
  87. }
  88. });
  89. var view = new BindingView({model: bindModel});</code></pre>
  90. <pre><code class="html">&lt;div id="app-luke" class="demo"&gt;
  91. &lt;label>First:&lt;/label&gt;
  92. &lt;input type="text" class="first-name"&gt;
  93. &lt;label>Last:&lt;/label&gt;
  94. &lt;input type="text" class="last-name"&gt;
  95. &lt;b>Full Name:&lt;/b&gt;
  96. &lt;span class="first-name"&gt;&lt;/span&gt;
  97. &lt;span class="last-name"&gt;&lt;/span&gt;
  98. &lt;/div&gt;</code></pre>
  99. </div>
  100. <p>In this example, we create a new instance of <tt>Epoxy.View</tt>, provide it a native Backbone model, then use the view's <b>bindings</b> hash to declare bindings between view selectors and model attributes.</p>
  101. <p>Binding declarations are formatted as <tt>"handler:dataSource"</tt>. Basically, that's a key/value pair where the <tt>key</tt> defines a <i>handler method</i> to perform the binding, and the <tt>value</tt> is a <i>data source</i> to populate the binding with. Epoxy provides a base set of <a href="documentation.html#binding-handlers">binding handlers</a>, and you're welcome to <a href="documentation.html#view-binding-handlers">add your own</a>. Data sources reference attributes on the view's <b>model</b> in most common binding implementations.</p>
  102. <p>In the above example, <tt>value:firstName</tt> establishes a two-way binding between the text input's <b>value</b> property and the bound model's <b>firstName</b> attribute. Likewise, <tt>text:firstName</tt> establishes a one-way binding that populates the bound element's <b>text</b> with the model's <b>firstName</b> attribute. Lastly, <tt>events:['keyup']</tt> is used to specify DOM events that the binding should respond to in addition to the default <tt>"change"</tt> event.</p>
  103. <p><b>Inline Binding Declarations</b></p>
  104. <p>Another popular approach to data binding is to declare bindings as attributes directly on the DOM elements that they target. This approach shifts bindings declarations out of the View and into the DOM. Epoxy does also supports this syntax, should you prefer it:</p>
  105. <div class="example">
  106. <ul class="tabs">
  107. <li data-tab="js">JavaScript</li>
  108. <li data-tab="html">HTML</li>
  109. </ul>
  110. <pre><code class="js">var bindModel = new Backbone.Model({
  111. firstName: "Han",
  112. lastName: "Solo"
  113. });
  114. var BindingView = Backbone.Epoxy.View.extend({
  115. el: "#app-han",
  116. bindings: "data-bind"
  117. });
  118. var view = new BindingView({model: bindModel});</code></pre>
  119. <pre><code class="html">&lt;div id="app-han" class="demo"&gt;
  120. &lt;label>First:&lt;/label&gt;
  121. &lt;input type="text" data-bind="value:firstName,events:['keyup']"&gt;
  122. &lt;label>Last:&lt;/label&gt;
  123. &lt;input type="text" data-bind="value:lastName,events:['keyup']"&gt;
  124. &lt;b>Full Name:&lt;/b&gt;
  125. &lt;span data-bind="text:firstName"&gt;&lt;/span&gt;
  126. &lt;span data-bind="text:lastName"&gt;&lt;/span&gt;
  127. &lt;/div&gt;</code></pre>
  128. </div>
  129. <p>Here, the exact same binding scheme has been applied directly to the DOM using element attributes, and the view's <b>bindings</b> property simply defines the attribute name to query for (note that <tt>"data-bind"</tt> is Epoxy's default selector, so you don't need to specifically declare that within your view).</p>
  130. <p>The two above examples are functionally identical, therefore the location of your binding declarations (view or DOM) is entirely a matter of preference&mdash;provided that they're all in one place. The proceeding tutorial examples will use the inline binding form to obviate element-to-binding relationships. However, don't interpret this as a bias: there are many advantages to declaring your bindings within the view (such as keeping all functional definition within the view, and maintaining DOM cleanliness).</p>
  131. <p>Learn more about setting up view bindings in the <b><a href="documentation.html#view">Epoxy.View</a></b> documentation.</p>
  132. <h2 id="observables-computeds">Model Observables &amp; Computeds</h2>
  133. <p>Now let's add an <tt>Epoxy.Model</tt> into the mix. An Epoxy model introduces <i>observable attributes</i>, which are virtualized attributes of the model: they may be <b>get</b> and <b>set</b> just like normal model attributes, and will trigger <tt>"change"</tt> events on the model when modified, however they do not exist within the model's <b>attributes</b> table, nor will they be saved with model data. Observable attributes are a great way to store view-specific data (such as selection state) without polluting your model data. Also, observable attributes may be <i>computed</i>, meaning that they can construct values using other model attributes.</p>
  134. <p>Let's start by adding an Epoxy computed attribute, which will assemble its value using other model values:</p>
  135. <div class="example">
  136. <ul class="tabs">
  137. <li data-tab="js">JavaScript</li>
  138. <li data-tab="html">HTML</li>
  139. </ul>
  140. <pre><code class="js">var BindingModel = Backbone.Epoxy.Model.extend({
  141. defaults: {
  142. firstName: "Obi-Wan",
  143. lastName: "Kenobi"
  144. },
  145. computeds: {
  146. fullName: function() {
  147. return this.get("firstName") +" "+ this.get("lastName");
  148. }
  149. }
  150. });
  151. var view = new Backbone.Epoxy.View({
  152. el: "#app-computed",
  153. model: new BindingModel()
  154. });</code></pre>
  155. <pre><code class="html">&lt;div id="app-computed"&gt;
  156. &lt;label&gt;First:&lt;/label&gt;
  157. &lt;input type="text" data-bind="value:firstName,events:['keyup']"&gt;
  158. &lt;label&gt;Last:&lt;/label&gt;
  159. &lt;input type="text" data-bind="value:lastName,events:['keyup']"&gt;
  160. &lt;b&gt;Full Name:&lt;/b&gt;
  161. &lt;span data-bind="text:fullName">&lt;/span&gt;
  162. &lt;/div&gt;</code></pre>
  163. </div>
  164. <p>In this example, our Epoxy model includes a computed attribute, <tt>fullName</tt>, that assembles its value from other model values. Also, note our view's <tt>"text:fullName"</tt> binding. Because the computed <tt>fullName</tt> attribute can be <b>get</b> from the model just like any other attribute, it's able to bind seamlessly into an Epoxy view.</p>
  165. <p>However, what happens to the computed <tt>fullName</tt> attribute if the <tt>firstName</tt> or <tt>lastName</tt> attributes change? Good news: Epoxy can automatically map computed attribute dependencies, and will register model <tt>"change:attribute"</tt> events to keep the computed value in sync. We'll discuss computed dependency management in the next section.</p>
  166. <p>Learn more about observable and computed attributes in the <b><a href="documentation.html#model">Epoxy.Model</a></b> documentation.</p>
  167. <h2 id="computed-dependencies">Managing Computed Dependencies</h2>
  168. <p>As discussed in the previous example, Epoxy can automatically map and bind computed model attribute dependencies. While this may smell of black magic, all that's really going on is that Epoxy has wrapped the Backbone model's native <b>get</b> method, and is using it to keep track of requested attribute names. As long as you use an Epoxy model's <b>get</b> method for all attribute access (no digging directly into a model's <b>attributes</b> table), then one or more <tt>Epoxy.Model</tt> instances will automatically map references between one another.</p>
  169. <p>However, there's one big "gotcha" here... consider the following BROKEN example:</p>
  170. <pre><code class="js">// BROKEN:
  171. var BrokenModel = Backbone.Epoxy.Model.extend({
  172. defaults: {
  173. userName: "tatooine_luke",
  174. fullName: "Luke Skywalker",
  175. isOnline: true
  176. },
  177. computeds: {
  178. displayName: function() {
  179. return this.get("isOnline") ? this.get("fullName") : this.get("userName");
  180. }
  181. }
  182. });</code></pre>
  183. <p>See the problem above? Because <tt>displayName</tt> uses conditional logic, one of the two conditional <b>get</b> calls will be unreachable (and therefore missed) while automatically mapping dependencies. This makes for a busted model. To fix this, you may take two approaches...</p>
  184. <p>The first solution is to move all <b>get</b> calls outside of the conditional statement, and then let automatic mapping safely take its course. The following will work:</p>
  185. <pre><code class="js">// FIXED by pre-collecting references:
  186. var FixedModel = Backbone.Epoxy.Model.extend({
  187. defaults: {
  188. userName: "tatooine_luke",
  189. fullName: "Luke Skywalker",
  190. isOnline: true
  191. },
  192. computeds: {
  193. displayName: function() {
  194. var fullName = this.get("fullName");
  195. var userName = this.get("userName");
  196. return this.get("isOnline") ? fullName : userName;
  197. }
  198. }
  199. });</code></pre>
  200. <p>In the above solution, we've elevated all <b>get</b> calls to above the conditional block where they'll all be reached. This is <i>always</i> a good practice to follow when using automatic mapping.</p>
  201. <p>Alternatively, you may also declare computed dependencies manually, like so:</p>
  202. <pre><code class="js">// FIXED by manual declarations:
  203. var FixedModel = Backbone.Epoxy.Model.extend({
  204. defaults: {
  205. userName: "tatooine_luke",
  206. fullName: "Luke Skywalker",
  207. isOnline: true
  208. },
  209. computeds: {
  210. displayName: {
  211. deps: ["isOnline", "fullName", "userName"],
  212. get: function() {
  213. return this.get("isOnline") ? this.get("fullName") : this.get("userName");
  214. }
  215. }
  216. }
  217. });
  218. </code></pre>
  219. <p>In the above solution, we've defined the computed attribute using a params object with a <tt>deps</tt> array. This array declares attribute names that the computed getter depends on. Manually declaring dependencies will alleviate automation errors, yet introduce the margin for human errors. It's your call on which direction seems safer.</p>
  220. <p>Learn more about computed dependencies and automatic dependency mapping in the <b><a href="documentation.html#model-add-computed">Model.addComputed</a></b> documentation.</p>
  221. <h2 id="computed-get-set">Computed Getters and Setters</h2>
  222. <p>So far we've only looked at computed attributes using read-only <b>get</b> functions. Now let's create a read-write computed attribute that will both <b>get</b> a computed value, and <b>set</b> one or more mutated values back to the model:</p>
  223. <div class="example">
  224. <ul class="tabs">
  225. <li data-tab="js">JavaScript</li>
  226. <li data-tab="html">HTML</li>
  227. </ul>
  228. <pre><code class="js">var PriceModel = Backbone.Epoxy.Model.extend({
  229. defaults: {
  230. productName: "Light Saber",
  231. price: 5000
  232. },
  233. computeds: {
  234. displayPrice: {
  235. get: function() {
  236. return "$"+this.get("price");
  237. },
  238. set: function( value ) {
  239. return {price: parseInt(value.replace("$", "")||0, 10)};
  240. }
  241. }
  242. }
  243. });
  244. var view = new Backbone.Epoxy.View({
  245. el: "#app-readwrite",
  246. model: new PriceModel()
  247. });</code></pre>
  248. <pre><code class="html">&lt;div id="app-readwrite"&gt;
  249. &lt;label&gt;Price (updates on blur):&lt;/label&gt;
  250. &lt;input type="text" data-bind="value:displayPrice"&gt;
  251. &lt;b&gt;Display Price:&lt;/b&gt;
  252. &lt;span data-bind="text:displayPrice">&lt;/span&gt;
  253. &lt;b&gt;Model Price:&lt;/b&gt;
  254. &lt;span data-bind="text:price">&lt;/span&gt;
  255. &lt;/div&gt;</code></pre>
  256. </div>
  257. <p>Here, we've defined our computed attribute using a params object with both a <b>get</b> and <b>set</b> function. The <b>get</b> function will access our assembled model value, and the <b>set</b> function will mutate a raw value back into formatted model data. In the above example, the <tt>displayPrice</tt> computed attribute formats a currency string using its <b>get</b> method, and then reformats input as a valid number within its <b>set</b> method before submitting it back to the model.</p>
  258. <p>Note that the setter function returns an <i>attributes hash</i> rather than calling <b>set</b> on its model directly. Attributes returned by a computed setter will get merged into the model's running <b>set</b> operation. This allows a computed setter to define multiple attribute modifications, all of which are performed synchronously with other queued model changes.</p>
  259. <p>Learn more about computed getters and setters in the <b><a href="documentation.html#model-add-computed">Model.addComputed</a></b> documentation.</p>
  260. <h2 id="binding-operators">View Binding Operators</h2>
  261. <p>Now let's turn our attention back to using bindings within an Epoxy view. Epoxy tries to strike a balance between robust binding options and clean binding definitions. While Epoxy uses a similar binding technique to <a href="http://knockoutjs.com/">Knockout.js</a>, it intentionally discourages some of Knockout's inline-javascript allowances.</p>
  262. <p>Instead, Epoxy provides some operational wrappers for formatting data directly within your bindings. Notice how the <tt>not()</tt> and <tt>format()</tt> operators are used in the following binding scheme:</p>
  263. <div class="example">
  264. <ul class="tabs">
  265. <li data-tab="js">JavaScript</li>
  266. <li data-tab="html">HTML</li>
  267. </ul>
  268. <pre><code class="js">var view = new Backbone.Epoxy.View({
  269. el: "#app-operators",
  270. model: new Backbone.Model({
  271. firstName: "Luke",
  272. lastName: "Skywalker"
  273. })
  274. });
  275. </code></pre>
  276. <pre><code class="html">&lt;div id="app-operators"&gt;
  277. &lt;p&gt;
  278. &lt;label&gt;First name*&lt;/label&gt;
  279. &lt;input type="text" data-bind="value:firstName,events:['keyup']"&gt;
  280. &lt;span data-bind="toggle:not(firstName)"&gt;Please enter a first name.&lt;/span&gt;
  281. &lt;/p&gt;
  282. &lt;p&gt;
  283. &lt;label&gt;Last name*&lt;/label&gt;
  284. &lt;input type="text" data-bind="value:lastName,events:['keyup']"&gt;
  285. &lt;span data-bind="toggle:not(lastName)"&gt;Please enter a last name.&lt;/span&gt;
  286. &lt;/p&gt;
  287. &lt;p data-bind="text:format('Name: $1 $2',firstName,lastName)"&gt;&lt;/p&gt;
  288. &lt;p class="req"&gt;* Required&lt;/p&gt;
  289. &lt;/div&gt;</code></pre>
  290. </div>
  291. <p>In the above example, operational wrappers are used to format binding data for specific implementations. The <tt>not()</tt> operator is used to negate a value's truthiness, and the <tt>format()</tt> operator is used to combine multiple values into a display string through a familiar RegEx backreference format.</p>
  292. <p>The only catch with binding operators is that they may <strong>NOT</strong> be nested. Binding operators only accept primitive values and data within the binding context. This is a deliberate limitation about which Epoxy is fairly opinionated: application logic does not belong in your binding declarations. If a value requires more than a simple formatting pass, then it should be pre-processed within a model, or else applied to the view using a custom handler.</p>
  293. <p>See a full list of available <a href="documentation.html#binding-operators">binding operators</a> in documentation.</p>
  294. <h2 id="binding-handlers">Custom Binding Handlers</h2>
  295. <p>Epoxy provides a collection of default <a href="documentation.html#binding-handlers">binding handlers</a> that cover many basic view operations. For everything else, developers are encouraged to write their own binding handlers for specific operations within the view. Custom bindings handlers are easy to define:</p>
  296. <div class="example">
  297. <ul class="tabs">
  298. <li data-tab="js">JavaScript</li>
  299. <li data-tab="html">HTML</li>
  300. </ul>
  301. <pre><code class="js">var model = new Backbone.Model({shipsList: []});
  302. var BindingView = Backbone.Epoxy.View.extend({
  303. el: "#app-custom",
  304. bindingHandlers: {
  305. listing: function( $element, value ) {
  306. $element.text( value.join(", ") );
  307. }
  308. }
  309. });
  310. var view = new BindingView({model: model});</code></pre>
  311. <pre><code class="html">&lt;div id="app-custom"&gt;
  312. &lt;label&gt;Millennium Falcon:&lt;/label&gt;
  313. &lt;input type="checkbox" value="Millennium Falcon" data-bind="checked:shipsList"&gt;
  314. &lt;label&gt;Death Star:&lt;/label&gt;
  315. &lt;input type="checkbox" value="Death Star" data-bind="checked:shipsList"&gt;
  316. &lt;b&gt;Ships:&lt;/b&gt;
  317. &lt;span data-bind="listing:shipsList">&lt;/span&gt;
  318. &lt;/div&gt;</code></pre>
  319. </div>
  320. <p>In the above example, we've set up a custom binding handler called <tt>listing</tt> to neatly print out an array of values. That custom handler may then be declared within the view's bindings, as seen in the <tt>"listing:shipsList"</tt> binding.</p>
  321. <p>A binding handler is just a function that accepts two arguments: the first is a jQuery object wrapping the bound element, and the second is the data value being provided to the binding. Within a custom binding handler, you simply specify a process by which the value is formatted and then applied to the element. Note, the above example demonstrates a simple <i>read-only</i> binding.</p>
  322. <p>Learn more about custom handlers and how to configure a two-way binding in the <b><a href="documentation.html#view-binding-handlers">View.bindingHandlers</a></b> documentation.</p>
  323. <h2 id="binding-sources">Binding Collections &amp; Multiple Sources</h2>
  324. <p>So far we've only discussed the basic use case of binding a view to attributes of its <b>model</b> property. Now let's explore adding additional data sources, including <tt>Backbone.Collection</tt> instances.</p>
  325. <p>First off, what is a data source? A data source provides itself and its attributes to the <i>binding context</i> &mdash; which is a compiled list of all data available in the view. Data sources may be instances of <tt>Backbone.Model</tt> or <tt>Backbone.Collection</tt>. By default, an Epoxy view's <b>model</b> and <b>collection</b> properties are automatically configured as data sources (you may also <a href="documentation.html#view-binding-sources">add additional sources</a> if you need them).</p>
  326. <p>Sources are included in the binding context under the alias <tt>"$sourceName"</tt>. Therefore, the view's <b>model</b> and <b>collection</b> properties may be referenced within bindings as <tt>$model</tt> and <tt>$collection</tt>. These direct source references are used in cases such as the <b>collection</b> binding:</p>
  327. <div class="example">
  328. <ul class="tabs">
  329. <li data-tab="js">JavaScript</li>
  330. <li data-tab="html">HTML</li>
  331. </ul>
  332. <pre><code class="js">var ListItemView = Backbone.View.extend({
  333. tagName: "li",
  334. initialize: function() {
  335. this.$el.text( this.model.get("label") );
  336. }
  337. });
  338. var ListCollection = Backbone.Collection.extend({
  339. model: Backbone.Model,
  340. view: ListItemView
  341. });
  342. var ListView = Backbone.Epoxy.View.extend({
  343. el: "#bind-collection",
  344. initialize: function() {
  345. this.collection = new ListCollection();
  346. this.collection.reset([{label: "Luke Skywalker"}, {label: "Han Solo"}]);
  347. }
  348. });
  349. var view = new ListView();</code></pre>
  350. <pre><code class="html">&lt;div id="bind-collection"&gt;
  351. &lt;ul data-bind="collection:$collection"&gt;&lt;/ul&gt;
  352. &lt;/div&gt;</code></pre>
  353. </div>
  354. <p>In the above example, <tt>data-bind="collection:$collection"</tt> binds an unordered list's contents to the view's <b>collection</b> data source. However, what renders individual collection items? Note how a <b>view</b> property is assigned to the <tt>Backbone.Collection</tt> object... That <b>view</b> property defines an item renderer for the collection; this is Epoxy's one and only assertion on the native <tt>Backbone.Collection</tt> object.</p>
  355. <p>For more details on setting up a collection binding, see the <a href="documentation.html#handler-collection">collection handler</a> documentation. For more examples on using data source bindings, see the <a href="#epoxy-todos">Epoxy ToDos</a> demo below.</p>
  356. <h2 id="epoxy-todos">Epoxy ToDos</h2>
  357. <p>Per the status-quo of JavaScript MV* frameworks, let's build a small ToDos app using Epoxy view bindings paired with native Backbone models:</p>
  358. <div class="example">
  359. <ul class="tabs">
  360. <li data-tab="js">JavaScript</li>
  361. <li data-tab="html">HTML</li>
  362. </ul>
  363. <pre><code class="js">// Model for each ToDo item:
  364. var TodoItemModel = Backbone.Model.extend({
  365. defaults: {
  366. todo: "",
  367. complete: false
  368. }
  369. });
  370. // Epoxy View for each ToDo item:
  371. var TodoItemView = Backbone.Epoxy.View.extend({
  372. el:"&lt;li&gt;&lt;input type='checkbox'&gt; &lt;input type='text' class='todo'&gt;&lt;/li&gt;",
  373. bindings: {
  374. "input[type='text']": "value:todo,readonly:complete,save:$model",
  375. "input[type='checkbox']": "checked:complete,save:$model"
  376. },
  377. bindingHandlers: {
  378. readonly: function( $element, value ) {
  379. $element.prop( "readonly", !!value );
  380. },
  381. save: function( $element, $model ) {
  382. $model.save();
  383. }
  384. }
  385. });
  386. // Collection for ToDo items:
  387. var TodosCollection = Backbone.Collection.extend({
  388. model: TodoItemModel,
  389. view: TodoItemView,
  390. localStorage: new Backbone.LocalStorage("todos")
  391. });
  392. // Epoxy View for main ToDos app:
  393. var TodoAppView = Backbone.Epoxy.View.extend({
  394. el: "#epoxy-todo-app",
  395. collection: new TodosCollection(),
  396. initialize: function() {
  397. this.collection.fetch();
  398. },
  399. events: {
  400. "click .add": "onAdd",
  401. "click .cleanup": "onCleanup",
  402. "keydown .todo-add": "onEnter"
  403. },
  404. onEnter: function( evt ) {
  405. if ( evt.which == 13 ) this.onAdd();
  406. },
  407. onAdd: function() {
  408. var input = this.$(".todo-add");
  409. if ( input.val() ) {
  410. this.collection.create({todo: input.val()});
  411. input.val("");
  412. }
  413. },
  414. onCleanup: function() {
  415. _.invoke(this.collection.where({complete:true}), "destroy");
  416. }
  417. });
  418. var app = new TodoAppView();</code></pre>
  419. <pre><code class="html">&lt;div id="epoxy-todo-app"&gt;
  420. &lt;b&gt;What do you need to do?&lt;/b&gt;
  421. &lt;p&gt;
  422. &lt;input type="text" class="todo-add"&gt;
  423. &lt;button class="add"&gt;Add&lt;/button&gt;
  424. &lt;/p&gt;
  425. &lt;ul class="todos" data-bind="collection:$collection"&gt;&lt;/ul&gt;
  426. &lt;button class="cleanup"&gt;Clear complete&lt;/button&gt;
  427. &lt;/div&gt;</code></pre>
  428. </div>
  429. <p>There are four components used within this application, including:</p>
  430. <ul>
  431. <li><tt>TodoItemModel</tt> : this is a native Backbone model used to store the data required for each individual todo; in this example, each todo item has a <tt>todo</tt> caption, and a <tt>complete</tt> status.</li>
  432. <li><tt>TodoItemView</tt> : this is an Epoxy view used for the display of each individual todo list item. This view constructs a DOM fragment with a checkbox and text input, and then binds those elements' values to the view's model. In addition, the view adds a few custom binding handlers to help manage the view: the <tt>readonly:</tt> handler toggles the text input's <tt>"readonly"</tt> property, and the <tt>save:</tt> binding is used used to call <b>save</b> on the bound model after elements are changed.</li>
  433. <li><tt>TodosCollection</tt> : this is a native Backbone collection used to manage our active list of todos. It cites <tt>TodoItemModel</tt> as its model constructor, and <tt>TodoItemView</tt> as its view constructor. Remember, a collection must specify a <b>view</b> constructor property in order to work with Epoxy's <tt>collection:</tt> binding.</li>
  434. <li><tt>TodoAppView</tt> : finally, this Epoxy view manages the main application container view. It uses native Backbone <tt>events</tt> to setup the application's primary controls used to add and remove items from the <tt>TodosCollection</tt> instance. It also applies an Epoxy <tt>collection:</tt> binding to the view's default collection source (referenced as <tt>$collection</tt>).</li>
  435. </ul>
  436. <p>Mind you, this application certainly did not require data binding to make it work. In fact, data binding is overkill for many common application scenarios. Keep that in mind while assessing the goals and objectives of your projects. Ironically, the author of this library is a very reserved advocate of data binding: while data binding is a great tool at the moments when you need it, it should NOT be considered as an automatic choice approach when creating an interface; especially when using Backbone. However when a situation does lend itself to data binding... you'd like it to be done well.</p>
  437. <div class="footer" role="contentinfo">
  438. <p>Epoxy.js for Backbone is available on <a href="https://github.com/gmac/backbone.epoxy">GitHub</a>.<br>
  439. &copy; 2013 Greg MacWilliam (<a href="https://twitter.com/gmacwilliam">@gmacwilliam</a>), a project of <a href="http://www.threespot.com">Threespot</a>.</p>
  440. </div>
  441. </div>
  442. </div>
  443. <script src="js/jquery.js"></script>
  444. <script src="js/underscore.js"></script>
  445. <script src="js/backbone.js"></script>
  446. <script src="js/backbone-localstorage.js"></script>
  447. <script src="js/backbone.epoxy.min.js"></script>
  448. <script src="js/global.js"></script>
  449. </body>
  450. </html>