Skip to content

Commit c0ee36f

Browse files
author
Rebecca Murphey
committed
code org concepts md
1 parent b871650 commit c0ee36f

File tree

1 file changed

+309
-0
lines changed

1 file changed

+309
-0
lines changed

content/code-organization/concepts.md

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

0 commit comments

Comments
 (0)