Skip to content

Commit d04315e

Browse files
committed
refactor filter application
1 parent 1c02f8c commit d04315e

File tree

16 files changed

+191
-285
lines changed

16 files changed

+191
-285
lines changed

src/api/data.js

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
var _ = require('../util')
21
var Watcher = require('../watcher')
32
var Path = require('../parsers/path')
43
var textParser = require('../parsers/text')
@@ -99,13 +98,10 @@ exports.$eval = function (text) {
9998
// the filter regex check might give false positive
10099
// for pipes inside strings, so it's possible that
101100
// we don't get any filters here
101+
var val = this.$get(dir.expression)
102102
return dir.filters
103-
? _.applyFilters(
104-
this.$get(dir.expression),
105-
_.resolveFilters(this, dir.filters).read,
106-
this
107-
)
108-
: this.$get(dir.expression)
103+
? this._applyFilters(val, null, dir.filters)
104+
: val
109105
} else {
110106
// no filter
111107
return this.$get(text)

src/directive.js

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ function Directive (name, el, vm, descriptor, def, host) {
3131
this.raw = descriptor.raw
3232
this.expression = descriptor.expression
3333
this.arg = descriptor.arg
34-
this.filters = _.resolveFilters(vm, descriptor.filters)
34+
this.filters = descriptor.filters
3535
// private
3636
this._descriptor = descriptor
3737
this._host = host
@@ -78,14 +78,20 @@ p._bind = function (def) {
7878
}
7979
}
8080
: function () {} // noop if no update is provided
81+
// pre-process hook called before the value is piped
82+
// through the filters. used in v-repeat.
83+
var preProcess = this._preProcess
84+
? _.bind(this._preProcess, this)
85+
: null
8186
var watcher = this._watcher = new Watcher(
8287
this.vm,
8388
this._watcherExp,
8489
update, // callback
8590
{
8691
filters: this.filters,
8792
twoWay: this.twoWay,
88-
deep: this.deep
93+
deep: this.deep,
94+
preProcess: preProcess
8995
}
9096
)
9197
if (this._initValue != null) {
@@ -139,11 +145,7 @@ p._checkStatement = function () {
139145
fn.call(vm, vm)
140146
}
141147
if (this.filters) {
142-
handler = _.applyFilters(
143-
handler,
144-
this.filters.read,
145-
vm
146-
)
148+
handler = vm._applyFilters(handler, null, this.filters)
147149
}
148150
this.update(handler)
149151
return true

src/directives/model/index.js

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ module.exports = {
2727

2828
bind: function () {
2929
// friendly warning...
30-
var filters = this.filters
31-
if (filters && filters.read && !filters.write) {
30+
this.checkFilters()
31+
if (this.hasRead && !this.hasWrite) {
3232
_.warn(
3333
'It seems you are using a read-only filter with ' +
3434
'v-model. You might want to use a two-way filter ' +
@@ -51,6 +51,24 @@ module.exports = {
5151
handler.bind.call(this)
5252
this.update = handler.update
5353
this.unbind = handler.unbind
54+
},
55+
56+
/**
57+
* Check read/write filter stats.
58+
*/
59+
60+
checkFilters: function () {
61+
var filters = this.filters
62+
if (!filters) return
63+
var i = filters.length
64+
while (i--) {
65+
var filter = _.resolveAsset(this.vm.$options, 'filters', filters[i].name)
66+
if (typeof filter === 'function' || filter.read) {
67+
this.hasRead = true
68+
} else if (filter.write) {
69+
this.hasWrite = true
70+
}
71+
}
5472
}
5573

5674
}

src/directives/model/select.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ function initOptions (expression) {
8080
optionUpdateWatcher,
8181
{
8282
deep: true,
83-
filters: _.resolveFilters(this.vm, descriptor.filters)
83+
filters: descriptor.filters
8484
}
8585
)
8686
// update with initial value

src/directives/model/text.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ module.exports = {
4949
// the input with the filtered value.
5050
// also force update for type="range" inputs to enable
5151
// "lock in range" (see #506)
52-
if ((this.filters && this.filters.read) || el.type === 'range') {
52+
if (this.hasRead || el.type === 'range') {
5353
this.listener = function () {
5454
if (composing) return
5555
var charsOffset
@@ -156,5 +156,4 @@ module.exports = {
156156
_.off(el,'keyup', this.onDel)
157157
}
158158
}
159-
160159
}

src/directives/repeat.js

Lines changed: 47 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,6 @@ module.exports = {
2323
bind: function () {
2424
// uid as a cache identifier
2525
this.id = '__v_repeat_' + (++uid)
26-
// we need to insert the objToArray converter
27-
// as the first read filter, because it has to be invoked
28-
// before any user filters. (can't do it in `update`)
29-
if (!this.filters) {
30-
this.filters = {}
31-
}
32-
// add the object -> array convert filter
33-
var objectConverter = _.bind(objToArray, this)
34-
if (!this.filters.read) {
35-
this.filters.read = [objectConverter]
36-
} else {
37-
this.filters.read.unshift(objectConverter)
38-
}
3926
// setup anchor node
4027
this.anchor = _.createAnchor('v-repeat')
4128
_.replace(this.el, this.anchor)
@@ -219,13 +206,6 @@ module.exports = {
219206
*/
220207

221208
realUpdate: function (data) {
222-
data = data || []
223-
var type = typeof data
224-
if (type === 'number') {
225-
data = range(data)
226-
} else if (type === 'string') {
227-
data = _.toArray(data)
228-
}
229209
this.vms = this.diff(data, this.vms)
230210
// update v-ref
231211
if (this.refID) {
@@ -399,8 +379,11 @@ module.exports = {
399379
}
400380
// sync back changes for two-way bindings of primitive values
401381
var type = typeof raw
402-
if (type === 'string' || type === 'number') {
403-
var dir = this
382+
var dir = this
383+
if (
384+
this.rawType === 'object' &&
385+
(type === 'string' || type === 'number')
386+
) {
404387
vm.$watch(alias || '$value', function (val) {
405388
dir._withLock(function () {
406389
if (dir.converted) {
@@ -532,6 +515,48 @@ module.exports = {
532515
} else {
533516
this.cache[data].pop()
534517
}
518+
},
519+
520+
/**
521+
* Pre-process the value before piping it through the
522+
* filters, and convert non-Array objects to arrays.
523+
*
524+
* This function will be bound to this directive instance
525+
* and passed into the watcher.
526+
*
527+
* @param {*} value
528+
* @return {Array}
529+
* @private
530+
*/
531+
532+
_preProcess: function (value) {
533+
// regardless of type, store the un-filtered raw value.
534+
this.rawValue = value
535+
var type = this.rawType = typeof value
536+
if (!isPlainObject(value)) {
537+
this.converted = false
538+
if (type === 'number') {
539+
value = range(value)
540+
} else if (type === 'string') {
541+
value = _.toArray(value)
542+
}
543+
return value || []
544+
} else {
545+
// convert plain object to array.
546+
var keys = Object.keys(value)
547+
var i = keys.length
548+
var res = new Array(i)
549+
var key
550+
while (i--) {
551+
key = keys[i]
552+
res[i] = {
553+
$key: key,
554+
$value: value[key]
555+
}
556+
}
557+
this.converted = true
558+
return res
559+
}
535560
}
536561

537562
}
@@ -556,43 +581,6 @@ function findNextVm (vm, anchor) {
556581
return el.__vue__
557582
}
558583

559-
/**
560-
* Attempt to convert non-Array objects to array.
561-
* This is the default filter installed to every v-repeat
562-
* directive.
563-
*
564-
* It will be called with **the directive** as `this`
565-
* context so that we can mark the repeat array as converted
566-
* from an object.
567-
*
568-
* @param {*} obj
569-
* @return {Array}
570-
* @private
571-
*/
572-
573-
function objToArray (obj) {
574-
// regardless of type, store the un-filtered raw value.
575-
this.rawValue = obj
576-
if (!isPlainObject(obj)) {
577-
this.converted = false
578-
return obj
579-
}
580-
var keys = Object.keys(obj)
581-
var i = keys.length
582-
var res = new Array(i)
583-
var key
584-
while (i--) {
585-
key = keys[i]
586-
res[i] = {
587-
$key: key,
588-
$value: obj[key]
589-
}
590-
}
591-
// `this` points to the repeat directive instance
592-
this.converted = true
593-
return res
594-
}
595-
596584
/**
597585
* Create a range array from given number.
598586
*

src/instance/misc.js

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,40 @@
11
var _ = require('../util')
22

33
/**
4-
* Apply a filter to a list of arguments.
5-
* This is only used internally inside expressions with
6-
* inlined filters.
4+
* Apply a list of filter (descriptors) to a value.
5+
* Using plain for loops here because this will be called in
6+
* the getter of any watcher with filters so it is very
7+
* performance sensitive.
78
*
8-
* @param {String} id
9-
* @param {Array} args
9+
* @param {*} value
10+
* @param {*} [oldValue]
11+
* @param {Array} filters
12+
* @param {Boolean} write
1013
* @return {*}
1114
*/
1215

13-
exports._applyFilter = function (id, args) {
14-
var filter = _.resolveAsset(this.$options, 'filters', id)
15-
_.assertAsset(filter, 'filter', id)
16-
return (filter.read || filter).apply(this, args)
16+
exports._applyFilters = function (value, oldValue, filters, write) {
17+
var filter, fn, args, arg, offset, i, l, j, k
18+
for (i = 0, l = filters.length; i < l; i++) {
19+
filter = filters[i]
20+
fn = _.resolveAsset(this.$options, 'filters', filter.name)
21+
_.assertAsset(fn, 'filter', filter.name)
22+
if (!fn) continue
23+
fn = write ? fn.write : (fn.read || fn)
24+
if (typeof fn !== 'function') continue
25+
args = write ? [value, oldValue] : [value]
26+
offset = write ? 2 : 1
27+
if (filter.args) {
28+
for (j = 0, k = filter.args.length; j < k; j++) {
29+
arg = filter.args[j]
30+
args[j + offset] = arg.dynamic
31+
? this.$get(arg.value)
32+
: arg.value
33+
}
34+
}
35+
value = fn.apply(this, args)
36+
}
37+
return value
1738
}
1839

1940
/**

src/parsers/text.js

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -162,15 +162,11 @@ function inlineFilters (exp, single) {
162162
if (!dir.filters) {
163163
return '(' + exp + ')'
164164
} else {
165-
exp = dir.expression
166-
for (var i = 0, l = dir.filters.length; i < l; i++) {
167-
var filter = dir.filters[i]
168-
var args = filter.args
169-
? ',' + JSON.stringify(filter.args).slice(1, -1)
170-
: ''
171-
exp = 'this._applyFilter("' + filter.name + '",[' + exp + args + '])'
172-
}
173-
return exp
165+
return 'this._applyFilters(' +
166+
dir.expression + // value
167+
',null,' + // oldValue (null for read)
168+
JSON.stringify(dir.filters) + // filter descriptors
169+
',false)' // write?
174170
}
175171
}
176172
}

0 commit comments

Comments
 (0)