chore(compiler): change default restriction to attribute only for directives

This commit is contained in:
Misko Hevery 2012-03-07 22:47:01 -08:00
parent 6aa3cfc31b
commit 6a98c52c84
13 changed files with 302 additions and 234 deletions

View file

@ -17,13 +17,14 @@ list of some of the possible directive names: `ng:bind`, `ng-bind`, `ng_bind`, `
`data-ng-bind`.
The directives can be placed in element names, attributes, class names, as well as comments. Here
are some equivalent examples of invoking `ngBind`.
are some equivalent examples of invoking `myDir`. (However, most directives are restricted to
attribute only.)
<pre>
<span ng-bind="exp"></span>
<span class="ng-bind: exp;"></span>
<ng-bind></ng-bind>
<!-- directive: ng-bind exp --!>
<span my-dir="exp"></span>
<span class="my-dir: exp;"></span>
<my-dir></my-dir>
<!-- directive: my-dir exp -->
</pre>
Directives can be invoked in many different ways, but are equivalent in the end result as shown in
@ -37,13 +38,12 @@ the following example.
}
</script>
<div ng-controller="Ctrl1">
Hello <input ng-model='name'> <hr/>
Hello <input ng-model='name' ng-model-instant> <hr/>
&ltspan ng:bind="name"&gt <span ng:bind="name"></span> <br/>
&ltspan ng_bind="name"&gt <span ng_bind="name"></span> <br/>
&ltspan ng-bind="name"&gt <span ng-bind="name"></span> <br/>
&ltspan data-ng-bind="name"&gt <span data-ng-bind="name"></span> <br/>
&ltspan x-ng-bind="name"&gt <span x-ng-bind="name"></span> <br/>
&ltspan class="ng-bind: name;"&gt <span class="ng-bind: name;"></span> <br/>
</div>
</doc:source>
<doc:scenario>
@ -239,7 +239,7 @@ The full skeleton of the directive is shown here:
templateUrl: 'directive.html',
replace: false,
transclude: false,
restrict: 'EACM',
restrict: 'A',
scope: false,
local: {},
compile: function compile(tElement, tAttrs, transclude) {
@ -312,50 +312,49 @@ compiler}. The attributes are:
* `scope` - If set to:
* `true` - then a new scope will be created for this directive. It is an error to have two
directives on the same element both requesting new scope. The new scope rule does not apply
for the root of the template since the root of the template always gets a new scope.
* `true` - then a new scope will be created for this directive. If multiple directives on the
same element request new scope, only one new scope is created. The new scope rule does not
apply for the root of the template since the root of the template always gets a new scope.
* `{}` (object hash) - then a new 'isolate' scope is created. The 'isolate' scope differs from
normal scope that it does not prototypically inherit from the parent scope. This is useful
when creating reusable widgets, which should not accidentally read or modify data in parent
scope. <br/>
The 'isolate' scope takes an object hash which defines a set of local scope properties derived
from the parent scope. These local properties are usefull for aliasing values for
when creating reusable components, which should not accidentally read or modify data in
parent scope. <br/>
The 'isolate' scope takes an object hash which defines a set of local scope properties
derived from the parent scope. These local properties are useful for aliasing values for
templates. Locals definition is a hash of normalized element attribute name to their
coresponding binding strategy. Valid binding strategies are:
corresponding binding strategy. Valid binding strategies are:
* `attribute` - one time read of element attribute value and save it to widget scope. <br/>
Given `<widget my-attr='abc'>` and widget definition of `locals: {myAttr:'attribute'}`, then
widget scope property `myAttr` will be `"abc"`.
Given `<widget my-attr='abc'>` and widget definition of `locals: {myAttr:'attribute'}`,
then widget scope property `myAttr` will be `"abc"`.
* `evaluate` - one time evaluation of expression stored in the attribute. <br/>
Given `<widget my-attr='name'>` and widget definition of `locals: {myAttr:'evaluate'}`, and
* `evaluate` - one time evaluation of expression stored in the attribute. <br/> Given
`<widget my-attr='name'>` and widget definition of `locals: {myAttr:'evaluate'}`, and
parent scope `{name:'angular'}` then widget scope property `myAttr` will be `"angular"`.
* `bind` - Set up one way binding from the element attribute to the widget scope. <br/>
Given `<widget my-attr='{{name}}'>` and widget definition of `locals: {myAttr:'bind'}`, and
parent scope `{name:'angular'}` then widget scope property `myAttr` will be `"angular"`, but
any changes in the parent scope will be reflected in the widget scope.
Given `<widget my-attr='{{name}}'>` and widget definition of `locals: {myAttr:'bind'}`,
and parent scope `{name:'angular'}` then widget scope property `myAttr` will be
`"angular"`, but any changes in the parent scope will be reflected in the widget scope.
* `accessor` - Set up getter/setter function for the expression in the widget element attribute
to the widget scope. <br/>
Given `<widget my-attr='name'>` and widget definition of `locals: {myAttr:'prop'}`, and
parent scope `{name:'angular'}` then widget scope property `myAttr` will be a function such
that `myAttr()` will return `"angular"` and `myAttr('new value')` will update the parent
scope `name` property. This is usefull for treating the element as a data-model for
reading/writing.
* `accessor` - Set up getter/setter function for the expression in the widget element
attribute to the widget scope. <br/> Given `<widget my-attr='name'>` and widget definition
of `locals: {myAttr:'prop'}`, and parent scope `{name:'angular'}` then widget scope
property `myAttr` will be a function such that `myAttr()` will return `"angular"` and
`myAttr('new value')` will update the parent scope `name` property. This is useful for
treating the element as a data-model for reading/writing.
* `expression` - Treat element attribute as an expression to be exectude in form of an event.
* `expression` - Treat element attribute as an expression to be executed in form of an event.
<br/>
Given `<widget my-attr='doSomething()'>` and widget definition of
`locals: {myAttr:'expression'}`, and parent scope `{doSomething:function() {}}` then calling
the widget scope function `myAttr` will execute the expression against the parent scope.
Given `<widget my-attr='doSomething()'>` and widget definition of `locals:
{myAttr:'expression'}`, and parent scope `{doSomething:function() {}}` then calling the
widget scope function `myAttr` will execute the expression against the parent scope.
* `controller` - Controller constructor function. The controller is instantiated before the
pre-linking phase and it is shared with directives, if they request it by name. This allows the
directives to communicate with each other and augment each other behavior. The controller is
injectable with the following locals:
pre-linking phase and it is shared with other directives if they request it by name (see
`require` attribute). This allows the directives to communicate with each other and augment
each other behavior. The controller is injectable with the following locals:
* `$scope` - Current scope associated with the element
* `$element` - Current element
@ -363,8 +362,16 @@ compiler}. The attributes are:
* `$transclude` - A transclude linking function pre-bound to the correct transclusion scope:
`function(cloneLinkingFn)`.
* `require` - Require another controller be passed into current directive linking function. The
`require` takes a name of the directive controller to pass in. If no such controller can be
found an error is raised. The name can be prefixed with:
* `?` - Don't raise an error. This makes the require dependency optional.
* `^` - Look for the controller on parent elements as well.
* `inject` (object hash) - Specifies a way to inject bindings into a controller. Injection
definition is a hash of normalized element attribute name to their coresponding binding
definition is a hash of normalized element attribute name to their corresponding binding
strategy. Valid binding strategies are:
* `attribute` - inject attribute value. <br/>
@ -389,16 +396,8 @@ compiler}. The attributes are:
injecting `myAttr` will inject a function which when called will execute the expression
against the parent scope.
* `require` - Require the another controller be passed into current directive linking function.
The `require` takes a name of the directive controller to pass in. If no such controller
can be found an error is raised. The name can be prefixd with:
* `?` - Don't reaise an error. This makes the require dependency optional.
* `^` - Look for the controller on parent elements as well.
* `restrict` - String of subset of `EACM` which restricts the directive to a specific directive
declaration style.
declaration style. If omitted directives are allowed on attributes only.
* `E` - Element name: `<my-directive></my-directive>`
* `A` - Attribute: `<div my-directive="exp"></div>`
@ -534,8 +533,8 @@ function linkingFn(scope, elm, attrs, ctrl) {
# Understanding Transclusion and Scopes
It is often desirable to have reusable components, which we will refer to as widgets. Below is a
pseudo code showing how a simplified dialog widget may work.
It is often desirable to have reusable components. Below is a pseudo code showing how a simplified
dialog component may work.
<pre>
<div>
@ -570,7 +569,9 @@ This will not render properly, unless we do some scope magic.
The first issue we have to solve is that the dialog box template expect `title` to be defined, but
the place of instantiation would like to bind to `username`. Furthermore the buttons expect `onOk`
as well as `onCancel` functions to be present in the scope. This limits the usefulness of the
widget. To solve the mapping issue we use the `locals` to create local variables which the template expects as follows
widget. To solve the mapping issue we use the `locals` to create local variables which the
template expects as follows
<pre>
locals: {
title: 'bind', // set up title to accept data-binding
@ -606,8 +607,7 @@ Therefore the final directive definition looks something like this:
<pre>
transclude: true,
scope: 'isolate',
locals: {
scope: {
title: 'bind', // set up title to accept data-binding
onOk: 'exp', // create a delegate onOk function
onCancel: 'exp', // create a delegate onCancel function
@ -615,7 +615,7 @@ locals: {
}
</pre>
# Creating Widgets
# Creating Components
It is often desirable to replace a single directive with a more complex DOM structure. This
allows the directives to become a short hand for reusable components from which applications
@ -635,6 +635,7 @@ Following is an example of building a reusable widget.
angular.module('zippyModule', [])
.directive('zippy', function(){
return {
restrict: 'C',
// This HTML will replace the zippy directive.
replace: true,
transclude: true,

View file

@ -365,15 +365,50 @@ Doc.prototype = {
html_usage_directive: function(dom){
var self = this;
dom.h('Usage', function() {
dom.tag('pre', {'class':"brush: js; html-script: true;"}, function() {
dom.text('<' + self.element + ' ');
dom.text(self.shortName);
if (self.param.length) {
dom.text('="' + self.param[0].name + '"');
}
dom.text('>\n ...\n');
dom.text('</' + self.element + '>');
});
var restrict = self.restrict || 'AC';
if (restrict.match(/E/)) {
dom.text('as element');
dom.code(function() {
dom.text('<');
dom.text(self.shortName);
(self.param||[]).forEach(function(param){
dom.text('\n ');
dom.text(param.optional ? ' [' : ' ');
dom.text(param.name);
dom.text(BOOLEAN_ATTR[param.name] ? '' : '="..."');
dom.text(param.optional ? ']' : '');
});
dom.text('></');
dom.text(self.shortName);
dom.text('>');
});
}
if (restrict.match(/A/)) {
var element = self.element || 'ANY'
dom.text('as attribute');
dom.code(function() {
dom.text('<' + element + ' ');
dom.text(self.shortName);
if (self.param.length) {
dom.text('="' + self.param[0].name + '"');
}
dom.text('>\n ...\n');
dom.text('</' + element + '>');
});
}
if (restrict.match(/C/)) {
dom.text('as class');
var element = self.element || 'ANY'
dom.code(function() {
dom.text('<' + element + ' class="');
dom.text(self.shortName);
if (self.param.length) {
dom.text(': ' + self.param[0].name + ';');
}
dom.text('">\n ...\n');
dom.text('</' + element + '>');
});
}
self.html_usage_directiveInfo(dom);
self.html_usage_parameters(dom);
});
@ -427,46 +462,6 @@ Doc.prototype = {
});
},
html_usage_widget: function(dom){
var self = this;
dom.h('Usage', function() {
dom.h('In HTML Template Binding', function() {
dom.code(function() {
if (self.shortName.match(/^@/)) {
dom.text('<');
dom.text(self.element);
dom.text(' ');
dom.text(self.shortName.substring(1));
if (self.param.length) {
dom.text('="');
dom.text(self.param[0].name);
dom.text('"');
}
dom.text('>\n ...\n</');
dom.text(self.element);
dom.text('>');
} else {
dom.text('<');
dom.text(self.shortName);
(self.param||[]).forEach(function(param){
dom.text('\n ');
dom.text(param.optional ? ' [' : ' ');
dom.text(param.name);
dom.text(BOOLEAN_ATTR[param.name] ? '' : '="..."');
dom.text(param.optional ? ']' : '');
});
dom.text('></');
dom.text(self.shortName);
dom.text('>');
}
});
});
self.html_usage_directiveInfo(dom);
self.html_usage_parameters(dom);
});
},
html_usage_directiveInfo: function(dom) {
var self = this;
var list = [];

View file

@ -25,6 +25,7 @@ angular.module('ngdocs.directives', [], function($compileProvider) {
$compileProvider.directive('docExample', ['$injector', '$log', '$browser', '$location',
function($injector, $log, $browser, $location) {
return {
restrict: 'E',
terminal: true,
compile: function(element, attrs) {
var module = attrs.module;
@ -238,6 +239,7 @@ angular.module('ngdocs.directives', [], function($compileProvider) {
'</div>';
return {
restrict: 'EA',
compile: function(element, attrs) {
var tabs = angular.element(HTML_TPL.replace('{show}', attrs.show || 'false')),
nav = tabs.find('ul'),
@ -268,35 +270,38 @@ angular.module('ngdocs.directives', [], function($compileProvider) {
$compileProvider.directive('docTutorialNav', function() {
return function(scope, element, attrs) {
var prevStep, codeDiff, nextStep,
content, step = attrs.docTutorialNav;
return {
restrict: 'EA',
link:function(scope, element, attrs) {
var prevStep, codeDiff, nextStep,
content, step = attrs.docTutorialNav;
step = parseInt(step, 10);
step = parseInt(step, 10);
if (step === 0) {
prevStep = '';
nextStep = 'step_01';
codeDiff = 'step-0~7...step-0';
} else if (step === 11){
prevStep = 'step_10';
nextStep = 'the_end';
codeDiff = 'step-10...step-11';
} else {
prevStep = 'step_' + pad(step - 1);
nextStep = 'step_' + pad(step + 1);
codeDiff = 'step-' + step + '...step-' + step;
if (step === 0) {
prevStep = '';
nextStep = 'step_01';
codeDiff = 'step-0~7...step-0';
} else if (step === 11){
prevStep = 'step_10';
nextStep = 'the_end';
codeDiff = 'step-10...step-11';
} else {
prevStep = 'step_' + pad(step - 1);
nextStep = 'step_' + pad(step + 1);
codeDiff = 'step-' + step + '...step-' + step;
}
content = angular.element(
'<li><a href="#!/tutorial/' + prevStep + '">Previous</a></li>' +
'<li><a href="http://angular.github.com/angular-phonecat/step-' + step + '/app">Live Demo</a></li>' +
'<li><a href="https://github.com/angular/angular-phonecat/compare/' + codeDiff + '">Code Diff</a></li>' +
'<li><a href="#!/tutorial/' + nextStep + '">Next</a></li>'
);
element.attr('id', 'tutorial-nav');
element.append(content);
}
content = angular.element(
'<li><a href="#!/tutorial/' + prevStep + '">Previous</a></li>' +
'<li><a href="http://angular.github.com/angular-phonecat/step-' + step + '/app">Live Demo</a></li>' +
'<li><a href="https://github.com/angular/angular-phonecat/compare/' + codeDiff + '">Code Diff</a></li>' +
'<li><a href="#!/tutorial/' + nextStep + '">Next</a></li>'
);
element.attr('id', 'tutorial-nav');
element.append(content);
};
function pad(step) {

View file

@ -158,6 +158,6 @@ angular.module('ngdocs', ['ngdocs.directives'], function($locationProvider, $fil
});
$compileProvider.directive('code', function() {
return { terminal: true };
return { restrict: 'E', terminal: true };
});
});

View file

@ -60,16 +60,13 @@ function publishExternalAPI(angular){
angularModule('ng', ['ngLocale'], ['$provide',
function ngModule($provide) {
$provide.service('$anchorScroll', $AnchorScrollProvider);
$provide.service('$browser', $BrowserProvider);
$provide.service('$cacheFactory', $CacheFactoryProvider);
$provide.service('$compile', $CompileProvider).
directive({
a: htmlAnchorDirective,
input: inputDirective,
textarea: inputDirective,
form: ngFormDirective,
script: scriptTemplateLoader,
form: formDirective,
script: scriptDirective,
select: selectDirective,
style: styleDirective,
onload: onloadDirective,
@ -84,7 +81,7 @@ function publishExternalAPI(angular){
ngClassOdd: ngClassOddDirective,
ngCloak: ngCloakDirective,
ngController: ngControllerDirective,
ngForm: ngFormDirective,
ngForm: formDirective,
ngHide: ngHideDirective,
ngInclude: ngIncludeDirective,
ngInit: ngInitDirective,
@ -106,30 +103,36 @@ function publishExternalAPI(angular){
ngModelInstant: ngModelInstantDirective,
required: requiredDirective,
ngRequired: requiredDirective
}).
directive(ngEventDirectives).
directive(ngAttributeAliasDirectives);
$provide.service('$controller', $ControllerProvider);
$provide.service('$cookies', $CookiesProvider);
$provide.service('$cookieStore', $CookieStoreProvider);
$provide.service('$defer', $DeferProvider);
$provide.service('$document', $DocumentProvider);
$provide.service('$exceptionHandler', $ExceptionHandlerProvider);
$provide.service('$filter', $FilterProvider);
$provide.service('$interpolate', $InterpolateProvider);
$provide.service('$http', $HttpProvider);
$provide.service('$httpBackend', $HttpBackendProvider);
$provide.service('$location', $LocationProvider);
$provide.service('$log', $LogProvider);
$provide.service('$parse', $ParseProvider);
$provide.service('$resource', $ResourceProvider);
$provide.service('$route', $RouteProvider);
$provide.service('$routeParams', $RouteParamsProvider);
$provide.service('$rootScope', $RootScopeProvider);
$provide.service('$q', $QProvider);
$provide.service('$sanitize', $SanitizeProvider);
$provide.service('$sniffer', $SnifferProvider);
$provide.service('$templateCache', $TemplateCacheProvider);
$provide.service('$window', $WindowProvider);
}]);
}).
directive(ngAttributeAliasDirectives).
directive(ngEventDirectives);
$provide.service({
$anchorScroll: $AnchorScrollProvider,
$browser: $BrowserProvider,
$cacheFactory: $CacheFactoryProvider,
$controller: $ControllerProvider,
$cookies: $CookiesProvider,
$cookieStore: $CookieStoreProvider,
$defer: $DeferProvider,
$document: $DocumentProvider,
$exceptionHandler: $ExceptionHandlerProvider,
$filter: $FilterProvider,
$interpolate: $InterpolateProvider,
$http: $HttpProvider,
$httpBackend: $HttpBackendProvider,
$location: $LocationProvider,
$log: $LogProvider,
$parse: $ParseProvider,
$resource: $ResourceProvider,
$route: $RouteProvider,
$routeParams: $RouteParamsProvider,
$rootScope: $RootScopeProvider,
$q: $QProvider,
$sanitize: $SanitizeProvider,
$sniffer: $SnifferProvider,
$templateCache: $TemplateCacheProvider,
$window: $WindowProvider
});
}
]);
};

View file

@ -1,5 +1,15 @@
'use strict';
function ngDirective(directive) {
if (isFunction(directive)) {
directive = {
link: directive
}
}
directive.restrict = directive.restrict || 'AC';
return valueFn(directive);
};
/**
* @ngdoc directive
* @name angular.module.ng.$compileProvider.directive.ng:init
@ -26,7 +36,7 @@
</doc:scenario>
</doc:example>
*/
var ngInitDirective = valueFn({
var ngInitDirective = ngDirective({
compile: function() {
return {
pre: function(scope, element, attrs) {
@ -179,14 +189,14 @@ var ngControllerDirective = ['$controller', '$window', function($controller, $wi
</doc:scenario>
</doc:example>
*/
var ngBindDirective = valueFn(function(scope, element, attr) {
var ngBindDirective = ngDirective(function(scope, element, attr) {
element.addClass('ng-binding').data('$binding', attr.ngBind);
scope.$watch(attr.ngBind, function(value) {
element.text(value == undefined ? '' : value);
});
});
var ngBindHtmlUnsafeDirective = valueFn(function(scope, element, attr) {
var ngBindHtmlUnsafeDirective = ngDirective(function(scope, element, attr) {
element.addClass('ng-binding').data('$binding', attr.ngBindHtmlUnsafe);
scope.$watch(attr.ngBindHtmlUnsafe, function(value) {
element.html(value == undefined ? '' : value);
@ -572,7 +582,7 @@ forEach(
</doc:scenario>
</doc:example>
*/
var ngSubmitDirective = valueFn(function(scope, element, attrs) {
var ngSubmitDirective = ngDirective(function(scope, element, attrs) {
element.bind('submit', function() {
scope.$apply(attrs.ngSubmit);
});
@ -581,7 +591,7 @@ var ngSubmitDirective = valueFn(function(scope, element, attrs) {
function classDirective(name, selector) {
name = 'ngClass' + name;
return valueFn(function(scope, element, attr) {
return ngDirective(function(scope, element, attr) {
scope.$watch(attr[name], function(newVal, oldVal) {
if (selector === true || scope.$index % 2 === selector) {
if (oldVal && (newVal !== oldVal)) {
@ -754,7 +764,7 @@ var ngClassEvenDirective = classDirective('Even', 1);
</doc:example>
*/
//TODO(misko): refactor to remove element from the DOM
var ngShowDirective = valueFn(function(scope, element, attr){
var ngShowDirective = ngDirective(function(scope, element, attr){
scope.$watch(attr.ngShow, function(value){
element.css('display', toBoolean(value) ? '' : 'none');
});
@ -793,7 +803,7 @@ var ngShowDirective = valueFn(function(scope, element, attr){
</doc:example>
*/
//TODO(misko): refactor to remove element from the DOM
var ngHideDirective = valueFn(function(scope, element, attr){
var ngHideDirective = ngDirective(function(scope, element, attr){
scope.$watch(attr.ngHide, function(value){
element.css('display', toBoolean(value) ? 'none' : '');
});
@ -831,7 +841,7 @@ var ngHideDirective = valueFn(function(scope, element, attr){
</doc:scenario>
</doc:example>
*/
var ngStyleDirective = valueFn(function(scope, element, attr) {
var ngStyleDirective = ngDirective(function(scope, element, attr) {
scope.$watch(attr.ngStyle, function(newStyles, oldStyles) {
if (oldStyles && (newStyles !== oldStyles)) {
forEach(oldStyles, function(val, style) { element.css(style, '');});
@ -894,7 +904,7 @@ var ngStyleDirective = valueFn(function(scope, element, attr) {
</doc:example>
*
*/
var ngCloakDirective = valueFn({
var ngCloakDirective = ngDirective({
compile: function(element, attr) {
attr.$set(attr.$attr.ngCloak, undefined);
element.removeClass('ng-cloak');
@ -935,6 +945,7 @@ ngAttributeAliasDirective(null, 'src');
angular.module('transclude', [])
.directive('pane', function(){
return {
restrict: 'E',
transclude: true,
scope: 'isolate',
locals: { title:'bind' },
@ -962,7 +973,7 @@ ngAttributeAliasDirective(null, 'src');
</doc:example>
*
*/
var ngTranscludeDirective = valueFn({
var ngTranscludeDirective = ngDirective({
controller: ['$transclude', '$element', function($transclude, $element) {
$transclude(function(clone) {
$element.append(clone);

View file

@ -162,7 +162,7 @@ function $CompileProvider($provide) {
directive.priority = directive.priority || 0;
directive.name = directive.name || name;
directive.require = directive.require || (directive.controller && directive.name);
directive.restrict = directive.restrict || 'EACM';
directive.restrict = directive.restrict || 'A';
directives.push(directive);
} catch (e) {
$exceptionHandler(e);

View file

@ -117,7 +117,7 @@ FormController.prototype.registerWidget = function(widget, alias) {
/**
* @ngdoc widget
* @ngdoc directive
* @name angular.module.ng.$compileProvider.directive.form
*
* @scope
@ -204,7 +204,7 @@ FormController.prototype.registerWidget = function(widget, alias) {
</doc:scenario>
</doc:example>
*/
var ngFormDirective = [function() {
var formDirective = [function() {
return {
name: 'form',
restrict: 'E',

View file

@ -599,7 +599,7 @@ function checkboxInputType(scope, element, attr, ctrl) {
/**
* @ngdoc widget
* @ngdoc directive
* @name angular.module.ng.$compileProvider.directive.textarea
*
* @description
@ -623,7 +623,7 @@ function checkboxInputType(scope, element, attr, ctrl) {
/**
* @ngdoc widget
* @ngdoc directive
* @name angular.module.ng.$compileProvider.directive.input
*
* @description

View file

@ -1,7 +1,7 @@
'use strict';
/**
* @ngdoc widget
* @ngdoc directive
* @name angular.module.ng.$compileProvider.directive.select
*
* @description
@ -424,6 +424,7 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
var optionDirective = ['$interpolate', function($interpolate) {
return {
restrict: 'E',
priority: 100,
compile: function(element, attr) {
if (isUndefined(attr.value)) {

View file

@ -1,8 +1,9 @@
'use strict';
/**
* @ngdoc widget
* @ngdoc directive
* @name angular.module.ng.$compileProvider.directive.ng:include
* @restrict EA
*
* @description
* Fetches, compiles and includes an external HTML fragment.
@ -42,22 +43,22 @@
</select>
url of the template: <tt><a href="{{template.url}}">{{template.url}}</a></tt>
<hr/>
<div class="ng-include" src="template.url"></div>
<div ng-include src="template.url"></div>
</div>
</doc:source>
<doc:scenario>
it('should load template1.html', function() {
expect(element('.doc-example-live .ng-include').text()).
expect(element('.doc-example-live [ng-include]').text()).
toBe('Content of template1.html\n');
});
it('should load template2.html', function() {
select('template').option('1');
expect(element('.doc-example-live .ng-include').text()).
expect(element('.doc-example-live [ng-include]').text()).
toBe('Content of template2.html\n');
});
it('should change to blank', function() {
select('template').option('');
expect(element('.doc-example-live .ng-include').text()).toEqual('');
expect(element('.doc-example-live [ng-include]').text()).toEqual('');
});
</doc:scenario>
</doc:example>
@ -65,6 +66,7 @@
var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile',
function($http, $templateCache, $anchorScroll, $compile) {
return {
restrict: 'EA',
compile: function(element, attr) {
var srcExp = attr.src,
scopeExp = attr.scope || '',
@ -117,8 +119,9 @@ var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile'
}];
/**
* @ngdoc widget
* @ngdoc directive
* @name angular.module.ng.$compileProvider.directive.ng:switch
* @restrict EA
*
* @description
* Conditionally change the DOM structure.
@ -176,6 +179,7 @@ var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile'
*/
var NG_SWITCH = 'ng-switch';
var ngSwitchDirective = valueFn({
restrict: 'EA',
compile: function(element, attr) {
var watchExpr = attr.ngSwitch || attr.on,
cases = {};
@ -203,7 +207,7 @@ var ngSwitchDirective = valueFn({
}
});
var ngSwitchWhenDirective = valueFn({
var ngSwitchWhenDirective = ngDirective({
transclude: 'element',
priority: 500,
compile: function(element, attrs, transclude) {
@ -213,7 +217,7 @@ var ngSwitchWhenDirective = valueFn({
}
});
var ngSwitchDefaultDirective = valueFn({
var ngSwitchDefaultDirective = ngDirective({
transclude: 'element',
priority: 500,
compile: function(element, attrs, transclude) {
@ -312,7 +316,7 @@ var htmlAnchorDirective = valueFn({
</doc:scenario>
</doc:example>
*/
var ngRepeatDirective = valueFn({
var ngRepeatDirective = ngDirective({
transclude: 'element',
priority: 1000,
terminal: true,
@ -436,7 +440,7 @@ var ngRepeatDirective = valueFn({
/**
* @ngdoc widget
* @ngdoc directive
* @name angular.module.ng.$compileProvider.directive.ng:non-bindable
*
* @description
@ -466,12 +470,13 @@ var ngRepeatDirective = valueFn({
</doc:scenario>
</doc:example>
*/
var ngNonBindableDirective = valueFn({ terminal: true });
var ngNonBindableDirective = ngDirective({ terminal: true });
/**
* @ngdoc widget
* @ngdoc directive
* @name angular.module.ng.$compileProvider.directive.ng:view
* @restrict ECA
*
* @description
* # Overview
@ -560,6 +565,7 @@ var ngViewDirective = ['$http', '$templateCache', '$route', '$anchorScroll', '$c
function($http, $templateCache, $route, $anchorScroll, $compile,
$controller) {
return {
restrict: 'ECA',
terminal: true,
link: function(scope, element) {
var changeCounter = 0,
@ -619,8 +625,9 @@ var ngViewDirective = ['$http', '$templateCache', '$route', '$anchorScroll', '$c
/**
* @ngdoc widget
* @ngdoc directive
* @name angular.module.ng.$compileProvider.directive.ng:pluralize
* @restrict EA
*
* @description
* # Overview
@ -788,44 +795,49 @@ var ngViewDirective = ['$http', '$templateCache', '$route', '$anchorScroll', '$c
*/
var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interpolate) {
var BRACE = /{}/g;
return function(scope, element, attr) {
var numberExp = attr.count,
whenExp = element.attr(attr.$attr.when), // this is becaues we have {{}} in attrs
offset = attr.offset || 0,
whens = scope.$eval(whenExp),
whensExpFns = {};
return {
restrict: 'EA',
link: function(scope, element, attr) {
var numberExp = attr.count,
whenExp = element.attr(attr.$attr.when), // this is becaues we have {{}} in attrs
offset = attr.offset || 0,
whens = scope.$eval(whenExp),
whensExpFns = {};
forEach(whens, function(expression, key) {
whensExpFns[key] =
$interpolate(expression.replace(BRACE, '{{' + numberExp + '-' + offset + '}}'));
});
forEach(whens, function(expression, key) {
whensExpFns[key] =
$interpolate(expression.replace(BRACE, '{{' + numberExp + '-' + offset + '}}'));
});
scope.$watch(function() {
var value = parseFloat(scope.$eval(numberExp));
scope.$watch(function() {
var value = parseFloat(scope.$eval(numberExp));
if (!isNaN(value)) {
//if explicit number rule such as 1, 2, 3... is defined, just use it. Otherwise,
//check it against pluralization rules in $locale service
if (!whens[value]) value = $locale.pluralCat(value - offset);
return whensExpFns[value](scope, element, true);
} else {
return '';
}
}, function(newVal) {
element.text(newVal);
});
if (!isNaN(value)) {
//if explicit number rule such as 1, 2, 3... is defined, just use it. Otherwise,
//check it against pluralization rules in $locale service
if (!whens[value]) value = $locale.pluralCat(value - offset);
return whensExpFns[value](scope, element, true);
} else {
return '';
}
}, function(newVal) {
element.text(newVal);
});
}
};
}];
/**
* @ngdoc widget
* @ngdoc directive
* @name angular.module.ng.$compileProvider.directive.script
*
* @description
* Load content of a script tag, with type `text/ng-template`, into `$templateCache`, so that the
* template can be used by `ng:include`, `ng:view` or directive templates.
*
* @restrict E
*
* @example
<doc:example>
<doc:source>
@ -844,8 +856,9 @@ var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interp
</doc:scenario>
</doc:example>
*/
var scriptTemplateLoader = ['$templateCache', function($templateCache) {
var scriptDirective = ['$templateCache', function($templateCache) {
return {
restrict: 'E',
terminal: true,
compile: function(element, attr) {
if (attr.type == 'text/ng-template') {

View file

@ -8,6 +8,7 @@ describe('$compile', function() {
$compileProvider.directive('log', function(log) {
return {
restrict: 'CAM',
priority:0,
compile: valueFn(function(scope, element, attrs) {
log(attrs.log || 'LOG');
@ -16,19 +17,19 @@ describe('$compile', function() {
});
$compileProvider.directive('highLog', function(log) {
return { priority:3, compile: valueFn(function(scope, element, attrs) {
return { restrict: 'CAM', priority:3, compile: valueFn(function(scope, element, attrs) {
log(attrs.highLog || 'HIGH');
})};
});
$compileProvider.directive('mediumLog', function(log) {
return { priority:2, compile: valueFn(function(scope, element, attrs) {
return { restrict: 'CAM', priority:2, compile: valueFn(function(scope, element, attrs) {
log(attrs.mediumLog || 'MEDIUM');
})};
});
$compileProvider.directive('greet', function() {
return { priority:10, compile: valueFn(function(scope, element, attrs) {
return { restrict: 'CAM', priority:10, compile: valueFn(function(scope, element, attrs) {
element.text("Hello " + attrs.greet);
})};
});
@ -64,9 +65,12 @@ describe('$compile', function() {
it('should register a directive', function() {
module(function($compileProvider) {
$compileProvider.directive('div', function(log) {
return function(scope, element) {
log('OK');
element.text('SUCCESS');
return {
restrict: 'ECA',
link: function(scope, element) {
log('OK');
element.text('SUCCESS');
}
};
})
});
@ -80,10 +84,16 @@ describe('$compile', function() {
it('should allow registration of multiple directives with same name', function() {
module(function($compileProvider) {
$compileProvider.directive('div', function(log) {
return log.fn('1');
return {
restrict: 'ECA',
link: log.fn('1')
};
});
$compileProvider.directive('div', function(log) {
return log.fn('2');
return {
restrict: 'ECA',
link: log.fn('2')
};
});
});
inject(function($compile, $rootScope, log) {
@ -142,6 +152,7 @@ describe('$compile', function() {
$compileProvider.directive('log', function($injector, $rootScope) {
injector = $injector;
return {
restrict: 'CA',
compile: function(element, templateAttr) {
expect(typeof templateAttr.$normalize).toBe('function');
expect(typeof templateAttr.$set).toBe('function');
@ -318,6 +329,7 @@ describe('$compile', function() {
beforeEach(module(function($compileProvider) {
$compileProvider.directive('replace', valueFn({
restrict: 'CAM',
replace: true,
template: '<div class="log" style="width: 10px" high-log>Hello: <<CONTENT>></div>',
compile: function(element, attr) {
@ -326,6 +338,7 @@ describe('$compile', function() {
}
}));
$compileProvider.directive('append', valueFn({
restrict: 'CAM',
template: '<div class="log" style="width: 10px" high-log>Hello: <<CONTENT>></div>',
compile: function(element, attr) {
attr.$set('compiled', 'COMPILED');
@ -415,16 +428,18 @@ describe('$compile', function() {
beforeEach(module(
function($compileProvider) {
$compileProvider.directive('hello', valueFn({ templateUrl: 'hello.html' }));
$compileProvider.directive('cau', valueFn({ templateUrl:'cau.html' }));
$compileProvider.directive('hello', valueFn({ restrict: 'CAM', templateUrl: 'hello.html' }));
$compileProvider.directive('cau', valueFn({ restrict: 'CAM', templateUrl:'cau.html' }));
$compileProvider.directive('cError', valueFn({
restrict: 'CAM',
templateUrl:'error.html',
compile: function() {
throw Error('cError');
}
}));
$compileProvider.directive('lError', valueFn({
restrict: 'CAM',
templateUrl: 'error.html',
compile: function() {
throw Error('lError');
@ -433,15 +448,18 @@ describe('$compile', function() {
$compileProvider.directive('iHello', valueFn({
restrict: 'CAM',
replace: true,
templateUrl: 'hello.html'
}));
$compileProvider.directive('iCau', valueFn({
restrict: 'CAM',
replace: true,
templateUrl:'cau.html'
}));
$compileProvider.directive('iCError', valueFn({
restrict: 'CAM',
replace: true,
templateUrl:'error.html',
compile: function() {
@ -449,6 +467,7 @@ describe('$compile', function() {
}
}));
$compileProvider.directive('iLError', valueFn({
restrict: 'CAM',
replace: true,
templateUrl: 'error.html',
compile: function() {
@ -685,9 +704,11 @@ describe('$compile', function() {
it('should prevent multiple templates per element', function() {
module(function($compileProvider) {
$compileProvider.directive('sync', valueFn({
restrict: 'C',
template: '<span></span>'
}));
$compileProvider.directive('async', valueFn({
restrict: 'C',
templateUrl: 'template.html'
}));
});
@ -876,6 +897,7 @@ describe('$compile', function() {
$compileProvider.directive('scope' + uppercase(name), function(log) {
return {
scope: true,
restrict: 'CA',
compile: function() {
return function (scope, element) {
log(scope.$id);
@ -887,6 +909,7 @@ describe('$compile', function() {
$compileProvider.directive('iscope' + uppercase(name), function(log) {
return {
scope: {},
restrict: 'CA',
compile: function() {
return function (scope, element) {
iscope = scope;
@ -899,6 +922,7 @@ describe('$compile', function() {
$compileProvider.directive('tiscope' + uppercase(name), function(log) {
return {
scope: {},
restrict: 'CA',
templateUrl: 'tiscope.html',
compile: function() {
return function (scope, element) {
@ -911,8 +935,11 @@ describe('$compile', function() {
});
});
$compileProvider.directive('log', function(log) {
return function(scope) {
log('log-' + scope.$id + '-' + scope.$parent.$id);
return {
restrict: 'CA',
link: function(scope) {
log('log-' + scope.$id + '-' + scope.$parent.$id);
}
};
});
}));
@ -1098,6 +1125,7 @@ describe('$compile', function() {
forEach(['a', 'b', 'c'], function(name) {
$compileProvider.directive(name, function(log) {
return {
restrict: 'ECA',
compile: function() {
log('t' + uppercase(name))
return {
@ -1174,8 +1202,11 @@ describe('$compile', function() {
it('should read boolean attributes as boolean', function() {
module(function($compileProvider) {
$compileProvider.directive({
div: valueFn(function(scope, element, attr) {
element.text(attr.required);
div: valueFn({
restrict: 'ECA',
link:function(scope, element, attr) {
element.text(attr.required);
}
})
});
});
@ -1207,8 +1238,11 @@ describe('$compile', function() {
it('should read boolean attributes as boolean', function() {
module(function($compileProvider) {
$compileProvider.directive({
div: valueFn(function(scope, element, attr) {
element.text(attr.required);
div: valueFn({
restrict: 'ECA',
link: function(scope, element, attr) {
element.text(attr.required);
}
})
});
});
@ -1274,8 +1308,11 @@ describe('$compile', function() {
var attr;
beforeEach(function(){
module(function($compileProvider) {
$compileProvider.directive('div', valueFn(function(scope, element, attr){
scope.attr = attr;
$compileProvider.directive('div', valueFn({
restrict: 'ECA',
link: function(scope, element, attr) {
scope.attr = attr;
}
}));
});
inject(function($compile, $rootScope) {
@ -1629,9 +1666,11 @@ describe('$compile', function() {
module(function($compileProvider) {
$compileProvider.directive('first', valueFn({
scope: {},
restrict: 'CA',
transclude: 'content'
}));
$compileProvider.directive('second', valueFn({
restrict: 'CA',
transclude: 'content'
}));
});

View file

@ -1186,7 +1186,7 @@ describe('widget', function() {
});
describe('scriptTemplateLoader', function() {
describe('scriptDirective', function() {
it('should populate $templateCache with contents of a ng-template script element', inject(
function($compile, $templateCache) {
if (msie <=8) return;