diff --git a/component.js b/component.js index 3014664..0ba5072 100644 --- a/component.js +++ b/component.js @@ -1,7 +1,6 @@ "use strict"; -var React = require('react'), - assign = require('lodash.assign'); +var assign = require('lodash.assign'); var shouldComponentUpdate = require('./shouldupdate'); var cached = require('./cached'); @@ -30,250 +29,285 @@ var cached = require('./cached'); * @returns {Component} * @api public */ -module.exports = factory(); - -/** - * Create a “local” instance of the Omniscient component creator by using the `.withDefaults` method. - * This also allows you to override any defaults that Omniscient use to check equality of objects, - * unwrap cursors, etc. - * - * ### Options - * ```js - * { - * // Goes directly to component - * shouldComponentUpdate: function (nextProps, nextState), // check update - * jsx: false, // whether or not to default to jsx components - * cursorField: '__singleCursor', // cursor property name to "unwrap" before passing in to render - * isNode: function (propValue), // determines if propValue is a valid React node - * - * // Passed on to `shouldComponentUpdate` - * isCursor: function (cursor), // check if prop is cursor - * unCursor: function (cursor), // convert cursor to object - * isEqualCursor: function (oneCursor, otherCursor), // compares cursor - * isEqualState: function (currentState, nextState), // compares state - * isIgnorable: function (propertyValue, propertyKey), // check if property item is ignorable - * isEqualProps: function (currentProps, nextProps), // compares props - * isImmutable: function (maybeImmutable) // check if object is immutable - * } - * ``` - * - * ### Examples - * #### Always use JSX - * ```js - * var component = require('omniscient'); - * var jsxComponent = component.withDefaults({ - * jsx: true - * }); - * - * var Greeting = jsxComponent(function () { - * return

Hello!

- * }); - * React.render(, document.body); - * ``` - * - * #### Un-wrapping curors - * ```jsx - * var localComponent = component.withDefaults({ - * cursorField: 'foobar' - * }); - * - * var Component = localComponent(function (myCursor) { - * // Now you have myCursor directly instead of having to do props.foobar - * }); - * - * React.render(, document.body); - * ``` - * - * @param {Object} Options Options with defaults to override - * - * @property {Function} shouldComponentUpdate Get default shouldComponentUpdate - - * @module omniscient.withDefaults - * @returns {Component} - * @api public - */ -module.exports.withDefaults = factory; - -function factory (options) { - var debug; - options = options || {}; - var _shouldComponentUpdate = options.shouldComponentUpdate || - shouldComponentUpdate.withDefaults(options); - var _isCursor = options.isCursor || shouldComponentUpdate.isCursor; - var _isImmutable = options.isImmutable || shouldComponentUpdate.isImmutable; - var _isJsx = !!options.jsx; - var _hiddenCursorField = options.cursorField || '__singleCursor'; - var _isNode = options.isNode || isNode; - var _cached = cached.withDefaults(_shouldComponentUpdate); +module.exports = function (React) { + var instance = factory(); /** - * Activate debugging for components. Will log when a component renders, - * the outcome of `shouldComponentUpdate`, and why the component re-renders. + * Create a “local” instance of the Omniscient component creator by using the `.withDefaults` method. + * This also allows you to override any defaults that Omniscient use to check equality of objects, + * unwrap cursors, etc. * - * ### Example + * ### Options * ```js - * Search>: shouldComponentUpdate => true (cursors have changed) - * Search>: render - * SearchBox>: shouldComponentUpdate => true (cursors have changed) - * SearchBox>: render + * { + * // Goes directly to component + * shouldComponentUpdate: function(nextProps, nextState), // check update + * jsx: false, // whether or not to default to jsx components + * cursorField: '__singleCursor', // cursor property name to "unwrap" before passing in to render + * isNode: function(propValue), // determines if propValue is a valid React node + * + * // Passed on to `shouldComponentUpdate` + * isCursor: function(cursor), // check if prop is cursor + * unCursor: function (cursor), // convert cursor to object + * isEqualCursor: function (oneCursor, otherCursor), // compares cursor + * isEqualState: function (currentState, nextState), // compares state + * isEqualProps: function (currentProps, nextProps), // compares props + * isImmutable: function (maybeImmutable) // check if object is immutable + * } * ``` * - * @example omniscient.debug(/Search/i); + * ### Examples + * #### Always use JSX + * ```js + * var component = require('omniscient'); + * var jsxComponent = component.withDefaults({ + * jsx: true + * }); + * + * var Greeting = jsxComponent(function () { + * return

Hello!

+ * }); + * React.render(, document.body); + * ``` * - * @param {RegExp} pattern Filter pattern. Only show messages matching pattern + * #### Un-wrapping curors + * ```jsx + * var localComponent = component.withDefaults({ + * cursorField: 'foobar' + * }); * - * @property {Object} jsx Get component for use in JSX + * var Component = localComponent(function (myCursor) { + * // Now you have myCursor directly instead of having to do props.foobar + * }); * - * @module omniscient.debug - * @returns {Immstruct} + * React.render(, document.body); + * ``` + * + * @param {Object} Options Options with defaults to override + * + * @property {Function} shouldComponentUpdate Get default shouldComponentUpdate + * + * @module omniscient.withDefaults + * @returns {Component} * @api public */ - ComponentCreator.debug = debugFn; - ComponentCreator.cached = _cached; - ComponentCreator.shouldComponentUpdate = _shouldComponentUpdate; - return ComponentCreator; - - function ComponentCreator (displayName, mixins, render) { - var options = createDefaultArguments(displayName, mixins, render); - var methodStatics = pickStaticMixins(options.mixins); - - var componentObject = { - displayName: options.displayName || options.render.name, - mixins: options.mixins, - render: function render () { - if (debug) debug.call(this, 'render'); - // If `props['__singleCursor']` is set a single cursor was passed - // to the component, pick it out and pass it. - var input = this.props[_hiddenCursorField] || this.props; - this.cursor = this.props[_hiddenCursorField]; - return options.render.call(this, input, this.props.statics); - } - }; - - if (methodStatics) { - componentObject.statics = methodStatics; - removeOldStaticMethods(options.mixins); - } - - var Component = React.createClass(componentObject); - if (_isJsx) { - return Component; - } + instance.withDefaults = factory; + + return instance; + + function factory (options) { + var debug; + options = options || {}; + var _shouldComponentUpdate = options.shouldComponentUpdate || + shouldComponentUpdate.withDefaults(options); + var _isCursor = options.isCursor || shouldComponentUpdate.isCursor; + var _isImmutable = options.isImmutable || shouldComponentUpdate.isImmutable; + var _isJsx = !!options.jsx; + var _hiddenCursorField = options.cursorField || '__singleCursor'; + var _isNode = options.isNode || isNode; + var _cached = cached.withDefaults(_shouldComponentUpdate); /** - * Invoke component (rendering it) + * Activate debugging for components. Will log when a component renders, + * the outcome of `shouldComponentUpdate`, and why the component re-renders. + * + * ### Example + * ```js + * Search>: shouldComponentUpdate => true (cursors have changed) + * Search>: render + * SearchBox>: shouldComponentUpdate => true (cursors have changed) + * SearchBox>: render + * ``` * - * @param {String} displayName Component display name. Used in debug and by React - * @param {Object} props Properties that **do** trigger update when changed. Can be cursors, object and immutable structures - * @param {Object} statics Properties that do not trigger update when changed. Can be cursors, object and immutable structuress - * @param {Object} ..rest Child components (React elements, scalar values) + * @example omniscient.debug(/Search/i); + * + * @param {RegExp} pattern Filter pattern. Only show messages matching pattern * * @property {Object} jsx Get component for use in JSX - - * @module Component - * @returns {ReactElement} + * + * @module omniscient.debug + * @returns {Immstruct} * @api public */ - var create = function (key, props, statics) { - var _props; - var inputCursor; - var children; - - if (typeof key === 'object') { - statics = props; - props = key; - key = void 0; - } + ComponentCreator.debug = debugFn; + ComponentCreator.cached = _cached; + ComponentCreator.shouldComponentUpdate = _shouldComponentUpdate; + return ComponentCreator; + + function ComponentCreator (displayName, mixins, render) { + var options = createDefaultArguments(displayName, mixins, render); + var methodStatics = pickStaticMixins(options.mixins); + + var componentObject = { + displayName: options.displayName || options.render.name, + mixins: options.mixins, + render: function render () { + if (debug) debug.call(this, 'render'); + // If `props['__singleCursor']` is set a single cursor was passed + // to the component, pick it out and pass it. + var input = this.props[_hiddenCursorField] || this.props; + this.cursor = this.props[_hiddenCursorField]; + return options.render.call(this, input, this.props.statics); + } + }; - children = flatten(sliceFrom(arguments, statics).filter(_isNode)); - - // If passed props is a signle cursor we move it to `props[_hiddenCursorField]` - // to simplify should component update. The render function will move it back. - // The name '__singleCursor' is used to not clash with names of user passed properties - if (_isCursor(props) || _isImmutable(props)) { - inputCursor = props; - _props = {}; - _props[_hiddenCursorField] = inputCursor; - } else { - _props = assign({}, props); + if (methodStatics) { + componentObject.statics = methodStatics; + removeOldStaticMethods(options.mixins); } - // If statics is a node (due to it being optional) - // don't attach the node to the statics prop - if (!!statics && !props.statics && !_isNode(statics)) { - _props.statics = statics; + var Component = React.createClass(componentObject); + if (_isJsx) { + return Component; } - if (key) { - _props.key = key; - } + /** + * Invoke component (rendering it) + * + * @param {String} displayName Component display name. Used in debug and by React + * @param {Object} props Properties that **do** trigger update when changed. Can be cursors, object and immutable structures + * @param {Object} statics Properties that do not trigger update when changed. Can be cursors, object and immutable structuress + * @param {Object} ..rest Child components (React elements, scalar values) + * + * @property {Object} jsx Get component for use in JSX + + * @module Component + * @returns {ReactElement} + * @api public + */ + var create = function (key, props, statics) { + var _props; + var inputCursor; + var children; + + if (typeof key === 'object') { + statics = props; + props = key; + key = void 0; + } - if (!!children.length) { - _props.children = children; - } + children = flatten(sliceFrom(arguments, statics).filter(_isNode)); - return React.createElement(Component, _props); - }; + // If passed props is a signle cursor we move it to `props[_hiddenCursorField]` + // to simplify should component update. The render function will move it back. + // The name '__singleCursor' is used to not clash with names of user passed properties + if (_isCursor(props) || _isImmutable(props)) { + inputCursor = props; + _props = {}; + _props[_hiddenCursorField] = inputCursor; + } else { + _props = assign({}, props); + } - create.jsx = Component; + // If statics is a node (due to it being optional) + // don't attach the node to the statics prop + if (!!statics && !props.statics && !_isNode(statics)) { + _props.statics = statics; + } - if (methodStatics) { - create = assign(create, methodStatics); - } + if (key) { + _props.key = key; + } - return create; - } + if (!!children.length) { + _props.children = children; + } - function debugFn (pattern, logFn) { - if (_shouldComponentUpdate.debug) { - debug = _shouldComponentUpdate.debug(pattern, logFn); - } - } + return React.createElement(Component, _props); + }; - function createDefaultArguments (displayName, mixins, render) { + create.jsx = Component; - // (render) - if (typeof displayName === 'function') { - render = displayName; - mixins = []; - displayName = void 0; - } + if (methodStatics) { + create = assign(create, methodStatics); + } - // (mixins, render) - if (typeof displayName === 'object' && typeof mixins === 'function') { - render = mixins; - mixins = displayName; - displayName = void 0; + return create; } - // (displayName, render) - if (typeof displayName === 'string' && typeof mixins === 'function') { - render = mixins; - mixins = []; + function debugFn (pattern, logFn) { + if (_shouldComponentUpdate.debug) { + debug = _shouldComponentUpdate.debug(pattern, logFn); + } } - // Else (displayName, mixins, render) + function createDefaultArguments (displayName, mixins, render) { - if (!Array.isArray(mixins)) { - mixins = [mixins]; - } + // (render) + if (typeof displayName === 'function') { + render = displayName; + mixins = []; + displayName = void 0; + } - // Add built-in lifetime methods to keep `statics` up to date. - mixins.unshift(componentWillMount.asMixin, - componentWillReceiveProps.asMixin); + // (mixins, render) + if (typeof displayName === 'object' && typeof mixins === 'function') { + render = mixins; + mixins = displayName; + displayName = void 0; + } - if (!hasShouldComponentUpdate(mixins)) { - mixins.unshift({ - shouldComponentUpdate: _shouldComponentUpdate - }); + // (displayName, render) + if (typeof displayName === 'string' && typeof mixins === 'function') { + render = mixins; + mixins = []; + } + + // Else (displayName, mixins, render) + + if (!Array.isArray(mixins)) { + mixins = [mixins]; + } + + // Add built-in lifetime methods to keep `statics` up to date. + mixins.unshift(componentWillMount.asMixin, + componentWillReceiveProps.asMixin); + + if (!hasShouldComponentUpdate(mixins)) { + mixins.unshift({ + shouldComponentUpdate: _shouldComponentUpdate + }); + } + + return { + displayName: displayName, + mixins: mixins, + render: render + }; } + } - return { - displayName: displayName, - mixins: mixins, - render: render - }; + /** + * Predicate showing whether or not the argument is a valid React Node + * or not. Can be numbers, strings, bools, and React Elements. + * + * React's isNode check from ReactPropTypes validator + * but adjusted to not accept objects to avoid collision with props & statics. + * + * @param {String} propValue Property value to check if is valid React Node + * + * @returns {Boolean} + * @api private + */ + function isNode (propValue) { + switch (typeof propValue) { + case 'number': + case 'string': + return true; + case 'boolean': + return !propValue; + case 'object': + if (Array.isArray(propValue)) { + return propValue.every(isNode); + } + if (React.isValidElement(propValue)) { + return true; + } + return false; + default: + return false; + } } } @@ -326,38 +360,6 @@ function flatten (array) { return Array.prototype.concat.apply([], array); } -/** - * Predicate showing whether or not the argument is a valid React Node - * or not. Can be numbers, strings, bools, and React Elements. - * - * React's isNode check from ReactPropTypes validator - * but adjusted to not accept objects to avoid collision with props & statics. - * - * @param {String} propValue Property value to check if is valid React Node - * - * @returns {Boolean} - * @api private - */ -function isNode (propValue) { - switch (typeof propValue) { - case 'number': - case 'string': - return true; - case 'boolean': - return !propValue; - case 'object': - if (Array.isArray(propValue)) { - return propValue.every(isNode); - } - if (React.isValidElement(propValue)) { - return true; - } - return false; - default: - return false; - } -} - function delegate(delegee) { var delegateFunction = function() { return delegateFunction.delegee.apply(this, arguments); @@ -397,8 +399,8 @@ function componentWillReceiveProps (newProps) { var currentStatics = currentProps.statics; var newStatics = newProps.statics; var haveChangedStatics = newStatics !== currentStatics && - newStatics && - typeof newStatics === 'object'; + newStatics && + typeof newStatics === 'object'; if (haveChangedStatics) { Object.keys(newStatics).forEach(function (key) { diff --git a/dist/test.html b/dist/test.html new file mode 100644 index 0000000..7ef16e5 --- /dev/null +++ b/dist/test.html @@ -0,0 +1,14 @@ + + +
+ + + + + \ No newline at end of file diff --git a/index.js b/index.js new file mode 100644 index 0000000..27a1179 --- /dev/null +++ b/index.js @@ -0,0 +1,2 @@ +var React = require('react'); +module.exports = require('./component')(React); diff --git a/makeBundle.js b/makeBundle.js index 7405583..f98a5e7 100644 --- a/makeBundle.js +++ b/makeBundle.js @@ -7,7 +7,7 @@ var fs = require('fs'); var UglifyJS = require('uglify-js'); var pack = require('./package.json'); -var inputFile = './component.js'; +var inputFile = './index.js'; var outputFile = './dist/omniscient.js'; var outputMinifiedFile = './dist/omniscient.min.js'; diff --git a/native.js b/native.js new file mode 100644 index 0000000..db0b12b --- /dev/null +++ b/native.js @@ -0,0 +1,2 @@ +var ReactNative = require('react-native'); +module.exports = require('./component')(ReactNative); \ No newline at end of file diff --git a/package.json b/package.json index 762da49..0524eb5 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "omniscient", "version": "3.2.0", "description": "A library providing an abstraction for React components for passing the same data structure through the entire component flow using cursors and immutable data structures.", - "main": "component.js", + "main": "index.js", "directories": { "example": "example" }, @@ -28,6 +28,9 @@ "dox": "^0.6.1", "doxme": "git://github.com/mikaelbr/doxme#dev" }, + "optionalDependencies": { + "react-native": "^0.8.0" + }, "scripts": { "test": "mocha -R spec tests/**-test.js", "test-watch": "mocha -G -w -R min tests/**-test.js",