compiler.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
  1. var Emitter = require('emitter'),
  2. Observer = require('./observer'),
  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. // need to refresh this everytime we compile
  18. eachAttr = config.prefix + '-each'
  19. vmAttr = config.prefix + '-viewmodel'
  20. // copy options
  21. options = options || {}
  22. for (var op in options) {
  23. this[op] = options[op]
  24. }
  25. // determine el
  26. var tpl = options.template,
  27. el = options.el
  28. el = typeof el === 'string'
  29. ? document.querySelector(el)
  30. : el
  31. if (el) {
  32. var tplExp = tpl || el.getAttribute(config.prefix + '-template')
  33. if (tplExp) {
  34. el.innerHTML = utils.getTemplate(tplExp) || ''
  35. el.removeAttribute(config.prefix + '-template')
  36. }
  37. } else if (tpl) {
  38. var template = utils.getTemplate(tpl)
  39. if (template) {
  40. var tplHolder = document.createElement('div')
  41. tplHolder.innerHTML = template
  42. el = tplHolder.childNodes[0]
  43. }
  44. }
  45. utils.log('\nnew VM instance: ', el, '\n')
  46. // set el
  47. vm.$el = el
  48. // link it up!
  49. vm.$compiler = this
  50. vm.$parent = options.parentCompiler && options.parentCompiler.vm
  51. // now for the compiler itself...
  52. this.vm = vm
  53. this.el = vm.$el
  54. this.directives = []
  55. this.computed = [] // computed props to parse deps from
  56. this.contextBindings = [] // computed props with dynamic context
  57. // prototypal inheritance of bindings
  58. var parent = this.parentCompiler
  59. this.bindings = parent
  60. ? Object.create(parent.bindings)
  61. : {}
  62. this.rootCompiler = parent
  63. ? getRoot(parent)
  64. : this
  65. // setup observer
  66. this.setupObserver()
  67. // copy data if any
  68. var key, data = options.data
  69. if (data) {
  70. if (data instanceof vm.constructor) {
  71. data = utils.dump(data)
  72. }
  73. for (key in data) {
  74. vm[key] = data[key]
  75. }
  76. }
  77. // call user init
  78. if (options.init) {
  79. options.init.apply(vm, options.args || [])
  80. }
  81. // check for async compilation (vm.$wait())
  82. if (vm.__wait__) {
  83. var self = this
  84. this.observer.once('ready', function () {
  85. vm.__wait__ = null
  86. self.compile()
  87. })
  88. } else {
  89. this.compile()
  90. }
  91. }
  92. var CompilerProto = Compiler.prototype
  93. /*
  94. * Setup observer.
  95. * The observer listens for get/set/mutate events on all VM
  96. * values/objects and trigger corresponding binding updates.
  97. */
  98. CompilerProto.setupObserver = function () {
  99. var compiler = this,
  100. bindings = this.bindings,
  101. observer = this.observer = new Emitter()
  102. // a hash to hold event proxies for each root level key
  103. // so they can be referenced and removed later
  104. observer.proxies = {}
  105. // add own listeners which trigger binding updates
  106. observer
  107. .on('get', function (key) {
  108. if (DepsParser.observer.isObserving) {
  109. DepsParser.observer.emit('get', bindings[key])
  110. }
  111. })
  112. .on('set', function (key, val) {
  113. if (key.match(/todo\./)) {
  114. console.log(key, val)
  115. }
  116. if (!bindings[key]) compiler.createBinding(key)
  117. bindings[key].update(val)
  118. })
  119. .on('mutate', function (key) {
  120. bindings[key].refresh()
  121. })
  122. }
  123. /*
  124. * Actually parse the DOM nodes for directives, create bindings,
  125. * and parse dependencies afterwards. For the dependency extraction to work,
  126. * this has to happen after all user-set values are present in the VM.
  127. */
  128. CompilerProto.compile = function () {
  129. var key,
  130. vm = this.vm,
  131. computed = this.computed,
  132. contextBindings = this.contextBindings
  133. // parse the DOM
  134. this.compileNode(this.el, true)
  135. // for anything in viewmodel but not binded in DOM, create bindings for them
  136. for (key in vm) {
  137. if (vm.hasOwnProperty(key) &&
  138. key.charAt(0) !== '$' &&
  139. !this.bindings[key])
  140. {
  141. this.createBinding(key)
  142. }
  143. }
  144. // extract dependencies for computed properties
  145. if (computed.length) DepsParser.parse(computed)
  146. this.computed = null
  147. // extract dependencies for computed properties with dynamic context
  148. if (contextBindings.length) this.bindContexts(contextBindings)
  149. this.contextBindings = null
  150. }
  151. /*
  152. * Compile a DOM node (recursive)
  153. */
  154. CompilerProto.compileNode = function (node, root) {
  155. var compiler = this, i, j
  156. if (node.nodeType === 3) { // text node
  157. compiler.compileTextNode(node)
  158. } else if (node.nodeType === 1) {
  159. var eachExp = node.getAttribute(eachAttr),
  160. vmExp = node.getAttribute(vmAttr),
  161. directive
  162. if (eachExp) { // each block
  163. directive = DirectiveParser.parse(eachAttr, eachExp)
  164. if (directive) {
  165. directive.el = node
  166. compiler.bindDirective(directive)
  167. }
  168. } else if (vmExp && !root) { // nested ViewModels
  169. node.removeAttribute(vmAttr)
  170. var ChildVM = utils.getVM(vmExp)
  171. if (ChildVM) {
  172. new ChildVM({
  173. el: node,
  174. child: true,
  175. parentCompiler: compiler
  176. })
  177. }
  178. } else { // normal node
  179. // parse if has attributes
  180. if (node.attributes && node.attributes.length) {
  181. var attrs = slice.call(node.attributes),
  182. attr, valid, exps, exp
  183. i = attrs.length
  184. while (i--) {
  185. attr = attrs[i]
  186. if (attr.name === vmAttr) continue
  187. valid = false
  188. exps = attr.value.split(',')
  189. j = exps.length
  190. while (j--) {
  191. exp = exps[j]
  192. directive = DirectiveParser.parse(attr.name, exp)
  193. if (directive) {
  194. valid = true
  195. directive.el = node
  196. compiler.bindDirective(directive)
  197. }
  198. }
  199. if (valid) node.removeAttribute(attr.name)
  200. }
  201. }
  202. // recursively compile childNodes
  203. if (node.childNodes.length) {
  204. var nodes = slice.call(node.childNodes)
  205. for (i = 0, j = nodes.length; i < j; i++) {
  206. this.compileNode(nodes[i])
  207. }
  208. }
  209. }
  210. }
  211. }
  212. /*
  213. * Compile a text node
  214. */
  215. CompilerProto.compileTextNode = function (node) {
  216. var tokens = TextParser.parse(node)
  217. if (!tokens) return
  218. var compiler = this,
  219. dirname = config.prefix + '-text',
  220. el, token, directive
  221. for (var i = 0, l = tokens.length; i < l; i++) {
  222. token = tokens[i]
  223. el = document.createTextNode('')
  224. if (token.key) {
  225. directive = DirectiveParser.parse(dirname, token.key)
  226. if (directive) {
  227. directive.el = el
  228. compiler.bindDirective(directive)
  229. }
  230. } else {
  231. el.nodeValue = token
  232. }
  233. node.parentNode.insertBefore(el, node)
  234. }
  235. node.parentNode.removeChild(node)
  236. }
  237. /*
  238. * Add a directive instance to the correct binding & viewmodel
  239. */
  240. CompilerProto.bindDirective = function (directive) {
  241. this.directives.push(directive)
  242. directive.compiler = this
  243. directive.vm = this.vm
  244. var key = directive.key,
  245. baseKey = key.split('.')[0],
  246. compiler = traceOwnerCompiler(directive, this)
  247. var binding
  248. if (compiler.vm.hasOwnProperty(baseKey)) {
  249. // if the value is present in the target VM, we create the binding on its compiler
  250. binding = compiler.bindings.hasOwnProperty(key)
  251. ? compiler.bindings[key]
  252. : compiler.createBinding(key)
  253. } else {
  254. // due to prototypal inheritance of bindings, if a key doesn't exist here,
  255. // it doesn't exist in the whole prototype chain. Therefore in that case
  256. // we create the new binding at the root level.
  257. binding = compiler.bindings[key] || this.rootCompiler.createBinding(key)
  258. }
  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. * Create binding and attach getter/setter for a key to the viewmodel object
  284. */
  285. CompilerProto.createBinding = function (key) {
  286. utils.log(' created binding: ' + key)
  287. var bindings = this.bindings,
  288. binding = new Binding(this, key)
  289. bindings[key] = binding
  290. var baseKey = key.split('.')[0]
  291. if (binding.root) {
  292. // this is a root level binding. we need to define getter/setters for it.
  293. this.define(baseKey, binding)
  294. } else {
  295. // TODO create placeholder objects
  296. if (!bindings[baseKey]) {
  297. // this is a nested value binding, but the binding for its root
  298. // has not been created yet. We better create that one too.
  299. this.createBinding(baseKey)
  300. }
  301. }
  302. return binding
  303. }
  304. /*
  305. * Defines the getter/setter for a top-level binding on the VM
  306. * and observe the initial value
  307. */
  308. CompilerProto.define = function (key, binding) {
  309. utils.log(' defined root binding: ' + key)
  310. var compiler = this,
  311. vm = this.vm,
  312. value = binding.value = vm[key] // save the value before redefinening it
  313. if (utils.typeOf(value) === 'Object' && value.get) {
  314. binding.isComputed = true
  315. binding.rawGet = value.get
  316. value.get = value.get.bind(vm)
  317. this.computed.push(binding)
  318. } else {
  319. Observer.observe(value, key, compiler.observer) // start observing right now
  320. }
  321. Object.defineProperty(vm, key, {
  322. enumerable: true,
  323. get: function () {
  324. if (!binding.isComputed && !binding.value.__observer__) {
  325. // only emit non-computed, non-observed values
  326. // because these are the cleanest dependencies
  327. compiler.observer.emit('get', key)
  328. }
  329. return binding.isComputed
  330. ? binding.value.get({
  331. el: compiler.el,
  332. vm: compiler.vm,
  333. item: compiler.each
  334. ? compiler.vm[compiler.eachPrefix.slice(0, -1)]
  335. : null
  336. })
  337. : binding.value
  338. },
  339. set: function (value) {
  340. if (binding.isComputed) {
  341. if (binding.value.set) {
  342. binding.value.set(value)
  343. }
  344. } else if (value !== binding.value) {
  345. // unwatch the old value!
  346. Observer.unobserve(binding.value, key, compiler.observer)
  347. // now watch the new one instead
  348. Observer.observe(value, key, compiler.observer)
  349. binding.value = value
  350. compiler.observer.emit('set', key, value)
  351. }
  352. }
  353. })
  354. }
  355. /*
  356. * Process subscriptions for computed properties that has
  357. * dynamic context dependencies
  358. */
  359. CompilerProto.bindContexts = function (bindings) {
  360. var i = bindings.length, j, k, binding, depKey, dep, ins
  361. while (i--) {
  362. binding = bindings[i]
  363. j = binding.contextDeps.length
  364. while (j--) {
  365. depKey = binding.contextDeps[j]
  366. k = binding.instances.length
  367. while (k--) {
  368. ins = binding.instances[k]
  369. dep = ins.compiler.bindings[depKey]
  370. dep.subs.push(ins)
  371. }
  372. }
  373. }
  374. }
  375. /*
  376. * Unbind and remove element
  377. */
  378. CompilerProto.destroy = function () {
  379. utils.log('compiler destroyed: ', this.vm.$el)
  380. var i, key, dir, inss,
  381. directives = this.directives,
  382. bindings = this.bindings,
  383. el = this.el
  384. // remove all directives that are instances of external bindings
  385. i = directives.length
  386. while (i--) {
  387. dir = directives[i]
  388. if (dir.binding.compiler !== this) {
  389. inss = dir.binding.instances
  390. if (inss) inss.splice(inss.indexOf(dir), 1)
  391. }
  392. dir.unbind()
  393. }
  394. // unbind all own bindings
  395. for (key in bindings) {
  396. if (bindings.hasOwnProperty(key)) {
  397. bindings[key].unbind()
  398. }
  399. }
  400. // remove el
  401. el.parentNode.removeChild(el)
  402. }
  403. // Helpers --------------------------------------------------------------------
  404. /*
  405. * determine which viewmodel a key belongs to based on nesting symbols
  406. */
  407. function traceOwnerCompiler (key, compiler) {
  408. if (key.nesting) {
  409. var levels = key.nesting
  410. while (compiler.parentCompiler && levels--) {
  411. compiler = compiler.parentCompiler
  412. }
  413. } else if (key.root) {
  414. while (compiler.parentCompiler) {
  415. compiler = compiler.parentCompiler
  416. }
  417. }
  418. return compiler
  419. }
  420. /*
  421. * shorthand for getting root compiler
  422. */
  423. function getRoot (compiler) {
  424. return traceOwnerCompiler({ root: true }, compiler)
  425. }
  426. module.exports = Compiler