From 5001c1a1217772d2bffe108bafd475b24badf559 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Wed, 7 Dec 2011 21:01:19 -0800 Subject: refactor($interpolate): improve interpolation service add documentation --- src/service/interpolate.js | 205 ++++++++++++++++++++++++++-------------- test/markupSpec.js | 86 ----------------- test/service/interpolateSpec.js | 92 ++++++++++++++++++ 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. + * + * +
+ var $interpolate = ...; // injected
+ var exp = $interpolate('Hello {{name}}!');
+ expect(exp({name:'Angular'}).toEqual('Hello Angular!');
+
+ *
+ *
+ * @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