src/legacy/Molecule.js
/*
* LICENSE
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
*/
import Modifier from 'famous/src/core/Modifier'
import RenderNode from 'famous/src/core/RenderNode'
import TransitionableTransform from 'famous/src/transitions/TransitionableTransform'
import EventHandler from 'famous/src/core/EventHandler'
import {simpleExtend} from './utils'
import 'army-knife/polyfill.Function.name'
/**
* Molecules are the basic building blocks of all UI components. Molecules
* extend [famous/src/core/RenderNode](#famous/src/core/RenderNode), so they can be
* added to any `RenderNode` of a famo.us render tree, and by default they will
* also accept anything that a normal Famo.us `RenderNode` can accept via the
* `add` method. Classes that extend from `Molecule` might override
* `RenderNode.add` in order to accept things like arrays of renderables in
* stead of a single renderable.
*
* Molecules encapsulate the basic things you need for a component -- a
* [famous/src/transitions/TransitionableTransform](#famous/src/transitions/TransitionableTransform)
* for positioning things in space, and a [famous/src/core/EventHandler](#famous/src/core/EventHandler)
* for capturing user interaction -- exposing a unified API for working with these
* things. For now, [famous/src/core/Modifier](#famous/src/core/Modifier) is used as the interface
* for applying transforms and sizing, but this will change in Mixed Mode
* Famo.us.
*
* All components extend Molecule, but at the same time they can also use any
* number of Molecules internally to do nice things like create layouts and
* position multiple things in space.
*
* @class Molecule
* @extends {module: famous/src/core/RenderNode}
*/
export class Molecule extends RenderNode {
/**
* Creates a new `Molecule` and applies `initialOptions` to it's internal
* `famous/src/core/Modifier`. See [famous/src/core/Modifier](#famous/src/core/Modifier)
* for details on what options you can pass.
*
* Note: Mixed Mode Famo.us does away with Modifiers, so this API will
* change slightly, but the change will be in such a way that APIs of
* higher level classes won't change because of this. One of the biggest
* changes in Mixed Mode will be that `size` will be set only on a
* per-Surface basis as far as a render tree is concerned. So if you
* normally put multiple `Surface` instances into a `Modifier` that has a
* size, then instead you'll have to explicitly assign a `size` to each
* `Surface`. This is a good thing, and makes for a cleaner and easier to
* use render tree with a separation of concerns from classes that can
* handle boundaries and group sizing. `Molecule` might then be an example
* of such a class with it's own size API.
*
* @constructor
* @param {Object} initialOptions The options to initialize this Molecule's `Modifier` with.
*/
constructor(initialOptions) {
super()
// "private" stuff. Not really, but regard it like so. For example, if
// you see something like obj._.someVariable then you're accessing
// internal stuff that wasn't designed to be accessed directly, and any
// problem you enounter with that is your own problem. :)
//
// TODO: Use a WeakMap to store these at some point.
this._ = {
options: {}, // set and get with this.options
defaultOptions: {},
}
// Add default values for this Molecule
// TODO: Make default options static for the class.
simpleExtend(this._.defaultOptions, {
align: [0.5, 0.5],
origin: [0.5, 0.5],
transform: new TransitionableTransform(),
handler: new EventHandler(),
})
// set the user's initial options. This automatically creates
// this.modifier, and adds it to this (don't forget, *this* is a
// RenderNode, so a Molecule can add things to itself).
//
// NOTE: this.options is a setter property. This statement applies all
// relevant properties to this.modifier.
this.options = initialOptions
}
/**
* @property {Object} options The Molecule's options, which get applied to
* `this.modifier`. This may change with Mixed Mode. Setting this property
* overrides existing options. To extend existing options with new options,
* use `setOptions` instead. Unspecified options will be set to their default
* values.
*
* Note: Anytime `this.options` is assigned a new value, `this.modifier` is set
* to a new [famous/src/core/Modifier](#famous/src/core/Modifier).
*/
set options(newOptions) {
this.resetOptions()
this.setOptions(newOptions)
}
get options() {
return this._.options
}
/**
* @property {module: famous/src/transitions/TransitionableTransform} transform
* The transform of this `Molecule`. The default is a
* [famous/src/transitions/TransitionableTransform](#famous/src/transitions/TransitionableTransform).
* Setting this property automatically puts the new transform into effect.
* See [famous/src/core/Modifier.transformFrom](#famous/src/core/Modifier.transformFrom).
*/
set transform(newTransform) {
this.setOptions({transform: newTransform})
}
get transform() {
return this.options.transform
}
/**
* Compounds `newOptions` into the existing options, similar to extending an
* object and overriding only the desired properties. To override all
* options with a set of new options, set `this.options` directly.
*
* An example of setting just a single option without erasing other options:
*
* ```js
* const myMolecule = new Molecule()
* myMolecule.setOptions({
* align: [0.2, 0.8]
* })
* ```
*
* @param {Object} newOptions An object containing the new options to apply to this `Molecule`.
*/
setOptions(newOptions) {
if (typeof newOptions == 'undefined' || newOptions.constructor.name != 'Object') newOptions = {}
for (const prop in newOptions) {
// Subject to change when Famo.us API changes.
if (Modifier.prototype['' + prop + 'From']) {
this.modifier['' + prop + 'From'](newOptions[prop])
}
this._.options[prop] = newOptions[prop]
}
}
/**
* Sets all options back to their defaults.
*
* Note: Anytime this is called, `this.modifier` is set to a new
* [famous/src/core/Modifier](#famous/src/core/Modifier) having the default
* options.
*/
resetOptions() {
this.modifier = new Modifier()
this.set(this.modifier)
this.setOptions(this._.defaultOptions)
}
/**
* Forwards events from this Molecule's [famous/src/core/EventHandler](#famous/src/core/EventHandler) to the given
* target, which can be another `EventHandler` or `Molecule`.
*
* This method is equivalent to [famous/src/core/EventHandler.pipe](#famous/src/core/EventHandler.pipe),
* acting upon `this.handler`.
*
* TODO v0.1.0: Let this method accept a `Molecule`, then stop doing `pipe(this._.handler)` in other places
*/
pipe() {
const args = Array.prototype.splice.call(arguments, 0)
return this.options.handler.pipe.apply(this.options.handler, args)
}
/**
* Stops events from this Molecule's [famous/src/core/EventHandler](#famous/src/core/EventHandler)
* from being sent to the given target.
*
* This method is equivalent to [famous/src/core/EventHandler.unpipe](#famous/src/core/EventHandler.unpipe),
* acting upon `this.handler`.
*
* TODO v0.1.0: Let this method accept a `Molecule`, then stop doing `pipe(this.options.handler)` in other places
*/
unpipe() {
const args = Array.prototype.splice.call(arguments, 0)
return this.options.handler.unpipe.apply(this.options.handler, args)
}
/**
* Register an event handler for the specified event.
* See [famous/src/core/EventHandler.on](#famous/src/core/EventHandler.on).
*/
on() {
const args = Array.prototype.splice.call(arguments, 0)
return this.options.handler.on.apply(this.options.handler, args)
}
/**
* Unregister an event handler for the specified event.
* See [famous/src/core/EventHandler.off](#famous/src/core/EventHandler.off).
*/
off() {
const args = Array.prototype.splice.call(arguments, 0)
return this.options.handler.on.apply(this.options.handler, args)
}
}
export default Molecule