oo7-react
Version:
The Reactive Bond API
554 lines (466 loc) • 15.9 kB
JavaScript
'use strict';
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
var React = require('react');
var _require = require('oo7'),
Bond = _require.Bond,
TimeBond = _require.TimeBond,
ReactiveBond = _require.ReactiveBond,
TransformBond = _require.TransformBond;
/**
* React element in which app should be placed if it needs to wait for the parent
* frame to inject the BondCache.
*/
var InjectedCacheWaiter = function (_React$Component) {
_inherits(InjectedCacheWaiter, _React$Component);
function InjectedCacheWaiter() {
_classCallCheck(this, InjectedCacheWaiter);
var _this = _possibleConstructorReturn(this, (InjectedCacheWaiter.__proto__ || Object.getPrototypeOf(InjectedCacheWaiter)).call(this));
_this.state = { haveCache: window ? window.injectedBondCache ? true : null : false };
if (_this.state.haveCache === null) {
_this._timers = [window.setInterval(_this.checkInject.bind(_this), 100), window.setInterval(_this.checksTimeout.bind(_this), 2000)];
}
return _this;
}
_createClass(InjectedCacheWaiter, [{
key: 'checkInject',
value: function checkInject() {
if (window.injectedBondCache) {
Bond.cache = window.injectedBondCache;
this._timers.forEach(window.clearInterval);
this.setState({ haveCache: true });
}
}
}, {
key: 'checksTimeout',
value: function checksTimeout() {
this._timers.forEach(window.clearInterval);
this.setState({ haveCache: false });
}
}, {
key: 'render',
value: function render() {
return this.state.haveCache === null ? React.createElement(
'div',
null,
'Waiting for cache...'
) : this.props.children;
}
}]);
return InjectedCacheWaiter;
}(React.Component);
/**
* @summary A derivable class for creating React components that can transparently
* accept deal with prop values that are {@link Bond}s.
*
* This class is almost exactly equivalent to the basic {React.Component} class:
* You can subclass it, just as you would with the basic {React.Component}, to
* create new React-framework components. However, it provides awareness for
* prop values provided that are {@link Bond}s. In the case of a {@link Bond}
* prop, then the `state` of the object (specifically the field in `state` with the
* same name as the prop) is kept up to date with the representative
* value of the prop's {@link Bond}.
*
* The props that are {@link Bond}-aware must be enumerated at construction. Props
* not named there will just pass the {@link Bond} object through transparently.
*
* In addition to the normal {ReactiveComponent#render} function which can be used
* normally, there are also {ReactiveComponent#readyRender} and {ReactiveComponent#unreadyRender},
* which allow different render functions to be given depending on whether all
* {@link Bond}-based props are considered _ready_. {ReactiveComponent#unreadyRender} has
* a default render function, so you may typically implement just {ReactiveComponent#readyRender}.
*
* The {ReactiveComponent#ready} function is provided for determining whether all
* {@link Bond}-based props are considered _ready_.
*
* If you override the functions {ReactiveComponent.componentWillMount},
* {ReactiveComponent.componentWillUnmount} or {ReactiveComponent.receiveProps}, ensure
* you first call the superclass implementation.
*/
var ReactiveComponent = function (_React$Component2) {
_inherits(ReactiveComponent, _React$Component2);
/**
* Construct an instance of this class.
*
* @param {array} reactiveProps - The names of each prop for which a corresponding
* key/value in `this.state` should be maintained for its representative value.
* @param {object} bonds - An object defining the {@link Bond}s and their names
* which should have state entries maintained to the current values of the
* {@link Bond}s.
*
* @example
* class Clock extends ReactiveComponent {
* constructor() { super([], {time: new TimeBond}); }
* readyRender() { return <span>{this.state.time.toString()}</span>; }
* }
*/
function ReactiveComponent() {
var reactiveProps = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
var bonds = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
_classCallCheck(this, ReactiveComponent);
var _this2 = _possibleConstructorReturn(this, (ReactiveComponent.__proto__ || Object.getPrototypeOf(ReactiveComponent)).call(this));
_this2.reactiveProps = reactiveProps;
_this2.bonds = bonds;
_this2.allBondKeys = [].concat(reactiveProps).concat(Object.keys(bonds));
_this2.state = {};
return _this2;
}
/**
* Overridden function from React.Component.
*
* Ensure that any further derivations of this function call this superclass
* implementation.
*/
_createClass(ReactiveComponent, [{
key: 'componentWillMount',
value: function componentWillMount() {
this.initProps();
}
/**
* Overridden function from React.Component.
*
* Ensure that any further derivations of this function call this superclass
* implementation.
*/
}, {
key: 'componentWillReceiveProps',
value: function componentWillReceiveProps(nextProps) {
this.updateProps(nextProps);
}
/**
* Overridden function from React.Component.
*
* Ensure that any further derivations of this function call this superclass
* implementation.
*/
}, {
key: 'componentWillUnmount',
value: function componentWillUnmount() {
this.finiProps();
}
}, {
key: 'initProps',
value: function initProps() {
this.manageProps({}, this.props);
var that = this;
var bonds = this.bonds;
var bondKeys = Object.keys(bonds).filter(function (k) {
return typeof bonds[k] !== 'function';
});
this._consolidatedExtraBonds = new ReactiveBond(bondKeys.map(function (f) {
return bonds[f];
}), [], function (a) {
var s = that.state || {};
bondKeys.forEach(function (f, i) {
s[f] = a[i];
});
that.setState(s);
}).use();
}
}, {
key: 'finiProps',
value: function finiProps() {
if (this._consolidatedExtraBonds) {
var x = this._consolidatedExtraBonds;
delete this._consolidatedExtraBonds;
x.drop();
}
if (this._consolidatedBonds) {
var _x3 = this._consolidatedBonds;
delete this._consolidatedBonds;
_x3.drop();
}
if (this._derivedBonds) {
var _x4 = this._derivedBonds;
delete this._derivedBonds;
_x4.drop();
}
}
}, {
key: 'updateProps',
value: function updateProps(nextProps) {
this.manageProps(this.props, nextProps);
}
}, {
key: 'manageProps',
value: function manageProps(props, nextProps) {
var _this3 = this;
var that = this;
var bonds = this.bonds;
var derivedBondKeys = Object.keys(bonds).filter(function (k) {
return typeof bonds[k] === 'function';
});
if (this._derivedBonds) {
var x = this._derivedBonds;
delete this._derivedBonds;
x.drop();
}
if (this._consolidatedBonds) {
var _x5 = this._consolidatedBonds;
delete this._consolidatedBonds;
_x5.drop();
}
if (that.reactiveProps.length > 0) {
this._consolidatedBonds = new TransformBond(function () {
for (var _len = arguments.length, a = Array(_len), _key = 0; _key < _len; _key++) {
a[_key] = arguments[_key];
}
var s = {};
that.reactiveProps.forEach(function (f, i) {
s[f] = a[i];
});
that.setState(s);
return s;
}, this.reactiveProps.map(function (f) {
return nextProps[f];
}), []).use().subscriptable();
}
if (derivedBondKeys.length > 0) {
this._derivedBonds = new ReactiveBond(derivedBondKeys.map(function (f) {
return bonds[f](_this3._consolidatedBonds);
}), [], function (a) {
var s = {};
derivedBondKeys.forEach(function (f, i) {
return s[f] = a[i];
});
that.setState(s);
}).use();
}
}
/**
* Determine whether all props are ready.
*
* @returns {boolean} - `true` if and only if all props, specifically those
* which are {@link Bond} values and which are {@link Bond} aware, are _ready_.
*/
}, {
key: 'ready',
value: function ready() {
var _this4 = this;
return this.allBondKeys.every(function (k) {
return _this4.state[k] !== undefined;
});
}
/**
* Render this object with present state and props.
*
* This will only be called when all {@link Bond}-aware props are _ready_ and
* have a corresponding value in `this.state`.
*/
}, {
key: 'readyRender',
value: function readyRender() {
return this.unreadyRender();
}
/**
* Render this object with present state and props.
*
* This will only be called when not all {@link Bond}-aware props are _ready_.
*/
}, {
key: 'unreadyRender',
value: function unreadyRender() {
return React.createElement('span', null);
}
/**
* Overridden function from React.Component. Render the object with present
* state and props.
*/
}, {
key: 'render',
value: function render() {
return this.ready() ? this.readyRender() : this.unreadyRender();
}
}]);
return ReactiveComponent;
}(React.Component);
/**
* Simple coditional to output one item over another dependent on some condition.
*
* @example
* class Clock extends React.Component {
* constructor (someBond) { this._someBond = someBond }
* render () {
* return <If condition={this.someBond.ready()}
* then={<Rspan>{this.someBond}</Rspan>}
* else='Not ready'
* />
* }
* }
*/
var If = function (_ReactiveComponent) {
_inherits(If, _ReactiveComponent);
function If() {
_classCallCheck(this, If);
return _possibleConstructorReturn(this, (If.__proto__ || Object.getPrototypeOf(If)).call(this, ['condition']));
}
_createClass(If, [{
key: 'render',
value: function render() {
var x = (this.state.condition ? this.props.then : this.props.else) || React.createElement('span', null);
return typeof x === 'function' ? x() : x;
}
}]);
return If;
}(ReactiveComponent);
/**
* {@link Bond}-aware, variant of `span` component.
*
* `className` and `style` props, and the child, behave as expected but are
* {@link Bond}-aware.
*
* @example
* class Clock extends React.Component {
* render () { return <Rspan>{(new TimeBond).map(_=>_.toString())}</Rspan>; }
* }
*/
var Rspan = function (_ReactiveComponent2) {
_inherits(Rspan, _ReactiveComponent2);
function Rspan() {
_classCallCheck(this, Rspan);
return _possibleConstructorReturn(this, (Rspan.__proto__ || Object.getPrototypeOf(Rspan)).call(this, ['className', 'style', 'children']));
}
_createClass(Rspan, [{
key: 'render',
value: function render() {
return React.createElement(
'span',
{
className: this.state.className,
style: this.state.style,
name: this.props.name
},
this.state.children
);
}
}]);
return Rspan;
}(ReactiveComponent);
/**
* {@link Bond}-aware, variant of `div` component.
*
* `className` and `style` props, and the child, behave as expected but are
* {@link Bond}-aware.
*
* @example
* class Clock extends React.Component {
* render () { return <Rdiv>{(new TimeBond).map(_=>_.toString())}</Rdiv>; }
* }
*/
var Rdiv = function (_ReactiveComponent3) {
_inherits(Rdiv, _ReactiveComponent3);
function Rdiv() {
_classCallCheck(this, Rdiv);
return _possibleConstructorReturn(this, (Rdiv.__proto__ || Object.getPrototypeOf(Rdiv)).call(this, ['className', 'style', 'children']));
}
_createClass(Rdiv, [{
key: 'render',
value: function render() {
return React.createElement(
'div',
{
className: this.state.className,
style: this.state.style,
name: this.props.name
},
this.state.children
);
}
}]);
return Rdiv;
}(ReactiveComponent);
/**
* {@link Bond}-aware, variant of `a` component.
*
* `href`, `target`, `className` and `style` props, and the child, behave as
* expected but are {@link Bond}-aware.
*/
var Ra = function (_ReactiveComponent4) {
_inherits(Ra, _ReactiveComponent4);
function Ra() {
_classCallCheck(this, Ra);
return _possibleConstructorReturn(this, (Ra.__proto__ || Object.getPrototypeOf(Ra)).call(this, ['href', 'target', 'className', 'style', 'children']));
}
_createClass(Ra, [{
key: 'render',
value: function render() {
return React.createElement(
'a',
{
href: this.state.href,
target: this.state.target,
className: this.state.className,
style: this.state.style,
name: this.props.name
},
this.state.children
);
}
}]);
return Ra;
}(ReactiveComponent);
/**
* {@link Bond}-aware, variant of `img` component.
*
* `src`, `className` and `style` props, and the child, behave as
* expected but are {@link Bond}-aware.
*/
var Rimg = function (_ReactiveComponent5) {
_inherits(Rimg, _ReactiveComponent5);
function Rimg() {
_classCallCheck(this, Rimg);
return _possibleConstructorReturn(this, (Rimg.__proto__ || Object.getPrototypeOf(Rimg)).call(this, ['src', 'className', 'style']));
}
_createClass(Rimg, [{
key: 'render',
value: function render() {
return React.createElement('img', {
src: this.state.src,
className: this.state.className,
style: this.state.style,
name: this.props.name
});
}
}]);
return Rimg;
}(ReactiveComponent);
/**
* {@link Bond}-aware component for displaying hash values.
*
* Hash value (encoded as hex and `0x` prefixed) should be placed in `value` prop.
*
* `value`, `className` and `style` props behave as expected but are {@link Bond}-aware.
*/
var Hash = function (_ReactiveComponent6) {
_inherits(Hash, _ReactiveComponent6);
function Hash() {
_classCallCheck(this, Hash);
return _possibleConstructorReturn(this, (Hash.__proto__ || Object.getPrototypeOf(Hash)).call(this, ['value', 'className', 'style']));
}
_createClass(Hash, [{
key: 'render',
value: function render() {
var v = this.state.value;
var d = typeof v === 'string' && v.startsWith('0x') && v.length >= 18 ? v.substr(0, 8) + '…' + v.substr(v.length - 4) : v;
return React.createElement(
'span',
{
className: this.state.className,
style: this.state.style,
title: this.state.value,
name: this.props.name
},
d
);
}
}]);
return Hash;
}(ReactiveComponent);
Hash.defaultProps = {
className: '_hash'
};
module.exports = {
ReactiveComponent: ReactiveComponent, Rspan: Rspan, Rdiv: Rdiv, Ra: Ra, Rimg: Rimg, Hash: Hash, InjectedCacheWaiter: InjectedCacheWaiter, If: If
};