Skip to content

Commit

Permalink
chore(compiler): change default restriction to attribute only for dir…
Browse files Browse the repository at this point in the history
…ectives
  • Loading branch information
mhevery committed Mar 8, 2012
1 parent 6aa3cfc commit 6a98c52
Show file tree
Hide file tree
Showing 13 changed files with 308 additions and 240 deletions.
111 changes: 56 additions & 55 deletions docs/content/api/angular.module.ng.$compileProvider.directive.ngdoc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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>
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -312,59 +312,66 @@ 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.

* `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.

* `expression` - Treat element attribute as an expression to be exectude in form of an event.
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 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.
<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
* `$attrs` - Current attributes obeject for the element
* `$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/>
Expand All @@ -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>`
Expand Down Expand Up @@ -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>
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -606,16 +607,15 @@ 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
show: 'prop' // create a getter/setter function for visibility.
}
</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
Expand All @@ -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,
Expand Down
93 changes: 44 additions & 49 deletions docs/src/ngdoc.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
Expand Down Expand Up @@ -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 = [];
Expand Down
59 changes: 32 additions & 27 deletions docs/src/templates/doc_widgets.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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'),
Expand Down Expand Up @@ -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(
'<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>'
);
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);
element.attr('id', 'tutorial-nav');
element.append(content);
}
};

function pad(step) {
Expand Down
Loading

0 comments on commit 6a98c52

Please sign in to comment.