Browse Source

attached/detached hook logic

Evan You 11 năm trước cách đây
mục cha
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`.
 ### 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.
 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
 ## 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.
 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
 ## Computed Properties
 
 
 `$get` and `$set` is now simply `get` and `set`:
 `$get` and `$set` is now simply `get` and `set`:

+ 1 - 0
component.json

@@ -46,6 +46,7 @@
     "src/instance/element.js",
     "src/instance/element.js",
     "src/instance/events.js",
     "src/instance/events.js",
     "src/instance/init.js",
     "src/instance/init.js",
+    "src/instance/misc.js",
     "src/instance/scope.js",
     "src/instance/scope.js",
     "src/observe/array-augmentations.js",
     "src/observe/array-augmentations.js",
     "src/observe/object-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) {
 exports.$appendTo = function (target, cb, withTransition) {
-  target = query(target)
   var op = withTransition === false
   var op = withTransition === false
     ? _.append
     ? _.append
     : transition.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) {
 exports.$before = function (target, cb, withTransition) {
-  target = query(target)
   var op = withTransition === false
   var op = withTransition === false
     ? _.before
     ? _.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) {
 exports.$remove = function (cb, withTransition) {
   var op
   var op
+  var shouldCallHook = this._isAttached && _.inDoc(this.$el)
+  var self = this
+  var realCb = function () {
+    if (shouldCallHook) {
+      self._callHook('detached')
+    }
+    if (cb) cb()
+  }
   if (
   if (
     this._isBlock &&
     this._isBlock &&
     !this._blockFragment.hasChildNodes()
     !this._blockFragment.hasChildNodes()
@@ -97,12 +89,44 @@ exports.$remove = function (cb, withTransition) {
     op = withTransition === false
     op = withTransition === false
       ? _.append
       ? _.append
       : transition.removeThenAppend 
       : transition.removeThenAppend 
-    blockOp(this, this._blockFragment, op, cb)
+    blockOp(this, this._blockFragment, op, realCb)
   } else if (this.$el.parentNode) {
   } else if (this.$el.parentNode) {
     op = withTransition === false
     op = withTransition === false
       ? _.remove
       ? _.remove
       : transition.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
  * Set instance target element and kick off the compilation
  * process. The passed in `el` can be a selector string, an
  * process. The passed in `el` can be a selector string, an
@@ -9,10 +11,24 @@
  */
  */
 
 
 exports.$mount = function (el) {
 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._initElement(el)
   this._compile()
   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),
         el: this.el.cloneNode(true),
         parent: this.vm
         parent: this.vm
       })
       })
-      this.cache[this.id] = this.childVM
+      if (this.keepAlive) {
+        this.cache[this.id] = this.childVM
+      }
       this.childVM.$before(this.ref)
       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 dirParser = require('../parse/directive')
 var templateParser = require('../parse/template')
 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.
  * The main entrance to the compilation process.
  * Calling this function requires the instance's `$el` to
  * Calling this function requires the instance's `$el` to
@@ -47,7 +19,6 @@ exports._compile = function () {
   } else {
   } else {
     this._compileNode(this.$el)
     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.
  * Setup the instance's option events.
  * If the value is a string, we pull it from the
  * 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
  * Trigger all handlers for a hook
  *
  *

+ 2 - 0
src/instance/init.js

@@ -33,6 +33,8 @@ exports._init = function (options) {
   // lifecycle state
   // lifecycle state
   this._isCompiled  = false
   this._isCompiled  = false
   this._isDestroyed = false
   this._isDestroyed = false
+  this._isReady     = false
+  this._isAttached  = false
 
 
   // anonymous instances are created by flow-control
   // anonymous instances are created by flow-control
   // directives such as v-if and v-repeat
   // 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.
  * Remove by appending to another parent with transition.
+ * This is only used in block operations.
  *
  *
  * @oaram {Element} el
  * @oaram {Element} el
  * @param {Element} target
  * @param {Element} target
@@ -77,10 +78,6 @@ exports.removeThenAppend = function (el, target, cb, vm) {
  */
  */
 
 
 var apply = exports.apply = function (el, direction, op, vm) {
 var apply = exports.apply = function (el, direction, op, vm) {
-  function applyOp () {
-    op()
-    vm._callHook(direction > 0 ? 'attached' : 'detached')
-  }
   var transData = el.__v_trans
   var transData = el.__v_trans
   if (
   if (
     !transData ||
     !transData ||
@@ -89,7 +86,7 @@ var apply = exports.apply = function (el, direction, op, vm) {
     // animation.
     // animation.
     (vm.$parent && !vm.$parent._isCompiled)
     (vm.$parent && !vm.$parent._isCompiled)
   ) {
   ) {
-    return applyOp()
+    return op()
   }
   }
   // determine the transition type on the element
   // determine the transition type on the element
   var jsTransition = vm._asset('transitions', transData.id)
   var jsTransition = vm._asset('transitions', transData.id)
@@ -98,7 +95,7 @@ var apply = exports.apply = function (el, direction, op, vm) {
     applyJSTransition(
     applyJSTransition(
       el,
       el,
       direction,
       direction,
-      applyOp,
+      op,
       transData,
       transData,
       jsTransition
       jsTransition
     )
     )
@@ -107,11 +104,11 @@ var apply = exports.apply = function (el, direction, op, vm) {
     applyCSSTransition(
     applyCSSTransition(
       el,
       el,
       direction,
       direction,
-      applyOp,
+      op,
       transData
       transData
     )
     )
   } else {
   } else {
     // not applicable
     // not applicable
-    applyOp()
+    op()
   }
   }
 }
 }

+ 15 - 0
src/util/dom.js

@@ -1,5 +1,20 @@
 var config = require('../config')
 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.
  * 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
  * Resolve read & write filters for a vm instance. The
  * filters descriptor Array comes from the directive parser.
  * filters descriptor Array comes from the directive parser.

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

@@ -23,9 +23,10 @@ strats.created =
 strats.ready =
 strats.ready =
 strats.attached =
 strats.attached =
 strats.detached =
 strats.detached =
-strats.beforeMount =
+strats.beforeCompile =
+strats.compiled =
 strats.beforeDestroy =
 strats.beforeDestroy =
-strats.afterDestroy =
+strats.destroyed =
 strats.paramAttributes = function (parentVal, childVal) {
 strats.paramAttributes = function (parentVal, childVal) {
   return (parentVal || []).concat(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/bindings'))
 extend(p, require('./instance/element'))
 extend(p, require('./instance/element'))
 extend(p, require('./instance/compile'))
 extend(p, require('./instance/compile'))
+extend(p, require('./instance/misc'))
 
 
 /**
 /**
  * Mixin public API methods
  * Mixin public API methods