From c7e282cc424fa91296cd548d6e0732bb0fde4097 Mon Sep 17 00:00:00 2001 From: Sean Matheson Date: Wed, 6 Apr 2016 13:00:48 +0100 Subject: [PATCH] perf(api): Injector API updated to inject via an 'inject' prop. The Injector no longer supports the 'elements' prop, it now has an 'inject' prop which allows you to pass through either a stateless component (that will recieve the same props as the component being wrapped) or an element. Using the stateless component method is preferable as it will only be executed when needed, whereas passing an element requires you to actually create the element up front. closes #1 BREAKING CHANGE: The Injector no longer supports the 'elements' prop, it now has an 'inject' prop which allows you to pass through either a stateless component (that will recieve the same props as the component being wrapped) or an element. Using the stateless component method is preferable as it will only be executed when needed, whereas passing an element requires you to acutally create the element up front. --- examples/router/src/InjectableHeader.js | 1 - examples/router/src/PageOne.js | 2 +- examples/router/src/PageTwo.js | 10 +++- examples/router/src/index.js | 2 +- lib/react-injectables.js | 2 +- src/Injectable.js | 19 +++--- src/Injector.js | 33 +++++++--- src/Provider.js | 80 ++++++++++++++----------- src/{utils/index.js => utils.js} | 18 +++++- test/Provider.test.js | 54 ++++++++--------- test/integration.test.js | 13 +++- 11 files changed, 145 insertions(+), 89 deletions(-) rename src/{utils/index.js => utils.js} (90%) diff --git a/examples/router/src/InjectableHeader.js b/examples/router/src/InjectableHeader.js index ccf784d..0a07d15 100644 --- a/examples/router/src/InjectableHeader.js +++ b/examples/router/src/InjectableHeader.js @@ -7,7 +7,6 @@ const Header = ({ injected }) => (

INJECTABLE HEADER

- INJECTED ITEMS: {injected.length > 0 ? injected :
Nothing has been injected
}
diff --git a/examples/router/src/PageOne.js b/examples/router/src/PageOne.js index b3c1623..f0d1342 100644 --- a/examples/router/src/PageOne.js +++ b/examples/router/src/PageOne.js @@ -9,5 +9,5 @@ const PageOne = () => ( ); export default Injector({ to: InjectableHeader, - elements: [
Injection from Page One
] + inject: () =>
Injection from Page One
})(PageOne); diff --git a/examples/router/src/PageTwo.js b/examples/router/src/PageTwo.js index 7032475..206b1c7 100644 --- a/examples/router/src/PageTwo.js +++ b/examples/router/src/PageTwo.js @@ -8,7 +8,15 @@ const PageTwo = () => ( ); +const Inject = (props) => ( +
+ Injection from Page Two.
+ I also recieved these props:
+ {Object.keys(props).join(`, `)} +
+); + export default Injector({ to: InjectableHeader, - elements: [
Injection from Page Two
] + inject: Inject })(PageTwo); diff --git a/examples/router/src/index.js b/examples/router/src/index.js index 89a75ad..efd7375 100644 --- a/examples/router/src/index.js +++ b/examples/router/src/index.js @@ -16,7 +16,7 @@ ReactDOM.render(( - + } /> diff --git a/lib/react-injectables.js b/lib/react-injectables.js index 8c66e65..46bfd0a 100644 --- a/lib/react-injectables.js +++ b/lib/react-injectables.js @@ -1 +1 @@ -!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("react")):"function"==typeof define&&define.amd?define("react-injectables",["react"],t):"object"==typeof exports?exports["react-injectables"]=t(require("react")):e["react-injectables"]=t(e.React)}(this,function(e){return function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={exports:{},id:r,loaded:!1};return e[r].call(o.exports,o,o.exports,t),o.loaded=!0,o.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{"default":e}}Object.defineProperty(t,"__esModule",{value:!0}),t.Injector=t.Injectable=t.Provider=void 0;var o=n(5),i=r(o),u=n(3),c=r(u),s=n(4),a=r(s);t.Provider=i.default,t.Injectable=c.default,t.Injector=a.default},function(t,n){t.exports=e},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{"default":e}}function o(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);e.length>t;t++)n[t]=e[t];return n}return Array.from(e)}function i(){for(var e=arguments.length,t=Array(e),n=0;e>n;n++)t[n]=arguments[n];return function(){if(0===t.length)return arguments.length>0?arguments[0]:void 0;var e=t[t.length-1],n=t.slice(0,-1);return n.reduceRight(function(e,t){return t(e)},e.apply(void 0,arguments))}}function u(e){var t=e.children;return s.Children.only(t)}function c(e,t){var n=0;return t.map(function(t){return n++,a.default.createElement(u,{key:e+"_"+n},t)})}Object.defineProperty(t,"__esModule",{value:!0}),t.find=t.map=t.concatAll=t.containsUniq=t.uniq=t.withoutAll=t.without=t.all=t.filter=void 0,t.compose=i,t.keyedElements=c;var s=n(1),a=r(s),f=t.filter=function(e){return function(t){return t.filter(e)}},l=t.all=function(e){return function(t){for(var n=0;t.length>n;n++)if(!e(t[n]))return!1;return!0}},p=(t.without=function(e){return function(t){return f(function(t){return!Object.is(t,e)})(t)}},t.withoutAll=function(e){return function(t){return f(function(t){return l(function(e){return!Object.is(t,e)})(e)},t)}});t.uniq=function(e){return Array.from(new Set(e))},t.containsUniq=function(e){return function(t){return p(e)(t).length>0}},t.concatAll=function(e){return e.reduce(function(e,t){return[].concat(o(e),o(t))},[])},t.map=function(e){return function(t){return t.map(e)}},t.find=function(e){return function(t){return t.find(e)}}},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{"default":e}}function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function u(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{value:!0});var c=Object.assign||function(e){for(var t=1;arguments.length>t;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e},s=function(){function e(e,t){for(var n=0;t.length>n;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}(),a=n(1),f=r(a),l=n(2),p=0,d=function(e){p++;var t="injection_"+p,n=function(t){function n(){var e,t,r,u;o(this,n);for(var c=arguments.length,s=Array(c),a=0;c>a;a++)s[a]=arguments[a];return t=r=i(this,(e=Object.getPrototypeOf(n)).call.apply(e,[this].concat(s))),r.state={injected:[]},r.consume=function(e){(e.length!==r.state.injected.length||(0,l.containsUniq)(r.state.injected,e))&&r.setState({injected:e})},u=t,i(r,u)}return u(n,t),s(n,[{key:"componentWillMount",value:function(){this.context.consumeElements({injectionId:n.injectionId,injectable:this})}},{key:"componentWillUnmount",value:function(){this.context.stopConsumingElements({listener:this})}},{key:"render",value:function(){return f.default.createElement(e,c({injected:(0,l.keyedElements)("injected",this.state.injected)},this.props))}}]),n}(a.Component);return n.injectionId=t,n.contextTypes={consumeElements:a.PropTypes.func.isRequired,stopConsumingElements:a.PropTypes.func.isRequired},n};t.default=d},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{"default":e}}function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function u(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{value:!0});var c=function(){function e(e,t){for(var n=0;t.length>n;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}(),s=n(1),a=r(s),f=function(e){var t=e.to,n=e.elements;return function(e){var r=function(r){function s(){return o(this,s),i(this,Object.getPrototypeOf(s).apply(this,arguments))}return u(s,r),c(s,[{key:"componentWillMount",value:function(){this.context.produceElements({injectionId:t.injectionId,injector:this,elements:n})}},{key:"componentWillUnmount",value:function(){this.context.removeProducer({injectionId:t.injectionId,injector:this})}},{key:"render",value:function(){return a.default.createElement(e,this.props)}}]),s}(s.Component);return r.contextTypes={produceElements:s.PropTypes.func.isRequired,removeProducer:s.PropTypes.func.isRequired},r}};t.default=f},function(e,t,n){"use strict";function r(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);e.length>t;t++)n[t]=e[t];return n}return Array.from(e)}function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function u(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{value:!0});var c=function(){function e(e,t){for(var n=0;t.length>n;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}(),s=n(1),a=n(2),f=function(e){function t(e,n){o(this,t);var r=i(this,Object.getPrototypeOf(t).call(this,e,n));return r.registrations=[],r}return u(t,e),c(t,[{key:"getChildContext",value:function(){var e=this;return{produceElements:function(t){return e.produceElements(t)},removeProducer:function(t){return e.removeProducer(t)},consumeElements:function(t){return e.consumeElements(t)},stopConsumingElements:function(t){return e.stopConsumingElements(t)}}}},{key:"getRegistration",value:function(e){var t=e.injectionId,n=(0,a.find)(function(e){return e.injectionId===t})(this.registrations);return n||(n={injectionId:t,injectables:[],injectors:[]},this.registrations.push(n)),n}},{key:"notifyConsumers",value:function(e){var t=e.registration,n=t.injectables,r=t.injectors,o=(0,a.compose)(a.uniq,a.concatAll,(0,a.map)(function(e){return e.elements}))(r);n.forEach(function(e){e.consume(o)})}},{key:"removeRegistration",value:function(e){var t=e.registration;this.registrations=(0,a.without)(t)(this.registrations)}},{key:"consumeElements",value:function(e){var t=e.injectionId,n=e.injectable,o=this.getRegistration({injectionId:t});(0,a.withoutAll)(o.injectables)([n]).length>0&&(o.injectables=[].concat(r(o.injectables),[n]),this.notifyConsumers({registration:o}))}},{key:"stopConsumingElements",value:function(e){var t=e.injectionId,n=e.injectable,r=this.getRegistration({injectionId:t}),o=(0,a.without)(n)(r.injectables);0===o.length&&0===r.injectors.length?this.removeRegistration({registration:r}):r.injectables=o}},{key:"findProducer",value:function(e){var t=e.registration,n=e.injector;return(0,a.find)(function(e){return Object.is(e.injector,n)})(t.injectors)}},{key:"produceElements",value:function(e){var t=e.injectionId,n=e.injector,o=e.elements,i=this.getRegistration({injectionId:t}),u=this.findProducer({registration:i,injector:n});if(!u){var c={injector:n,elements:o};i.injectors=[].concat(r(i.injectors),[c]),this.notifyConsumers({registration:i})}}},{key:"removeProducer",value:function(e){var t=e.injectionId,n=e.injector,r=this.getRegistration({injectionId:t}),o=this.findProducer({registration:r,injector:n});r.injectors=(0,a.without)(o)(r.injectors),this.notifyConsumers({registration:r})}},{key:"render",value:function(){return s.Children.only(this.props.children)}}]),t}(s.Component);f.childContextTypes={produceElements:s.PropTypes.func.isRequired,removeProducer:s.PropTypes.func.isRequired,consumeElements:s.PropTypes.func.isRequired,stopConsumingElements:s.PropTypes.func.isRequired},f.propTypes={children:s.PropTypes.element},t.default=f}])}); \ No newline at end of file +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("react")):"function"==typeof define&&define.amd?define("react-injectables",["react"],t):"object"==typeof exports?exports["react-injectables"]=t(require("react")):e["react-injectables"]=t(e.React)}(this,function(e){return function(e){function t(r){if(n[r])return n[r].exports;var i=n[r]={exports:{},id:r,loaded:!1};return e[r].call(i.exports,i,i.exports,t),i.loaded=!0,i.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{"default":e}}Object.defineProperty(t,"__esModule",{value:!0}),t.Injector=t.Injectable=t.Provider=void 0;var i=n(5),o=r(i),c=n(3),u=r(c),a=n(4),f=r(a);t.Provider=o.default,t.Injectable=u.default,t.Injector=f.default},function(t,n){t.exports=e},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{"default":e}}function i(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);e.length>t;t++)n[t]=e[t];return n}return Array.from(e)}function o(){for(var e=arguments.length,t=Array(e),n=0;e>n;n++)t[n]=arguments[n];return function(){if(0===t.length)return arguments.length>0?arguments[0]:void 0;var e=t[t.length-1],n=t.slice(0,-1);return n.reduceRight(function(e,t){return t(e)},e.apply(void 0,arguments))}}function c(e){var t=e.children;return a.Children.only(t)}function u(e,t){var n=0;return t.map(function(t){return n++,f.default.createElement(c,{key:e+"_"+n},t)})}Object.defineProperty(t,"__esModule",{value:!0}),t.find=t.map=t.concatAll=t.containsUniq=t.uniqBy=t.withoutAll=t.without=t.all=t.filter=void 0,t.compose=o,t.keyedElements=u;var a=n(1),f=r(a),s=t.filter=function(e){return function(t){return t.filter(e)}},l=t.all=function(e){return function(t){for(var n=0;t.length>n;n++)if(!e(t[n]))return!1;return!0}},p=(t.without=function(e){return function(t){return s(function(t){return!Object.is(t,e)})(t)}},t.withoutAll=function(e){return function(t){return s(function(t){return l(function(e){return!Object.is(t,e)})(e)})(t)}});t.uniqBy=function(e){return function(t){var n=new Set,r=[];return t.forEach(function(t){var i=t[e];n.has(i)||(n.add(i),r.push(t))}),r}},t.containsUniq=function(e){return function(t){return p(e)(t).length>0}},t.concatAll=function(e){return e.reduce(function(e,t){return[].concat(i(e),i(t))},[])},t.map=function(e){return function(t){return t.map(e)}},t.find=function(e){return function(t){return t.find(e)}}},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{"default":e}}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function c(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{value:!0});var u=Object.assign||function(e){for(var t=1;arguments.length>t;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e},a=function(){function e(e,t){for(var n=0;t.length>n;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}(),f=n(1),s=r(f),l=n(2),p=0,j=function(e){p++;var t="injectionId_"+p,n=function(n){function r(){var e,t,n,c;i(this,r);for(var u=arguments.length,a=Array(u),f=0;u>f;f++)a[f]=arguments[f];return t=n=o(this,(e=Object.getPrototypeOf(r)).call.apply(e,[this].concat(a))),n.state={injected:[]},n.consume=function(e){(e.length!==n.state.injected.length||(0,l.containsUniq)(n.state.injected,e))&&n.setState({injected:e})},c=t,o(n,c)}return c(r,n),a(r,[{key:"componentWillMount",value:function(){this.context.registerInjectable({injectionId:t,injectable:this})}},{key:"componentWillUnmount",value:function(){this.context.removeInjectable({injectionId:t,injectable:this})}},{key:"render",value:function(){return s.default.createElement(e,u({injected:(0,l.keyedElements)("injected",this.state.injected)},this.props))}}]),r}(f.Component);return n.injectionId=t,n.contextTypes={registerInjectable:f.PropTypes.func.isRequired,removeInjectable:f.PropTypes.func.isRequired},n};t.default=j},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{"default":e}}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function c(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{value:!0});var u=function(){function e(e,t){for(var n=0;t.length>n;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}(),a=n(1),f=r(a),s=0,l=function(e){var t=e.to,n=e.inject;s++;var r="injector_"+s;return function(e){var s=function(a){function s(){var e,t,r,c;i(this,s);for(var u=arguments.length,a=Array(u),f=0;u>f;f++)a[f]=arguments[f];return t=r=o(this,(e=Object.getPrototypeOf(s)).call.apply(e,[this].concat(a))),r.getInjectElement=function(){return"function"==typeof n?n(r.props):n},c=t,o(r,c)}return c(s,a),u(s,[{key:"componentWillMount",value:function(){this.context.registerInjector({injectionId:t.injectionId,injectorId:r,injector:this})}},{key:"componentWillUnmount",value:function(){this.context.removeInjector({injectionId:t.injectionId,injector:this})}},{key:"render",value:function(){return f.default.createElement(e,this.props)}}]),s}(a.Component);return s.contextTypes={registerInjector:a.PropTypes.func.isRequired,removeInjector:a.PropTypes.func.isRequired},s}};t.default=l},function(e,t,n){"use strict";function r(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);e.length>t;t++)n[t]=e[t];return n}return Array.from(e)}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function c(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{value:!0});var u=function(){function e(e,t){for(var n=0;t.length>n;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}(),a=n(1),f=n(2),s=function(e){function t(e,n){i(this,t);var r=o(this,Object.getPrototypeOf(t).call(this,e,n));return r.registrations=[],r}return c(t,e),u(t,[{key:"getChildContext",value:function(){var e=this;return{registerInjector:function(t){return e.registerInjector(t)},removeInjector:function(t){return e.removeInjector(t)},registerInjectable:function(t){return e.registerInjectable(t)},removeInjectable:function(t){return e.removeInjectable(t)}}}},{key:"getRegistration",value:function(e){var t=e.injectionId,n=(0,f.find)(function(e){return e.injectionId===t})(this.registrations);return n||(n={injectionId:t,injectables:[],injections:[]},this.registrations.push(n)),n}},{key:"runInjections",value:function(e){var t=e.registration,n=t.injectables,r=t.injections,i=(0,f.compose)((0,f.map)(function(e){return e.injector.getInjectElement()}),(0,f.uniqBy)("injectorId"))(r);n.forEach(function(e){e.consume(i)})}},{key:"removeRegistration",value:function(e){var t=e.registration;this.registrations=(0,f.without)(t)(this.registrations)}},{key:"registerInjectable",value:function(e){var t=e.injectionId,n=e.injectable,i=this.getRegistration({injectionId:t});(0,f.withoutAll)(i.injectables)([n]).length>0&&(i.injectables=[].concat(r(i.injectables),[n]),this.runInjections({registration:i}))}},{key:"removeInjectable",value:function(e){var t=e.injectionId,n=e.injectable,r=this.getRegistration({injectionId:t}),i=(0,f.without)(n)(r.injectables);0===i.length&&0===r.injections.length?this.removeRegistration({registration:r}):r.injectables=i}},{key:"findInjection",value:function(e){var t=e.registration,n=e.injector;return(0,f.find)(function(e){return Object.is(e.injector,n)})(t.injections)}},{key:"registerInjector",value:function(e){var t=e.injectionId,n=e.injectorId,i=e.injector,o=this.getRegistration({injectionId:t}),c=this.findInjection({registration:o,injector:i});if(!c){var u={injector:i,injectorId:n};o.injections=[].concat(r(o.injections),[u]),this.runInjections({registration:o})}}},{key:"removeInjector",value:function(e){var t=e.injectionId,n=e.injector,r=this.getRegistration({injectionId:t}),i=this.findInjection({registration:r,injector:n});i&&(r.injections=(0,f.without)(i)(r.injections),this.runInjections({registration:r}))}},{key:"render",value:function(){return a.Children.only(this.props.children)}}]),t}(a.Component);s.childContextTypes={registerInjector:a.PropTypes.func.isRequired,removeInjector:a.PropTypes.func.isRequired,registerInjectable:a.PropTypes.func.isRequired,removeInjectable:a.PropTypes.func.isRequired},s.propTypes={children:a.PropTypes.element},t.default=s}])}); \ No newline at end of file diff --git a/src/Injectable.js b/src/Injectable.js index 03c228b..0980d9e 100644 --- a/src/Injectable.js +++ b/src/Injectable.js @@ -1,18 +1,18 @@ import React, { PropTypes, Component } from 'react'; import { containsUniq, keyedElements } from './utils'; -let injectionIndex = 0; +let injectionIdIndex = 0; const Injectable = (WrappedComponent) => { - injectionIndex++; - const injectionId = `injection_${injectionIndex}`; + injectionIdIndex++; + const injectionId = `injectionId_${injectionIdIndex}`; class InjectableComponent extends Component { static injectionId = injectionId; static contextTypes = { - consumeElements: PropTypes.func.isRequired, - stopConsumingElements: PropTypes.func.isRequired + registerInjectable: PropTypes.func.isRequired, + removeInjectable: PropTypes.func.isRequired }; state = { @@ -20,14 +20,17 @@ const Injectable = (WrappedComponent) => { } componentWillMount() { - this.context.consumeElements({ - injectionId: InjectableComponent.injectionId, + this.context.registerInjectable({ + injectionId, injectable: this }); } componentWillUnmount() { - this.context.stopConsumingElements({ listener: this }); + this.context.removeInjectable({ + injectionId, + injectable: this + }); } consume = (elements) => { diff --git a/src/Injector.js b/src/Injector.js index b452667..e5b885b 100644 --- a/src/Injector.js +++ b/src/Injector.js @@ -1,29 +1,44 @@ import React, { PropTypes, Component } from 'react'; -const Injector = (args: { to: Object, elements : Array }) => { - const { to, elements } = args; +type Inject = Object | (props: Object) => Object; + +let injectorIndex = 0; + +const Injector = (args: { to: Object, inject: Inject }) => { + const { to, inject } = args; + + injectorIndex++; + const injectorId = `injector_${injectorIndex}`; return function WrapComponent(WrappedComponent) { class InjectorComponent extends Component { static contextTypes = { - produceElements: PropTypes.func.isRequired, - removeProducer: PropTypes.func.isRequired + registerInjector: PropTypes.func.isRequired, + removeInjector: PropTypes.func.isRequired }; componentWillMount() { - this.context.produceElements({ + this.context.registerInjector({ injectionId: to.injectionId, - injector: this, - elements + injectorId, + injector: this }); } componentWillUnmount() { - this.context.removeProducer({ - injectionId: to.injectionId, injector: this + this.context.removeInjector({ + injectionId: to.injectionId, + injector: this }); } + getInjectElement = () => { + if (typeof inject === `function`) { + return inject(this.props); + } + return inject; + } + render() { return (); } diff --git a/src/Provider.js b/src/Provider.js index 2ef631e..d542877 100644 --- a/src/Provider.js +++ b/src/Provider.js @@ -1,12 +1,12 @@ import { Children, Component, PropTypes } from 'react'; -import { compose, concatAll, find, map, uniq, without, withoutAll } from './utils'; +import { compose, find, map, uniqBy, without, withoutAll } from './utils'; class InjectablesProvider extends Component { static childContextTypes = { - produceElements: PropTypes.func.isRequired, - removeProducer: PropTypes.func.isRequired, - consumeElements: PropTypes.func.isRequired, - stopConsumingElements: PropTypes.func.isRequired, + registerInjector: PropTypes.func.isRequired, + removeInjector: PropTypes.func.isRequired, + registerInjectable: PropTypes.func.isRequired, + removeInjectable: PropTypes.func.isRequired, }; static propTypes = { @@ -20,13 +20,13 @@ class InjectablesProvider extends Component { getChildContext() { return { - produceElements: (args) => this.produceElements(args), + registerInjector: (args) => this.registerInjector(args), - removeProducer: (args) => this.removeProducer(args), + removeInjector: (args) => this.removeInjector(args), - consumeElements: (args) => this.consumeElements(args), + registerInjectable: (args) => this.registerInjectable(args), - stopConsumingElements: (args) => this.stopConsumingElements(args) + removeInjectable: (args) => this.removeInjectable(args) }; } @@ -38,11 +38,10 @@ class InjectablesProvider extends Component { )(this.registrations); if (!registration) { - // Need to create the registration. registration = { injectionId, injectables: [], - injectors: [] + injections: [] }; this.registrations.push(registration); @@ -51,15 +50,14 @@ class InjectablesProvider extends Component { return registration; } - notifyConsumers(args: { registration: Object }) { + runInjections(args: { registration: Object }) { const { registration } = args; - const { injectables, injectors } = registration; + const { injectables, injections } = registration; const elements = compose( - uniq, - concatAll, - map(x => x.elements) - )(injectors); + map(x => x.injector.getInjectElement()), + uniqBy(`injectorId`) + )(injections); injectables.forEach(injectable => { injectable.consume(elements); @@ -71,56 +69,66 @@ class InjectablesProvider extends Component { this.registrations = without(registration)(this.registrations); } - consumeElements(args: { injectionId: string, injectable: Object}) { + registerInjectable(args: { injectionId: string, injectable: Object}) { const { injectionId, injectable } = args; const registration = this.getRegistration({ injectionId }); if (withoutAll(registration.injectables)([injectable]).length > 0) { registration.injectables = [...registration.injectables, injectable]; - this.notifyConsumers({ registration }); // First time consumption. + this.runInjections({ registration }); // First time consumption. } } - stopConsumingElements(args: { injectionId: string, injectable: Object }) { + removeInjectable(args: { injectionId: string, injectable: Object }) { const { injectionId, injectable } = args; const registration = this.getRegistration({ injectionId }); const injectables = without(injectable)(registration.injectables); - if (injectables.length === 0 && registration.injectors.length === 0) { + if (injectables.length === 0 && registration.injections.length === 0) { this.removeRegistration({ registration }); } else { registration.injectables = injectables; } } - findProducer({ registration, injector }) { - return find(x => Object.is(x.injector, injector))(registration.injectors); + findInjection({ registration, injector }) { + return find(x => Object.is(x.injector, injector))(registration.injections); } - produceElements(args: { injectionId: string, injector: Object, elements: Array }) { - const { injectionId, injector, elements } = args; + registerInjector(args: { injectionId: string, injectorId: string, injector: Object }) { + const { injectionId, injectorId, injector } = args; const registration = this.getRegistration({ injectionId }); - const existingProducer = this.findProducer({ registration, injector }); + const existingInjection = this.findInjection({ registration, injector }); - if (existingProducer) { + if (existingInjection) { return; } - const newInjector = { injector, elements }; - registration.injectors = [ - ...registration.injectors, - newInjector + const newInjection = { injector, injectorId }; + registration.injections = [ + ...registration.injections, + newInjection ]; - this.notifyConsumers({ registration }); + + this.runInjections({ registration }); } - removeProducer(args: { injectionId: string, injector: Object }) { + removeInjector(args: { injectionId: string, injector: Object }) { const { injectionId, injector } = args; const registration = this.getRegistration({ injectionId }); - const existingInjector = this.findProducer({ registration, injector }); - registration.injectors = without(existingInjector)(registration.injectors); - this.notifyConsumers({ registration }); + const injection = this.findInjection({ registration, injector }); + + if (injection) { + registration.injections = without(injection)(registration.injections); + this.runInjections({ registration }); + } else { + /* istanbul ignore next */ + if (process.env.NODE_ENV === `development`) { + throw new Error( + `Trying to remove an injector which has not been registered`); + } + } } render() { diff --git a/src/utils/index.js b/src/utils.js similarity index 90% rename from src/utils/index.js rename to src/utils.js index c79be51..9b07d4c 100644 --- a/src/utils/index.js +++ b/src/utils.js @@ -14,6 +14,7 @@ import React, { Children } from 'react'; */ export function compose(...funcs) { return (...args) => { + /* istanbul ignore next */ if (funcs.length === 0) { return args[0]; } @@ -47,8 +48,21 @@ export const withoutAll = (toRemove) => (point) => (x) => all(y => !Object.is(x, y))(toRemove) )(point); -// :: [a] -> [a] -export const uniq = y => Array.from(new Set(y)); +// :: a -> [b] +export const uniqBy = x => y => { + const checked = new Set(); + const result = []; + + y.forEach(a => { + const prop = a[x]; + if (!checked.has(prop)) { + checked.add(prop); + result.push(a); + } + }); + + return result; +}; /** * :: [a] -> [a] -> boolean diff --git a/test/Provider.test.js b/test/Provider.test.js index 3b38dc0..2e81600 100644 --- a/test/Provider.test.js +++ b/test/Provider.test.js @@ -22,13 +22,13 @@ describe(`Given the Injectables Provider`, () => { consumed2 = []; consumer1 = { consume: (elements) => { consumed1 = elements; } }; consumer2 = { consume: (elements) => { consumed2 = elements; } }; - producer1 = {}; - producer2 = {}; - producer3 = {}; + producer1 = { getInjectElement: () => element1 }; + producer2 = { getInjectElement: () => element1 }; + producer3 = { getInjectElement: () => element2 }; }); it(`Then a newly added consumer should initially receive now elements`, () => { - instance.consumeElements({ + instance.registerInjectable({ injectionId: `foo`, injectable: consumer1 }); @@ -39,10 +39,10 @@ describe(`Given the Injectables Provider`, () => { }); it(`Then a produced element should be consumed`, () => { - instance.produceElements({ + instance.registerInjector({ injectionId: `foo`, - injector: producer1, - elements: [element1] + injectorId: `injector1`, + injector: producer1 }); const expected = [element1]; @@ -51,7 +51,7 @@ describe(`Given the Injectables Provider`, () => { }); it(`Then a removed producer should update the consumer`, () => { - instance.removeProducer({ + instance.removeInjector({ injectionId: `foo`, injector: producer1 }); @@ -62,14 +62,14 @@ describe(`Given the Injectables Provider`, () => { }); it(`Then a removed consumer should not recieve any more produced elements`, () => { - instance.stopConsumingElements({ + instance.removeInjectable({ injectionId: `foo`, injectable: consumer1 }); - instance.produceElements({ + instance.registerInjector({ injectionId: `foo`, - injector: producer1, - elements: [element1] + injectorId: `injector1`, + injector: producer1 }); const expected = []; @@ -78,11 +78,11 @@ describe(`Given the Injectables Provider`, () => { }); it(`Then new consumers should both recieve elements that were already produced`, () => { - instance.consumeElements({ + instance.registerInjectable({ injectionId: `foo`, injectable: consumer1 }); - instance.consumeElements({ + instance.registerInjectable({ injectionId: `foo`, injectable: consumer2 }); @@ -96,10 +96,10 @@ describe(`Given the Injectables Provider`, () => { }); it(`Then a duplicate producer should result in no change to element consumption`, () => { - instance.produceElements({ + instance.registerInjector({ injectionId: `foo`, - injector: producer1, - elements: [element2] + injectorId: `injector1`, + injector: producer1 }); const expected = [element1]; @@ -110,11 +110,11 @@ describe(`Given the Injectables Provider`, () => { expect(actual2).to.eql(expected); }); - it(`Then a new producer with an existing element should result in no consumption change`, () => { - instance.produceElements({ + it(`Then a duplicate producer registration should result in no consumption change`, () => { + instance.registerInjector({ injectionId: `foo`, - injector: producer2, - elements: [element1] + injectorId: `injector2`, + injector: producer1 }); const expected = [element1]; @@ -126,10 +126,10 @@ describe(`Given the Injectables Provider`, () => { }); it(`Then a new producer with an new element should result in 2 element consumption`, () => { - instance.produceElements({ + instance.registerInjector({ injectionId: `foo`, - injector: producer3, - elements: [element2] + injectorId: `injector3`, + injector: producer3 }); const expected = [element1, element2]; @@ -141,7 +141,7 @@ describe(`Given the Injectables Provider`, () => { }); it(`Then removing producer 2 should result in no consumption changes`, () => { - instance.removeProducer({ + instance.removeInjector({ injectionId: `foo`, injector: producer2 }); @@ -155,11 +155,11 @@ describe(`Given the Injectables Provider`, () => { }); it(`Then removing the all producers should result in all consumptions being zeroed`, () => { - instance.removeProducer({ + instance.removeInjector({ injectionId: `foo`, injector: producer1 }); - instance.removeProducer({ + instance.removeInjector({ injectionId: `foo`, injector: producer3 }); diff --git a/test/integration.test.js b/test/integration.test.js index 9b53a89..e3f7ec8 100644 --- a/test/integration.test.js +++ b/test/integration.test.js @@ -43,15 +43,24 @@ describeWithDOM(`Given an Injectables configuration`, () => { children: PropTypes.any }; + const sectionOnePropTypes = { + message: PropTypes.string + }; + const SectionOne = () => (

Section One

); + SectionOne.propTypes = sectionOnePropTypes; + + const SectionOneHeaderInjection = props => +
Section One, {props.message}.
; + SectionOneHeaderInjection.propTypes = sectionOnePropTypes; HeaderInjectingSectionOne = Injector({ to: InjectableHeader, - elements: [
Section One Header Injection.
] + inject: SectionOneHeaderInjection })(SectionOne); const SectionTwo = () => ( @@ -62,7 +71,7 @@ describeWithDOM(`Given an Injectables configuration`, () => { HeaderInjectingSectionTwo = Injector({ to: InjectableHeader, - elements: [
Section Two Header Injection.
] + inject:
Section Two Header Injection.
})(SectionTwo); });