2011-12-14 01:55:31 +00:00
|
|
|
@ngdoc overview
|
|
|
|
|
@name angular.module.ng.$compileProvider.directive
|
|
|
|
|
@description
|
|
|
|
|
|
|
|
|
|
Directives are a way to teach HTML new tricks. During DOM compilation directives are matched
|
|
|
|
|
against the HTML and executed. This allows directives to register behavior, or transform the DOM.
|
|
|
|
|
|
|
|
|
|
Angular comes with a built in set of directives which are useful for building web applications but
|
|
|
|
|
can be extended such that HTML can be turned into a declarative domain specific language (DSL).
|
|
|
|
|
|
|
|
|
|
# Invoking directives from HTML
|
|
|
|
|
|
|
|
|
|
Directives have camel cased names such as 'ngBind'. The directive can be invoked by translating
|
|
|
|
|
the camel case name into snake case with these special characters `:`, `-`, or `_`. Optionally the
|
|
|
|
|
directive can be prefixed with `x-`, or `data-` to make it HTML validator compliant. Here is a
|
|
|
|
|
list of some of the possible directive names: `ng:bind`, `ng-bind`, `ng_bind`, `x-ng-bind` and
|
|
|
|
|
`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`.
|
|
|
|
|
|
|
|
|
|
<pre>
|
|
|
|
|
<span ng-bind="exp"></span>
|
|
|
|
|
<span class="ng-bind: exp;"></span>
|
|
|
|
|
<ng-bind></ng-bind>
|
|
|
|
|
<!-- directive: ng-bind exp --!>
|
|
|
|
|
</pre>
|
|
|
|
|
|
|
|
|
|
Directives can be invoked in many different ways, but are equivalent in the end result as shown in
|
|
|
|
|
the following example.
|
|
|
|
|
|
|
|
|
|
<doc:example>
|
|
|
|
|
<doc:source >
|
|
|
|
|
<script>
|
|
|
|
|
function Ctrl1($scope) {
|
|
|
|
|
$scope.name = 'angular';
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
<div ng-controller="Ctrl1">
|
|
|
|
|
Hello <input ng-model='name'> <hr/>
|
|
|
|
|
<span ng:bind="name"> <span ng:bind="name"></span> <br/>
|
|
|
|
|
<span ng_bind="name"> <span ng_bind="name"></span> <br/>
|
|
|
|
|
<span ng-bind="name"> <span ng-bind="name"></span> <br/>
|
|
|
|
|
<span data-ng-bind="name"> <span data-ng-bind="name"></span> <br/>
|
|
|
|
|
<span x-ng-bind="name"> <span x-ng-bind="name"></span> <br/>
|
|
|
|
|
<span class="ng-bind: name;"> <span class="ng-bind: name;"></span> <br/>
|
|
|
|
|
</div>
|
|
|
|
|
</doc:source>
|
|
|
|
|
<doc:scenario>
|
|
|
|
|
it('should load template1.html', function() {
|
|
|
|
|
expect(element('div[ng-controller="Ctrl1"] span[ng-bind]').text()).toBe('angular');
|
|
|
|
|
});
|
|
|
|
|
</doc:scenario>
|
|
|
|
|
</doc:example>
|
|
|
|
|
|
|
|
|
|
# String interpolation
|
|
|
|
|
|
|
|
|
|
During the compilation process the {@link angular.module.ng.$compile compiler} matches text and
|
|
|
|
|
attributes using the {@link angular.module.ng.$interpolate $interpolate} service to see if they
|
|
|
|
|
contain embedded expressions. These expressions are registered as {@link
|
|
|
|
|
angular.module.ng.$rootScope.Scope#.watch watches} and will update as part of normal {@link
|
|
|
|
|
angular.module.ng.$rootScope.Scope#.digest digest} cycle. An example of interpolation is shown
|
|
|
|
|
here:
|
|
|
|
|
|
|
|
|
|
<pre>
|
|
|
|
|
<img src="img/{{username}}.jpg">Hello {{username}}!</img>
|
|
|
|
|
</pre>
|
|
|
|
|
|
|
|
|
|
# Compilation process, and directive matching
|
|
|
|
|
|
|
|
|
|
Compilation of HTML happens in three phases:
|
|
|
|
|
|
|
|
|
|
1. First the HTML is parsed into DOM using the standard browser API. This is important to
|
|
|
|
|
realize because the templates must be parsable HTML. This is in contrast to most templating
|
|
|
|
|
systems that operate on strings, rather then on DOM elements.
|
|
|
|
|
|
|
|
|
|
2. The compilation of the DOM is performed by the call to {@link angular.module.ng.$compile
|
|
|
|
|
$compile()} method. The method traverses the DOM and matches the directives. If a match is found
|
|
|
|
|
it is added to the list of directives associated with the given DOM element. Once all directives
|
|
|
|
|
for a given DOM element have been identified they are sorted by priority and their `compile()`
|
|
|
|
|
functions are executed. The directive compile function has a chance to modify the DOM structure
|
|
|
|
|
and is responsible for producing a `link()` function explained next. The {@link
|
|
|
|
|
angular.module.ng.$compile $compile()} method returns a combined linking function, which is a
|
|
|
|
|
collection of all of the linking functions returned from the individual directive compile
|
|
|
|
|
functions.
|
|
|
|
|
|
|
|
|
|
3. Link the template with scope by calling the liking function returned from the previous step.
|
|
|
|
|
This in turn will call the linking function of the individual directives allowing them to
|
|
|
|
|
register any listeners on the elements and set up any {@link
|
|
|
|
|
angular.module.ng.$rootScope.Scope#.watch watches} with the {@link
|
|
|
|
|
angular.module.ng.$rootScope.Scope scope}. The result of this is a live binding between the
|
|
|
|
|
scope and the DOM. A change in the scope is reflected in the DOM.
|
|
|
|
|
|
|
|
|
|
<pre>
|
|
|
|
|
var $compile = ...; // injected into your code
|
|
|
|
|
var scope = ...;
|
|
|
|
|
|
|
|
|
|
var html = '<div ng-bind='exp'></div>';
|
|
|
|
|
|
|
|
|
|
// Step 1: parse HTML into DOM element
|
|
|
|
|
var template = angular.element(html);
|
|
|
|
|
|
|
|
|
|
// Step 2: compile the template
|
|
|
|
|
var linkFn = $compile(template);
|
|
|
|
|
|
|
|
|
|
// Step 3: link the compiled template with the scope.
|
|
|
|
|
linkFn(scope);
|
|
|
|
|
</pre>
|
|
|
|
|
|
|
|
|
|
## Reasons behind the compile/link separation
|
|
|
|
|
|
|
|
|
|
At this point you may wonder why is the compile process broken down to a compile and link phase.
|
|
|
|
|
To understand this, lets look at a real world example with repeater:
|
|
|
|
|
|
|
|
|
|
<pre>
|
|
|
|
|
Hello {{user}}, you have these actions:
|
|
|
|
|
<ul>
|
|
|
|
|
<li ng:repeat="action in user.actions">
|
|
|
|
|
{{action.description}}
|
|
|
|
|
</li>
|
|
|
|
|
</ul>
|
|
|
|
|
</pre>
|
|
|
|
|
|
|
|
|
|
The short answer is that compile and link separation is needed any time a change in model causes
|
|
|
|
|
a change in DOM structure such as in repeaters.
|
|
|
|
|
|
|
|
|
|
When the above example is compiled, the compiler visits every node and looks for directives. The
|
|
|
|
|
`{{user}}` is an example of {@link angular.module.ng.$interpolate interpolation} directive. {@link
|
|
|
|
|
angular.module.ng.$compileProvider.directive.ng:repeat ng:repeat} is another directive. But {@link
|
|
|
|
|
angular.module.ng.$compileProvider.directive.ng:repeat ng:repeat} has a dilemma. It needs to be
|
|
|
|
|
able to quickly stamp out new `li`s for every `action` in `user.actions`. This means that it needs
|
|
|
|
|
to save a clean copy of the `li` element for cloning purposes and as new `action`s are inserted,
|
|
|
|
|
the template `li` element needs to be cloned and inserted into `ul`. But cloning the `li` element
|
|
|
|
|
is not enough. It also needs to compile the `li` so that its directives such as
|
|
|
|
|
`{{action.descriptions}}` evaluate against the right {@link angular.module.ng.$rootScope.Scope
|
|
|
|
|
scope}. A naive method would be to simply insert a copy of the `li` elemnt and then compile it.
|
|
|
|
|
But compiling on every `li` element clone would be slow, since the compilation requires that we
|
|
|
|
|
traverse the DOM tree and look for directives and execute them. If we put the compilation inside a
|
|
|
|
|
repeater which needs to unroll 100 items we would quickly run into performance problem.
|
|
|
|
|
|
|
|
|
|
The solution is to break the compilation process into two phases the compile phase where all of
|
|
|
|
|
the directives are identified and sorted by priority, and a linking phase where any work which
|
|
|
|
|
links a specific instance of the {@link angular.module.ng.$rootScope.Scope scope} and the specific
|
|
|
|
|
instance of an `li` is performed.
|
|
|
|
|
|
|
|
|
|
{@link angular.module.ng.$compileProvider.directive.ng:repeat ng:repeat} works by preventing the
|
|
|
|
|
compilation process form descending into `li` element. Instead the {@link
|
|
|
|
|
angular.module.ng.$compileProvider.directive.ng:repeat ng:repeat} directive compiles `li`
|
|
|
|
|
seperatly. The result of of the `li` element compilation is a linking function which contains all
|
|
|
|
|
of the directives contained in the `li` element ready to be attached to a specific clone of `li`
|
|
|
|
|
element. At runtime the {@link angular.module.ng.$compileProvider.directive.ng:repeat ng:repeat}
|
|
|
|
|
watches the expression and as items are added to the array it clones the `li` element, creates a
|
|
|
|
|
new {@link angular.module.ng.$rootScope.Scope scope} for the cloned `li` element and calls the
|
|
|
|
|
link function on the cloned `li`.
|
|
|
|
|
|
|
|
|
|
Summary:
|
|
|
|
|
|
|
|
|
|
* *compile function* - The compile function is relatively rare in directives, since most
|
|
|
|
|
directives are concerned with working with a specific DOM element instance rather then
|
|
|
|
|
transforming the template DOM element. Any operation which can be shared among the instance of
|
|
|
|
|
directives should be moved to the compile function for performance reasons.
|
|
|
|
|
|
|
|
|
|
* *link function* - It is rare for the directive not to have a link function. Link function
|
|
|
|
|
allows the directive to register listeners to the specific cloned DOM element instance as well
|
|
|
|
|
as to copy content into the DOM from the scope.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Writing directives (short version)
|
|
|
|
|
|
|
|
|
|
In this example we will build a directive which displays the current time.
|
|
|
|
|
|
|
|
|
|
<doc:example module="time">
|
|
|
|
|
<doc:source>
|
|
|
|
|
<script>
|
|
|
|
|
function Ctrl2($scope) {
|
|
|
|
|
$scope.format = 'M/d/yy h:mm:ss a';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
angular.module('time', [], function($compileProvider) {
|
|
|
|
|
// Register the 'myCurrentTime' directive factory method.
|
|
|
|
|
// We inject $defer and dateFilter service since the factory method is DI.
|
|
|
|
|
$compileProvider.directive('myCurrentTime', function($defer, dateFilter) {
|
|
|
|
|
// return the directive link function. (compile function not needed)
|
|
|
|
|
return function(scope, element, attrs) {
|
|
|
|
|
var format, // date format
|
|
|
|
|
deferId; // deferId, so that we can cancel the time updates
|
|
|
|
|
|
|
|
|
|
// used to update the UI
|
|
|
|
|
function updateTime() {
|
|
|
|
|
element.text(dateFilter(new Date(), format));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// watch the expression, and update the UI on change.
|
|
|
|
|
scope.$watch(attrs.myCurrentTime, function(value) {
|
|
|
|
|
format = value;
|
|
|
|
|
updateTime();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// schedule update in one second
|
|
|
|
|
function updateLater() {
|
|
|
|
|
// save the deferId for canceling
|
|
|
|
|
deferId = $defer(function() {
|
|
|
|
|
updateTime(); // update DOM
|
|
|
|
|
updateLater(); // schedule another update
|
|
|
|
|
}, 1000);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// listen on DOM destroy (removal) event, and cancel the next UI update
|
|
|
|
|
// to prevent updating time ofter the DOM element was removed.
|
|
|
|
|
element.bind('$destroy', function() {
|
|
|
|
|
$defer.cancel(deferId);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
updateLater(); // kick of the UI update process.
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
</script>
|
|
|
|
|
<div ng-controller="Ctrl2">
|
|
|
|
|
Date format: <input ng-model='format'> <hr/>
|
|
|
|
|
Current time is: <span my-current-time="format"></span
|
|
|
|
|
</div>
|
|
|
|
|
</doc:source>
|
|
|
|
|
<doc:scenario>
|
|
|
|
|
</doc:scenario>
|
|
|
|
|
</doc:example>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Writing directives (long version)
|
|
|
|
|
|
|
|
|
|
The full skeleton of the directive is shown here:
|
|
|
|
|
|
|
|
|
|
<pre>
|
|
|
|
|
var $compileProvider = ...;
|
|
|
|
|
|
|
|
|
|
$compileProvider.directive('directiveName', function factory(injectables) {
|
|
|
|
|
var directiveDefinitionObject = {
|
|
|
|
|
priority: 0,
|
|
|
|
|
template: '<div></div>',
|
|
|
|
|
templateUrl: 'directive.html',
|
|
|
|
|
restrict: 'EACM',
|
|
|
|
|
scope: false,
|
|
|
|
|
compile: function compile(tElement, tAttrs) {
|
|
|
|
|
return {
|
|
|
|
|
pre: function preLink(scope, iElement, iAttrs) { ... },
|
|
|
|
|
post: function postLink(scope, iElement, iAttrs) { ... }
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
link: function postLink(scope, iElement, iAttrs) { ... }
|
|
|
|
|
};
|
|
|
|
|
return directiveDefinitionObject;
|
|
|
|
|
});
|
|
|
|
|
</pre>
|
|
|
|
|
|
|
|
|
|
In most cases you will not need such fine control and so the above can be simplified. All of the
|
|
|
|
|
different parts of this skeleton are explained in following sections. In this section we are
|
|
|
|
|
interested only isomers of this skeleton.
|
|
|
|
|
|
|
|
|
|
It is rare that you need `preLink` method since most directives use the `postLink` method.
|
|
|
|
|
Therefore the above can be simplified as:
|
|
|
|
|
|
|
|
|
|
<pre>
|
|
|
|
|
var $compileProvider = ...;
|
|
|
|
|
|
|
|
|
|
$compileProvider.directive('directiveName', function factory(injectables) {
|
|
|
|
|
var directiveDefinitionObject = {
|
|
|
|
|
compile: function compile(tElement, tAttrs) {
|
|
|
|
|
return function postLink(scope, iElement, iAttrs) { ... }
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
return directiveDefinitionObject;
|
|
|
|
|
});
|
|
|
|
|
</pre>
|
|
|
|
|
|
|
|
|
|
Most directives concern themselves only with instances not with template transformations allowing
|
|
|
|
|
further simplification:
|
|
|
|
|
|
|
|
|
|
<pre>
|
|
|
|
|
var $compileProvider = ...;
|
|
|
|
|
|
|
|
|
|
$compileProvider.directive('directiveName', function factory(injectables) {
|
|
|
|
|
return function postLink(scope, iElement, iAttrs) { ... }
|
|
|
|
|
});
|
|
|
|
|
</pre>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Factory method
|
|
|
|
|
|
|
|
|
|
The factory method is responsible for creating the directive. It is invoked only once, when the
|
|
|
|
|
{@link angular.module.ng.$compile compiler} matches the directive for the first time. You can
|
|
|
|
|
perform any initialization work here. The method is invoked using the {@link
|
|
|
|
|
http://localhost:8000/build/docs/api/angular.module.AUTO.$injector#invoke $injector.invoke} which
|
|
|
|
|
makes it injectable following all of the rules of injection annotation.
|
|
|
|
|
|
|
|
|
|
## Directive Definition Object
|
|
|
|
|
|
|
|
|
|
The directive definition object provides instructions to the {@link angular.module.ng.$compile
|
|
|
|
|
compiler}. The attributes are:
|
|
|
|
|
|
|
|
|
|
* `priority` - When there are multiple directives defined on a single DOM element, sometimes it
|
|
|
|
|
is necessary to specify the order in which the directives are applied. The `priority` is used
|
|
|
|
|
to sort the directives before their `compile` functions get called. Higher `priority` goes
|
|
|
|
|
first. The order of directives within the same priority is undefined.
|
|
|
|
|
|
|
|
|
|
* `terminal` - If set to true then the current `priority` will be the last set of directives
|
|
|
|
|
which will execute (this means that any directives at the current priority will still execute
|
|
|
|
|
as the order of execution on same `priority` is undefined).
|
|
|
|
|
|
|
|
|
|
* `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.
|
|
|
|
|
|
|
|
|
|
* `restrict` - String of subset of `EACM` which restricts the directive to a specific directive
|
|
|
|
|
declaration style.
|
|
|
|
|
|
|
|
|
|
* `E` - Element name: `<my-directive></my-directive>`
|
|
|
|
|
* `A` - Attribute: `<div my-directive="exp"></div>`
|
|
|
|
|
* `C` - Class: `<div class="my-directive: exp;"></div>`
|
|
|
|
|
* `M` - Comment: `<!-- directive: my-directive exp -->`
|
|
|
|
|
|
|
|
|
|
* `template` - replace the current element with the contents of the HTML. The HTML may have
|
|
|
|
|
`<<content>>` string embedded in itself, in which case the current element content
|
|
|
|
|
will be transferred there. The replacement process migrates all of the attributes / classes
|
|
|
|
|
from the old element to the new one. See Creating Widgets section below for more information.
|
|
|
|
|
|
|
|
|
|
* `templateURL` - Same as `template` but the template is loaded from the specified URL. Because
|
|
|
|
|
the template loading is asynchronous the compilation/linking is suspended until the template
|
|
|
|
|
is loaded.
|
|
|
|
|
|
|
|
|
|
* `compile`: This is the compile function described in the section below.
|
|
|
|
|
|
|
|
|
|
* `link`: This is the link function described in the section below. This property is used only
|
|
|
|
|
if the `compile` property is not defined.
|
|
|
|
|
|
|
|
|
|
## Compile function
|
|
|
|
|
|
|
|
|
|
<pre>
|
|
|
|
|
function compile(tElement, tAttrs) { ... }
|
|
|
|
|
</pre>
|
|
|
|
|
|
|
|
|
|
Compile function deals with transforming the template DOM. Since most directives do not do
|
|
|
|
|
template transformation, it is not used often. Examples which require compile functions are
|
|
|
|
|
directives which transform template DOM such as {@link
|
|
|
|
|
angular.module.ng.$compileProvider.directive.ng:repeat ng:repeat} or load the contents
|
|
|
|
|
asynchronously such as {@link angular.module.ng.$compileProvider.directive.ng:view ng:view}. The
|
|
|
|
|
compile functions takes the following arguments.
|
|
|
|
|
|
|
|
|
|
* `tElement` - template element - The element where the directive has been declared. It is
|
|
|
|
|
safe to do template transformation on the element and child elements only.
|
|
|
|
|
|
|
|
|
|
* `tAttrs` - template attributes - Normalized list of attributes declared on this element shared
|
|
|
|
|
between all directive compile functions. See {@link
|
|
|
|
|
angular.module.ng.$compileProvider.directive.Attributes Attributes}
|
|
|
|
|
|
|
|
|
|
NOTE: The template instance and the link instance may not be the same objects if the template has
|
|
|
|
|
been cloned. For this reason it is not safe to do anything other the DOM transformation.
|
|
|
|
|
Specifically listener registration as not allowed inside the compile function.
|
|
|
|
|
|
|
|
|
|
## Link function
|
|
|
|
|
|
|
|
|
|
<pre>
|
|
|
|
|
function link(scope, iElement, iAttrs) { ... }
|
|
|
|
|
</pre>
|
|
|
|
|
|
2012-01-26 17:13:08 +00:00
|
|
|
Link function is responsible for registering DOM listeners as well as updating the DOM. It is
|
2011-12-14 01:55:31 +00:00
|
|
|
executed after the template has been cloned. This is where most of the directive logic will be
|
|
|
|
|
put.
|
|
|
|
|
|
2012-01-26 17:13:08 +00:00
|
|
|
* `scope` - {@link angular.module.ng.$rootScope.Scope Scope} - The scope to be used by the
|
2011-12-14 01:55:31 +00:00
|
|
|
directive for registering {@link angular.module.ng.$rootScope.Scope#.watch watches}.
|
|
|
|
|
|
|
|
|
|
* `iElement` - instance element - The element where the directive is to be used. It is safe to
|
|
|
|
|
manipulate the children of the element only in `postLink` function since the children have
|
|
|
|
|
already been linked.
|
|
|
|
|
|
|
|
|
|
* `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared
|
|
|
|
|
between all directive linking functions. See {@link
|
|
|
|
|
angular.module.ng.$compileProvider.directive.Attributes Attributes}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Pre link function
|
|
|
|
|
|
|
|
|
|
Executed before the child elements are linked. Not safe to do DOM transformation since the
|
|
|
|
|
compiler linking function will fail to locate the correct elements for linking.
|
|
|
|
|
|
|
|
|
|
### Post link function
|
|
|
|
|
|
|
|
|
|
Executed after the child elements are linked. Safe to do DOM transformation in here.
|
|
|
|
|
|
|
|
|
|
## Attributes
|
|
|
|
|
|
|
|
|
|
Attributes object is a way of accessing element attributes which:
|
|
|
|
|
|
|
|
|
|
* *normalize attribute names:* Since a directive such as 'ngBind' can be expressed in many ways
|
|
|
|
|
sucha s as 'ng:bind', or 'x-ng-bind', the attributes object allows for a normalize accessed to
|
|
|
|
|
the attributes.
|
|
|
|
|
|
|
|
|
|
* *directive inter-communication:* All directives share the same instance of the attributes
|
|
|
|
|
object which allows the directives to use the attributes object as inter directive
|
|
|
|
|
communication.
|
|
|
|
|
|
|
|
|
|
* *supports interpolation:* Interpolation attributes are assigned to the attribute object
|
|
|
|
|
allowing other directives to read the interpolated value.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Creating Widgets
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
can be built.
|
|
|
|
|
|
|
|
|
|
Following is an example of building a reusable widget.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<doc:example module="zippyModule">
|
|
|
|
|
<doc:source>
|
|
|
|
|
<script>
|
|
|
|
|
function Ctrl3($scope) {
|
|
|
|
|
$scope.title = 'Lorem Ipsum';
|
|
|
|
|
$scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
angular.module('zippyModule', [], function($compileProvider) {
|
|
|
|
|
$compileProvider.directive('zippy', function(){
|
|
|
|
|
return {
|
|
|
|
|
// This HTML will replace the zippy directive.
|
|
|
|
|
replace: true,
|
|
|
|
|
template: '<div>' +
|
|
|
|
|
'<div class="title"></div>' +
|
|
|
|
|
'<div class="body"><<content>></div>' +
|
|
|
|
|
'</div>',
|
|
|
|
|
// The linking function will add behavior to the template
|
|
|
|
|
link: function(scope, element, attrs) {
|
|
|
|
|
// Title element
|
|
|
|
|
var title = angular.element(element.children()[0]),
|
|
|
|
|
// Opened / closed state
|
|
|
|
|
opened = true;
|
|
|
|
|
|
|
|
|
|
// Watch the zippy-title attribute, copy changes to title element
|
|
|
|
|
scope.$watch(
|
|
|
|
|
function(){ return attrs.zippyTitle; },
|
|
|
|
|
function(value) { return title.text(value); }
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Clicking on title should open/close the zippy
|
|
|
|
|
title.bind('click', toggle);
|
|
|
|
|
|
|
|
|
|
// Toggle the closed/opened state
|
|
|
|
|
function toggle() {
|
|
|
|
|
opened = !opened;
|
|
|
|
|
element.removeClass(opened ? 'closed' : 'opened');
|
|
|
|
|
element.addClass(opened ? 'opened' : 'closed');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// initialize the zippy
|
|
|
|
|
toggle();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
</script>
|
|
|
|
|
<style>
|
|
|
|
|
.zippy {
|
|
|
|
|
border: 1px solid black;
|
|
|
|
|
display: inline-block;
|
|
|
|
|
width: 250px;
|
|
|
|
|
}
|
|
|
|
|
.zippy.opened > .title:before { content: '▼ '; }
|
|
|
|
|
.zippy.opened > .body { display: block; }
|
|
|
|
|
.zippy.closed > .title:before { content: '► '; }
|
|
|
|
|
.zippy.closed > .body { display: none; }
|
|
|
|
|
.zippy > .title {
|
|
|
|
|
background-color: black;
|
|
|
|
|
color: white;
|
|
|
|
|
padding: .1em .3em;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
}
|
|
|
|
|
.zippy > .body {
|
|
|
|
|
padding: .1em .3em;
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
<div ng-controller="Ctrl3">
|
|
|
|
|
Title: <input ng-model="title"> <br>
|
|
|
|
|
Text: <textarea ng-model="text"></textarea>
|
|
|
|
|
<hr>
|
|
|
|
|
<div class="zippy" zippy-title="Details: {{title}}...">{{text}}</div>
|
|
|
|
|
</div>
|
|
|
|
|
</doc:source>
|
|
|
|
|
<doc:scenario>
|
|
|
|
|
it('should bind and open / close', function() {
|
|
|
|
|
input('title').enter('TITLE');
|
|
|
|
|
input('text').enter('TEXT');
|
|
|
|
|
expect(element('.title').text()).toEqual('Details: TITLE...');
|
|
|
|
|
expect(binding('text')).toEqual('TEXT');
|
|
|
|
|
|
|
|
|
|
expect(element('.zippy').prop('className')).toMatch(/closed/);
|
|
|
|
|
element('.zippy > .title').click();
|
|
|
|
|
expect(element('.zippy').prop('className')).toMatch(/opened/);
|
|
|
|
|
});
|
|
|
|
|
</doc:scenario>
|
|
|
|
|
</doc:example>
|
|
|
|
|
|
|
|
|
|
|