Support for two-way formatters [#74]
Showing
2 changed files
with
159 additions
and
7 deletions
... | @@ -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 | ... | ... |
-
Please register or sign in to post a comment