Sfoglia il codice sorgente

attached/detached hook logic

Evan You 11 anni fa
parent
commit
6985f062c3

+ 10 - 2
changes.md

@@ -42,16 +42,24 @@ Whether to inherit parent scope data. Set it to `true` if you want to create a c
 
 ### removed options: `id`, `tagName`, `className`, `attributes`, `lazy`.
 
-Since now a vm must always be provided the `el` option or explicitly mounted to an existing element, the element creation releated options have been removed for simplicity. If you need to modify your element's attributes, simply do so in the new `beforeMount` hook.
+Since now a vm must always be provided the `el` option or explicitly mounted to an existing element, the element creation releated options have been removed for simplicity. If you need to modify your element's attributes, simply do so in the new `beforeCompile` hook.
 
 The `lazy` option is removed because this does not belong at the vm level. Users should be able to configure individual `v-model` instances to be lazy or not.
 
 ## Hook changes
 
-### new hook: `beforeMount`
+### new hook: `beforeCompile`
 
 This new hook is introduced to accompany the separation of instantiation and DOM mounting. It is called right before the DOM compilation starts and `this.$el` is available, so you can do some pre-processing on the element here.
 
+### new hook: `compiled` & redesigned hook: `ready`
+
+The `compiled` hook indicates the element has been fully compiled based on initial data. However this doesn't indicate if the element has been inserted into the DOM yet. This is essentially the old `ready` hook.
+
+The new `ready` hook now is only fired after the instance is compiled and **inserted into the document for the first time**.
+
+### renamed hook: `afterDestroy` -> `destroyed`
+
 ## Computed Properties
 
 `$get` and `$set` is now simply `get` and `set`:

+ 1 - 0
component.json

@@ -46,6 +46,7 @@
     "src/instance/element.js",
     "src/instance/events.js",
     "src/instance/init.js",
+    "src/instance/misc.js",
     "src/instance/scope.js",
     "src/observe/array-augmentations.js",
     "src/observe/object-augmentations.js",

+ 45 - 21
src/api/dom.js

@@ -10,18 +10,10 @@ var transition = require('../transition')
  */
 
 exports.$appendTo = function (target, cb, withTransition) {
-  target = query(target)
   var op = withTransition === false
     ? _.append
     : transition.append
-  if (this._isBlock) {
-    blockOp(this, target, op, cb)
-  } else {
-    op(this.$el, target, cb, this)
-  }
-  if (cb && !withTransition) {
-    cb()
-  }
+  insert(this, target, op, cb, withTransition)
 }
 
 /**
@@ -50,18 +42,10 @@ exports.$prependTo = function (target, cb, withTransition) {
  */
 
 exports.$before = function (target, cb, withTransition) {
-  target = query(target)
   var op = withTransition === false
     ? _.before
-    : transition.before 
-  if (this._isBlock) {
-    blockOp(this, target, op, cb)
-  } else {
-    op(this.$el, target, cb, this)
-  }
-  if (cb && !withTransition) {
-    cb()
-  }
+    : transition.before
+  insert(this, target, op, cb, withTransition)
 }
 
 /**
@@ -90,6 +74,14 @@ exports.$after = function (target, cb, withTransition) {
 
 exports.$remove = function (cb, withTransition) {
   var op
+  var shouldCallHook = this._isAttached && _.inDoc(this.$el)
+  var self = this
+  var realCb = function () {
+    if (shouldCallHook) {
+      self._callHook('detached')
+    }
+    if (cb) cb()
+  }
   if (
     this._isBlock &&
     !this._blockFragment.hasChildNodes()
@@ -97,12 +89,44 @@ exports.$remove = function (cb, withTransition) {
     op = withTransition === false
       ? _.append
       : transition.removeThenAppend 
-    blockOp(this, this._blockFragment, op, cb)
+    blockOp(this, this._blockFragment, op, realCb)
   } else if (this.$el.parentNode) {
     op = withTransition === false
       ? _.remove
       : transition.remove
-    op(this.$el, cb, this)
+    op(this.$el, realCb, this)
+  }
+}
+
+/**
+ * Shared DOM insertion function.
+ *
+ * @param {Vue} vm
+ * @param {Element} target
+ * @param {Function} op
+ * @param {Function} [cb]
+ * @param {Boolean} [withTransition]
+ */
+
+function insert (vm, target, op, cb, withTransition) {
+  target = query(target)
+  var shouldCallHook =
+    !vm._isAttached &&
+    !_.inDoc(vm.$el) &&
+    _.inDoc(target)
+  var realCb = function () {
+    if (shouldCallHook) {
+      vm._callHook('attached')
+    }
+    if (cb) cb()
+  }
+  if (vm._isBlock) {
+    blockOp(vm, target, op, realCb)
+  } else {
+    op(vm.$el, target, realCb, vm)
+  }
+  if (withTransition === false) {
+    realCb()
   }
 }
 

+ 18 - 2
src/api/lifecycle.js

@@ -1,3 +1,5 @@
+var _ = require('../util')
+
 /**
  * Set instance target element and kick off the compilation
  * process. The passed in `el` can be a selector string, an
@@ -9,10 +11,24 @@
  */
 
 exports.$mount = function (el) {
-  this._callHook('beforeMount')
+  if (this._isCompiled) {
+    _.warn('$mount() should be called only once.')
+    return
+  }
+  this._callHook('beforeCompile')
   this._initElement(el)
   this._compile()
-  this._callHook('ready')
+  this._isCompiled = true
+  this._callHook('compiled')
+  this.$once('hook:attached', function () {
+    this._isAttached = true
+    this._isReady = true
+    this._callHook('ready')
+    this._initDOMHooks()
+  })
+  if (_.inDoc(this.$el)) {
+    this._callHook('attached')
+  }
 }
 
 /**

+ 3 - 1
src/directives/component.js

@@ -136,7 +136,9 @@ module.exports = {
         el: this.el.cloneNode(true),
         parent: this.vm
       })
-      this.cache[this.id] = this.childVM
+      if (this.keepAlive) {
+        this.cache[this.id] = this.childVM
+      }
       this.childVM.$before(this.ref)
     }
   },

+ 0 - 29
src/instance/compile.js

@@ -5,34 +5,6 @@ var textParser = require('../parse/text')
 var dirParser = require('../parse/directive')
 var templateParser = require('../parse/template')
 
-/**
- * Retrive an asset by type and id.
- * Search order:
- *   -> instance options
- *   -> constructor options
- *   -> recursive parent search
- *
- * @param {String} type
- * @param {String} id
- */
-
-exports._asset = function (type, id) {
-  var own = this.$options[type]
-  var ctor = this.constructor.options[type]
-  var parent = this.$parent
-  var asset =
-    (own && own[id]) ||
-    (ctor && ctor[id]) ||
-    (parent && parent._asset(type, id))
-  if (!asset) {
-    _.warn(
-      'Failed to locate ' +
-      type.slice(0, -1) + ': ' + id
-    )
-  }
-  return asset
-}
-
 /**
  * The main entrance to the compilation process.
  * Calling this function requires the instance's `$el` to
@@ -47,7 +19,6 @@ exports._compile = function () {
   } else {
     this._compileNode(this.$el)
   }
-  this._isCompiled = true
 }
 
 /**

+ 28 - 0
src/instance/events.js

@@ -1,3 +1,5 @@
+var inDoc = require('../util').inDoc
+
 /**
  * Setup the instance's option events.
  * If the value is a string, we pull it from the
@@ -22,6 +24,32 @@ exports._initEvents = function () {
   }
 }
 
+/**
+ * Setup recursive attached/detached calls
+ */
+
+exports._initDOMHooks = function () {
+  var children = this._children
+  this.$on('hook:attached', function () {
+    this._isAttached = true
+    for (var i = 0, l = children.length; i < l; i++) {
+      var child = children[i]
+      if (!child._isAttached && inDoc(child.$el)) {
+        child._callHook('attached')
+      }
+    }
+  })
+  this.$on('hook:detached', function () {
+    this._isAttached = false
+    for (var i = 0, l = children.length; i < l; i++) {
+      var child = children[i]
+      if (child._isAttached && !inDoc(child.$el)) {
+        child._callHook('detached')
+      }
+    }
+  })
+}
+
 /**
  * Trigger all handlers for a hook
  *

+ 2 - 0
src/instance/init.js

@@ -33,6 +33,8 @@ exports._init = function (options) {
   // lifecycle state
   this._isCompiled  = false
   this._isDestroyed = false
+  this._isReady     = false
+  this._isAttached  = false
 
   // anonymous instances are created by flow-control
   // directives such as v-if and v-repeat

+ 30 - 0
src/instance/misc.js

@@ -0,0 +1,30 @@
+var _ = require('../util')
+
+/**
+ * Retrive an asset by type and id.
+ * Search order:
+ *   -> instance options
+ *   -> constructor options
+ *   -> recursive parent search
+ *
+ * @param {String} type
+ * @param {String} id
+ * @return {*}
+ */
+
+exports._asset = function (type, id) {
+  var own = this.$options[type]
+  var ctor = this.constructor.options[type]
+  var parent = this.$parent
+  var asset =
+    (own && own[id]) ||
+    (ctor && ctor[id]) ||
+    (parent && parent._asset(type, id))
+  if (!asset) {
+    _.warn(
+      'Failed to locate ' +
+      type.slice(0, -1) + ': ' + id
+    )
+  }
+  return asset
+}

+ 5 - 8
src/transition/index.js

@@ -51,6 +51,7 @@ exports.remove = function (el, cb, vm) {
 
 /**
  * Remove by appending to another parent with transition.
+ * This is only used in block operations.
  *
  * @oaram {Element} el
  * @param {Element} target
@@ -77,10 +78,6 @@ exports.removeThenAppend = function (el, target, cb, vm) {
  */
 
 var apply = exports.apply = function (el, direction, op, vm) {
-  function applyOp () {
-    op()
-    vm._callHook(direction > 0 ? 'attached' : 'detached')
-  }
   var transData = el.__v_trans
   if (
     !transData ||
@@ -89,7 +86,7 @@ var apply = exports.apply = function (el, direction, op, vm) {
     // animation.
     (vm.$parent && !vm.$parent._isCompiled)
   ) {
-    return applyOp()
+    return op()
   }
   // determine the transition type on the element
   var jsTransition = vm._asset('transitions', transData.id)
@@ -98,7 +95,7 @@ var apply = exports.apply = function (el, direction, op, vm) {
     applyJSTransition(
       el,
       direction,
-      applyOp,
+      op,
       transData,
       jsTransition
     )
@@ -107,11 +104,11 @@ var apply = exports.apply = function (el, direction, op, vm) {
     applyCSSTransition(
       el,
       direction,
-      applyOp,
+      op,
       transData
     )
   } else {
     // not applicable
-    applyOp()
+    op()
   }
 }

+ 15 - 0
src/util/dom.js

@@ -1,5 +1,20 @@
 var config = require('../config')
 
+/**
+ * Check if a node is in the document.
+ *
+ * @param {Node} node
+ * @return {Boolean}
+ */
+
+var doc =
+  typeof document !== 'undefined' &&
+  document.documentElement
+
+exports.inDoc = function (node) {
+  return doc && doc.contains(node)
+}
+
 /**
  * Extract an attribute from a node.
  *

+ 0 - 2
src/util/filter.js

@@ -1,5 +1,3 @@
-var _ = require('./debug')
-
 /**
  * Resolve read & write filters for a vm instance. The
  * filters descriptor Array comes from the directive parser.

+ 3 - 2
src/util/merge-option.js

@@ -23,9 +23,10 @@ strats.created =
 strats.ready =
 strats.attached =
 strats.detached =
-strats.beforeMount =
+strats.beforeCompile =
+strats.compiled =
 strats.beforeDestroy =
-strats.afterDestroy =
+strats.destroyed =
 strats.paramAttributes = function (parentVal, childVal) {
   return (parentVal || []).concat(childVal || [])
 }

+ 1 - 0
src/vue.js

@@ -86,6 +86,7 @@ extend(p, require('./instance/scope'))
 extend(p, require('./instance/bindings'))
 extend(p, require('./instance/element'))
 extend(p, require('./instance/compile'))
+extend(p, require('./instance/misc'))
 
 /**
  * Mixin public API methods