Skip to content

Commit

Permalink
feat: add attach utility method
Browse files Browse the repository at this point in the history
* chore: don't minify published output
* feat: add attach utility method
* doc: document the new attach method
* docs: further docs for attach
  • Loading branch information
tgvashworth authored Mar 27, 2017
1 parent cf010da commit 5cfa2a7
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 32 deletions.
80 changes: 76 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,28 @@ This construct supports trees of components because, if the child also mixes in
npm install --save flight-with-child-components
```

## Example
## Use

In the parent component, mixin `withChildComponents` into the parent.

```js
var withChildComponents = require('path/to/the/mixin');
defineComponent(Component, withChildComponents);
```

This will add a generated `this.childTeardownEvent` property to the component — like `_teardownEvent7` — which will then be used to coordinate teardown with any "child" components.

You don't need to use the `childTeardownEvent` manually: instead, use the `this.attachChild` method:

```js
this.attachChild(ChildComponent, this.select('someChild'));
```

This will do some magic to make sure that the `ChildComponent` instance does teardown with (actually, just before) the parent.

Here's a full example:

```js
var withChildComponents = require('fight-with-child-components');
var ChildComponent = require('some/child');
var AnotherChildComponent = require('some/other/child');

Expand All @@ -35,13 +51,69 @@ function parentComponent() {

// it supports the same API as 'attachTo'
this.attachChild(AnotherChildComponent, '.another-child', {
teardownOn: 'someEvent'
someProperty: true,
// You can manually specify a teardown event
teardownOn: 'someTeardownEvent'
});
});


setTimeout(() => {
this.trigger('someTeardownEvent');
}, 1000);
});
}
```

As in the above example, you can specify a custom teardown event:

```js
this.attachChild(AnotherChildComponent, '.another-child', {
teardownOn: 'someTeardownEvent'
});
```

This allows you to *manually* cause the teardown of that child.

Importantly, this **overrides** the parent-child teardown behaviour. If you want to keep it, you must additionally supply the `childTeardownEvent`:

```js
this.attachChild(AnotherChildComponent, '.another-child', {
teardownOn: `someTeardownEvent ${this.childTeardownEvent}`
});
```

### Non-Flight code

`withChildComponents` provides a utility to help you coordinate Flight-component teardown from non-Flight code.

First, import the `attach` method:

```js
const { attach } = require('flight-with-child-components');
```

You can use `attach` to attach Flight components like you would with `attachTo`, but you *also* can grab the resulting teardown event from the returned object:

```js
const { teardownEvent } = attach(Component, '.some-node');
```

You can then manually tear the component down using a jQuery event.

```js
$(document).trigger(teardownEvent);
```

Like with `attachChild`, you can supply a custom `teardownOn` event name:

```js
const { teardownEvent } = attach(Component, '.some-node', {
teardownOn: 'someTeardownEvent'
});
```

In this example, `teardownEvent` will be `someTeardownEvent`.

## Development

To develop this module, clone the repository and run:
Expand Down
14 changes: 0 additions & 14 deletions config/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,12 @@ var webpack = require('webpack');

var DedupePlugin = webpack.optimize.DedupePlugin;
var OccurenceOrderPlugin = webpack.optimize.OccurenceOrderPlugin;
var UglifyJsPlugin = webpack.optimize.UglifyJsPlugin;

var plugins = [
new DedupePlugin(),
new OccurenceOrderPlugin()
];

if (process.env.NODE_ENV === 'publish') {
plugins.push(
new UglifyJsPlugin({
compress: {
dead_code: true,
drop_console: true,
screw_ie8: true,
warnings: true
}
})
);
}

module.exports = {
entry: './src',
module: {
Expand Down
62 changes: 48 additions & 14 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,34 @@

var teardownEventCount = 0;

/**
* attacher takes a function that generates event-name strings. The supplied function is
* called every time a child component is attached.
*/
function attacher(eventNameGenerator) {
if (typeof eventNameGenerator !== 'function') {
eventNameGenerator = withChildComponents.nextTeardownEvent;
}

return function attach(Component, destination, attrs = {}) {
if (!attrs.teardownOn) {
attrs.teardownOn = eventNameGenerator.call(this);
}
var mixins = Component.prototype.mixedIn || [];
var isMixedIn = (mixins.indexOf(withBoundLifecycle) > -1);
var ComponentWithMixin = (
isMixedIn
? Component
: Component.mixin(withBoundLifecycle)
);
ComponentWithMixin.attachTo(destination, attrs);

return {
teardownEvent: attrs.teardownOn
};
};
}

function withBoundLifecycle() {
// Use deprecated defaultAttrs() only if necessary
var defineDefaultAttributes = (this.attrDef ? this.attributes : this.defaultAttrs);
Expand Down Expand Up @@ -51,20 +79,9 @@ function withChildComponents() {
* Takes Component (with attachTo method) plus destination and attrs arguments, which should
* be the same as in a normal attachTo call.
*/
this.attachChild = function (Component, destination, attrs) {
attrs = attrs || {};
if (!attrs.teardownOn) {
attrs.teardownOn = this.childTeardownEvent;
}
var mixins = Component.prototype.mixedIn || [];
var isMixedIn = (mixins.indexOf(withBoundLifecycle) > -1);
var ComponentWithMixin = (
isMixedIn
? Component
: Component.mixin(withBoundLifecycle)
);
ComponentWithMixin.attachTo(destination, attrs);
};
this.attachChild = attacher(function () {
return this.childTeardownEvent;
});
}

withChildComponents.nextTeardownEvent = function () {
Expand All @@ -74,4 +91,21 @@ withChildComponents.nextTeardownEvent = function () {

withChildComponents.withBoundLifecycle = withBoundLifecycle;

/**
* `attach` helps non-Flight code attach components and tear them down.
*
* Example usage:
*
* const { teardownEvent } = attach(Component, $someNode, { ... });
*
* ... sometime later ...
*
* $(document).trigger(teardownEvent);
*/
withChildComponents.attach = attacher(function () {
// This is called in this function, rather than passed directly, so that the
// generator can be tested
return withChildComponents.nextTeardownEvent();
});

module.exports = withChildComponents;
49 changes: 49 additions & 0 deletions src/with-child-components.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,12 @@ describe('withChildComponents', function () {
teardownOn: component.childTeardownEvent
});
});
it('should return an object with the child teardown event', function () {
var component = new Component();
component.initialize(window.outerDiv);
var result = component.attachChild(FakeComponent, '.my-selector', { test: true });
expect(result.teardownEvent).toEqual(component.childTeardownEvent);
});
it('should mix withBoundLifecycle into child', function () {
var component = new Component();
component.initialize(window.outerDiv);
Expand Down Expand Up @@ -144,4 +150,47 @@ describe('withChildComponents', function () {
});
});
});

describe('attach', function () {
let _nextTeardownEvent;
beforeEach(function () {
_nextTeardownEvent = withChildComponents.nextTeardownEvent;
withChildComponents.nextTeardownEvent = () => 'nextTeardownEvent';
});
afterEach(function () {
withChildComponents.nextTeardownEvent = _nextTeardownEvent;
});
it('should allow attaching without a parent', function () {
withChildComponents.attach(FakeComponent, '.my-selector', {
test: true
});
expect(FakeComponent.attachTo).toHaveBeenCalledWith('.my-selector', {
test: true,
teardownOn: 'nextTeardownEvent'
});
});
it('should allow attaching without a parent with a custom teardown event', function () {
withChildComponents.attach(FakeComponent, '.my-selector', {
test: true,
teardownOn: 'someTeardownEvent'
});
expect(FakeComponent.attachTo).toHaveBeenCalledWith('.my-selector', {
test: true,
teardownOn: 'someTeardownEvent'
});
});
it('should return an object with the supplied teardown event', function () {
var result = withChildComponents.attach(FakeComponent, '.my-selector', {
test: true,
teardownOn: 'someTeardownEvent'
});
expect(result.teardownEvent).toEqual('someTeardownEvent');
});
it('should return an object with the generated teardown event', function () {
var result = withChildComponents.attach(FakeComponent, '.my-selector', {
test: true
});
expect(result.teardownEvent).toEqual('nextTeardownEvent');
});
});
});

0 comments on commit 5cfa2a7

Please sign in to comment.