1 | var Value = require('./value')
|
2 | var LazyWatcher = require('./lib/lazy-watcher')
|
3 | var isSame = require('./lib/is-same')
|
4 | var extend = require('xtend')
|
5 |
|
6 | module.exports = Struct
|
7 |
|
8 | var blackList = {
|
9 | 'length': 'Clashes with `Function.prototype.length`.\n',
|
10 | 'name': 'Clashes with `Function.prototype.name`\n',
|
11 | 'set': '`set` is a reserved key of struct\n'
|
12 | }
|
13 |
|
14 | function Struct (properties, opts) {
|
15 | var object = Object.create({})
|
16 | var releases = []
|
17 | var binder = LazyWatcher(update, listen, unlisten)
|
18 | binder.value = object
|
19 |
|
20 | if (opts && opts.nextTick) binder.nextTick = true
|
21 | if (opts && opts.idle) binder.idle = true
|
22 |
|
23 | var comparer = opts && opts.comparer || null
|
24 |
|
25 | var observable = function MutantStruct (listener) {
|
26 | if (!listener) {
|
27 | return binder.getValue()
|
28 | }
|
29 | return binder.addListener(listener)
|
30 | }
|
31 |
|
32 | var keys = Object.keys(properties)
|
33 | var suspendBroadcast = false
|
34 |
|
35 | keys.forEach(function (key) {
|
36 | if (blackList.hasOwnProperty(key)) {
|
37 | throw new Error("Cannot create a struct with a key named '" + key + "'.\n" + blackList[key])
|
38 | }
|
39 |
|
40 | var obs = typeof properties[key] === 'function'
|
41 | ? properties[key]
|
42 | : Value(properties[key])
|
43 |
|
44 | object[key] = obs()
|
45 | observable[key] = obs
|
46 | })
|
47 |
|
48 | observable.set = function (values, opts) {
|
49 | var lastValue = suspendBroadcast
|
50 |
|
51 | suspendBroadcast = true
|
52 | values = values || {}
|
53 |
|
54 | if (opts && opts.merge) {
|
55 | values = extend(object, values)
|
56 | }
|
57 |
|
58 |
|
59 | keys.forEach(function (key) {
|
60 | if (observable[key]() !== values[key]) {
|
61 | observable[key].set(values[key])
|
62 | }
|
63 | })
|
64 |
|
65 |
|
66 | Object.keys(values).forEach(function (key) {
|
67 | if (!(key in properties)) {
|
68 | object[key] = values[key]
|
69 | }
|
70 | })
|
71 |
|
72 | suspendBroadcast = lastValue
|
73 | if (!suspendBroadcast) {
|
74 | binder.broadcast()
|
75 | }
|
76 | }
|
77 |
|
78 | return observable
|
79 |
|
80 |
|
81 |
|
82 | function listen () {
|
83 | keys.map(function (key) {
|
84 | var obs = observable[key]
|
85 | releases.push(obs(function (val) {
|
86 | if (!isSame(val, object[key], comparer)) {
|
87 | object[key] = val
|
88 | if (!suspendBroadcast) {
|
89 | binder.broadcast(object)
|
90 | }
|
91 | }
|
92 | }))
|
93 | })
|
94 | }
|
95 |
|
96 | function unlisten () {
|
97 | while (releases.length) {
|
98 | releases.pop()()
|
99 | }
|
100 | }
|
101 |
|
102 | function update () {
|
103 | var changed = false
|
104 | keys.forEach(function (key) {
|
105 | var newValue = observable[key]()
|
106 | if (!isSame(newValue, object[key], comparer)) {
|
107 | object[key] = observable[key]()
|
108 | changed = true
|
109 | }
|
110 | })
|
111 | return changed
|
112 | }
|
113 | }
|