compiler.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. var config = require('./config'),
  2. utils = require('./utils'),
  3. Binding = require('./binding'),
  4. DirectiveParser = require('./directive-parser'),
  5. TextParser = require('./text-parser'),
  6. DepsParser = require('./deps-parser'),
  7. eventbus = require('./utils').eventbus
  8. var slice = Array.prototype.slice,
  9. ctrlAttr = config.prefix + '-controller',
  10. eachAttr = config.prefix + '-each'
  11. /*
  12. * The DOM compiler
  13. * scans a DOM node and compile bindings for a ViewModel
  14. */
  15. function Compiler (vm, options) {
  16. utils.log('\ncreated new Compiler instance.\n')
  17. // copy options
  18. options = options || {}
  19. for (var op in options) {
  20. this[op] = options[op]
  21. }
  22. this.vm = vm
  23. vm.$compiler = this
  24. this.el = vm.$el
  25. this.bindings = {}
  26. this.directives = []
  27. this.watchers = {}
  28. this.listeners = []
  29. // list of computed properties that need to parse dependencies for
  30. this.computed = []
  31. // list of bindings that has dynamic context dependencies
  32. this.contextBindings = []
  33. // copy data if any
  34. var data = options.data
  35. if (data) {
  36. if (data instanceof vm.constructor) {
  37. data = utils.dump(data)
  38. }
  39. for (var key in data) {
  40. vm[key] = data[key]
  41. }
  42. }
  43. // call user init
  44. if (options.initialize) {
  45. options.initialize.apply(vm, options.args || [])
  46. }
  47. // now parse the DOM
  48. this.compileNode(this.el, true)
  49. // for anything in viewmodel but not binded in DOM, create bindings for them
  50. for (var key in vm) {
  51. if (vm.hasOwnProperty(key) &&
  52. key.charAt(0) !== '$' &&
  53. !this.bindings[key])
  54. {
  55. this.createBinding(key)
  56. }
  57. }
  58. // extract dependencies for computed properties
  59. if (this.computed.length) DepsParser.parse(this.computed)
  60. this.computed = null
  61. // extract dependencies for computed properties with dynamic context
  62. if (this.contextBindings.length) this.bindContexts(this.contextBindings)
  63. this.contextBindings = null
  64. }
  65. // for better compression
  66. var CompilerProto = Compiler.prototype
  67. /*
  68. * Compile a DOM node (recursive)
  69. */
  70. CompilerProto.compileNode = function (node, root) {
  71. var compiler = this
  72. if (node.nodeType === 3) { // text node
  73. compiler.compileTextNode(node)
  74. } else if (node.nodeType === 1) {
  75. var eachExp = node.getAttribute(eachAttr),
  76. ctrlExp = node.getAttribute(ctrlAttr),
  77. directive
  78. if (eachExp) { // each block
  79. directive = DirectiveParser.parse(eachAttr, eachExp)
  80. if (directive) {
  81. directive.el = node
  82. compiler.bindDirective(directive)
  83. }
  84. } else if (ctrlExp && !root) { // nested controllers
  85. new Compiler(node, {
  86. child: true,
  87. parentCompiler: compiler
  88. })
  89. } else { // normal node
  90. // parse if has attributes
  91. if (node.attributes && node.attributes.length) {
  92. var attrs = slice.call(node.attributes),
  93. i = attrs.length, attr, j, valid, exps, exp
  94. while (i--) {
  95. attr = attrs[i]
  96. if (attr.name === ctrlAttr) continue
  97. valid = false
  98. exps = attr.value.split(',')
  99. j = exps.length
  100. while (j--) {
  101. exp = exps[j]
  102. directive = DirectiveParser.parse(attr.name, exp)
  103. if (directive) {
  104. valid = true
  105. directive.el = node
  106. compiler.bindDirective(directive)
  107. }
  108. }
  109. if (valid) node.removeAttribute(attr.name)
  110. }
  111. }
  112. // recursively compile childNodes
  113. if (node.childNodes.length) {
  114. slice.call(node.childNodes).forEach(compiler.compileNode, compiler)
  115. }
  116. }
  117. }
  118. }
  119. /*
  120. * Compile a text node
  121. */
  122. CompilerProto.compileTextNode = function (node) {
  123. var tokens = TextParser.parse(node)
  124. if (!tokens) return
  125. var compiler = this,
  126. dirname = config.prefix + '-text',
  127. el, token, directive
  128. for (var i = 0, l = tokens.length; i < l; i++) {
  129. token = tokens[i]
  130. el = document.createTextNode('')
  131. if (token.key) {
  132. directive = DirectiveParser.parse(dirname, token.key)
  133. if (directive) {
  134. directive.el = el
  135. compiler.bindDirective(directive)
  136. }
  137. } else {
  138. el.nodeValue = token
  139. }
  140. node.parentNode.insertBefore(el, node)
  141. }
  142. node.parentNode.removeChild(node)
  143. }
  144. /*
  145. * Create binding and attach getter/setter for a key to the viewmodel object
  146. */
  147. CompilerProto.createBinding = function (key) {
  148. utils.log(' created binding: ' + key)
  149. var binding = new Binding(this, key)
  150. this.bindings[key] = binding
  151. if (binding.isComputed) this.computed.push(binding)
  152. return binding
  153. }
  154. /*
  155. * Add a directive instance to the correct binding & viewmodel
  156. */
  157. CompilerProto.bindDirective = function (directive) {
  158. this.directives.push(directive)
  159. directive.compiler = this
  160. directive.vm = this.vm
  161. var key = directive.key,
  162. compiler = this
  163. // deal with each block
  164. if (this.each) {
  165. if (key.indexOf(this.eachPrefix) === 0) {
  166. key = directive.key = key.replace(this.eachPrefix, '')
  167. } else {
  168. compiler = this.parentCompiler
  169. }
  170. }
  171. // deal with nesting
  172. compiler = traceOwnerCompiler(directive, compiler)
  173. var binding = compiler.bindings[key] || compiler.createBinding(key)
  174. binding.instances.push(directive)
  175. directive.binding = binding
  176. // for newly inserted sub-VMs (each items), need to bind deps
  177. // because they didn't get processed when the parent compiler
  178. // was binding dependencies.
  179. var i, dep
  180. if (binding.contextDeps) {
  181. i = binding.contextDeps.length
  182. while (i--) {
  183. dep = this.bindings[binding.contextDeps[i]]
  184. dep.subs.push(directive)
  185. }
  186. }
  187. // invoke bind hook if exists
  188. if (directive.bind) {
  189. directive.bind(binding.value)
  190. }
  191. // set initial value
  192. directive.update(binding.value)
  193. if (binding.isComputed) {
  194. directive.refresh()
  195. }
  196. }
  197. /*
  198. * Process subscriptions for computed properties that has
  199. * dynamic context dependencies
  200. */
  201. CompilerProto.bindContexts = function (bindings) {
  202. var i = bindings.length, j, k, binding, depKey, dep, ins
  203. while (i--) {
  204. binding = bindings[i]
  205. j = binding.contextDeps.length
  206. while (j--) {
  207. depKey = binding.contextDeps[j]
  208. k = binding.instances.length
  209. while (k--) {
  210. ins = binding.instances[k]
  211. dep = ins.compiler.bindings[depKey]
  212. dep.subs.push(ins)
  213. }
  214. }
  215. }
  216. }
  217. /*
  218. * Unbind and remove element
  219. */
  220. CompilerProto.destroy = function () {
  221. var i, key, dir, listener, inss
  222. // remove all directives that are instances of external bindings
  223. i = this.directives.length
  224. while (i--) {
  225. dir = this.directives[i]
  226. if (dir.binding.compiler !== this) {
  227. inss = dir.binding.instances
  228. if (inss) inss.splice(inss.indexOf(dir), 1)
  229. }
  230. dir.unbind()
  231. }
  232. // remove all listeners on eventbus
  233. i = this.listeners.length
  234. while (i--) {
  235. listener = this.listeners[i]
  236. eventbus.off(listener.event, listener.handler)
  237. }
  238. // unbind all bindings
  239. for (key in this.bindings) {
  240. this.bindings[key].unbind()
  241. }
  242. // remove el
  243. this.el.parentNode.removeChild(this.el)
  244. }
  245. // Helpers --------------------------------------------------------------------
  246. /*
  247. * determine which viewmodel a key belongs to based on nesting symbols
  248. */
  249. function traceOwnerCompiler (key, compiler) {
  250. if (key.nesting) {
  251. var levels = key.nesting
  252. while (compiler.parentCompiler && levels--) {
  253. compiler = compiler.parentCompiler
  254. }
  255. } else if (key.root) {
  256. while (compiler.parentCompiler) {
  257. compiler = compiler.parentCompiler
  258. }
  259. }
  260. return compiler
  261. }
  262. module.exports = Compiler