mirror of
https://github.com/Hopiu/angular.js.git
synced 2026-03-19 08:00:23 +00:00
BREAKING CHANGE: ngInclude's priority is now set to 1000 It's quite rare for anyone to depend on explicity directive priority, but if a custom directive that needs to run before ngInclude exists, it should have its priority checked and adjusted if needed. Closes #3793
216 lines
7.2 KiB
JavaScript
216 lines
7.2 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* @ngdoc directive
|
|
* @name ng.directive:ngInclude
|
|
* @restrict ECA
|
|
*
|
|
* @description
|
|
* Fetches, compiles and includes an external HTML fragment.
|
|
*
|
|
* Keep in mind that:
|
|
*
|
|
* - by default, the template URL is restricted to the same domain and protocol as the
|
|
* application document. This is done by calling {@link ng.$sce#getTrustedResourceUrl
|
|
* $sce.getTrustedResourceUrl} on it. To load templates from other domains and/or protocols,
|
|
* you may either either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist them} or
|
|
* {@link ng.$sce#trustAsResourceUrl wrap it} into a trusted value. Refer Angular's {@link
|
|
* ng.$sce Strict Contextual Escaping}.
|
|
* - in addition, the browser's
|
|
* {@link https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest
|
|
* Same Origin Policy} and {@link http://www.w3.org/TR/cors/ Cross-Origin Resource Sharing
|
|
* (CORS)} policy apply that may further restrict whether the template is successfully loaded.
|
|
* (e.g. ngInclude won't work for cross-domain requests on all browsers and for `file://`
|
|
* access on some browsers)
|
|
*
|
|
* @animations
|
|
* enter - animation is used to bring new content into the browser.
|
|
* leave - animation is used to animate existing content away.
|
|
*
|
|
* The enter and leave animation occur concurrently.
|
|
*
|
|
* @scope
|
|
*
|
|
* @param {string} ngInclude|src angular expression evaluating to URL. If the source is a string constant,
|
|
* make sure you wrap it in quotes, e.g. `src="'myPartialTemplate.html'"`.
|
|
* @param {string=} onload Expression to evaluate when a new partial is loaded.
|
|
*
|
|
* @param {string=} autoscroll Whether `ngInclude` should call {@link ng.$anchorScroll
|
|
* $anchorScroll} to scroll the viewport after the content is loaded.
|
|
*
|
|
* - If the attribute is not set, disable scrolling.
|
|
* - If the attribute is set without value, enable scrolling.
|
|
* - Otherwise enable scrolling only if the expression evaluates to truthy value.
|
|
*
|
|
* @example
|
|
<example animations="true">
|
|
<file name="index.html">
|
|
<div ng-controller="Ctrl">
|
|
<select ng-model="template" ng-options="t.name for t in templates">
|
|
<option value="">(blank)</option>
|
|
</select>
|
|
url of the template: <tt>{{template.url}}</tt>
|
|
<hr/>
|
|
<div class="example-animate-container">
|
|
<div class="include-example" ng-include="template.url"></div>
|
|
</div>
|
|
</div>
|
|
</file>
|
|
<file name="script.js">
|
|
function Ctrl($scope) {
|
|
$scope.templates =
|
|
[ { name: 'template1.html', url: 'template1.html'}
|
|
, { name: 'template2.html', url: 'template2.html'} ];
|
|
$scope.template = $scope.templates[0];
|
|
}
|
|
</file>
|
|
<file name="template1.html">
|
|
Content of template1.html
|
|
</file>
|
|
<file name="template2.html">
|
|
Content of template2.html
|
|
</file>
|
|
<file name="animations.css">
|
|
.example-animate-container {
|
|
position:relative;
|
|
background:white;
|
|
border:1px solid black;
|
|
height:40px;
|
|
overflow:hidden;
|
|
}
|
|
|
|
.example-animate-container > div {
|
|
padding:10px;
|
|
}
|
|
|
|
.include-example.ng-enter, .include-example.ng-leave {
|
|
-webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
|
|
-moz-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
|
|
-o-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
|
|
transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
|
|
|
|
position:absolute;
|
|
top:0;
|
|
left:0;
|
|
right:0;
|
|
bottom:0;
|
|
display:block;
|
|
padding:10px;
|
|
}
|
|
|
|
.include-example.ng-enter {
|
|
top:-50px;
|
|
}
|
|
.include-example.ng-enter.ng-enter-active {
|
|
top:0;
|
|
}
|
|
|
|
.include-example.ng-leave {
|
|
top:0;
|
|
}
|
|
.include-example.ng-leave.ng-leave-active {
|
|
top:50px;
|
|
}
|
|
</file>
|
|
<file name="scenario.js">
|
|
it('should load template1.html', function() {
|
|
expect(element('.doc-example-live [ng-include]').text()).
|
|
toMatch(/Content of template1.html/);
|
|
});
|
|
it('should load template2.html', function() {
|
|
select('template').option('1');
|
|
expect(element('.doc-example-live [ng-include]').text()).
|
|
toMatch(/Content of template2.html/);
|
|
});
|
|
it('should change to blank', function() {
|
|
select('template').option('');
|
|
expect(element('.doc-example-live [ng-include]')).toBe(undefined);
|
|
});
|
|
</file>
|
|
</example>
|
|
*/
|
|
|
|
|
|
/**
|
|
* @ngdoc event
|
|
* @name ng.directive:ngInclude#$includeContentRequested
|
|
* @eventOf ng.directive:ngInclude
|
|
* @eventType emit on the scope ngInclude was declared in
|
|
* @description
|
|
* Emitted every time the ngInclude content is requested.
|
|
*/
|
|
|
|
|
|
/**
|
|
* @ngdoc event
|
|
* @name ng.directive:ngInclude#$includeContentLoaded
|
|
* @eventOf ng.directive:ngInclude
|
|
* @eventType emit on the current ngInclude scope
|
|
* @description
|
|
* Emitted every time the ngInclude content is reloaded.
|
|
*/
|
|
var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile', '$animate', '$sce',
|
|
function($http, $templateCache, $anchorScroll, $compile, $animate, $sce) {
|
|
return {
|
|
restrict: 'ECA',
|
|
priority: 1000,
|
|
terminal: true,
|
|
transclude: 'element',
|
|
compile: function(element, attr, transclusion) {
|
|
var srcExp = attr.ngInclude || attr.src,
|
|
onloadExp = attr.onload || '',
|
|
autoScrollExp = attr.autoscroll;
|
|
|
|
return function(scope, $element) {
|
|
var changeCounter = 0,
|
|
currentScope,
|
|
currentElement;
|
|
|
|
var cleanupLastIncludeContent = function() {
|
|
if (currentScope) {
|
|
currentScope.$destroy();
|
|
currentScope = null;
|
|
}
|
|
if(currentElement) {
|
|
$animate.leave(currentElement);
|
|
currentElement = null;
|
|
}
|
|
};
|
|
|
|
scope.$watch($sce.parseAsResourceUrl(srcExp), function ngIncludeWatchAction(src) {
|
|
var thisChangeId = ++changeCounter;
|
|
|
|
if (src) {
|
|
$http.get(src, {cache: $templateCache}).success(function(response) {
|
|
if (thisChangeId !== changeCounter) return;
|
|
var newScope = scope.$new();
|
|
|
|
transclusion(newScope, function(clone) {
|
|
cleanupLastIncludeContent();
|
|
|
|
currentScope = newScope;
|
|
currentElement = clone;
|
|
|
|
currentElement.html(response);
|
|
$animate.enter(currentElement, null, $element);
|
|
$compile(currentElement.contents())(currentScope);
|
|
|
|
if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) {
|
|
$anchorScroll();
|
|
}
|
|
|
|
currentScope.$emit('$includeContentLoaded');
|
|
scope.$eval(onloadExp);
|
|
});
|
|
}).error(function() {
|
|
if (thisChangeId === changeCounter) cleanupLastIncludeContent();
|
|
});
|
|
scope.$emit('$includeContentRequested');
|
|
} else {
|
|
cleanupLastIncludeContent();
|
|
}
|
|
});
|
|
};
|
|
}
|
|
};
|
|
}];
|