@thi.ng/atom
Advanced tools
Comparing version 1.0.3 to 1.1.0
@@ -24,3 +24,3 @@ import * as api from "@thi.ng/api/api"; | ||
export interface IViewable { | ||
addView<T>(path: Path, tx?: ViewTransform<T>): IView<T>; | ||
addView<T>(path: Path, tx?: ViewTransform<T>, lazy?: boolean): IView<T>; | ||
} |
@@ -21,4 +21,4 @@ import { IEquiv, Watch } from "@thi.ng/api/api"; | ||
notifyWatches(oldState: T, newState: T): void; | ||
addView<V>(path: Path, tx?: ViewTransform<V>): IView<V>; | ||
addView<V>(path: Path, tx?: ViewTransform<V>, lazy?: boolean): IView<V>; | ||
release(): boolean; | ||
} |
@@ -64,4 +64,4 @@ "use strict"; | ||
notifyWatches(oldState, newState) { } | ||
addView(path, tx) { | ||
return new view_1.View(this, path, tx); | ||
addView(path, tx, lazy = true) { | ||
return new view_1.View(this, path, tx, lazy); | ||
} | ||
@@ -68,0 +68,0 @@ release() { |
@@ -6,2 +6,13 @@ # Change Log | ||
<a name="1.1.0"></a> | ||
# [1.1.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/atom@1.0.3...@thi.ng/atom@1.1.0) (2018-03-18) | ||
### Features | ||
* **atom:** add optional support for eager views, update tests/readme ([c0ec274](https://github.com/thi-ng/umbrella/commit/c0ec274)) | ||
<a name="1.0.3"></a> | ||
@@ -8,0 +19,0 @@ ## [1.0.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/atom@1.0.2...@thi.ng/atom@1.0.3) (2018-03-18) |
@@ -23,3 +23,3 @@ import { IID, IRelease, Watch } from "@thi.ng/api/api"; | ||
notifyWatches(oldState: T, newState: T): void; | ||
addView<V>(path: Path, tx?: ViewTransform<V>): IView<V>; | ||
addView<V>(path: Path, tx?: ViewTransform<V>, lazy?: boolean): IView<V>; | ||
} |
@@ -78,4 +78,4 @@ "use strict"; | ||
} | ||
addView(path, tx) { | ||
return new view_1.View(this, path, tx); | ||
addView(path, tx, lazy = true) { | ||
return new view_1.View(this, path, tx, lazy); | ||
} | ||
@@ -82,0 +82,0 @@ } |
@@ -103,4 +103,4 @@ import { Predicate2, Watch } from "@thi.ng/api/api"; | ||
notifyWatches(oldState: T, newState: T): any; | ||
addView<V>(path: Path, tx?: ViewTransform<V>): IView<V>; | ||
addView<V>(path: Path, tx?: ViewTransform<V>, lazy?: boolean): IView<V>; | ||
release(): boolean; | ||
} |
@@ -154,4 +154,4 @@ "use strict"; | ||
} | ||
addView(path, tx) { | ||
return new view_1.View(this, path, tx); | ||
addView(path, tx, lazy = true) { | ||
return new view_1.View(this, path, tx, lazy); | ||
} | ||
@@ -158,0 +158,0 @@ release() { |
{ | ||
"name": "@thi.ng/atom", | ||
"version": "1.0.3", | ||
"version": "1.1.0", | ||
"description": "Mutable wrapper for immutable values", | ||
@@ -5,0 +5,0 @@ "main": "./index.js", |
@@ -203,2 +203,37 @@ # @thi.ng/atom | ||
Since v1.1.0 views can also be configured to be eager, instead of the | ||
"lazy" default behavior. If the optional `lazy` arg is true (default), | ||
the view's transformer will only be executed with the first `deref()` | ||
after each value change. If `lazy` is false, the transformer function | ||
will be executed immediately after a value change occurred and so can be | ||
used like a selective watch which only triggers if there was an actual | ||
value change (in contrast to normal watches, which execute with each | ||
update, regardless of value change). | ||
Related, the actual value change predicate can be customized. If not | ||
given, the default `@thi.ng/api/equiv` will be used. | ||
```typescript | ||
let x; | ||
let a = new Atom({value: 1}) | ||
// create an eager view by passing `false` as last arg | ||
view = a.addView("value", (y) => (x = y, y * 10), false); | ||
// check `x` to verify that transformer already has run | ||
x === 1 | ||
// true | ||
// reset x | ||
x = null | ||
// verify transformed value | ||
view.deref() === 10 | ||
// true | ||
// verify transformer hasn't rerun because of deref() | ||
x === null | ||
// true | ||
``` | ||
Atoms & views are useful tools for keeping state outside UI components. Here's | ||
@@ -205,0 +240,0 @@ an example of a tiny |
@@ -14,2 +14,13 @@ import { equiv as _equiv } from "@thi.ng/api/equiv"; | ||
* | ||
* If the optional `lazy` is true (default), the transformer will only | ||
* be executed with the first `deref()` after each value change. If | ||
* `lazy` is false, the transformer function will be executed | ||
* immediately after a value change occurred and so can be used like a | ||
* watch which only triggers if there was an actual value change (in | ||
* contrast to normal watches, which execute with each update, | ||
* regardless of value change). | ||
* | ||
* Related, the actual value change predicate can be customized. If not | ||
* given, the default `@thi.ng/api/equiv` will be used. | ||
* | ||
* ``` | ||
@@ -44,4 +55,15 @@ * a = new Atom({a: {b: 1}}); | ||
protected isDirty: boolean; | ||
constructor(parent: ReadonlyAtom<any>, path: Path, tx?: ViewTransform<T>, equiv?: typeof _equiv); | ||
protected isLazy: boolean; | ||
constructor(parent: ReadonlyAtom<any>, path: Path, tx?: ViewTransform<T>, lazy?: boolean, equiv?: typeof _equiv); | ||
/** | ||
* Returns view's value. If the view has a transformer, the | ||
* transformed value is returned. The transformer is only run once | ||
* per value change. See class comments about difference between | ||
* lazy/eager behaviors. | ||
*/ | ||
deref(): T; | ||
/** | ||
* Returns true, if the view's value has changed since last | ||
* `deref()`. | ||
*/ | ||
changed(): boolean; | ||
@@ -59,3 +81,7 @@ /** | ||
view(): T; | ||
/** | ||
* Disconnects this view from parent state, marks itself | ||
* dirty/changed and sets its unprocessed raw value to `undefined`. | ||
*/ | ||
release(): boolean; | ||
} |
50
view.js
@@ -15,2 +15,13 @@ "use strict"; | ||
* | ||
* If the optional `lazy` is true (default), the transformer will only | ||
* be executed with the first `deref()` after each value change. If | ||
* `lazy` is false, the transformer function will be executed | ||
* immediately after a value change occurred and so can be used like a | ||
* watch which only triggers if there was an actual value change (in | ||
* contrast to normal watches, which execute with each update, | ||
* regardless of value change). | ||
* | ||
* Related, the actual value change predicate can be customized. If not | ||
* given, the default `@thi.ng/api/equiv` will be used. | ||
* | ||
* ``` | ||
@@ -37,3 +48,3 @@ * a = new Atom({a: {b: 1}}); | ||
class View { | ||
constructor(parent, path, tx, equiv = equiv_1.equiv) { | ||
constructor(parent, path, tx, lazy = true, equiv = equiv_1.equiv) { | ||
this.parent = parent; | ||
@@ -44,5 +55,10 @@ this.id = `view-${View.NEXT_ID++}`; | ||
this.isDirty = true; | ||
this.isLazy = lazy; | ||
const lookup = paths_1.getter(this.path); | ||
const state = this.parent.deref(); | ||
this.unprocessed = state ? lookup(state) : undefined; | ||
if (!lazy) { | ||
this.state = this.tx(this.unprocessed); | ||
this.unprocessed = undefined; | ||
} | ||
parent.addWatch(this.id, (_, prev, curr) => { | ||
@@ -52,3 +68,8 @@ const pval = prev ? lookup(prev) : prev; | ||
if (!equiv(val, pval)) { | ||
this.unprocessed = val; | ||
if (lazy) { | ||
this.unprocessed = val; | ||
} | ||
else { | ||
this.state = this.tx(val); | ||
} | ||
this.isDirty = true; | ||
@@ -58,6 +79,14 @@ } | ||
} | ||
/** | ||
* Returns view's value. If the view has a transformer, the | ||
* transformed value is returned. The transformer is only run once | ||
* per value change. See class comments about difference between | ||
* lazy/eager behaviors. | ||
*/ | ||
deref() { | ||
if (this.isDirty) { | ||
this.state = this.tx(this.unprocessed); | ||
this.unprocessed = undefined; | ||
if (this.isLazy) { | ||
this.state = this.tx(this.unprocessed); | ||
this.unprocessed = undefined; | ||
} | ||
this.isDirty = false; | ||
@@ -67,2 +96,6 @@ } | ||
} | ||
/** | ||
* Returns true, if the view's value has changed since last | ||
* `deref()`. | ||
*/ | ||
changed() { | ||
@@ -82,6 +115,13 @@ return this.isDirty; | ||
view() { | ||
return this.isDirty ? this.tx(this.unprocessed) : this.state; | ||
return this.isDirty && this.isLazy ? this.tx(this.unprocessed) : this.state; | ||
} | ||
/** | ||
* Disconnects this view from parent state, marks itself | ||
* dirty/changed and sets its unprocessed raw value to `undefined`. | ||
*/ | ||
release() { | ||
this.unprocessed = undefined; | ||
if (!this.isLazy) { | ||
this.state = this.tx(undefined); | ||
} | ||
this.isDirty = true; | ||
@@ -88,0 +128,0 @@ return this.parent.removeWatch(this.id); |
57180
725
388