Evan You 12 лет назад
Родитель
Сommit
6fba91895d
3 измененных файлов с 151 добавлено и 32 удалено
  1. 12 0
      changes.md
  2. 128 32
      src/directive.js
  3. 11 0
      src/parse/directive.js

+ 12 - 0
changes.md

@@ -67,6 +67,18 @@ computed: {
 }
 ```
 
+## Directive changes
+
+### New options
+
+- `literal`: replaces old options `isLiteral` and `isEmpty`.
+- `twoway`: indicates the directive is two-way and may write back to the model. Allows the use of `directive.set(value)`.
+- `paramAttributes`: an Array of attribute names to extract as parameters for the directive. For example, given the option value `['lazy']` and markup `<input v-my-dir="msg" my-param="123">`, you can access `this.params['my-param']` with value `'123'` inside directive functions.
+
+### Removed options: `isLiteral`, `isEmpty`, `isFn`
+
+- `isFn` is no longer necessary for directives expecting function values.
+
 ## Two Way filters
 
 If a filter is defined as a function, it is treated as a read filter by default - i.e. it is applied when data is read from the model and applied to the DOM. You can now specify write filters as well, which are applied when writing to the model, triggered by user input. Write filters are only triggered on two-way bindings like `v-model`.

+ 128 - 32
src/directive.js

@@ -17,24 +17,28 @@ var uid = 0
  * @param {Node} el
  * @param {Vue} vm
  * @param {Object} descriptor
- *                 - {String} arg
  *                 - {String} expression
- *                 - {Array<Object>} filters
+ *                 - {String} [arg]
+ *                 - {Array<Object>} [filters]
+ * @param {Object|Function} definition
+ *                          - {Function} update
+ *                          - {Function} [bind]
+ *                          - {Function} [unbind]
+ *                          - {Boolean} [literal]
+ *                          - {Boolean} [twoway]
+ *                          - {Array} [params]
  * @constructor
  */
 
-function Directive (type, el, vm, descriptor) {
+function Directive (type, el, vm, descriptor, definition) {
   // public
   this.type = type
   this.el = el
   this.vm = vm
+  this.value = undefined
   this.arg = descriptor.arg
-  this.expression = descriptor.expression
+  this.expression = descriptor.expression.trim()
   this.filters = descriptor.filters
-  this.value = undefined
-
-  // TODO
-  // mixin type definition
 
   // private
   this._id = ++uid
@@ -43,18 +47,82 @@ function Directive (type, el, vm, descriptor) {
   this._deps = Object.create(null)
   this._newDeps = Object.create(null)
 
-  // TODO
-  // test for simple path vs. expression
-  this._getter = expParser.parse(this.expression)
-  this._setter = this._getter.setter
+  // init definition
+  this._initDef(definition)
+
+  if (this.expression && !this.isLiteral) {
+    // TODO
+    // test for simple path vs. expression
+    this._getter = expParser.parse(this.expression, this.twoway)
+    this._setter = this._getter.setter
+
+    // init filters
+    this._initFilters()
+    // init dependencies
+    this._initDeps()
+    // init methods that need to be context-bound
+    this._initBoundMethods()
+    // update for the first time
+    this._realUpdate(true)
+  }
+}
+
+var p = Directive.prototype
+
+/**
+ * Initialize read and write filters
+ */
 
+p._initFilters = function () {
+  if (!this.filters) {
+    return
+  }
   var self = this
+  var vm = this.vm
+  var registry = vm.$options.filters
+  this.filters.forEach(function (f) {
+    var def = registry[f.name]
+    var args = f.args
+    var read, write
+    if (typeof def === 'function') {
+      read = def
+    } else {
+      read = def.read
+      write = def.write
+    }
+    if (read) {
+      if (!self._readFilters) {
+        self._readFilters = []
+      }
+      self._readFilters.push(function (value) {
+        return args
+          ? read.apply(vm, [value].concat(args))
+          : read.call(vm, value)
+      })
+    }
+    if (write) {
+      if (!self._writeFilters) {
+        self._writeFilters = []
+      }
+      self._writeFilters.push(function (value) {
+        return args
+          ? write.apply(vm, [value, self.value].concat(args))
+          : write.call(vm, value, self.value)
+      })
+    }
+  })
+}
+
+/**
+ * Add root level path as a dependency.
+ * this is specifically for the case where the expression
+ * references a non-existing root level path, and later
+ * that path is created with `vm.$add`.
+ * e.g. "a && a.b"
+ */
 
-  // add root level path as a dependency.
-  // this is specifically for the case where the expression
-  // references a non-existing root level path, and later
-  // that path is created with `vm.$add`.
-  // e.g. "a && a.b"
+p._initDeps = function () {
+  var self = this
   var paths = this._getter.paths
   paths.forEach(function (path) {
     if (path.indexOf('.') < 0 && path.indexOf('[') < 0) {
@@ -62,6 +130,37 @@ function Directive (type, el, vm, descriptor) {
     }
   })
   this._deps = this._newDeps
+}
+
+/**
+ * Initialize the directive instance's definition.
+ */
+
+p._initDef = function (definition) {
+  _.extend(this, definition)
+  // init params
+  var el = this.el
+  var attrs = this.paramAttributes
+  if (attrs) {
+    var params = this.params = {}
+    attrs.forEach(function (p) {
+      params[p] = el.getAttribute(p)
+      el.removeAttribute(p)
+    })
+  }
+  // call bind hook
+  if (this.bind) {
+    this.bind()
+  }
+}
+
+/**
+ * Initialize a few methods that need to be context-bound
+ * so we don't have to create them ad-hoc everytime
+ */
+
+p._initBoundMethods = function () {
+  var self = this
 
   /**
    * Unlock function used in .set()
@@ -89,17 +188,13 @@ function Directive (type, el, vm, descriptor) {
       init
     ) {
       self.value = value
-      // TODO call definition update
-      console.log('updated! new value: ' + value)
+      if (self.update) {
+        self.update(value)
+      }
     }
   }
-
-  // update for the first time
-  this._realUpdate(true)
 }
 
-var p = Directive.prototype
-
 /**
  * Add a binding dependency to this directive.
  *
@@ -128,7 +223,7 @@ p._addDep = function (path) {
 p.get = function () {
   this._beforeGet()
   var value = this._getter.call(this.vm, this.vm.$scope)
-  if (this.filters) {
+  if (this._readFilters) {
     value = this._applyFilters(value, -1)
   }
   this._afterGet()
@@ -145,7 +240,7 @@ p.get = function () {
 p.set = function (value) {
   if (this._setter) {
     this._locked = true
-    if (this.filters) {
+    if (this._writeFilters) {
       value = this._applyFilters(value, 1)
     }
     this._setter.call(this.vm, this.vm.$scope, value)
@@ -194,13 +289,13 @@ p._update = function () {
  */
 
 p._applyFilters = function (value, direction) {
-  if (direction < 0) {
-    // TODO read
-    return value
-  } else {
-    // TODO write
-    return value
+  var filters = direction > 0
+    ? this._writeFilters
+    : this._readFilters
+  for (var i = 0, l = filters.length; i < l; i++) {
+    value = filters[i](value)
   }
+  return value
 }
 
 /**
@@ -208,6 +303,7 @@ p._applyFilters = function (value, direction) {
  */
 
 p._teardown = function () {
+  if (this.unbind) this.unbind()
   this._unbound = true
   for (var p in this._deps) {
     this._deps[p]._removeSub(this)

+ 11 - 0
src/parse/directive.js

@@ -60,6 +60,17 @@ function pushFilter () {
  * Parse a directive string into an Array of AST-like objects
  * representing directives.
  *
+ * Example:
+ *
+ * "click: a = a + 1 | uppercase" will yield:
+ * {
+ *   arg: 'click',
+ *   expression: 'a = a + 1',
+ *   filters: [
+ *     { name: 'uppercase', args: null }
+ *   ]
+ * }
+ *
  * @param {String} str
  * @return {Array<Object>}
  */