Ver Fonte

Filter syntax change
- quoted arguments are treated as plain strings
- non-quoted arguments are treated as dynamic paths

Evan You há 11 anos atrás
pai
commit
5dfa2f0ccd

+ 3 - 3
examples/todomvc/index.html

@@ -16,7 +16,7 @@
                     autocomplete="off"
                     placeholder="What needs to be done?"
                     v-model="newTodo"
-                    v-on="keyup:addTodo | key enter"
+                    v-on="keyup:addTodo | key 'enter'"
                 >
             </header>
             <section id="main" v-show="todos.length" v-cloak>
@@ -50,8 +50,8 @@
                             v-todo-focus="this == editedTodo"
                             v-on="
                                 blur  : doneEdit(this),
-                                keyup : doneEdit(this) | key enter,
-                                keyup : cancelEdit(this) | key esc
+                                keyup : doneEdit(this) | key 'enter',
+                                keyup : cancelEdit(this) | key 'esc'
                             "
                         >
                     </li>

+ 11 - 24
src/filters/array-filters.js

@@ -9,24 +9,17 @@ var Path = require('../parsers/path')
  * @param {String} dataKey
  */
 
-exports.filterBy = function (arr, searchKey, delimiter, dataKey) {
+exports.filterBy = function (arr, search, delimiter, dataKey) {
   // allow optional `in` delimiter
   // because why not
   if (delimiter && delimiter !== 'in') {
     dataKey = delimiter
   }
-  // get the search string
-  var search =
-    _.stripQuotes(searchKey) ||
-    this.$get(searchKey)
   if (!search) {
     return arr
   }
+  // cast to lowercase string
   search = ('' + search).toLowerCase()
-  // get the optional dataKey
-  dataKey =
-    dataKey &&
-    (_.stripQuotes(dataKey) || this.$get(dataKey))
   return arr.filter(function (item) {
     return dataKey
       ? contains(Path.get(item, dataKey), search)
@@ -38,35 +31,29 @@ exports.filterBy = function (arr, searchKey, delimiter, dataKey) {
  * Filter filter for v-repeat
  *
  * @param {String} sortKey
- * @param {String} reverseKey
+ * @param {String} reverse
  */
 
-exports.orderBy = function (arr, sortKey, reverseKey) {
-  var key =
-    _.stripQuotes(sortKey) ||
-    this.$get(sortKey)
-  if (!key) {
+exports.orderBy = function (arr, sortKey, reverse) {
+  if (!sortKey) {
     return arr
   }
   var order = 1
-  if (reverseKey) {
-    if (reverseKey === '-1') {
+  if (arguments.length > 2) {
+    if (reverse === '-1') {
       order = -1
-    } else if (reverseKey.charCodeAt(0) === 0x21) { // !
-      reverseKey = reverseKey.slice(1)
-      order = this.$get(reverseKey) ? 1 : -1
     } else {
-      order = this.$get(reverseKey) ? -1 : 1
+      order = reverse ? -1 : 1
     }
   }
   // sort on a copy to avoid mutating original array
   return arr.slice().sort(function (a, b) {
-    if (key !== '$key' && key !== '$value') {
+    if (sortKey !== '$key' && sortKey !== '$value') {
       if (a && '$value' in a) a = a.$value
       if (b && '$value' in b) b = b.$value
     }
-    a = _.isObject(a) ? Path.get(a, key) : a
-    b = _.isObject(b) ? Path.get(b, key) : b
+    a = _.isObject(a) ? Path.get(a, sortKey) : a
+    b = _.isObject(b) ? Path.get(b, sortKey) : b
     return a === b ? 0 : a > b ? order : -order
   })
 }

+ 12 - 1
src/parsers/directive.js

@@ -3,6 +3,7 @@ var Cache = require('../cache')
 var cache = new Cache(1000)
 var argRE = /^[^\{\?]+$|^'[^']*'$|^"[^"]*"$/
 var filterTokenRE = /[^\s'"]+|'[^']+'|"[^"]+"/g
+var reservedArgRE = /^in$|^-?\d+/
 
 /**
  * Parser state
@@ -49,7 +50,17 @@ function pushFilter () {
     filter = {}
     var tokens = exp.match(filterTokenRE)
     filter.name = tokens[0]
-    filter.args = tokens.length > 1 ? tokens.slice(1) : null
+    if (tokens.length > 1) {
+      filter.args = tokens.slice(1).map(function (arg) {
+        var stripped = reservedArgRE.test(arg)
+          ? arg
+          : _.stripQuotes(arg)
+        return {
+          value: stripped || arg,
+          dynamic: !stripped
+        }
+      })
+    }
   }
   if (filter) {
     (dir.filters = dir.filters || []).push(filter)

+ 1 - 1
src/parsers/text.js

@@ -166,7 +166,7 @@ function inlineFilters (exp, single) {
       for (var i = 0, l = dir.filters.length; i < l; i++) {
         var filter = dir.filters[i]
         var args = filter.args
-          ? ',"' + filter.args.join('","') + '"'
+          ? ',' + JSON.stringify(filter.args).slice(1, -1)
           : ''
         exp = 'this._applyFilter("' + filter.name + '",[' + exp + args + '])'
       }

+ 18 - 2
src/util/misc.js

@@ -81,7 +81,7 @@ exports.resolveFilters = function (vm, filters, target) {
       if (!res.read) res.read = []
       res.read.push(function (value) {
         return args
-          ? reader.apply(vm, [value].concat(args))
+          ? reader.apply(vm, [value].concat(resolveArgs(vm, args)))
           : reader.call(vm, value)
       })
     }
@@ -89,7 +89,7 @@ exports.resolveFilters = function (vm, filters, target) {
       if (!res.write) res.write = []
       res.write.push(function (value, oldVal) {
         return args
-          ? writer.apply(vm, [value, oldVal].concat(args))
+          ? writer.apply(vm, [value, oldVal].concat(resolveArgs(vm, args)))
           : writer.call(vm, value, oldVal)
       })
     }
@@ -97,6 +97,22 @@ exports.resolveFilters = function (vm, filters, target) {
   return res
 }
 
+/**
+ * Resolve arguments into static values
+ *
+ * @param {Vue} vm
+ * @param {Array} args
+ * @return {Array}
+ */
+
+function resolveArgs (vm, args) {
+  return args.map(function (arg) {
+    return arg.dynamic
+      ? vm.$get(arg.value)
+      : arg.value
+  })
+}
+
 /**
  * Apply filters to a value
  *

+ 1 - 1
test/unit/specs/directives/on_spec.js

@@ -53,7 +53,7 @@ if (_.inBrowser) {
     it('with key filter', function (done) {
       var vm = new Vue({
         el: el,
-        template: '<a v-on="keyup:test | key enter">{{a}}</a>',
+        template: '<a v-on="keyup:test | key \'enter\'">{{a}}</a>',
         data: {a:1},
         methods: {
           test: function () {

+ 14 - 36
test/unit/specs/filters/filters_spec.js

@@ -102,41 +102,28 @@ describe('Filters', function () {
       { a: 2, b: 'hello'},
       { a: 3, b: ['yoyo'] }
     ]
-    var vm = new Vue({
-      data: {
-        search: {
-          key: 'hello',
-          datakey: 'b.c',
-          n: 2
-        }
-      }
-    })
     var res
     // normal
-    res = filter.call(vm, arr, 'search.key')
+    res = filter(arr, 'hello')
     expect(res.length).toBe(2)
     expect(res[0]).toBe(arr[0])
     expect(res[1]).toBe(arr[1])
     // data key
-    res = filter.call(vm, arr, 'search.key', 'search.datakey')
-    expect(res.length).toBe(1)
-    expect(res[0]).toBe(arr[0])
-    // quotes
-    res = filter.call(vm, arr, "'hello'", "'b.c'")
+    res = filter(arr, 'hello', 'b.c')
     expect(res.length).toBe(1)
     expect(res[0]).toBe(arr[0])
     // delimiter
-    res = filter.call(vm, arr, 'search.key', 'in', 'search.datakey')
+    res = filter(arr, 'hello', 'in', 'b.c')
     expect(res.length).toBe(1)
     expect(res[0]).toBe(arr[0])
     // no search key
-    res = filter.call(vm, arr, 'abc')
+    res = filter(arr, null)
     expect(res).toBe(arr)
     // number search key
-    res = filter.call(vm, arr, 'search.n')
+    res = filter(arr, 2)
     expect(res[0]).toBe(arr[1])
     // search in sub array
-    res = filter.call(vm, arr, "'yoyo'")
+    res = filter(arr, 'yoyo')
     expect(res.length).toBe(1)
     expect(res[0]).toBe(arr[2])
   })
@@ -150,56 +137,47 @@ describe('Filters', function () {
     ]
     var res
     // sort key
-    res = filter.call(new Vue({
-      data: {
-        sortby: 'a.b',
-      }
-    }), arr, 'sortby')
+    res = filter(arr, 'a.b')
     expect(res.length).toBe(3)
     expect(res[0].a.b).toBe(0)
     expect(res[1].a.b).toBe(1)
     expect(res[2].a.b).toBe(2)
     // reverse key
-    res = filter.call(new Vue({
-      data: { sortby: 'a.b', reverse: true }
-    }), arr, 'sortby', 'reverse')
+    res = filter(arr, 'a.b', true)
     expect(res.length).toBe(3)
     expect(res[0].a.b).toBe(2)
     expect(res[1].a.b).toBe(1)
     expect(res[2].a.b).toBe(0)
     // literal args
-    res = filter.call(new Vue(), arr, "'c'", '-1')
+    res = filter(arr, 'c', '-1')
     expect(res.length).toBe(3)
     expect(res[0].c).toBe('c')
     expect(res[1].c).toBe('b')
     expect(res[2].c).toBe('a')
     // negate reverse
-    res = filter.call(new Vue({
-      data: { reverse: true }
-    }), arr, "'c'", '!reverse')
+    res = filter(arr, 'c', false)
     expect(res.length).toBe(3)
     expect(res[0].c).toBe('a')
     expect(res[1].c).toBe('b')
     expect(res[2].c).toBe('c')
     // no sort key
-    res = filter.call(new Vue(), arr, 'abc')
+    res = filter(arr, null)
     expect(res).toBe(arr)
   })
 
   it('orderBy on Object-converted array', function () {
     // object converted
     var filter = filters.orderBy
-    var vm = new Vue()
     var arr = [
       { $key: 'a', $value: 3 },
       { $key: 'c', $value: 1 },
       { $key: 'b', $value: 2 }
     ]
-    var res = filter.call(vm, arr, '"$key"')
+    var res = filter(arr, '$key')
     expect(res[0].$value).toBe(3)
     expect(res[1].$value).toBe(2)
     expect(res[2].$value).toBe(1)
-    res = filter.call(vm, arr, '"$value"')
+    res = filter(arr, '$value')
     expect(res[0].$value).toBe(1)
     expect(res[1].$value).toBe(2)
     expect(res[2].$value).toBe(3)
@@ -209,7 +187,7 @@ describe('Filters', function () {
       { $key: 'c', $value: { v: 1 } },
       { $key: 'b', $value: { v: 2 } }
     ]
-    res = filter.call(vm, arr, '"v"')
+    res = filter(arr, 'v')
     expect(res[0].$value.v).toBe(1)
     expect(res[1].$value.v).toBe(2)
     expect(res[2].$value.v).toBe(3)

+ 18 - 12
test/unit/specs/parsers/directive_spec.js

@@ -18,17 +18,20 @@ describe('Directive Parser', function () {
   })
 
   it('with filters', function () {
-    var res = parse(' arg : exp | abc de | bcd')
+    var res = parse(' arg : exp | abc de \'ok\' | bcd')
     expect(res.length).toBe(1)
     expect(res[0].expression).toBe('exp')
     expect(res[0].arg).toBe('arg')
-    expect(res[0].raw).toBe('arg : exp | abc de | bcd')
+    expect(res[0].raw).toBe('arg : exp | abc de \'ok\' | bcd')
     expect(res[0].filters.length).toBe(2)
     expect(res[0].filters[0].name).toBe('abc')
-    expect(res[0].filters[0].args.length).toBe(1)
-    expect(res[0].filters[0].args[0]).toBe('de')
+    expect(res[0].filters[0].args.length).toBe(2)
+    expect(res[0].filters[0].args[0].value).toBe('de')
+    expect(res[0].filters[0].args[0].dynamic).toBe(true)
+    expect(res[0].filters[0].args[1].value).toBe('ok')
+    expect(res[0].filters[0].args[1].dynamic).toBe(false)
     expect(res[0].filters[1].name).toBe('bcd')
-    expect(res[0].filters[1].args).toBeNull()
+    expect(res[0].filters[1].args).toBeUndefined()
   })
 
   it('double pipe', function () {
@@ -38,7 +41,7 @@ describe('Directive Parser', function () {
     expect(res[0].raw).toBe('a || b | c')
     expect(res[0].filters.length).toBe(1)
     expect(res[0].filters[0].name).toBe('c')
-    expect(res[0].filters[0].args).toBeNull()
+    expect(res[0].filters[0].args).toBeUndefined()
   })
 
   it('single quote + boolean', function () {
@@ -65,31 +68,34 @@ describe('Directive Parser', function () {
   })
 
   it('multiple complex clauses', function () {
-    var res = parse('a:b | c | j, d:e | f | k l, g:h | i')
+    var res = parse('a:b | c | j, d:e | f | k l, g:h | i "k"')
     expect(res.length).toBe(3)
 
     expect(res[0].arg).toBe('a')
     expect(res[0].expression).toBe('b')
     expect(res[0].filters.length).toBe(2)
     expect(res[0].filters[0].name).toBe('c')
-    expect(res[0].filters[0].args).toBeNull()
+    expect(res[0].filters[0].args).toBeUndefined()
     expect(res[0].filters[1].name).toBe('j')
-    expect(res[0].filters[1].args).toBeNull()
+    expect(res[0].filters[1].args).toBeUndefined()
 
     expect(res[1].arg).toBe('d')
     expect(res[1].expression).toBe('e')
     expect(res[1].filters.length).toBe(2)
     expect(res[1].filters[0].name).toBe('f')
-    expect(res[1].filters[0].args).toBeNull()
+    expect(res[1].filters[0].args).toBeUndefined()
     expect(res[1].filters[1].name).toBe('k')
     expect(res[1].filters[1].args.length).toBe(1)
-    expect(res[1].filters[1].args[0]).toBe('l')
+    expect(res[1].filters[1].args[0].value).toBe('l')
+    expect(res[1].filters[1].args[0].dynamic).toBe(true)
 
     expect(res[2].arg).toBe('g')
     expect(res[2].expression).toBe('h')
     expect(res[2].filters.length).toBe(1)
     expect(res[2].filters[0].name).toBe('i')
-    expect(res[2].filters[0].args).toBeNull()
+    expect(res[2].filters[0].args.length).toBe(1)
+    expect(res[2].filters[0].args[0].value).toBe('k')
+    expect(res[2].filters[0].args[0].dynamic).toBe(false)
   })
 
   it('nexted function calls + array/object literals', function () {

+ 4 - 1
test/unit/specs/parsers/text_spec.js

@@ -112,7 +112,10 @@ describe('Text Parser', function () {
   it('tokens to expression with filters, multiple expressions', function () {
     var tokens = textParser.parse('a {{b | c d | f}} e')
     var exp = textParser.tokensToExp(tokens)
-    expect(exp).toBe('"a "+this._applyFilter("f",[this._applyFilter("c",[b,"d"])])+" e"')
+    expect(exp).toBe(
+      '"a "+this._applyFilter("f",[this._applyFilter("c",[b,' +
+        JSON.stringify({value:'d',dynamic:true}) +
+      '])])+" e"')
   })
 
 })

+ 35 - 7
test/unit/specs/util/filter_spec.js → test/unit/specs/util/misc_spec.js

@@ -1,17 +1,45 @@
 var _ = require('../../../../src/util')
 
-describe('Util - Filter', function () {
-  
+describe('Util - Misc', function () {
+
+  it('checkComponent', function () {
+    var el = document.createElement('component')
+    // <component> with no is attr
+    var res = _.checkComponent(el)
+    expect(res).toBe(null)
+    // <component is="...">
+    el.setAttribute('is', '{{what}}')
+    res = _.checkComponent(el)
+    expect(res).toBe('{{what}}')
+    // custom element, not defined
+    el = document.createElement('test')
+    res = _.checkComponent(el, {
+      components: {}
+    })
+    expect(res).toBeUndefined()
+    // custom element, defined
+    res = _.checkComponent(el, {
+      components: { test: true }
+    })
+    expect(res).toBe('test')
+  })
+
   it('resolveFilters', function () {
     var filters = [
-      { name: 'a', args: ['a'] },
-      { name: 'b', args: ['b']},
+      { name: 'a', args: [{ value: 'a', dynamic: false }] },
+      { name: 'b', args: [{ value: 'b', dynamic: true }]},
       { name: 'c' }
     ]
     var vm = {
       _asset: function (type, id) {
         return this.$options[type][id]
       },
+      $get: function (key) {
+        var data = {
+          b: 'BB'
+        }
+        return data[key]
+      },
       $options: {
         filters: {
           a: function (v, arg) {
@@ -40,15 +68,15 @@ describe('Util - Filter', function () {
     expect(readA.value).toBe(1)
     expect(readA.arg).toBe('a')
 
-    var readB = res.read[1](2)
+    var readB = res.read[1].call(vm, 2)
     expect(readB.id).toBe('b')
     expect(readB.value).toBe(2)
-    expect(readB.arg).toBe('b')
+    expect(readB.arg).toBe('BB')
     
     var writeB = res.write[0](3)
     expect(writeB.id).toBe('bw')
     expect(writeB.value).toBe(3)
-    expect(writeB.arg).toBe('b')
+    expect(writeB.arg).toBe('BB')
   })
 
   it('applyFilters', function () {

+ 5 - 4
test/unit/specs/watcher_spec.js

@@ -17,7 +17,8 @@ describe('Watcher', function () {
           c: 2,
           d: 4
         },
-        c: 'c'
+        c: 'c',
+        msg: 'yo'
       }
     })
     spy = jasmine.createSpy('watcher')
@@ -236,8 +237,8 @@ describe('Watcher', function () {
       return val + str
     }
     var filters = _.resolveFilters(vm, [
-      { name: 'test', args: [3] },
-      { name: 'test2', args: ['yo']}
+      { name: 'test', args: [{value:3, dynamic:false}] },
+      { name: 'test2', args: [{value:'msg', dynamic:true}]}
     ])
     var watcher = new Watcher(vm, 'b.c', spy, {
       filters: filters
@@ -258,7 +259,7 @@ describe('Watcher', function () {
       }
     }
     var filters = _.resolveFilters(vm, [
-      { name: 'test', args: [5] }
+      { name: 'test', args: [{value:5, dynamic:false}] }
     ])
     var watcher = new Watcher(vm, 'b["c"]', spy, {
       filters: filters,