Skip to content

Commit

Permalink
rewrite lifecycle hook system
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Feb 7, 2014
1 parent 4fdeba5 commit d75ee62
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 55 deletions.
57 changes: 43 additions & 14 deletions src/compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,14 @@ var Emitter = require('./emitter'),
makeHash = utils.hash,
extend = utils.extend,
def = utils.defProtected,
hasOwn = Object.prototype.hasOwnProperty
hasOwn = Object.prototype.hasOwnProperty,

// hooks to register
hooks = [
'created', 'ready',
'beforeDestroy', 'afterDestroy',
'enteredView', 'leftView'
]

/**
* The DOM compiler
Expand Down Expand Up @@ -82,7 +89,7 @@ function Compiler (vm, options) {
}

// beforeCompile hook
compiler.execHook('beforeCompile', 'created')
compiler.execHook('created')

// the user might have set some props on the vm
// so copy it back to the data...
Expand Down Expand Up @@ -130,7 +137,7 @@ function Compiler (vm, options) {
compiler.init = false

// post compile / ready hook
compiler.execHook('afterCompile', 'ready')
compiler.execHook('ready')
}

var CompilerProto = Compiler.prototype
Expand Down Expand Up @@ -179,11 +186,13 @@ CompilerProto.setupElement = function (options) {
* Setup observer.
* The observer listens for get/set/mutate events on all VM
* values/objects and trigger corresponding binding updates.
* It also listens for lifecycle hooks.
*/
CompilerProto.setupObserver = function () {

var compiler = this,
bindings = compiler.bindings,
options = compiler.options,
observer = compiler.observer = new Emitter()

// a hash to hold event proxies for each root level key
Expand All @@ -206,6 +215,27 @@ CompilerProto.setupObserver = function () {
check(key)
bindings[key].pub()
})

// register hooks
hooks.forEach(function (hook) {
var fns = options[hook]
if (Array.isArray(fns)) {
var i = fns.length
// since hooks were merged with child at head,
// we loop reversely.
while (i--) {
register(hook, fns[i])
}
} else if (fns) {
register(hook, fns)
}
})

function register (hook, fn) {
observer.on('hook:' + hook, function () {
fn.call(compiler.vm, options)
})
}

function check (key) {
if (!bindings[key]) {
Expand Down Expand Up @@ -585,14 +615,12 @@ CompilerProto.getOption = function (type, id) {
}

/**
* Execute a user hook
* Emit lifecycle events to trigger hooks
*/
CompilerProto.execHook = function (id, alt) {
var opts = this.options,
hook = opts[id] || opts[alt]
if (hook) {
hook.call(this.vm, opts)
}
CompilerProto.execHook = function (event) {
event = 'hook:' + event
this.observer.emit(event)
this.emitter.emit(event)
}

/**
Expand Down Expand Up @@ -627,10 +655,6 @@ CompilerProto.destroy = function () {

compiler.execHook('beforeDestroy')

// unwatch
compiler.observer.off()
compiler.emitter.off()

// unbind all direcitves
i = directives.length
while (i--) {
Expand Down Expand Up @@ -679,7 +703,12 @@ CompilerProto.destroy = function () {
vm.$remove()
}

// emit destroy hook
compiler.execHook('afterDestroy')

// finally, unregister all listeners
compiler.observer.off()
compiler.emitter.off()
}

// Helpers --------------------------------------------------------------------
Expand Down
20 changes: 7 additions & 13 deletions src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,13 @@ function inheritOptions (child, parent, topLevel) {
parentVal = parent[key],
type = utils.typeOf(val)
if (topLevel && type === 'Function' && parentVal) {
// merge hook functions
child[key] = mergeHook(val, parentVal)
// merge hook functions into an array
child[key] = [val]
if (Array.isArray(parentVal)) {
child[key] = child[key].concat(parentVal)
} else {
child[key].push(parentVal)
}
} else if (topLevel && type === 'Object') {
// merge toplevel object options
inheritOptions(val, parentVal)
Expand All @@ -146,15 +151,4 @@ function inheritOptions (child, parent, topLevel) {
return child
}

/**
* Merge hook functions
* so parent hooks also get called
*/
function mergeHook (fn, parentFn) {
return function (opts) {
parentFn.call(this, opts)
fn.call(this, opts)
}
}

module.exports = ViewModel
98 changes: 70 additions & 28 deletions test/unit/specs/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -664,29 +664,23 @@ describe('UNIT: API', function () {

describe('hooks', function () {

describe('beforeCompile / created', function () {
describe('created', function () {

it('should be called before compile', function () {

var called = false,
Test = Vue.extend({ beforeCompile: function (options) {
assert.ok(options.ok)
called = true
}}),
Test2 = Vue.extend({ created: function (options) {
Test = Vue.extend({ created: function (options) {
assert.ok(options.ok)
called = true
}})
new Test({ ok: true })
assert.ok(called)
called = false
new Test2({ ok: true })
assert.ok(called)

})

})

describe('afterCompile / ready', function () {
describe('ready', function () {

it('should be called after compile with options', function () {
var called = false,
Expand All @@ -695,69 +689,117 @@ describe('UNIT: API', function () {
assert.notOk(this.$compiler.init)
called = true
},
Test = Vue.extend({ afterCompile: hook }),
Test2 = Vue.extend({ ready: hook })
Test = Vue.extend({ ready: hook })
new Test({ ok: true })
assert.ok(called)
called = false
new Test2({ ok: true })
assert.ok(called)
})

})

describe('beforeDestroy', function () {

it('should be called before a vm is destroyed', function () {
var called = false
var called1 = false,
called2 = false
var Test = Vue.extend({
beforeDestroy: function () {
called = true
called1 = true
}
})
var test = new Test()
test.$on('hook:beforeDestroy', function () {
called2 = true
})
test.$destroy()
assert.ok(called)
assert.ok(called1)
assert.ok(called2)
})

})

describe('afterDestroy', function () {

it('should be called after a vm is destroyed', function () {
var called = false,
var called1 = false, called2 = false,
Test = Vue.extend({
afterDestroy: function () {
assert.notOk(this.$el.parentNode)
called = true
called1 = true
}
})
var test = new Test()
document.body.appendChild(test.$el)
test.$on('hook:afterDestroy', function () {
called2 = true
})
test.$destroy()
assert.ok(called)
assert.ok(called1)
assert.ok(called2)
})

})

describe('enteredView', function () {

it('should be called after enter view', function () {
var called1 = false, called2 = false,
test = new Vue({
enteredView: function () {
assert.strictEqual(this.$el.parentNode, document.getElementById('test'))
called1 = true
}
})
test.$on('hook:enteredView', function () {
called2 = true
})
test.$appendTo('#test')
assert.ok(called1)
assert.ok(called2)
})

})

describe('leftView', function () {

it('should be called after left view', function () {
var called1 = false, called2 = false,
test = new Vue({
leftView: function () {
assert.strictEqual(this.$el.parentNode, null)
called1 = true
}
})
test.$on('hook:leftView', function () {
called2 = true
})
document.getElementById('test').appendChild(test.$el)
test.$remove()
assert.ok(called1)
assert.ok(called2)
})

})

describe('Hook inheritance', function () {

it('should merge hooks with parent Class', function () {
var parentCreated = false,
childCreated = false
var called = []
var Parent = Vue.extend({
created: function () {
parentCreated = true
called.push('parent')
}
})
var Child = Parent.extend({
created: function () {
childCreated = true
called.push('child')
}
})
new Child({
created: function () {
called.push('instance')
}
})
new Child()
assert.ok(parentCreated)
assert.ok(childCreated)
assert.deepEqual(called, ['parent', 'child', 'instance'])
})

})
Expand Down

0 comments on commit d75ee62

Please sign in to comment.