From 9210cdc4b83e6c1640d34d7946fe5f33479fa9c7 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Wed, 7 Mar 2012 22:47:01 -0800 Subject: [PATCH] chore(compiler): change default restriction to attribute only for directives --- ...module.ng.$compileProvider.directive.ngdoc | 111 +++++++++--------- docs/src/ngdoc.js | 93 +++++++-------- docs/src/templates/doc_widgets.js | 59 +++++----- docs/src/templates/docs.js | 2 +- src/AngularPublic.js | 67 ++++++----- src/directives.js | 31 +++-- src/service/compiler.js | 2 +- src/widget/form.js | 4 +- src/widget/input.js | 4 +- src/widget/select.js | 3 +- src/widgets.js | 95 ++++++++------- test/service/compilerSpec.js | 75 +++++++++--- test/widgetsSpec.js | 2 +- 13 files changed, 308 insertions(+), 240 deletions(-) diff --git a/docs/content/api/angular.module.ng.$compileProvider.directive.ngdoc b/docs/content/api/angular.module.ng.$compileProvider.directive.ngdoc index 40f272c061e7..356414fbd90e 100644 --- a/docs/content/api/angular.module.ng.$compileProvider.directive.ngdoc +++ b/docs/content/api/angular.module.ng.$compileProvider.directive.ngdoc @@ -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.)
-  
-  
-  
-  
+  
+  
+  
+  
 
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. }
- Hello
+ Hello
<span ng:bind="name">
<span ng_bind="name">
<span ng-bind="name">
<span data-ng-bind="name">
<span x-ng-bind="name">
- <span class="ng-bind: name;">
@@ -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.
- 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.
+ 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.
- Given `` and widget definition of `locals: {myAttr:'attribute'}`, then - widget scope property `myAttr` will be `"abc"`. + Given `` 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.
- Given `` and widget definition of `locals: {myAttr:'evaluate'}`, and + * `evaluate` - one time evaluation of expression stored in the attribute.
Given + `` 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.
- Given `` 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.
- Given `` 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. - - * `expression` - Treat element attribute as an expression to be exectude in form of an event. + Given `` 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.
Given `` 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 executed in form of an event.
- Given `` 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 `` 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.
@@ -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: `` * `A` - Attribute: `
` @@ -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.
   
@@ -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 +
   locals: {
     title: 'bind',   // set up title to accept data-binding
@@ -606,8 +607,7 @@ Therefore the final directive definition looks something like this:
 
 
 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: {
 }
 
-# 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, diff --git a/docs/src/ngdoc.js b/docs/src/ngdoc.js index fc606d54d128..3a872af8e5a1 100644 --- a/docs/src/ngdoc.js +++ b/docs/src/ngdoc.js @@ -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(''); - }); + var restrict = self.restrict || 'A'; + 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('>'); + }); + } + 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(''); + }); + } + 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(''); + }); + } 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'); - } 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('>'); - } - }); - }); - - self.html_usage_directiveInfo(dom); - self.html_usage_parameters(dom); - }); - }, - html_usage_directiveInfo: function(dom) { var self = this; var list = []; diff --git a/docs/src/templates/doc_widgets.js b/docs/src/templates/doc_widgets.js index 5d4f89052c11..acbdae4f6d6d 100644 --- a/docs/src/templates/doc_widgets.js +++ b/docs/src/templates/doc_widgets.js @@ -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) { '
'; 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; - - 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; - } + return { + restrict: 'EA', + link:function(scope, element, attrs) { + var prevStep, codeDiff, nextStep, + content, step = attrs.docTutorialNav; + + 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; + } - content = angular.element( - '
  • Previous
  • ' + - '
  • Live Demo
  • ' + - '
  • Code Diff
  • ' + - '
  • Next
  • ' - ); + content = angular.element( + '
  • Previous
  • ' + + '
  • Live Demo
  • ' + + '
  • Code Diff
  • ' + + '
  • Next
  • ' + ); - element.attr('id', 'tutorial-nav'); - element.append(content); + element.attr('id', 'tutorial-nav'); + element.append(content); + } }; function pad(step) { diff --git a/docs/src/templates/docs.js b/docs/src/templates/docs.js index b9fc0407a59f..95cac2c92c1b 100644 --- a/docs/src/templates/docs.js +++ b/docs/src/templates/docs.js @@ -158,6 +158,6 @@ angular.module('ngdocs', ['ngdocs.directives'], function($locationProvider, $fil }); $compileProvider.directive('code', function() { - return { terminal: true }; + return { restrict: 'E', terminal: true }; }); }); diff --git a/src/AngularPublic.js b/src/AngularPublic.js index 9d31601fa527..8ee8bc064ba9 100644 --- a/src/AngularPublic.js +++ b/src/AngularPublic.js @@ -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 + }); + } + ]); }; diff --git a/src/directives.js b/src/directives.js index c988bf7bd923..1fc7f1b55356 100644 --- a/src/directives.js +++ b/src/directives.js @@ -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 @@ */ -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 */ -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( */ -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); */ //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){ */ //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){ */ -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) { * */ -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'); * */ -var ngTranscludeDirective = valueFn({ +var ngTranscludeDirective = ngDirective({ controller: ['$transclude', '$element', function($transclude, $element) { $transclude(function(clone) { $element.append(clone); diff --git a/src/service/compiler.js b/src/service/compiler.js index 43db1e9b4e89..42c694fff400 100644 --- a/src/service/compiler.js +++ b/src/service/compiler.js @@ -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); diff --git a/src/widget/form.js b/src/widget/form.js index 24f4ff424fae..e3823f418ecc 100644 --- a/src/widget/form.js +++ b/src/widget/form.js @@ -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) { */ -var ngFormDirective = [function() { +var formDirective = [function() { return { name: 'form', restrict: 'E', diff --git a/src/widget/input.js b/src/widget/input.js index ecdb9d237b2c..45d7d6a0fc25 100644 --- a/src/widget/input.js +++ b/src/widget/input.js @@ -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 diff --git a/src/widget/select.js b/src/widget/select.js index f3c0f3dd17c7..5ed1367f06ee 100644 --- a/src/widget/select.js +++ b/src/widget/select.js @@ -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)) { diff --git a/src/widgets.js b/src/widgets.js index 8bfd232dcd5d..4ea0553b39be 100644 --- a/src/widgets.js +++ b/src/widgets.js @@ -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 @@ url of the template: {{template.url}}
    -
    +
    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(''); }); @@ -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({ */ -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({ */ -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 = {}; - - forEach(whens, function(expression, key) { - whensExpFns[key] = - $interpolate(expression.replace(BRACE, '{{' + numberExp + '-' + offset + '}}')); - }); - - 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); - }); + 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 + '}}')); + }); + + 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); + }); + } }; }]; /** - * @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 @@ -844,8 +856,9 @@ var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interp */ -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') { diff --git a/test/service/compilerSpec.js b/test/service/compilerSpec.js index 9af11f33180d..2a293acf2203 100644 --- a/test/service/compilerSpec.js +++ b/test/service/compilerSpec.js @@ -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: '
    Hello: <>
    ', compile: function(element, attr) { @@ -326,6 +338,7 @@ describe('$compile', function() { } })); $compileProvider.directive('append', valueFn({ + restrict: 'CAM', template: '
    Hello: <>
    ', 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: '' })); $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' })); }); diff --git a/test/widgetsSpec.js b/test/widgetsSpec.js index 3d2f0a5668ce..8273f83812c0 100644 --- a/test/widgetsSpec.js +++ b/test/widgetsSpec.js @@ -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;