Skip to content

Commit

Permalink
feat(filters): add filters in support of pure fields and methods, and…
Browse files Browse the repository at this point in the history
  • Loading branch information
chalin committed Mar 20, 2014
1 parent f4527ea commit 655dc14
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 2 deletions.
5 changes: 5 additions & 0 deletions lib/filter/module.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
library angular.filter;

import 'dart:convert' show JSON;
import 'dart:mirrors';
import 'package:intl/intl.dart';
import 'package:di/di.dart';
import 'package:angular/core/module.dart';
Expand All @@ -15,6 +16,7 @@ part 'lowercase.dart';
part 'number.dart';
part 'order_by.dart';
part 'uppercase.dart';
part 'pure.dart';

class NgFilterModule extends Module {
NgFilterModule() {
Expand All @@ -27,5 +29,8 @@ class NgFilterModule extends Module {
type(NumberFilter);
type(OrderByFilter);
type(UppercaseFilter);
type(ObserveFilter);
type(GetPureFieldFilter);
type(ApplyPureMethodFilter);
}
}
64 changes: 64 additions & 0 deletions lib/filter/pure.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
part of angular.filter;

/**
* This filter returns its argument unchanged but, for `List` and `Map`
* arguments, it causes the argument contents to be observed (as opposed to
* only its identity).
*
* Example:
*
* {{ list | observe }}
*/
@NgFilter(name: 'observe')
class ObserveFilter implements Function {
dynamic call(dynamic _) => _;
}

/**
* This filter returns the argument's value of the named field. Use this only
* when the field get operation is known to be pure (side-effect free).
*
* Examples:
*
* {{ map | field:'keys' }}
* {{ map | field:'values' }}
* {{ list | field:'reversed' }}
*/
@NgFilter(name: 'field')
class GetPureFieldFilter implements Function {
dynamic call(Object o, String fieldName) =>
o == null ? null :
reflect(o).getField(new Symbol(fieldName)).reflectee;
}

/**
* This filter returns the result of invoking the named method on the filter
* argument. Use this only when the method is known to be pure (side-effect free).
*
* Examples:
*
* <span>{{ expression | method:'toString' }}</span>
* <ul><li ng-repeat="n in (names | method:'split':[','])">{{n}}</li></ul>
*
* The first example is useful when _expression_ yields a new identity but its
* string rendering is unchanged.
*/
@NgFilter(name: 'method')
class ApplyPureMethodFilter implements Function {
dynamic call(Object o, String methodName, [args, Map<String,dynamic> namedArgs]) {
if (o == null) return null;

if (args is Map) {
namedArgs = args;
args = const [];
} else if (args == null) {
args = const [];
}
final Map<Symbol,dynamic> _namedArgs = namedArgs == null ?
const <Symbol,dynamic>{} : <Symbol,dynamic>{};
if (namedArgs != null) {
namedArgs.forEach((k,v) => _namedArgs[new Symbol(k)] = v);
}
return reflect(o).invoke(new Symbol(methodName), args, _namedArgs).reflectee;
}
}
17 changes: 15 additions & 2 deletions test/core/scope_spec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,6 @@ void main() {
expect(logger).toEqual([]);
});


it('should prefix', (Logger logger, Map context, RootScope rootScope) {
context['a'] = true;
rootScope.watch('!a', (value, previous) => logger(value));
Expand Down Expand Up @@ -228,7 +227,21 @@ void main() {
expect(logger).toEqual(['identity', 'keys', ['foo', 'bar']]);
logger.clear();
});


it('should watch list value (vs. identity) changes when "observe" filter is used',
(Logger logger, Map context, RootScope rootScope, AstParser parser,
FilterMap filters) {
final list = [true, 2, 'abc'];
final logVal = (value, _) => logger(value);
context['list'] = list;
rootScope.watch( parser('list | observe', filters: filters), logVal);
rootScope.digest();
expect(logger).toEqual([list]);
logger.clear();
context['list'][2] = 'def';
rootScope.digest();
expect(logger).toEqual([[true, 2, 'def']]);
});
});


Expand Down
49 changes: 49 additions & 0 deletions test/filter/pure_spec.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
library pure_spec;

import '../_specs.dart';

void main() {
describe('pure filters', () {
beforeEach((Scope scope, Parser parse, FilterMap filters) {
scope.context['string'] = 'abc';
scope.context['list'] = 'abc'.split('');
scope.context['map'] = { 'a': 1, 'b': 2, 'c': 3 };
});

// Note that the `observe` filter is tested in [scope_spec.dart].

it('should return the value of the named field',
(Scope scope, Parser parse, FilterMap filters) {
expect(parse("list | field:'reversed'").eval(scope.context, filters)
).toEqual(['c', 'b', 'a']);
expect(parse("map | field:'keys'").eval(scope.context, filters)).toEqual(
['a', 'b', 'c']);
expect(parse("map | field:'values'").eval(scope.context, filters)
).toEqual([1, 2, 3]);
});

it('should return method call result',
(Scope scope, Parser parse, FilterMap filters) {
expect(parse("list | method:'toString'").eval(scope.context, filters)
).toEqual('[a, b, c]');
expect(parse("list | method:'join':['']").eval(scope.context, filters)
).toEqual('abc');
expect(parse("string | method:'split':['']").eval(scope.context, filters)
).toEqual(['a', 'b', 'c']);
});

it('should return method call result using namedArgs',
(Scope scope, Parser parse, FilterMap filters) {
scope.context['isB'] = (s) => s == 'b';
scope.context['zero'] = () => 0;

// Test for no positional args but with named args.
expect(parse("list | method:'toList':{'growable':false}").eval(
scope.context, filters)).toEqual(['a', 'b', 'c']);

// Test for both positional and named args.
expect(parse("list | method:'firstWhere':[isB]:{'orElse':zero}").eval(
scope.context, filters)).toEqual('b');
});
});
}

0 comments on commit 655dc14

Please sign in to comment.