-
Notifications
You must be signed in to change notification settings - Fork 0
/
README.html
586 lines (579 loc) · 41.8 KB
/
README.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
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Classy.js - classes and mixins in Javascript</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link type="text/css" rel="stylesheet" href="assets/css/bootstrap.min.css"/>
<style>
body {
padding-top: 60px; /* 60px to make the container go all the way to the bottom of the topbar */
}
@media (min-width: 980px) {
/* make sure headers dont go below the fixed navbar when using #links */
/* http://css-tricks.com/hash-tag-links-padding/ */
h2:before, h3:before, h4:before {
display: block;
content: " ";
margin-top: -40px;
height: 40px;
visibility: hidden;
}
}
/* highlight target */
h2:target, h3:target, h4:target { background: yellow; }
/* styles for TOC menu */
li.toc1 { display: none;}
li.toc2 { padding-left: 0px; border-bottom: 1px solid rgba(0, 0, 0, 0.2);}
li.toc2 a { font-weight: bold;}
li.toc3 { padding-left: 15px; }
li.toc4 { padding-left: 30px; }
li.toc4 a { font-size: 12px; }
li.toc5 { display: none; }
</style>
<link type="text/css" rel="stylesheet" href="assets/css/bootstrap-responsive.min.css"/>
<link type="text/css" rel="stylesheet" href="assets/css/prettify.css"/>
</head>
<body onload="prettyPrint()">
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="navbar-inner">
<div class="container">
<a class="brand" href="#">Classy.js - classes and mixins in Javascript</a>
<div class="btn-group pull-right">
<a class="btn dropdown-toggle btn-inverse" data-toggle="dropdown" href="#">
Content
<span class="caret"></span>
</a>
<ul class="dropdown-menu"><li class="toc1""><a href="#classy___yet_another__object_oriented_framework_for_javascript">Classy, (yet another) object-oriented framework for Javascript</a></li><li class="toc2""><a href="#a_taste_of_how_it__39_s_used_">A taste of how it's used</a></li><li class="toc3""><a href="#declaring_a_class">Declaring a class</a></li><li class="toc3""><a href="#declaring_a_subclass">Declaring a subclass</a></li><li class="toc3""><a href="#wrappers_and_mixins">Wrappers and Mixins</a></li><li class="toc2""><a href="#what__39_s_different_between_classy_and_other_oo_frameworks">What's different between Classy and other OO frameworks</a></li><li class="toc3""><a href="#declaring_classes">Declaring classes</a></li><li class="toc3""><a href="#fields">Fields</a></li><li class="toc3""><a href="#constructors">Constructors</a></li><li class="toc3""><a href="#call_to_super">Call to super</a></li><li class="toc3""><a href="#method_wrappers">Method wrappers</a></li><li class="toc3""><a href="#mixins">Mixins</a></li><li class="toc3""><a href="#field_wrappers">Field wrappers</a></li><li class="toc3""><a href="#disclaimer">Disclaimer</a></li><li class="toc2""><a href="#user_manual">User Manual</a></li><li class="toc3""><a href="#defining_classes">Defining classes</a></li><li class="toc4""><a href="#fields">Fields</a></li><li class="toc4""><a href="#constructors_and_object_instantiation">Constructors and object instantiation</a></li><li class="toc4""><a href="#methods">Methods</a></li><li class="toc4""><a href="#calls_to_super">Calls to super</a></li><li class="toc4""><a href="#instance_methods">Instance methods</a></li><li class="toc4""><a href="#class_fields_and_class_methods">Class fields and Class methods</a></li><li class="toc4""><a href="#chaining_calls">Chaining calls</a></li><li class="toc3""><a href="#method_wrappers">Method wrappers</a></li><li class="toc3""><a href="#field_wrappers">Field wrappers</a></li><li class="toc4""><a href="#wrapping_fields_at_the_object_level">Wrapping fields at the object level</a></li><li class="toc4""><a href="#wrapping_fields_at_the_class_level">Wrapping fields at the class level</a></li><li class="toc3""><a href="#mixins">Mixins</a></li><li class="toc3""><a href="#introspection">Introspection</a></li></ul>
</div>
</div>
</div>
</div>
<div class="container">
<a name="classy___yet_another__object_oriented_framework_for_javascript"></a><h1>Classy, (yet another) object-oriented framework for Javascript<a class="anchorlink" href="#classy___yet_another__object_oriented_framework_for_javascript"></a></h1>
<p><em>© 2011-2013, Michel Beaudouin-Lafon, [email protected]</em><br><em>Open-sourced under the MIT License</em></p>
<a name="a_taste_of_how_it__39_s_used_"></a><h2>A taste of how it's used:<a class="anchorlink" href="#a_taste_of_how_it__39_s_used_"></a></h2>
<p>Classy is a lightweight framework to program with classes in Javascript.
Here is a quick overview of its features.</p>
<a name="declaring_a_class"></a><h3>Declaring a class<a class="anchorlink" href="#declaring_a_class"></a></h3>
<p>Define a class <code>Shape</code> with two properties (<code>x</code> and <code>y</code>), a default constructor and some methods:</p>
<pre class="prettyprint">var Shape = Classy.newClass();
Shape.fields({x: 0, y: 0});
Shape.constructor(function(x, y) {
this.x = x; this.y = y;
});
Shape.methods({
moveby: function (dx, dy) { this.x += dx; this.y += dy; },
moveto: function (x, y) { this.x = x; this.y = y; },
});</pre>
<p>Create a shape, call a method and print its properties:</p>
<pre class="prettyprint">var s = Shape.create(5, 5);
s.moveby(10, 15);
console.log(s.x,',',s.y); // 15,20</pre>
<a name="declaring_a_subclass"></a><h3>Declaring a subclass<a class="anchorlink" href="#declaring_a_subclass"></a></h3>
<p>Create a <code>Circle</code> subclass, with a new field, two constructors and some methods.
Note that we can use a chain of calls instead of a sequence as above.
Note also that it is possible to call the redefined method with <code>this._super()</code>:</p>
<pre class="prettyprint">var Circle = Shape.subclass()
.fields({radius: 1}) // this could also be written .field('radius', 1)
.constructor(function (x, y, r) {
this._super(x, y); // call to super, i.e. the Shape constructor
this.radius = r;
})
.constructor('withradius', function(r) { // this is a named constructor
this.radius = r; // no need to call super, x and y will be inited to 0
})
.methods({
area: function() { return this.radius * this.radius * 3.14; },
// as with constructors, we can call this._super(...) in a method
});</pre>
<p>Create a circle with the named constructor and play with it:</p>
<pre class="prettyprint">var c = Circle.withradius(12).moveby(10, 15);
print (c.area());</pre>
<p>Add an active field for the diameter.
This uses the Javascript syntax for getter/setter for properties:</p>
<pre class="prettyprint">Circle.fields({
get diameter() {return this.radius * 2},
set diameter(val) {this.radius = val / 2},
});
var c2 = Circle.create();
c2.diameter = 20;
print (c2.radius); // 10</pre>
<p>In addition to these basic object-oriented features, Classy supports various ways
of modifying an existing class: wrapping methods, using mixins, and wrapping fields.</p>
<a name="wrappers_and_mixins"></a><h3>Wrappers and Mixins<a class="anchorlink" href="#wrappers_and_mixins"></a></h3>
<p>First, let's create a <strong>method wrapper</strong>, a function that will be called
instead of an existing method. The special call <code>this._inner()</code>
calls the original method (you can think of it as <code>super</code> working
backwards):</p>
<pre class="prettyprint">function traceWrapper() {
log('calling traced method ')
return this._inner(); // calls the wrapped method
}
// add the wrapper to a couple methods
Shape.wrap('moveby', traceWrapper);
Circle.wrap('area', traceWrapper);
c.area(); // will print 'calling traced method'
// remove the wrapper
Circle.unwrap('area', traceWrapper);</pre>
<p><strong>Mixins</strong> allow us to inject code into an existing class.
Here we define a mixin that calls a redisplay method
when some of the methods of the original class are called:</p>
<pre class="prettyprint">// define a mixin for redisplaying a shape when it is changed
function redisplayWrapper() {
this._inner.apply(this, arguments); // call original function with same arguments
this.redisplay(); // call the mixin method
}
var displayMixin = {
constructor: function() { this.redisplay(); }, // constructor called for each new object
fields: {}, // additional fields (none here)
methods: { // additional methods
redisplay: function() { ... redisplay shape ...}
},
wrappers: { // wrappers for existing methods
moveto: redisplayWrapper,
moveby: redisplayWrapper
}
};
// add the mixin to a class
Shape.mixin(displayMixin);
var c = Circle.create();
c.moveby(10, 12); // calls redisplay</pre>
<p>Finally, we can also create <strong>field wrappers</strong>, i.e. wrap the fields of an existing class to
add side effects when they are read and/or written.
Here we wrap the <code>radius</code> field so that the circle gets redisplayed
when assigning its radius:</p>
<pre class="prettyprint">Shape.wrapFields({
set radius(r) { this._set(r); this.redisplay(); }
// this._set() and this._get(val) invoke the original setter/getter
});</pre>
<p>Wrapped fields can also be specified in a mixin, as described in the user manual below.</p>
<a name="what__39_s_different_between_classy_and_other_oo_frameworks"></a><h2>What's different between Classy and other OO frameworks<a class="anchorlink" href="#what__39_s_different_between_classy_and_other_oo_frameworks"></a></h2>
<a name="declaring_classes"></a><h3>Declaring classes<a class="anchorlink" href="#declaring_classes"></a></h3>
<p>Classes are objects, <em>not</em> constructors in the Javascript sense.
This means that we can't use <code>new MyClass(...)</code> to create an object.
Instead, a class is instantiated using <code>MyClass.create(...)</code>.</p>
<p>While the syntax is a matter of taste, this means that we have a real metaclass (the class's prototype)
and that we can easily add fields, methods, constructors and subclasses to the class using
method chaining: </p>
<pre class="prettyprint">var A = Classy.newClass().fields(...).constructors(...).methods(...);</pre>
<p>These can be called in any order, as many times as you want.
Note however that if you add fields after having created objects, the existing objects
will not get these fields.</p>
<a name="fields"></a><h3>Fields<a class="anchorlink" href="#fields"></a></h3>
<p>A class can declare a set of fields, which are object properties that are automatically
initialized in every new object. This makes constructors easier to write, sometimes even unecessary.
Note that the constructor can still create properties in the usual way (<code>this.a = 1</code>).</p>
<p>If the default value of a field is a function, it is called each time a new object is created.</p>
<p>Fields can also be defined as "active" by specifying a getter and/or setter function.
This supports dynamically computed fields, or fields with side effects.</p>
<p>Note that the default constructor of every class (<code>create</code>) can take a literal object with property values
that are copied to the fields of the new object. These properties are copied as is (no special treatment
of properties whose value is a function or which have setters and/or getters).</p>
<a name="constructors"></a><h3>Constructors<a class="anchorlink" href="#constructors"></a></h3>
<p>You are not stuck with a single constructor and complex parsing of its arguments
when you want different possible sets of parameters for your constructor.</p>
<p>Not only can you overload the default constructor (<code>create</code>) with your own,
you can also create other constructors with different names, each with their own parameters.</p>
<p>Because of the call to <code>_super</code> (see below), constructor chaining is very simple.</p>
<a name="call_to_super"></a><h3>Call to super<a class="anchorlink" href="#call_to_super"></a></h3>
<p>Constructors and methods can call-to-super, i.e. call the constructor or method that they
are overriding in a parent class.</p>
<p>Call-to-super is simple: just use <code>this._super(...)</code> in a constructor or method.</p>
<p>In my opinion, this is cleaner that, e.g., <code>prototype.js</code> use of <code>$super</code> or David Flanagan's
use of <code>arguments.callee</code> in his <code>DefineClass</code> method from his book.</p>
<a name="method_wrappers"></a><h3>Method wrappers<a class="anchorlink" href="#method_wrappers"></a></h3>
<p>Wrappers and mixins make it possible to modify a class in a reversible way.
This goes beyond redefining or adding methods using subclassing:</p>
<p>A <em>method wrapper</em> redefines a method in a class in such a way that the original method can be called
using <code>this._inner(...)</code>. This is similar to a call to super but within a class.</p>
<p>Wrappers can be stacked (a wrapper can be added to a method that already has a wrapper)
and removed dynamically.</p>
<a name="mixins"></a><h3>Mixins<a class="anchorlink" href="#mixins"></a></h3>
<p>A <em>mixin</em> is similar to a class that is 'merged' into an existing class: it can define fields
to be added to (future) objects of the class, new methods and wrappers, and a default constructor.
(One current limitation is that changes to a mixin after it is added to a class do not affect the class.)</p>
<p>Mixins provide some of the effects of multiple inheritance without many of the drawbacks.
Note that fields added by a mixin override fields with the same name in the orignal class, if they exist.
This can be useful, for example to change the default value, but it can also be dangerous
if the collision was unexpected.</p>
<a name="field_wrappers"></a><h3>Field wrappers<a class="anchorlink" href="#field_wrappers"></a></h3>
<p>A field of an existing object or class can be wrapped with a getter and/or setter.
This makes it possible to add side effects to every access (get or set) to a field.</p>
<p>A given field can be wrapped only once per class or per mixin, but if several mixins
wrap the same field, the wrappers will be nested.</p>
<p>Field wrappers can be added on a per-object basis, with the methods <code>wrapField</code>, <code>wrapFields</code>,
<code>unwrapField</code> and <code>unwrapFields</code>, defined for every object. </p>
<p>They can also be specified at the class-level, in the <code>fieldWrappers</code> property of a mixin.</p>
<p>Note that <code>o.unwrapField(s)</code> is the only way to remove field wrappers from an existing object.
If a mixin that has wrapped fields is removed from a class, the existing objects are unaffected.</p>
<a name="disclaimer"></a><h3>Disclaimer<a class="anchorlink" href="#disclaimer"></a></h3>
<p>I am not claiming this is a better framework than what's already out there.
It's just different, with a different set of tradeoffs. And it was a good exercise
for playing (and sometimes fighting with) Javascript's object model!</p>
<p>Comments and suggestions welcome!</p>
<a name="user_manual"></a><h2>User Manual<a class="anchorlink" href="#user_manual"></a></h2>
<p>There are two versions of the package, <code>oo.js</code> and <code>classy.js</code>:</p>
<ul class="list">
<li><code>oo.js</code> implements the basic class-based model, but not the mixins and wrappers;</li>
<li><code>classy.js</code> implements the complete model, including mixins and wrappers.</li>
</ul>
<p>The scripts are self-contained and can be used both in a browser, using a <script> tag:</p>
<pre class="prettyprint"><script src="oo.js"></script><!-- defines global variable OO -->
<script src="classy.js"></script><!-- defines global variable Classy --></pre>
<p>or with node.js, using require:</p>
<pre class="prettyprint">var OO = require('./oo');
var Classy = require('./classy');</pre>
<p>In the rest of this documentation, we use <code>Classy</code> as the global name exported by the package.</p>
<p>The script exports three objects:</p>
<ul class="list">
<li><code>Classy.newClass</code>, the function to create new classes. This is all you need to use Classy;</li>
<li><code>Classy.Metaclass</code>, the metaclass object, if you want to add new methods to it;</li>
<li><code>Classy.object</code>, the constructor of all Classy objects: <code>o instanceof Classy.object</code> is true for all Classy objects.</li>
</ul>
<a name="defining_classes"></a><h3>Defining classes<a class="anchorlink" href="#defining_classes"></a></h3>
<p>To create a class, use the exported <code>newClass</code> function. It is customary to also give a name to the class, which is used by the default <code>toString</code> function of Classy objects:</p>
<pre class="prettyprint">var A = Classy.newClass().name('A');
var a = A.create(); // create an instance of A
console.log(a.toString()); // "instance of class A"</pre>
<p>To create a subclass of an existing class, use the <code>subclass</code> method of the parent class:</p>
<pre class="prettyprint">var B = A.subclass().name('B')</pre>
<a name="fields"></a><h4>Fields<a class="anchorlink" href="#fields"></a></h4>
<p>To define fields, use the <code>field</code>, <code>activeField</code> and <code>fields</code> methods on the class object:</p>
<pre class="prettyprint">A.field('x', 0) // field name and default value
A.activeField('', <getter>, <setter>) // active field, <getter> and <setter> are functions (or null)
A.fields({ // define multiple fields at once
radius: 0,
position: [0, 0], // a copy of the array is created for each new object
color: {r: 100, g: 20, b: 30}, // a copy of the literal is created for every new object
created: function() { return new Date(); } // the function is called for every new object
// an active field
get diameter: function() { return this.radius * 2;}
set diameter: function(d) { this.radius = d/2;}
})</pre>
<p>Note that initial values are copied for each new object. This is usually what is wanted, so that each
object gets a fresh copy of the initial values.
If however you want to share the object specified as initial value instead of copying it, you can either:</p>
<ul class="list">
<li>Use as initializer a function that returns the shared object</li>
<li>Pass an object with the property <code>__immutable</code> set to true</li>
</ul>
<p>Here is an example:</p>
<pre class="prettyprint">// first solution: use a function returning the object
A.field('color', function() { return {r: 100, g: 0, b: 0}; });
// alternative:
function shared(v) { return function(v) { return v}};
A.field('color', shared({r: 100, g: 0, b: 0}));
// second solution: use __immutable
A.field('color', {r: 0, g: 0, b: 100, __immutable: true });
// alternative:
function immutable(v) { v._immutable = true; return v}
A.field('color', immutable({r: 100, g: 0, b: 0}));</pre>
<p>In the full version of Classy, fields can be wrapped with or without the use of mixins.
See the description of field wrappers and mixins below.</p>
<a name="constructors_and_object_instantiation"></a><h4>Constructors and object instantiation<a class="anchorlink" href="#constructors_and_object_instantiation"></a></h4>
<p>Constructors are used to create new objects. The body of the constructor
is called after the object has been created and the properties corresponding to
the fields declared in the class have been initialized.</p>
<p>Each class has a predefined constructor called <code>create</code>.
This constructor can be redefined, and other constructors (with different names)
can be defined. The class methods to define constructors are as follows:</p>
<pre class="prettyprint">A.constructor(<function>) // define the default constructor
A.constructor('name', <function>) // define a named constructor
A.constructors({ // define multiple constructors at once
create: <function>, // default constructor
createWithSize: <function>, // named constructor
})</pre>
<p>As with all class methods, these can be chained.</p>
<p>The expression <code>A.create()</code> creates a new object of class <code>A</code>, initializes it,
and calls the default constructor, if defined. The default constructor takes
an optional argument, which is a literal object used to intialize the fields
of the object. Only the properties of this literal objects that are also declared
as fields of the class being instantiated or one of its parent classes are initialized.
So for example :</p>
<pre class="prettyprint">var A = Classy.newClass().field('x', 0);
var a = A.create({x: 0, y: 0});
a.y; // undefined</pre>
<p>In the following example, <code>createWithSize</code> is a constructor that takes a size.
The expression <code>A.createWithSize(20)</code> creates a new object of class <code>A</code>, initializes it,
and calls the constructor <code>createWithSize</code>.</p>
<pre class="prettyprint">var A = Classy.newClass().name('A')
.fields({width: 0, height:0})
.constructors({ createWithSize: function(size) { this.width = size; this.height = size; } })
.methods({ area: function() { return this.width*this.height; } });
A.createWithSize(20).area(); // 400</pre>
<p>For subclasses, constructors can call the constructor of the parent class using
the <em>call-to-super</em> described below. Note that if the <code>create</code> default constructor
is not defined in the subclass, but is defined in the parent class, the parent's
constructor is <em>not</em> called.</p>
<pre class="prettyprint">var A = Classy.newClass().name('A')
.constructor( function() { console.log('inited and A') });
var B = A.subclass().name('B')
.constructor( function() { this._super(); console.log('inited and B') });
var b = B.create(); // prints "inited an A" then "inited a B"</pre>
<a name="methods"></a><h4>Methods<a class="anchorlink" href="#methods"></a></h4>
<p>Methods applicable to instances of a class are defined in a way similar to constructors:</p>
<pre class="prettyprint">A.method('m', <function>) // define a single method
A.methods({ // define multiple methods at once
m1: <function>,
m2: <function>,
})</pre>
<p>These calls can be chained, like those defining fields and constructors.</p>
<p>Methods are called as usual in Javascript:</p>
<pre class="prettyprint">var a = A.create();
a.m();
a.m1(...);</pre>
<p>Methods in a subclass can call the corresponding method of the parent class using
the <em>call-to-super</em> described below.</p>
<p>In the full version of Classy, methods can be wrapped with or without the use of mixins.
See the description of method wrappers and mixins below.</p>
<a name="calls_to_super"></a><h4>Calls to super<a class="anchorlink" href="#calls_to_super"></a></h4>
<p>When class <code>B</code> inherits from class <code>A</code>, constructors and methods in <code>B</code> can
call the corresponding constructor or method in class <code>A</code> using the expression</p>
<pre class="prettyprint">this._super(<arguments>)</pre>
<p>This is particularly useful in constructors in order to chain them together.
Note that this works <em>only</em> when the call to <code>_super</code> is in the body of the
constructor or method itself. So for example, the following will <em>not</em> work:</p>
<pre class="prettyprint">function doesNotWork(o) { return o._super();}
var A = Classy.newClass()
.method('m', function() { return 'Hello'; });
var B = A.subclass()
.method('m', function() { return doesNotWork(this); });</pre>
<p><em>A note about performance: the implementation of <code>_super</code> requires that the
constructors and methods that call to super be wrapped in a function.
This has a slight performance impact since a call to <code>o.m()</code> is not a direct
call anymore, but a call to a function that calls the method <code>m</code>. In practice,
the overhead is negligible.</em></p>
<p>It is sometimes necessary to call a method of the parent class that is <em>not</em>
of the same name. For example, a named constructor wants to call the default
constructor of the parent class. The best way to achieve this is to use the
methods <code>getConstructor</code> and <code>getMethod</code> of the parent class. These methods
return the function implementing the constructor or method (if it exists):</p>
<pre class="prettyprint">var A = Classy.newClass()
.constructor('create', function() {...});
var B = A.subclass()
.constructor('createWithSize', function(s) {
...
A.getConstructor('create').call(this, ...);
// alternative:
// this.class().superclass().getConstructor().call(this, ...)
})</pre>
<a name="instance_methods"></a><h4>Instance methods<a class="anchorlink" href="#instance_methods"></a></h4>
<p>In addition to the standard Javascript methods of Object,
all Classy objects have the following methods:</p>
<pre class="prettyprint">o.toString() // return a simple string "instance of <class name>"
o.className() // return the name of the class of object o
o.classs() // return the class object used to create o
o.set(...) // set values of object fields - see below
o.get(...) // get values of object fields - see below</pre>
<p>Note that <code>toString</code> and <code>className</code> are really useful only when the class has been given a name with, e.g., <code>A.name('A')</code>. </p>
<p>Note also that the method <code>classs</code> has 3 's's. This is not an error and is due to the fact that the word <code>class</code> is reserved in Javascript. If an object is created with <code>o = A.create()</code>, then <code>o.classs() === A</code>.</p>
<p><code>set</code> and <code>get</code> allow you to set and get the values of one or more fields at once. Of course, fields can also be accessed with the usual Javascript dot notation, i.e. <code>o.x</code> and <code>o['x']</code>.</p>
<p>Setting fields with <code>o.set</code> can use a list of field names and values, an array of field names plus an array of field values, a literal object, or another Classy object. Whichever syntax you use, only fields that are declared in the object's class or one of its parent classes are assigned. Here are the four version of <code>set</code>:</p>
<pre class="prettyprint">o.set('field1', value1, 'field2', value2, ...)
o.set(['field1', 'field2', ...], [value1, value2, ...])
o.set({ field1: value, field2: value2, ...})
o.set(o2)</pre>
<p>Getting field values with <code>get</code> follows the same pattern, with the addition of a call without arguments, which simply retrieves all defined fields of the object, and a call with a single field name, which simply returns its value:</p>
<pre class="prettyprint">o.get() // return all the fields and their values in a literal object
o.get('field') // return the value of the field
o.get('field1', 'field2', ...) // return an array 'field1', value, 'field2', value, ...
o.get(['field1', 'field2', ...]) // same as above
o.get({ field1: v1, field2: v2, ...}) // return a literal object
o.get(o2) // return a literal object with the fields of o2 present in o</pre>
<p>Note that in the last two cases (using a literal object or Classy object), the existing values are ignored and the resulting object only contains the fields that are defined in the receiving object's class. By constrast, the calls that return an array include all the fields listed in the arguments, with a value of <code>undefined</code> for those fields that are not defined in the receiving object's class.</p>
<p><code>set</code> and <code>get</code> are designed to play nice together. For example, one can write:</p>
<pre class="prettyprint">o2.set(o1.get('x', 'y', 'z')); // copy the values of o1.x, o1.y, o1.z to o2
var rgb = ['r', 'g', 'b'];
o2.set(o1.get(rgb)); // copy the values of o1.r, o1.g, o1.b to o2</pre>
<p>While these calls are more expensive that directly accessing the object's properties, they provide the added security of only setting and getting fields that are declared in the object's classes. This makes it possible to treat other properties of objects as private:</p>
<pre class="prettyprint">var A = Classy.newClass().field('x', 0).constructor(function() { this.hidden = 1; });
var a = A.create(), b = B.create();
a.x = 10; a.hidden = 2;
b.set(a); // b.x is 10, but b.hidden is still 1</pre>
<p>One last point about instance methods: the version of Classy that supports mixins defines additional instance methods (<code>wrapField</code>, <code>unwrapField</code> and <code>unwrapFields</code>), described below in the section on Field wrappers.</p>
<a name="class_fields_and_class_methods"></a><h4>Class fields and Class methods<a class="anchorlink" href="#class_fields_and_class_methods"></a></h4>
<p>Since a Classy class is an object, it can have its own fields and methods.
These are called <em>class fields</em> and <em>class methods</em> (other languages sometimes call them
static fields or methods). They are declared in a similar way as instance fields and methods:</p>
<pre class="prettyprint">classField('name', value)
classFields({ f1: v1, f2: v2, ...})
classMethod('name', <function>)
classMethods({ m1: <function>, m2: <function>, ...})</pre>
<p>Here is an example of use, to assign unique ids to instances of a class:</p>
<pre class="prettyprint">var A = Classy.newClass()
.field('id', 0)
.classField('count', 0)
.classMethod('nextId', function() { return ++this.count; })
.constructor(function() { this.id = A.nextId(); })</pre>
<a name="chaining_calls"></a><h4>Chaining calls<a class="anchorlink" href="#chaining_calls"></a></h4>
<p>As shown in many of the examples above, the methods that define a class return the class itself,
so they can be chained.</p>
<p>Here is a typical example of a class definition:</p>
<pre class="prettyprint">var A = Classy.newClass().name('A')
.fields({
x: 0,
y: 0,
...
})
.classField('count', 0)
.constructors({
createAt: function(x, y) { this.x = x; this.y = y; },
...
})
.methods({
moveBy: function(dx, dy) { this.x += dx; this.y += dy; },
...
});</pre>
<p>Note that every call to these methods <em>add</em> to the class definition.
In case of a redefinition of a field or method, the old definition is simply replaced.</p>
<p>When fields are added to a class <em>after</em> some objects of that class have been created,
the changes do not affect these objects.</p>
<a name="method_wrappers"></a><h3>Method wrappers<a class="anchorlink" href="#method_wrappers"></a></h3>
<p>Wrapping a method consists of replacing it with a new function that can (and usually does)
call the original one. The wrapped method can be restored to its original value (unwrapping).
A method can be wrapped multiple times, in which case the wrappers stack and can call each other
from the most recent one down to the original method.</p>
<p>In this example, a wrapper is added to method <code>m</code> of class <code>A</code> to trace it:</p>
<pre class="prettyprint">var A = Classy.newClass();
A.method('m', function() { return 'Hello'; });
A.wrap('m', function() {
console.log('tracing A.m');
return this._inner();
})</pre>
<p>The expression <code>this._inner()</code> calls the original function, in a similar spirit as <code>_super</code>.
It is called "inner" because the original method is "inside" the wrapper.</p>
<p>The following methods can be used with a class to managed method wrappers:</p>
<pre class="prettyprint">A.wrap('m', <function>) // define a method wrapper
A.wrappers({ // define multipled wrappers at once
'm': <function>,
...
})
A.wrapped('m') // true if method m is wrapped in class A
A.wrapped('m', fun) // true if method m is wrapped by function fun in class A
A.unwrap('m') // unwrap the topmost wrapper of method m in class A (if any)
A.unwrap('m', fun) // unwrap the wrapper of method m in class A defined by fun
A.unwrapAll() // unwrap all wrappers of method m in class A</pre>
<p>If a method has multiple wrappers, it is safer to remove them using <code>A.unwrap('m', fun)</code>
so that you don't remove a wrapper set by someone else. For this to work, you need to keep
the wrapper function around. Here is an example</p>
<pre class="prettyprint">var mTracer = function() {
console.log('calling m of '+this.name());
return this._inner();
})
A.wrap('m', mTracer);
...
A.unwrap('m', mTracer);</pre>
<a name="field_wrappers"></a><h3>Field wrappers<a class="anchorlink" href="#field_wrappers"></a></h3>
<p>Wrapping a field consists of wrapping the methods used to set and get the field,
i.e. turning the field into an active field.</p>
<p>Unlike method wrappers which can only be set on classes, field wrappers can be set
on individual objects or at the class level.</p>
<a name="wrapping_fields_at_the_object_level"></a><h4>Wrapping fields at the object level<a class="anchorlink" href="#wrapping_fields_at_the_object_level"></a></h4>
<p>To wrap a field of an object <code>o</code>, use the following:</p>
<pre class="prettyprint">o.wrapField(field, <getter>, <setter>, /*opt*/ owner)</pre>
<p>Where <code>field</code> is the name of the field, <code>getter</code> and <code>setter</code> are the functions to get and set the value,
and <code>owner</code> is an arbitrary object (defaults to the object itself) used to identify the wrapper when unwrapping the field (see below).</p>
<p>The getter and setter functions have the same profiles as for active fields.
In addition, they can call <code>this._get()</code> and <code>this._set(val)</code> to access the original field.
(Note that using <code>this.f</code> in a field wrapper of the field <code>f</code> will cause an infinite recursion).</p>
<p>Here is an example, again for tracing access to a field:</p>
<pre class="prettyprint">o.wrapField('x',
function() { console.log('getting x'); return this._get(); },
function(val) { console.log('setting x'); this._set(val); },
);</pre>
<p>Note that the getter or the setter can be <code>null</code>, in which case the original definition stands.</p>
<p>As for methods wrappers, field wrappers can be nested: a field can be wrapped multiple times.
A wrapped field can be unwrapped as follows (<code>owner</code> defaults to the object <code>o</code>):</p>
<pre class="prettyprint">o.unwrapField(field, /*opt*/ owner) // remove the wrapper of field f owned by owner
o.unwrapFields(/*opt*/ owner) // remove all wrappers of all fields of o owned by owner</pre>
<a name="wrapping_fields_at_the_class_level"></a><h4>Wrapping fields at the class level<a class="anchorlink" href="#wrapping_fields_at_the_class_level"></a></h4>
<p>Wrapping fields at the class level means that all <em>future</em> instances of that class
will feature the wrapped fields. The declarations are very similar to those used above,
except that they are applied to the class, not the object, and there is no notion of owner:</p>
<pre class="prettyprint">A.wrapField(field, <getter>, <setter>)
A.unwrapField(field) // unwrap field
A.wrappedField(field) // true if field is wrapped</pre>
<p>It is also possible to specify the wrappers using active fields, as follows:</p>
<pre class="prettyprint">A.wrapFields({
get f1: <getter>,
set f1: <setter>,
...
})</pre>
<p>Like object field wrappers, it is possible to defined multiple class field wrappers for a single field.</p>
<a name="mixins"></a><h3>Mixins<a class="anchorlink" href="#mixins"></a></h3>
<p>A mixin is a set of capabilities added to an existing class to modify its behavior.
It differs from inheritance in that it does not change the identity of the class.
It provides a flexible alternative to multiple inheritance in languages (like Javascript)
that do not support it.</p>
<p>In Classy, a mixin is defined by a literal object with the following (optional) properties:</p>
<pre class="prettyprint">var mixin = { // define a mixin
constructor: function() {...},
fields: { f1: v1, ...},
methods: { m: <function>, ...},
wrappers: { m1: <function>, ...}
}</pre>
<p><code>constructor</code> is called for every object created by a class to which this mixin has been added.
There can be only one constructor and it cannot take arguments. It is called after all other initializations of the object have been performed: after initializing the fields defined in its class and those defined in the mixins (see below), and after calling the class constructor.</p>
<p><code>fields</code> is a set of fields to be added to the target class.
If the field is already defined in the target class, the mixin field will replace it.
However, if the mixin field is defined as an active field and there is a field with the same name
in the target class, then the mixin field will wrap the original field. For example, the following
mixin is designed to wrap the field <code>x</code> of the original class:</p>
<pre class="prettyprint">var mixin = {
fields: {
get x: function() { console.log('getting x'); return this._get(); },
set x: function(val) { console.log('setting x'); this._set(val); },
}
}</pre>
<p><code>methods</code> is a set of methods to be added to the target class.
Methods that are already defined in the target class are silently ignored.
To redefine such methods, use <code>wrappers</code> instead (see below).</p>
<p><code>wrappers</code> is a set of method wrappers. In the example below, the <code>setPosition</code> method is wrapped
to trigger a <code>redisplay</code>, which itself is defined as a method of the mixin:</p>
<pre class="prettyprint">var mixin = {
methods: { redisplay: function() {...} },
wrappers: { setPosition: function(x, y) { this._inner(x, y); this.redisplay(); } }
}</pre>
<p>Mixins can be added to and removed from a class as follows:</p>
<pre class="prettyprint">A.mixin(mixin) // add mixin to A
A.unmix(mixin) // remove mixin from A
A.hasMixin(mixin) // true if mixin has been added to A</pre>
<a name="introspection"></a><h3>Introspection<a class="anchorlink" href="#introspection"></a></h3>
<p>All classes created with Classy have a single metaclass, called <code>Metaclass</code> and exported by the module.
This object holds all the methods applicable to a class, most of which have been introduced before: the <code>create</code> default constructor and the methods to define constructors, fields, methods and mixins.</p>
<p>The <code>Metaclass</code> object also features a set of methods that are useful for introspection of the classes.</p>
<p>General class information :</p>
<pre class="prettyprint">A.superclass() // return parent class (Object for root classes)
A.name('...') // assign a name to the class
A.className() // return the class's name
A.toString() // return 'class <className>'</pre>
<p>Information about fields:</p>
<pre class="prettyprint">A.hasOwnField('x') // true if x is a field of A
A.listOwnFields() // return the list of fields of A
A.hasField('x') // true if x is a field of A or one of its parent classes
A.listAllFields() // return the list of fields of A and its parent classes
A.listFields(spec) // return the list of fields of A and its parent classes matching the specification</pre>
<p>Information about constructors defined in the class itself:</p>
<pre class="prettyprint">A.hasOwnConstructor(name) // true if 'name' is a constructor of class A
A.getOwnConstructor(name) // return the constructor 'name' if defined in class A
A.listOwnConstructors() // return the list of constructors defined in class A</pre>
<p>Information about constructors defined in the class itself or one of its parent classes:</p>
<pre class="prettyprint">A.hasConstructor(name) // true if 'name' is a constructor available to class A
A.getConstructor(name) // return the constructor 'name' if it is available to class A
A.listConstructors() // return the list of constructors available to class A</pre>
<p>Information about methods defined in the class itself:</p>
<pre class="prettyprint">A.hasOwnMethod(name) // true if 'name' is a method of class A
A.getOwnMethod(name) // return the method 'name' if defined in class A
A.listOwnMethods() // return the list of methods defined in class A</pre>
<p>Information about methods defined in the class itself or one of its parent classes:</p>
<pre class="prettyprint">A.hasMethod(name) // true if 'name' is a method available to class A
A.getMethod(name) // return the method 'name' if it is available to class A
A.listAllMethods() // return the list of methods available to class A
A.listMethods(spec) // return the list of fields of A and its parent classes matching the specification</pre>
</div>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script type="text/javascript" src="assets/js/bootstrap.min.js"></script>
<script type="text/javascript" src="assets/js/prettify.js"></script>
</body>
</html>