Skip to content

Commit

Permalink
Allows customizing propery creation
Browse files Browse the repository at this point in the history
Fixes #905. A couple changes to make customizing property accessors easier.

* Adds static methods`setOptionsForProperty(name, options)`, `getOptionsForProperty` and renames `_requestUpdate` to `requestUpdateInternal` and makes it protected.

With these changes, users can override `createProperty` and (1) pass in and later retrieve custom property otions, (2) customize how accessors are defined and call `requestUpdateInternal` in the setter to make the property reactive.
  • Loading branch information
Steven Orvell committed Feb 29, 2020
1 parent 672e457 commit 26a85ed
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 9 deletions.
28 changes: 21 additions & 7 deletions src/lib/updating-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ export interface PropertyDeclaration<Type = unknown, TypeHint = unknown> {
* the property changes.
*/
readonly noAccessor?: boolean;

// Allows extension while preserving the ability to use the
// @property decorator.
[index: string]: any;
}

/**
Expand Down Expand Up @@ -293,8 +297,7 @@ export abstract class UpdatingElement extends HTMLElement {
// Note, since this can be called by the `@property` decorator which
// is called before `finalize`, we ensure storage exists for property
// metadata.
this._ensureClassProperties();
this._classProperties!.set(name, options);
this.setOptionsForProperty(name, options);
// Do not generate an accessor if the prototype already has one, since
// it would be lost otherwise and that would never be the user's intention;
// Instead, we expect users to call `requestUpdate` themselves from
Expand All @@ -313,13 +316,24 @@ export abstract class UpdatingElement extends HTMLElement {
const oldValue =
(this as {} as {[key: string]: unknown})[name as string];
(this as {} as {[key: string]: unknown})[key as string] = value;
(this as unknown as UpdatingElement)._requestUpdate(name, oldValue);
(this as unknown as UpdatingElement).requestUpdateInternal(name, oldValue);
},
configurable: true,
enumerable: true
});
}

static setOptionsForProperty(name: PropertyKey,
options: PropertyDeclaration = defaultPropertyDeclaration) {
this._ensureClassProperties();
this._classProperties!.set(name, options);
}

static getOptionsForProperty(name: PropertyKey) {
this._ensureClassProperties();
return this._classProperties!.get(name);
}

/**
* Creates property accessors for registered properties and ensures
* any superclasses are also finalized.
Expand Down Expand Up @@ -452,7 +466,7 @@ export abstract class UpdatingElement extends HTMLElement {
this._saveInstanceProperties();
// ensures first update will be caught by an early access of
// `updateComplete`
this._requestUpdate();
this.requestUpdateInternal();
}

/**
Expand Down Expand Up @@ -576,11 +590,11 @@ export abstract class UpdatingElement extends HTMLElement {
}

/**
* This private version of `requestUpdate` does not access or return the
* This protected version of `requestUpdate` does not access or return the
* `updateComplete` promise. This promise can be overridden and is therefore
* not free to access.
*/
private _requestUpdate(name?: PropertyKey, oldValue?: unknown) {
protected requestUpdateInternal(name?: PropertyKey, oldValue?: unknown) {
let shouldRequestUpdate = true;
// If we have a property key, perform property update steps.
if (name !== undefined) {
Expand Down Expand Up @@ -627,7 +641,7 @@ export abstract class UpdatingElement extends HTMLElement {
* @returns {Promise} A Promise that is resolved when the update completes.
*/
requestUpdate(name?: PropertyKey, oldValue?: unknown) {
this._requestUpdate(name, oldValue);
this.requestUpdateInternal(name, oldValue);
return this.updateComplete;
}

Expand Down
56 changes: 54 additions & 2 deletions src/test/lib/updating-element_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
* http://polymer.github.io/PATENTS.txt
*/

import {property} from '../../lib/decorators.js';
import {ComplexAttributeConverter, PropertyDeclarations, PropertyValues, UpdatingElement} from '../../lib/updating-element.js';
import {property, customElement} from '../../lib/decorators.js';
import {ComplexAttributeConverter, PropertyDeclarations, PropertyValues, UpdatingElement, PropertyDeclaration} from '../../lib/updating-element.js';
import {generateElementName} from '../test-helpers.js';

// tslint:disable:no-any ok in tests
Expand Down Expand Up @@ -1802,6 +1802,58 @@ suite('UpdatingElement', () => {
assert.equal(sub.getAttribute('foo'), '5');
});

test.only('can implement createProperty to customize property options and accessors', async () => {

interface MyPropertyDeclaration<TypeHint = unknown> extends PropertyDeclaration {
validator?: (value: any) => TypeHint;
}

@customElement(generateElementName())
class E extends UpdatingElement {

static createProperty(
name: PropertyKey,
options: MyPropertyDeclaration) {
this.setOptionsForProperty(name, options);
if (options.noAccessor || this.prototype.hasOwnProperty(name)) {
return;
}
const key = typeof name === 'symbol' ? Symbol() : `__${name}`;
Object.defineProperty(this.prototype, name, {
// tslint:disable-next-line:no-any no symbol in index
get(): any {
return (this as {[key: string]: unknown})[key as string];
},
set(this: E, value: unknown) {
const oldValue =
(this as {} as {[key: string]: unknown})[name as string];
if (options.validator) {
value = options.validator(value);
}
(this as {} as {[key: string]: unknown})[key as string] = value;
(this as unknown as E).requestUpdateInternal(name, oldValue);
},
configurable: true,
enumerable: true
});
}

@property({type: Number, validator: (value: number) => Math.min(10, Math.max(value, 0))})
foo = 5;

@property({})
bar = 'bar';
}

const el = new E();
container.appendChild(el);
await el.updateComplete;
el.foo = 20;
assert.equal(el.foo, 10);
el.foo = -5;
assert.equal(el.foo, 0);
});

test('attribute-based property storage', async () => {
class E extends UpdatingElement {
_updateCount = 0;
Expand Down

0 comments on commit 26a85ed

Please sign in to comment.