aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/service/interpolate.js205
-rw-r--r--test/markupSpec.js86
-rw-r--r--test/service/interpolateSpec.js92
3 files changed, 226 insertions, 157 deletions
diff --git a/src/service/interpolate.js b/src/service/interpolate.js
index 03692824..6d3ae868 100644
--- a/src/service/interpolate.js
+++ b/src/service/interpolate.js
@@ -1,82 +1,145 @@
'use strict';
-function $InterpolateProvider(){
- this.$get = ['$parse', function($parse){
- return function(text, templateOnly) {
- var bindings = parseBindings(text);
- if (hasBindings(bindings) || !templateOnly) {
- return compileBindTemplate(text);
- }
- };
- }];
-}
+/**
+ * @ngdoc function
+ * @name angular.module.ng.$interpolateProvider
+ * @function
+ *
+ * @description
+ *
+ * Used for configuring the interpolation markup. Deafults to `{{` and `}}`.
+ */
+function $InterpolateProvider() {
+ var startSymbol = '{{';
+ var endSymbol = '}}';
-var bindTemplateCache = {};
-function compileBindTemplate(template){
- var fn = bindTemplateCache[template];
- if (!fn) {
- var bindings = [];
- forEach(parseBindings(template), function(text){
- var exp = binding(text);
- bindings.push(exp
- ? function(scope, element) { return scope.$eval(exp); }
- : function() { return text; });
- });
- bindTemplateCache[template] = fn = function(scope, element, prettyPrintJson) {
- var parts = [],
- hadOwnElement = scope.hasOwnProperty('$element'),
- oldElement = scope.$element;
+ /**
+ * @ngdoc method
+ * @name angular.module.ng.$interpolateProvider#startSymbol
+ * @methodOf angular.module.ng.$interpolateProvider
+ * @description
+ * Symbol to denote start of expression in the interpolated string. Defaults to `{{`.
+ *
+ * @prop {string=} value new value to set the starting symbol to.
+ */
+ this.startSymbol = function(value){
+ if (value) {
+ startSymbol = value;
+ return this;
+ } else {
+ return startSymbol;
+ }
+ };
- // TODO(misko): get rid of $element
- scope.$element = element;
- try {
- for (var i = 0; i < bindings.length; i++) {
- var value = bindings[i](scope, element);
- if (isElement(value))
- value = '';
- else if (isObject(value))
- value = toJson(value, prettyPrintJson);
- parts.push(value);
- }
- return parts.join('');
- } finally {
- if (hadOwnElement) {
- scope.$element = oldElement;
- } else {
- delete scope.$element;
- }
- }
- };
- }
- return fn;
-}
+ /**
+ * @ngdoc method
+ * @name angular.module.ng.$interpolateProvider#endSymbol
+ * @methodOf angular.module.ng.$interpolateProvider
+ * @description
+ * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
+ *
+ * @prop {string=} value new value to set the ending symbol to.
+ */
+ this.endSymbol = function(value){
+ if (value) {
+ endSymbol = value;
+ return this;
+ } else {
+ return startSymbol;
+ }
+ };
-function parseBindings(string) {
- var results = [];
- var lastIndex = 0;
- var index;
- while((index = string.indexOf('{{', lastIndex)) > -1) {
- if (lastIndex < index)
- results.push(string.substr(lastIndex, index - lastIndex));
- lastIndex = index;
+ this.$get = ['$parse', function($parse) {
+ var startSymbolLength = startSymbol.length,
+ endSymbolLength = endSymbol.length;
- index = string.indexOf('}}', index);
- index = index < 0 ? string.length : index + 2;
+ /**
+ * @ngdoc function
+ * @name angular.module.ng.$interpolate
+ * @function
+ *
+ * @requires $parse
+ *
+ * @description
+ *
+ * Compiles a string with markup into an interpolation function. This service is used by the
+ * HTML {@link angular.module.ng.$compile $compile} service for data binding. See
+ * {@link angular.module.ng.$interpolateProvider $interpolateProvider} for configuring the
+ * interpolation markup.
+ *
+ *
+ <pre>
+ var $interpolate = ...; // injected
+ var exp = $interpolate('Hello {{name}}!');
+ expect(exp({name:'Angular'}).toEqual('Hello Angular!');
+ </pre>
+ *
+ *
+ * @param {string} text The text with markup to interpolate.
+ * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have
+ * embedded expression in order to return an interpolation function. Strings with no
+ * embedded expression will return null for the interpolation function.
+ * @returns {function(context)} an interpolation function which is used to compute the interpolated
+ * string. The function has these parameters:
+ *
+ * * `context`: an object against which any expressions embedded in the strings are evaluated
+ * against.
+ *
+ */
+ return function(text, mustHaveExpression) {
+ var startIndex,
+ endIndex,
+ index = 0,
+ parts = [],
+ length = text.length,
+ hasInterpolation = false,
+ fn,
+ exp,
+ concat = [];
- results.push(string.substr(lastIndex, index - lastIndex));
- lastIndex = index;
- }
- if (lastIndex != string.length)
- results.push(string.substr(lastIndex, string.length - lastIndex));
- return results.length === 0 ? [ string ] : results;
-}
+ while(index < length) {
+ if ( ((startIndex = text.indexOf(startSymbol, index)) != -1) &&
+ ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1) ) {
+ (index != startIndex) && parts.push(text.substring(index, startIndex));
+ parts.push(fn = $parse(exp = text.substring(startIndex + startSymbolLength, endIndex)));
+ fn.exp = exp;
+ index = endIndex + endSymbolLength;
+ hasInterpolation = true;
+ } else {
+ // we did not find anything, so we have to add the remainder to the parts array
+ (index != length) && parts.push(text.substring(index));
+ index = length;
+ }
+ }
-function binding(string) {
- var binding = string.replace(/\n/gm, ' ').match(/^\{\{(.*)\}\}$/);
- return binding ? binding[1] : null;
-}
+ if (!(length = parts.length)) {
+ // we added, nothing, must have been an empty string.
+ parts.push('');
+ length = 1;
+ }
-function hasBindings(bindings) {
- return bindings.length > 1 || binding(bindings[0]) !== null;
+ if (!mustHaveExpression || hasInterpolation) {
+ concat.length = length;
+ fn = function(context) {
+ for(var i = 0, ii = length, part; i<ii; i++) {
+ if (typeof (part = parts[i]) == 'function') {
+ part = part(context);
+ if (part == null || part == undefined) {
+ part = '';
+ } else if (typeof part != 'string') {
+ part = toJson(part);
+ }
+ }
+ concat[i] = part;
+ }
+ return concat.join('');
+ };
+ fn.exp = text;
+ fn.parts = parts;
+ return fn;
+ }
+ };
+ }];
}
+
diff --git a/test/markupSpec.js b/test/markupSpec.js
index e2e11f7f..0dcbbfe9 100644
--- a/test/markupSpec.js
+++ b/test/markupSpec.js
@@ -170,91 +170,5 @@ describe("markups", function() {
expect(sortedHtml(element)).toEqual('<div href="some" ng:bind-attr="{"href":"some"}"></div>');
dealoc(element);
}));
-
- it('should Parse Text With No Bindings', inject(function($rootScope, $compile) {
- var parts = parseBindings("a");
- expect(parts.length).toBe(1);
- expect(parts[0]).toBe("a");
- expect(binding(parts[0])).toBeFalsy();
- }));
-
- it('should Parse Empty Text', inject(function($rootScope, $compile) {
- var parts = parseBindings("");
- expect(parts.length).toBe(1);
- expect(parts[0]).toBe("");
- expect(binding(parts[0])).toBeFalsy();
- }));
-
- it('should Parse Inner Binding', inject(function($rootScope, $compile) {
- var parts = parseBindings("a{{b}}C");
- expect(parts.length).toBe(3);
- expect(parts[0]).toBe("a");
- expect(binding(parts[0])).toBeFalsy();
- expect(parts[1]).toBe("{{b}}");
- expect(binding(parts[1])).toBe("b");
- expect(parts[2]).toBe("C");
- expect(binding(parts[2])).toBeFalsy();
- }));
-
- it('should Parse Ending Binding', inject(function($rootScope, $compile) {
- var parts = parseBindings("a{{b}}");
- expect(parts.length).toBe(2);
- expect(parts[0]).toBe("a");
- expect(binding(parts[0])).toBeFalsy();
- expect(parts[1]).toBe("{{b}}");
- expect(binding(parts[1])).toBe("b");
- }));
-
- it('should Parse Begging Binding', inject(function($rootScope, $compile) {
- var parts = parseBindings("{{b}}c");
- expect(parts.length).toBe(2);
- expect(parts[0]).toBe("{{b}}");
- expect(binding(parts[0])).toBe("b");
- expect(parts[1]).toBe("c");
- expect(binding(parts[1])).toBeFalsy();
- }));
-
- it('should Parse Loan Binding', inject(function($rootScope, $compile) {
- var parts = parseBindings("{{b}}");
- expect(parts.length).toBe(1);
- expect(parts[0]).toBe("{{b}}");
- expect(binding(parts[0])).toBe("b");
- }));
-
- it('should Parse Two Bindings', inject(function($rootScope, $compile) {
- var parts = parseBindings("{{b}}{{c}}");
- expect(parts.length).toBe(2);
- expect(parts[0]).toBe("{{b}}");
- expect(binding(parts[0])).toBe("b");
- expect(parts[1]).toBe("{{c}}");
- expect(binding(parts[1])).toBe("c");
- }));
-
- it('should Parse Two Bindings With Text In Middle', inject(function($rootScope, $compile) {
- var parts = parseBindings("{{b}}x{{c}}");
- expect(parts.length).toBe(3);
- expect(parts[0]).toBe("{{b}}");
- expect(binding(parts[0])).toBe("b");
- expect(parts[1]).toBe("x");
- expect(binding(parts[1])).toBeFalsy();
- expect(parts[2]).toBe("{{c}}");
- expect(binding(parts[2])).toBe("c");
- }));
-
- it('should Parse Multiline', inject(function($rootScope, $compile) {
- var parts = parseBindings('"X\nY{{A\nB}}C\nD"');
- expect(binding('{{A\nB}}')).toBeTruthy();
- expect(parts.length).toBe(3);
- expect(parts[0]).toBe('"X\nY');
- expect(parts[1]).toBe('{{A\nB}}');
- expect(parts[2]).toBe('C\nD"');
- }));
-
- it('should Has Binding', inject(function($rootScope, $compile) {
- expect(hasBindings(parseBindings("{{a}}"))).toBe(true);
- expect(hasBindings(parseBindings("a"))).toBeFalsy();
- expect(hasBindings(parseBindings("{{b}}x{{c}}"))).toBe(true);
- }));
-
});
diff --git a/test/service/interpolateSpec.js b/test/service/interpolateSpec.js
index 8644ee0a..d5f251ea 100644
--- a/test/service/interpolateSpec.js
+++ b/test/service/interpolateSpec.js
@@ -13,9 +13,101 @@ describe('$interpolate', function() {
expect($interpolate('some text', true)).toBeUndefined();
}));
+ it('should suppress falsy objects', inject(function($interpolate) {
+ expect($interpolate('{{undefined}}')()).toEqual('');
+ expect($interpolate('{{null}}')()).toEqual('');
+ expect($interpolate('{{a.b}}')()).toEqual('');
+ }));
+
+ it('should jsonify objects', inject(function($interpolate) {
+ expect($interpolate('{{ {} }}')()).toEqual('{}');
+ expect($interpolate('{{ true }}')()).toEqual('true');
+ expect($interpolate('{{ false }}')()).toEqual('false');
+ }));
+
it('should return interpolation function', inject(function($interpolate, $rootScope) {
$rootScope.name = 'Misko';
expect($interpolate('Hello {{name}}!')($rootScope)).toEqual('Hello Misko!');
}));
+
+ describe('provider', function() {
+ beforeEach(module(function($interpolateProvider) {
+ $interpolateProvider.startSymbol('--');
+ $interpolateProvider.endSymbol('--');
+ }));
+
+ it('should not get confused with same markers', inject(function($interpolate) {
+ expect($interpolate('---').parts).toEqual(['---']);
+ expect($interpolate('----')()).toEqual('');
+ expect($interpolate('--1--')()).toEqual('1');
+ }));
+ });
+
+ describe('parseBindings', function() {
+ it('should Parse Text With No Bindings', inject(function($interpolate) {
+ var parts = $interpolate("a").parts;
+ expect(parts.length).toEqual(1);
+ expect(parts[0]).toEqual("a");
+ }));
+
+ it('should Parse Empty Text', inject(function($interpolate) {
+ var parts = $interpolate("").parts;
+ expect(parts.length).toEqual(1);
+ expect(parts[0]).toEqual("");
+ }));
+
+ it('should Parse Inner Binding', inject(function($interpolate) {
+ var parts = $interpolate("a{{b}}C").parts;
+ expect(parts.length).toEqual(3);
+ expect(parts[0]).toEqual("a");
+ expect(parts[1].exp).toEqual("b");
+ expect(parts[1]({b:123})).toEqual(123);
+ expect(parts[2]).toEqual("C");
+ }));
+
+ it('should Parse Ending Binding', inject(function($interpolate) {
+ var parts = $interpolate("a{{b}}").parts;
+ expect(parts.length).toEqual(2);
+ expect(parts[0]).toEqual("a");
+ expect(parts[1].exp).toEqual("b");
+ expect(parts[1]({b:123})).toEqual(123);
+ }));
+
+ it('should Parse Begging Binding', inject(function($interpolate) {
+ var parts = $interpolate("{{b}}c").parts;
+ expect(parts.length).toEqual(2);
+ expect(parts[0].exp).toEqual("b");
+ expect(parts[1]).toEqual("c");
+ }));
+
+ it('should Parse Loan Binding', inject(function($interpolate) {
+ var parts = $interpolate("{{b}}").parts;
+ expect(parts.length).toEqual(1);
+ expect(parts[0].exp).toEqual("b");
+ }));
+
+ it('should Parse Two Bindings', inject(function($interpolate) {
+ var parts = $interpolate("{{b}}{{c}}").parts;
+ expect(parts.length).toEqual(2);
+ expect(parts[0].exp).toEqual("b");
+ expect(parts[1].exp).toEqual("c");
+ }));
+
+ it('should Parse Two Bindings With Text In Middle', inject(function($interpolate) {
+ var parts = $interpolate("{{b}}x{{c}}").parts;
+ expect(parts.length).toEqual(3);
+ expect(parts[0].exp).toEqual("b");
+ expect(parts[1]).toEqual("x");
+ expect(parts[2].exp).toEqual("c");
+ }));
+
+ it('should Parse Multiline', inject(function($interpolate) {
+ var parts = $interpolate('"X\nY{{A\n+B}}C\nD"').parts;
+ expect(parts.length).toEqual(3);
+ expect(parts[0]).toEqual('"X\nY');
+ expect(parts[1].exp).toEqual('A\n+B');
+ expect(parts[2]).toEqual('C\nD"');
+ }));
+ });
});