Sfoglia il codice sorgente

Directive: use new params API

Evan You 10 anni fa
parent
commit
4e3fb0da17

+ 73 - 27
src/directive.js

@@ -77,6 +77,9 @@ Directive.prototype._bind = function () {
     _.extend(this, def)
   }
 
+  // setup directive params
+  this._setupParams()
+
   // initial bind
   if (this.bind) {
     this.bind()
@@ -131,6 +134,65 @@ Directive.prototype._bind = function () {
   this._bound = true
 }
 
+/**
+ * Setup all param attributes, e.g. track-by,
+ * transition-mode, etc...
+ */
+
+Directive.prototype._setupParams = function () {
+  if (!this.params) {
+    return
+  }
+  var params = this.params
+  // swap the params array with a fresh object.
+  this.params = Object.create(null)
+  var i = params.length
+  var key, val, mappedKey
+  while (i--) {
+    key = params[i]
+    mappedKey = _.camelize(key)
+    val = _.attr(this.el, key)
+    if (val != null) {
+      // static
+      this.params[mappedKey] = val === '' ? true : val
+    } else {
+      // dynamic
+      val = _.getBindAttr(this.el, key)
+      if (val != null) {
+        this._setupParamWatcher(mappedKey, val)
+      }
+    }
+  }
+}
+
+/**
+ * Setup a watcher for a dynamic param.
+ *
+ * @param {String} key
+ * @param {String} expression
+ */
+
+Directive.prototype._setupParamWatcher = function (key, expression) {
+  var self = this
+  var called = false
+  var unwatch = (this._scope || this.vm).$watch(expression, function (val, oldVal) {
+    self.params[key] = val
+    // since we are in immediate mode,
+    // only call the param change callbacks if this is not the first update.
+    if (called) {
+      var cb = self.paramWatchers && self.paramWatchers[key]
+      if (cb) {
+        cb.call(self, val, oldVal)
+      }
+    } else {
+      called = true
+    }
+  }, {
+    immediate: true
+  })
+  ;(this._paramUnwatchFns || (this._paramUnwatchFns = [])).push(unwatch)
+}
+
 /**
  * Check if the directive is a function caller
  * and if the expression is a callable one. If both true,
@@ -161,30 +223,6 @@ Directive.prototype._checkStatement = function () {
   }
 }
 
-/**
- * Check for an attribute directive param, e.g. lazy
- *
- * @param {String} name
- * @return {String}
- */
-
-Directive.prototype.param = function (name) {
-  var param = _.attr(this.el, name)
-  if (param != null) {
-    param = (this._scope || this.vm).$interpolate(param)
-  } else {
-    param = _.getBindAttr(this.el, name)
-    if (param != null) {
-      param = (this._scope || this.vm).$eval(param)
-      process.env.NODE_ENV !== 'production' && _.log(
-        'You are using bind- syntax on "' + name + '", which ' +
-        'is a directive param. It will be evaluated only once.'
-      )
-    }
-  }
-  return param
-}
-
 /**
  * Set the corresponding value with the setter.
  * This should only be used in two-way directives
@@ -253,13 +291,21 @@ Directive.prototype._teardown = function () {
       this._watcher.teardown()
     }
     var listeners = this._listeners
+    var i
     if (listeners) {
-      for (var i = 0; i < listeners.length; i++) {
+      i = listeners.length
+      while (i--) {
         _.off(this.el, listeners[i][0], listeners[i][1])
       }
     }
-    this.vm = this.el =
-    this._watcher = this._listeners = null
+    var unwatchFns = this._paramUnwatchFns
+    if (unwatchFns) {
+      i = unwatchFns.length
+      while (i--) {
+        unwatchFns[i]()
+      }
+    }
+    this.vm = this.el = this._watcher = this._listeners = null
   }
 }
 

+ 13 - 29
src/directives/element/partial.js

@@ -1,40 +1,27 @@
 var _ = require('../../util')
-var FragmentFactory = require('../../fragment/factory')
 var vIf = require('../public/if')
-var Watcher = require('../../watcher')
+var FragmentFactory = require('../../fragment/factory')
 
 module.exports = {
 
   priority: 1750,
 
-  bind: function () {
-    var el = this.el
-    this.anchor = _.createAnchor('v-partial')
-    _.replace(el, this.anchor)
-    var id = el.getAttribute('name')
-    if (id != null) {
-      // static partial
-      this.insert(id)
-    } else {
-      id = _.getBindAttr(el, 'name')
-      if (id) {
-        this.setupDynamic(id)
-      }
-    }
-  },
+  params: ['name'],
 
-  setupDynamic: function (exp) {
-    var self = this
-    var onNameChange = function (value) {
-      vIf.remove.call(self)
+  // watch changes to name for dynamic partials
+  paramWatchers: {
+    name: function (value) {
+      vIf.remove.call(this)
       if (value) {
-        self.insert(value)
+        this.insert(value)
       }
     }
-    this.nameWatcher = new Watcher(this.vm, exp, onNameChange, {
-      scope: this._scope
-    })
-    onNameChange(this.nameWatcher.value)
+  },
+
+  bind: function () {
+    this.anchor = _.createAnchor('v-partial')
+    _.replace(this.el, this.anchor)
+    this.insert(this.params.name)
   },
 
   insert: function (id) {
@@ -52,8 +39,5 @@ module.exports = {
     if (this.frag) {
       this.frag.destroy()
     }
-    if (this.nameWatcher) {
-      this.nameWatcher.teardown()
-    }
   }
 }

+ 3 - 1
src/directives/element/slot.js

@@ -10,6 +10,8 @@ module.exports = {
 
   priority: 1750,
 
+  params: ['name'],
+
   bind: function () {
     var host = this.vm
     var raw = host.$options._content
@@ -19,7 +21,7 @@ module.exports = {
       return
     }
     var context = host._context
-    var slotName = this.param('name')
+    var slotName = this.params.name
     if (!slotName) {
       // Default content
       var self = this

+ 10 - 11
src/directives/internal/component.js

@@ -5,6 +5,12 @@ module.exports = {
 
   priority: 1500,
 
+  params: [
+    'keep-alive',
+    'transition-mode',
+    'inline-template'
+  ],
+
   /**
    * Setup. Two possible usages:
    *
@@ -17,25 +23,19 @@ module.exports = {
 
   bind: function () {
     if (!this.el.__vue__) {
-      // check keep-alive options.
-      // If yes, instead of destroying the active vm when
-      // hiding (v-if) or switching (dynamic literal) it,
-      // we simply remove it from the DOM and save it in a
-      // cache object, with its constructor id as the key.
-      this.keepAlive = this.param('keep-alive') != null
-
       // check ref
       this.ref = _.findRef(this.el)
       var refs = (this._scope || this.vm).$refs
       if (this.ref && !refs.hasOwnProperty(this.ref)) {
         _.defineReactive(refs, this.ref, null)
       }
-
+      // keep-alive cache
+      this.keepAlive = this.params.keepAlive
       if (this.keepAlive) {
         this.cache = {}
       }
       // check inline-template
-      if (this.param('inline-template') !== null) {
+      if (this.params.inlineTemplate) {
         // extract inline template as a DocumentFragment
         this.inlineTemplate = _.extractContent(this.el, true)
       }
@@ -49,7 +49,6 @@ module.exports = {
         // create a ref anchor
       this.anchor = _.createAnchor('v-component')
       _.replace(this.el, this.anchor)
-      this.transMode = this.param('transition-mode')
       // if static, build right now.
       if (this.literal) {
         this.setComponent(this.expression)
@@ -296,7 +295,7 @@ module.exports = {
     var self = this
     var current = this.childVM
     this.childVM = target
-    switch (self.transMode) {
+    switch (self.params.transitionMode) {
       case 'in-out':
         target.$before(self.anchor, function () {
           self.remove(current, cb)

+ 5 - 1
src/directives/public/bind.js

@@ -29,10 +29,14 @@ module.exports = {
 
   bind: function () {
     var attr = this.arg
+    var tag = this.el.tagName
     // handle interpolation bindings
     if (this.descriptor.interp) {
       // only allow binding on native attributes
-      if (disallowedInterpAttrRE.test(attr)) {
+      if (
+        disallowedInterpAttrRE.test(attr) ||
+        (attr === 'name' && (tag === 'PARTIAL' || tag === 'SLOT'))
+      ) {
         process.env.NODE_ENV !== 'production' && _.warn(
           attr + '="' + this.descriptor.raw + '": ' +
           'attribute interpolation is not allowed in Vue.js ' +

+ 26 - 27
src/directives/public/for.js

@@ -7,6 +7,13 @@ module.exports = {
 
   priority: 2000,
 
+  params: [
+    'track-by',
+    'stagger',
+    'enter-stagger',
+    'leave-stagger'
+  ],
+
   bind: function () {
     // support "item in items" syntax
     var inMatch = this.expression.match(/(.*) in (.*)/)
@@ -48,17 +55,9 @@ module.exports = {
     _.replace(this.el, this.end)
     _.before(this.start, this.end)
 
-    // check for trackby param
-    this.idKey = this.param('track-by')
-
     // check ref
     this.ref = _.findRef(this.el)
 
-    // check for transition stagger
-    var stagger = +this.param('stagger')
-    this.enterStagger = +this.param('enter-stagger') || stagger
-    this.leaveStagger = +this.param('leave-stagger') || stagger
-
     // cache
     this.cache = Object.create(null)
 
@@ -94,7 +93,7 @@ module.exports = {
       item.hasOwnProperty('$key') &&
       item.hasOwnProperty('$value')
 
-    var idKey = this.idKey
+    var trackByKey = this.params.trackBy
     var oldFrags = this.frags
     var frags = this.frags = new Array(data.length)
     var alias = this.alias
@@ -128,7 +127,7 @@ module.exports = {
         }
         // update data for track-by, object repeat &
         // primitive values.
-        if (idKey || convertedFromObject || primitive) {
+        if (trackByKey || convertedFromObject || primitive) {
           frag.scope[alias] = value
         }
       } else { // new isntance
@@ -355,19 +354,19 @@ module.exports = {
    */
 
   cacheFrag: function (value, frag, index, key) {
-    var idKey = this.idKey
+    var trackByKey = this.params.trackBy
     var cache = this.cache
     var primitive = !isObject(value)
     var id
-    if (key || idKey || primitive) {
-      id = idKey
-        ? idKey === '$index'
+    if (key || trackByKey || primitive) {
+      id = trackByKey
+        ? trackByKey === '$index'
           ? index
-          : value[idKey]
+          : value[trackByKey]
         : (key || value)
       if (!cache[id]) {
         cache[id] = frag
-      } else if (idKey !== '$index') {
+      } else if (trackByKey !== '$index') {
         process.env.NODE_ENV !== 'production' &&
         this.warnDuplicate(value)
       }
@@ -397,14 +396,14 @@ module.exports = {
    */
 
   getCachedFrag: function (value, index, key) {
-    var idKey = this.idKey
+    var trackByKey = this.params.trackBy
     var primitive = !isObject(value)
     var frag
-    if (key || idKey || primitive) {
-      var id = idKey
-        ? idKey === '$index'
+    if (key || trackByKey || primitive) {
+      var id = trackByKey
+        ? trackByKey === '$index'
           ? index
-          : value[idKey]
+          : value[trackByKey]
         : (key || value)
       frag = this.cache[id]
     } else {
@@ -425,18 +424,18 @@ module.exports = {
 
   deleteCachedFrag: function (frag) {
     var value = frag.raw
-    var idKey = this.idKey
+    var trackByKey = this.params.trackBy
     var scope = frag.scope
     var index = scope.$index
     // fix #948: avoid accidentally fall through to
     // a parent repeater which happens to have $key.
     var key = scope.hasOwnProperty('$key') && scope.$key
     var primitive = !isObject(value)
-    if (idKey || key || primitive) {
-      var id = idKey
-        ? idKey === '$index'
+    if (trackByKey || key || primitive) {
+      var id = trackByKey
+        ? trackByKey === '$index'
           ? index
-          : value[idKey]
+          : value[trackByKey]
         : (key || value)
       this.cache[id] = null
     } else {
@@ -461,7 +460,7 @@ module.exports = {
     var hook = hooks && (hooks[type] || hooks.stagger)
     return hook
       ? hook.call(frag, index, total)
-      : index * this[type]
+      : index * parseInt(this.params[type] || this.params.stagger, 10)
   },
 
   /**

+ 1 - 2
src/directives/public/model/checkbox.js

@@ -5,12 +5,11 @@ module.exports = {
   bind: function () {
     var self = this
     var el = this.el
-    var number = this.param('number') != null
 
     this.getValue = function () {
       return el.hasOwnProperty('_value')
         ? el._value
-        : number
+        : self.params.number
           ? _.toNumber(el.value)
           : el.value
     }

+ 1 - 0
src/directives/public/model/index.js

@@ -12,6 +12,7 @@ module.exports = {
   priority: 800,
   twoWay: true,
   handlers: handlers,
+  params: ['lazy', 'number', 'debounce'],
 
   /**
    * Possible elements:

+ 1 - 2
src/directives/public/model/radio.js

@@ -5,7 +5,6 @@ module.exports = {
   bind: function () {
     var self = this
     var el = this.el
-    var number = this.param('number') != null
 
     this.getValue = function () {
       // value overwrite via v-bind:value
@@ -13,7 +12,7 @@ module.exports = {
         return el._value
       }
       var val = el.value
-      if (number) {
+      if (self.params.number) {
         val = _.toNumber(val)
       }
       return val

+ 2 - 2
src/directives/public/model/select.js

@@ -13,13 +13,13 @@ module.exports = {
       }
     }
 
-    this.number = this.param('number') != null
+    // check if this is a multiple select
     var multiple = this.multiple = el.hasAttribute('multiple')
 
     // attach listener
     this.listener = function () {
       var value = getValue(el, multiple)
-      value = self.number
+      value = self.params.number
         ? _.isArray(value)
           ? value.map(_.toNumber)
           : _.toNumber(value)

+ 5 - 8
src/directives/public/model/text.js

@@ -6,14 +6,9 @@ module.exports = {
     var self = this
     var el = this.el
     var isRange = el.type === 'range'
-
-    // check params
-    // - lazy: update model on "change" instead of "input"
-    var lazy = this.param('lazy') != null
-    // - number: cast value into number when updating model.
-    var number = this.param('number') != null
-    // - debounce: debounce the input listener
-    var debounce = parseInt(this.param('debounce'), 10)
+    var lazy = this.params.lazy
+    var number = this.params.number
+    var debounce = this.params.debounce
 
     // handle composition events.
     //   http://blog.evanyou.me/2014/01/03/composition-event/
@@ -68,6 +63,8 @@ module.exports = {
         }
       })
     }
+
+    // apply debounce
     if (debounce) {
       this.listener = _.debounce(this.listener, debounce)
     }

+ 1 - 1
test/unit/specs/directives/element/slot_spec.js

@@ -124,7 +124,7 @@ describe('Slot Distribution', function () {
 
   it('should accept expressions in selectors', function () {
     el.innerHTML = '<p>one</p><p slot="two">two</p>'
-    options.template = '<slot name="{{theName}}"></slot>'
+    options.template = '<slot :name="theName"></slot>'
     options.data = {
       theName: 'two'
     }