e0ecf8db by Michael Richards

Merge pull request #90 from mdekmetzian/master

Support for two-way formatters [#74]
2 parents 11c6d643 24689897
...@@ -130,7 +130,7 @@ describe('Rivets.Binding', function() { ...@@ -130,7 +130,7 @@ describe('Rivets.Binding', function() {
130 }); 130 });
131 131
132 it('applies any formatters to the value before performing the routine', function() { 132 it('applies any formatters to the value before performing the routine', function() {
133 rivets.formatters.awesome = function(value) { return 'awesome ' + value }; 133 rivets.formatters.awesome = function(value) { return 'awesome ' + value; };
134 binding.formatters.push('awesome'); 134 binding.formatters.push('awesome');
135 spyOn(binding.binder, 'routine'); 135 spyOn(binding.binder, 'routine');
136 binding.set('sweater'); 136 binding.set('sweater');
...@@ -163,15 +163,149 @@ describe('Rivets.Binding', function() { ...@@ -163,15 +163,149 @@ describe('Rivets.Binding', function() {
163 }); 163 });
164 }); 164 });
165 165
166 describe('publishTwoWay()', function() {
167
168 it('applies a two-way read formatter to function same as a single-way', function() {
169 rivets.formatters.awesome = {
170 read: function(value) { return 'awesome ' + value; },
171 };
172 binding.formatters.push('awesome');
173 spyOn(binding.binder, 'routine');
174 binding.set('sweater');
175 expect(binding.binder.routine).toHaveBeenCalledWith(el, 'awesome sweater');
176 });
177
178 it("should publish the value of a number input", function() {
179 rivets.formatters.awesome = {
180 publish: function(value) { return 'awesome ' + value; },
181 };
182
183 numberInput = document.createElement('input');
184 numberInput.setAttribute('type', 'number');
185 numberInput.setAttribute('data-value', 'obj.num | awesome');
186
187 view = rivets.bind(numberInput, {obj: {num: 42}});
188 binding = view.bindings[0];
189 model = binding.model;
190
191 numberInput.value = 42;
192
193 spyOn(rivets.config.adapter, 'publish');
194 binding.publish({target: numberInput});
195 expect(rivets.config.adapter.publish).toHaveBeenCalledWith(model, 'num', 'awesome 42');
196 });
197
198 it("should format a value in both directions", function() {
199 rivets.formatters.awesome = {
200 publish: function(value) { return 'awesome ' + value; },
201 read: function(value) { return value + ' is awesome'; }
202 };
203 valueInput = document.createElement('input');
204 valueInput.setAttribute('type','text');
205 valueInput.setAttribute('data-value', 'obj.name | awesome');
206
207 view = rivets.bind(valueInput, {obj: { name: 'nothing' }});
208 binding = view.bindings[0];
209 model = binding.model;
210
211 spyOn(rivets.config.adapter, 'publish');
212 valueInput.value = 'charles';
213 binding.publish({target: valueInput});
214 expect(rivets.config.adapter.publish).toHaveBeenCalledWith(model, 'name', 'awesome charles');
215
216 spyOn(binding.binder, 'routine');
217 binding.set('fred');
218 expect(binding.binder.routine).toHaveBeenCalledWith(valueInput, 'fred is awesome');
219 });
220
221 it("should not fail or format if the specified binding function doesn't exist", function() {
222 rivets.formatters.awesome = { };
223 valueInput = document.createElement('input');
224 valueInput.setAttribute('type','text');
225 valueInput.setAttribute('data-value', 'obj.name | awesome');
226
227 view = rivets.bind(valueInput, {obj: { name: 'nothing' }});
228 binding = view.bindings[0];
229 model = binding.model;
230
231 spyOn(rivets.config.adapter, 'publish');
232 valueInput.value = 'charles';
233 binding.publish({target: valueInput});
234 expect(rivets.config.adapter.publish).toHaveBeenCalledWith(model, 'name', 'charles');
235
236 spyOn(binding.binder, 'routine');
237 binding.set('fred');
238 expect(binding.binder.routine).toHaveBeenCalledWith(valueInput, 'fred');
239 });
240
241
242 it("should apply read binders left to right, and write binders right to left", function() {
243 rivets.formatters.totally = {
244 publish: function(value) { return value + ' totally'; },
245 read: function(value) { return value + ' totally'; }
246 };
247 rivets.formatters.awesome = {
248 publish: function(value) { return value + ' is awesome'; },
249 read: function(value) { return value + ' is awesome'; }
250 };
251
252 valueInput = document.createElement('input');
253 valueInput.setAttribute('type','text');
254 valueInput.setAttribute('data-value', 'obj.name | awesome | totally');
255
256 view = rivets.bind(valueInput, {obj: { name: 'nothing' }});
257 binding = view.bindings[0];
258 model = binding.model;
259
260 spyOn(binding.binder, 'routine');
261 binding.set('fred');
262 expect(binding.binder.routine).toHaveBeenCalledWith(valueInput, 'fred is awesome totally');
263
264 spyOn(rivets.config.adapter, 'publish');
265 valueInput.value = 'fred';
266 binding.publish({target: valueInput});
267 expect(rivets.config.adapter.publish).toHaveBeenCalledWith(model, 'name', 'fred totally is awesome');
268 });
269
270 it("binders in a chain should be skipped if they're not there", function() {
271 rivets.formatters.totally = {
272 publish: function(value) { return value + ' totally'; },
273 read: function(value) { return value + ' totally'; }
274 };
275 rivets.formatters.radical = {
276 publish: function(value) { return value + ' is radical'; },
277 };
278 rivets.formatters.awesome = function(value) { return value + ' is awesome'; };
279
280 valueInput = document.createElement('input');
281 valueInput.setAttribute('type','text');
282 valueInput.setAttribute('data-value', 'obj.name | awesome | radical | totally');
283
284 view = rivets.bind(valueInput, {obj: { name: 'nothing' }});
285 binding = view.bindings[0];
286 model = binding.model;
287
288 spyOn(binding.binder, 'routine');
289 binding.set('fred');
290 expect(binding.binder.routine).toHaveBeenCalledWith(valueInput, 'fred is awesome totally');
291
292 spyOn(rivets.config.adapter, 'publish');
293 valueInput.value = 'fred';
294 binding.publish({target: valueInput});
295 expect(rivets.config.adapter.publish).toHaveBeenCalledWith(model, 'name', 'fred totally is radical');
296 });
297
298 });
299
166 describe('formattedValue()', function() { 300 describe('formattedValue()', function() {
167 it('applies the current formatters on the supplied value', function() { 301 it('applies the current formatters on the supplied value', function() {
168 rivets.formatters.awesome = function(value) { return 'awesome ' + value }; 302 rivets.formatters.awesome = function(value) { return 'awesome ' + value; };
169 binding.formatters.push('awesome'); 303 binding.formatters.push('awesome');
170 expect(binding.formattedValue('hat')).toBe('awesome hat'); 304 expect(binding.formattedValue('hat')).toBe('awesome hat');
171 }); 305 });
172 306
173 it('uses formatters on the model', function() { 307 it('uses formatters on the model', function() {
174 model.modelAwesome = function(value) { return 'model awesome ' + value }; 308 model.modelAwesome = function(value) { return 'model awesome ' + value; };
175 binding.formatters.push('modelAwesome'); 309 binding.formatters.push('modelAwesome');
176 expect(binding.formattedValue('hat')).toBe('model awesome hat'); 310 expect(binding.formattedValue('hat')).toBe('model awesome hat');
177 }); 311 });
......
...@@ -40,8 +40,14 @@ class Rivets.Binding ...@@ -40,8 +40,14 @@ class Rivets.Binding
40 value = if @model[id] instanceof Function 40 value = if @model[id] instanceof Function
41 @model[id] value, args... 41 @model[id] value, args...
42 else if Rivets.formatters[id] 42 else if Rivets.formatters[id]
43 Rivets.formatters[id] value, args... 43 if Rivets.formatters[id].read instanceof Function
44 44 Rivets.formatters[id].read value, args...
45 else if Rivets.formatters[id] instanceof Function # could occur if fmt = { publish: function() {}}
46 Rivets.formatters[id] value, args...
47 else # skip if no read function exists
48 value
49 else # skip if no formatter exists
50 value
45 value 51 value
46 52
47 # Sets the value for the binding. This Basically just runs the binding routine 53 # Sets the value for the binding. This Basically just runs the binding routine
...@@ -62,8 +68,20 @@ class Rivets.Binding ...@@ -62,8 +68,20 @@ class Rivets.Binding
62 Rivets.config.adapter.read @model, @keypath 68 Rivets.config.adapter.read @model, @keypath
63 69
64 # Publishes the value currently set on the input element back to the model. 70 # Publishes the value currently set on the input element back to the model.
65 publish: => 71 publish: =>
66 Rivets.config.adapter.publish @model, @keypath, getInputValue @el 72 value = getInputValue @el
73 if @formatters
74 i = @formatters.length-1
75 while(i > -1)
76 formatter = @formatters[i]
77 args = formatter.split /\s+/
78 id = args.shift()
79 # only re-assign if there is a two-way formatter.
80 if Rivets.formatters[id] and Rivets.formatters[id].publish
81 value = Rivets.formatters[id].publish value, args...
82 i--
83 if(value)
84 Rivets.config.adapter.publish @model, @keypath, value
67 85
68 # Subscribes to the model for changes at the specified keypath. Bi-directional 86 # Subscribes to the model for changes at the specified keypath. Bi-directional
69 # routines will also listen for changes on the element to propagate them back 87 # routines will also listen for changes on the element to propagate them back
......