From 84511ba6d42b82c43589eff7de5462ecb0ab8a5f Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Tue, 9 Nov 2021 20:09:23 -0500 Subject: [PATCH 1/9] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20initial=20resource?= =?UTF-8?q?=20filter=20param=20system?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../addon/decorators/resource-filter-param.js | 83 +++++++++++++++++++ .../addon/helpers/resource-filter-param.js | 73 ++++++++++++++++ .../core/app/helpers/resource-filter-param.js | 1 + 3 files changed, 157 insertions(+) create mode 100644 addons/core/addon/decorators/resource-filter-param.js create mode 100644 addons/core/addon/helpers/resource-filter-param.js create mode 100644 addons/core/app/helpers/resource-filter-param.js diff --git a/addons/core/addon/decorators/resource-filter-param.js b/addons/core/addon/decorators/resource-filter-param.js new file mode 100644 index 000000000..db80bd832 --- /dev/null +++ b/addons/core/addon/decorators/resource-filter-param.js @@ -0,0 +1,83 @@ +/** + * A resource filter param is a concise way to express a route query parameter. + * Resource filter params abstract away a route's query parameter + * interface, treating a query param as a simple attribute on a route instance. + * Getting the attribute value is equivalent to looking up the associated query + * parameter. Setting the attribute is equivalent to transitioning to the route + * and passing the associated query parameter value. + * + * Additionally, resource filter params may declare `allowedValues`, an array + * containing the set of values the parameter may take. While unenforced, + * `allowedValues` are useful metadata about a resource filter and may be fetch + * via `ResourceFilterParamHelper`. + * + * @see ResourceFilterParamHelper + * + * @example + * + * export default class ScopesScopeAuthMethodsRoute extends Route { + * + * @resourceFilterParam(['active', 'pending']) + * status; + * + * @action + * changeStatus(status) { + * // Sets query param `filter-status` on the current route, which occurs + * // via a route transition. + * this.status = status; + * // Log the value of query param `filter-status`. + * console.log(this.status); + * } + * } + * + */ +export function resourceFilterParam(allowedValues) { + /** + * @param {object} target + * @param {string} name + * @param {object} descriptor + * @return {object{get, set}} + */ + return function (target, name /*, descriptor*/) { + const filterName = `filter-${name}`; + const filterOptionsName = `filter-options-${name}`; + + // If the route has no query params specified yet, declare them. + if (!Object.keys(target.queryParams).length) target.queryParams = {}; + + // Add the resource filter query parameter to the route. Resource filter + // param changes refresh the model but do not contribute to browser history. + // See https://guides.emberjs.com/release/routing/query-params/ + target.queryParams[filterName] = { + refreshModel: true, + replace: true, + }; + + // Store the allowed values for future lookup + // (via `ResourceFilterParamHelper`). + target[filterOptionsName] = allowedValues; + + // Override the decorated attribute with a getter and setter. + return { + /** + * Returns the JSON-parsed query parameter value. + */ + get() { + const value = this.currentRoute.queryParams[filterName]; + return value ? JSON.parse(value) : null; + }, + + /** + * Sets the resource filter param to the stringified representation + * of `value`. NOTE: this is equivalent to transitioning into the + * current route, changing the associated query param. + * @param value + */ + set(value) { + const queryParams = {}; + queryParams[filterName] = JSON.stringify(value); + this.transitionTo({ queryParams }); + }, + }; + }; +} diff --git a/addons/core/addon/helpers/resource-filter-param.js b/addons/core/addon/helpers/resource-filter-param.js new file mode 100644 index 000000000..98eb791bf --- /dev/null +++ b/addons/core/addon/helpers/resource-filter-param.js @@ -0,0 +1,73 @@ +import Helper from '@ember/component/helper'; +import { inject as service } from '@ember/service'; +import { getOwner } from '@ember/application'; +import { action } from '@ember/object'; + +/** + * Use this helper to fetch the current value of a resource filter param, + * as well as its allowedValues. + * + * @see resourceFilterParam (decorator) + * + * @example + * + * {{#with resource-filter-param 'status' as |param|}} + * {{param.name}} / {{param.value}} / {{param.selectedValue}} + * {{/with}} + */ +export default class ResourceFilterParamHelper extends Helper { + // =services + + @service router; + + // =methods + + // =lifecycle management methods + + /** + * Recompute this helper when the route changes. + */ + constructor() { + super(...arguments); + this.router.on('routeWillChange', this.routeWillChange); + } + + /** + * Stop listening to route events when this helper is destroyed. + */ + willDestroy() { + this.router.off('routeWillChange', this.routeWillChange); + super.willDestroy(); + } + + // =compute method + + /** + * Returns an object containing `name`, `value`, and `selectedValue`. + * `name` - The name of the resource filter param. + * `value` - The value of the resource filter param + * @return {object} + */ + compute([routeName, name]) { + const owner = getOwner(this); + // Filter options + const route = owner.lookup(`route:${routeName}`); + const filterOptionsName = `filter-options-${name}`; + const value = route[filterOptionsName]; + // Selected filters + const rawValue = route[name]; + const selectedValue = rawValue ? JSON.parse(rawValue) : null; + return { name, value, selectedValue }; + } + + // =actions + + /** + * Triggers a recompute (see above) when the route changes, especially when + * route query params change. + */ + @action + routeWillChange() { + this.recompute(); + } +} diff --git a/addons/core/app/helpers/resource-filter-param.js b/addons/core/app/helpers/resource-filter-param.js new file mode 100644 index 000000000..8977c55f0 --- /dev/null +++ b/addons/core/app/helpers/resource-filter-param.js @@ -0,0 +1 @@ +export { default } from 'core/helpers/resource-filter-param'; From 9ae05e3cc4e736344f8a1765210f40fc3129f56d Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Wed, 10 Nov 2021 09:12:02 -0500 Subject: [PATCH 2/9] chore: add support for defaultValue --- .../core/addon/decorators/resource-filter-param.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/addons/core/addon/decorators/resource-filter-param.js b/addons/core/addon/decorators/resource-filter-param.js index db80bd832..2eeb3bf76 100644 --- a/addons/core/addon/decorators/resource-filter-param.js +++ b/addons/core/addon/decorators/resource-filter-param.js @@ -9,7 +9,8 @@ * Additionally, resource filter params may declare `allowedValues`, an array * containing the set of values the parameter may take. While unenforced, * `allowedValues` are useful metadata about a resource filter and may be fetch - * via `ResourceFilterParamHelper`. + * via `ResourceFilterParamHelper`. An optional default value may be specified + * via `defaultValue` passed to the decorator. * * @see ResourceFilterParamHelper * @@ -17,7 +18,7 @@ * * export default class ScopesScopeAuthMethodsRoute extends Route { * - * @resourceFilterParam(['active', 'pending']) + * @resourceFilterParam(['active', 'pending'], ['active']) * status; * * @action @@ -31,7 +32,7 @@ * } * */ -export function resourceFilterParam(allowedValues) { +export function resourceFilterParam(allowedValues, defaultValue) { /** * @param {object} target * @param {string} name @@ -55,16 +56,16 @@ export function resourceFilterParam(allowedValues) { // Store the allowed values for future lookup // (via `ResourceFilterParamHelper`). - target[filterOptionsName] = allowedValues; + target[filterOptionsName] = allowedValue; // Override the decorated attribute with a getter and setter. return { /** - * Returns the JSON-parsed query parameter value. + * Returns the JSON-parsed query parameter value OR defaultValue. */ get() { const value = this.currentRoute.queryParams[filterName]; - return value ? JSON.parse(value) : null; + return value ? JSON.parse(value) : defaultValue || null; }, /** From 4472b17fc5c02676b81aa4a9056e1b67f37198f9 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Wed, 10 Nov 2021 09:31:59 -0500 Subject: [PATCH 3/9] chore: add export --- addons/core/app/decorators/resource-filter-param.js | 1 + 1 file changed, 1 insertion(+) create mode 100644 addons/core/app/decorators/resource-filter-param.js diff --git a/addons/core/app/decorators/resource-filter-param.js b/addons/core/app/decorators/resource-filter-param.js new file mode 100644 index 000000000..4e5cebc3c --- /dev/null +++ b/addons/core/app/decorators/resource-filter-param.js @@ -0,0 +1 @@ +export { default } from 'core/decorators/resource-filter-param'; From 4f8ea30d9dd61df42aa60bc690c362e7ae0e9b56 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Wed, 10 Nov 2021 09:35:19 -0500 Subject: [PATCH 4/9] fix: bad variable name --- addons/core/addon/decorators/resource-filter-param.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/core/addon/decorators/resource-filter-param.js b/addons/core/addon/decorators/resource-filter-param.js index 2eeb3bf76..3815c94bb 100644 --- a/addons/core/addon/decorators/resource-filter-param.js +++ b/addons/core/addon/decorators/resource-filter-param.js @@ -56,7 +56,7 @@ export function resourceFilterParam(allowedValues, defaultValue) { // Store the allowed values for future lookup // (via `ResourceFilterParamHelper`). - target[filterOptionsName] = allowedValue; + target[filterOptionsName] = allowedValues; // Override the decorated attribute with a getter and setter. return { From 1562f0ff073d7664f583386fa961d0384d275b25 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Wed, 10 Nov 2021 09:37:46 -0500 Subject: [PATCH 5/9] fix: router --- addons/core/addon/decorators/resource-filter-param.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/core/addon/decorators/resource-filter-param.js b/addons/core/addon/decorators/resource-filter-param.js index 3815c94bb..3312ed744 100644 --- a/addons/core/addon/decorators/resource-filter-param.js +++ b/addons/core/addon/decorators/resource-filter-param.js @@ -64,7 +64,7 @@ export function resourceFilterParam(allowedValues, defaultValue) { * Returns the JSON-parsed query parameter value OR defaultValue. */ get() { - const value = this.currentRoute.queryParams[filterName]; + const value = this.router.currentRoute.queryParams[filterName]; return value ? JSON.parse(value) : defaultValue || null; }, From c1b60c634e11071277f36a4f50b0882bc7f4dbef Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Wed, 10 Nov 2021 09:45:53 -0500 Subject: [PATCH 6/9] fix: currentroute again --- addons/core/addon/decorators/resource-filter-param.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/core/addon/decorators/resource-filter-param.js b/addons/core/addon/decorators/resource-filter-param.js index 3312ed744..d87e00e4f 100644 --- a/addons/core/addon/decorators/resource-filter-param.js +++ b/addons/core/addon/decorators/resource-filter-param.js @@ -64,7 +64,7 @@ export function resourceFilterParam(allowedValues, defaultValue) { * Returns the JSON-parsed query parameter value OR defaultValue. */ get() { - const value = this.router.currentRoute.queryParams[filterName]; + const value = this._router.currentRoute.queryParams[filterName]; return value ? JSON.parse(value) : defaultValue || null; }, From dca54b2040a44b59a3888915a8a64015dbcc0ab2 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Wed, 10 Nov 2021 09:59:28 -0500 Subject: [PATCH 7/9] fix: key names --- .../core/addon/decorators/resource-filter-param.js | 12 ++++++------ addons/core/addon/helpers/resource-filter-param.js | 14 ++++++++++---- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/addons/core/addon/decorators/resource-filter-param.js b/addons/core/addon/decorators/resource-filter-param.js index d87e00e4f..fda969722 100644 --- a/addons/core/addon/decorators/resource-filter-param.js +++ b/addons/core/addon/decorators/resource-filter-param.js @@ -40,8 +40,8 @@ export function resourceFilterParam(allowedValues, defaultValue) { * @return {object{get, set}} */ return function (target, name /*, descriptor*/) { - const filterName = `filter-${name}`; - const filterOptionsName = `filter-options-${name}`; + const filterKey = `filter-${name}`; + const filterAllowedValuesKey = `filter-allowed-values-${name}`; // If the route has no query params specified yet, declare them. if (!Object.keys(target.queryParams).length) target.queryParams = {}; @@ -49,14 +49,14 @@ export function resourceFilterParam(allowedValues, defaultValue) { // Add the resource filter query parameter to the route. Resource filter // param changes refresh the model but do not contribute to browser history. // See https://guides.emberjs.com/release/routing/query-params/ - target.queryParams[filterName] = { + target.queryParams[filterKey] = { refreshModel: true, replace: true, }; // Store the allowed values for future lookup // (via `ResourceFilterParamHelper`). - target[filterOptionsName] = allowedValues; + target[filterAllowedValuesKey] = allowedValues; // Override the decorated attribute with a getter and setter. return { @@ -64,7 +64,7 @@ export function resourceFilterParam(allowedValues, defaultValue) { * Returns the JSON-parsed query parameter value OR defaultValue. */ get() { - const value = this._router.currentRoute.queryParams[filterName]; + const value = this._router.currentRoute.queryParams[filterKey]; return value ? JSON.parse(value) : defaultValue || null; }, @@ -76,7 +76,7 @@ export function resourceFilterParam(allowedValues, defaultValue) { */ set(value) { const queryParams = {}; - queryParams[filterName] = JSON.stringify(value); + queryParams[filterKey] = JSON.stringify(value); this.transitionTo({ queryParams }); }, }; diff --git a/addons/core/addon/helpers/resource-filter-param.js b/addons/core/addon/helpers/resource-filter-param.js index 98eb791bf..5cf4e40d1 100644 --- a/addons/core/addon/helpers/resource-filter-param.js +++ b/addons/core/addon/helpers/resource-filter-param.js @@ -52,12 +52,18 @@ export default class ResourceFilterParamHelper extends Helper { const owner = getOwner(this); // Filter options const route = owner.lookup(`route:${routeName}`); - const filterOptionsName = `filter-options-${name}`; - const value = route[filterOptionsName]; + const filterAllowedValuesKey = `filter-allowed-values-${name}`; + const allowedValues = route[filterAllowedValuesKey]; // Selected filters const rawValue = route[name]; - const selectedValue = rawValue ? JSON.parse(rawValue) : null; - return { name, value, selectedValue }; + let value = rawValue; + try { + value = JSON.parse(rawValue); + } catch (e) { + /* ignore parse errors */ + } + const selectedValue = rawValue ? value : null; + return { name, allowedValues, selectedValue }; } // =actions From f21627a9ba9e32766650e2a5f68ebc8fe0a7ade3 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Wed, 10 Nov 2021 10:38:16 -0500 Subject: [PATCH 8/9] fix: value handling and helper recomputation --- addons/api/addon/services/resource-filter-store.js | 2 +- .../core/addon/decorators/resource-filter-param.js | 8 +++++--- addons/core/addon/helpers/resource-filter-param.js | 14 ++++---------- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/addons/api/addon/services/resource-filter-store.js b/addons/api/addon/services/resource-filter-store.js index 94d934359..8bd4130ae 100644 --- a/addons/api/addon/services/resource-filter-store.js +++ b/addons/api/addon/services/resource-filter-store.js @@ -10,7 +10,7 @@ import { inject as service } from '@ember/service'; * type: 'oidc', * status: ['active', 'pending'] * }); - * console.log(filter.queryExpression); + * log(filter.queryExpression); * // ("/item/type" == "oidc") and ("/item/status" == "active" or "/item/status" == "pending") */ export class ResourceFilter { diff --git a/addons/core/addon/decorators/resource-filter-param.js b/addons/core/addon/decorators/resource-filter-param.js index fda969722..bf48c2d4b 100644 --- a/addons/core/addon/decorators/resource-filter-param.js +++ b/addons/core/addon/decorators/resource-filter-param.js @@ -27,7 +27,7 @@ * // via a route transition. * this.status = status; * // Log the value of query param `filter-status`. - * console.log(this.status); + * log(this.status); * } * } * @@ -64,8 +64,10 @@ export function resourceFilterParam(allowedValues, defaultValue) { * Returns the JSON-parsed query parameter value OR defaultValue. */ get() { - const value = this._router.currentRoute.queryParams[filterKey]; - return value ? JSON.parse(value) : defaultValue || null; + const value = + this._router.currentRoute.queryParams[filterKey] || + JSON.stringify(defaultValue); + return value ? JSON.parse(value) : null; }, /** diff --git a/addons/core/addon/helpers/resource-filter-param.js b/addons/core/addon/helpers/resource-filter-param.js index 5cf4e40d1..e78ac20a7 100644 --- a/addons/core/addon/helpers/resource-filter-param.js +++ b/addons/core/addon/helpers/resource-filter-param.js @@ -29,14 +29,14 @@ export default class ResourceFilterParamHelper extends Helper { */ constructor() { super(...arguments); - this.router.on('routeWillChange', this.routeWillChange); + this.router.on('routeDidChange', this.routeDidChange); } /** * Stop listening to route events when this helper is destroyed. */ willDestroy() { - this.router.off('routeWillChange', this.routeWillChange); + this.router.off('routeDidChange', this.routeDidChange); super.willDestroy(); } @@ -56,13 +56,7 @@ export default class ResourceFilterParamHelper extends Helper { const allowedValues = route[filterAllowedValuesKey]; // Selected filters const rawValue = route[name]; - let value = rawValue; - try { - value = JSON.parse(rawValue); - } catch (e) { - /* ignore parse errors */ - } - const selectedValue = rawValue ? value : null; + const selectedValue = rawValue || null; return { name, allowedValues, selectedValue }; } @@ -73,7 +67,7 @@ export default class ResourceFilterParamHelper extends Helper { * route query params change. */ @action - routeWillChange() { + routeDidChange() { this.recompute(); } } From 80a82ea925e5fcdf40d54658be7aa55432866812 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Wed, 10 Nov 2021 11:01:00 -0500 Subject: [PATCH 9/9] fix: use paramsFor --- addons/core/addon/decorators/resource-filter-param.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/core/addon/decorators/resource-filter-param.js b/addons/core/addon/decorators/resource-filter-param.js index bf48c2d4b..61978ee06 100644 --- a/addons/core/addon/decorators/resource-filter-param.js +++ b/addons/core/addon/decorators/resource-filter-param.js @@ -16,7 +16,7 @@ * * @example * - * export default class ScopesScopeAuthMethodsRoute extends Route { + * export default class MyRoute extends Route { * * @resourceFilterParam(['active', 'pending'], ['active']) * status; @@ -65,7 +65,7 @@ export function resourceFilterParam(allowedValues, defaultValue) { */ get() { const value = - this._router.currentRoute.queryParams[filterKey] || + this.paramsFor(this.routeName)[filterKey] || JSON.stringify(defaultValue); return value ? JSON.parse(value) : null; },