diff options
| author | Misko Hevery | 2011-11-22 21:28:39 -0800 | 
|---|---|---|
| committer | Misko Hevery | 2012-01-25 11:50:37 -0800 | 
| commit | 9ee2cdff44e7d496774b340de816344126c457b3 (patch) | |
| tree | 476ffcb4425e7160865029d6b57d41b766750285 /src/widgets.js | |
| parent | 8af4fde18246ac1587b471a549e70d5d858bf0ee (diff) | |
| download | angular.js-9ee2cdff44e7d496774b340de816344126c457b3.tar.bz2 | |
refactor(directives): connect new compiler
- turn everything into a directive
Diffstat (limited to 'src/widgets.js')
| -rw-r--r-- | src/widgets.js | 591 | 
1 files changed, 298 insertions, 293 deletions
| diff --git a/src/widgets.js b/src/widgets.js index 53be8b14..cf32bdc1 100644 --- a/src/widgets.js +++ b/src/widgets.js @@ -67,7 +67,7 @@           </select>           url of the template: <tt><a href="{{template.url}}">{{template.url}}</a></tt>           <hr/> -         <ng:include src="template.url"></ng:include> +         <div class="ng-include" src="template.url"></div>         </div>        </doc:source>        <doc:scenario> @@ -87,64 +87,62 @@        </doc:scenario>      </doc:example>   */ -angularWidget('ng:include', function(element){ -  var compiler = this, -      srcExp = element.attr("src"), -      scopeExp = element.attr("scope") || '', -      onloadExp = element[0].getAttribute('onload') || '', //workaround for jquery bug #7537 -      autoScrollExp = element.attr('autoscroll'); - -  if (element[0]['ng:compiled']) { -    this.descend(true); -    this.directives(true); -  } else { -    element[0]['ng:compiled'] = true; -    return ['$http', '$templateCache', '$anchorScroll', '$element', -    function($http,   $templateCache,   $anchorScroll,   element) { -      var scope = this, -          changeCounter = 0, -          childScope; - -      function incrementChange() { changeCounter++;} -      this.$watch(srcExp, incrementChange); -      this.$watch(function() { -        var includeScope = scope.$eval(scopeExp); -        if (includeScope) return includeScope.$id; -      }, incrementChange); -      this.$watch(function() {return changeCounter;}, function(newChangeCounter) { -        var src = scope.$eval(srcExp), -            useScope = scope.$eval(scopeExp); - -        function clearContent() { -          // if this callback is still desired -          if (newChangeCounter === changeCounter) { -            if (childScope) childScope.$destroy(); -            childScope = null; -            element.html(''); -          } -        } - -        if (src) { -          $http.get(src, {cache: $templateCache}).success(function(response) { -            // if this callback is still desired -            if (newChangeCounter === changeCounter) { -              element.html(response); -              if (childScope) childScope.$destroy(); -              childScope = useScope ? useScope : scope.$new(); -              compiler.compile(element)(childScope); -              if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) { -                $anchorScroll(); +var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile', +                  function($http,   $templateCache,   $anchorScroll,   $compile) { +  return { +    compile: function(element, attr) { +      var srcExp = attr.src, +          scopeExp = attr.scope || '', +          onloadExp = attr.onload || '', //workaround for jquery bug #7537 +          autoScrollExp = attr.autoscroll; +      if (!element[0]['ng:compiled']) { +        element[0]['ng:compiled'] = true; +        return function(scope, element, attr){ +          var changeCounter = 0, +              childScope; + +          function incrementChange() { changeCounter++;} +          scope.$watch(srcExp, incrementChange); +          scope.$watch(function() { +            var includeScope = scope.$eval(scopeExp); +            if (includeScope) return includeScope.$id; +          }, incrementChange); +          scope.$watch(function() {return changeCounter;}, function(newChangeCounter) { +             var src = scope.$eval(srcExp), +                 useScope = scope.$eval(scopeExp); + +            function clearContent() { +              // if this callback is still desired +              if (newChangeCounter === changeCounter) { +                if (childScope) childScope.$destroy(); +                childScope = null; +                element.html('');                } -              scope.$eval(onloadExp);              } -          }).error(clearContent); -        } else { -          clearContent(); -        } -      }); -    }]; + +             if (src) { +               $http.get(src, {cache: $templateCache}).success(function(response) { +                 // if this callback is still desired +                 if (newChangeCounter === changeCounter) { +                   element.html(response); +                   if (childScope) childScope.$destroy(); +                   childScope = useScope ? useScope : scope.$new(); +                   $compile(element)(childScope); +                   if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) { +                     $anchorScroll(); +                   } +                   scope.$eval(onloadExp); +                 } +               }).error(clearContent); +             } else { +               clearContent(); +             } +          }); +        }; +      } +    }    } -}); +}];  /**   * @ngdoc widget @@ -203,58 +201,62 @@ angularWidget('ng:include', function(element){        </doc:scenario>      </doc:example>   */ -angularWidget('ng:switch', function(element) { -  var compiler = this, -      watchExpr = element.attr("on"), -      changeExpr = element.attr('change'), -      casesTemplate = {}, -      defaultCaseTemplate, -      children = element.children(), -      length = children.length, -      child, -      when; - -  if (!watchExpr) throw new Error("Missing 'on' attribute."); -  while(length--) { -    child = jqLite(children[length]); -    // this needs to be here for IE -    child.remove(); -    when = child.attr('ng:switch-when'); -    if (isString(when)) { -      casesTemplate[when] = compiler.compile(child); -    } else if (isString(child.attr('ng:switch-default'))) { -      defaultCaseTemplate = compiler.compile(child); -    } -  } -  children = null; // release memory; -  element.html(''); +var ngSwitchDirective = ['$compile', function($compile){ +  return { +    compile: function(element, attr) { +      var watchExpr = attr.on, +        changeExpr = attr.change, +        casesTemplate = {}, +        defaultCaseTemplate, +        children = element.children(), +        length = children.length, +        child, +        when; + +      if (!watchExpr) throw new Error("Missing 'on' attribute."); +      while(length--) { +        child = jqLite(children[length]); +        // this needs to be here for IE +        child.remove(); +        // TODO(misko): this attr reading is not normilized +        when = child.attr('ng:switch-when'); +        if (isString(when)) { +          casesTemplate[when] = $compile(child); +          // TODO(misko): this attr reading is not normilized +        } else if (isString(child.attr('ng:switch-default'))) { +          defaultCaseTemplate = $compile(child); +        } +      } +      children = null; // release memory; +      element.html(''); -  return function(element){ -    var changeCounter = 0; -    var childScope; -    var selectedTemplate; -    var scope = this; +      return function(scope, element, attr){ +        var changeCounter = 0; +        var childScope; +        var selectedTemplate; -    this.$watch(watchExpr, function(value) { -      element.html(''); -      if ((selectedTemplate = casesTemplate[value] || defaultCaseTemplate)) { -        changeCounter++; -        if (childScope) childScope.$destroy(); -        childScope = scope.$new(); -        childScope.$eval(changeExpr); -      } -    }); +        scope.$watch(watchExpr, function(value) { +          element.html(''); +          if ((selectedTemplate = casesTemplate[value] || defaultCaseTemplate)) { +            changeCounter++; +            if (childScope) childScope.$destroy(); +            childScope = scope.$new(); +            childScope.$eval(changeExpr); +          } +        }); -    this.$watch(function() {return changeCounter;}, function() { -      element.html(''); -      if (selectedTemplate) { -        selectedTemplate(childScope, function(caseElement) { -          element.append(caseElement); +        scope.$watch(function() {return changeCounter;}, function() { +          element.html(''); +          if (selectedTemplate) { +            selectedTemplate(childScope, function(caseElement) { +              element.append(caseElement); +            }); +          }          }); -      } -    }); +      }; +    }    }; -}); +}];  /* @@ -265,25 +267,24 @@ angularWidget('ng:switch', function(element) {   * changing the location or causing page reloads, e.g.:   * <a href="" ng:click="model.$save()">Save</a>   */ -angularWidget('a', function() { -  this.descend(true); -  this.directives(true); - -  return function(element) { -    var hasNgHref = ((element.attr('ng:bind-attr') || '').indexOf('"href":') !== -1); - +var htmlAnchorDirective = valueFn({ +  restrict: 'E', +  compile: function(element, attr) {      // turn <a href ng:click="..">link</a> into a link in IE      // but only if it doesn't have name attribute, in which case it's an anchor -    if (!hasNgHref && !element.attr('name') && !element.attr('href')) { -      element.attr('href', ''); +    if (!attr.href) { +      attr.$set('href', '');      } -    if (element.attr('href') === '' && !hasNgHref) { +    return function(scope, element) {        element.bind('click', function(event){ -        event.preventDefault(); +        // if we have no href url, then don't navigate anywhere. +        if (!element.attr('href')) { +          event.preventDefault(); +        }        });      } -  }; +  }  }); @@ -344,125 +345,131 @@ angularWidget('a', function() {        </doc:scenario>      </doc:example>   */ -angularWidget('@ng:repeat', function(expression, element){ -  element.removeAttr('ng:repeat'); -  element.replaceWith(jqLite('<!-- ng:repeat: ' + expression + ' -->')); -  var linker = this.compile(element); -  return function(iterStartElement){ -    var match = expression.match(/^\s*(.+)\s+in\s+(.*)\s*$/), -        lhs, rhs, valueIdent, keyIdent; -    if (! match) { -      throw Error("Expected ng:repeat in form of '_item_ in _collection_' but got '" + -      expression + "'."); -    } -    lhs = match[1]; -    rhs = match[2]; -    match = lhs.match(/^([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\)$/); -    if (!match) { -      throw Error("'item' in 'item in collection' should be identifier or (key, value) but got '" + -      keyValue + "'."); -    } -    valueIdent = match[3] || match[1]; -    keyIdent = match[2]; - -    var parentScope = this; -    // Store a list of elements from previous run. This is a hash where key is the item from the -    // iterator, and the value is an array of objects with following properties. -    //   - scope: bound scope -    //   - element: previous element. -    //   - index: position -    // We need an array of these objects since the same object can be returned from the iterator. -    // We expect this to be a rare case. -    var lastOrder = new HashQueueMap(); -    this.$watch(function(scope){ -      var index, length, -          collection = scope.$eval(rhs), -          collectionLength = size(collection, true), -          childScope, -          // Same as lastOrder but it has the current state. It will become the -          // lastOrder on the next iteration. -          nextOrder = new HashQueueMap(), -          key, value, // key/value of iteration -          array, last,       // last object information {scope, element, index} -          cursor = iterStartElement;     // current position of the node - -      if (!isArray(collection)) { -        // if object, extract keys, sort them and use to determine order of iteration over obj props -        array = []; -        for(key in collection) { -          if (collection.hasOwnProperty(key) && key.charAt(0) != '$') { -            array.push(key); -          } +var ngRepeatDirective = ['$compile', function($compile) { +  return { +    priority: 1000, +    terminal: true, +    compile: function(element, attr) { +      var expression = attr.ngRepeat; +      attr.$set(attr.$attr.ngRepeat); +      element.replaceWith(jqLite('<!-- ng:repeat: ' + expression + ' -->')); +      var linker = $compile(element); +      return function(scope, iterStartElement, attr){ +        var match = expression.match(/^\s*(.+)\s+in\s+(.*)\s*$/), +          lhs, rhs, valueIdent, keyIdent; +        if (! match) { +          throw Error("Expected ng:repeat in form of '_item_ in _collection_' but got '" + +            expression + "'.");          } -        array.sort(); -      } else { -        array = collection || []; -      } - -      // we are not using forEach for perf reasons (trying to avoid #call) -      for (index = 0, length = array.length; index < length; index++) { -        key = (collection === array) ? index : array[index]; -        value = collection[key]; -        last = lastOrder.shift(value); -        if (last) { -          // if we have already seen this object, then we need to reuse the -          // associated scope/element -          childScope = last.scope; -          nextOrder.push(value, last); - -          if (index === last.index) { -            // do nothing -            cursor = last.element; +        lhs = match[1]; +        rhs = match[2]; +        match = lhs.match(/^([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\)$/); +        if (!match) { +          throw Error("'item' in 'item in collection' should be identifier or (key, value) but got '" + +            keyValue + "'."); +        } +        valueIdent = match[3] || match[1]; +        keyIdent = match[2]; + +        // Store a list of elements from previous run. This is a hash where key is the item from the +        // iterator, and the value is an array of objects with following properties. +        //   - scope: bound scope +        //   - element: previous element. +        //   - index: position +        // We need an array of these objects since the same object can be returned from the iterator. +        // We expect this to be a rare case. +        var lastOrder = new HashQueueMap(); +        scope.$watch(function(scope){ +          var index, length, +              collection = scope.$eval(rhs), +              collectionLength = size(collection, true), +              childScope, +              // Same as lastOrder but it has the current state. It will become the +              // lastOrder on the next iteration. +              nextOrder = new HashQueueMap(), +              key, value, // key/value of iteration +              array, last,       // last object information {scope, element, index} +              cursor = iterStartElement;     // current position of the node + +          if (!isArray(collection)) { +            // if object, extract keys, sort them and use to determine order of iteration over obj props +            array = []; +            for(key in collection) { +              if (collection.hasOwnProperty(key) && key.charAt(0) != '$') { +                array.push(key); +              } +            } +            array.sort();            } else { -            // existing item which got moved -            last.index = index; -            // This may be a noop, if the element is next, but I don't know of a good way to -            // figure this out,  since it would require extra DOM access, so let's just hope that -            // the browsers realizes that it is noop, and treats it as such. -            cursor.after(last.element); -            cursor = last.element; +            array = collection || [];            } -        } else { -          // new item which we don't know about -          childScope = parentScope.$new(); -        } -        childScope[valueIdent] = value; -        if (keyIdent) childScope[keyIdent] = key; -        childScope.$index = index; -        childScope.$position = index === 0 ? -            'first' : -            (index == collectionLength - 1 ? 'last' : 'middle'); - -        if (!last) { -          linker(childScope, function(clone){ -            cursor.after(clone); -            last = { -                scope: childScope, -                element: (cursor = clone), -                index: index -              }; -            nextOrder.push(value, last); -          }); -        } -      } +          // we are not using forEach for perf reasons (trying to avoid #call) +          for (index = 0, length = array.length; index < length; index++) { +            key = (collection === array) ? index : array[index]; +            value = collection[key]; +            last = lastOrder.shift(value); +            if (last) { +              // if we have already seen this object, then we need to reuse the +              // associated scope/element +              childScope = last.scope; +              nextOrder.push(value, last); + +              if (index === last.index) { +                // do nothing +                cursor = last.element; +              } else { +                // existing item which got moved +                last.index = index; +                // This may be a noop, if the element is next, but I don't know of a good way to +                // figure this out,  since it would require extra DOM access, so let's just hope that +                // the browsers realizes that it is noop, and treats it as such. +                cursor.after(last.element); +                cursor = last.element; +              } +            } else { +              // new item which we don't know about +              childScope = scope.$new(); +            } -      //shrink children -      for (key in lastOrder) { -        if (lastOrder.hasOwnProperty(key)) { -          array = lastOrder[key]; -          while(array.length) { -            value = array.pop(); -            value.element.remove(); -            value.scope.$destroy(); +            childScope[valueIdent] = value; +            if (keyIdent) childScope[keyIdent] = key; +            childScope.$index = index; +            childScope.$position = index === 0 ? +                'first' : +                (index == collectionLength - 1 ? 'last' : 'middle'); + +            if (!last) { +              linker(childScope, function(clone){ +                cursor.after(clone); +                last = { +                    scope: childScope, +                    element: (cursor = clone), +                    index: index +                  }; +                nextOrder.push(value, last); +              }); +            }            } -        } -      } -      lastOrder = nextOrder; -    }); +          //shrink children +          for (key in lastOrder) { +            if (lastOrder.hasOwnProperty(key)) { +              array = lastOrder[key]; +              while(array.length) { +                value = array.pop(); +                value.element.remove(); +                value.scope.$destroy(); +              } +            } +          } + +          lastOrder = nextOrder; +        }); +      }; +    }    }; -}); +}];  /** @@ -496,7 +503,7 @@ angularWidget('@ng:repeat', function(expression, element){        </doc:scenario>      </doc:example>   */ -angularWidget("@ng:non-bindable", noop); +var ngNonBindableDirective = valueFn({ terminal: true });  /** @@ -564,49 +571,48 @@ angularWidget("@ng:non-bindable", noop);        </doc:scenario>      </doc:example>   */ -angularWidget('ng:view', function(element) { -  var compiler = this; - -  if (!element[0]['ng:compiled']) { -    element[0]['ng:compiled'] = true; -    return ['$http', '$templateCache', '$route', '$anchorScroll', '$element', -    function($http,   $templateCache,   $route,   $anchorScroll,   element) { -      var template; -      var changeCounter = 0; - -      this.$on('$afterRouteChange', function() { -        changeCounter++; -      }); +var ngViewDirective = ['$http', '$templateCache', '$route', '$anchorScroll', '$compile', +               function($http,   $templateCache,   $route,   $anchorScroll,   $compile) { +  return { +    compile: function(element, attr) { +      if (!element[0]['ng:compiled']) { +        element[0]['ng:compiled'] = true; + +        return function(scope, element, attrs) { +          var changeCounter = 0; + +          scope.$on('$afterRouteChange', function() { +            changeCounter++; +          }); -      this.$watch(function() {return changeCounter;}, function(newChangeCounter) { -        var template = $route.current && $route.current.template; +          scope.$watch(function() {return changeCounter;}, function(newChangeCounter) { +            var template = $route.current && $route.current.template; -        function clearContent() { -          // ignore callback if another route change occured since -          if (newChangeCounter == changeCounter) { -            element.html(''); -          } -        } +            function clearContent() { +              // ignore callback if another route change occured since +              if (newChangeCounter == changeCounter) { +                element.html(''); +              } +            } -        if (template) { -          $http.get(template, {cache: $templateCache}).success(function(response) { -            // ignore callback if another route change occured since -            if (newChangeCounter == changeCounter) { -              element.html(response); -              compiler.compile(element)($route.current.scope); -              $anchorScroll(); +            if (template) { +              $http.get(template, {cache: $templateCache}).success(function(response) { +                // ignore callback if another route change occured since +                if (newChangeCounter == changeCounter) { +                  element.html(response); +                  $compile(element)($route.current.scope); +                  $anchorScroll(); +                } +              }).error(clearContent); +            } else { +              clearContent();              } -          }).error(clearContent); -        } else { -          clearContent(); -        } -      }); -    }]; -  } else { -    compiler.descend(true); -    compiler.directives(true); -  } -}); +          }); +        }; +      } +    } +  }; +}];  /** @@ -715,81 +721,80 @@ angularWidget('ng:view', function(element) {            <!--- Example with simple pluralization rules for en locale --->            Without Offset: -          <ng:pluralize count="personCount" +          <ng-pluralize count="personCount"                          when="{'0': 'Nobody is viewing.',                                 'one': '1 person is viewing.',                                 'other': '{} people are viewing.'}"> -          </ng:pluralize><br> +          </ng-pluralize><br>            <!--- Example with offset --->            With Offset(2): -          <ng:pluralize count="personCount" offset=2 +          <ng-pluralize count="personCount" offset=2                          when="{'0': 'Nobody is viewing.',                                 '1': '{{person1}} is viewing.',                                 '2': '{{person1}} and {{person2}} are viewing.',                                 'one': '{{person1}}, {{person2}} and one other person are viewing.',                                 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}"> -          </ng:pluralize> +          </ng-pluralize>          </div>        </doc:source>        <doc:scenario>          it('should show correct pluralized string', function() { -          expect(element('.doc-example-live .ng-pluralize:first').text()). +          expect(element('.doc-example-live ng-pluralize:first').text()).                                               toBe('1 person is viewing.'); -          expect(element('.doc-example-live .ng-pluralize:last').text()). +          expect(element('.doc-example-live ng-pluralize:last').text()).                                                  toBe('Igor is viewing.');            using('.doc-example-live').input('personCount').enter('0'); -          expect(element('.doc-example-live .ng-pluralize:first').text()). +          expect(element('.doc-example-live ng-pluralize:first').text()).                                                 toBe('Nobody is viewing.'); -          expect(element('.doc-example-live .ng-pluralize:last').text()). +          expect(element('.doc-example-live ng-pluralize:last').text()).                                                toBe('Nobody is viewing.');            using('.doc-example-live').input('personCount').enter('2'); -          expect(element('.doc-example-live .ng-pluralize:first').text()). +          expect(element('.doc-example-live ng-pluralize:first').text()).                                              toBe('2 people are viewing.'); -          expect(element('.doc-example-live .ng-pluralize:last').text()). +          expect(element('.doc-example-live ng-pluralize:last').text()).                                toBe('Igor and Misko are viewing.');            using('.doc-example-live').input('personCount').enter('3'); -          expect(element('.doc-example-live .ng-pluralize:first').text()). +          expect(element('.doc-example-live ng-pluralize:first').text()).                                              toBe('3 people are viewing.'); -          expect(element('.doc-example-live .ng-pluralize:last').text()). +          expect(element('.doc-example-live ng-pluralize:last').text()).                                toBe('Igor, Misko and one other person are viewing.');            using('.doc-example-live').input('personCount').enter('4'); -          expect(element('.doc-example-live .ng-pluralize:first').text()). +          expect(element('.doc-example-live ng-pluralize:first').text()).                                              toBe('4 people are viewing.'); -          expect(element('.doc-example-live .ng-pluralize:last').text()). +          expect(element('.doc-example-live ng-pluralize:last').text()).                                toBe('Igor, Misko and 2 other people are viewing.');          });          it('should show data-binded names', function() {            using('.doc-example-live').input('personCount').enter('4'); -          expect(element('.doc-example-live .ng-pluralize:last').text()). +          expect(element('.doc-example-live ng-pluralize:last').text()).                toBe('Igor, Misko and 2 other people are viewing.');            using('.doc-example-live').input('person1').enter('Di');            using('.doc-example-live').input('person2').enter('Vojta'); -          expect(element('.doc-example-live .ng-pluralize:last').text()). +          expect(element('.doc-example-live ng-pluralize:last').text()).                toBe('Di, Vojta and 2 other people are viewing.');          });        </doc:scenario>      </doc:example>   */ -angularWidget('ng:pluralize', function(element) { -  var numberExp = element.attr('count'), -      whenExp = element.attr('when'), -      offset = element.attr('offset') || 0; - -  return ['$locale', '$element', function($locale, element) { -    var scope = this, +var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interpolate) { +  var BRACE = /{}/g; +  return function(scope, element, attr) { +    var numberExp = attr.count, +        whenExp = attr.when, +        offset = attr.offset || 0,          whens = scope.$eval(whenExp),          whensExpFns = {};      forEach(whens, function(expression, key) { -      whensExpFns[key] = compileBindTemplate(expression.replace(/{}/g, -                                             '{{' + numberExp + '-' + offset + '}}')); +      whensExpFns[key] = +        $interpolate(expression.replace(BRACE, '{{' + numberExp + '-' + offset + '}}'));      });      scope.$watch(function() { @@ -806,5 +811,5 @@ angularWidget('ng:pluralize', function(element) {      }, function(newVal) {        element.text(newVal);      }); -  }]; -}); +  }; +}]; | 
