Merge pull request #80 from mikeric/advanced-custom-binders-api
Advanced custom binders API
Showing
3 changed files
with
150 additions
and
168 deletions
... | @@ -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('<em>gluten-free</em>'); | 23 | expect(el.innerHTML).toBe('<em>gluten-free</em>'); |
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, ''] | ||
140 | skipNodes.push n for n in node.getElementsByTagName '*' | ||
141 | iterator = [attribute] | ||
142 | 132 | ||
143 | for attribute in iterator or node.attributes | 133 | if binder.block |
134 | skipNodes.push n for n in node.getElementsByTagName '*' | ||
135 | attributes = [attribute] | ||
136 | |||
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 | |
304 | if el.type is 'radio' | 241 | checked: |
305 | el.checked = el.value is value | 242 | publishes: true |
306 | else | 243 | bind: (el) -> |
307 | el.checked = !!value | 244 | bindEvent el, 'change', @publish |
308 | unchecked: (el, value) -> | 245 | unbind: (el) -> |
309 | if el.type is 'radio' | 246 | unbindEvent el, 'change', @publish |
310 | el.checked = el.value isnt value | 247 | routine: (el, value) -> |
311 | else | 248 | if el.type is 'radio' |
312 | el.checked = !value | 249 | el.checked = el.value is value |
250 | else | ||
251 | el.checked = !!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) -> | ||
260 | if el.type is 'radio' | ||
261 | el.checked = el.value isnt value | ||
262 | else | ||
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 '' |
317 | html: (el, value) -> | 270 | |
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 | |
320 | if el.type is 'select-multiple' | 274 | value: |
321 | o.selected = o.value in value for o in el if value? | 275 | publishes: true |
322 | else | 276 | bind: (el) -> |
323 | el.value = if value? then value else '' | 277 | bindEvent el, 'change', @publish |
278 | unbind: (el) -> | ||
279 | unbindEvent el, 'change', @publish | ||
280 | routine: (el, value) -> | ||
281 | if el.type is 'select-multiple' | ||
282 | o.selected = o.value in value for o in el if value? | ||
283 | 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 | ... | ... |
-
Please register or sign in to post a comment