UNPKG

4.3 kBJavaScriptView Raw
1var isObservable = require('../is-observable')
2var Set = require('../set')
3var watch = require('../watch')
4
5module.exports = applyProperties
6
7function applyProperties (target, properties, data) {
8 var classList = Set()
9 if (target.classList && target.classList.value) {
10 classList.add(target.classList.value)
11 }
12
13 for (var key in properties) {
14 var valueOrObs = properties[key]
15
16 if (key === 'style') {
17 // TODO: handle observable at root for style objects
18 var value = resolve(valueOrObs)
19 for (var k in value) {
20 var styleObs = isObservable(value[k]) ? value[k] : null
21 if (styleObs) {
22 data.bindings.push(new Binding(bindStyle, target, styleObs, k))
23 } else {
24 target.style.setProperty(k, value[k])
25 }
26 }
27 } else if (key === 'hooks') {
28 var value = resolve(valueOrObs)
29 if (Array.isArray(value)) {
30 value.forEach(function (v) {
31 data.bindings.push(new HookBinding(v, target))
32 })
33 }
34 } else if (key === 'attributes') {
35 var value = resolve(valueOrObs)
36 for (var k in value) {
37 var attrObs = isObservable(value[k]) ? value[k] : null
38 if (attrObs) {
39 data.bindings.push(new Binding(bindAttr, target, attrObs, k))
40 } else {
41 target.setAttribute(k, value[k])
42 }
43 }
44 } else if (key === 'events') {
45 for (var name in valueOrObs) {
46 target.addEventListener(name, valueOrObs[name], false)
47 }
48 } else if (key.slice(0, 3) === 'ev-') {
49 target.addEventListener(key.slice(3), valueOrObs, false)
50 } else if (key === 'className' || key === 'classList') {
51 if (Array.isArray(valueOrObs)) {
52 valueOrObs.forEach(function (v) {
53 classList.add(v)
54 })
55 } else {
56 classList.add(valueOrObs)
57 }
58 } else {
59 target[key] = resolve(valueOrObs)
60 var obs = isObservable(valueOrObs) ? valueOrObs : null
61 if (obs) {
62 data.bindings.push(new Binding(bind, target, obs, key))
63 }
64 }
65 }
66
67 if (containsObservables(classList)) {
68 data.bindings.push(new Binding(bindClassList, target.classList, classList, 'value'))
69 } else {
70 // OPTIMISATION: no need to create a binding if the list is never going to change
71 target.classList.value = classList().join(' ')
72 }
73}
74
75function containsObservables (obs) {
76 for (var i = 0, len = obs.getLength(); i < len; i++) {
77 if (isObservable(obs.get(i))) {
78 return true
79 }
80 }
81}
82
83function bindClassList (target, obs, key) {
84 return watch(obs, function boundClassList (value) {
85 value = [].concat.apply([], value).filter(present).join(' ')
86 if (value || target[key]) {
87 target[key] = value
88 }
89 })
90}
91
92function bindStyle (target, styleObs, key) {
93 return watch(styleObs, function boundStyle (value) {
94 target.style.setProperty(key, value)
95 })
96}
97
98function bindAttr (target, attrObs, key) {
99 return watch(attrObs, function boundAttr (value) {
100 if (value == null) {
101 target.removeAttribute(key)
102 } else {
103 target.setAttribute(key, value)
104 }
105 })
106}
107
108function bind (target, obs, key) {
109 return watch(obs, function bound (toValue) {
110 var fromValue = target[key]
111 if (fromValue !== toValue) {
112 target[key] = toValue
113 }
114 })
115}
116
117function present (val) {
118 return val != null
119}
120
121function resolve (source) {
122 return typeof source === 'function' ? source() : source
123}
124
125function Binding (fn, element, source, key) {
126 this.element = element
127 this.source = source
128 this.key = key
129 this.fn = fn
130 this.bound = false
131}
132
133Binding.prototype = {
134 bind: function () {
135 if (!this.bound) {
136 this._release = this.fn(this.element, this.source, this.key)
137 this.bound = true
138 }
139 },
140 unbind: function () {
141 if (this.bound) {
142 this._release()
143 this._release = null
144 this.bound = false
145 }
146 }
147}
148
149function HookBinding (fn, element) {
150 this.element = element
151 this.fn = fn
152 this.bound = false
153}
154
155HookBinding.prototype = {
156 bind: function () {
157 if (!this.bound) {
158 this._release = this.fn(this.element)
159 this.bound = true
160 }
161 },
162 unbind: function () {
163 if (this.bound && typeof this._release === 'function') {
164 this._release()
165 this._release = null
166 this.bound = false
167 }
168 }
169}