compiler.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  1. var Emitter = require('emitter'),
  2. observe = require('./observe'),
  3. config = require('./config'),
  4. utils = require('./utils'),
  5. Binding = require('./binding'),
  6. DirectiveParser = require('./directive-parser'),
  7. TextParser = require('./text-parser'),
  8. DepsParser = require('./deps-parser')
  9. var slice = Array.prototype.slice
  10. // late bindings
  11. var vmAttr, eachAttr
  12. /*
  13. * The DOM compiler
  14. * scans a DOM node and compile bindings for a ViewModel
  15. */
  16. function Compiler (vm, options) {
  17. utils.log('\nnew Compiler instance: ', vm.$el, '\n')
  18. // need to refresh this everytime we compile
  19. eachAttr = config.prefix + '-each'
  20. vmAttr = config.prefix + '-viewmodel'
  21. // copy options
  22. options = options || {}
  23. for (var op in options) {
  24. this[op] = options[op]
  25. }
  26. this.vm = vm
  27. vm.$compiler = this
  28. this.el = vm.$el
  29. this.bindings = {}
  30. this.observer = new Emitter()
  31. this.directives = []
  32. this.watchers = {}
  33. // list of computed properties that need to parse dependencies for
  34. this.computed = []
  35. // list of bindings that has dynamic context dependencies
  36. this.contextBindings = []
  37. // setup observer
  38. this.setupObserver()
  39. // copy data if any
  40. var key, data = options.data
  41. if (data) {
  42. if (data instanceof vm.constructor) {
  43. data = utils.dump(data)
  44. }
  45. for (key in data) {
  46. vm[key] = data[key]
  47. }
  48. }
  49. // call user init
  50. if (options.init) {
  51. options.init.apply(vm, options.args || [])
  52. }
  53. // now parse the DOM
  54. this.compileNode(this.el, true)
  55. // for anything in viewmodel but not binded in DOM, create bindings for them
  56. for (key in vm) {
  57. if (vm.hasOwnProperty(key) &&
  58. key.charAt(0) !== '$' &&
  59. !this.bindings[key])
  60. {
  61. this.createBinding(key)
  62. }
  63. }
  64. // extract dependencies for computed properties
  65. if (this.computed.length) DepsParser.parse(this.computed)
  66. this.computed = null
  67. // extract dependencies for computed properties with dynamic context
  68. if (this.contextBindings.length) this.bindContexts(this.contextBindings)
  69. this.contextBindings = null
  70. utils.log('\ncompilation done.\n')
  71. }
  72. // for better compression
  73. var CompilerProto = Compiler.prototype
  74. /*
  75. * setup observer
  76. */
  77. CompilerProto.setupObserver = function () {
  78. var bindings = this.bindings, compiler = this
  79. this.observer
  80. .on('get', function (key) {
  81. if (DepsParser.observer.isObserving) {
  82. DepsParser.observer.emit('get', bindings[key])
  83. }
  84. })
  85. .on('set', function (key, val) {
  86. if (!bindings[key]) compiler.createBinding(key)
  87. bindings[key].update(val)
  88. })
  89. .on('mutate', function (key) {
  90. bindings[key].refresh()
  91. })
  92. }
  93. /*
  94. * Compile a DOM node (recursive)
  95. */
  96. CompilerProto.compileNode = function (node, root) {
  97. var compiler = this, i, j
  98. if (node.nodeType === 3) { // text node
  99. compiler.compileTextNode(node)
  100. } else if (node.nodeType === 1) {
  101. var eachExp = node.getAttribute(eachAttr),
  102. vmExp = node.getAttribute(vmAttr),
  103. directive
  104. if (eachExp) { // each block
  105. directive = DirectiveParser.parse(eachAttr, eachExp)
  106. if (directive) {
  107. directive.el = node
  108. compiler.bindDirective(directive)
  109. }
  110. } else if (vmExp && !root) { // nested ViewModels
  111. var ChildVM = utils.getVM(vmExp)
  112. if (ChildVM) {
  113. new ChildVM({
  114. el: node,
  115. child: true,
  116. parentCompiler: compiler
  117. })
  118. }
  119. } else { // normal node
  120. // parse if has attributes
  121. if (node.attributes && node.attributes.length) {
  122. var attrs = slice.call(node.attributes),
  123. attr, valid, exps, exp
  124. i = attrs.length
  125. while (i--) {
  126. attr = attrs[i]
  127. if (attr.name === vmAttr) continue
  128. valid = false
  129. exps = attr.value.split(',')
  130. j = exps.length
  131. while (j--) {
  132. exp = exps[j]
  133. directive = DirectiveParser.parse(attr.name, exp)
  134. if (directive) {
  135. valid = true
  136. directive.el = node
  137. compiler.bindDirective(directive)
  138. }
  139. }
  140. if (valid) node.removeAttribute(attr.name)
  141. }
  142. }
  143. // recursively compile childNodes
  144. if (node.childNodes.length) {
  145. var nodes = slice.call(node.childNodes)
  146. for (i = 0, j = nodes.length; i < j; i++) {
  147. this.compileNode(nodes[i])
  148. }
  149. }
  150. }
  151. }
  152. }
  153. /*
  154. * Compile a text node
  155. */
  156. CompilerProto.compileTextNode = function (node) {
  157. var tokens = TextParser.parse(node)
  158. if (!tokens) return
  159. var compiler = this,
  160. dirname = config.prefix + '-text',
  161. el, token, directive
  162. for (var i = 0, l = tokens.length; i < l; i++) {
  163. token = tokens[i]
  164. el = document.createTextNode('')
  165. if (token.key) {
  166. directive = DirectiveParser.parse(dirname, token.key)
  167. if (directive) {
  168. directive.el = el
  169. compiler.bindDirective(directive)
  170. }
  171. } else {
  172. el.nodeValue = token
  173. }
  174. node.parentNode.insertBefore(el, node)
  175. }
  176. node.parentNode.removeChild(node)
  177. }
  178. /*
  179. * Create binding and attach getter/setter for a key to the viewmodel object
  180. */
  181. CompilerProto.createBinding = function (key) {
  182. utils.log(' created binding: ' + key)
  183. var binding = new Binding(this, key)
  184. this.bindings[key] = binding
  185. var baseKey = key.split('.')[0]
  186. if (binding.root) {
  187. // this is a root level binding. we need to define getter/setters for it.
  188. this.define(baseKey, binding)
  189. } else if (!this.bindings[baseKey]) {
  190. // this is a nested value binding, but the binding for its root
  191. // has not been created yet. We better create that one too.
  192. this.createBinding(baseKey)
  193. }
  194. return binding
  195. }
  196. /*
  197. * Defines the getter/setter for a top-level binding on the VM
  198. * and observe the initial value
  199. */
  200. CompilerProto.define = function (key, binding) {
  201. utils.log(' defined root binding: ' + key)
  202. var compiler = this,
  203. value = binding.value = this.vm[key] // save the value before redefinening it
  204. if (utils.typeOf(value) === 'Object' && value.get) {
  205. binding.isComputed = true
  206. binding.rawGet = value.get
  207. value.get = value.get.bind(this.vm)
  208. this.computed.push(binding)
  209. } else {
  210. observe(value, key, compiler.observer) // start observing right now
  211. }
  212. Object.defineProperty(this.vm, key, {
  213. enumerable: true,
  214. get: function () {
  215. if (!binding.isComputed && !binding.value.__observer__) {
  216. // only emit non-computed, non-observed values
  217. // because these are the cleanest dependencies
  218. compiler.observer.emit('get', key)
  219. }
  220. return binding.isComputed
  221. ? binding.value.get({
  222. el: compiler.el,
  223. vm: compiler.vm
  224. })
  225. : binding.value
  226. },
  227. set: function (value) {
  228. if (binding.isComputed) {
  229. if (binding.value.set) {
  230. binding.value.set(value)
  231. }
  232. } else if (value !== binding.value) {
  233. compiler.observer.emit('set', key, value)
  234. observe(value, key, compiler.observer)
  235. }
  236. }
  237. })
  238. }
  239. /*
  240. * Add a directive instance to the correct binding & viewmodel
  241. */
  242. CompilerProto.bindDirective = function (directive) {
  243. this.directives.push(directive)
  244. directive.compiler = this
  245. directive.vm = this.vm
  246. var key = directive.key,
  247. compiler = this
  248. // deal with each block
  249. if (this.each) {
  250. if (key.indexOf(this.eachPrefix) === 0) {
  251. key = directive.key = key.replace(this.eachPrefix, '')
  252. } else {
  253. compiler = this.parentCompiler
  254. }
  255. }
  256. // deal with nesting
  257. compiler = traceOwnerCompiler(directive, compiler)
  258. var binding = compiler.bindings[key] || compiler.createBinding(key)
  259. binding.instances.push(directive)
  260. directive.binding = binding
  261. // for newly inserted sub-VMs (each items), need to bind deps
  262. // because they didn't get processed when the parent compiler
  263. // was binding dependencies.
  264. var i, dep
  265. if (binding.contextDeps) {
  266. i = binding.contextDeps.length
  267. while (i--) {
  268. dep = this.bindings[binding.contextDeps[i]]
  269. dep.subs.push(directive)
  270. }
  271. }
  272. // invoke bind hook if exists
  273. if (directive.bind) {
  274. directive.bind(binding.value)
  275. }
  276. // set initial value
  277. directive.update(binding.value)
  278. if (binding.isComputed) {
  279. directive.refresh()
  280. }
  281. }
  282. /*
  283. * Process subscriptions for computed properties that has
  284. * dynamic context dependencies
  285. */
  286. CompilerProto.bindContexts = function (bindings) {
  287. var i = bindings.length, j, k, binding, depKey, dep, ins
  288. while (i--) {
  289. binding = bindings[i]
  290. j = binding.contextDeps.length
  291. while (j--) {
  292. depKey = binding.contextDeps[j]
  293. k = binding.instances.length
  294. while (k--) {
  295. ins = binding.instances[k]
  296. dep = ins.compiler.bindings[depKey]
  297. dep.subs.push(ins)
  298. }
  299. }
  300. }
  301. }
  302. /*
  303. * Unbind and remove element
  304. */
  305. CompilerProto.destroy = function () {
  306. utils.log('compiler destroyed: ', this.vm.$el)
  307. var i, key, dir, inss
  308. // remove all directives that are instances of external bindings
  309. i = this.directives.length
  310. while (i--) {
  311. dir = this.directives[i]
  312. if (dir.binding.compiler !== this) {
  313. inss = dir.binding.instances
  314. if (inss) inss.splice(inss.indexOf(dir), 1)
  315. }
  316. dir.unbind()
  317. }
  318. // unbind all bindings
  319. for (key in this.bindings) {
  320. this.bindings[key].unbind()
  321. }
  322. // remove el
  323. this.el.parentNode.removeChild(this.el)
  324. }
  325. // Helpers --------------------------------------------------------------------
  326. /*
  327. * determine which viewmodel a key belongs to based on nesting symbols
  328. */
  329. function traceOwnerCompiler (key, compiler) {
  330. if (key.nesting) {
  331. var levels = key.nesting
  332. while (compiler.parentCompiler && levels--) {
  333. compiler = compiler.parentCompiler
  334. }
  335. } else if (key.root) {
  336. while (compiler.parentCompiler) {
  337. compiler = compiler.parentCompiler
  338. }
  339. }
  340. return compiler
  341. }
  342. module.exports = Compiler