From 9ee2cdff44e7d496774b340de816344126c457b3 Mon Sep 17 00:00:00 2001
From: Misko Hevery
Date: Tue, 22 Nov 2011 21:28:39 -0800
Subject: refactor(directives): connect new compiler
- turn everything into a directive
---
 src/widgets.js | 591 +++++++++++++++++++++++++++++----------------------------
 1 file changed, 298 insertions(+), 293 deletions(-)
(limited to 'src/widgets.js')
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 @@
          
          url of the template: {{template.url}}
          
-         
+         
        
       
       
@@ -87,64 +87,62 @@
       
     
  */
-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){
       
     
  */
-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.:
  * Save
  */
-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 link 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() {
       
     
  */
-angularWidget('@ng:repeat', function(expression, element){
-  element.removeAttr('ng:repeat');
-  element.replaceWith(jqLite(''));
-  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(''));
+      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){
       
     
  */
-angularWidget("@ng:non-bindable", noop);
+var ngNonBindableDirective = valueFn({ terminal: true });
 
 
 /**
@@ -564,49 +571,48 @@ angularWidget("@ng:non-bindable", noop);
       
     
  */
-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) {
 
           
           Without Offset:
-          
-          
+          
 
           
           With Offset(2):
-          
-          
+          
         
       
       
         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.');
         });
       
     
  */
-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);
     });
-  }];
-});
+  };
+}];
-- 
cgit v1.2.3