aboutsummaryrefslogtreecommitdiffstats
path: root/src/widgets.js
diff options
context:
space:
mode:
authorMisko Hevery2011-11-22 21:28:39 -0800
committerMisko Hevery2012-01-25 11:50:37 -0800
commit9ee2cdff44e7d496774b340de816344126c457b3 (patch)
tree476ffcb4425e7160865029d6b57d41b766750285 /src/widgets.js
parent8af4fde18246ac1587b471a549e70d5d858bf0ee (diff)
downloadangular.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.js591
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);
});
- }];
-});
+ };
+}];