PageRenderTime 45ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/vendor/assets/bower_components/rivets/src/bindings.coffee

https://gitlab.com/VoroninNick/radok
CoffeeScript | 279 lines | 207 code | 36 blank | 36 comment | 21 complexity | 1fcc641c88e10808e4d70a85a1725245 MD5 | raw file
  1. # Rivets.Binding
  2. # --------------
  3. # A single binding between a model attribute and a DOM element.
  4. class Rivets.Binding
  5. # All information about the binding is passed into the constructor; the
  6. # containing view, the DOM node, the type of binding, the model object and the
  7. # keypath at which to listen for changes.
  8. constructor: (@view, @el, @type, @keypath, @options = {}) ->
  9. @formatters = @options.formatters or []
  10. @dependencies = []
  11. @formatterObservers = {}
  12. @model = undefined
  13. @setBinder()
  14. # Sets the binder to use when binding and syncing.
  15. setBinder: =>
  16. unless @binder = @view.binders[@type]
  17. for identifier, value of @view.binders
  18. if identifier isnt '*' and identifier.indexOf('*') isnt -1
  19. regexp = new RegExp "^#{identifier.replace(/\*/g, '.+')}$"
  20. if regexp.test @type
  21. @binder = value
  22. @args = new RegExp("^#{identifier.replace(/\*/g, '(.+)')}$").exec @type
  23. @args.shift()
  24. @binder or= @view.binders['*']
  25. @binder = {routine: @binder} if @binder instanceof Function
  26. observe: (obj, keypath, callback) =>
  27. Rivets.sightglass obj, keypath, callback,
  28. root: @view.rootInterface
  29. adapters: @view.adapters
  30. parseTarget: =>
  31. token = Rivets.TypeParser.parse @keypath
  32. if token.type is 0
  33. @value = token.value
  34. else
  35. @observer = @observe @view.models, @keypath, @sync
  36. @model = @observer.target
  37. # Applies all the current formatters to the supplied value and returns the
  38. # formatted value.
  39. formattedValue: (value) =>
  40. for formatter, fi in @formatters
  41. args = formatter.match /[^\s']+|'([^']|'[^\s])*'|"([^"]|"[^\s])*"/g
  42. id = args.shift()
  43. formatter = @view.formatters[id]
  44. args = (Rivets.TypeParser.parse(arg) for arg in args)
  45. processedArgs = []
  46. for arg, ai in args
  47. processedArgs.push if arg.type is 0
  48. arg.value
  49. else
  50. @formatterObservers[fi] or= {}
  51. unless observer = @formatterObservers[fi][ai]
  52. observer = @observe @view.models, arg.value, @sync
  53. @formatterObservers[fi][ai] = observer
  54. observer.value()
  55. if formatter?.read instanceof Function
  56. value = formatter.read value, processedArgs...
  57. else if formatter instanceof Function
  58. value = formatter value, processedArgs...
  59. value
  60. # Returns an event handler for the binding around the supplied function.
  61. eventHandler: (fn) =>
  62. handler = (binding = @).view.handler
  63. (ev) -> handler.call fn, @, ev, binding
  64. # Sets the value for the binding. This Basically just runs the binding routine
  65. # with the suplied value formatted.
  66. set: (value) =>
  67. value = if value instanceof Function and !@binder.function
  68. @formattedValue value.call @model
  69. else
  70. @formattedValue value
  71. @binder.routine?.call @, @el, value
  72. # Syncs up the view binding with the model.
  73. sync: =>
  74. @set if @observer
  75. if @model isnt @observer.target
  76. observer.unobserve() for observer in @dependencies
  77. @dependencies = []
  78. if (@model = @observer.target)? and @options.dependencies?.length
  79. for dependency in @options.dependencies
  80. observer = @observe @model, dependency, @sync
  81. @dependencies.push observer
  82. @observer.value()
  83. else
  84. @value
  85. # Publishes the value currently set on the input element back to the model.
  86. publish: =>
  87. if @observer
  88. value = @getValue @el
  89. for formatter in @formatters.slice(0).reverse()
  90. args = formatter.split /\s+/
  91. id = args.shift()
  92. if @view.formatters[id]?.publish
  93. value = @view.formatters[id].publish value, args...
  94. @observer.setValue value
  95. # Subscribes to the model for changes at the specified keypath. Bi-directional
  96. # routines will also listen for changes on the element to propagate them back
  97. # to the model.
  98. bind: =>
  99. @parseTarget()
  100. @binder.bind?.call @, @el
  101. if @model? and @options.dependencies?.length
  102. for dependency in @options.dependencies
  103. observer = @observe @model, dependency, @sync
  104. @dependencies.push observer
  105. @sync() if @view.preloadData
  106. # Unsubscribes from the model and the element.
  107. unbind: =>
  108. @binder.unbind?.call @, @el
  109. @observer?.unobserve()
  110. observer.unobserve() for observer in @dependencies
  111. @dependencies = []
  112. for fi, args of @formatterObservers
  113. observer.unobserve() for ai, observer of args
  114. @formatterObservers = {}
  115. # Updates the binding's model from what is currently set on the view. Unbinds
  116. # the old model first and then re-binds with the new model.
  117. update: (models = {}) =>
  118. @model = @observer?.target
  119. @binder.update?.call @, models
  120. # Returns elements value
  121. getValue: (el) =>
  122. if @binder and @binder.getValue?
  123. @binder.getValue.call @, el
  124. else
  125. Rivets.Util.getInputValue el
  126. # Rivets.ComponentBinding
  127. # -----------------------
  128. # A component view encapsulated as a binding within it's parent view.
  129. class Rivets.ComponentBinding extends Rivets.Binding
  130. # Initializes a component binding for the specified view. The raw component
  131. # element is passed in along with the component type. Attributes and scope
  132. # inflections are determined based on the components defined attributes.
  133. constructor: (@view, @el, @type) ->
  134. @component = @view.components[@type]
  135. @static = {}
  136. @observers = {}
  137. @upstreamObservers = {}
  138. bindingRegExp = view.bindingRegExp()
  139. for attribute in @el.attributes or []
  140. unless bindingRegExp.test attribute.name
  141. propertyName = @camelCase attribute.name
  142. if propertyName in (@component.static ? [])
  143. @static[propertyName] = attribute.value
  144. else
  145. @observers[propertyName] = attribute.value
  146. # Intercepts `Rivets.Binding::sync` since component bindings are not bound to
  147. # a particular model to update it's value.
  148. sync: ->
  149. # Intercepts `Rivets.Binding::update` since component bindings are not bound
  150. # to a particular model to update it's value.
  151. update: ->
  152. # Intercepts `Rivets.Binding::publish` since component bindings are not bound
  153. # to a particular model to update it's value.
  154. publish: ->
  155. # Returns an object map using the component's scope inflections.
  156. locals: =>
  157. result = {}
  158. for key, value of @static
  159. result[key] = value
  160. for key, observer of @observers
  161. result[key] = observer.value()
  162. result
  163. # Returns a camel-cased version of the string. Used when translating an
  164. # element's attribute name into a property name for the component's scope.
  165. camelCase: (string) ->
  166. string.replace /-([a-z])/g, (grouped) ->
  167. grouped[1].toUpperCase()
  168. # Intercepts `Rivets.Binding::bind` to build `@componentView` with a localized
  169. # map of models from the root view. Bind `@componentView` on subsequent calls.
  170. bind: =>
  171. unless @bound
  172. for key, keypath of @observers
  173. @observers[key] = @observe @view.models, keypath, ((key) => =>
  174. @componentView.models[key] = @observers[key].value()
  175. ).call(@, key)
  176. @bound = true
  177. if @componentView?
  178. @componentView.bind()
  179. else
  180. @el.innerHTML = @component.template.call this
  181. scope = @component.initialize.call @, @el, @locals()
  182. @el._bound = true
  183. options = {}
  184. for option in Rivets.extensions
  185. options[option] = {}
  186. options[option][k] = v for k, v of @component[option] if @component[option]
  187. options[option][k] ?= v for k, v of @view[option]
  188. for option in Rivets.options
  189. options[option] = @component[option] ? @view[option]
  190. @componentView = new Rivets.View(@el, scope, options)
  191. @componentView.bind()
  192. for key, observer of @observers
  193. @upstreamObservers[key] = @observe @componentView.models, key, ((key, observer) => =>
  194. observer.setValue @componentView.models[key]
  195. ).call(@, key, observer)
  196. # Intercept `Rivets.Binding::unbind` to be called on `@componentView`.
  197. unbind: =>
  198. for key, observer of @upstreamObservers
  199. observer.unobserve()
  200. for key, observer of @observers
  201. observer.unobserve()
  202. @componentView?.unbind.call @
  203. # Rivets.TextBinding
  204. # -----------------------
  205. # A text node binding, defined internally to deal with text and element node
  206. # differences while avoiding it being overwritten.
  207. class Rivets.TextBinding extends Rivets.Binding
  208. # Initializes a text binding for the specified view and text node.
  209. constructor: (@view, @el, @type, @keypath, @options = {}) ->
  210. @formatters = @options.formatters or []
  211. @dependencies = []
  212. @formatterObservers = {}
  213. # A standard routine binder used for text node bindings.
  214. binder:
  215. routine: (node, value) ->
  216. node.data = value ? ''
  217. # Wrap the call to `sync` in fat-arrow to avoid function context issues.
  218. sync: =>
  219. super