diff --git a/examples/ui/widget_design_patterns/js/example.js b/examples/ui/widget_design_patterns/js/example.js index d8ab3c80..884f0d8a 100644 --- a/examples/ui/widget_design_patterns/js/example.js +++ b/examples/ui/widget_design_patterns/js/example.js @@ -1,7 +1,11 @@ /* -The following examples explain the common design pattern for OxJS UI widgets: an -inheritance model that is neither classical nor prototypal, but "parasitical" (a -term coined by +**Parasitical Inheritance** +*/ +/* +The following examples illustrate the common design pattern for `OxJS` UI +widgets: an inheritance model that is neither classical nor prototypal, but +"parasitical" (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 @@ -14,13 +18,15 @@ be accessed from outside, but since `self` itself is an argument of the /* -Create our own namespace. Not required, but if you wanted to create a -module named `My`, this is how you would do it. +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 = {}; /* -
@@ -293,7 +303,7 @@ Ox.My.MetaBox = function(options, self) {
/*
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 value. In that case, we convert it to an array of one row and one
+ 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.
*/
@@ -314,10 +324,11 @@ Ox.My.MetaBox = function(options, self) {
];
/*
- `self.$boxes` are the actual boxes. We use `Ox.My.InvertibleBox`, but
- remove their `doubleclick` handlers, since our meta-box already has one.
- (`unbindEvent(event)` removes all handlers, `unbindEvent(event, handler)`
- removes a specific one.) Then we simply append each box to the meta-box.
+ `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) {
@@ -372,29 +383,42 @@ Ox.My.MetaBox = function(options, self) {
};
/*
-
+**PixelBox**
+*/
+/*
The next widget is a peculiar type of meta-box. A PixelBox has only one color,
-but this color will be split into a red box, a green box and a blue box.
+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 || {};
- /*
- ...
- */
- self.options = Ox.extend(Ox.My.Box().defaults(), options || {});
- var that = Ox.My.MetaBox(Ox.extend(self.options, {
- color: getColor()
- }), self)
- .update({
- color: setColor
- });
/*
- This is how the color gets split up into a red box, a green box and a blue
- box.
+ 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
+ `Ox.My.PixelBox().options('color')`
+ will now return
+ `[[[128, 0, 0], [0, 128, 0], [0, 0, 128]]]`.
*/
- function getColor() {
+ self.options = Ox.extend(Ox.My.Box().defaults(), options || {});
+ self.options.color = getColor();
+ /*
+ Now we can pass `self.options` to `Ox.My.MetaBox`.
+ */
+ var that = Ox.My.MetaBox(self.options, self)
+ /*
+ Again, we add a custom handler for `color` updates.
+ */
+ .update({color: setColor});
+
+ /*
+ This is how a single RGB value gets split up into a red box, a green box and
+ a blue box.
+ */
+ function getColor(color) {
return [[
[self.options.color[0], 0, 0],
[0, self.options.color[1], 0],
@@ -403,7 +427,11 @@ Ox.My.PixelBox = function(options, self) {
}
/*
- ...
+ When the `color` option gets updated to a new single value, we update it
+ again, this time to multiple values, and return `false` to keep the MetaBox
+ handler from running. We have updated `color`, so our handler will get
+ called again, but now it does nothing, and the MetaBox handler will get
+ invoked.
*/
function setColor() {
if (Ox.isNumber(self.options.color[0])) {
@@ -429,12 +457,17 @@ Ox.My.PixelBox = function(options, self) {
});
};
+ /*
+ And that's the PixelBox.
+ */
return that;
};
/*
-
+**ImageBox**
+*/
+/*
And finally — a meta-meta-box! An ImageBox takes an image and, for each
pixel, displays a PixelBox.
*/
@@ -447,6 +480,12 @@ Ox.My.ImageBox = function(options, self) {
its `displayText` method.
*/
var that = Ox.My.Box({}, self).displayText('Loading...');
+ /*
+ It's not required to define empty defaults — omitting them would
+ simply leave them undefined). Still, to add an explicit `null` default is a
+ good practice, as it makes it obvious to any reader of our code that
+ `Ox.My.ImageBox` expects an `image` option.
+ */
that.defaults(Ox.extend(that.defaults(), {
image: null
}))
@@ -500,29 +539,40 @@ Ox.My.ImageBox = function(options, self) {
We've inherited from `Ox.My.Box`, so we don't have an `invert` method yet.
This is how we can borrow the one from `Ox.My.MetaBox`. We're passing our
own `self`, so the `self.$boxes` that the `invert` method of `Ox.My.MetaBox`
- operates on will be the PixelBoxes that we are assinging in the asynchronous
- callback above.
+ operates on will be the PixelBoxes that we are assigning in the asynchronous
+ callback above. (This pattern is somewhat analogous to the
+ `someOtherObject.method.apply(this, args)` idiom that is common in
+ JavaScript.)
- This is somewhat analogous to the
-
- someOtherObject.method.apply(this, args)
-
- pattern that is common in JavaScript.
+ Note that we have to pass `self.options` too, otherwise our own
+ `self.options.size` would get overwritten by the MetaBox default size.
- Note that we have to pass `self.options` too, otherwise our own size would
- get overwritten by the MetaBox default size.
+ Passing `self` to `Ox.My.MetaBox` has another nice effect: the MetaBox will
+ add its own event handlers to it. So even though `Ox.My.ImageBox` is just an
+ `Ox.My.Box`, it has now inherited `doubleclick` handling from
+ `Ox.My.MetaBox`.
+
+ (Internally, `Ox.Element` stores event handlers in `self`. So what happens
+ is this: `self` gets passed all the way down from `Ox.My.ImageBox` to
+ `Ox.My.MetaBox` to `Ox.My.InvertibleBox` to `Ox.My.Box` to Ox.Element, and
+ when `Ox.My.InvertibleBox` defines its `doubleclick` handler, it ends up on
+ `self`. So when the Ox.Element that is actually in the DOM — the
+ `Ox.My.Box` that `Ox.My.ImageBox` inherits from, which shares the same
+ `self` — receives a `doubleclick`, there is now a handler for it.)
*/
that.invert = Ox.My.MetaBox(self.options, self).invert;
/*
- But wait — where's our `doubleclick` handler?
+ And that's it.
*/
return that;
};
/*
-
+**VideoBox**
+*/
+/*
This one is left as an exercise to the reader ;)
*/
Ox.My.VideoBox = function(options, self) {
@@ -530,13 +580,26 @@ Ox.My.VideoBox = function(options, self) {
};
/*
-
-Load the UI and Image modules.
+**Demo**
+*/
+/*
+Load the Image and UI modules.
*/
Ox.load(['Image', 'UI'], function() {
+ /*
+ Pick a random color. Ox.rgb will convert HSL to RGB.
+ */
var h = Ox.random(360), s = 1, l = 0.5;
+ /*
+ Create a global variable, so that we can play with our widgets in the
+ console.
+ */
window.My = {};
- My.$backgroundBox = Ox.My.Box({
+ /*
+ Since `Ox.My.Box` is a good multi-purpose container, we create one to
+ contain the first four boxes.
+ */
+ My.$container = Ox.My.Box({
size: [256, 256]
})
.append(
@@ -558,16 +621,23 @@ Ox.load(['Image', 'UI'], function() {
})
)
.appendTo(Ox.$body);
+ /*
+ The ImageBox is a bit larger.
+ */
My.$imageBox = Ox.My.ImageBox({
image: 'png/pandora32.png',
size: [256, 256]
})
.appendTo(Ox.$body);
+ /*
+ As a last step, we add a handler to the `invert` event of each widgets. It
+ will display the widget name inside the `Ox.My.Box`.
+ */
Ox.forEach(My, function($element, name) {
$element.bindEvent({
invert: function() {
My.$box.displayText(name[1].toUpperCase() + name.slice(2));
}
- })
+ });
});
-});
\ No newline at end of file
+});