├── src
├── index.js
├── feed.js
├── di.js
└── signal.js
├── examples
├── public
│ ├── favicon.ico
│ └── index.html
├── .gitignore
├── src
│ ├── index.js
│ ├── App.js
│ ├── components
│ │ ├── ListTodos.js
│ │ ├── Footer.js
│ │ ├── Todo.js
│ │ └── AddingNewToDo.js
│ ├── stores
│ │ └── ToDosStore.js
│ └── index.css
└── package.json
├── reports
├── lcov-report
│ ├── sort-arrow-sprite.png
│ ├── prettify.css
│ ├── index.js.html
│ ├── feed.jsx.html
│ ├── waitFor.js.html
│ ├── waitFor.jsx.html
│ ├── feed.js.html
│ ├── sorter.js
│ ├── index.html
│ ├── base.css
│ ├── di.js.html
│ ├── signal.js.html
│ └── prettify.js
├── test-results.xml
├── lcov.info
└── coverage.raw.json
├── .istanbul.yml
├── test
├── setup.js
└── spec
│ ├── di.spec.js
│ ├── feed.spec.jsx
│ └── signal.spec.js
├── .babelrc
├── .gitignore
├── lib
├── index.js
├── waitFor.js
├── feed.js
├── di.js
└── signal.js
├── LICENSE
├── package.json
└── README.md
/src/index.js:
--------------------------------------------------------------------------------
1 | export * from './feed';
2 | export * from './di';
3 | export * from './signal';
4 |
--------------------------------------------------------------------------------
/examples/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/krasimir/hocbox/master/examples/public/favicon.ico
--------------------------------------------------------------------------------
/reports/lcov-report/sort-arrow-sprite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/krasimir/hocbox/master/reports/lcov-report/sort-arrow-sprite.png
--------------------------------------------------------------------------------
/.istanbul.yml:
--------------------------------------------------------------------------------
1 | instrumentation:
2 | root: src
3 | extensions: ['.js', '.jsx']
4 | include-all-sources: true
5 | es-module: true
6 | reporting:
7 | dir: reports
8 |
--------------------------------------------------------------------------------
/test/setup.js:
--------------------------------------------------------------------------------
1 | import chai, { expect } from 'chai';
2 | import sinon from 'sinon';
3 | import sinonChai from 'sinon-chai';
4 |
5 | chai.config.truncateThreshold = 0;
6 |
7 | global.expect = expect;
8 | global.sinon = sinon;
9 |
10 | chai.use(sinonChai);
11 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "react",
4 | [ "es2015", { "loose": true } ],
5 | "stage-3"
6 | ],
7 | "plugins": [
8 | "syntax-async-functions",
9 | "transform-html-import-to-string"
10 | ],
11 | "ignore": [ "node_modules/**/*" ]
12 | }
13 |
--------------------------------------------------------------------------------
/examples/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env
15 | npm-debug.log*
16 | yarn-debug.log*
17 | yarn-error.log*
18 |
19 |
--------------------------------------------------------------------------------
/examples/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 | import ToDosStore from './stores/ToDosStore';
5 | import './index.css';
6 |
7 | import { register } from '../../lib';
8 |
9 | const store = new ToDosStore();
10 |
11 | register({ store });
12 |
13 | ReactDOM.render(
14 |
{ this.props.getText() }
; 8 | } 9 | } 10 | var Foo, Bar; 11 | 12 | function renderComponent(Component, props = {}) { 13 | return mount(| 1 47 | 2 48 | 3 49 | 4 | 10x 50 | 20x 51 | 46x 52 | | export * from './feed'; 53 | export * from './di'; 54 | export * from './signal'; 55 | |
{ `${ this.props.text || 'Hello' } ${ this.props.name || 'World' }` }
9 | ) 10 | } 11 | } 12 | 13 | function renderComponent(Component, props = {}) { 14 | return mount(| 1 47 | 2 48 | 3 49 | 4 50 | 5 51 | 6 52 | 7 53 | 8 54 | 9 55 | 10 56 | 11 57 | 12 58 | 13 59 | 14 60 | 15 61 | 16 62 | 17 63 | 18 64 | 19 65 | 20 66 | 21 67 | 22 68 | 23 69 | 24 70 | 25 | 71 | 1x 72 | 73 | 74 | 75 | 4x 76 | 77 | 4x 78 | 12x 79 | 12x 80 | 81 | 82 | 4x 83 | 4x 84 | 85 | 86 | 6x 87 | 88 | 89 | 90 | 3x 91 | 3x 92 | 93 | 94 | |
95 | import React from 'react';
96 |
97 | export default function feed(Component) {
98 | var _listener;
99 | var _defaultFeedProps = {};
100 |
101 | return {
102 | Component: class FeedComponent extends React.Component {
103 | constructor(props) {
104 | super(props);
105 |
106 | this.state = { feedProps: _defaultFeedProps }
107 | _listener = feedProps => this.setState({ feedProps });
108 | }
109 | render() {
110 | return <Component { ...this.props } { ...this.state.feedProps } />;
111 | }
112 | },
113 | update: function (props) {
114 | _defaultFeedProps = props;
115 | if (_listener) _listener(props);
116 | }
117 | }
118 | } |
| 1 47 | 2 48 | 3 49 | 4 50 | 5 51 | 6 52 | 7 53 | 8 54 | 9 55 | 10 56 | 11 57 | 12 58 | 13 59 | 14 60 | 15 61 | 16 62 | 17 63 | 18 64 | 19 65 | 20 66 | 21 67 | 22 68 | 23 69 | 24 70 | 25 | 71 | 1x 72 | 73 | 74 | 75 | 2x 76 | 2x 77 | 78 | 10x 79 | 80 | 1x 81 | 1x 82 | 1x 83 | 84 | 6x 85 | 86 | 87 | 2x 88 | 2x 89 | 90 | 91 | 3x 92 | 93 | 94 | |
95 | import React from 'react';
96 |
97 | export default function waitFor(Component) {
98 | var _listener;
99 | var _defaultWaitForProps = {};
100 | var _ready = false;
101 |
102 | return class FeedComponent extends React.Component {
103 | static done(props) {
104 | _defaultWaitForProps = props;
105 | _ready = true;
106 | Eif (_listener) _listener(props);
107 | }
108 | constructor(props) {
109 | super(props);
110 |
111 | this.state = { waitForProps: _defaultWaitForProps }
112 | _listener = waitForProps => this.setState({ waitForProps });
113 | }
114 | render() {
115 | return _ready ? <Component { ...this.props } { ...this.state.waitForProps } /> : null;
116 | }
117 | };
118 | } |
| 1 47 | 2 48 | 3 49 | 4 50 | 5 51 | 6 52 | 7 53 | 8 54 | 9 55 | 10 56 | 11 57 | 12 58 | 13 59 | 14 60 | 15 61 | 16 62 | 17 63 | 18 64 | 19 65 | 20 66 | 21 67 | 22 68 | 23 69 | 24 70 | 25 71 | 26 72 | 27 | 73 | 1x 74 | 75 | 76 | 77 | 2x 78 | 2x 79 | 80 | 2x 81 | 6x 82 | 6x 83 | 84 | 85 | 2x 86 | 2x 87 | 88 | 89 | 3x 90 | 91 | 92 | 93 | 1x 94 | 1x 95 | 1x 96 | 97 | 98 | |
99 | import React from 'react';
100 |
101 | export default function waitFor(Component) {
102 | var _listener;
103 | var _defaultWaitForProps = {};
104 | var _ready = false;
105 |
106 | return {
107 | Component: class FeedComponent extends React.Component {
108 | constructor(props) {
109 | super(props);
110 |
111 | this.state = { waitForProps: _defaultWaitForProps }
112 | _listener = waitForProps => this.setState({ waitForProps });
113 | }
114 | render() {
115 | return _ready ? <Component { ...this.props } { ...this.state.waitForProps } /> : null;
116 | }
117 | },
118 | done: function (props) {
119 | _defaultWaitForProps = props;
120 | _ready = true;
121 | Eif (_listener) _listener(props);
122 | }
123 | }
124 | } |
| 1 47 | 2 48 | 3 49 | 4 50 | 5 51 | 6 52 | 7 53 | 8 54 | 9 55 | 10 56 | 11 57 | 12 58 | 13 59 | 14 60 | 15 61 | 16 62 | 17 63 | 18 64 | 19 65 | 20 66 | 21 67 | 22 68 | 23 69 | 24 70 | 25 71 | 26 72 | 27 73 | 28 | 74 | 1x 75 | 76 | 77 | 5x 78 | 5x 79 | 80 | 25x 81 | 82 | 4x 83 | 4x 84 | 85 | 18x 86 | 87 | 88 | 6x 89 | 90 | 6x 91 | 2x 92 | 93 | 4x 94 | 95 | 96 | 97 | 10x 98 | 99 | 100 | |
101 | import React from 'react';
102 |
103 | export function feed(Component) {
104 | var _listeners = [];
105 | var _defaultFeedProps = {};
106 |
107 | return class FeedComponent extends React.Component {
108 | static feed(props) {
109 | _defaultFeedProps = props;
110 | if (_listeners.length > 0) _listeners.forEach(l => l(props));
111 | }
112 | constructor(props) {
113 | super(props);
114 |
115 | this.state = { feedProps: _defaultFeedProps };
116 |
117 | if (props.feeder) {
118 | _listeners.push(() => this.setState({ feedProps: props.feeder() }));
119 | } else {
120 | _listeners.push(feedProps => this.setState({ feedProps }));
121 | }
122 | }
123 | render() {
124 | return <Component { ...this.props } { ...this.state.feedProps } />;
125 | }
126 | };
127 | } |
Hello { firstName } { lastName}
; 13 | }); 14 | const result = mount(Hello { firstName } { lastName}
; 25 | }, ['firstName', 'lastName']); 26 | const result = mount({ foo }
; 39 | }); 40 | const ComponentB = signal(({ dispatch }) => { 41 | dispatch('foo', 'bar'); 42 | returnBar
; 43 | }); 44 | 45 | const resultA = mount(Hello { firstName } { lastName }
; 56 | }); 57 | const result = mount(Hello { firstName } { lastName }
; 70 | }); 71 | const result = mount({ props.foo }
; 84 | }); 85 | const ComponentB = signal(function CB(props) { 86 | props.dispatch('foo', 'B'); 87 | return{ props.bar }
; 88 | }); 89 | class ComponentCClass extends React.Component { 90 | constructor(props) { 91 | super(props); 92 | props.dispatch('bar', 'C'); 93 | } 94 | render() { 95 | return{ this.props.tartar }
; 96 | } 97 | } 98 | const ComponentC = signal(ComponentCClass); 99 | 100 | mount(feed(<component>):<component> |
21 | |
| For the cases when we want to rerender a component with given props | 24 ||
| accepts | 27 |React component | 28 |
| returns | 31 |Enhanced React Component with a static method `feed` | 32 |
The answer is { answer || '...' }
; 43 | }); 44 | 45 | // ... and we render our Component 46 | class App extends React.Component { 47 | render() { 48 | returnregister(<object>) |
72 | |
| Defines dependencies | 75 ||
| accepts | 78 |Object of key-value props | 79 |
| returns | 82 |nothing | 83 |
91 | wire(<component>, <array>, <function>):<component>
92 | |
93 | |
| We describe what dependencies we need and map them to props sent to our component. | 96 ||
| accepts | 99 |
100 |
|
106 |
| returns | 109 |Enhanced React component | 110 |
invalidate() |
118 | |
| Invalidates the dependencies. Useful when we change some of them and we want to rerender. | 121 ||
| accepts | 124 |nothing | 125 |
| returns | 128 |nothing | 129 |
163 | signal(<component>):<component>
164 | |
165 | |
Enhancing React component so it has dispatch, subscribe and unsubscribe methods as props. |
168 | |
| accepts | 171 |React component | 172 |
| returns | 175 |Enhanced React component | 176 |
184 | subscribe(<string>, <function>)
185 | |
186 | |
| Subscribing to a signal | 189 ||
| accepts | 192 |
193 |
|
198 |
| returns | 201 |nothing | 202 |
210 | dispatch(<string>, <data>)
211 | |
212 | |
| Dispatching/emitting a signal | 215 ||
| accepts | 218 |
219 |
|
224 |
| returns | 227 |nothing | 228 |
| File | 50 |51 | | Statements | 52 |53 | | Branches | 54 |55 | | Functions | 56 |57 | | Lines | 58 |59 | |
|---|---|---|---|---|---|---|---|---|---|
| di.js | 63 |88.57% | 65 |31/35 | 66 |69.23% | 67 |18/26 | 68 |100% | 69 |14/14 | 70 |96% | 71 |24/25 | 72 ||
| feed.js | 76 |100% | 78 |16/16 | 79 |100% | 80 |4/4 | 81 |100% | 82 |8/8 | 83 |100% | 84 |12/12 | 85 ||
| index.js | 89 |100% | 91 |3/3 | 92 |100% | 93 |6/6 | 94 |100% | 95 |3/3 | 96 |100% | 97 |3/3 | 98 ||
| signal.js | 102 |100% | 104 |53/53 | 105 |88.46% | 106 |23/26 | 107 |100% | 108 |21/21 | 109 |100% | 110 |42/42 | 111 |
| 1 47 | 2 48 | 3 49 | 4 50 | 5 51 | 6 52 | 7 53 | 8 54 | 9 55 | 10 56 | 11 57 | 12 58 | 13 59 | 14 60 | 15 61 | 16 62 | 17 63 | 18 64 | 19 65 | 20 66 | 21 67 | 22 68 | 23 69 | 24 70 | 25 71 | 26 72 | 27 73 | 28 74 | 29 75 | 30 76 | 31 77 | 32 78 | 33 79 | 34 80 | 35 81 | 36 82 | 37 83 | 38 84 | 39 85 | 40 86 | 41 87 | 42 88 | 43 89 | 44 90 | 45 91 | 46 92 | 47 93 | 48 94 | 49 95 | 50 96 | 51 | 1x 97 | 98 | 1x 99 | 100 | 101 | 4x 102 | 4x 103 | 104 | 105 | 106 | 4x 107 | 108 | 109 | 3x 110 | 3x 111 | 3x 112 | 113 | 114 | 115 | 2x 116 | 117 | 118 | 1x 119 | 1x 120 | 1x 121 | 1x 122 | 123 | 124 | 3x 125 | 3x 126 | 3x 127 | 128 | 129 | 3x 130 | 3x 131 | 132 | 133 | 3x 134 | 135 | 12x 136 | 9x 137 | 138 | 139 | 3x 140 | 3x 141 | 142 | 143 | 4x 144 | 145 | 146 | | import React from 'react';
147 |
148 | var Data = {};
149 |
150 | function dependenciesToProps(dependencies, mapToProps, storage) {
151 | const deps = dependencies.map(key => {
152 | Eif (Data[storage][key]) return Data[storage][key];
153 | throw new Error(`Hocbox: Missing dependency with key = "${ key }"`);
154 | });
155 |
156 | return mapToProps.apply({}, deps);
157 | }
158 | function registerDependenciesToPropsCallback(func, storage) {
159 | Iif (!Data[storage]) Data[storage] = {};
160 | Eif (!Data[storage].___dependenciesToProps___) Data[storage].___dependenciesToProps___ = [];
161 | Data[storage].___dependenciesToProps___.push(func);
162 | }
163 |
164 | export function clear() {
165 | Data = {};
166 | }
167 |
168 | export function invalidate(storage = 'hocbox') {
169 | Iif (!Data[storage]) Data[storage] = {};
170 | Iif (!Data[storage].___dependenciesToProps___) Data[storage].___dependenciesToProps___ = [];
171 | Data[storage].___dependenciesToProps___.forEach(f => f());
172 | }
173 |
174 | export function register(dependencies, storage = 'hocbox') {
175 | Eif (!Data[storage]) Data[storage] = {};
176 | Object.keys(dependencies).forEach(key => Data[storage][key] = dependencies[key]);
177 | }
178 |
179 | export function wire(Component, dependencies, mapToProps, storage = 'hocbox') {
180 | const _getDepsProps = dependenciesToProps.bind({}, dependencies, mapToProps, storage);
181 | var _listener;
182 |
183 | registerDependenciesToPropsCallback(() => _listener && _listener(), storage);
184 |
185 | return class DIComponent extends React.Component {
186 | constructor(props) {
187 | super(props);
188 |
189 | this.state = { depsProps: _getDepsProps() }
190 | _listener = () => this.setState({ depsProps: _getDepsProps() });
191 | }
192 | render() {
193 | return <Component { ...this.props } { ...this.state.depsProps } />;
194 | }
195 | }
196 | } |
| 1 47 | 2 48 | 3 49 | 4 50 | 5 51 | 6 52 | 7 53 | 8 54 | 9 55 | 10 56 | 11 57 | 12 58 | 13 59 | 14 60 | 15 61 | 16 62 | 17 63 | 18 64 | 19 65 | 20 66 | 21 67 | 22 68 | 23 69 | 24 70 | 25 71 | 26 72 | 27 73 | 28 74 | 29 75 | 30 76 | 31 77 | 32 78 | 33 79 | 34 80 | 35 81 | 36 82 | 37 83 | 38 84 | 39 85 | 40 86 | 41 87 | 42 88 | 43 89 | 44 90 | 45 91 | 46 92 | 47 93 | 48 94 | 49 95 | 50 96 | 51 97 | 52 98 | 53 99 | 54 100 | 55 101 | 56 102 | 57 103 | 58 104 | 59 105 | 60 106 | 61 107 | 62 108 | 63 109 | 64 110 | 65 111 | 66 112 | 67 113 | 68 114 | 69 115 | 70 116 | 71 117 | 72 118 | 73 119 | 74 120 | 75 121 | 76 122 | 77 123 | 78 124 | 79 125 | 80 126 | 81 127 | 82 128 | 83 129 | 84 130 | 85 131 | 86 132 | 87 133 | 88 134 | 89 135 | 90 136 | 91 137 | 92 | 1x 138 | 139 | 1x 140 | 1x 141 | 1x 142 | 143 | 144 | 12x 145 | 146 | 147 | 148 | 1x 149 | 150 | 151 | 152 | 8x 153 | 154 | 155 | 156 | 18x 157 | 158 | 159 | 160 | 161 | 18x 162 | 16x 163 | 26x 164 | 165 | 166 | 167 | 168 | 16x 169 | 2x 170 | 1x 171 | 172 | 15x 173 | 15x 174 | 3x 175 | 3x 176 | 177 | 15x 178 | 15x 179 | 180 | 181 | 182 | 4x 183 | 1x 184 | 3x 185 | 186 | 187 | 3x 188 | 189 | 190 | 191 | 192 | 36x 193 | 27x 194 | 195 | 9x 196 | 9x 197 | 7x 198 | 199 | 12x 200 | 12x 201 | 23x 202 | 203 | 204 | 9x 205 | 4x 206 | 1x 207 | 1x 208 | 209 | 3x 210 | 3x 211 | 212 | 213 | 214 | 9x 215 | 9x 216 | 10x 217 | 218 | 219 | 220 | 32x 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | | import React from 'react';
229 |
230 | var SignalStorage = {};
231 | var SignalComponentID = 0;
232 | var SignalLog = [];
233 |
234 | function getNewId() {
235 | return ++SignalComponentID;
236 | }
237 |
238 | export function getLog() {
239 | return SignalLog;
240 | }
241 |
242 | export function clearLog() {
243 | SignalLog = [];
244 | }
245 |
246 | export function dispatch(key, data, source) {
247 | SignalLog.push({
248 | key: key,
249 | data: data,
250 | source: source
251 | });
252 | if (SignalStorage[key]) {
253 | Object.keys(SignalStorage[key]).forEach(id => {
254 | SignalStorage[key][id](data);
255 | });
256 | }
257 | }
258 | export function subscribe(key, signalID, callback) {
259 | if (typeof key === 'object') {
260 | Object.keys(key).forEach(k => subscribe(k, key[k]));
261 | return;
262 | }
263 | if (!SignalStorage[key]) SignalStorage[key] = {};
264 | if (typeof signalID === 'function') {
265 | callback = signalID;
266 | signalID = getNewId();
267 | }
268 | Eif (!SignalStorage[key][signalID]) {
269 | SignalStorage[key][signalID] = callback;
270 | }
271 | }
272 | export function unsubscribe(key, signalID) {
273 | if (key === null) {
274 | Object.keys(SignalStorage).forEach(key => {
275 | if (SignalStorage[key][signalID]) delete SignalStorage[key][signalID];
276 | });
277 | } else {
278 | if (SignalStorage[key][signalID]) delete SignalStorage[key][signalID];
279 | }
280 | }
281 |
282 | export function signal(Component, subscribeTo) {
283 | return class SignalComponent extends React.Component {
284 | constructor(props) {
285 | super(props);
286 | this._signalID = (Component.name || '') + '_' + getNewId();
287 | this._dispatch = (key, data) => {
288 | dispatch(key, data, this._signalID);
289 | }
290 | this._subscribe = (...keys) => {
291 | keys.forEach(key => {
292 | subscribe(key, this._signalID, data => this.setState({ [key]: data }));
293 | });
294 | }
295 | this._unsubscribe = (...keys) => {
296 | if (keys.length === 0) {
297 | unsubscribe(null, this._signalID);
298 | return;
299 | }
300 | keys.forEach(key => {
301 | unsubscribe(key, this._signalID);
302 | });
303 | }
304 |
305 | subscribeTo && subscribeTo.forEach(to => this._subscribe(to));
306 | Object.keys(props).forEach(prop => {
307 | Eif (props[prop] === true) this._subscribe(prop);
308 | })
309 | }
310 | render() {
311 | return <Component
312 | { ...this.props }
313 | { ...this.state }
314 | dispatch={ this._dispatch }
315 | subscribe={ this._subscribe }
316 | unsubscribe={ this._unsubscribe } />;
317 | }
318 | };
319 | } |