Skip to content

Commit

Permalink
feat: queryParam & withParachute decorators (#69)
Browse files Browse the repository at this point in the history
  • Loading branch information
offirgolan authored Jan 9, 2019
1 parent 979799b commit 2deef3f
Show file tree
Hide file tree
Showing 13 changed files with 406 additions and 118 deletions.
21 changes: 11 additions & 10 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
module.exports = {
root: true,
parser: 'babel-eslint',
parserOptions: {
ecmaVersion: 2017,
sourceType: 'module'
},
plugins: [
'ember'
],
extends: [
'eslint:recommended',
'plugin:ember/recommended'
],
plugins: ['ember'],
extends: ['eslint:recommended', 'plugin:ember/recommended'],
env: {
browser: true
},
rules: {
'ember/avoid-leaking-state-in-ember-objects': 'off'
},
overrides: [
// node files
Expand Down Expand Up @@ -44,9 +41,13 @@ module.exports = {
node: true
},
plugins: ['node'],
rules: Object.assign({}, require('eslint-plugin-node').configs.recommended.rules, {
// add your custom rules and overrides for node files here
})
rules: Object.assign(
{},
require('eslint-plugin-node').configs.recommended.rules,
{
// add your custom rules and overrides for node files here
}
)
}
]
};
84 changes: 66 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ If it is a bug [please open an issue on GitHub](http://github.com/offirgolan/emb
The source of truth for your application's query params are query param maps. First, define one in your controller:

```js
// controllers/my-route.js
import Ember from 'ember';
import Controller from '@ember/controller';
import QueryParams from 'ember-parachute';
import { or } from '@ember/object/computed';

export const myQueryParams = new QueryParams({
parachuteOpen: {
Expand All @@ -62,10 +62,8 @@ export const myQueryParams = new QueryParams({
}
});

export default Ember.Controller.extend(myQueryParams.Mixin, {
queryParamsChanged: Ember.computed.or(
'queryParamsState.{page,search,tags}.changed'
),
export default Controller.extend(myQueryParams.Mixin, {
queryParamsChanged: or('queryParamsState.{page,search,tags}.changed'),

setup({ queryParams }) {
this.fetchData(queryParams);
Expand Down Expand Up @@ -101,6 +99,60 @@ In the above example, the mixin adds the `setup`, `reset`, and `queryParamsDidCh

Please continue reading for more advanced usage.

## Decorators

This package provides some decorators in order to use ember-parachute with the now supported class syntax.

### `queryParam`

```js
import Controller from '@ember/controller';
import { queryParam } from 'ember-parachute/decorators';

export default class MyController extends Controller {
@queryParam({
as: 'parachute',
serialize(value) {
return value ? 'open' : 'closed';
},
deserialize(value) {
return value === 'open' ? true : false;
}
})
parachuteOpen = true;

@queryParam({ refresh: true, replace: true }) page = 1;

@queryParam({ refresh: true }) search = '';

@queryParam({
refresh: true,
serialize(value = '') {
return value.toString();
},
deserialize(value = '') {
return value.split(',');
}
})
tags = ['Ember', 'Parachute'];
}
```

### `withParachute`

If you're not using any query params but still want the `setup` and `reset` hooks, you can use the `withParachute` class decorator.

```js
import Controller from '@ember/controller';
import { withParachute } from 'ember-parachute/decorators';

@withParachute
export default class MyController extends Controller {
setup() {}
reset() {}
}
```

## Query Param Map

The query param map is the source of truth for your query params. Here, you'll be able to define configuration for each query param:
Expand Down Expand Up @@ -267,7 +319,7 @@ const myQueryParams = new QueryParams({
/* ... */
});

export default Ember.Controller.extend(myQueryParams.Mixin, {
export default Controller.extend(myQueryParams.Mixin, {
// ...
});
```
Expand Down Expand Up @@ -295,9 +347,7 @@ controller.get('queryParamsState.page'); // { value: 2, defaultValue: 1, changed
This CP is useful when creating another CP to determine if any query params have changed from their default values:

```js
queryParamsChanged: Ember.computed.or(
'queryParamsState.{page,search,tags}.changed'
);
queryParamsChanged: or('queryParamsState.{page,search,tags}.changed');
```

You can then use this CP to conditionally display a button that can clear all query params to their default values.
Expand Down Expand Up @@ -402,18 +452,18 @@ export default Controller.extend(myQueryParams.Mixin, {
The controller also emits an event for each hook which receives the same arguments:

```ts
export default Ember.Controller.extend({
onChange: Ember.on('queryParamsDidChange', function(
export default Controller.extend({
onChange: on('queryParamsDidChange', function(
queryParamsChangedEvent: ParachuteEvent
) {
// ...
}),

onSetup: Ember.on('setup', function(queryParamsChangedEvent: ParachuteEvent) {
onSetup: on('setup', function(queryParamsChangedEvent: ParachuteEvent) {
// ...
}),

onReset: Ember.on('reset', function(
onReset: on('reset', function(
queryParamsChangedEvent: ParachuteEvent,
isExiting: boolean
) {
Expand Down Expand Up @@ -445,10 +495,8 @@ function resetQueryParams(params?: string[]): void;
Reset all or given params to their default value. The second argument is an array of query params to reset. If empty, all query params will be reset. You can use this in an action to reset query params when they have changed:

```js
export default Ember.Controller.extend(myQueryParams.Mixin, {
queryParamsChanged: Ember.computed.or(
'queryParamsState.{page,search,tags}.changed'
),
export default Controller.extend(myQueryParams.Mixin, {
queryParamsChanged: or('queryParamsState.{page,search,tags}.changed'),

actions: {
resetAll() {
Expand Down
27 changes: 1 addition & 26 deletions addon/-private/query-param.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
import { get } from '@ember/object';
import { assert } from '@ember/debug';
import { isPresent, isEmpty } from '@ember/utils';
import { isPresent } from '@ember/utils';
import Ember from 'ember';

const { canInvoke } = Ember;

const { keys } = Object;

const REQUIRED_PROPS = ['defaultValue'];

/**
* Normalized query param object.
*
Expand All @@ -21,10 +17,6 @@ export default class QueryParam {
`[ember-parachute] You must specify a key to the QueryParam Class`,
isPresent(key)
);
assert(
`[ember-parachute] You must specify all required fields for the query param: '${key}'`,
this._validateOptions(options)
);

/** @type {string} */
this.key = key;
Expand Down Expand Up @@ -88,21 +80,4 @@ export default class QueryParam {
toString() {
return `QueryParam<${this.key}>`;
}

/**
* Validate required options.
*
* @private
* @param {object} options
* @returns {boolean}
*
* @memberof QueryParam
*/
_validateOptions(options) {
let optionKeys = keys(options);
return (
!isEmpty(optionKeys) &&
REQUIRED_PROPS.every(p => optionKeys.indexOf(p) > -1)
);
}
}
16 changes: 16 additions & 0 deletions addon/decorators/-private/query-params-for.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import QueryParams from '../../query-params';

const QP_MAP = new WeakMap();

export function getQueryParamsFor(klass) {
QP_MAP.set(klass, QP_MAP.get(klass) || new QueryParams());

return QP_MAP.get(klass);
}

export function addQueryParamFor(klass, key, definition) {
QP_MAP.set(
klass,
getQueryParamsFor(klass).extend({ [key]: definition || {} })
);
}
2 changes: 2 additions & 0 deletions addon/decorators/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default as queryParam } from './query-param';
export { default as withParachute } from './with-parachute';
40 changes: 40 additions & 0 deletions addon/decorators/query-param.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import {
addQueryParamFor,
getQueryParamsFor
} from './-private/query-params-for';

function createDescriptor(desc, qpDefinition) {
qpDefinition = qpDefinition || {};

const descriptor = {
...desc,
finisher(klass) {
addQueryParamFor(klass, desc.key, qpDefinition);
klass.reopen(getQueryParamsFor(klass).Mixin);

return klass;
}
};

if (desc.kind === 'field') {
if (typeof desc.initializer === 'function') {
qpDefinition.defaultValue = desc.initializer();
}

descriptor.initializer = function initializer() {
return qpDefinition.defaultValue;
};
}

return descriptor;
}

export default function queryParam(qpDefinition) {
// Handle `@queryParam` usage
if (`${qpDefinition}` === '[object Descriptor]') {
return createDescriptor(qpDefinition);
}

// Handle `@queryParam()` usage
return desc => createDescriptor(desc, qpDefinition);
}
12 changes: 12 additions & 0 deletions addon/decorators/with-parachute.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import QueryParams from '../query-params';

export default function withParachute(desc) {
return {
...desc,
finisher(klass) {
klass.reopen(new QueryParams().Mixin);

return klass;
}
};
}
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,11 @@
"ember-cli-babel": "^7.1.2"
},
"devDependencies": {
"@ember-decorators/babel-transforms": "^4.0.0",
"@ember/jquery": "^0.5.2",
"@ember/optional-features": "^0.6.3",
"@types/ember": "^3.0.26",
"babel-eslint": "^10.0.1",
"broccoli-asset-rev": "^2.7.0",
"ember-cli": "~3.6.1",
"ember-cli-autoprefixer": "^0.8.1",
Expand All @@ -54,6 +56,8 @@
"ember-cli-uglify": "^2.1.0",
"ember-composable-helpers": "^2.1.0",
"ember-concurrency": "^0.8.26",
"ember-concurrency-decorators": "^0.5.0",
"ember-decorators": "^4.0.0",
"ember-disable-prototype-extensions": "^1.1.3",
"ember-export-application-global": "^2.0.0",
"ember-load-initializers": "^2.0.0",
Expand Down
Loading

0 comments on commit 2deef3f

Please sign in to comment.