Skip to content

Commit 655dc14

Browse files
committed
feat(filters): add filters in support of pure fields and methods, and to observe lists and maps
Closes dart-archive#359, dart-archive#394, dart-archive#397, dart-archive#757. Relates to dart-archive#772, dart-archive#773.
1 parent f4527ea commit 655dc14

File tree

4 files changed

+133
-2
lines changed

4 files changed

+133
-2
lines changed

lib/filter/module.dart

+5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
library angular.filter;
22

33
import 'dart:convert' show JSON;
4+
import 'dart:mirrors';
45
import 'package:intl/intl.dart';
56
import 'package:di/di.dart';
67
import 'package:angular/core/module.dart';
@@ -15,6 +16,7 @@ part 'lowercase.dart';
1516
part 'number.dart';
1617
part 'order_by.dart';
1718
part 'uppercase.dart';
19+
part 'pure.dart';
1820

1921
class NgFilterModule extends Module {
2022
NgFilterModule() {
@@ -27,5 +29,8 @@ class NgFilterModule extends Module {
2729
type(NumberFilter);
2830
type(OrderByFilter);
2931
type(UppercaseFilter);
32+
type(ObserveFilter);
33+
type(GetPureFieldFilter);
34+
type(ApplyPureMethodFilter);
3035
}
3136
}

lib/filter/pure.dart

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
part of angular.filter;
2+
3+
/**
4+
* This filter returns its argument unchanged but, for `List` and `Map`
5+
* arguments, it causes the argument contents to be observed (as opposed to
6+
* only its identity).
7+
*
8+
* Example:
9+
*
10+
* {{ list | observe }}
11+
*/
12+
@NgFilter(name: 'observe')
13+
class ObserveFilter implements Function {
14+
dynamic call(dynamic _) => _;
15+
}
16+
17+
/**
18+
* This filter returns the argument's value of the named field. Use this only
19+
* when the field get operation is known to be pure (side-effect free).
20+
*
21+
* Examples:
22+
*
23+
* {{ map | field:'keys' }}
24+
* {{ map | field:'values' }}
25+
* {{ list | field:'reversed' }}
26+
*/
27+
@NgFilter(name: 'field')
28+
class GetPureFieldFilter implements Function {
29+
dynamic call(Object o, String fieldName) =>
30+
o == null ? null :
31+
reflect(o).getField(new Symbol(fieldName)).reflectee;
32+
}
33+
34+
/**
35+
* This filter returns the result of invoking the named method on the filter
36+
* argument. Use this only when the method is known to be pure (side-effect free).
37+
*
38+
* Examples:
39+
*
40+
* <span>{{ expression | method:'toString' }}</span>
41+
* <ul><li ng-repeat="n in (names | method:'split':[','])">{{n}}</li></ul>
42+
*
43+
* The first example is useful when _expression_ yields a new identity but its
44+
* string rendering is unchanged.
45+
*/
46+
@NgFilter(name: 'method')
47+
class ApplyPureMethodFilter implements Function {
48+
dynamic call(Object o, String methodName, [args, Map<String,dynamic> namedArgs]) {
49+
if (o == null) return null;
50+
51+
if (args is Map) {
52+
namedArgs = args;
53+
args = const [];
54+
} else if (args == null) {
55+
args = const [];
56+
}
57+
final Map<Symbol,dynamic> _namedArgs = namedArgs == null ?
58+
const <Symbol,dynamic>{} : <Symbol,dynamic>{};
59+
if (namedArgs != null) {
60+
namedArgs.forEach((k,v) => _namedArgs[new Symbol(k)] = v);
61+
}
62+
return reflect(o).invoke(new Symbol(methodName), args, _namedArgs).reflectee;
63+
}
64+
}

test/core/scope_spec.dart

+15-2
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,6 @@ void main() {
150150
expect(logger).toEqual([]);
151151
});
152152

153-
154153
it('should prefix', (Logger logger, Map context, RootScope rootScope) {
155154
context['a'] = true;
156155
rootScope.watch('!a', (value, previous) => logger(value));
@@ -228,7 +227,21 @@ void main() {
228227
expect(logger).toEqual(['identity', 'keys', ['foo', 'bar']]);
229228
logger.clear();
230229
});
231-
230+
231+
it('should watch list value (vs. identity) changes when "observe" filter is used',
232+
(Logger logger, Map context, RootScope rootScope, AstParser parser,
233+
FilterMap filters) {
234+
final list = [true, 2, 'abc'];
235+
final logVal = (value, _) => logger(value);
236+
context['list'] = list;
237+
rootScope.watch( parser('list | observe', filters: filters), logVal);
238+
rootScope.digest();
239+
expect(logger).toEqual([list]);
240+
logger.clear();
241+
context['list'][2] = 'def';
242+
rootScope.digest();
243+
expect(logger).toEqual([[true, 2, 'def']]);
244+
});
232245
});
233246

234247

test/filter/pure_spec.dart

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
library pure_spec;
2+
3+
import '../_specs.dart';
4+
5+
void main() {
6+
describe('pure filters', () {
7+
beforeEach((Scope scope, Parser parse, FilterMap filters) {
8+
scope.context['string'] = 'abc';
9+
scope.context['list'] = 'abc'.split('');
10+
scope.context['map'] = { 'a': 1, 'b': 2, 'c': 3 };
11+
});
12+
13+
// Note that the `observe` filter is tested in [scope_spec.dart].
14+
15+
it('should return the value of the named field',
16+
(Scope scope, Parser parse, FilterMap filters) {
17+
expect(parse("list | field:'reversed'").eval(scope.context, filters)
18+
).toEqual(['c', 'b', 'a']);
19+
expect(parse("map | field:'keys'").eval(scope.context, filters)).toEqual(
20+
['a', 'b', 'c']);
21+
expect(parse("map | field:'values'").eval(scope.context, filters)
22+
).toEqual([1, 2, 3]);
23+
});
24+
25+
it('should return method call result',
26+
(Scope scope, Parser parse, FilterMap filters) {
27+
expect(parse("list | method:'toString'").eval(scope.context, filters)
28+
).toEqual('[a, b, c]');
29+
expect(parse("list | method:'join':['']").eval(scope.context, filters)
30+
).toEqual('abc');
31+
expect(parse("string | method:'split':['']").eval(scope.context, filters)
32+
).toEqual(['a', 'b', 'c']);
33+
});
34+
35+
it('should return method call result using namedArgs',
36+
(Scope scope, Parser parse, FilterMap filters) {
37+
scope.context['isB'] = (s) => s == 'b';
38+
scope.context['zero'] = () => 0;
39+
40+
// Test for no positional args but with named args.
41+
expect(parse("list | method:'toList':{'growable':false}").eval(
42+
scope.context, filters)).toEqual(['a', 'b', 'c']);
43+
44+
// Test for both positional and named args.
45+
expect(parse("list | method:'firstWhere':[isB]:{'orElse':zero}").eval(
46+
scope.context, filters)).toEqual('b');
47+
});
48+
});
49+
}

0 commit comments

Comments
 (0)