diff options
| -rw-r--r-- | src/service/interpolate.js | 205 | ||||
| -rw-r--r-- | test/markupSpec.js | 86 | ||||
| -rw-r--r-- | 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. +     * +     * +       <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"'); +    })); +  });  }); | 
