|
| 1 | +--- |
| 2 | +chapter : Code Organization |
| 3 | +section: 0 |
| 4 | +title: Code Organization Concepts |
| 5 | +attribution: $jQuery Fundamentals |
| 6 | +--- |
| 7 | + |
| 8 | +When you move beyond adding simple enhancements to your website with jQuery and |
| 9 | +start developing full-blown client-side applications, you need to consider how |
| 10 | +to organize your code. In this chapter, we'll take a look at various code |
| 11 | +organization patterns you can use in your jQuery application and explore the |
| 12 | +RequireJS dependency management and build system. |
| 13 | + |
| 14 | +## Key Concepts |
| 15 | + |
| 16 | +Before we jump into code organization patterns, it's important to understand |
| 17 | +some concepts that are common to all good code organization patterns. |
| 18 | + |
| 19 | +- Your code should be divided into units of functionality — modules, services, |
| 20 | + etc. Avoid the temptation to have all of your code in one huge |
| 21 | + `$(document).ready()` block. This concept, loosely, is known as |
| 22 | + encapsulation. |
| 23 | +- Don't repeat yourself. Identify similarities among pieces of functionality, |
| 24 | + and use inheritance techniques to avoid repetitive code. |
| 25 | +- Despite jQuery's DOM-centric nature, JavaScript applications are not all |
| 26 | + about the DOM. Remember that not all pieces of functionality need to — or |
| 27 | + should — have a DOM representation. |
| 28 | +- Units of functionality should be [loosely |
| 29 | + coupled](http://en.wikipedia.org/wiki/Loose_coupling) -- a unit of |
| 30 | + functionality should be able to exist on its own, and communication between |
| 31 | + units should be handled via a messaging system such as custom events or |
| 32 | + pub/sub. Stay away from direct communication between units of functionality |
| 33 | + whenever possible. |
| 34 | + |
| 35 | +The concept of loose coupling can be especially troublesome to developers |
| 36 | +making their first foray into complex applications, so be mindful of this as |
| 37 | +you're getting started. |
| 38 | + |
| 39 | +## Encapsulation |
| 40 | + |
| 41 | +The first step to code organization is separating pieces of your application |
| 42 | +into distinct pieces; sometimes, even just this effort is sufficient to lend |
| 43 | + |
| 44 | +### The Object Literal |
| 45 | + |
| 46 | +An object literal is perhaps the simplest way to encapsulate related code. It |
| 47 | +doesn't offer any privacy for properties or methods, but it's useful for |
| 48 | +eliminating anonymous functions from your code, centralizing configuration |
| 49 | +options, and easing the path to reuse and refactoring. |
| 50 | + |
| 51 | +<div class="example" markdown="1"> |
| 52 | +An object literal |
| 53 | + |
| 54 | + var myFeature = { |
| 55 | + myProperty : 'hello', |
| 56 | + |
| 57 | + myMethod : function() { |
| 58 | + console.log(myFeature.myProperty); |
| 59 | + }, |
| 60 | + |
| 61 | + init : function(settings) { |
| 62 | + myFeature.settings = settings; |
| 63 | + }, |
| 64 | + |
| 65 | + readSettings : function() { |
| 66 | + console.log(myFeature.settings); |
| 67 | + } |
| 68 | + }; |
| 69 | + |
| 70 | + myFeature.myProperty; // 'hello' |
| 71 | + myFeature.myMethod(); // logs 'hello' |
| 72 | + myFeature.init({ foo : 'bar' }); |
| 73 | + myFeature.readSettings(); // logs { foo : 'bar' } |
| 74 | +</div> |
| 75 | + |
| 76 | +The object literal above is simply an object assigned to a variable. The object |
| 77 | +has one property and several methods. All of the properties and methods are |
| 78 | +public, so any part of your application can see the properties and call methods |
| 79 | +on the object. While there is an init method, there's nothing requiring that it |
| 80 | +be called before the object is functional. |
| 81 | + |
| 82 | +How would we apply this pattern to jQuery code? Let's say that we had this code |
| 83 | +written in the traditional jQuery style: |
| 84 | + |
| 85 | + // clicking on a list item loads some content |
| 86 | + // using the list item's ID and hides content |
| 87 | + // in sibling list items |
| 88 | + $(document).ready(function() { |
| 89 | + $('#myFeature li') |
| 90 | + .append('<div/>') |
| 91 | + .click(function() { |
| 92 | + var $this = $(this); |
| 93 | + var $div = $this.find('div'); |
| 94 | + $div.load('foo.php?item=' + |
| 95 | + $this.attr('id'), |
| 96 | + function() { |
| 97 | + $div.show(); |
| 98 | + $this.siblings() |
| 99 | + .find('div').hide(); |
| 100 | + } |
| 101 | + ); |
| 102 | + }); |
| 103 | + }); |
| 104 | + |
| 105 | +If this were the extent of our application, leaving it as-is would be fine. On |
| 106 | +the other hand, if this was a piece of a larger application, we'd do well to |
| 107 | +keep this functionality separate from unrelated functionality. We might also |
| 108 | +want to move the URL out of the code and into a configuration area. Finally, we |
| 109 | +might want to break up the chain to make it easier to modify pieces of the |
| 110 | +functionality later. |
| 111 | + |
| 112 | +<div class="example" markdown="1"> |
| 113 | +Using an object literal for a jQuery feature |
| 114 | + |
| 115 | + var myFeature = { |
| 116 | + init : function(settings) { |
| 117 | + myFeature.config = { |
| 118 | + $items : $('#myFeature li'), |
| 119 | + $container : $('<div class="container"></div>'), |
| 120 | + urlBase : '/foo.php?item=' |
| 121 | + }; |
| 122 | + |
| 123 | + // allow overriding the default config |
| 124 | + $.extend(myFeature.config, settings); |
| 125 | + |
| 126 | + myFeature.setup(); |
| 127 | + }, |
| 128 | + |
| 129 | + setup : function() { |
| 130 | + myFeature.config.$items |
| 131 | + .each(myFeature.createContainer) |
| 132 | + .click(myFeature.showItem); |
| 133 | + }, |
| 134 | + |
| 135 | + createContainer : function() { |
| 136 | + var $i = $(this), |
| 137 | + $c = myFeature.config.$container.clone() |
| 138 | + .appendTo($i); |
| 139 | + |
| 140 | + $i.data('container', $c); |
| 141 | + }, |
| 142 | + |
| 143 | + buildUrl : function() { |
| 144 | + return myFeature.config.urlBase + |
| 145 | + myFeature.$currentItem.attr('id'); |
| 146 | + }, |
| 147 | + |
| 148 | + showItem : function() { |
| 149 | + var myFeature.$currentItem = $(this); |
| 150 | + myFeature.getContent(myFeature.showContent); |
| 151 | + }, |
| 152 | + |
| 153 | + getContent : function(callback) { |
| 154 | + var url = myFeature.buildUrl(); |
| 155 | + myFeature.$currentItem |
| 156 | + .data('container').load(url, callback); |
| 157 | + }, |
| 158 | + |
| 159 | + showContent : function() { |
| 160 | + myFeature.$currentItem |
| 161 | + .data('container').show(); |
| 162 | + myFeature.hideContent(); |
| 163 | + }, |
| 164 | + |
| 165 | + hideContent : function() { |
| 166 | + myFeature.$currentItem.siblings() |
| 167 | + .each(function() { |
| 168 | + $(this).data('container').hide(); |
| 169 | + }); |
| 170 | + } |
| 171 | + }; |
| 172 | + |
| 173 | + $(document).ready(myFeature.init); |
| 174 | +</div> |
| 175 | + |
| 176 | +The first thing you'll notice is that this approach is obviously far longer |
| 177 | +than the original -- again, if this were the extent of our application, using an |
| 178 | +object literal would likely be overkill. Assuming it's not the extent of our |
| 179 | +application, though, we've gained several things: |
| 180 | + |
| 181 | +- We've broken our feature up into tiny methods. In the future, if we want to |
| 182 | + change how content is shown, it's clear where to change it. In the original |
| 183 | + code, this step is much harder to locate. |
| 184 | +- We've eliminated the use of anonymous functions. |
| 185 | +- We've moved configuration options out of the body of the code and put them in |
| 186 | + a central location. |
| 187 | +- We've eliminated the constraints of the chain, making the code easier to |
| 188 | + refactor, remix, and rearrange. |
| 189 | + |
| 190 | +For non-trivial features, object literals are a clear improvement over a long |
| 191 | +stretch of code stuffed in a `$(document).ready()` block, as they get us |
| 192 | +thinking about the pieces of our functionality. However, they aren't a whole |
| 193 | +lot more advanced than simply having a bunch of function declarations inside of |
| 194 | +that `$(document).ready()` block. |
| 195 | + |
| 196 | +### The Module Pattern |
| 197 | + |
| 198 | +The module pattern overcomes some of the limitations of the object literal, |
| 199 | +offering privacy for variables and functions while exposing a public API if |
| 200 | +desired. |
| 201 | + |
| 202 | +<div class="example" markdown="1"> |
| 203 | +The module pattern |
| 204 | + |
| 205 | + var feature =(function() { |
| 206 | + |
| 207 | + // private variables and functions |
| 208 | + var privateThing = 'secret', |
| 209 | + publicThing = 'not secret', |
| 210 | + |
| 211 | + changePrivateThing = function() { |
| 212 | + privateThing = 'super secret'; |
| 213 | + }, |
| 214 | + |
| 215 | + sayPrivateThing = function() { |
| 216 | + console.log(privateThing); |
| 217 | + changePrivateThing(); |
| 218 | + }; |
| 219 | + |
| 220 | + // public API |
| 221 | + return { |
| 222 | + publicThing : publicThing, |
| 223 | + sayPrivateThing : sayPrivateThing |
| 224 | + } |
| 225 | + |
| 226 | + })(); |
| 227 | + |
| 228 | + feature.publicThing; // 'not secret' |
| 229 | + |
| 230 | + feature.sayPrivateThing(); |
| 231 | + // logs 'secret' and changes the value |
| 232 | + // of privateThing |
| 233 | +</div> |
| 234 | + |
| 235 | +In the example above, we self-execute an anonymous function that returns an |
| 236 | +object. Inside of the function, we define some variables. Because the variables |
| 237 | +are defined inside of the function, we don't have access to them outside of the |
| 238 | +function unless we put them in the return object. This means that no code |
| 239 | +outside of the function has access to the `privateThing` variable or to the |
| 240 | +`changePrivateThing` function. However, `sayPrivateThing` does have access to |
| 241 | +`privateThing` and `changePrivateThing`, because both were defined in the same |
| 242 | +scope as `sayPrivateThing`. |
| 243 | + |
| 244 | +This pattern is powerful because, as you can gather from the variable names, it |
| 245 | +can give you private variables and functions while exposing a limited API |
| 246 | +consisting of the returned object's properties and methods. |
| 247 | + |
| 248 | +Below is a revised version of the previous example, showing how we could create |
| 249 | +the same feature using the module pattern while only exposing one public method |
| 250 | +of the module, `showItemByIndex()`. |
| 251 | + |
| 252 | +<div class="example" markdown="1"> |
| 253 | +Using the module pattern for a jQuery feature |
| 254 | + |
| 255 | + $(document).ready(function() { |
| 256 | + var feature = (function() { |
| 257 | + var $items = $('#myFeature li'), |
| 258 | + $container = $('<div class="container"></div>'), |
| 259 | + $currentItem, |
| 260 | + |
| 261 | + urlBase = '/foo.php?item=', |
| 262 | + |
| 263 | + createContainer = function() { |
| 264 | + var $i = $(this), |
| 265 | + $c = $container.clone().appendTo($i); |
| 266 | + |
| 267 | + $i.data('container', $c); |
| 268 | + }, |
| 269 | + |
| 270 | + buildUrl = function() { |
| 271 | + return urlBase + $currentItem.attr('id'); |
| 272 | + }, |
| 273 | + |
| 274 | + showItem = function() { |
| 275 | + var $currentItem = $(this); |
| 276 | + getContent(showContent); |
| 277 | + }, |
| 278 | + |
| 279 | + showItemByIndex = function(idx) { |
| 280 | + $.proxy(showItem, $items.get(idx)); |
| 281 | + }, |
| 282 | + |
| 283 | + getContent = function(callback) { |
| 284 | + $currentItem.data('container').load(buildUrl(), callback); |
| 285 | + }, |
| 286 | + |
| 287 | + showContent = function() { |
| 288 | + $currentItem.data('container').show(); |
| 289 | + hideContent(); |
| 290 | + }, |
| 291 | + |
| 292 | + hideContent = function() { |
| 293 | + $currentItem.siblings() |
| 294 | + .each(function() { |
| 295 | + $(this).data('container').hide(); |
| 296 | + }); |
| 297 | + }; |
| 298 | + |
| 299 | + $items |
| 300 | + .each(createContainer) |
| 301 | + .click(showItem); |
| 302 | + |
| 303 | + return { showItemByIndex : showItemByIndex }; |
| 304 | + })(); |
| 305 | + |
| 306 | + feature.showItemByIndex(0); |
| 307 | + }); |
| 308 | +</div> |
0 commit comments