Skip to content
This repository has been archived by the owner on Feb 22, 2018. It is now read-only.

Commit

Permalink
feat(interpolate): use $watchSet to remove memory pressure
Browse files Browse the repository at this point in the history
  • Loading branch information
mhevery committed Nov 26, 2013
1 parent 25aaaa3 commit 283ea25
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 108 deletions.
83 changes: 35 additions & 48 deletions lib/core/interpolate.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,26 @@ String _endSymbol = '}}';
num _startSymbolLength = _startSymbol.length;
num _endSymbolLength = _endSymbol.length;

class Interpolation {
final String template;
final List<String> seperators;
final List<Expression> watchExpressions;
Function setter = (_) => _;

Interpolation(this.template, this.seperators, this.watchExpressions);

String call(List parts, [_, __]) {
var str = [];
for(var i = 0, ii = parts.length; i < ii; i++) {
str.add(seperators[i]);
var value = parts[i];
str.add(value == null ? '' : '$value');
}
str.add(seperators.last);
return setter(str.join(''));
}
}

/**
* Compiles a string with markup into an interpolation function. This service
* is used by the HTML [Compiler] service for data binding.
Expand All @@ -15,10 +35,9 @@ num _endSymbolLength = _endSymbol.length;
* expect(exp({name:'Angular'}).toEqual('Hello Angular!');
*/
class Interpolate {
Parser _parse;
ExceptionHandler _exceptionHandler;
final Parser _parse;

Interpolate(Parser this._parse, ExceptionHandler this._exceptionHandler);
Interpolate(Parser this._parse);

/**
* Compile markup text into interpolation function.
Expand All @@ -29,67 +48,35 @@ class Interpolate {
* Strings with no embedded expression will return null for the
* interpolation function.
*/
Expression call(String text, [bool mustHaveExpression = false]) {
Interpolation call(String template, [bool mustHaveExpression = false]) {
num startIndex;
num endIndex;
num index = 0;
List chunks = [];
num length = text.length;
num length = template.length;
bool hasInterpolation = false;
String exp;
List concat = [];
Expression fn;
List<String> separators = [];
List<Expression> watchExpressions = [];

while(index < length) {
if ( ((startIndex = text.indexOf(_startSymbol, index)) != -1) &&
((endIndex = text.indexOf(_endSymbol, startIndex + _startSymbolLength)) != -1) ) {
if (index != startIndex) {
chunks.add(text.substring(index, startIndex));
}
fn = _parse(exp = text.substring(startIndex + _startSymbolLength, endIndex));
chunks.add(fn);
fn.exp = exp;
if ( ((startIndex = template.indexOf(_startSymbol, index)) != -1) &&
((endIndex = template.indexOf(_endSymbol, startIndex + _startSymbolLength)) != -1) ) {
separators.add(template.substring(index, startIndex));
exp = template.substring(startIndex + _startSymbolLength, endIndex);
watchExpressions.add((_parse(exp)..exp = exp).eval);
index = endIndex + _endSymbolLength;
hasInterpolation = true;
} else {
// we did not find anything, so we have to add the remainder to the chunks array
if (index != length) {
chunks.add(text.substring(index));
}
separators.add(template.substring(index));
index = length;
}
}

if ((length = chunks.length) == 0) {
// we added, nothing, must have been an empty string.
chunks.add('');
length = 1;
if (separators.length == watchExpressions.length) {
separators.add('');
}

if (!mustHaveExpression || hasInterpolation) {
fn = new Expression((context, [locals]) {
try {
for(var i = 0, ii = length, chunk; i<ii; i++) {
if ((chunk = chunks[i]) is Expression) {
chunk = chunk.eval(context);
if (chunk == null) {
chunk = '';
} else if (!(chunk is String)) {
chunk = '$chunk';
}
}
concat.add(chunk);
}
return concat.join('');
} catch(err, s) {
_exceptionHandler("\$interpolate error! Can't interpolate: $text\n$err", s);
} finally {
concat.length = 0;
}
});
fn.exp = text;
fn.parts = chunks;
return fn;
return new Interpolation(template, separators, watchExpressions);
}
}
}
19 changes: 10 additions & 9 deletions lib/core_dom/ng_mustache.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ class NgTextMustacheDirective {
Interpolate interpolate,
Scope scope,
TextChangeListener listener) {
Expression interpolateFn = interpolate(markup);
setter(text) {
Interpolation interpolation = interpolate(markup);
interpolation.setter = (text) {
element.text = text;
if (listener != null) listener.call(text);
}
setter('');
scope.$watch(interpolateFn.eval, setter, markup.trim());
};
interpolation.setter('');
print(interpolation.watchExpressions);
scope.$watchSet(interpolation.watchExpressions, interpolation.call, markup.trim());
}

}
Expand All @@ -27,10 +28,10 @@ class NgAttrMustacheDirective {
NgAttrMustacheDirective(NodeAttrs attrs, String markup, Interpolate interpolate, Scope scope) {
var match = ATTR_NAME_VALUE_REGEXP.firstMatch(markup);
var attrName = match[1];
Expression interpolateFn = interpolate(match[2]);
Function attrSetter = (text) => attrs[attrName] = text;
attrSetter('');
scope.$watch(interpolateFn.eval, attrSetter, markup.trim());
Interpolation interpolation = interpolate(match[2]);
interpolation.setter = (text) => attrs[attrName] = text;
interpolation.setter('');
scope.$watchSet(interpolation.watchExpressions, interpolation.call, markup.trim());
}
}

85 changes: 34 additions & 51 deletions test/core/interpolate_spec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,115 +15,98 @@ main() {
}));

it('should suppress falsy objects', inject((Interpolate $interpolate) {
expect($interpolate('{{undefined}}').eval(null)).toEqual('');
expect($interpolate('{{undefined+undefined}}').eval(null)).toEqual('');
expect($interpolate('{{null}}').eval(null)).toEqual('');
expect($interpolate('{{a.b}}').eval(null)).toEqual('');
expect($interpolate('{{undefined}}')([null])).toEqual('');
expect($interpolate('{{undefined+undefined}}')([null])).toEqual('');
expect($interpolate('{{null}}')([null])).toEqual('');
expect($interpolate('{{a.b}}')([null])).toEqual('');
}));

it('should jsonify objects', inject((Interpolate $interpolate) {
expect($interpolate('{{ {} }}').eval(null)).toEqual('{}');
expect($interpolate('{{ true }}').eval(null)).toEqual('true');
expect($interpolate('{{ false }}').eval(null)).toEqual('false');
expect($interpolate('{{ {} }}')([{}])).toEqual('{}');
expect($interpolate('{{ true }}')([true])).toEqual('true');
expect($interpolate('{{ false }}')([false])).toEqual('false');
}));

it('should rethrow exceptions', inject((Interpolate $interpolate, Scope $rootScope) {
$rootScope.err = () {
throw 'oops';
};
expect(() {
$interpolate('{{err()}}').eval($rootScope);
}).toThrow(r"$interpolate error! Can't interpolate: {{err()}}");
}));

it('should return interpolation function', inject((Interpolate $interpolate, Scope $rootScope) {
$rootScope.name = 'Misko';
var fn = $interpolate('Hello {{name}}!');
expect(fn.eval($rootScope)).toEqual('Hello Misko!');
expect(fn.eval($rootScope)).toEqual('Hello Misko!');
expect(fn(['Misko'])).toEqual('Hello Misko!');
}));


it('should ignore undefined model', inject((Interpolate $interpolate) {
expect($interpolate("Hello {{'World' + foo}}").eval(null)).toEqual('Hello World');
expect($interpolate("Hello {{'World' + foo}}")(['World'])).toEqual('Hello World');
}));


it('should ignore undefined return value', inject((Interpolate $interpolate, Scope $rootScope) {
$rootScope.foo = () => null;
expect($interpolate("Hello {{'World' + foo()}}").eval($rootScope)).toEqual('Hello World');
}));

it('should use toString to conver objects to string', inject((Interpolate $interpolate, Scope $rootScope) {
$rootScope.obj = new ToStringableObject();
expect($interpolate("Hello, {{obj}}!").eval($rootScope)).toEqual('Hello, World!');
expect($interpolate("Hello, {{obj}}!")([new ToStringableObject()])).toEqual('Hello, World!');
}));


describe('parseBindings', () {
it('should Parse Text With No Bindings', inject((Interpolate $interpolate) {
var parts = $interpolate("a").parts;
var parts = $interpolate("a").seperators;
expect(parts.length).toEqual(1);
expect(parts[0]).toEqual("a");
}));

it('should Parse Empty Text', inject((Interpolate $interpolate) {
var parts = $interpolate("").parts;
var parts = $interpolate("").seperators;
expect(parts.length).toEqual(1);
expect(parts[0]).toEqual("");
}));

it('should Parse Inner Binding', inject((Interpolate $interpolate) {
var parts = $interpolate("a{{b}}C").parts;
expect(parts.length).toEqual(3);
var parts = $interpolate("a{{b}}C").seperators;
expect(parts.length).toEqual(2);
expect(parts[0]).toEqual("a");
expect(parts[1].exp).toEqual("b");
expect(parts[1].eval({'b':123})).toEqual(123);
expect(parts[2]).toEqual("C");
expect(parts[1]).toEqual("C");
}));

it('should Parse Ending Binding', inject((Interpolate $interpolate) {
var parts = $interpolate("a{{b}}").parts;
var parts = $interpolate("a{{b}}").seperators;
expect(parts.length).toEqual(2);
expect(parts[0]).toEqual("a");
expect(parts[1].exp).toEqual("b");
expect(parts[1].eval({'b':123})).toEqual(123);
expect(parts[1]).toEqual("");
}));

it('should Parse Begging Binding', inject((Interpolate $interpolate) {
var parts = $interpolate("{{b}}c").parts;
var parts = $interpolate("{{b}}c").seperators;
expect(parts.length).toEqual(2);
expect(parts[0].exp).toEqual("b");
expect(parts[0]).toEqual("");
expect(parts[1]).toEqual("c");
}));

it('should Parse Loan Binding', inject((Interpolate $interpolate) {
var parts = $interpolate("{{b}}").parts;
expect(parts.length).toEqual(1);
expect(parts[0].exp).toEqual("b");
var parts = $interpolate("{{b}}").seperators;
expect(parts.length).toEqual(2);
expect(parts[0]).toEqual("");
expect(parts[1]).toEqual("");
}));

it('should Parse Two Bindings', inject((Interpolate $interpolate) {
var parts = $interpolate("{{b}}{{c}}").parts;
expect(parts.length).toEqual(2);
expect(parts[0].exp).toEqual("b");
expect(parts[1].exp).toEqual("c");
var parts = $interpolate("{{b}}{{c}}").seperators;
expect(parts.length).toEqual(3);
expect(parts[0]).toEqual("");
expect(parts[1]).toEqual("");
expect(parts[2]).toEqual("");
}));

it('should Parse Two Bindings With Text In Middle', inject((Interpolate $interpolate) {
var parts = $interpolate("{{b}}x{{c}}").parts;
var parts = $interpolate("{{b}}x{{c}}").seperators;
expect(parts.length).toEqual(3);
expect(parts[0].exp).toEqual("b");
expect(parts[0]).toEqual("");
expect(parts[1]).toEqual("x");
expect(parts[2].exp).toEqual("c");
expect(parts[2]).toEqual("");
}));

it('should Parse Multiline', inject((Interpolate $interpolate) {
var parts = $interpolate('"X\nY{{A\n+B}}C\nD"').parts;
expect(parts.length).toEqual(3);
var parts = $interpolate('"X\nY{{A\n+B}}C\nD"').seperators;
expect(parts.length).toEqual(2);
expect(parts[0]).toEqual('"X\nY');
expect(parts[1].exp).toEqual('A\n+B');
expect(parts[2]).toEqual('C\nD"');
expect(parts[1]).toEqual('C\nD"');
}));
});
});
Expand Down

0 comments on commit 283ea25

Please sign in to comment.