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 new file mode 100644 index 000000000..61978ee06 --- /dev/null +++ b/addons/core/addon/decorators/resource-filter-param.js @@ -0,0 +1,86 @@ +/** + * 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`. An optional default value may be specified + * via `defaultValue` passed to the decorator. + * + * @see ResourceFilterParamHelper + * + * @example + * + * export default class MyRoute extends Route { + * + * @resourceFilterParam(['active', 'pending'], ['active']) + * 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`. + * log(this.status); + * } + * } + * + */ +export function resourceFilterParam(allowedValues, defaultValue) { + /** + * @param {object} target + * @param {string} name + * @param {object} descriptor + * @return {object{get, set}} + */ + return function (target, name /*, descriptor*/) { + 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 = {}; + + // 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[filterKey] = { + refreshModel: true, + replace: true, + }; + + // Store the allowed values for future lookup + // (via `ResourceFilterParamHelper`). + target[filterAllowedValuesKey] = allowedValues; + + // Override the decorated attribute with a getter and setter. + return { + /** + * Returns the JSON-parsed query parameter value OR defaultValue. + */ + get() { + const value = + this.paramsFor(this.routeName)[filterKey] || + JSON.stringify(defaultValue); + 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[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 new file mode 100644 index 000000000..e78ac20a7 --- /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('routeDidChange', this.routeDidChange); + } + + /** + * Stop listening to route events when this helper is destroyed. + */ + willDestroy() { + this.router.off('routeDidChange', this.routeDidChange); + 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 filterAllowedValuesKey = `filter-allowed-values-${name}`; + const allowedValues = route[filterAllowedValuesKey]; + // Selected filters + const rawValue = route[name]; + const selectedValue = rawValue || null; + return { name, allowedValues, selectedValue }; + } + + // =actions + + /** + * Triggers a recompute (see above) when the route changes, especially when + * route query params change. + */ + @action + routeDidChange() { + this.recompute(); + } +} 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'; 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';