Evan You 11 лет назад
Родитель
Сommit
b6a3a13b22
7 измененных файлов с 273 добавлено и 98 удалено
  1. 42 6
      src/api/data.js
  2. 9 21
      src/parse/text.js
  3. 75 0
      src/util/filter.js
  4. 1 0
      src/util/index.js
  5. 9 71
      src/watcher.js
  6. 67 0
      test/unit/specs/text_parser_spec.js
  7. 70 0
      test/unit/specs/util_spec.js

+ 42 - 6
src/api/data.js

@@ -1,6 +1,9 @@
-var expParser = require('../parse/expression')
-var textParser = require('../parse/text')
+var _ = require('../util')
 var Watcher = require('../watcher')
+var textParser = require('../parse/text')
+var dirParser = require('../parse/directive')
+var expParser = require('../parse/expression')
+var filterRE = /[^|]\|[^|]/
 
 /**
  * Get the value from an expression on this vm.
@@ -85,6 +88,32 @@ exports.$unwatch = function (id) {
   }
 }
 
+/**
+ * Evaluate a text directive, including filters.
+ *
+ * @param {String} text
+ * @return {String}
+ */
+
+exports.$eval = function (text) {
+  // check for filters.
+  if (filterRE.test(text)) {
+    var dir = dirParser.parse(text)[0]
+    // the filter regex check might give false positive
+    // for pipes inside strings, so it's possible that
+    // we don't get any filters here
+    return dir.filters
+      ? _.applyFilters(
+          this.$get(dir.expression),
+          _.resolveFilters(this, dir.filters).read
+        )
+      : this.$get(dir.expression)
+  } else {
+    // no filter
+    return this.$get(text)
+  }
+}
+
 /**
  * Interpolate a piece of template text.
  *
@@ -93,10 +122,17 @@ exports.$unwatch = function (id) {
  */
 
 exports.$interpolate = function (text) {
-  var exp = textParser.textToExpression(text)
-  return exp
-    ? this.$get(exp)
-    : text
+  var tokens = textParser.parse(text)
+  var vm = this
+  if (tokens) {
+    return tokens.map(function (token) {
+      return token.tag
+        ? vm.$eval(token.value)
+        : token.value
+    }).join('')
+  } else {
+    return text
+  }
 }
 
 /**

+ 9 - 21
src/parse/text.js

@@ -1,5 +1,8 @@
+var _ = require('../util')
 var Cache = require('../cache')
 var config = require('../config')
+var dirParser = require('./directive')
+
 var regexEscapeRE = /[-.*+?^${}()|[\]\/\\]/g
 var cache, tagRE, htmlRE, firstChar, lastChar
 
@@ -70,6 +73,7 @@ exports.parse = function (text) {
   var tokens = []
   var lastIndex = tagRE.lastIndex = 0
   var match, index, value, oneTime
+  /* jshint boss:true */
   while (match = tagRE.exec(text)) {
     index = match.index
     // push text token
@@ -79,11 +83,13 @@ exports.parse = function (text) {
       })
     }
     // tag token
-    value = match[1].trim()
-    oneTime = value.charAt(0) === '*'
+    oneTime = match[1].charAt(0) === '*'
+    value = oneTime
+      ? match[1].slice(1)
+      : match[1]
     tokens.push({
       tag: true,
-      value: oneTime ? value.slice(1) : value,
+      value: value.trim(),
       html: htmlRE.test(match[0]),
       oneTime: oneTime
     })
@@ -96,22 +102,4 @@ exports.parse = function (text) {
   }
   cache.put(text, tokens)
   return tokens
-}
-
-/**
- * Parse a template text string into an expression
- *
- * @param {String} text
- * @return {String}
- */
-
-exports.textToExpression = function (text) {
-  var tokens = exports.parse(text)
-  if (tokens) {
-    return tokens.map(function (token) {
-      return token.tag
-        ? token.value
-        : ('"' + token.value + '"')
-    }).join('+')
-  }
 }

+ 75 - 0
src/util/filter.js

@@ -0,0 +1,75 @@
+var _ = require('./debug')
+
+/**
+ * Resolve read & write filters for a vm instance. The
+ * filters descriptor Array comes from the directive parser.
+ *
+ * This is extracted into its own utility so it can
+ * be used in multiple scenarios.
+ *
+ * @param {Vue} vm
+ * @param {Array<Object>} filters
+ * @param {Watcher} [target]
+ * @return {Object}
+ */
+
+exports.resolveFilters = function (vm, filters, target) {
+  if (!filters) {
+    return
+  }
+  var res = target || {}
+  var registry = vm.$options.filters
+  filters.forEach(function (f) {
+    var def = registry[f.name]
+    var args = f.args
+    var reader, writer
+    if (!def) {
+      _.warn('Failed to resolve filter: ' + f.name)
+    } else if (typeof def === 'function') {
+      reader = def
+    } else {
+      reader = def.read
+      writer = def.write
+    }
+    if (reader) {
+      if (!res.read) {
+        res.read = []
+      }
+      res.read.push(function (value) {
+        return args
+          ? reader.apply(vm, [value].concat(args))
+          : reader.call(vm, value)
+      })
+    }
+    // only watchers needs write filters
+    if (target && writer) {
+      if (!res.write) {
+        res.write = []
+      }
+      res.write.push(function (value) {
+        return args
+          ? writer.apply(vm, [value, res.value].concat(args))
+          : writer.call(vm, value, res.value)
+      })
+    }
+  })
+  return res
+}
+
+/**
+ * Apply filters to a value
+ *
+ * @param {*} value
+ * @param {Array} filters
+ * @return {*}
+ */
+
+exports.applyFilters = function (value, filters) {
+  if (!filters) {
+    return value
+  }
+  for (var i = 0, l = filters.length; i < l; i++) {
+    value = filters[i](value)
+  }
+  return value
+}

+ 1 - 0
src/util/index.js

@@ -5,4 +5,5 @@ extend(exports, lang)
 extend(exports, require('./env'))
 extend(exports, require('./dom'))
 extend(exports, require('./option'))
+extend(exports, require('./filter'))
 extend(exports, require('./debug'))

+ 9 - 71
src/watcher.js

@@ -31,9 +31,14 @@ function Watcher (vm, expression, cb, ctx, filters, needSet) {
   this.deps = Object.create(null)
   this.newDeps = Object.create(null)
   // setup filters if any.
-  this.initFilters(filters)
+  // We delegate directive filters here to the watcher
+  // because they need to be included in the dependency
+  // collection process.
+  var res = _.resolveFilters(vm, filters, this)
+  this.readFilters = res && res.read
+  this.writeFilters = res && res.write
   // parse expression for getter/setter
-  var res = expParser.parse(expression, needSet)
+  res = expParser.parse(expression, needSet)
   this.getter = res.get
   this.setter = res.set
   this.initDeps(res.paths)
@@ -64,55 +69,6 @@ p.initDeps = function (paths) {
   this.value = this.get()
 }
 
-/**
- * Initialize read and write filters.
- * We delegate directive filters here to the watcher
- * because they need to be included in the dependency
- * collection process.
- *
- * @param {Array} filters
- */
-
-p.initFilters = function (filters) {
-  if (!filters) {
-    return
-  }
-  var self = this
-  var vm = this.vm
-  var registry = vm.$options.filters
-  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 a binding dependency to this directive.
  *
@@ -141,9 +97,7 @@ p.addDep = function (path) {
 p.get = function () {
   this.beforeGet()
   var value = this.getter.call(this.vm, this.vm.$scope)
-  if (this.readFilters) {
-    value = applyFilters(value, this.readFilters)
-  }
+  value = _.applyFilters(value, this.readFilters)
   this.afterGet()
   return value
 }
@@ -155,9 +109,7 @@ p.get = function () {
  */
 
 p.set = function (value) {
-  if (this.writeFilters) {
-    value = applyFilters(value, this.writeFilters)
-  }
+  value = _.applyFilters(value, this.writeFilters)
   this.setter.call(this.vm, this.vm.$scope, value)
 }
 
@@ -224,18 +176,4 @@ p.teardown = function () {
   }
 }
 
-/**
- * Apply filters to a value
- *
- * @param {*} value
- * @param {Array} filters
- */
-
-function applyFilters (value, filters) {
-  for (var i = 0, l = filters.length; i < l; i++) {
-    value = filters[i](value)
-  }
-  return value
-}
-
 module.exports = Watcher

+ 67 - 0
test/unit/specs/text_parser_spec.js

@@ -0,0 +1,67 @@
+var textParser = require('../../../src/parse/text')
+var config = require('../../../src/config')
+
+var testCases = [
+  {
+    // no tags
+    text: 'haha',
+    expected: null
+  },
+  {
+    // basic
+    text: 'a {{ a }} c',
+    expected: [
+      { value: 'a ' },
+      { tag: true, value: 'a', html: false, oneTime: false },
+      { value: ' c' }
+    ]
+  },
+  {
+    // html
+    text: '{{ text }} and {{{ html }}}',
+    expected: [
+      { tag: true, value: 'text', html: false, oneTime: false },
+      { value: ' and ' },
+      { tag: true, value: 'html', html: true, oneTime: false },
+    ]
+  },
+  {
+    // one time
+    text: '{{* text }} and {{{* html }}}',
+    expected: [
+      { tag: true, value: 'text', html: false, oneTime: true },
+      { value: ' and ' },
+      { tag: true, value: 'html', html: true, oneTime: true },
+    ]
+  }
+]
+
+function assertParse (test) {
+  var res = textParser.parse(test.text)
+  var exp = test.expected
+  if (!Array.isArray(exp)) {
+    expect(res).toBe(exp)
+  } else {
+    expect(res.length).toBe(exp.length)
+    res.forEach(function (r, i) {
+      var e = exp[i]
+      for (var key in e) {
+        expect(e[key]).toEqual(r[key])
+      }
+    })
+  }
+}
+
+describe('Text Parser', function () {
+
+  it('parse', function () {
+    testCases.forEach(assertParse)
+  })
+
+  it('cache', function () {
+    var res1 = textParser.parse('{{a}}')
+    var res2 = textParser.parse('{{a}}')
+    expect(res1).toBe(res2)
+  })
+
+})

+ 70 - 0
test/unit/specs/util_spec.js

@@ -134,6 +134,76 @@ describe('Util', function () {
 
   })
 
+  describe('Filter', function () {
+
+    var debug = require('../../../src/util/debug')
+    beforeEach(function () {
+      spyOn(debug, 'warn')
+    })
+    
+    it('resolveFilters', function () {
+      var filters = [
+        { name: 'a', args: ['a'] },
+        { name: 'b', args: ['b']},
+        { name: 'c' }
+      ]
+      var vm = {
+        $options: {
+          filters: {
+            a: function (v, arg) {
+              return { id: 'a', value: v, arg: arg }
+            },
+            b: {
+              read: function (v, arg) {
+                return { id: 'b', value: v, arg: arg }
+              },
+              write: function (v, oldVal, arg) {
+                return { id: 'bw', value: v, arg: arg }
+              }
+            }
+          }
+        }
+      }
+      var target = {
+        value: 'v'
+      }
+      var res = _.resolveFilters(vm, filters, target)
+      expect(res.read.length).toBe(2)
+      expect(res.write.length).toBe(1)
+
+      var readA = res.read[0](1)
+      expect(readA.id).toBe('a')
+      expect(readA.value).toBe(1)
+      expect(readA.arg).toBe('a')
+
+      var readB = res.read[1](2)
+      expect(readB.id).toBe('b')
+      expect(readB.value).toBe(2)
+      expect(readB.arg).toBe('b')
+      
+      var writeB = res.write[0](3)
+      expect(writeB.id).toBe('bw')
+      expect(writeB.value).toBe(3)
+      expect(writeB.arg).toBe('b')
+
+      expect(debug.warn).toHaveBeenCalled()
+    })
+
+    it('applyFilters', function () {
+      var filters = [
+        function (v) {
+          return v + 2
+        },
+        function (v) {
+          return v + 3
+        }
+      ]
+      var res = _.applyFilters(1, filters)
+      expect(res).toBe(6)
+    })
+
+  })
+
   if (_.inBrowser) {
 
     describe('DOM', function () {