compiler.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649
  1. var Emitter = require('./emitter'),
  2. Observer = require('./observer'),
  3. config = require('./config'),
  4. utils = require('./utils'),
  5. Binding = require('./binding'),
  6. Directive = require('./directive'),
  7. TextParser = require('./text-parser'),
  8. DepsParser = require('./deps-parser'),
  9. ExpParser = require('./exp-parser'),
  10. transition = require('./transition'),
  11. // cache deps ob
  12. depsOb = DepsParser.observer,
  13. // cache methods
  14. slice = Array.prototype.slice,
  15. log = utils.log,
  16. makeHash = utils.hash,
  17. def = utils.defProtected,
  18. hasOwn = Object.prototype.hasOwnProperty
  19. /**
  20. * The DOM compiler
  21. * scans a DOM node and compile bindings for a ViewModel
  22. */
  23. function Compiler (vm, options) {
  24. var compiler = this
  25. // indicate that we are intiating this instance
  26. // so we should not run any transitions
  27. compiler.init = true
  28. // extend options
  29. options = compiler.options = options || makeHash()
  30. utils.processOptions(options)
  31. utils.extend(compiler, options.compilerOptions)
  32. // initialize element
  33. var el = compiler.setupElement(options)
  34. log('\nnew VM instance:', el.tagName, '\n')
  35. // copy scope properties to vm
  36. var scope = options.scope
  37. if (scope) utils.extend(vm, scope, true)
  38. compiler.vm = vm
  39. def(vm, '$', makeHash())
  40. def(vm, '$el', el)
  41. def(vm, '$compiler', compiler)
  42. // keep track of directives and expressions
  43. // so they can be unbound during destroy()
  44. compiler.dirs = []
  45. compiler.exps = []
  46. compiler.childCompilers = [] // keep track of child compilers
  47. compiler.emitter = new Emitter() // the emitter used for nested VM communication
  48. // Store things during parsing to be processed afterwards,
  49. // because we want to have created all bindings before
  50. // observing values / parsing dependencies.
  51. var observables = compiler.observables = [],
  52. computed = compiler.computed = []
  53. // prototypal inheritance of bindings
  54. var parent = compiler.parentCompiler
  55. compiler.bindings = parent
  56. ? Object.create(parent.bindings)
  57. : makeHash()
  58. compiler.rootCompiler = parent
  59. ? getRoot(parent)
  60. : compiler
  61. // set parent VM
  62. // and register child id on parent
  63. var childId = utils.attr(el, 'id')
  64. if (parent) {
  65. def(vm, '$parent', parent.vm)
  66. if (childId) {
  67. compiler.childId = childId
  68. parent.vm.$[childId] = vm
  69. }
  70. }
  71. // setup observer
  72. compiler.setupObserver()
  73. // call user init. this will capture some initial values.
  74. if (options.init) {
  75. options.init.apply(vm, options.args || [])
  76. }
  77. // create bindings for keys set on the vm by the user
  78. var key, keyPrefix
  79. for (key in vm) {
  80. keyPrefix = key.charAt(0)
  81. if (keyPrefix !== '$' && keyPrefix !== '_') {
  82. compiler.createBinding(key)
  83. }
  84. }
  85. // for repeated items, create an index binding
  86. // which should be inenumerable but configurable
  87. if (compiler.repeat) {
  88. vm.$index = compiler.repeatIndex
  89. def(vm, '$collection', compiler.repeatCollection)
  90. compiler.createBinding('$index')
  91. }
  92. // now parse the DOM, during which we will create necessary bindings
  93. // and bind the parsed directives
  94. compiler.compile(el, true)
  95. // observe root values so that they emit events when
  96. // their nested values change (for an Object)
  97. // or when they mutate (for an Array)
  98. var i = observables.length, binding
  99. while (i--) {
  100. binding = observables[i]
  101. Observer.observe(binding.value, binding.key, compiler.observer)
  102. }
  103. // extract dependencies for computed properties
  104. if (computed.length) DepsParser.parse(computed)
  105. // done!
  106. compiler.init = false
  107. }
  108. var CompilerProto = Compiler.prototype
  109. /**
  110. * Initialize the VM/Compiler's element.
  111. * Fill it in with the template if necessary.
  112. */
  113. CompilerProto.setupElement = function (options) {
  114. // create the node first
  115. var el = this.el = typeof options.el === 'string'
  116. ? document.querySelector(options.el)
  117. : options.el || document.createElement(options.tagName || 'div')
  118. var template = options.template
  119. if (template) {
  120. // replace option: use the first node in
  121. // the template directly
  122. if (options.replace && template.childNodes.length === 1) {
  123. var replacer = template.childNodes[0].cloneNode(true)
  124. if (el.parentNode) {
  125. el.parentNode.insertBefore(replacer, el)
  126. el.parentNode.removeChild(el)
  127. }
  128. el = replacer
  129. } else {
  130. el.innerHTML = ''
  131. el.appendChild(template.cloneNode(true))
  132. }
  133. }
  134. // apply element options
  135. if (options.id) el.id = options.id
  136. if (options.className) el.className = options.className
  137. var attrs = options.attributes
  138. if (attrs) {
  139. for (var attr in attrs) {
  140. el.setAttribute(attr, attrs[attr])
  141. }
  142. }
  143. return el
  144. }
  145. /**
  146. * Setup observer.
  147. * The observer listens for get/set/mutate events on all VM
  148. * values/objects and trigger corresponding binding updates.
  149. */
  150. CompilerProto.setupObserver = function () {
  151. var compiler = this,
  152. bindings = compiler.bindings,
  153. observer = compiler.observer = new Emitter()
  154. // a hash to hold event proxies for each root level key
  155. // so they can be referenced and removed later
  156. observer.proxies = makeHash()
  157. // add own listeners which trigger binding updates
  158. observer
  159. .on('get', function (key) {
  160. check(key)
  161. depsOb.emit('get', bindings[key])
  162. })
  163. .on('set', function (key, val) {
  164. observer.emit('change:' + key, val)
  165. check(key)
  166. bindings[key].update(val)
  167. })
  168. .on('mutate', function (key, val, mutation) {
  169. observer.emit('change:' + key, val, mutation)
  170. check(key)
  171. bindings[key].pub()
  172. })
  173. function check (key) {
  174. if (!bindings[key]) {
  175. compiler.createBinding(key)
  176. }
  177. }
  178. }
  179. /**
  180. * Compile a DOM node (recursive)
  181. */
  182. CompilerProto.compile = function (node, root) {
  183. var compiler = this
  184. if (node.nodeType === 1) { // a normal node
  185. // skip anything with sd-pre
  186. if (utils.attr(node, 'pre') !== null) return
  187. // special attributes to check
  188. var repeatExp,
  189. componentId,
  190. partialId,
  191. customElementFn = utils.elements[node.tagName.toLowerCase()]
  192. // It is important that we access these attributes
  193. // procedurally because the order matters.
  194. //
  195. // `utils.attr` removes the attribute once it gets the
  196. // value, so we should not access them all at once.
  197. // sd-repeat has the highest priority
  198. // and we need to preserve all other attributes for it.
  199. /* jshint boss: true */
  200. if (repeatExp = utils.attr(node, 'repeat')) {
  201. // repeat block cannot have sd-id at the same time.
  202. var directive = Directive.parse(config.attrs.repeat, repeatExp, compiler, node)
  203. if (directive) {
  204. compiler.bindDirective(directive)
  205. }
  206. // custom elements has 2nd highest priority
  207. } else if (customElementFn) {
  208. addChild(customElementFn)
  209. // sd-component has 3rd highest priority
  210. } else if (!root && (componentId = utils.attr(node, 'component'))) {
  211. var ChildVM = compiler.getOption('components', componentId)
  212. if (ChildVM) addChild(ChildVM)
  213. } else {
  214. // check transition property
  215. node.sd_trans = utils.attr(node, 'transition')
  216. // replace innerHTML with partial
  217. partialId = utils.attr(node, 'partial')
  218. if (partialId) {
  219. var partial = compiler.getOption('partials', partialId)
  220. if (partial) {
  221. node.innerHTML = ''
  222. node.appendChild(partial.cloneNode(true))
  223. }
  224. }
  225. // finally, only normal directives left!
  226. compiler.compileNode(node)
  227. }
  228. } else if (node.nodeType === 3) { // text node
  229. compiler.compileTextNode(node)
  230. }
  231. function addChild (Ctor) {
  232. if (utils.isConstructor(Ctor)) {
  233. var child = new Ctor({
  234. el: node,
  235. child: true,
  236. compilerOptions: {
  237. parentCompiler: compiler
  238. }
  239. })
  240. compiler.childCompilers.push(child.$compiler)
  241. } else {
  242. // simply call the function
  243. Ctor(node)
  244. }
  245. }
  246. }
  247. /**
  248. * Compile a normal node
  249. */
  250. CompilerProto.compileNode = function (node) {
  251. var i, j
  252. // parse if has attributes
  253. if (node.attributes && node.attributes.length) {
  254. var attrs = slice.call(node.attributes),
  255. attr, valid, exps, exp
  256. // loop through all attributes
  257. i = attrs.length
  258. while (i--) {
  259. attr = attrs[i]
  260. valid = false
  261. exps = Directive.split(attr.value)
  262. // loop through clauses (separated by ",")
  263. // inside each attribute
  264. j = exps.length
  265. while (j--) {
  266. exp = exps[j]
  267. var directive = Directive.parse(attr.name, exp, this, node)
  268. if (directive) {
  269. valid = true
  270. this.bindDirective(directive)
  271. }
  272. }
  273. if (valid) node.removeAttribute(attr.name)
  274. }
  275. }
  276. // recursively compile childNodes
  277. if (node.childNodes.length) {
  278. var nodes = slice.call(node.childNodes)
  279. for (i = 0, j = nodes.length; i < j; i++) {
  280. this.compile(nodes[i])
  281. }
  282. }
  283. }
  284. /**
  285. * Compile a text node
  286. */
  287. CompilerProto.compileTextNode = function (node) {
  288. var tokens = TextParser.parse(node.nodeValue)
  289. if (!tokens) return
  290. var dirname = config.attrs.text,
  291. el, token, directive
  292. for (var i = 0, l = tokens.length; i < l; i++) {
  293. token = tokens[i]
  294. if (token.key) { // a binding
  295. if (token.key.charAt(0) === '>') { // a partial
  296. var partialId = token.key.slice(1).trim(),
  297. partial = this.getOption('partials', partialId)
  298. if (partial) {
  299. el = partial.cloneNode(true)
  300. this.compileNode(el)
  301. }
  302. } else { // a binding
  303. el = document.createTextNode('')
  304. directive = Directive.parse(dirname, token.key, this, el)
  305. if (directive) {
  306. this.bindDirective(directive)
  307. }
  308. }
  309. } else { // a plain string
  310. el = document.createTextNode(token)
  311. }
  312. node.parentNode.insertBefore(el, node)
  313. }
  314. node.parentNode.removeChild(node)
  315. }
  316. /**
  317. * Add a directive instance to the correct binding & viewmodel
  318. */
  319. CompilerProto.bindDirective = function (directive) {
  320. // keep track of it so we can unbind() later
  321. this.dirs.push(directive)
  322. // for a simple directive, simply call its bind() or _update()
  323. // and we're done.
  324. if (directive.isSimple) {
  325. if (directive.bind) directive.bind()
  326. return
  327. }
  328. // otherwise, we got more work to do...
  329. var binding,
  330. compiler = this,
  331. key = directive.key,
  332. baseKey = key.split('.')[0],
  333. ownerCompiler = traceOwnerCompiler(directive, compiler)
  334. if (directive.isExp) {
  335. // expression bindings are always created on current compiler
  336. binding = compiler.createBinding(key, true, directive.isFn)
  337. } else if (ownerCompiler.vm.hasOwnProperty(baseKey)) {
  338. // If the directive's owner compiler's VM has the key,
  339. // it belongs there. Create the binding if it's not already
  340. // created, and return it.
  341. binding = hasOwn.call(ownerCompiler.bindings, key)
  342. ? ownerCompiler.bindings[key]
  343. : ownerCompiler.createBinding(key)
  344. } else {
  345. // due to prototypal inheritance of bindings, if a key doesn't exist
  346. // on the owner compiler's VM, then it doesn't exist in the whole
  347. // prototype chain. In this case we create the new binding at the root level.
  348. binding = ownerCompiler.bindings[key] || compiler.rootCompiler.createBinding(key)
  349. }
  350. binding.instances.push(directive)
  351. directive.binding = binding
  352. var value = binding.value
  353. // invoke bind hook if exists
  354. if (directive.bind) {
  355. directive.bind(value)
  356. }
  357. // set initial value
  358. if (binding.isComputed) {
  359. directive.refresh(value)
  360. } else {
  361. directive.update(value, true)
  362. }
  363. }
  364. /**
  365. * Create binding and attach getter/setter for a key to the viewmodel object
  366. */
  367. CompilerProto.createBinding = function (key, isExp, isFn) {
  368. var compiler = this,
  369. bindings = compiler.bindings,
  370. binding = new Binding(compiler, key, isExp, isFn)
  371. if (isExp) {
  372. // a complex expression binding
  373. // we need to generate an anonymous computed property for it
  374. var getter = ExpParser.parse(key, compiler)
  375. if (getter) {
  376. log(' created expression binding: ' + key)
  377. binding.value = isFn
  378. ? getter
  379. : { $get: getter }
  380. compiler.markComputed(binding)
  381. compiler.exps.push(binding)
  382. }
  383. } else {
  384. log(' created binding: ' + key)
  385. bindings[key] = binding
  386. // make sure the key exists in the object so it can be observed
  387. // by the Observer!
  388. Observer.ensurePath(compiler.vm, key)
  389. if (binding.root) {
  390. // this is a root level binding. we need to define getter/setters for it.
  391. compiler.define(key, binding)
  392. } else {
  393. var parentKey = key.slice(0, key.lastIndexOf('.'))
  394. if (!hasOwn.call(bindings, parentKey)) {
  395. // this is a nested value binding, but the binding for its parent
  396. // has not been created yet. We better create that one too.
  397. compiler.createBinding(parentKey)
  398. }
  399. }
  400. }
  401. return binding
  402. }
  403. /**
  404. * Defines the getter/setter for a root-level binding on the VM
  405. * and observe the initial value
  406. */
  407. CompilerProto.define = function (key, binding) {
  408. log(' defined root binding: ' + key)
  409. var compiler = this,
  410. vm = compiler.vm,
  411. ob = compiler.observer,
  412. value = binding.value = vm[key], // save the value before redefinening it
  413. type = utils.typeOf(value)
  414. if (type === 'Object' && value.$get) {
  415. // computed property
  416. compiler.markComputed(binding)
  417. } else if (type === 'Object' || type === 'Array') {
  418. // observe objects later, becase there might be more keys
  419. // to be added to it. we also want to emit all the set events
  420. // after all values are available.
  421. compiler.observables.push(binding)
  422. }
  423. Object.defineProperty(vm, key, {
  424. enumerable: true,
  425. get: function () {
  426. var value = binding.value
  427. if (depsOb.active && (!binding.isComputed && (!value || !value.__observer__)) ||
  428. Array.isArray(value)) {
  429. // only emit non-computed, non-observed (primitive) values, or Arrays.
  430. // because these are the cleanest dependencies
  431. ob.emit('get', key)
  432. }
  433. return binding.isComputed
  434. ? value.$get()
  435. : value
  436. },
  437. set: function (newVal) {
  438. var value = binding.value
  439. if (binding.isComputed) {
  440. if (value.$set) {
  441. value.$set(newVal)
  442. }
  443. } else if (newVal !== value) {
  444. // unwatch the old value
  445. Observer.unobserve(value, key, ob)
  446. // set new value
  447. binding.value = newVal
  448. ob.emit('set', key, newVal)
  449. Observer.ensurePaths(key, newVal, compiler.bindings)
  450. // now watch the new value, which in turn emits 'set'
  451. // for all its nested values
  452. Observer.observe(newVal, key, ob)
  453. }
  454. }
  455. })
  456. }
  457. /**
  458. * Process a computed property binding
  459. */
  460. CompilerProto.markComputed = function (binding) {
  461. var value = binding.value,
  462. vm = this.vm
  463. binding.isComputed = true
  464. // bind the accessors to the vm
  465. if (binding.isFn) {
  466. binding.value = utils.bind(value, vm)
  467. } else {
  468. value.$get = utils.bind(value.$get, vm)
  469. if (value.$set) {
  470. value.$set = utils.bind(value.$set, vm)
  471. }
  472. }
  473. // keep track for dep parsing later
  474. this.computed.push(binding)
  475. }
  476. /**
  477. * Retrive an option from the compiler
  478. */
  479. CompilerProto.getOption = function (type, id) {
  480. var opts = this.options
  481. return (opts[type] && opts[type][id]) || (utils[type] && utils[type][id])
  482. }
  483. /**
  484. * Unbind and remove element
  485. */
  486. CompilerProto.destroy = function () {
  487. var compiler = this,
  488. i, key, dir, instances, binding,
  489. el = compiler.el,
  490. directives = compiler.dirs,
  491. exps = compiler.exps,
  492. bindings = compiler.bindings,
  493. teardown = compiler.options.teardown
  494. // call user teardown first
  495. if (teardown) teardown()
  496. // unwatch
  497. compiler.observer.off()
  498. compiler.emitter.off()
  499. // unbind all direcitves
  500. i = directives.length
  501. while (i--) {
  502. dir = directives[i]
  503. // if this directive is an instance of an external binding
  504. // e.g. a directive that refers to a variable on the parent VM
  505. // we need to remove it from that binding's instances
  506. if (!dir.isSimple && dir.binding.compiler !== compiler) {
  507. instances = dir.binding.instances
  508. if (instances) instances.splice(instances.indexOf(dir), 1)
  509. }
  510. dir.unbind()
  511. }
  512. // unbind all expressions (anonymous bindings)
  513. i = exps.length
  514. while (i--) {
  515. exps[i].unbind()
  516. }
  517. // unbind/unobserve all own bindings
  518. for (key in bindings) {
  519. if (hasOwn.call(bindings, key)) {
  520. binding = bindings[key]
  521. if (binding.root) {
  522. Observer.unobserve(binding.value, binding.key, compiler.observer)
  523. }
  524. binding.unbind()
  525. }
  526. }
  527. // remove self from parentCompiler
  528. var parent = compiler.parentCompiler,
  529. childId = compiler.childId
  530. if (parent) {
  531. parent.childCompilers.splice(parent.childCompilers.indexOf(compiler), 1)
  532. if (childId) {
  533. delete parent.vm.$[childId]
  534. }
  535. }
  536. // finally remove dom element
  537. if (el === document.body) {
  538. el.innerHTML = ''
  539. } else if (el.parentNode) {
  540. transition(el, -1, function () {
  541. el.parentNode.removeChild(el)
  542. }, this)
  543. }
  544. }
  545. // Helpers --------------------------------------------------------------------
  546. /**
  547. * determine which viewmodel a key belongs to based on nesting symbols
  548. */
  549. function traceOwnerCompiler (key, compiler) {
  550. if (key.nesting) {
  551. var levels = key.nesting
  552. while (compiler.parentCompiler && levels--) {
  553. compiler = compiler.parentCompiler
  554. }
  555. } else if (key.root) {
  556. while (compiler.parentCompiler) {
  557. compiler = compiler.parentCompiler
  558. }
  559. }
  560. return compiler
  561. }
  562. /**
  563. * shorthand for getting root compiler
  564. */
  565. function getRoot (compiler) {
  566. return traceOwnerCompiler({ root: true }, compiler)
  567. }
  568. module.exports = Compiler