Skip to content

Commit

Permalink
implement $ event methods, optimize for minification
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Oct 9, 2013
1 parent 3cbf498 commit a21e890
Show file tree
Hide file tree
Showing 3 changed files with 165 additions and 96 deletions.
208 changes: 112 additions & 96 deletions src/compiler.js
Expand Up @@ -21,79 +21,49 @@ function Compiler (vm, options) {

refreshPrefix()

options = this.options = options || {}
utils.extend(this, options.compilerOptions || {})

// initialize element
var el = typeof options.el === 'string'
? document.querySelector(options.el)
: options.el || document.createElement(options.tagName || 'div')

// apply element options
if (options.id) el.id = options.id
if (options.className) el.className = options.className
var attrs = options.attributes
if (attrs) {
for (var attr in attrs) {
el.setAttribute(attr, attrs[attr])
}
}
var compiler = this

// initialize template
var template = options.template
if (typeof template === 'string') {
if (template.charAt(0) === '#') {
var templateNode = document.querySelector(template)
if (templateNode) {
el.innerHTML = templateNode.innerHTML
}
} else {
el.innerHTML = template
}
} else if (options.templateFragment) {
el.innerHTML = ''
el.appendChild(options.templateFragment.cloneNode(true))
}
// extend options
options = compiler.options = options || {}
utils.extend(compiler, options.compilerOptions || {})

utils.log('\nnew VM instance: ', el, '\n')
// initialize element
compiler.setupElement(options)
utils.log('\nnew VM instance: ', compiler.el, '\n')

// copy data to vm
var data = options.data
if (data) utils.extend(vm, data)

// set stuff on the ViewModel
vm.$el = el
vm.$compiler = this
vm.$parent = options.parentCompiler && options.parentCompiler.vm
compiler.vm = vm
vm.$compiler = compiler
vm.$el = compiler.el

// now for the compiler itself...
this.vm = vm
this.el = el
this.dirs = []
// keep track of anonymous expression bindings
// that needs to be unbound during destroy()
this.exps = []
// keep track of directives and expressions
// so they can be unbound during destroy()
compiler.dirs = []
compiler.exps = []
compiler.childCompilers = [] // keep track of child compilers
compiler.emitter = new Emitter() // the emitter used for nested VM communication

// Store things during parsing to be processed afterwards,
// because we want to have created all bindings before
// observing values / parsing dependencies.
var observables = this.observables = []
// computed props to parse deps from
var computed = this.computed = []
// computed props with dynamic context
var ctxBindings = this.ctxBindings = []
var observables = compiler.observables = [],
computed = compiler.computed = [],
ctxBindings = compiler.ctxBindings = []

// prototypal inheritance of bindings
var parent = this.parentCompiler
this.bindings = parent
var parent = compiler.parentCompiler
compiler.bindings = parent
? Object.create(parent.bindings)
: {}
this.rootCompiler = parent
compiler.rootCompiler = parent
? getRoot(parent)
: this
: compiler

// setup observer
this.setupObserver()
compiler.setupObserver()

// call user init. this will capture some initial values.
if (options.init) {
Expand All @@ -103,37 +73,74 @@ function Compiler (vm, options) {
// create bindings for keys set on the vm by the user
for (var key in vm) {
if (key.charAt(0) !== '$') {
this.createBinding(key)
compiler.createBinding(key)
}
}

// for each items, create an index binding
if (this.each) {
vm[this.eachPrefix].$index = this.eachIndex
if (compiler.each) {
vm[compiler.eachPrefix].$index = compiler.eachIndex
}

// now parse the DOM, during which we will create necessary bindings
// and bind the parsed directives
this.compile(this.el, true)
compiler.compile(compiler.el, true)

// observe root values so that they emit events when
// their nested values change (for an Object)
// or when they mutate (for an Array)
var i = observables.length, binding
while (i--) {
binding = observables[i]
Observer.observe(binding.value, binding.key, this.observer)
Observer.observe(binding.value, binding.key, compiler.observer)
}
// extract dependencies for computed properties
if (computed.length) DepsParser.parse(computed)
// extract dependencies for computed properties with dynamic context
if (ctxBindings.length) this.bindContexts(ctxBindings)
if (ctxBindings.length) compiler.bindContexts(ctxBindings)
// unset these no longer needed stuff
this.observables = this.computed = this.ctxBindings = this.arrays = null
compiler.observables = compiler.computed = compiler.ctxBindings = compiler.arrays = null
}

var CompilerProto = Compiler.prototype

/*
* Initialize the VM/Compiler's element.
* Fill it in with the template if necessary.
*/
CompilerProto.setupElement = function (options) {
// create the node first
var el = this.el = typeof options.el === 'string'
? document.querySelector(options.el)
: options.el || document.createElement(options.tagName || 'div')

// apply element options
if (options.id) el.id = options.id
if (options.className) el.className = options.className
var attrs = options.attributes
if (attrs) {
for (var attr in attrs) {
el.setAttribute(attr, attrs[attr])
}
}

// initialize template
var template = options.template
if (typeof template === 'string') {
if (template.charAt(0) === '#') {
var templateNode = document.querySelector(template)
if (templateNode) {
el.innerHTML = templateNode.innerHTML
}
} else {
el.innerHTML = template
}
} else if (options.templateFragment) {
el.innerHTML = ''
el.appendChild(options.templateFragment.cloneNode(true))
}
}

/*
* Setup observer.
* The observer listens for get/set/mutate events on all VM
Expand Down Expand Up @@ -183,17 +190,18 @@ CompilerProto.compile = function (node, root) {
if (directive) {
compiler.bindDirective(directive)
}
} else if (vmId && !root) { // nested ViewModels
} else if (vmId && !root) { // child ViewModels
node.removeAttribute(vmAttr)
var ChildVM = compiler.getOption('vms', vmId)
if (ChildVM) {
new ChildVM({
var child = new ChildVM({
el: node,
child: true,
compilerOptions: {
parentCompiler: compiler
}
})
compiler.childCompilers.push(child.$compiler)
}
} else {
if (partialId) { // replace innerHTML with partial
Expand All @@ -205,7 +213,7 @@ CompilerProto.compile = function (node, root) {
}
}
// finally, only normal directives left!
this.compileNode(node)
compiler.compileNode(node)
}
} else if (node.nodeType === 3) { // text node
compiler.compileTextNode(node)
Expand Down Expand Up @@ -288,25 +296,26 @@ CompilerProto.compileTextNode = function (node) {
*/
CompilerProto.bindDirective = function (directive) {

this.dirs.push(directive)
var binding,
compiler = this,
key = directive.key,
baseKey = key.split('.')[0],
ownerCompiler = traceOwnerCompiler(directive, compiler)

var key = directive.key,
baseKey = key.split('.')[0],
compiler = traceOwnerCompiler(directive, this)
compiler.dirs.push(directive)

var binding
if (directive.isExp) {
binding = this.createBinding(key, true)
} else if (compiler.vm.hasOwnProperty(baseKey)) {
binding = compiler.createBinding(key, true)
} else if (ownerCompiler.vm.hasOwnProperty(baseKey)) {
// if the value is present in the target VM, we create the binding on its compiler
binding = compiler.bindings.hasOwnProperty(key)
? compiler.bindings[key]
: compiler.createBinding(key)
binding = ownerCompiler.bindings.hasOwnProperty(key)
? ownerCompiler.bindings[key]
: ownerCompiler.createBinding(key)
} else {
// due to prototypal inheritance of bindings, if a key doesn't exist here,
// it doesn't exist in the whole prototype chain. Therefore in that case
// we create the new binding at the root level.
binding = compiler.bindings[key] || this.rootCompiler.createBinding(key)
binding = ownerCompiler.bindings[key] || compiler.rootCompiler.createBinding(key)
}

binding.instances.push(directive)
Expand All @@ -319,7 +328,7 @@ CompilerProto.bindDirective = function (directive) {
if (deps) {
i = deps.length
while (i--) {
dep = this.bindings[deps[i]]
dep = compiler.bindings[deps[i]]
dep.subs.push(directive)
}
}
Expand All @@ -343,8 +352,9 @@ CompilerProto.bindDirective = function (directive) {
*/
CompilerProto.createBinding = function (key, isExp) {

var bindings = this.bindings,
binding = new Binding(this, key, isExp)
var compiler = this,
bindings = compiler.bindings,
binding = new Binding(compiler, key, isExp)

if (isExp) {
// a complex expression binding
Expand All @@ -353,15 +363,15 @@ CompilerProto.createBinding = function (key, isExp) {
if (result) {
utils.log(' created anonymous binding: ' + key)
binding.value = { get: result.getter }
this.markComputed(binding)
this.exps.push(binding)
compiler.markComputed(binding)
compiler.exps.push(binding)
// need to create the bindings for keys
// that do not exist yet
var i = result.vars.length, v
while (i--) {
v = result.vars[i]
if (!bindings[v]) {
this.rootCompiler.createBinding(v)
compiler.rootCompiler.createBinding(v)
}
}
} else {
Expand All @@ -372,16 +382,16 @@ CompilerProto.createBinding = function (key, isExp) {
bindings[key] = binding
// make sure the key exists in the object so it can be observed
// by the Observer!
this.ensurePath(key)
compiler.ensurePath(key)
if (binding.root) {
// this is a root level binding. we need to define getter/setters for it.
this.define(key, binding)
compiler.define(key, binding)
} else {
var parentKey = key.slice(0, key.lastIndexOf('.'))
if (!bindings.hasOwnProperty(parentKey)) {
// this is a nested value binding, but the binding for its parent
// has not been created yet. We better create that one too.
this.createBinding(parentKey)
compiler.createBinding(parentKey)
}
}
}
Expand Down Expand Up @@ -416,19 +426,19 @@ CompilerProto.define = function (key, binding) {
utils.log(' defined root binding: ' + key)

var compiler = this,
vm = this.vm,
ob = this.observer,
vm = compiler.vm,
ob = compiler.observer,
value = binding.value = vm[key], // save the value before redefinening it
type = utils.typeOf(value)

if (type === 'Object' && value.get) {
// computed property
this.markComputed(binding)
compiler.markComputed(binding)
} else if (type === 'Object' || type === 'Array') {
// observe objects later, becase there might be more keys
// to be added to it. we also want to emit all the set events
// after all values are available.
this.observables.push(binding)
compiler.observables.push(binding)
}

Object.defineProperty(vm, key, {
Expand Down Expand Up @@ -521,19 +531,20 @@ CompilerProto.getOption = function (type, id) {
* Unbind and remove element
*/
CompilerProto.destroy = function () {
utils.log('compiler destroyed: ', this.vm.$el)
var compiler = this
utils.log('compiler destroyed: ', compiler.vm.$el)
// unwatch
this.observer.off()
compiler.observer.off()
var i, key, dir, inss, binding,
el = this.el,
directives = this.dirs,
exps = this.exps,
bindings = this.bindings
el = compiler.el,
directives = compiler.dirs,
exps = compiler.exps,
bindings = compiler.bindings
// remove all directives that are instances of external bindings
i = directives.length
while (i--) {
dir = directives[i]
if (dir.binding.compiler !== this) {
if (dir.binding.compiler !== compiler) {
inss = dir.binding.instances
if (inss) inss.splice(inss.indexOf(dir), 1)
}
Expand All @@ -549,11 +560,16 @@ CompilerProto.destroy = function () {
if (bindings.hasOwnProperty(key)) {
binding = bindings[key]
if (binding.root) {
Observer.unobserve(binding.value, binding.key, this.observer)
Observer.unobserve(binding.value, binding.key, compiler.observer)
}
binding.unbind()
}
}
// remove self from parentCompiler
var parent = compiler.parentCompiler
if (parent) {
parent.childCompilers.splice(parent.childCompilers.indexOf(compiler), 1)
}
// remove el
if (el === document.body) {
el.innerHTML = ''
Expand Down

0 comments on commit a21e890

Please sign in to comment.