/* The following examples explain the common design pattern for Ox.js 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 (`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 `[
]`, and the widget's prototype has all the methods of a `$('
')`, with proper chaining. If you have `var $d = $('
'), $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; }; /*
    Ox.My.Box({color: [255, 0, 0]})
        .appendTo(Ox.$body)
        .options({size: 256})
        .showOptions();
    
*/ 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({ MozBorderRadius: self.options.radius + 'px', OBorderRadius: self.options.radius + 'px', WebkitBorderRadius: 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('
', 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); });