/*
**Parasitic Inheritance**
*/
/*
The following examples illustrate the common design pattern for `OxJS` UI
widgets: an inheritance model that is neither classical nor prototypal, but
"parasitic" (a term coined by Douglas
Crockford).
In a nutshell, "instances" are created by augmenting other
instances, but in addition to private members (`var foo`) and public members
(`that.bar`), they can have shared private members (`self.baz`). `self` cannot
be accessed from outside, but since `self` itself is an argument of the
"constructor", an instance can inherit its parent's `self` by passing its own
`self`.
*/
'use strict';
/*
Create our own namespace. Not required, but if we wanted to create a module
named `My`, this is how we would do it.
*/
Ox.My = {};
/*
**Box**
*/
/*
First, lets build the most basic Box widget. A widget is a "constructor"
function that takes two (optional) arguments, `options` and `self`, and returns
a widget object. It's not a constructor in JavaScript terms though: It doesn't
have to be called with `new`, and doesn't return an `instanceof` anything. It
just enhances another widget object and returns it.
*/
Ox.My.Box = function(options, self) {
/*
This is how every widget "constructor" begins. `self` is the widget's shared
private object.
*/
self = self || {};
/*
`that` is the widget itself, its public object, or, in JavaScript terms, its
`this`. Every widget "inherits" from another widget by simple assignment.
All public properties of the "super" widget, i.e. all properties of its
`that`, will be present on our own `that`. In this case, we use Ox.Element,
the "root" widget at the end of the inheritance chain, and pass an empty
options object. But we always pass our own `self`, which means that any
property that Ox.Element (or any other widget in the inheritance chain) adds
to `self` will be present on our own `self`.
*/
var that = Ox.Element({}, self)
/*
Then we call the public `defaults`, `options` and `update` methods of
Ox.Element. `defaults` assigns the defaults object to `self.defaults`
and copies it to `self.options`, `options` extends `self.options` with
the options object, and `update` adds one or more callbacks that are
invoked whenever, by way of calling the `options` method, a property of
`self.options` is modified or added.
*/
.defaults({
color: [128, 128, 128],
size: [128, 128]
})
.options(options || {})
.update({
color: setColor,
size: setSize
})
/*
`addClass` is a jQuery method. In fact, Ox.Element (and any widget
derived from it) provides, on its prototype, all methods of a jQuery
`$('
var that = Ox.My.Box({}, self) .defaults({ color: [128, 128, 128], inverted: false, size: [128, 128] }) .options(options || ()) .update({ ... })— but why repeat the defaults of `Ox.My.Box` if we can simply extend them. (Just like `options()` returns all options of a widget, `defaults()` returns all its defaults.) */ var that = Ox.My.Box({}, self); that.defaults(Ox.extend(that.defaults(), { inverted: false })) .options(options || {}) /* Again, we add handlers that run when the widget's options are updated. The original handlers of `Ox.My.Box` will run next, so we just add the ones we need. We leave out `size`, so when the `size` option changes, we'll get the original behavior. */ .update({ color: setColor, inverted: setColor }) /* The same as `.css({cursor: 'pointer'})`. */ .addClass('OxMyInvertibleBox') /* Ox.Element and its descendants provide a number of public methods (`bindEvent`, `bindEventOnce`, `triggerEvent` and `unbindEvent`) that allow widgets to communicate via custom events. Here, we add a handler for Ox.Element's `doubleclick` event. If we just wanted to handle a `click` event, we could also use jQuery here:
.on({ click: function() { that.invert(); } })*/ .bindEvent({ doubleclick: function() { that.invert(); } }); /* The idea is that our widget's inverted state is separate from its color. If the inverted option is set, then the color option stays the same, but has the inverse effect. This means that when initializing the widget, we have to call our custom `setColor` method if `self.options.inverted` is `true`. */ self.options.inverted && setColor(); /* When `setColor` is invoked as an update handler, returning `false` signals that no further handlers should run. Otherwise, the original handler of `Ox.My.Box` would run next, and revert any inversion we might have done here. */ function setColor() { that.css({backgroundColor: 'rgb(' + ( self.options.inverted ? self.options.color.map(function(value) { return 255 - value; }) : self.options.color ).join(', ') + ')'}); return false; } /* The public `invert` method is added as a convenience for the users of our widget, so that when they want to toggle its inverted state, they don't have to write
$widget.options({ inverted: !$widget.options('inverted') });all the time. Also, we trigger an `invert` event, that anyone can bind to via
$widget.bindEvent({ invert: function() { ... } });*/ that.invert = function() { that.options({inverted: !self.options.inverted}).triggerEvent('invert'); return that; }; /* And again, we return `that`. */ return that; }; /* **MetaBox** */ /* Now it's time for something more funky: A MetaBox — that is, a box of boxes. */ Ox.My.MetaBox = function(options, self) { /* This time, we inherit from `Ox.My.InvertibleBox`. The one thing that's different though is the `color` option: It is no longer a single value, but an array of array of values. That's how the boxes inside our MetaBox are specified. The following would create a grid of boxes with two rows and three columns:
Ox.My.MetaBox({ color: [ [[64, 0, 0], [64, 64, 0], [0, 64, 0]], [[0, 64, 64], [0, 0, 64], [64, 0, 64]] ] });*/ self = self || {}; var that = Ox.My.InvertibleBox({}, self) .options(options || {}) .update({color: setColor}); /* But we keep the default color of `Ox.My.InvertibleBox` (`[128, 128, 128]`) as our own default color, and only here check if the color option is a single RGB value. In that case, we convert it to an array of one row and one column. This way, whenever someone accidentally passes a single color value, our MetaBox can handle it. */ if (Ox.isNumber(self.options.color[0])) { self.options.color = [[self.options.color]]; } /* `self.sizes` holds the width of each column and the height of each row. `self.options.color.length` is the number of rows, `self.options.color[0].length` the number of columns, and Ox.splitInt(a, b) "splits" an integer `a` into an array of `b` integers that sum up to `a`. (We don't want fractional pixel sizes.) */ self.sizes = [ Ox.splitInt(self.options.size[0], self.options.color[0].length), Ox.splitInt(self.options.size[1], self.options.color.length) ]; /* `self.$boxes` are the actual boxes. We use `Ox.My.InvertibleBox`, but remove the `doubleclick` handlers, since our MetaBox already has one, being an InvertibleBox itself. (`unbindEvent(event)` removes all handlers, `unbindEvent(event, handler)` removes a specific one.) Then we simply append each box to the meta-box. */ self.$boxes = self.options.color.map(function(array, y) { return array.map(function(color, x) { return Ox.My.InvertibleBox({ color: color, size: [self.sizes[0][x], self.sizes[1][y]] }) .unbindEvent('doubleclick') .appendTo(that); }); }); /* To set the color of a meta-box means to set the color of each box. */ function setColor() { self.$boxes.forEach(function(array, y) { array.forEach(function($box, x) { $box.options({color: self.options.color[y][x]}); }); }); } /* This is the rare case of a shared private method. Its purpose will become apparent a bit later. Otherwise, we could just have made a private function, or an anonymous function in the loop below. */ self.invertBox = function($box) { $box.invert(); }; /* Here, we override the public `invert` method of `Ox.My.InvertibleBox`. When inverting an `Ox.My.MetaBox`, we have to invert each of its boxes. (If we wanted to keep the original method around, we could store it as `that.superInvert` before.) */ that.invert = function() { self.$boxes.forEach(function(array) { array.forEach(self.invertBox); }); that.options({inverted: !self.options.inverted}).triggerEvent('invert'); return that; }; /* And that's all it takes to make a meta-box. */ return that; }; /* **PixelBox** */ /* The next widget is a peculiar type of meta-box. A PixelBox has only one color, but this color will be split up into a red box, a green box and a blue box. */ Ox.My.PixelBox = function(options, self) { self = self || {}; /* The challenge here is that we want our PixelBox to be an instance of `Ox.My.MetaBox`, but with a `color` option like `Ox.My.Box`. So we have to parse the options ourselves, by first extending the defaults of `Ox.My.Box` and then transforming the single-value `color` option into a multi-value `color` option. Calling