Merge pull request #186 from mikeric/components
Components
Showing
1 changed file
with
71 additions
and
2 deletions
... | @@ -155,6 +155,56 @@ class Rivets.Binding | ... | @@ -155,6 +155,56 @@ class Rivets.Binding |
155 | 155 | ||
156 | @binder.update?.call @, models | 156 | @binder.update?.call @, models |
157 | 157 | ||
158 | # Rivets.ComponentBinding | ||
159 | # ----------------------- | ||
160 | |||
161 | # A component view encapsulated as a binding within it's parent view. | ||
162 | class Rivets.ComponentBinding extends Rivets.Binding | ||
163 | # Initializes a component binding for the specified view. The raw component | ||
164 | # element is passed in along with the component type. Attributes and scope | ||
165 | # inflections are determined based on the components defined attributes. | ||
166 | constructor: (@view, @el, @type) -> | ||
167 | @component = Rivets.components[@type] | ||
168 | @attributes = {} | ||
169 | @inflections = {} | ||
170 | |||
171 | for attribute in @el.attributes or [] | ||
172 | if attribute.name in @component.attributes | ||
173 | @attributes[attribute.name] = attribute.value | ||
174 | else | ||
175 | @inflections[attribute.name] = attribute.value | ||
176 | |||
177 | # Intercepts `Rivets.Binding::sync` since component bindings are not bound to a | ||
178 | # particular model to update it's value. | ||
179 | sync: -> | ||
180 | |||
181 | # Returns an object map using the component's scope inflections. | ||
182 | locals: (models = @view.models) => | ||
183 | result = {} | ||
184 | result[key] = models[inverse] for key, inverse of @inflections | ||
185 | result[key] ?= model for key, model of models | ||
186 | |||
187 | result | ||
188 | |||
189 | # Intercepts `Rivets.Binding::update` to be called on `@componentView` with a | ||
190 | # localized map of the models. | ||
191 | update: (models) => | ||
192 | @componentView?.update @locals models | ||
193 | |||
194 | # Intercepts `Rivets.Binding::bind` to build `@componentView` with a localized | ||
195 | # map of models from the root view. Bind `@componentView` on subsequent calls. | ||
196 | bind: => | ||
197 | if @componentView? | ||
198 | @componentView?.bind() | ||
199 | else | ||
200 | el = @component.build.call @attributes | ||
201 | (@componentView = new Rivets.View(el, @locals(), @view.options)).bind() | ||
202 | @el.parentNode.replaceChild el, @el | ||
203 | |||
204 | # Intercept `Rivets.Binding::unbind` to be called on `@componentView`. | ||
205 | unbind: => | ||
206 | @componentView?.unbind() | ||
207 | |||
158 | # Rivets.View | 208 | # Rivets.View |
159 | # ----------- | 209 | # ----------- |
160 | 210 | ||
... | @@ -178,13 +228,18 @@ class Rivets.View | ... | @@ -178,13 +228,18 @@ class Rivets.View |
178 | prefix = @config.prefix | 228 | prefix = @config.prefix |
179 | if prefix then new RegExp("^data-#{prefix}-") else /^data-/ | 229 | if prefix then new RegExp("^data-#{prefix}-") else /^data-/ |
180 | 230 | ||
231 | # Regular expression used to match component nodes. | ||
232 | componentRegExp: => | ||
233 | new RegExp "^#{@config.prefix?.toUpperCase() ? 'RV'}-" | ||
234 | |||
181 | # Parses the DOM tree and builds `Rivets.Binding` instances for every matched | 235 | # Parses the DOM tree and builds `Rivets.Binding` instances for every matched |
182 | # binding declaration. Subsequent calls to build will replace the previous | 236 | # binding declaration. |
183 | # `Rivets.Binding` instances with new ones, so be sure to unbind them first. | ||
184 | build: => | 237 | build: => |
185 | @bindings = [] | 238 | @bindings = [] |
186 | skipNodes = [] | 239 | skipNodes = [] |
187 | bindingRegExp = @bindingRegExp() | 240 | bindingRegExp = @bindingRegExp() |
241 | componentRegExp = @componentRegExp() | ||
242 | |||
188 | 243 | ||
189 | buildBinding = (node, type, declaration) => | 244 | buildBinding = (node, type, declaration) => |
190 | options = {} | 245 | options = {} |
... | @@ -227,6 +282,9 @@ class Rivets.View | ... | @@ -227,6 +282,9 @@ class Rivets.View |
227 | for token in restTokens | 282 | for token in restTokens |
228 | node.parentNode.appendChild (text = document.createTextNode token.value) | 283 | node.parentNode.appendChild (text = document.createTextNode token.value) |
229 | buildBinding text, 'textNode', token.value if token.type is 1 | 284 | buildBinding text, 'textNode', token.value if token.type is 1 |
285 | else if componentRegExp.test node.tagName | ||
286 | type = node.tagName.replace(componentRegExp, '').toLowerCase() | ||
287 | @bindings.push new Rivets.ComponentBinding @, node, type | ||
230 | 288 | ||
231 | else if node.attributes? | 289 | else if node.attributes? |
232 | for attribute in node.attributes | 290 | for attribute in node.attributes |
... | @@ -591,6 +649,14 @@ Rivets.internalBinders = | ... | @@ -591,6 +649,14 @@ Rivets.internalBinders = |
591 | textNode: (node, value) -> | 649 | textNode: (node, value) -> |
592 | node.data = value ? '' | 650 | node.data = value ? '' |
593 | 651 | ||
652 | # Rivets.components | ||
653 | # ----------------- | ||
654 | |||
655 | # Default components (there aren't any), publicly accessible on | ||
656 | # `module.components`. Can be overridden globally or local to a `Rivets.View` | ||
657 | # instance. | ||
658 | Rivets.components = {} | ||
659 | |||
594 | # Rivets.config | 660 | # Rivets.config |
595 | # ------------- | 661 | # ------------- |
596 | 662 | ||
... | @@ -620,6 +686,9 @@ Rivets.factory = (exports) -> | ... | @@ -620,6 +686,9 @@ Rivets.factory = (exports) -> |
620 | # Exposes the core binding routines that can be extended or stripped down. | 686 | # Exposes the core binding routines that can be extended or stripped down. |
621 | exports.binders = Rivets.binders | 687 | exports.binders = Rivets.binders |
622 | 688 | ||
689 | # Exposes the components object to be extended. | ||
690 | exports.components = Rivets.components | ||
691 | |||
623 | # Exposes the formatters object to be extended. | 692 | # Exposes the formatters object to be extended. |
624 | exports.formatters = Rivets.formatters | 693 | exports.formatters = Rivets.formatters |
625 | 694 | ... | ... |
-
Please register or sign in to post a comment