From 307a2a124cd68b2b30b193f9c305f65c36bfb56a Mon Sep 17 00:00:00 2001 From: offirgolan Date: Wed, 9 Jan 2019 13:06:05 -0800 Subject: [PATCH 1/2] feat: Decorators --- .eslintrc.js | 20 +-- addon/-private/query-param.js | 27 +--- addon/decorators/-private/query-params-for.js | 16 ++ addon/decorators/index.js | 1 + addon/decorators/query-param.js | 32 ++++ package.json | 3 + tests/dummy/app/controllers/index.js | 88 +++++----- tests/unit/query-params-test.js | 2 - tsconfig.json | 9 +- yarn.lock | 152 +++++++++++++++++- 10 files changed, 257 insertions(+), 93 deletions(-) create mode 100644 addon/decorators/-private/query-params-for.js create mode 100644 addon/decorators/index.js create mode 100644 addon/decorators/query-param.js diff --git a/.eslintrc.js b/.eslintrc.js index ec0142c..5c5f466 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -4,17 +4,13 @@ module.exports = { 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 @@ -44,9 +40,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 + } + ) } ] }; diff --git a/addon/-private/query-param.js b/addon/-private/query-param.js index b985847..a3401d6 100644 --- a/addon/-private/query-param.js +++ b/addon/-private/query-param.js @@ -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. * @@ -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; @@ -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) - ); - } } diff --git a/addon/decorators/-private/query-params-for.js b/addon/decorators/-private/query-params-for.js new file mode 100644 index 0000000..d1000e9 --- /dev/null +++ b/addon/decorators/-private/query-params-for.js @@ -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 || {} }) + ); +} diff --git a/addon/decorators/index.js b/addon/decorators/index.js new file mode 100644 index 0000000..2e92dd4 --- /dev/null +++ b/addon/decorators/index.js @@ -0,0 +1 @@ +export { default as queryParam } from './query-param'; diff --git a/addon/decorators/query-param.js b/addon/decorators/query-param.js new file mode 100644 index 0000000..29617f0 --- /dev/null +++ b/addon/decorators/query-param.js @@ -0,0 +1,32 @@ +import { + addQueryParamFor, + getQueryParamsFor +} from './-private/query-params-for'; + +export default function queryParam(qpDefinition) { + return desc => { + 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; + }; +} diff --git a/package.json b/package.json index 11854ac..d915364 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "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", @@ -54,6 +55,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", diff --git a/tests/dummy/app/controllers/index.js b/tests/dummy/app/controllers/index.js index eaafc5b..ee3cde5 100644 --- a/tests/dummy/app/controllers/index.js +++ b/tests/dummy/app/controllers/index.js @@ -1,83 +1,83 @@ -import { or } from '@ember/object/computed'; import Controller from '@ember/controller'; import { A } from '@ember/array'; -import QueryParams from 'ember-parachute'; -import { task, timeout } from 'ember-concurrency'; +import { queryParam } from 'ember-parachute/decorators'; +import { timeout } from 'ember-concurrency'; +import { action } from '@ember-decorators/object'; +import { or } from '@ember-decorators/object/computed'; +import { task } from 'ember-concurrency-decorators'; -const queryParams = new QueryParams({ - parachuteOpen: { +export default class IndexController extends Controller { + @queryParam({ as: 'parachute', - defaultValue: true, serialize(value) { return value ? 'open' : 'closed'; }, deserialize(value) { return value === 'open' ? true : false; } - }, - page: { - defaultValue: 1, - refresh: true - }, - search: { - defaultValue: '', - refresh: true - }, - tags: { - defaultValue: ['Ember', 'Parachute'], + }) + parachuteOpen = true; + + @queryParam({ refresh: true }) page = 1; + + @queryParam({ refresh: true }) search = ''; + + @queryParam({ refresh: true, - serialize(value) { + serialize(value = '') { return value.toString(); }, deserialize(value = '') { return value.split(','); } - } -}); + }) + tags = ['Ember', 'Parachute']; -export default Controller.extend(queryParams.Mixin, { - queryParamsChanged: or('queryParamsState.{page,search,tags}.changed'), + @or('queryParamsState.{page,search,tags}.changed') queryParamsChanged; setup({ queryParams }) { if (queryParams.parachuteOpen) { this.get('fetchModel').perform(); } - }, + } reset(_, isExiting) { if (isExiting) { this.resetQueryParams(); } - }, + } queryParamsDidChange({ shouldRefresh, queryParams }) { if (shouldRefresh && queryParams.parachuteOpen) { this.get('fetchModel').perform(); } - }, + } - fetchModel: task(function*() { + @task({ restartable: true }) + *fetchModel() { yield timeout(1000); - }).restartable(), + } - actions: { - addTag(tag) { - A(this.tags).addObject(tag); - }, + @action + addTag(tag) { + A(this.tags).addObject(tag); + } - removeTag(tag) { - A(this.tags).removeObject(tag); - }, + @action + removeTag(tag) { + A(this.tags).removeObject(tag); + } - resetAll() { - this.resetQueryParams(); - }, + @action + resetAll() { + this.resetQueryParams(); + } - setDefaults() { - ['search', 'page', 'tags'].forEach(key => { - let value = key === 'tags' ? this.get(key).concat() : this.get(key); - this.setDefaultQueryParamValue(key, value); - }); - } + @action + setDefaults() { + ['search', 'page', 'tags'].forEach(key => { + let value = key === 'tags' ? this.get(key).concat() : this.get(key); + this.setDefaultQueryParamValue(key, value); + }); } -}); +} diff --git a/tests/unit/query-params-test.js b/tests/unit/query-params-test.js index 7ffc509..c0b6d2d 100644 --- a/tests/unit/query-params-test.js +++ b/tests/unit/query-params-test.js @@ -93,8 +93,6 @@ module('Unit | QueryParams', function(hooks) { assert.expect(1); let QP = new QueryParams({ foo: { defaultValue: 1 } }); - - // eslint-disable-next-line ember/avoid-leaking-state-in-ember-objects QP = QP.extend({ bar: { defaultValue: 1 } }, { baz: { defaultValue: 1 } }); assert.deepEqual(keys(QP.queryParams), ['foo', 'bar', 'baz']); diff --git a/tsconfig.json b/tsconfig.json index 24aa5b4..9286b98 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,11 +4,8 @@ "noEmit": true, "allowJs": true, "checkJs": true, - "types": [ - "ember" - ] + "experimentalDecorators": true, + "types": ["ember"] }, - "include": [ - "addon/**/*" - ] + "include": ["addon/**/*"] } diff --git a/yarn.lock b/yarn.lock index 25f286f..578633e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -64,6 +64,17 @@ "@babel/traverse" "^7.1.0" "@babel/types" "^7.0.0" +"@babel/helper-create-class-features-plugin@^7.2.3": + version "7.2.3" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.2.3.tgz#f6e719abb90cb7f4a69591e35fd5eb89047c4a7c" + integrity sha512-xO/3Gn+2C7/eOUeb0VRnSP1+yvWHNxlpAot1eMhtoKDCN7POsyQP5excuT5UsV5daHxMWBeIIOeI5cmB8vMRgQ== + dependencies: + "@babel/helper-function-name" "^7.1.0" + "@babel/helper-member-expression-to-functions" "^7.0.0" + "@babel/helper-optimise-call-expression" "^7.0.0" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-replace-supers" "^7.2.3" + "@babel/helper-define-map@^7.1.0": version "7.1.0" resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.1.0.tgz#3b74caec329b3c80c116290887c0dd9ae468c20c" @@ -160,7 +171,7 @@ "@babel/traverse" "^7.1.0" "@babel/types" "^7.0.0" -"@babel/helper-replace-supers@^7.1.0": +"@babel/helper-replace-supers@^7.1.0", "@babel/helper-replace-supers@^7.2.3": version "7.2.3" resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.2.3.tgz#19970020cf22677d62b3a689561dbd9644d8c5e5" integrity sha512-GyieIznGUfPXPWu0yLS6U55Mz67AZD9cUk0BfirOWlPrXlBcan9Gz+vHGz+cPfuoweZSnPzPIm67VtQM0OWZbA== @@ -227,6 +238,23 @@ "@babel/helper-remap-async-to-generator" "^7.1.0" "@babel/plugin-syntax-async-generators" "^7.2.0" +"@babel/plugin-proposal-class-properties@^7.1.0": + version "7.2.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.2.3.tgz#c9e1294363b346cff333007a92080f3203698461" + integrity sha512-FVuQngLoN2iDrpW7LmhPZ2sO4DJxf35FOcwidwB9Ru9tMvI5URthnkVHuG14IStV+TzkMTyLMoOUlSTtrdVwqw== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.2.3" + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-proposal-decorators@^7.1.2": + version "7.2.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.2.3.tgz#1fe5b0d22ce0c4418f225474ebd40267430364c0" + integrity sha512-jhCFm7ftmue02EWIYqbhzP0iConEPsgVQeDriOs/Qc2lgr6MDtHTTrv3hE2GOOQDFjQ9tjP7nWQq0ad0JhIsQg== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.2.3" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-syntax-decorators" "^7.2.0" + "@babel/plugin-proposal-json-strings@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.2.0.tgz#568ecc446c6148ae6b267f02551130891e29f317" @@ -267,6 +295,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" +"@babel/plugin-syntax-decorators@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.2.0.tgz#c50b1b957dcc69e4b1127b65e1c33eef61570c1b" + integrity sha512-38QdqVoXdHUQfTpZo3rQwqQdWtCn5tMv4uV6r2RMfTqNBuv4ZBhz79SfaQWKTVmxHjeFv/DnXVC/+agHCklYWA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-syntax-json-strings@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.2.0.tgz#72bd13f6ffe1d25938129d2a186b11fd62951470" @@ -591,6 +626,78 @@ lodash "^4.17.10" to-fast-properties "^2.0.0" +"@ember-decorators/babel-transforms@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@ember-decorators/babel-transforms/-/babel-transforms-4.0.0.tgz#a1358f5d185c3e4f722b5b30d5bae6e9884cae07" + integrity sha512-67bJ5EXwMYd96WiiloSsgcvZplSUKSKl20rmjFve3+AXWislTQbnTKjwMPNMjKeajEALwmC7if+EkQUoCqrUog== + dependencies: + "@babel/plugin-proposal-class-properties" "^7.1.0" + "@babel/plugin-proposal-decorators" "^7.1.2" + ember-cli-babel-plugin-helpers "^1.0.0" + ember-cli-version-checker "^2.1.0" + +"@ember-decorators/component@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@ember-decorators/component/-/component-4.0.0.tgz#fc9d7ad90bd5506fee71a444ddd6652f81281df2" + integrity sha512-Rusqy6ydTocKDp0lwJFNvIDPeFaVBsEf+J+0PEAYBM1DzK4yMKn9f4auOXoiijWmAR2hzG5h5uXmwHN8DB3XxA== + dependencies: + "@ember-decorators/utils" "^4.0.0" + ember-cli-babel "^7.1.3" + +"@ember-decorators/controller@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@ember-decorators/controller/-/controller-4.0.0.tgz#f2ca635083f8292586584f19728bfe04253f3170" + integrity sha512-OecGBMCiH3l1Vt7iZVXXT+zhDtf3Swqc9vYzGo8IooWW3RiwNoyzzl1RNE51TduFTjVubWjrUU6CEz2j1J8F9w== + dependencies: + "@ember-decorators/utils" "^4.0.0" + ember-cli-babel "^7.1.3" + +"@ember-decorators/data@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@ember-decorators/data/-/data-4.0.0.tgz#10d4cc780a7742c384bfe476d11fc55f024d2ab0" + integrity sha512-HJ2L2RJsufvQZ+2rvqCbaYUnTJc5e0p2nIfM/s83BOeXW9ZS9/U/cooZB+xEfB+pSgBjM1ZL4N7ICvb/RHs7aA== + dependencies: + "@ember-decorators/utils" "^4.0.0" + ember-cli-babel "^7.1.3" + +"@ember-decorators/object@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@ember-decorators/object/-/object-4.0.0.tgz#a7c220c83e45af79a12912f3589e5071bebf53c2" + integrity sha512-Bv7E9GsplnjRQ7MQPqTqmskOt8EedgfWeS7Dwy6B0pjW1NCQK3KDhcqU/UJ3yhCLIFdVlJCk8omlMreI+zMbnQ== + dependencies: + "@ember-decorators/utils" "^4.0.0" + ember-cli-babel "^7.1.3" + +"@ember-decorators/service@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@ember-decorators/service/-/service-4.0.0.tgz#62401e92c26fc06d102824004a10375b795ee24b" + integrity sha512-UnxVM8wdL0Ikxf8oizgA/Lh7c9nqabQ5q1lQyOI8Wvmv/ztuOtX1BDNykfjAG3f7XkcX6iOutoafseQucEI3Tw== + dependencies: + "@ember-decorators/utils" "^4.0.0" + ember-cli-babel "^7.1.3" + +"@ember-decorators/utils@^3.1.2": + version "3.1.5" + resolved "https://registry.yarnpkg.com/@ember-decorators/utils/-/utils-3.1.5.tgz#8f692582329a88553192e998012c7ca59e547c37" + integrity sha512-SqckJdUgnsZGZw6XHdRMeOk7YEdXHOZjJNOeJgNzjUdhS9jWSNu3OQrXcwYjJZBW8ICdqV87lpJB/rozTCezoQ== + dependencies: + babel-plugin-debug-macros "^0.1.11" + ember-cli-babel "^7.1.3" + ember-cli-version-checker "^2.1.2" + ember-compatibility-helpers "^1.1.2" + semver "^5.6.0" + +"@ember-decorators/utils@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@ember-decorators/utils/-/utils-4.0.0.tgz#f338e6e2e20bd781e708bf4214976f3520b7ffe3" + integrity sha512-uK8F9NjYO7TXH+egidJren/cZK+u111mu3W03lNUfrjbHOnKzIdMeb6JBgs5J1giQb8R2MurfyKW92xNJEhc1w== + dependencies: + babel-plugin-debug-macros "^0.1.11" + ember-cli-babel "^7.1.3" + ember-cli-version-checker "^2.1.2" + ember-compatibility-helpers "^1.1.2" + semver "^5.6.0" + "@ember/jquery@^0.5.2": version "0.5.2" resolved "https://registry.yarnpkg.com/@ember/jquery/-/jquery-0.5.2.tgz#fe312c03ada0022fa092d23f7cd7e2eb0374b53a" @@ -1701,7 +1808,7 @@ babel-plugin-dead-code-elimination@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/babel-plugin-dead-code-elimination/-/babel-plugin-dead-code-elimination-1.0.2.tgz#5f7c451274dcd7cccdbfbb3e0b85dd28121f0f65" -babel-plugin-debug-macros@^0.1.10: +babel-plugin-debug-macros@^0.1.10, babel-plugin-debug-macros@^0.1.11: version "0.1.11" resolved "https://registry.yarnpkg.com/babel-plugin-debug-macros/-/babel-plugin-debug-macros-0.1.11.tgz#6c562bf561fccd406ce14ab04f42c218cf956605" integrity sha512-hZw5qNNGAR02Y+yBUrtsnJHh8OXavkayPRqKGAXnIm4t5rWVpj3ArwsC7TWdpZsBguQvHAeyTxZ7s23yY60HHg== @@ -1714,7 +1821,7 @@ babel-plugin-debug-macros@^0.1.6: dependencies: semver "^5.3.0" -babel-plugin-debug-macros@^0.2.0-beta.6: +babel-plugin-debug-macros@^0.2.0, babel-plugin-debug-macros@^0.2.0-beta.6: version "0.2.0" resolved "https://registry.yarnpkg.com/babel-plugin-debug-macros/-/babel-plugin-debug-macros-0.2.0.tgz#0120ac20ce06ccc57bf493b667cf24b85c28da7a" integrity sha512-Wpmw4TbhR3Eq2t3W51eBAQSdKlr+uAyF0GI4GtPfMCD12Y4cIdpKC9l0RjNTH/P9isFypSqqewMPm7//fnZlNA== @@ -4429,6 +4536,11 @@ ember-cli-autoprefixer@^0.8.1: broccoli-autoprefixer "^5.0.0" lodash "^4.0.0" +ember-cli-babel-plugin-helpers@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/ember-cli-babel-plugin-helpers/-/ember-cli-babel-plugin-helpers-1.0.2.tgz#d4bec0f32febc530e621ea8d66d3365727cb5e6c" + integrity sha512-tTWmHiIvadgtu0i+Zlb5Jnue69qO6dtACcddkRhhV+m9NfAr+2XNoTKRSeGL8QyRDhfWeo4rsK9dqPrU4PQ+8g== + ember-cli-babel@^5.1.5, ember-cli-babel@^5.1.6: version "5.2.4" resolved "https://registry.yarnpkg.com/ember-cli-babel/-/ember-cli-babel-5.2.4.tgz#5ce4f46b08ed6f6d21e878619fb689719d6e8e13" @@ -4769,7 +4881,7 @@ ember-cli-version-checker@^1.0.2, ember-cli-version-checker@^1.2.0: dependencies: semver "^5.3.0" -ember-cli-version-checker@^2.0.0, ember-cli-version-checker@^2.1.0, ember-cli-version-checker@^2.1.2: +ember-cli-version-checker@^2.0.0, ember-cli-version-checker@^2.1.0, ember-cli-version-checker@^2.1.1, ember-cli-version-checker@^2.1.2: version "2.2.0" resolved "https://registry.yarnpkg.com/ember-cli-version-checker/-/ember-cli-version-checker-2.2.0.tgz#47771b731fe0962705e27c8199a9e3825709f3b3" integrity sha512-G+KtYIVlSOWGcNaTFHk76xR4GdzDLzAS4uxZUKdASuFX0KJE43C6DaqL+y3VTpUFLI2FIkAS6HZ4I1YBi+S3hg== @@ -4872,6 +4984,15 @@ ember-cli@~3.6.1: watch-detector "^0.1.0" yam "^0.0.24" +ember-compatibility-helpers@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/ember-compatibility-helpers/-/ember-compatibility-helpers-1.1.2.tgz#ae0ee4a7a2858b5ffdf79b428c23aee85c47d93d" + integrity sha512-yN163MzERpotO8M0b+q+kXs4i3Nx6aIriiZHWv+yXQzr2TAtYlVwg9V7/3+jcurOa3oDEYDpN7y9UZ6q3mnoTg== + dependencies: + babel-plugin-debug-macros "^0.2.0" + ember-cli-version-checker "^2.1.1" + semver "^5.4.1" + ember-composability-tools@0.0.9: version "0.0.9" resolved "https://registry.yarnpkg.com/ember-composability-tools/-/ember-composability-tools-0.0.9.tgz#b5e790cc337c6546b55fe2a793ca6da3791916bb" @@ -4889,6 +5010,14 @@ ember-composable-helpers@^2.1.0: broccoli-funnel "^1.0.1" ember-cli-babel "^6.6.0" +ember-concurrency-decorators@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/ember-concurrency-decorators/-/ember-concurrency-decorators-0.5.0.tgz#498c54ee1a6ab643f6be2161145b104cb737f8b0" + integrity sha512-zVkyqZd2qn+HqUhrilvdAy8s4ClRV2TIqOL/JU3b+QwXGbuDCEN1tA8CsLmpctKsY51gzqQlKKZbMS5X3tL8Kw== + dependencies: + "@ember-decorators/utils" "^3.1.2" + ember-cli-babel "^7.1.3" + ember-concurrency@^0.8.19, ember-concurrency@^0.8.26: version "0.8.26" resolved "https://registry.yarnpkg.com/ember-concurrency/-/ember-concurrency-0.8.26.tgz#7aeaa5c00e87903a57726823efe68787a83154b0" @@ -4906,6 +5035,19 @@ ember-css-transitions@^0.1.15: ember-cli-babel "^6.16.0" ember-cli-requestanimationframe-polyfill "^0.0.1" +ember-decorators@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/ember-decorators/-/ember-decorators-4.0.0.tgz#18357c209701aa318017d75f0f553844982c4d2e" + integrity sha512-n2ZPaim3QZSihghyKXM1UcijlOKBm/TY+GLyRuXU7IWzl0tA1pLtKdPUT5rP6CgeMBY8DC5M7os3+RQRv+oXWQ== + dependencies: + "@ember-decorators/component" "^4.0.0" + "@ember-decorators/controller" "^4.0.0" + "@ember-decorators/data" "^4.0.0" + "@ember-decorators/object" "^4.0.0" + "@ember-decorators/service" "^4.0.0" + ember-cli-babel "^7.1.3" + semver "^5.5.0" + ember-disable-prototype-extensions@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/ember-disable-prototype-extensions/-/ember-disable-prototype-extensions-1.1.3.tgz#1969135217654b5e278f9fe2d9d4e49b5720329e" @@ -10667,7 +10809,7 @@ semver@^4.3.1: version "4.3.6" resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.6.tgz#300bc6e0e86374f7ba61068b5b1ecd57fc6532da" -semver@^5.4.1, semver@^5.5.0: +semver@^5.4.1, semver@^5.5.0, semver@^5.6.0: version "5.6.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== From 9a5d2eddb886e53bc13ea10b5827cfb2918bbace Mon Sep 17 00:00:00 2001 From: offirgolan Date: Wed, 9 Jan 2019 15:26:05 -0800 Subject: [PATCH 2/2] add with-parachute, docs, and tests --- .eslintrc.js | 1 + README.md | 84 +++++++++++++++++++++++------- addon/decorators/index.js | 1 + addon/decorators/query-param.js | 48 ++++++++++------- addon/decorators/with-parachute.js | 12 +++++ package.json | 1 + tests/unit/decorators-test.js | 60 +++++++++++++++++++++ tests/unit/query-params-test.js | 7 --- 8 files changed, 169 insertions(+), 45 deletions(-) create mode 100644 addon/decorators/with-parachute.js create mode 100644 tests/unit/decorators-test.js diff --git a/.eslintrc.js b/.eslintrc.js index 5c5f466..ee2217c 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,5 +1,6 @@ module.exports = { root: true, + parser: 'babel-eslint', parserOptions: { ecmaVersion: 2017, sourceType: 'module' diff --git a/README.md b/README.md index 9db6212..d97045e 100644 --- a/README.md +++ b/README.md @@ -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: { @@ -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); @@ -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: @@ -267,7 +319,7 @@ const myQueryParams = new QueryParams({ /* ... */ }); -export default Ember.Controller.extend(myQueryParams.Mixin, { +export default Controller.extend(myQueryParams.Mixin, { // ... }); ``` @@ -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. @@ -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 ) { @@ -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() { diff --git a/addon/decorators/index.js b/addon/decorators/index.js index 2e92dd4..fccebf1 100644 --- a/addon/decorators/index.js +++ b/addon/decorators/index.js @@ -1 +1,2 @@ export { default as queryParam } from './query-param'; +export { default as withParachute } from './with-parachute'; diff --git a/addon/decorators/query-param.js b/addon/decorators/query-param.js index 29617f0..d46af58 100644 --- a/addon/decorators/query-param.js +++ b/addon/decorators/query-param.js @@ -3,30 +3,38 @@ import { getQueryParamsFor } from './-private/query-params-for'; -export default function queryParam(qpDefinition) { - return desc => { - qpDefinition = qpDefinition || {}; +function createDescriptor(desc, qpDefinition) { + qpDefinition = qpDefinition || {}; + + const descriptor = { + ...desc, + finisher(klass) { + addQueryParamFor(klass, desc.key, qpDefinition); + klass.reopen(getQueryParamsFor(klass).Mixin); + + return klass; + } + }; - const descriptor = { - ...desc, - finisher(klass) { - addQueryParamFor(klass, desc.key, qpDefinition); - klass.reopen(getQueryParamsFor(klass).Mixin); + if (desc.kind === 'field') { + if (typeof desc.initializer === 'function') { + qpDefinition.defaultValue = desc.initializer(); + } - return klass; - } + descriptor.initializer = function initializer() { + return qpDefinition.defaultValue; }; + } - if (desc.kind === 'field') { - if (typeof desc.initializer === 'function') { - qpDefinition.defaultValue = desc.initializer(); - } + return descriptor; +} - descriptor.initializer = function initializer() { - return qpDefinition.defaultValue; - }; - } +export default function queryParam(qpDefinition) { + // Handle `@queryParam` usage + if (`${qpDefinition}` === '[object Descriptor]') { + return createDescriptor(qpDefinition); + } - return descriptor; - }; + // Handle `@queryParam()` usage + return desc => createDescriptor(desc, qpDefinition); } diff --git a/addon/decorators/with-parachute.js b/addon/decorators/with-parachute.js new file mode 100644 index 0000000..331c122 --- /dev/null +++ b/addon/decorators/with-parachute.js @@ -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; + } + }; +} diff --git a/package.json b/package.json index d915364..78dc031 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "@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", diff --git a/tests/unit/decorators-test.js b/tests/unit/decorators-test.js new file mode 100644 index 0000000..46b234f --- /dev/null +++ b/tests/unit/decorators-test.js @@ -0,0 +1,60 @@ +import EmberObject from '@ember/object'; +import QueryParams from 'ember-parachute'; +import { withParachute, queryParam } from 'ember-parachute/decorators'; +import { module, test } from 'qunit'; + +@withParachute +class WithParachuteController extends EmberObject {} + +class QPController extends EmberObject { + @queryParam({ + as: 'dir', + refresh: true + }) + direction = 'asc'; + + @queryParam page = 1; + + @queryParam({ + refresh: true, + serialize() {}, + deserialize() {} + }) + color = []; +} + +module('Unit | Decorators', function() { + let controller; + + module('withParachute', function(hooks) { + hooks.beforeEach(function() { + controller = WithParachuteController.create(); + }); + + test('it works', function(assert) { + assert.ok(typeof controller.setup === 'function'); + assert.ok(typeof controller.reset === 'function'); + assert.ok(QueryParams.metaFor(controller)); + }); + }); + + module('queryParam', function(hooks) { + hooks.beforeEach(function() { + controller = QPController.create(); + }); + + test('it works', function(assert) { + const { queryParams, queryParamsArray } = QueryParams.metaFor(controller); + + assert.equal(queryParamsArray.length, 3); + assert.deepEqual(Object.keys(queryParams), [ + 'direction', + 'page', + 'color' + ]); + assert.equal(queryParams.direction.as, 'dir'); + assert.equal(queryParams.page.defaultValue, 1); + assert.ok(queryParams.color.serialize); + }); + }); +}); diff --git a/tests/unit/query-params-test.js b/tests/unit/query-params-test.js index c0b6d2d..0f99dc1 100644 --- a/tests/unit/query-params-test.js +++ b/tests/unit/query-params-test.js @@ -56,13 +56,6 @@ module('Unit | QueryParams', function(hooks) { controller = Controller.create(); }); - test('asserts', function(assert) { - assert.expect(2); - - assert.throws(() => new QueryParams({ foo: {} })); - assert.throws(() => QueryParams.queryParamsFor(EmberObject.create())); - }); - test('create', function(assert) { assert.expect(2);