Skip to content

Commit 336d06d

Browse files
Evan Youyyx990803
authored andcommitted
filterBy & orderBy first pass
1 parent e11f5fd commit 336d06d

File tree

8 files changed

+208
-26
lines changed

8 files changed

+208
-26
lines changed

.jshintrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"node": true,
1111
"laxbreak": true,
1212
"evil": true,
13+
"eqnull": true,
1314
"globals": {
1415
"console": true
1516
}

src/directive.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ var utils = require('./utils'),
1212

1313
ARG_RE = /^([\w-$ ]+):(.+)$/,
1414
FILTERS_RE = /\|[^\|]+/g,
15-
FILTER_TOKEN_RE = /[^\s']+|'[^']+'/g,
15+
FILTER_TOKEN_RE = /[^\s']+|'[^']+'|[^\s"]+|"[^"]+"/g,
1616
NESTING_RE = /^\$(parent|root)\./,
1717
SINGLE_VAR_RE = /^[\w\.$]+$/
1818

@@ -110,9 +110,6 @@ function parseFilter (filter, compiler) {
110110

111111
var tokens = filter.slice(1).match(FILTER_TOKEN_RE)
112112
if (!tokens) return
113-
tokens = tokens.map(function (token) {
114-
return token.replace(/'/g, '').trim()
115-
})
116113

117114
var name = tokens[0],
118115
apply = compiler.getOption('filters', name)

src/exp-parser.js

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
var utils = require('./utils'),
2-
stringSaveRE = /"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/g,
3-
stringRestoreRE = /"(\d+)"/g,
4-
constructorRE = new RegExp('constructor'.split('').join('[\'"+, ]*')),
5-
unicodeRE = /\\u\d\d\d\d/
2+
STR_SAVE_RE = /"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/g,
3+
STR_RESTORE_RE = /"(\d+)"/g,
4+
CTOR_RE = new RegExp('constructor'.split('').join('[\'"+, ]*')),
5+
UNICODE_RE = /\\u\d\d\d\d/,
6+
QUOTE_RE = /"/g
67

78
// Variable extraction scooped from https://github.com/RubyLouvre/avalon
89

@@ -94,7 +95,7 @@ function makeGetter (exp, raw) {
9495
try {
9596
fn = new Function(exp)
9697
} catch (e) {
97-
utils.warn('Invalid expression: ' + raw)
98+
utils.warn('Error parsing expression: ' + raw)
9899
}
99100
return fn
100101
}
@@ -108,14 +109,24 @@ function escapeDollar (v) {
108109
: v
109110
}
110111

112+
/**
113+
* Convert double quotes to single quotes
114+
* so they don't mess up the generated function body
115+
*/
116+
function escapeQuote (v) {
117+
return v.indexOf('"') > -1
118+
? v.replace(QUOTE_RE, '\'')
119+
: v
120+
}
121+
111122
/**
112123
* Parse and return an anonymous computed property getter function
113124
* from an arbitrary expression, together with a list of paths to be
114125
* created as bindings.
115126
*/
116127
exports.parse = function (exp, compiler, data, filters) {
117128
// unicode and 'constructor' are not allowed for XSS security.
118-
if (unicodeRE.test(exp) || constructorRE.test(exp)) {
129+
if (UNICODE_RE.test(exp) || CTOR_RE.test(exp)) {
119130
utils.warn('Unsafe expression: ' + exp)
120131
return
121132
}
@@ -138,15 +149,15 @@ exports.parse = function (exp, compiler, data, filters) {
138149
")[$\\w\\.]*\\b", 'g'
139150
),
140151
body = (' ' + exp)
141-
.replace(stringSaveRE, saveStrings)
152+
.replace(STR_SAVE_RE, saveStrings)
142153
.replace(pathRE, replacePath)
143-
.replace(stringRestoreRE, restoreStrings)
154+
.replace(STR_RESTORE_RE, restoreStrings)
144155

145156
// wrap expression with computed filters
146157
if (filters) {
147158
filters.forEach(function (filter) {
148159
var args = filter.args
149-
? ',"' + filter.args.join('","') + '"'
160+
? ',"' + filter.args.map(escapeQuote).join('","') + '"'
150161
: ''
151162
body =
152163
'this.$compiler.getOption("filters", "' +

src/filters.js

Lines changed: 91 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,46 @@
1+
var utils = require('./utils'),
2+
get = utils.get,
3+
slice = [].slice,
4+
QUOTE_RE = /^'.*'$/
5+
16
var keyCodes = {
2-
enter : 13,
3-
tab : 9,
4-
'delete' : 46,
5-
up : 38,
6-
left : 37,
7-
right : 39,
8-
down : 40,
9-
esc : 27
10-
},
11-
slice = [].slice
7+
enter : 13,
8+
tab : 9,
9+
'delete' : 46,
10+
up : 38,
11+
left : 37,
12+
right : 39,
13+
down : 40,
14+
esc : 27
15+
}
16+
17+
/**
18+
* String contain helper
19+
*/
20+
function contains (val, search) {
21+
/* jshint eqeqeq: false */
22+
if (utils.typeOf(val) === 'Object') {
23+
for (var key in val) {
24+
if (contains(val[key], search)) {
25+
return true
26+
}
27+
}
28+
} else if (val != null) {
29+
return val.toString().toLowerCase().indexOf(search) > -1
30+
}
31+
}
1232

13-
module.exports = {
33+
/**
34+
* Test whether a string is in quotes,
35+
* if yes return stripped string
36+
*/
37+
function stripQuotes (str) {
38+
if (QUOTE_RE.test(str)) {
39+
return str.slice(1, -1)
40+
}
41+
}
42+
43+
var filters = module.exports = {
1444

1545
/**
1646
* 'abc' => 'Abc'
@@ -83,5 +113,54 @@ module.exports = {
83113
handler.call(this, e)
84114
}
85115
}
116+
},
117+
118+
filterBy: function (arr, searchKey, delimiter, dataKey) {
119+
120+
// get the search string
121+
var search = stripQuotes(searchKey) || get(this, searchKey)
122+
if (!search) return arr
123+
search = search.toLowerCase()
124+
125+
// get the optional dataKey
126+
dataKey = dataKey && (stripQuotes(dataKey) || get(this, dataKey))
127+
128+
return arr.filter(function (item) {
129+
return dataKey
130+
? contains(get(item, dataKey), search)
131+
: contains(item, search)
132+
})
133+
134+
},
135+
136+
orderBy: function (arr, sortKey, reverseKey) {
137+
138+
var key = stripQuotes(sortKey) || get(this, sortKey)
139+
if (!key) return arr
140+
141+
var order = 1
142+
if (reverseKey) {
143+
if (reverseKey === '-1') {
144+
order = -1
145+
} else if (reverseKey.charAt(0) === '!') {
146+
reverseKey = reverseKey.slice(1)
147+
order = get(this, reverseKey) ? 1 : -1
148+
} else {
149+
order = get(this, reverseKey) ? -1 : 1
150+
}
151+
}
152+
153+
// sort on a copy to avoid mutating original array
154+
return arr.slice().sort(function (a, b) {
155+
a = a[key]
156+
b = b[key]
157+
return a === b ? 0 : a > b ? order : -order
158+
})
159+
86160
}
87-
}
161+
162+
}
163+
164+
// mark computed filters
165+
filters.filterBy.computed = true
166+
filters.orderBy.computed = true

src/utils.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ var utils = module.exports = {
1313
* get a value from an object keypath
1414
*/
1515
get: function (obj, key) {
16+
if (key.indexOf('.') < 0) {
17+
return obj[key]
18+
}
1619
var path = key.split('.'),
1720
d = -1, l = path.length
1821
while (++d < l && obj !== undefined) {
@@ -25,6 +28,10 @@ var utils = module.exports = {
2528
* set a value to an object keypath
2629
*/
2730
set: function (obj, key, val) {
31+
if (key.indexOf('.') < 0) {
32+
obj[key] = val
33+
return
34+
}
2835
var path = key.split('.'),
2936
d = -1, l = path.length - 1
3037
while (++d < l) {
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<!DOCTYPE html>
2+
<script src="../../../dist/vue.js"></script>
3+
4+
<div id="test">
5+
Sort by
6+
<select v-model="sortKey">
7+
<option>name</option>
8+
<option>phone</option>
9+
</select>
10+
<br>
11+
<input type="checkbox" v-model="reverse"> Reverse
12+
<hr></hr>
13+
Filter by <input v-model="searchText"> in name only
14+
<table id="t1">
15+
<tr><th>Name</th><th>Phone</th></tr>
16+
<tr v-repeat="friends | filterBy searchText in 'name' | orderBy sortKey reverse">
17+
<td>{{name}}</td>
18+
<td>{{phone}}</td>
19+
</tr>
20+
</table>
21+
<hr></hr>
22+
Filter by input in all fields and reversed
23+
<table id="t2">
24+
<tr><th>Name</th><th>Phone</th></tr>
25+
<tr v-repeat="friends | filterBy searchText | orderBy sortKey !reverse">
26+
<td>{{name}}</td>
27+
<td>{{phone}}</td>
28+
</tr>
29+
</table>
30+
<hr></hr>
31+
Filter by "Julie" in
32+
<select v-model="filterKey">
33+
<option>name</option>
34+
<option>phone</option>
35+
</select>
36+
<table id="t3">
37+
<tr><th>Name</th><th>Phone</th></tr>
38+
<tr v-repeat="friends | filterBy 'Julie' in filterKey | orderBy sortKey -1">
39+
<td>{{name}}</td>
40+
<td>{{phone}}</td>
41+
</tr>
42+
</table>
43+
</div>
44+
45+
<script>
46+
new Vue({
47+
el: '#test',
48+
data: {
49+
searchText: '',
50+
filterKey: 'name',
51+
sortKey: 'name',
52+
reverse: false,
53+
friends: [
54+
{name:'John', phone:'555-1276', hidden: { id: 'hidden!' } },
55+
{name:'Mary', phone:'800-BIG-MARY'},
56+
{name:'Mike', phone:'555-4321'},
57+
{name:'Adam', phone:'555-5678'},
58+
{name:'Julie', phone:'555-8765'},
59+
{name:'Juliette', phone:'555-5678'}
60+
]
61+
}
62+
})
63+
</script>

test/unit/specs/directive.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ describe('Directive', function () {
210210
assert.strictEqual(f.name, 'pluralize', 'name')
211211
assert.strictEqual(f.args.length, 2, 'args length')
212212
assert.strictEqual(f.args[0], 'item', 'args value 1')
213-
assert.strictEqual(f.args[1], 'arg with spaces', 'args value 2')
213+
assert.strictEqual(f.args[1], '\'arg with spaces\'', 'args value 2')
214214
})
215215

216216
it('should extract correct filters (multiple filters)', function () {

test/unit/specs/filters.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,30 @@ describe('Filters', function () {
128128

129129
})
130130

131+
describe('filterBy', function () {
132+
133+
var filter = filters.filterBy
134+
135+
it('should be computed', function () {
136+
assert.ok(filter.computed)
137+
})
138+
139+
// TODO
140+
141+
})
142+
143+
describe('orderBy', function () {
144+
145+
var filter = filters.orderBy
146+
147+
it('should be computed', function () {
148+
assert.ok(filter.computed)
149+
})
150+
151+
// TODO
152+
153+
})
154+
131155
})
132156

133157
function assertNumberAndFalsy (filter) {

0 commit comments

Comments
 (0)