86b99ab0 by Michael Richards

Merge pull request #149 from mikeric/local-binders-and-config

Local config, binders and formatters
2 parents e0d736ad 4f0a4974
...@@ -131,7 +131,7 @@ describe('Rivets.Binding', function() { ...@@ -131,7 +131,7 @@ describe('Rivets.Binding', function() {
131 }); 131 });
132 132
133 it('applies any formatters to the value before performing the routine', function() { 133 it('applies any formatters to the value before performing the routine', function() {
134 rivets.formatters.awesome = function(value) { return 'awesome ' + value; }; 134 view.formatters.awesome = function(value) { return 'awesome ' + value; };
135 binding.formatters.push('awesome'); 135 binding.formatters.push('awesome');
136 spyOn(binding.binder, 'routine'); 136 spyOn(binding.binder, 'routine');
137 binding.set('sweater'); 137 binding.set('sweater');
...@@ -167,7 +167,7 @@ describe('Rivets.Binding', function() { ...@@ -167,7 +167,7 @@ describe('Rivets.Binding', function() {
167 describe('publishTwoWay()', function() { 167 describe('publishTwoWay()', function() {
168 168
169 it('applies a two-way read formatter to function same as a single-way', function() { 169 it('applies a two-way read formatter to function same as a single-way', function() {
170 rivets.formatters.awesome = { 170 view.formatters.awesome = {
171 read: function(value) { return 'awesome ' + value; }, 171 read: function(value) { return 'awesome ' + value; },
172 }; 172 };
173 binding.formatters.push('awesome'); 173 binding.formatters.push('awesome');
...@@ -300,7 +300,7 @@ describe('Rivets.Binding', function() { ...@@ -300,7 +300,7 @@ describe('Rivets.Binding', function() {
300 300
301 describe('formattedValue()', function() { 301 describe('formattedValue()', function() {
302 it('applies the current formatters on the supplied value', function() { 302 it('applies the current formatters on the supplied value', function() {
303 rivets.formatters.awesome = function(value) { return 'awesome ' + value; }; 303 view.formatters.awesome = function(value) { return 'awesome ' + value; };
304 binding.formatters.push('awesome'); 304 binding.formatters.push('awesome');
305 expect(binding.formattedValue('hat')).toBe('awesome hat'); 305 expect(binding.formattedValue('hat')).toBe('awesome hat');
306 }); 306 });
...@@ -311,15 +311,9 @@ describe('Rivets.Binding', function() { ...@@ -311,15 +311,9 @@ describe('Rivets.Binding', function() {
311 expect(binding.formattedValue('hat')).toBe('model awesome hat'); 311 expect(binding.formattedValue('hat')).toBe('model awesome hat');
312 }); 312 });
313 313
314 it('uses formatters from the bind options', function() {
315 opts.formatters = { optAwesome: function(value) { return 'option awesome ' + value; } };
316 binding.formatters.push("optAwesome");
317 expect(binding.formattedValue('hat')).toBe('option awesome hat');
318 });
319
320 describe('with a multi-argument formatter string', function() { 314 describe('with a multi-argument formatter string', function() {
321 beforeEach(function() { 315 beforeEach(function() {
322 rivets.formatters.awesome = function(value, prefix) { 316 view.formatters.awesome = function(value, prefix) {
323 return prefix + ' awesome ' + value; 317 return prefix + ' awesome ' + value;
324 }; 318 };
325 binding.formatters.push('awesome super'); 319 binding.formatters.push('awesome super');
......
...@@ -11,12 +11,12 @@ unless String::trim then String::trim = -> @replace /^\s+|\s+$/g, '' ...@@ -11,12 +11,12 @@ unless String::trim then String::trim = -> @replace /^\s+|\s+$/g, ''
11 11
12 # A single binding between a model attribute and a DOM element. 12 # A single binding between a model attribute and a DOM element.
13 class Rivets.Binding 13 class Rivets.Binding
14 # All information about the binding is passed into the constructor; the DOM 14 # All information about the binding is passed into the constructor; the
15 # element, the type of binding, the model object and the keypath at which 15 # containing view, the DOM node, the type of binding, the model object and the
16 # to listen for changes. 16 # keypath at which to listen for changes.
17 constructor: (@el, @type, @model, @keypath, @options = {}) -> 17 constructor: (@view, @el, @type, @model, @keypath, @options = {}) ->
18 unless @binder = Rivets.binders[type] 18 unless @binder = @view.binders[type]
19 for identifier, value of Rivets.binders 19 for identifier, value of @view.binders
20 if identifier isnt '*' and identifier.indexOf('*') isnt -1 20 if identifier isnt '*' and identifier.indexOf('*') isnt -1
21 regexp = new RegExp "^#{identifier.replace('*', '.+')}$" 21 regexp = new RegExp "^#{identifier.replace('*', '.+')}$"
22 if regexp.test type 22 if regexp.test type
...@@ -24,7 +24,7 @@ class Rivets.Binding ...@@ -24,7 +24,7 @@ class Rivets.Binding
24 @args = new RegExp("^#{identifier.replace('*', '(.+)')}$").exec type 24 @args = new RegExp("^#{identifier.replace('*', '(.+)')}$").exec type
25 @args.shift() 25 @args.shift()
26 26
27 @binder or= Rivets.binders['*'] 27 @binder or= @view.binders['*']
28 28
29 if @binder instanceof Function 29 if @binder instanceof Function
30 @binder = {routine: @binder} 30 @binder = {routine: @binder}
...@@ -43,7 +43,7 @@ class Rivets.Binding ...@@ -43,7 +43,7 @@ class Rivets.Binding
43 else if @options?.bindingOptions?.formatters?[id] instanceof Function 43 else if @options?.bindingOptions?.formatters?[id] instanceof Function
44 @options.bindingOptions.formatters[id] 44 @options.bindingOptions.formatters[id]
45 else 45 else
46 Rivets.formatters[id] 46 @view.formatters[id]
47 47
48 if formatter?.read instanceof Function 48 if formatter?.read instanceof Function
49 value = formatter.read value, args... 49 value = formatter.read value, args...
...@@ -67,7 +67,7 @@ class Rivets.Binding ...@@ -67,7 +67,7 @@ class Rivets.Binding
67 @set if @options.bypass 67 @set if @options.bypass
68 @model[@keypath] 68 @model[@keypath]
69 else 69 else
70 Rivets.config.adapter.read @model, @keypath 70 @view.config.adapter.read @model, @keypath
71 71
72 # Publishes the value currently set on the input element back to the model. 72 # Publishes the value currently set on the input element back to the model.
73 publish: => 73 publish: =>
...@@ -77,10 +77,10 @@ class Rivets.Binding ...@@ -77,10 +77,10 @@ class Rivets.Binding
77 args = formatter.split /\s+/ 77 args = formatter.split /\s+/
78 id = args.shift() 78 id = args.shift()
79 79
80 if Rivets.formatters[id]?.publish 80 if @view.formatters[id]?.publish
81 value = Rivets.formatters[id].publish value, args... 81 value = @view.formatters[id].publish value, args...
82 82
83 Rivets.config.adapter.publish @model, @keypath, value 83 @view.config.adapter.publish @model, @keypath, value
84 84
85 # Subscribes to the model for changes at the specified keypath. Bi-directional 85 # Subscribes to the model for changes at the specified keypath. Bi-directional
86 # routines will also listen for changes on the element to propagate them back 86 # routines will also listen for changes on the element to propagate them back
...@@ -91,8 +91,8 @@ class Rivets.Binding ...@@ -91,8 +91,8 @@ class Rivets.Binding
91 if @options.bypass 91 if @options.bypass
92 @sync() 92 @sync()
93 else 93 else
94 Rivets.config.adapter.subscribe @model, @keypath, @sync 94 @view.config.adapter.subscribe @model, @keypath, @sync
95 @sync() if Rivets.config.preloadData 95 @sync() if @view.config.preloadData
96 96
97 if @options.dependencies?.length 97 if @options.dependencies?.length
98 for dependency in @options.dependencies 98 for dependency in @options.dependencies
...@@ -104,14 +104,14 @@ class Rivets.Binding ...@@ -104,14 +104,14 @@ class Rivets.Binding
104 model = @view.models[dependency.shift()] 104 model = @view.models[dependency.shift()]
105 keypath = dependency.join '.' 105 keypath = dependency.join '.'
106 106
107 Rivets.config.adapter.subscribe model, keypath, @sync 107 @view.config.adapter.subscribe model, keypath, @sync
108 108
109 # Unsubscribes from the model and the element. 109 # Unsubscribes from the model and the element.
110 unbind: => 110 unbind: =>
111 @binder.unbind?.call @, @el 111 @binder.unbind?.call @, @el
112 112
113 unless @options.bypass 113 unless @options.bypass
114 Rivets.config.adapter.unsubscribe @model, @keypath, @sync 114 @view.config.adapter.unsubscribe @model, @keypath, @sync
115 115
116 if @options.dependencies?.length 116 if @options.dependencies?.length
117 for dependency in @options.dependencies 117 for dependency in @options.dependencies
...@@ -123,19 +123,26 @@ class Rivets.Binding ...@@ -123,19 +123,26 @@ class Rivets.Binding
123 model = @view.models[dependency.shift()] 123 model = @view.models[dependency.shift()]
124 keypath = dependency.join '.' 124 keypath = dependency.join '.'
125 125
126 Rivets.config.adapter.unsubscribe model, keypath, @sync 126 @view.config.adapter.unsubscribe model, keypath, @sync
127 127
128 # A collection of bindings built from a set of parent elements. 128 # A collection of bindings built from a set of parent elements.
129 class Rivets.View 129 class Rivets.View
130 # The DOM elements and the model objects for binding are passed into the 130 # The DOM elements and the model objects for binding are passed into the
131 # constructor. 131 # constructor along with any local options that should be used throughout the
132 constructor: (@els, @models, @options) -> 132 # context of the view and it's bindings.
133 constructor: (@els, @models, @options = {}) ->
133 @els = [@els] unless (@els.jquery || @els instanceof Array) 134 @els = [@els] unless (@els.jquery || @els instanceof Array)
135
136 for option in ['config', 'binders', 'formatters']
137 @[option] = {}
138 @[option][k] = v for k, v of @options[option] if @options[option]
139 @[option][k] ?= v for k, v of Rivets[option]
140
134 @build() 141 @build()
135 142
136 # Regular expression used to match binding attributes. 143 # Regular expression used to match binding attributes.
137 bindingRegExp: => 144 bindingRegExp: =>
138 prefix = Rivets.config.prefix 145 prefix = @config.prefix
139 if prefix then new RegExp("^data-#{prefix}-") else /^data-/ 146 if prefix then new RegExp("^data-#{prefix}-") else /^data-/
140 147
141 # Parses the DOM tree and builds Rivets.Binding instances for every matched 148 # Parses the DOM tree and builds Rivets.Binding instances for every matched
...@@ -151,14 +158,14 @@ class Rivets.View ...@@ -151,14 +158,14 @@ class Rivets.View
151 for attribute in node.attributes 158 for attribute in node.attributes
152 if bindingRegExp.test attribute.name 159 if bindingRegExp.test attribute.name
153 type = attribute.name.replace bindingRegExp, '' 160 type = attribute.name.replace bindingRegExp, ''
154 unless binder = Rivets.binders[type] 161 unless binder = @binders[type]
155 for identifier, value of Rivets.binders 162 for identifier, value of @binders
156 if identifier isnt '*' and identifier.indexOf('*') isnt -1 163 if identifier isnt '*' and identifier.indexOf('*') isnt -1
157 regexp = new RegExp "^#{identifier.replace('*', '.+')}$" 164 regexp = new RegExp "^#{identifier.replace('*', '.+')}$"
158 if regexp.test type 165 if regexp.test type
159 binder = value 166 binder = value
160 167
161 binder or= Rivets.binders['*'] 168 binder or= @binders['*']
162 169
163 if binder.block 170 if binder.block
164 skipNodes.push n for n in node.getElementsByTagName '*' 171 skipNodes.push n for n in node.getElementsByTagName '*'
...@@ -167,10 +174,6 @@ class Rivets.View ...@@ -167,10 +174,6 @@ class Rivets.View
167 for attribute in attributes or node.attributes 174 for attribute in attributes or node.attributes
168 if bindingRegExp.test attribute.name 175 if bindingRegExp.test attribute.name
169 options = {} 176 options = {}
170
171 if @options? and typeof @options is 'object'
172 options.bindingOptions = @options
173
174 type = attribute.name.replace bindingRegExp, '' 177 type = attribute.name.replace bindingRegExp, ''
175 pipes = (pipe.trim() for pipe in attribute.value.split '|') 178 pipes = (pipe.trim() for pipe in attribute.value.split '|')
176 context = (ctx.trim() for ctx in pipes.shift().split '<') 179 context = (ctx.trim() for ctx in pipes.shift().split '<')
...@@ -189,10 +192,7 @@ class Rivets.View ...@@ -189,10 +192,7 @@ class Rivets.View
189 if dependencies = context.shift() 192 if dependencies = context.shift()
190 options.dependencies = dependencies.split /\s+/ 193 options.dependencies = dependencies.split /\s+/
191 194
192 binding = new Rivets.Binding node, type, model, keypath, options 195 @bindings.push new Rivets.Binding @, node, type, model, keypath, options
193 binding.view = @
194
195 @bindings.push binding
196 196
197 attributes = null if attributes 197 attributes = null if attributes
198 198
...@@ -344,7 +344,7 @@ Rivets.binders = ...@@ -344,7 +344,7 @@ Rivets.binders =
344 "each-*": 344 "each-*":
345 block: true 345 block: true
346 bind: (el, collection) -> 346 bind: (el, collection) ->
347 el.removeAttribute ['data', Rivets.config.prefix, @type].join('-').replace '--', '-' 347 el.removeAttribute ['data', @view.config.prefix, @type].join('-').replace '--', '-'
348 unbind: (el, collection) -> 348 unbind: (el, collection) ->
349 view.unbind() for view in @iterated if @iterated? 349 view.unbind() for view in @iterated if @iterated?
350 routine: (el, collection) -> 350 routine: (el, collection) ->
...@@ -372,7 +372,7 @@ Rivets.binders = ...@@ -372,7 +372,7 @@ Rivets.binders =
372 @marker 372 @marker
373 373
374 @marker.parentNode.insertBefore itemEl, previous.nextSibling ? null 374 @marker.parentNode.insertBefore itemEl, previous.nextSibling ? null
375 view = new Rivets.View(itemEl, data) 375 view = new Rivets.View(itemEl, data, @view.options)
376 view.bind() 376 view.bind()
377 @iterated.push view 377 @iterated.push view
378 378
......