Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions src/widget-core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,34 @@ class Hello extends WidgetBase<MyProperties> {

New properties are compared with the previous properties to determine if a widget requires re-rendering. By default Dojo uses the `auto` diffing strategy, that performs a shallow comparison for objects and arrays, ignores functions (except classes that extend `WidgetBase`) and a reference comparison for all other values.

#### Internal Widget State

It is common for widgets to maintain internal state, that directly affects the results of the render output or passed as properties to child widgets. The most common pattern is that an action (often user initiated via an event) occurs which updates the internal state leaving the user to manually call `this.invalidate()` to trigger a re-render.

For class properties that always need to trigger a re-render when they're updated, a property decorator, `@watch` can be used, which implicitly calls `this.invalidate` each time the property is set.

```ts
import WidgetBase from '@dojo/framework/widget-core/WidgetBase';
import watch from '@dojo/framework/widget-core/decorators/watch';

class Counter extends WidgetBase {

@watch()
private _count = 0;

private _increment() {
this._count = this._count + 1; // this automatically calls invalidate
}

protected render() {
return v('div', [
v('button', { onclick: this._increment }, [ 'Increment' ]),
`${this._count}`)
]);
}
}
```

#### Composing Widgets

As mentioned, often widgets are composed of other widgets in their `render` output. This promotes widget reuse across an application (or multiple applications) and promotes widget best practices.
Expand Down
20 changes: 20 additions & 0 deletions src/widget-core/decorators/watch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import handleDecorator, { DecoratorHandler } from './handleDecorator';

export function watch(): DecoratorHandler {
return handleDecorator((target, propertyKey) => {
target.addDecorator('afterConstructor', function(this: any) {
if (propertyKey) {
let _value: any = this[propertyKey];
Object.defineProperty(this, propertyKey, {
set(value: any) {
_value = value;
this.invalidate();
},
get() {
return _value;
}
});
}
});
});
}
1 change: 1 addition & 0 deletions tests/widget-core/unit/decorators/all.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ import './customElement';
import './diffProperty';
import './inject';
import './registry';
import './watch';
28 changes: 28 additions & 0 deletions tests/widget-core/unit/decorators/watch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const { describe, it } = intern.getInterface('bdd');
const { assert } = intern.getPlugin('chai');
import WidgetBase from '../../../../src/widget-core/WidgetBase';
import { watch } from '../../../../src/widget-core/decorators/watch';
import { VNode } from '../../../../src/widget-core/interfaces';

describe('Watch', () => {
it('should invalidate on set', () => {
let invalidateCount = 0;
class A extends WidgetBase {
@watch() private _a: string;

invalidate() {
invalidateCount++;
}

render() {
this._a = 'other';
return this._a;
}
}

const widget = new A();
const result = widget.__render__() as VNode;
assert.strictEqual(result.text, 'other');
assert.strictEqual(invalidateCount, 1);
});
});