forked from maryrosecook/androjs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.html
295 lines (236 loc) · 11 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Andro.js</title>
<link rel="stylesheet" type="text/css" href="resources/main.css" />
<link href="resources/prettify.css" type="text/css" rel="stylesheet" />
<script type="text/javascript" src="resources/prettify.js"></script>
</head>
<body onload="prettyPrint()">
<h1>Andro.js</h1>
<div class="strapline">Mix behaviours into objects</div>
<h2>What is Andro.js?</h2>
<p>
Andro.js takes mixins and applies them, each in its own namespace, to an object, and lets them talk to one another via an event emitter.
</p>
<h3>Come again?</h3>
Imagine a cube. It can be touched. You want this cube to make a sound when it goes from not being touched to being touched. You write a little behaviour that emits an event when a first touch occurs. You write another little behaviour that plays a sound when it receives a first touch event. You combine these behaviours on your cube with Andro.js.
<p>
<p>
Or, to put it another way, Andro.js a library for objects that can't quite decide who they are.
</p>
<h2>Get the code</h2>
<ul>
<li><a href='/andro-min.js'>Minified</a></li>
<li><a href='/andro.js'>Single file</a></li>
<li>
<a href='https://github.com/maryrosecook/androjs'>GitHub</a>
</li>
<li><a href='https://npmjs.org/androjs'>npm</a> <code>$ npm install androjs</code></li>
</ul>
<h2>Example</h2>
<p>
I require the <code>andro.js</code> file. I define the owner object as a constructor called <code>Cube</code>. It has a <code>touch</code> function that, when called, uses the <code>andro.eventer()</code> function to get the eventer to emit a <code>touch</code> event to all behaviours attached to the cube.
</p>
<pre class="prettyprint">
var andro = require('andro').andro;
function Cube() {
this.touch = function(contact) {
andro.eventer(this).emit("touch", contact);
};
};
</pre>
<p>
I define <code>firstTouchBehaviour</code>. This binds to the <code>touch</code> events emitted by its owner and keeps track of the number of things currently in contact. When the owner goes from being untouched to being touched, <code>firstTouchBehaviour</code> emits a <code>FirstTouch:newlyBeingTouched</code> event.
</p>
<pre class="prettyprint">
var firstTouchBehaviour = {
touchCount: 0,
setup: function(owner, eventer) {
eventer.bind(this, "touch", function(contact) {
if(contact === "added") {
if(this.touchCount === 0) {
eventer.emit("FirstTouch:newlyBeingTouched");
}
this.touchCount++;
} else if(contact === "removed") {
this.touchCount--;
}
});
}
};
</pre>
<p>
I define <code>soundBehaviour</code>. This binds to the <code>FirstTouch:newlyBeingTouched</code> event. Each time this event occurs, <code>soundBehaviour</code> makes a noise: "Rarrrrrwwwwwwwwwwwwwwww".
</p>
<pre class="prettyprint">
var soundBehaviour = {
setup: function(owner, eventer) {
eventer.bind(this, "FirstTouch:newlyBeingTouched", function() {
console.log("Rarrrrrwwwwwwwwwwwwwwww");
});
}
};
</pre>
<p>
I now put everything together. I instantiate <code>cube</code>. I augment it with <code>firstTouchBehaviour</code> and <code>soundBehaviour</code>. I simulate two touches upon the cube. On the first, it roars. On the second, it does not.
</p>
<pre class="prettyprint">
var cube = new Cube();
andro.augment(cube, firstTouchBehaviour);
andro.augment(cube, soundBehaviour);
cube.touch("added"); // rarrrww
cube.touch("added"); // silence
</pre>
<h2>Why is this cool?</h2>
<p>
Behaviours are completely separate from each other, so they are reusable. You could write <code>explodeBehaviour</code>, combine it with <code>firstTouchBehaviour</code> and have exploding cubes. Or you could write <code>wetBehaviour</code>, combine it with explodey and touchey and have depth charges.
</p>
<p>
Behaviour mixins are good for writing video games. They help with the concoction of game object logic: no more nightmarish inheritance hierarchies or weird bundles of functions that invent cryptic interfaces for the objects upon which they operate.
</p>
<p>
See <a href="#notcool">the end of this document</a> for why Andro.js might not be cool.
</p>
<h2>Reference</h2>
<h3>Add a behaviour to an owner object</h3>
<p>
A behaviour is a JavaScript object that has some properties and functions. It can be an empty object, if you like. Here is a behaviour that is not an empty object, but still does absolutely nothing:
</p>
<pre class="prettyprint">
var wooBehaviour = {
setup: function(owner, eventer, settings) {
this.owner = owner;
}
};
</pre>
<p>
We add this behaviour to the owner object by calling <code>augment()</code> and passing in <code>ownerObject</code>, <code>wooBehaviour</code> and an optional <code>settings</code> object. This creates a behaviour object, adds it to the <code>ownerObject.behaviours</code> array and writes the properties and functions of the behaviour to it.
</p>
<pre class="prettyprint">
var ownerObject = {};
andro.augment(ownerObject, wooBehaviour, {
followUp: "hoo"
});
</pre>
<p>
<code>wooBehaviour</code> includes the optional function, <code>setup()</code>, that will automatically be run when we call <code>augment()</code>. When it is run, <code>setup()</code> receives an <code>owner</code> argument that is bound to the owner of the behaviour, and the optional <code>settings</code> object, if one was passed to <code>augment()</code>.
</p>
<h3>Bind a behaviour to an event</h3>
<p>
We can improve the version of <code>wooBehaviour</code> from the previous section so that, when it receives an event, <code>woo</code>, it goes "woo":
</p>
<pre class="prettyprint">
var wooBehaviour = {
setup: function(owner, eventer, settings) {
this.owner = owner;
eventer.bind(this, "woo", function() {
console.log("Woo.");
});
}
};
</pre>
<p>
The <code>setup()</code> function now calls <code>bind</code>, passing the behaviour, event name and a callback. Now, whenever the event, <code>woo</code>, is emitted, our behaviour will go, "woo".
</p>
<h3>Emit events from a behaviour</h3>
<p>
We can further improve the <code>wooBehaviour</code> from the previous section. As well as going "woo", it will emit an event, <code>hoo</code>.
</p>
<pre class="prettyprint">
var wooBehaviour = {
setup: function(owner, eventer, settings) {
this.owner = owner;
this.eventer = eventer;
this.followUp = settings.followUp;
eventer.bind(this, "woo", this.woo);
},
woo: function() {
console.log("Woo.");
this.eventer.emit(this.followUp, "Woo");
}
};
</pre>
<p>
We emitted the <code>hoo</code> event that was passed in via <code>settings</code>. We sent along an identifying string, <code>Woo</code>, as data. That way, anyone who binds to the <code>hoo</code> event will know who is doing the hooing.
</p>
<h3>Unbind a behaviour from an event</h3>
<p>
Let's say we become worried that we are going a bit mental with the wooing. We can alter our behaviour so that, after wooing once, it unbinds from the <code>woo</code> event, thus cutting out any future woos.
</p>
<pre class="prettyprint">
woo: function() {
console.log("Woo.");
this.eventer.emit(this.followUp, "Woo");
this.eventer.unbind(this, "woo");
}
</pre>
<p>
See how on line four it calls unbind on the eventer, passing in the behaviour and the event to unbind from?
</p>
<h3>Export from a behaviour</h3>
<p>
To gild the lily, we will make <code>wooBehaviour</code> respond to enquiries about whether "woo" has been said. To do this, we add an attribute called <code>hasWooed</code> and alter the <code>woo</code> function to set <code>hasWooed</code> to <code>true</code>. We add a function, <code>getHasWooed()</code>, that returns <code>hasWooed</code>. Finally, we alter <code>setup()</code> so that it returns an object that includes <code>getHasWooed</code>. This function will get written onto the owner object so it can be used by the owner, or by other objects.
</p>
<pre class="prettyprint">
var wooBehaviour = {
hasWooed: false,
setup: function(owner, eventer, settings) {
this.owner = owner;
this.eventer = eventer;
this.followUp = settings.followUp;
eventer.bind(this, "woo", this.woo);
return {
"getHasWooed": this.getHasWooed
};
},
woo: function() {
console.log("Woo.");
this.hasWooed = true;
eventer.emit(this.followUp, "Woo");
eventer.unbind(this, "woo");
},
getHasWooed: function() {
return this.hasWooed;
}
};
</pre>
<h3>Remove Andro augmentation</h3>
<p>
When we grow tired of all the wooing and hooing, we can restore our faithful old object to its pre-Andro state by calling <code>tearDown()</code>. This will remove the andro object from the owner object and unbind all event callbacks.
</p>
<pre class="prettyprint">
andro.tearDown(ownerObject);
</pre>
<h2><a name="notcool">Why might this not be cool?</a></h2>
<p>
Andro.js contains four modes of expression: mixins, exports, settings and event emitters. The library protects you from harming your owner objects: behaviours are name-spaced and you cannot give an export the same name as an existing owner attribute. However, armed with an event emitter and mixins, you can still express yourself into a hell of a mess. Therefore, I humbly offer you some things that seem to be true more often than not.
</p>
<p>
Behaviours should have one responsbility: flash a light, make a sound, register touches.
</p>
<p>
Status behaviours are good. That is, behaviours that solely talk about what is going on. Like, "My owner is no longer being touched by anything".
</p>
<p>
Status-consuming behaviours are also good. Like, "Every time I hear that my owner has been touched, I make a sound."
</p>
<p>
Exported functions that allow the interrogation of state are OK. Like, "Here you are, owner. Here is a function that others can use to find out if anything is touching you."
</p>
<p>
Changing the state of the owner, either via an exported function or from within a behaviour: bad.
</p>
<p>
Behaviours that mediate between other behaviours are hard to understand. Like, "Every time I hear my owner has been touched, I emit an event that tells the sound behaviour to pipe up." Use these sparingly and name them well.
</p>
<h2>Licence</h2>
<p>
The <a href='http://github.com/maryrosecook/androjs'>code</a> is open source, under the <a href='http://en.wikipedia.org/wiki/MIT_License'>MIT licence</a>.
</p>
<hr/>
<div class="footer">
<a href="http://maryrosecook.com">maryrosecook.com</a>
</div>
</body>
</html>