Explorar o código

filterBy & orderBy first pass

Evan You %!s(int64=12) %!d(string=hai) anos
pai
achega
336d06de1d

+ 1 - 0
.jshintrc

@@ -10,6 +10,7 @@
     "node": true,
     "laxbreak": true,
     "evil": true,
+    "eqnull": true,
     "globals": {
         "console": true
     }

+ 1 - 4
src/directive.js

@@ -12,7 +12,7 @@ var utils      = require('./utils'),
 
     ARG_RE          = /^([\w-$ ]+):(.+)$/,
     FILTERS_RE      = /\|[^\|]+/g,
-    FILTER_TOKEN_RE = /[^\s']+|'[^']+'/g,
+    FILTER_TOKEN_RE = /[^\s']+|'[^']+'|[^\s"]+|"[^"]+"/g,
     NESTING_RE      = /^\$(parent|root)\./,
     SINGLE_VAR_RE   = /^[\w\.$]+$/
 
@@ -110,9 +110,6 @@ function parseFilter (filter, compiler) {
 
     var tokens = filter.slice(1).match(FILTER_TOKEN_RE)
     if (!tokens) return
-    tokens = tokens.map(function (token) {
-        return token.replace(/'/g, '').trim()
-    })
 
     var name = tokens[0],
         apply = compiler.getOption('filters', name)

+ 20 - 9
src/exp-parser.js

@@ -1,8 +1,9 @@
 var utils           = require('./utils'),
-    stringSaveRE    = /"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/g,
-    stringRestoreRE = /"(\d+)"/g,
-    constructorRE   = new RegExp('constructor'.split('').join('[\'"+, ]*')),
-    unicodeRE       = /\\u\d\d\d\d/
+    STR_SAVE_RE     = /"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/g,
+    STR_RESTORE_RE  = /"(\d+)"/g,
+    CTOR_RE         = new RegExp('constructor'.split('').join('[\'"+, ]*')),
+    UNICODE_RE      = /\\u\d\d\d\d/,
+    QUOTE_RE        = /"/g
 
 // Variable extraction scooped from https://github.com/RubyLouvre/avalon
 
@@ -94,7 +95,7 @@ function makeGetter (exp, raw) {
     try {
         fn = new Function(exp)
     } catch (e) {
-        utils.warn('Invalid expression: ' + raw)
+        utils.warn('Error parsing expression: ' + raw)
     }
     return fn
 }
@@ -108,6 +109,16 @@ function escapeDollar (v) {
         : v
 }
 
+/**
+ *  Convert double quotes to single quotes
+ *  so they don't mess up the generated function body
+ */
+function escapeQuote (v) {
+    return v.indexOf('"') > -1
+        ? v.replace(QUOTE_RE, '\'')
+        : v
+}
+
 /**
  *  Parse and return an anonymous computed property getter function
  *  from an arbitrary expression, together with a list of paths to be
@@ -115,7 +126,7 @@ function escapeDollar (v) {
  */
 exports.parse = function (exp, compiler, data, filters) {
     // unicode and 'constructor' are not allowed for XSS security.
-    if (unicodeRE.test(exp) || constructorRE.test(exp)) {
+    if (UNICODE_RE.test(exp) || CTOR_RE.test(exp)) {
         utils.warn('Unsafe expression: ' + exp)
         return
     }
@@ -138,15 +149,15 @@ exports.parse = function (exp, compiler, data, filters) {
             ")[$\\w\\.]*\\b", 'g'
         ),
         body = (' ' + exp)
-            .replace(stringSaveRE, saveStrings)
+            .replace(STR_SAVE_RE, saveStrings)
             .replace(pathRE, replacePath)
-            .replace(stringRestoreRE, restoreStrings)
+            .replace(STR_RESTORE_RE, restoreStrings)
 
     // wrap expression with computed filters
     if (filters) {
         filters.forEach(function (filter) {
             var args = filter.args
-                ? ',"' + filter.args.join('","') + '"'
+                ? ',"' + filter.args.map(escapeQuote).join('","') + '"'
                 : ''
             body =
                 'this.$compiler.getOption("filters", "' +

+ 91 - 12
src/filters.js

@@ -1,16 +1,46 @@
+var utils    = require('./utils'),
+    get      = utils.get,
+    slice    = [].slice,
+    QUOTE_RE = /^'.*'$/
+
 var keyCodes = {
-        enter    : 13,
-        tab      : 9,
-        'delete' : 46,
-        up       : 38,
-        left     : 37,
-        right    : 39,
-        down     : 40,
-        esc      : 27
-    },
-    slice = [].slice
+    enter    : 13,
+    tab      : 9,
+    'delete' : 46,
+    up       : 38,
+    left     : 37,
+    right    : 39,
+    down     : 40,
+    esc      : 27
+}
+
+/**
+ *  String contain helper
+ */
+function contains (val, search) {
+    /* jshint eqeqeq: false */
+    if (utils.typeOf(val) === 'Object') {
+        for (var key in val) {
+            if (contains(val[key], search)) {
+                return true
+            }
+        }
+    } else if (val != null) {
+        return val.toString().toLowerCase().indexOf(search) > -1
+    }
+}
 
-module.exports = {
+/**
+ *  Test whether a string is in quotes,
+ *  if yes return stripped string
+ */
+function stripQuotes (str) {
+    if (QUOTE_RE.test(str)) {
+        return str.slice(1, -1)
+    }
+}
+
+var filters = module.exports = {
 
     /**
      *  'abc' => 'Abc'
@@ -83,5 +113,54 @@ module.exports = {
                 handler.call(this, e)
             }
         }
+    },
+
+    filterBy: function (arr, searchKey, delimiter, dataKey) {
+
+        // get the search string
+        var search = stripQuotes(searchKey) || get(this, searchKey)
+        if (!search) return arr
+        search = search.toLowerCase()
+
+        // get the optional dataKey
+        dataKey = dataKey && (stripQuotes(dataKey) || get(this, dataKey))
+
+        return arr.filter(function (item) {
+            return dataKey
+                ? contains(get(item, dataKey), search)
+                : contains(item, search)
+        })
+
+    },
+
+    orderBy: function (arr, sortKey, reverseKey) {
+
+        var key = stripQuotes(sortKey) || get(this, sortKey)
+        if (!key) return arr
+
+        var order = 1
+        if (reverseKey) {
+            if (reverseKey === '-1') {
+                order = -1
+            } else if (reverseKey.charAt(0) === '!') {
+                reverseKey = reverseKey.slice(1)
+                order = get(this, reverseKey) ? 1 : -1
+            } else {
+                order = get(this, reverseKey) ? -1 : 1
+            }
+        }
+
+        // sort on a copy to avoid mutating original array
+        return arr.slice().sort(function (a, b) {
+            a = a[key]
+            b = b[key]
+            return a === b ? 0 : a > b ? order : -order
+        })
+
     }
-}
+
+}
+
+// mark computed filters
+filters.filterBy.computed = true
+filters.orderBy.computed = true

+ 7 - 0
src/utils.js

@@ -13,6 +13,9 @@ var utils = module.exports = {
      *  get a value from an object keypath
      */
     get: function (obj, key) {
+        if (key.indexOf('.') < 0) {
+            return obj[key]
+        }
         var path = key.split('.'),
             d = -1, l = path.length
         while (++d < l && obj !== undefined) {
@@ -25,6 +28,10 @@ var utils = module.exports = {
      *  set a value to an object keypath
      */
     set: function (obj, key, val) {
+        if (key.indexOf('.') < 0) {
+            obj[key] = val
+            return
+        }
         var path = key.split('.'),
             d = -1, l = path.length - 1
         while (++d < l) {

+ 63 - 0
test/functional/fixtures/array-filters.html

@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<script src="../../../dist/vue.js"></script>
+
+<div id="test">
+    Sort by
+    <select v-model="sortKey">
+        <option>name</option>
+        <option>phone</option>
+    </select>
+    <br>
+    <input type="checkbox" v-model="reverse"> Reverse
+    <hr></hr>
+    Filter by <input v-model="searchText"> in name only
+    <table id="t1">
+        <tr><th>Name</th><th>Phone</th></tr>
+        <tr v-repeat="friends | filterBy searchText in 'name' | orderBy sortKey reverse">
+            <td>{{name}}</td>
+            <td>{{phone}}</td>
+        </tr>
+    </table>
+    <hr></hr>
+    Filter by input in all fields and reversed
+    <table id="t2">
+        <tr><th>Name</th><th>Phone</th></tr>
+        <tr v-repeat="friends | filterBy searchText | orderBy sortKey !reverse">
+            <td>{{name}}</td>
+            <td>{{phone}}</td>
+        </tr>
+    </table>
+    <hr></hr>
+    Filter by "Julie" in
+    <select v-model="filterKey">
+        <option>name</option>
+        <option>phone</option>
+    </select>
+    <table id="t3">
+        <tr><th>Name</th><th>Phone</th></tr>
+        <tr v-repeat="friends | filterBy 'Julie' in filterKey | orderBy sortKey -1">
+            <td>{{name}}</td>
+            <td>{{phone}}</td>
+        </tr>
+    </table>
+</div>
+
+<script>
+new Vue({
+    el: '#test',
+    data: {
+        searchText: '',
+        filterKey: 'name',
+        sortKey: 'name',
+        reverse: false,
+        friends: [
+            {name:'John', phone:'555-1276', hidden: { id: 'hidden!' } },
+            {name:'Mary', phone:'800-BIG-MARY'},
+            {name:'Mike', phone:'555-4321'},
+            {name:'Adam', phone:'555-5678'},
+            {name:'Julie', phone:'555-8765'},
+            {name:'Juliette', phone:'555-5678'}
+        ]
+    }
+})
+</script>

+ 1 - 1
test/unit/specs/directive.js

@@ -210,7 +210,7 @@ describe('Directive', function () {
             assert.strictEqual(f.name, 'pluralize', 'name')
             assert.strictEqual(f.args.length, 2, 'args length')
             assert.strictEqual(f.args[0], 'item', 'args value 1')
-            assert.strictEqual(f.args[1], 'arg with spaces', 'args value 2')
+            assert.strictEqual(f.args[1], '\'arg with spaces\'', 'args value 2')
         })
 
         it('should extract correct filters (multiple filters)', function () {

+ 24 - 0
test/unit/specs/filters.js

@@ -128,6 +128,30 @@ describe('Filters', function () {
 
     })
 
+    describe('filterBy', function () {
+        
+        var filter = filters.filterBy
+
+        it('should be computed', function () {
+            assert.ok(filter.computed)
+        })
+
+        // TODO
+
+    })
+
+    describe('orderBy', function () {
+
+        var filter = filters.orderBy
+        
+        it('should be computed', function () {
+            assert.ok(filter.computed)
+        })
+
+        // TODO
+
+    })
+
 })
 
 function assertNumberAndFalsy (filter) {