oxjs/examples/ui/widget_design_patterns/js/example.js
2012-06-21 23:14:38 +02:00

236 lines
7.6 KiB
JavaScript

/*
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 <a
href="http://www.crockford.com/javascript/inheritance.html">Douglas
Crockford</a>). 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';
/*
Load the UI module.
*/
Ox.load('UI', function() {
/*
Create our own namespace.
*/
Ox.My = {};
/*
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`. 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.
*/
var that = Ox.Element({}, self)
.defaults({
color: [128, 128, 128],
size: 128
})
.options(options || {})
.update({
color: setColor,
size: setSize
});
/*
The second part of the "constructor" function can be thought of as the
"initializer", and contains everything needed to set up the "instance".
In this case, we just define a minimum and maximum size and then set the
widget's color and size. We could have used `var minSize` and `var
maxSize` here, but by using `self` for private variables that we want to
be accessible across all the widget's methods, we can be sure that
inside such methods, any local `var` is actually local to the method.
*/
self.minSize = 128;
self.maxSize = 384;
setColor();
setSize();
/*
Third, we declare the widget's private methods. These are just function
declarations, hoisted to the top of the "constructor".
*/
function setColor() {
/*
To interact with the DOM, Ox.Element (and any widget derived from
it) wraps jQuery. If you type Ox.Element() in the console, you
will get something like `[<div class="OxElement"></div>]`, and the
widget's prototype has all the methods of a `$('<div>')`, with
proper chaining. If you have `var $d = $('<div>'), $e =
Ox.Element();`, then `$d.appendTo($e)` returns `$d`, and
`$e.append($d)` returns `$e`.
*/
that.css({
backgroundColor: 'rgb(' + self.options.color.join(', ') + ')',
});
}
function setSize() {
/*
Before setting the size, we make sure the value is between `minSize`
and `maxSize`.
*/
self.options.size = Ox.limit(
self.options.size, self.minSize, self.maxSize
);
that.css({
height: self.options.size + 'px',
width: self.options.size + 'px'
});
}
/*
Next, we define the widgets public methods, as properties of `that`.
(Note that unlike private methods, they are not hoisted.)
*/
that.showOptions = function() {
/*
As there isn't much to do yet, this method just displays the
widget's options.
*/
that.html(JSON.stringify(self.options).replace(/([,:])/g, '$1 '));
/*
Public methods should return `that`, for chaining.
*/
return that;
};
/*
And finally, at the very end of the "constructor", we return `that`. And
that's it.
*/
return that;
};
/*
<pre>
Ox.My.Box({color: [255, 0, 0]})
.appendTo(Ox.$body)
.options({size: 256})
.showOptions();
</pre>
*/
/*
Now we create a new widget that subclasses `Ox.My.Box`.
*/
Ox.My.RoundedBox = function(options, self) {
self = self || {};
/*
This time `that` is an instance of `Ox.My.Box`
*/
var that = Ox.My.Box({}, self);
/*
`Ox.RoundedBox` has additional default properties,
we define this by overwriting the defaults.
Sice we do not want to overwrite the existing defaults,
we first get the defaults and extend them with our new values.
Here we also show an alternative way of getting updates.
Instead of passing an object `{key: function}`,
we can also pass one function that gets called with key, value.
*/
that.defaults(Ox.extend(that.defaults(), {
radius: 16
}))
.options(options || {})
.update(function(key, value) {
if (key == 'radius') {
setRadius();
}
});
setRadius();
function setRadius() {
that.css({
borderRadius: self.options.radius + 'px'
});
}
return that;
};
/*
<pre>
Ox.My.RoundedBox({
color: [255, 0, 0],
radius: 32
})
.appendTo(Ox.$body)
.options({size: 256})
.showOptions();
</pre>
*/
/*
Its also possible to pass objects or other elements as options
*/
Ox.My.MetaBox = function(options, self) {
self = self || {};
var that = Ox.My.Box({}, self);
that.defaults(Ox.extend(that.defaults(), {
boxes: []
}))
.options(options || {});
self.options.boxes.forEach(function(box) {
that.append(box);
});
return that;
};
/*
Now its time to create some boxes for this demo:
*/
window.boxes = Ox.My.MetaBox({
boxes: [
Ox.My.Box({
color: [64, 128, 255]
}),
Ox.My.RoundedBox({
color: [255, 128, 64]
})
],
size: 384
})
.appendTo(Ox.$body);
});