d0b185b9 by Michael Richards

Merge pull request #80 from mikeric/advanced-custom-binders-api

Advanced custom binders API
2 parents 4093da62 9b103172
...@@ -18,8 +18,8 @@ describe('Rivets.Binding', function() { ...@@ -18,8 +18,8 @@ describe('Rivets.Binding', function() {
18 model = binding.model; 18 model = binding.model;
19 }); 19 });
20 20
21 it('gets assigned the routine function matching the identifier', function() { 21 it('gets assigned the proper binder routine matching the identifier', function() {
22 expect(binding.routine).toBe(rivets.routines.text); 22 expect(binding.binder.routine).toBe(rivets.binders.text);
23 }); 23 });
24 24
25 describe('bind()', function() { 25 describe('bind()', function() {
...@@ -72,50 +72,24 @@ describe('Rivets.Binding', function() { ...@@ -72,50 +72,24 @@ describe('Rivets.Binding', function() {
72 72
73 describe('set()', function() { 73 describe('set()', function() {
74 it('performs the binding routine with the supplied value', function() { 74 it('performs the binding routine with the supplied value', function() {
75 spyOn(binding, 'routine'); 75 spyOn(binding.binder, 'routine');
76 binding.set('sweater'); 76 binding.set('sweater');
77 expect(binding.routine).toHaveBeenCalledWith(el, 'sweater'); 77 expect(binding.binder.routine).toHaveBeenCalledWith(el, 'sweater');
78 }); 78 });
79 79
80 it('applies any formatters to the value before performing the routine', function() { 80 it('applies any formatters to the value before performing the routine', function() {
81 rivets.formatters.awesome = function(value) { return 'awesome ' + value }; 81 rivets.formatters.awesome = function(value) { return 'awesome ' + value };
82 binding.formatters.push('awesome'); 82 binding.formatters.push('awesome');
83 spyOn(binding, 'routine'); 83 spyOn(binding.binder, 'routine');
84 binding.set('sweater'); 84 binding.set('sweater');
85 expect(binding.routine).toHaveBeenCalledWith(el, 'awesome sweater'); 85 expect(binding.binder.routine).toHaveBeenCalledWith(el, 'awesome sweater');
86 }); 86 });
87 87
88 it('calls methods with the object as context', function() { 88 it('calls methods with the object as context', function() {
89 binding.model = {foo: 'bar'}; 89 binding.model = {foo: 'bar'};
90 spyOn(binding, 'routine'); 90 spyOn(binding.binder, 'routine');
91 binding.set(function() { return this.foo; }); 91 binding.set(function() { return this.foo; });
92 expect(binding.routine).toHaveBeenCalledWith(el, binding.model.foo); 92 expect(binding.binder.routine).toHaveBeenCalledWith(el, binding.model.foo);
93 });
94
95 describe('on an event binding', function() {
96 beforeEach(function() {
97 binding.options.special = 'event';
98 });
99
100 it('performs the binding routine with the supplied function and current listener', function() {
101 spyOn(binding, 'routine');
102 func = function() { return 1 + 2; }
103 binding.set(func);
104 expect(binding.routine).toHaveBeenCalledWith(el, binding.model, func, undefined);
105 });
106 });
107
108 describe('on an iteration binding', function(){
109 beforeEach(function(){
110 binding.options.special = 'iteration';
111 });
112
113 it('performs the binding routine with the supplied collection and binding', function() {
114 spyOn(binding, 'routine');
115 array = [{name: 'a'}, {name: 'b'}];
116 binding.set(array);
117 expect(binding.routine).toHaveBeenCalledWith(el, array, binding);
118 });
119 }); 93 });
120 }); 94 });
121 95
......
...@@ -18,13 +18,13 @@ describe('Routines', function() { ...@@ -18,13 +18,13 @@ describe('Routines', function() {
18 18
19 describe('text', function() { 19 describe('text', function() {
20 it("sets the element's text content", function() { 20 it("sets the element's text content", function() {
21 rivets.routines.text(el, '<em>gluten-free</em>'); 21 rivets.binders.text(el, '<em>gluten-free</em>');
22 expect(el.textContent || el.innerText).toBe('<em>gluten-free</em>'); 22 expect(el.textContent || el.innerText).toBe('<em>gluten-free</em>');
23 expect(el.innerHTML).toBe('&lt;em&gt;gluten-free&lt;/em&gt;'); 23 expect(el.innerHTML).toBe('&lt;em&gt;gluten-free&lt;/em&gt;');
24 }); 24 });
25 25
26 it("sets the element's text content to zero when a numeric zero is passed", function() { 26 it("sets the element's text content to zero when a numeric zero is passed", function() {
27 rivets.routines.text(el, 0); 27 rivets.binders.text(el, 0);
28 expect(el.textContent || el.innerText).toBe('0'); 28 expect(el.textContent || el.innerText).toBe('0');
29 expect(el.innerHTML).toBe('0'); 29 expect(el.innerHTML).toBe('0');
30 }); 30 });
...@@ -32,13 +32,13 @@ describe('Routines', function() { ...@@ -32,13 +32,13 @@ describe('Routines', function() {
32 32
33 describe('html', function() { 33 describe('html', function() {
34 it("sets the element's HTML content", function() { 34 it("sets the element's HTML content", function() {
35 rivets.routines.html(el, '<strong>fixie</strong>'); 35 rivets.binders.html(el, '<strong>fixie</strong>');
36 expect(el.textContent || el.innerText).toBe('fixie'); 36 expect(el.textContent || el.innerText).toBe('fixie');
37 expect(el.innerHTML).toBe('<strong>fixie</strong>'); 37 expect(el.innerHTML).toBe('<strong>fixie</strong>');
38 }); 38 });
39 39
40 it("sets the element's HTML content to zero when a zero value is passed", function() { 40 it("sets the element's HTML content to zero when a zero value is passed", function() {
41 rivets.routines.html(el, 0); 41 rivets.binders.html(el, 0);
42 expect(el.textContent || el.innerText).toBe('0'); 42 expect(el.textContent || el.innerText).toBe('0');
43 expect(el.innerHTML).toBe('0'); 43 expect(el.innerHTML).toBe('0');
44 }); 44 });
...@@ -46,17 +46,17 @@ describe('Routines', function() { ...@@ -46,17 +46,17 @@ describe('Routines', function() {
46 46
47 describe('value', function() { 47 describe('value', function() {
48 it("sets the element's value", function() { 48 it("sets the element's value", function() {
49 rivets.routines.value(input, 'pitchfork'); 49 rivets.binders.value.routine(input, 'pitchfork');
50 expect(input.value).toBe('pitchfork'); 50 expect(input.value).toBe('pitchfork');
51 }); 51 });
52 52
53 it("applies a default value to the element when the model doesn't contain it", function() { 53 it("applies a default value to the element when the model doesn't contain it", function() {
54 rivets.routines.value(input, undefined); 54 rivets.binders.value.routine(input, undefined);
55 expect(input.value).toBe(''); 55 expect(input.value).toBe('');
56 }); 56 });
57 57
58 it("sets the element's value to zero when a zero value is passed", function() { 58 it("sets the element's value to zero when a zero value is passed", function() {
59 rivets.routines.value(input, 0); 59 rivets.binders.value.routine(input, 0);
60 expect(input.value).toBe('0'); 60 expect(input.value).toBe('0');
61 }); 61 });
62 }); 62 });
...@@ -64,14 +64,14 @@ describe('Routines', function() { ...@@ -64,14 +64,14 @@ describe('Routines', function() {
64 describe('show', function() { 64 describe('show', function() {
65 describe('with a truthy value', function() { 65 describe('with a truthy value', function() {
66 it('shows the element', function() { 66 it('shows the element', function() {
67 rivets.routines.show(el, true); 67 rivets.binders.show(el, true);
68 expect(el.style.display).toBe(''); 68 expect(el.style.display).toBe('');
69 }); 69 });
70 }); 70 });
71 71
72 describe('with a falsey value', function() { 72 describe('with a falsey value', function() {
73 it('hides the element', function() { 73 it('hides the element', function() {
74 rivets.routines.show(el, false); 74 rivets.binders.show(el, false);
75 expect(el.style.display).toBe('none'); 75 expect(el.style.display).toBe('none');
76 }); 76 });
77 }); 77 });
...@@ -80,14 +80,14 @@ describe('Routines', function() { ...@@ -80,14 +80,14 @@ describe('Routines', function() {
80 describe('hide', function() { 80 describe('hide', function() {
81 describe('with a truthy value', function() { 81 describe('with a truthy value', function() {
82 it('hides the element', function() { 82 it('hides the element', function() {
83 rivets.routines.hide(el, true); 83 rivets.binders.hide(el, true);
84 expect(el.style.display).toBe('none'); 84 expect(el.style.display).toBe('none');
85 }); 85 });
86 }); 86 });
87 87
88 describe('with a falsey value', function() { 88 describe('with a falsey value', function() {
89 it('shows the element', function() { 89 it('shows the element', function() {
90 rivets.routines.hide(el, false); 90 rivets.binders.hide(el, false);
91 expect(el.style.display).toBe(''); 91 expect(el.style.display).toBe('');
92 }); 92 });
93 }); 93 });
...@@ -96,14 +96,14 @@ describe('Routines', function() { ...@@ -96,14 +96,14 @@ describe('Routines', function() {
96 describe('enabled', function() { 96 describe('enabled', function() {
97 describe('with a truthy value', function() { 97 describe('with a truthy value', function() {
98 it('enables the element', function() { 98 it('enables the element', function() {
99 rivets.routines.enabled(el, true); 99 rivets.binders.enabled(el, true);
100 expect(el.disabled).toBe(false); 100 expect(el.disabled).toBe(false);
101 }); 101 });
102 }); 102 });
103 103
104 describe('with a falsey value', function() { 104 describe('with a falsey value', function() {
105 it('disables the element', function() { 105 it('disables the element', function() {
106 rivets.routines.enabled(el, false); 106 rivets.binders.enabled(el, false);
107 expect(el.disabled).toBe(true); 107 expect(el.disabled).toBe(true);
108 }); 108 });
109 }); 109 });
...@@ -112,14 +112,14 @@ describe('Routines', function() { ...@@ -112,14 +112,14 @@ describe('Routines', function() {
112 describe('disabled', function() { 112 describe('disabled', function() {
113 describe('with a truthy value', function() { 113 describe('with a truthy value', function() {
114 it('disables the element', function() { 114 it('disables the element', function() {
115 rivets.routines.disabled(el, true); 115 rivets.binders.disabled(el, true);
116 expect(el.disabled).toBe(true); 116 expect(el.disabled).toBe(true);
117 }); 117 });
118 }); 118 });
119 119
120 describe('with a falsey value', function() { 120 describe('with a falsey value', function() {
121 it('enables the element', function() { 121 it('enables the element', function() {
122 rivets.routines.disabled(el, false); 122 rivets.binders.disabled(el, false);
123 expect(el.disabled).toBe(false); 123 expect(el.disabled).toBe(false);
124 }); 124 });
125 }); 125 });
...@@ -128,14 +128,14 @@ describe('Routines', function() { ...@@ -128,14 +128,14 @@ describe('Routines', function() {
128 describe('checked', function() { 128 describe('checked', function() {
129 describe('with a truthy value', function() { 129 describe('with a truthy value', function() {
130 it('checks the element', function() { 130 it('checks the element', function() {
131 rivets.routines.checked(el, true); 131 rivets.binders.checked.routine(el, true);
132 expect(el.checked).toBe(true); 132 expect(el.checked).toBe(true);
133 }); 133 });
134 }); 134 });
135 135
136 describe('with a falsey value', function() { 136 describe('with a falsey value', function() {
137 it('unchecks the element', function() { 137 it('unchecks the element', function() {
138 rivets.routines.checked(el, false); 138 rivets.binders.checked.routine(el, false);
139 expect(el.checked).toBe(false); 139 expect(el.checked).toBe(false);
140 }); 140 });
141 }); 141 });
...@@ -144,14 +144,14 @@ describe('Routines', function() { ...@@ -144,14 +144,14 @@ describe('Routines', function() {
144 describe('unchecked', function() { 144 describe('unchecked', function() {
145 describe('with a truthy value', function() { 145 describe('with a truthy value', function() {
146 it('unchecks the element', function() { 146 it('unchecks the element', function() {
147 rivets.routines.unchecked(el, true); 147 rivets.binders.unchecked.routine(el, true);
148 expect(el.checked).toBe(false); 148 expect(el.checked).toBe(false);
149 }); 149 });
150 }); 150 });
151 151
152 describe('with a falsey value', function() { 152 describe('with a falsey value', function() {
153 it('checks the element', function() { 153 it('checks the element', function() {
154 rivets.routines.unchecked(el, false); 154 rivets.binders.unchecked.routine(el, false);
155 expect(el.checked).toBe(true); 155 expect(el.checked).toBe(true);
156 }); 156 });
157 }); 157 });
......
...@@ -15,19 +15,21 @@ class Rivets.Binding ...@@ -15,19 +15,21 @@ class Rivets.Binding
15 # element, the type of binding, the model object and the keypath at which 15 # element, the type of binding, the model object and the keypath at which
16 # to listen for changes. 16 # to listen for changes.
17 constructor: (@el, @type, @model, @keypath, @options = {}) -> 17 constructor: (@el, @type, @model, @keypath, @options = {}) ->
18 @routine = switch @options.special 18 unless @binder = Rivets.binders[type]
19 when 'event' then eventBinding @type 19 for identifier, value of Rivets.binders
20 when 'class' then classBinding @type 20 if identifier isnt '*' and identifier.indexOf('*') isnt -1
21 when 'iteration' then iterationBinding @type 21 regexp = new RegExp "^#{identifier.replace('*', '.+')}$"
22 else Rivets.routines[@type] || attributeBinding @type 22 if regexp.test type
23 @binder = value
24 @args = new RegExp("^#{identifier.replace('*', '(.+)')}$").exec type
25 @args.shift()
23 26
24 @formatters = @options.formatters || [] 27 @binder or= Rivets.binders['*']
28
29 if @binder instanceof Function
30 @binder = {routine: @binder}
25 31
26 # Returns true|false depending on whether or not the binding should also 32 @formatters = @options.formatters || []
27 # observe the DOM element for changes in order to propagate those changes
28 # back to the model object.
29 isBidirectional: =>
30 @type in ['value', 'checked', 'unchecked']
31 33
32 # Applies all the current formatters to the supplied value and returns the 34 # Applies all the current formatters to the supplied value and returns the
33 # formatted value. 35 # formatted value.
...@@ -45,17 +47,12 @@ class Rivets.Binding ...@@ -45,17 +47,12 @@ class Rivets.Binding
45 # Sets the value for the binding. This Basically just runs the binding routine 47 # Sets the value for the binding. This Basically just runs the binding routine
46 # with the suplied value formatted. 48 # with the suplied value formatted.
47 set: (value) => 49 set: (value) =>
48 value = if value instanceof Function and @options.special isnt 'event' 50 value = if value instanceof Function and !@binder.function
49 @formattedValue value.call @model 51 @formattedValue value.call @model
50 else 52 else
51 @formattedValue value 53 @formattedValue value
52 54
53 if @options.special is 'event' 55 @binder.routine?.call @, @el, value
54 @currentListener = @routine @el, @model, value, @currentListener
55 else if @options.special is 'iteration'
56 @routine @el, value, @
57 else
58 @routine @el, value
59 56
60 # Syncs up the view binding with the model. 57 # Syncs up the view binding with the model.
61 sync: => 58 sync: =>
...@@ -75,12 +72,10 @@ class Rivets.Binding ...@@ -75,12 +72,10 @@ class Rivets.Binding
75 if @options.bypass 72 if @options.bypass
76 @sync() 73 @sync()
77 else 74 else
75 @binder.bind?.call @, @el
78 Rivets.config.adapter.subscribe @model, @keypath, @sync 76 Rivets.config.adapter.subscribe @model, @keypath, @sync
79 @sync() if Rivets.config.preloadData 77 @sync() if Rivets.config.preloadData
80 78
81 if @isBidirectional()
82 bindEvent @el, 'change', @publish
83
84 if @options.dependencies?.length 79 if @options.dependencies?.length
85 for dependency in @options.dependencies 80 for dependency in @options.dependencies
86 if /^\./.test dependency 81 if /^\./.test dependency
...@@ -93,15 +88,12 @@ class Rivets.Binding ...@@ -93,15 +88,12 @@ class Rivets.Binding
93 88
94 Rivets.config.adapter.subscribe model, keypath, @sync 89 Rivets.config.adapter.subscribe model, keypath, @sync
95 90
96
97 # Unsubscribes from the model and the element. 91 # Unsubscribes from the model and the element.
98 unbind: => 92 unbind: =>
99 unless @options.bypass 93 unless @options.bypass
94 @binder.unbind?.call @, @el
100 Rivets.config.adapter.unsubscribe @model, @keypath, @sync 95 Rivets.config.adapter.unsubscribe @model, @keypath, @sync
101 96
102 if @isBidirectional()
103 unbindEvent @el, 'change', @publish
104
105 if @options.dependencies?.length 97 if @options.dependencies?.length
106 for keypath in @options.dependencies 98 for keypath in @options.dependencies
107 Rivets.config.adapter.unsubscribe @model, keypath, @sync 99 Rivets.config.adapter.unsubscribe @model, keypath, @sync
...@@ -119,28 +111,30 @@ class Rivets.View ...@@ -119,28 +111,30 @@ class Rivets.View
119 prefix = Rivets.config.prefix 111 prefix = Rivets.config.prefix
120 if prefix then new RegExp("^data-#{prefix}-") else /^data-/ 112 if prefix then new RegExp("^data-#{prefix}-") else /^data-/
121 113
122 # Builds the Rivets.Binding instances for the view.
123 build: => 114 build: =>
124 @bindings = [] 115 @bindings = []
125 skipNodes = [] 116 skipNodes = []
126 iterator = null
127 bindingRegExp = @bindingRegExp() 117 bindingRegExp = @bindingRegExp()
128 eventRegExp = /^on-/
129 classRegExp = /^class-/
130 iterationRegExp = /^each-/
131 118
132 parseNode = (node) => 119 parseNode = (node) =>
133 unless node in skipNodes 120 unless node in skipNodes
134 for attribute in node.attributes 121 for attribute in node.attributes
135 if bindingRegExp.test attribute.name 122 if bindingRegExp.test attribute.name
136 type = attribute.name.replace bindingRegExp, '' 123 type = attribute.name.replace bindingRegExp, ''
124 unless binder = Rivets.binders[type]
125 for identifier, value of Rivets.binders
126 if identifier isnt '*' and identifier.indexOf('*') isnt -1
127 regexp = new RegExp "^#{identifier.replace('*', '.+')}$"
128 if regexp.test type
129 binder = value
137 130
138 if iterationRegExp.test type 131 binder or= Rivets.binders['*']
139 unless @models[type.replace iterationRegExp, ''] 132
133 if binder.block
140 skipNodes.push n for n in node.getElementsByTagName '*' 134 skipNodes.push n for n in node.getElementsByTagName '*'
141 iterator = [attribute] 135 attributes = [attribute]
142 136
143 for attribute in iterator or node.attributes 137 for attribute in attributes or node.attributes
144 if bindingRegExp.test attribute.name 138 if bindingRegExp.test attribute.name
145 options = {} 139 options = {}
146 140
...@@ -162,31 +156,19 @@ class Rivets.View ...@@ -162,31 +156,19 @@ class Rivets.View
162 if dependencies = context.shift() 156 if dependencies = context.shift()
163 options.dependencies = dependencies.split /\s+/ 157 options.dependencies = dependencies.split /\s+/
164 158
165 if eventRegExp.test type
166 type = type.replace eventRegExp, ''
167 options.special = 'event'
168
169 if classRegExp.test type
170 type = type.replace classRegExp, ''
171 options.special = 'class'
172
173 if iterationRegExp.test type
174 type = type.replace iterationRegExp, ''
175 options.special = 'iteration'
176
177 binding = new Rivets.Binding node, type, model, keypath, options 159 binding = new Rivets.Binding node, type, model, keypath, options
178 binding.view = @ 160 binding.view = @
179 161
180 @bindings.push binding 162 @bindings.push binding
181 163
182 if iterator 164 attributes = null if attributes
183 node.removeAttribute(a.name) for a in iterator 165
184 iterator = null
185 return 166 return
186 167
187 for el in @els 168 for el in @els
188 parseNode el 169 parseNode el
189 parseNode node for node in el.getElementsByTagName '*' 170 parseNode node for node in el.getElementsByTagName '*'
171
190 return 172 return
191 173
192 # Returns an array of bindings where the supplied function evaluates to true. 174 # Returns an array of bindings where the supplied function evaluates to true.
...@@ -207,7 +189,7 @@ class Rivets.View ...@@ -207,7 +189,7 @@ class Rivets.View
207 189
208 # Publishes the input values from the view back to the model (reverse sync). 190 # Publishes the input values from the view back to the model (reverse sync).
209 publish: => 191 publish: =>
210 binding.publish() for binding in @select (b) -> b.isBidirectional() 192 binding.publish() for binding in @select (b) -> b.binder.publishes
211 193
212 # Cross-browser event binding. 194 # Cross-browser event binding.
213 bindEvent = (el, event, handler, context) -> 195 bindEvent = (el, event, handler, context) ->
...@@ -248,85 +230,111 @@ getInputValue = (el) -> ...@@ -248,85 +230,111 @@ getInputValue = (el) ->
248 when 'select-multiple' then o.value for o in el when o.selected 230 when 'select-multiple' then o.value for o in el when o.selected
249 else el.value 231 else el.value
250 232
251 # Returns an event binding routine for the specified event.
252 eventBinding = (event) -> (el, context, bind, unbind) ->
253 unbindEvent el, event, unbind if unbind
254 bindEvent el, event, bind, context
255
256 # Returns a class binding routine for the specified class name.
257 classBinding = (name) -> (el, value) ->
258 elClass = " #{el.className} "
259 hasClass = elClass.indexOf(" #{name} ") != -1
260
261 if !value is hasClass
262 el.className = if value
263 "#{el.className} #{name}"
264 else
265 elClass.replace(" #{name} ", ' ').trim()
266
267 # Returns an iteration binding routine for the specified collection.
268 iterationBinding = (name) -> (el, collection, binding) ->
269 if binding.iterated?
270 for iteration in binding.iterated
271 iteration.view.unbind()
272 iteration.el.parentNode.removeChild iteration.el
273 else
274 binding.marker = document.createComment " rivets: each-#{name} "
275 el.parentNode.insertBefore binding.marker, el
276 el.parentNode.removeChild el
277
278 binding.iterated = []
279
280 for item in collection
281 data = {}
282 data[n] = m for n, m of binding.view.models
283 data[name] = item
284 itemEl = el.cloneNode true
285 previous = binding.iterated[binding.iterated.length - 1] or binding.marker
286 binding.marker.parentNode.insertBefore itemEl, previous.nextSibling ? null
287
288 binding.iterated.push
289 el: itemEl
290 view: rivets.bind itemEl, data
291
292 # Returns an attribute binding routine for the specified attribute. This is what
293 # is used when there are no matching routines for an identifier.
294 attributeBinding = (attr) -> (el, value) ->
295 if value then el.setAttribute attr, value else el.removeAttribute attr
296
297 # Core binding routines. 233 # Core binding routines.
298 Rivets.routines = 234 Rivets.binders =
299 enabled: (el, value) -> 235 enabled: (el, value) ->
300 el.disabled = !value 236 el.disabled = !value
237
301 disabled: (el, value) -> 238 disabled: (el, value) ->
302 el.disabled = !!value 239 el.disabled = !!value
303 checked: (el, value) -> 240
241 checked:
242 publishes: true
243 bind: (el) ->
244 bindEvent el, 'change', @publish
245 unbind: (el) ->
246 unbindEvent el, 'change', @publish
247 routine: (el, value) ->
304 if el.type is 'radio' 248 if el.type is 'radio'
305 el.checked = el.value is value 249 el.checked = el.value is value
306 else 250 else
307 el.checked = !!value 251 el.checked = !!value
308 unchecked: (el, value) -> 252
253 unchecked:
254 publishes: true
255 bind: (el) ->
256 bindEvent el, 'change', @publish
257 unbind: (el) ->
258 unbindEvent el, 'change', @publish
259 routine: (el, value) ->
309 if el.type is 'radio' 260 if el.type is 'radio'
310 el.checked = el.value isnt value 261 el.checked = el.value isnt value
311 else 262 else
312 el.checked = !value 263 el.checked = !value
264
313 show: (el, value) -> 265 show: (el, value) ->
314 el.style.display = if value then '' else 'none' 266 el.style.display = if value then '' else 'none'
267
315 hide: (el, value) -> 268 hide: (el, value) ->
316 el.style.display = if value then 'none' else '' 269 el.style.display = if value then 'none' else ''
270
317 html: (el, value) -> 271 html: (el, value) ->
318 el.innerHTML = if value? then value else '' 272 el.innerHTML = if value? then value else ''
319 value: (el, value) -> 273
274 value:
275 publishes: true
276 bind: (el) ->
277 bindEvent el, 'change', @publish
278 unbind: (el) ->
279 unbindEvent el, 'change', @publish
280 routine: (el, value) ->
320 if el.type is 'select-multiple' 281 if el.type is 'select-multiple'
321 o.selected = o.value in value for o in el if value? 282 o.selected = o.value in value for o in el if value?
322 else 283 else
323 el.value = if value? then value else '' 284 el.value = if value? then value else ''
285
324 text: (el, value) -> 286 text: (el, value) ->
325 if el.innerText? 287 if el.innerText?
326 el.innerText = if value? then value else '' 288 el.innerText = if value? then value else ''
327 else 289 else
328 el.textContent = if value? then value else '' 290 el.textContent = if value? then value else ''
329 291
292 "on-*":
293 function: true
294 routine: (el, value) ->
295 unbindEvent el, @args[0], @currentListener if @currentListener
296 @currentListener = bindEvent el, @args[0], value, @model
297
298 "each-*":
299 block: true
300 bind: (el, collection) ->
301 el.removeAttribute ['data', rivets.config.prefix, @type].join('-').replace '--', '-'
302 routine: (el, collection) ->
303 if @iterated?
304 for view in @iterated
305 view.unbind()
306 e.parentNode.removeChild e for e in view.els
307 else
308 @marker = document.createComment " rivets: #{@type} "
309 el.parentNode.insertBefore @marker, el
310 el.parentNode.removeChild el
311
312 @iterated = []
313
314 for item in collection
315 data = {}
316 data[n] = m for n, m of @view.models
317 data[@args[0]] = item
318 itemEl = el.cloneNode true
319 previous = @iterated[@iterated.length - 1] or @marker
320 @marker.parentNode.insertBefore itemEl, previous.nextSibling ? null
321 @iterated.push rivets.bind itemEl, data
322
323 "class-*": (el, value) ->
324 elClass = " #{el.className} "
325
326 if !value is (elClass.indexOf(" #{@args[0]} ") isnt -1)
327 el.className = if value
328 "#{el.className} #{@args[0]}"
329 else
330 elClass.replace(" #{@args[0]} ", ' ').trim()
331
332 "*": (el, value) ->
333 if value
334 el.setAttribute @type, value
335 else
336 el.removeAttribute @type
337
330 # Default configuration. 338 # Default configuration.
331 Rivets.config = 339 Rivets.config =
332 preloadData: true 340 preloadData: true
...@@ -337,7 +345,7 @@ Rivets.formatters = {} ...@@ -337,7 +345,7 @@ Rivets.formatters = {}
337 # The rivets module. This is the public interface that gets exported. 345 # The rivets module. This is the public interface that gets exported.
338 rivets = 346 rivets =
339 # Exposes the core binding routines that can be extended or stripped down. 347 # Exposes the core binding routines that can be extended or stripped down.
340 routines: Rivets.routines 348 binders: Rivets.binders
341 349
342 # Exposes the formatters object to be extended. 350 # Exposes the formatters object to be extended.
343 formatters: Rivets.formatters 351 formatters: Rivets.formatters
......