oxjs/examples/widget_design_patterns/js/example.js
2012-06-15 11:36:21 +02:00

330 lines
No EOL
10 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>
*/
Ox.My.RoundedBox = function(options, self) {
self = self || {};
var that = Ox.My.Box({}, self);
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;
};
window.myBox = Ox.My.Box().appendTo(Ox.$body);
window.myRoundedBox = Ox.My.RoundedBox().appendTo(Ox.$body);
return;
Ox.My.Box = function(options, self) {
self = self || {};
var that = Ox.Element('<div>', self)
.defaults({
color: [128, 128, 128],
size: 128
})
.options(options || {})
.addClass('OxMyBox OxMonospace')
.css({
color: 'white',
padding: '16px',
textShadow: '1px 1px 1px black'
})
.bindEvent({
anyclick: focus,
key_0: reset,
key_equal: function() {
Ox.print('+', self.options.size)
setSize(self.options.size + 16, true);
},
key_minus: function() {
setSize(self.options.size - 16, true);
}
});
self.initialSize = self.options.size;
self.minSize = 128;
self.maxSize = 384;
setColor();
setSize(self.options.size);
showOptions();
function focus(e) {
Ox.print(e.target);
if (e.target == that.$element[0]) {
that.gainFocus();
}
}
function setColor() {
that.css({
backgroundColor: 'rgb(' + self.options.color.join(', ') + ')',
});
}
function setSize(size, animate) {
var css;
self.options.size = Ox.limit(size, self.minSize, self.maxSize);
css = {
height: self.options.size + 'px',
width: self.options.size + 'px',
};
Ox.print('SET SIZE', size, animate, self.minSize, self.maxSize, css)
if (animate) {
that.stop().animate(css, 100, showOptions);
} else {
that.css(css);
}
}
function showOptions() {
try {
that.html(JSON.stringify(self.options).replace(/([,:])/g, '$1 '));
} catch(e) {}
}
function reset() {
self.options = self.initialOptions;
showOptions();
}
self.setOption = function(key, value) {
if (key == 'color') {
setColor();
} else if (key == 'size') {
setSize(value);
}
showOptions();
};
return that;
};
Ox.My.RoundedBox = function(options, self) {
self = self || {};
var that = Ox.My.Box({}, self);
that.defaults(Ox.extend(that.defaults(), {
radius: 16
}))
.options(options || {});
setRadius();
function setRadius() {
that.css({
MozBorderRadius: self.options.radius + 'px',
OBorderRadius: self.options.radius + 'px',
WebkitBorderRadius: self.options.radius + 'px'
});
}
self.setSuperOption = self.setOption;
self.setOption = function(key, value) {
if (key == 'radius') {
setRadius();
} else {
self.setSuperOption(key, value);
}
}
return that;
};
Ox.My.MetaBox = function(options, self) {
self = self || {};
Ox.print('MB', options)
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;
};
Ox.My.MetaBox({
boxes: [
Ox.My.Box({
color: [64, 128, 255]
}),
Ox.My.RoundedBox({
color: [255, 128, 64]
})
],
size: 384
})
.appendTo(Ox.$body);
});