mirror of
https://github.com/Hopiu/angular.js.git
synced 2026-03-20 00:10:26 +00:00
feat($compile) add locals, isolate scope, transclusion
This commit is contained in:
parent
cb10ccc44f
commit
78656fe0df
13 changed files with 838 additions and 107 deletions
|
|
@ -94,7 +94,8 @@ function publishExternalAPI(angular){
|
|||
ngStyle: ngStyleDirective,
|
||||
ngSwitch: ngSwitchDirective,
|
||||
ngOptions: ngOptionsDirective,
|
||||
ngView: ngViewDirective
|
||||
ngView: ngViewDirective,
|
||||
ngTransclude: ngTranscludeDirective
|
||||
}).
|
||||
directive(ngEventDirectives).
|
||||
directive(ngAttributeAliasDirectives);
|
||||
|
|
|
|||
1
src/angular-bootstrap.js
vendored
1
src/angular-bootstrap.js
vendored
|
|
@ -101,6 +101,7 @@
|
|||
globalVars = {};
|
||||
|
||||
bindJQuery();
|
||||
publishExternalAPI(window.angular);
|
||||
|
||||
angularInit(document, angular.bootstrap);
|
||||
}
|
||||
|
|
|
|||
3
src/angular-mocks.js
vendored
3
src/angular-mocks.js
vendored
|
|
@ -1458,6 +1458,9 @@ window.jstestdriver && (function(window) {
|
|||
args.push(angular.mock.dump(arg));
|
||||
});
|
||||
jstestdriver.console.log.apply(jstestdriver.console, args);
|
||||
if (window.console) {
|
||||
window.console.log.apply(window.console, args);
|
||||
}
|
||||
};
|
||||
})(window);
|
||||
|
||||
|
|
|
|||
|
|
@ -133,17 +133,7 @@ var ngInitDirective = valueFn({
|
|||
var ngControllerDirective = ['$controller', '$window', function($controller, $window) {
|
||||
return {
|
||||
scope: true,
|
||||
compile: function() {
|
||||
return {
|
||||
pre: function(scope, element, attr) {
|
||||
var expression = attr.ngController,
|
||||
Controller = getter(scope, expression, true) || getter($window, expression, true);
|
||||
|
||||
assertArgFn(Controller, expression);
|
||||
$controller(Controller, scope);
|
||||
}
|
||||
};
|
||||
}
|
||||
controller: '@'
|
||||
}
|
||||
}];
|
||||
|
||||
|
|
@ -264,6 +254,7 @@ var ngBindHtmlDirective = ['$sanitize', function($sanitize) {
|
|||
var ngBindTemplateDirective = ['$interpolate', function($interpolate) {
|
||||
return function(scope, element, attr) {
|
||||
var interpolateFn = $interpolate(attr.ngBindTemplate);
|
||||
var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate));
|
||||
element.addClass('ng-binding').data('$binding', interpolateFn);
|
||||
scope.$watch(interpolateFn, function(value) {
|
||||
element.text(value);
|
||||
|
|
@ -921,3 +912,59 @@ function ngAttributeAliasDirective(propName, attrName) {
|
|||
var ngAttributeAliasDirectives = {};
|
||||
forEach(BOOLEAN_ATTR, ngAttributeAliasDirective);
|
||||
ngAttributeAliasDirective(null, 'src');
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name angular.module.ng.$compileProvider.directive.ng:transclude
|
||||
*
|
||||
* @description
|
||||
* Insert the transcluded DOM here.
|
||||
*
|
||||
* @element ANY
|
||||
*
|
||||
* @example
|
||||
<doc:example module="transclude">
|
||||
<doc:source>
|
||||
<script>
|
||||
function Ctrl($scope) {
|
||||
$scope.title = 'Lorem Ipsum';
|
||||
$scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...';
|
||||
}
|
||||
|
||||
angular.module('transclude', [])
|
||||
.directive('pane', function(){
|
||||
return {
|
||||
transclude: true,
|
||||
scope: 'isolate',
|
||||
locals: { title:'bind' },
|
||||
template: '<div style="border: 1px solid black;">' +
|
||||
'<div style="background-color: gray">{{title}}</div>' +
|
||||
'<div ng-transclude></div>' +
|
||||
'</div>'
|
||||
};
|
||||
});
|
||||
</script>
|
||||
<div ng:controller="Ctrl">
|
||||
<input ng:model="title"><br>
|
||||
<textarea ng:model="text"></textarea> <br/>
|
||||
<pane title="{{title}}">{{text}}</pane>
|
||||
</div>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should have transcluded', function() {
|
||||
input('title').enter('TITLE');
|
||||
input('text').enter('TEXT');
|
||||
expect(binding('title')).toEqual('TITLE');
|
||||
expect(binding('text')).toEqual('TEXT');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*
|
||||
*/
|
||||
var ngTranscludeDirective = valueFn({
|
||||
controller: ['$transclude', '$element', function($transclude, $element) {
|
||||
$transclude(function(clone) {
|
||||
$element.append(clone);
|
||||
});
|
||||
}]
|
||||
});
|
||||
|
|
|
|||
|
|
@ -72,6 +72,9 @@
|
|||
*
|
||||
*
|
||||
* @param {string|DOMElement} element Element or HTML string to compile into a template function.
|
||||
* @param {function(angular.Scope[, cloneAttachFn]} transclude function available to directives.
|
||||
* @param {number} maxPriority only apply directives lower then given priority (Only effects the
|
||||
* root element(s), not their children)
|
||||
* @returns {function(scope[, cloneAttachFn])} a link function which is used to bind template
|
||||
* (a DOM element/tree) to a scope. Where:
|
||||
*
|
||||
|
|
@ -157,7 +160,8 @@ function $CompileProvider($provide) {
|
|||
directive.compile = valueFn(directive.link);
|
||||
}
|
||||
directive.priority = directive.priority || 0;
|
||||
directive.name = name;
|
||||
directive.name = directive.name || name;
|
||||
directive.require = directive.require || (directive.controller && directive.name);
|
||||
directive.restrict = directive.restrict || 'EACM';
|
||||
directives.push(directive);
|
||||
} catch (e) {
|
||||
|
|
@ -175,10 +179,58 @@ function $CompileProvider($provide) {
|
|||
};
|
||||
|
||||
|
||||
this.$get = ['$injector', '$interpolate', '$exceptionHandler', '$http', '$templateCache',
|
||||
function($injector, $interpolate, $exceptionHandler, $http, $templateCache) {
|
||||
this.$get = [
|
||||
'$injector', '$interpolate', '$exceptionHandler', '$http', '$templateCache', '$parse',
|
||||
'$controller',
|
||||
function($injector, $interpolate, $exceptionHandler, $http, $templateCache, $parse,
|
||||
$controller) {
|
||||
|
||||
return function(templateElement) {
|
||||
var LOCAL_MODE = {
|
||||
attribute: function(localName, mode, parentScope, scope, attr) {
|
||||
scope[localName] = attr[localName];
|
||||
},
|
||||
|
||||
evaluate: function(localName, mode, parentScope, scope, attr) {
|
||||
scope[localName] = parentScope.$eval(attr[localName]);
|
||||
},
|
||||
|
||||
bind: function(localName, mode, parentScope, scope, attr) {
|
||||
var getter = $interpolate(attr[localName]);
|
||||
scope.$watch(
|
||||
function() { return getter(parentScope); },
|
||||
function(v) { scope[localName] = v; }
|
||||
);
|
||||
},
|
||||
|
||||
accessor: function(localName, mode, parentScope, scope, attr) {
|
||||
var getter = noop,
|
||||
setter = noop,
|
||||
exp = attr[localName];
|
||||
|
||||
if (exp) {
|
||||
getter = $parse(exp);
|
||||
setter = getter.assign || function() {
|
||||
throw Error("Expression '" + exp + "' not assignable.");
|
||||
};
|
||||
}
|
||||
|
||||
scope[localName] = function(value) {
|
||||
return arguments.length ? setter(parentScope, value) : getter(parentScope);
|
||||
};
|
||||
},
|
||||
|
||||
expression: function(localName, mode, parentScope, scope, attr) {
|
||||
scope[localName] = function(locals) {
|
||||
$parse(attr[localName])(parentScope, locals);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
return compile;
|
||||
|
||||
//================================
|
||||
|
||||
function compile(templateElement, transcludeFn, maxPriority) {
|
||||
templateElement = jqLite(templateElement);
|
||||
// We can not compile top level text elements since text nodes can be merged and we will
|
||||
// not be able to attach scope data to them, so we will wrap them in <span>
|
||||
|
|
@ -187,7 +239,7 @@ function $CompileProvider($provide) {
|
|||
templateElement[index] = jqLite(node).wrap('<span>').parent()[0];
|
||||
}
|
||||
});
|
||||
var linkingFn = compileNodes(templateElement, templateElement);
|
||||
var linkingFn = compileNodes(templateElement, transcludeFn, templateElement, maxPriority);
|
||||
return function(scope, cloneConnectFn){
|
||||
assertArg(scope, 'scope');
|
||||
// important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
|
||||
|
|
@ -200,9 +252,11 @@ function $CompileProvider($provide) {
|
|||
if (linkingFn) linkingFn(scope, element, element);
|
||||
return element;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
//================================
|
||||
function wrongMode(localName, mode) {
|
||||
throw Error("Unsupported '" + mode + "' for '" + localName + "'.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile function matches each node in nodeList against the directives. Once all directives
|
||||
|
|
@ -211,12 +265,15 @@ function $CompileProvider($provide) {
|
|||
* function, which is the a linking function for the node.
|
||||
*
|
||||
* @param {NodeList} nodeList an array of nodes to compile
|
||||
* @param {function(angular.Scope[, cloneAttachFn]} transcludeFn A linking function, where the
|
||||
* scope argument is auto-generated to the new child of the transcluded parent scope.
|
||||
* @param {DOMElement=} rootElement If the nodeList is the root of the compilation tree then the
|
||||
* rootElement must be set the jqLite collection of the compile root. This is
|
||||
* needed so that the jqLite collection items can be replaced with widgets.
|
||||
* @param {number=} max directive priority
|
||||
* @returns {?function} A composite linking function of all of the matched directives or null.
|
||||
*/
|
||||
function compileNodes(nodeList, rootElement) {
|
||||
function compileNodes(nodeList, transcludeFn, rootElement, maxPriority) {
|
||||
var linkingFns = [],
|
||||
directiveLinkingFn, childLinkingFn, directives, attrs, linkingFnFound;
|
||||
|
||||
|
|
@ -227,15 +284,16 @@ function $CompileProvider($provide) {
|
|||
$set: attrSetter
|
||||
};
|
||||
// we must always refer to nodeList[i] since the nodes can be replaced underneath us.
|
||||
directives = collectDirectives(nodeList[i], [], attrs);
|
||||
directives = collectDirectives(nodeList[i], [], attrs, maxPriority);
|
||||
|
||||
directiveLinkingFn = (directives.length)
|
||||
? applyDirectivesToNode(directives, nodeList[i], attrs, rootElement)
|
||||
? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, rootElement)
|
||||
: null;
|
||||
|
||||
childLinkingFn = (directiveLinkingFn && directiveLinkingFn.terminal)
|
||||
? null
|
||||
: compileNodes(nodeList[i].childNodes);
|
||||
: compileNodes(nodeList[i].childNodes,
|
||||
directiveLinkingFn ? directiveLinkingFn.transclude : transcludeFn);
|
||||
|
||||
linkingFns.push(directiveLinkingFn);
|
||||
linkingFns.push(childLinkingFn);
|
||||
|
|
@ -245,28 +303,42 @@ function $CompileProvider($provide) {
|
|||
// return a linking function if we have found anything, null otherwise
|
||||
return linkingFnFound ? linkingFn : null;
|
||||
|
||||
function linkingFn(scope, nodeList, rootElement) {
|
||||
/* nodesetLinkingFn */ function linkingFn(scope, nodeList, rootElement, boundTranscludeFn) {
|
||||
if (linkingFns.length != nodeList.length * 2) {
|
||||
throw Error('Template changed structure!');
|
||||
}
|
||||
|
||||
var childLinkingFn, directiveLinkingFn, node, childScope;
|
||||
var childLinkingFn, directiveLinkingFn, node, childScope, childTransclusionFn;
|
||||
|
||||
for(var i=0, n=0, ii=linkingFns.length; i<ii; n++) {
|
||||
node = nodeList[n];
|
||||
directiveLinkingFn = linkingFns[i++];
|
||||
childLinkingFn = linkingFns[i++];
|
||||
directiveLinkingFn = /* directiveLinkingFn */ linkingFns[i++];
|
||||
childLinkingFn = /* nodesetLinkingFn */ linkingFns[i++];
|
||||
|
||||
if (directiveLinkingFn) {
|
||||
if (directiveLinkingFn.scope && !rootElement) {
|
||||
childScope = scope.$new();
|
||||
childScope = scope.$new(isObject(directiveLinkingFn.scope));
|
||||
jqLite(node).data('$scope', childScope);
|
||||
} else {
|
||||
childScope = scope;
|
||||
}
|
||||
directiveLinkingFn(childLinkingFn, childScope, node, rootElement);
|
||||
childTransclusionFn = directiveLinkingFn.transclude;
|
||||
if (childTransclusionFn || (!boundTranscludeFn && transcludeFn)) {
|
||||
directiveLinkingFn(childLinkingFn, childScope, node, rootElement,
|
||||
(function(transcludeFn) {
|
||||
return function(cloneFn) {
|
||||
var transcludeScope = scope.$new();
|
||||
|
||||
return transcludeFn(transcludeScope, cloneFn).
|
||||
bind('$destroy', bind(transcludeScope, transcludeScope.$destroy));
|
||||
};
|
||||
})(childTransclusionFn || transcludeFn)
|
||||
);
|
||||
} else {
|
||||
directiveLinkingFn(childLinkingFn, childScope, node, undefined, boundTranscludeFn);
|
||||
}
|
||||
} else if (childLinkingFn) {
|
||||
childLinkingFn(scope, node.childNodes);
|
||||
childLinkingFn(scope, node.childNodes, undefined, boundTranscludeFn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -280,8 +352,9 @@ function $CompileProvider($provide) {
|
|||
* @param directives an array to which the directives are added to. This array is sorted before
|
||||
* the function returns.
|
||||
* @param attrs the shared attrs object which is used to populate the normalized attributes.
|
||||
* @param {number=} max directive priority
|
||||
*/
|
||||
function collectDirectives(node, directives, attrs) {
|
||||
function collectDirectives(node, directives, attrs, maxPriority) {
|
||||
var nodeType = node.nodeType,
|
||||
attrsMap = attrs.$attr,
|
||||
match,
|
||||
|
|
@ -290,7 +363,8 @@ function $CompileProvider($provide) {
|
|||
switch(nodeType) {
|
||||
case 1: /* Element */
|
||||
// use the node name: <directive>
|
||||
addDirective(directives, directiveNormalize(nodeName_(node).toLowerCase()), 'E');
|
||||
addDirective(directives,
|
||||
directiveNormalize(nodeName_(node).toLowerCase()), 'E', maxPriority);
|
||||
|
||||
// iterate over the attributes
|
||||
for (var attr, name, nName, value, nAttrs = node.attributes,
|
||||
|
|
@ -305,15 +379,15 @@ function $CompileProvider($provide) {
|
|||
if (BOOLEAN_ATTR[nName]) {
|
||||
attrs[nName] = true; // presence means true
|
||||
}
|
||||
addAttrInterpolateDirective(directives, value, nName);
|
||||
addDirective(directives, nName, 'A');
|
||||
addAttrInterpolateDirective(directives, value, nName)
|
||||
addDirective(directives, nName, 'A', maxPriority);
|
||||
}
|
||||
|
||||
// use class as directive
|
||||
className = node.className;
|
||||
while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) {
|
||||
nName = directiveNormalize(match[2]);
|
||||
if (addDirective(directives, nName, 'C')) {
|
||||
if (addDirective(directives, nName, 'C', maxPriority)) {
|
||||
attrs[nName] = trim(match[3]);
|
||||
}
|
||||
className = className.substr(match.index + match[0].length);
|
||||
|
|
@ -326,7 +400,7 @@ function $CompileProvider($provide) {
|
|||
match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue);
|
||||
if (match) {
|
||||
nName = directiveNormalize(match[1]);
|
||||
if (addDirective(directives, nName, 'M')) {
|
||||
if (addDirective(directives, nName, 'M', maxPriority)) {
|
||||
attrs[nName] = trim(match[2]);
|
||||
}
|
||||
}
|
||||
|
|
@ -347,40 +421,81 @@ function $CompileProvider($provide) {
|
|||
* this needs to be pre-sorted by priority order.
|
||||
* @param {Node} templateNode The raw DOM node to apply the compile functions to
|
||||
* @param {Object} templateAttrs The shared attribute function
|
||||
* @param {function(angular.Scope[, cloneAttachFn]} transcludeFn A linking function, where the
|
||||
* scope argument is auto-generated to the new child of the transcluded parent scope.
|
||||
* @param {DOMElement} rootElement If we are working on the root of the compile tree then this
|
||||
* argument has the root jqLite array so that we can replace widgets on it.
|
||||
* @returns linkingFn
|
||||
*/
|
||||
function applyDirectivesToNode(directives, templateNode, templateAttrs, rootElement) {
|
||||
function applyDirectivesToNode(directives, templateNode, templateAttrs, transcludeFn, rootElement) {
|
||||
var terminalPriority = -Number.MAX_VALUE,
|
||||
preLinkingFns = [],
|
||||
postLinkingFns = [],
|
||||
newScopeDirective = null,
|
||||
newIsolatedScopeDirective = null,
|
||||
templateDirective = null,
|
||||
delayedLinkingFn = null,
|
||||
element = templateAttrs.$element = jqLite(templateNode),
|
||||
directive, linkingFn;
|
||||
directive,
|
||||
directiveName,
|
||||
template,
|
||||
transcludeDirective,
|
||||
childTranscludeFn = transcludeFn,
|
||||
controllerDirectives,
|
||||
linkingFn,
|
||||
directiveValue;
|
||||
|
||||
// executes all directives on the current element
|
||||
for(var i = 0, ii = directives.length; i < ii; i++) {
|
||||
directive = directives[i];
|
||||
template = undefined;
|
||||
|
||||
if (terminalPriority > directive.priority) {
|
||||
break; // prevent further processing of directives
|
||||
}
|
||||
|
||||
if (directive.scope) {
|
||||
assertNoDuplicate('new scope', newScopeDirective, directive, element);
|
||||
if (directiveValue = directive.scope) {
|
||||
assertNoDuplicate('isolated scope', newIsolatedScopeDirective, directive, element);
|
||||
if (isObject(directiveValue)) {
|
||||
element.addClass('ng-isolate-scope');
|
||||
newIsolatedScopeDirective = directive;
|
||||
}
|
||||
element.addClass('ng-scope');
|
||||
newScopeDirective = directive;
|
||||
newScopeDirective = newScopeDirective || directive;
|
||||
}
|
||||
|
||||
if (directive.template) {
|
||||
directiveName = directive.name;
|
||||
|
||||
if (directiveValue = directive.controller) {
|
||||
controllerDirectives = controllerDirectives || {};
|
||||
assertNoDuplicate("'" + directiveName + "' controller",
|
||||
controllerDirectives[directiveName], directive, element);
|
||||
controllerDirectives[directiveName] = directive;
|
||||
}
|
||||
|
||||
if (directiveValue = directive.transclude) {
|
||||
assertNoDuplicate('transclusion', transcludeDirective, directive, element);
|
||||
transcludeDirective = directive;
|
||||
terminalPriority = directive.priority;
|
||||
if (directiveValue == 'element') {
|
||||
template = jqLite(templateNode);
|
||||
templateNode = (element = templateAttrs.$element = jqLite(
|
||||
'<!-- ' + directiveName + ': ' + templateAttrs[directiveName] + ' -->'))[0];
|
||||
template.replaceWith(templateNode);
|
||||
childTranscludeFn = compile(template, transcludeFn, terminalPriority);
|
||||
} else {
|
||||
template = jqLite(JQLiteClone(templateNode));
|
||||
element.html(''); // clear contents
|
||||
childTranscludeFn = compile(template.contents(), transcludeFn);
|
||||
}
|
||||
}
|
||||
|
||||
if (directiveValue = directive.template) {
|
||||
assertNoDuplicate('template', templateDirective, directive, element);
|
||||
templateDirective = directive;
|
||||
|
||||
// include the contents of the original element into the template and replace the element
|
||||
var content = directive.template.replace(CONTENT_REGEXP, element.html());
|
||||
var content = directiveValue.replace(CONTENT_REGEXP, element.html());
|
||||
templateNode = jqLite(content)[0];
|
||||
if (directive.replace) {
|
||||
replaceWith(rootElement, element, templateNode);
|
||||
|
|
@ -411,16 +526,16 @@ function $CompileProvider($provide) {
|
|||
assertNoDuplicate('template', templateDirective, directive, element);
|
||||
templateDirective = directive;
|
||||
delayedLinkingFn = compileTemplateUrl(directives.splice(i, directives.length - i),
|
||||
compositeLinkFn, element, templateAttrs, rootElement, directive.replace);
|
||||
/* directiveLinkingFn */ compositeLinkFn, element, templateAttrs, rootElement,
|
||||
directive.replace, childTranscludeFn);
|
||||
ii = directives.length;
|
||||
} else if (directive.compile) {
|
||||
try {
|
||||
linkingFn = directive.compile(element, templateAttrs);
|
||||
linkingFn = directive.compile(element, templateAttrs, childTranscludeFn);
|
||||
if (isFunction(linkingFn)) {
|
||||
postLinkingFns.push(linkingFn);
|
||||
addLinkingFns(null, linkingFn);
|
||||
} else if (linkingFn) {
|
||||
if (linkingFn.pre) preLinkingFns.push(linkingFn.pre);
|
||||
if (linkingFn.post) postLinkingFns.push(linkingFn.post);
|
||||
addLinkingFns(linkingFn.pre, linkingFn.post);
|
||||
}
|
||||
} catch (e) {
|
||||
$exceptionHandler(e, startingTag(element));
|
||||
|
|
@ -433,16 +548,57 @@ function $CompileProvider($provide) {
|
|||
}
|
||||
|
||||
}
|
||||
compositeLinkFn.scope = !!newScopeDirective;
|
||||
|
||||
linkingFn = delayedLinkingFn || compositeLinkFn;
|
||||
linkingFn.scope = newScopeDirective && newScopeDirective.scope;
|
||||
linkingFn.transclude = transcludeDirective && childTranscludeFn;
|
||||
|
||||
// if we have templateUrl, then we have to delay linking
|
||||
return delayedLinkingFn || compositeLinkFn;
|
||||
return linkingFn;
|
||||
|
||||
////////////////////
|
||||
|
||||
function addLinkingFns(pre, post) {
|
||||
if (pre) {
|
||||
pre.require = directive.require;
|
||||
preLinkingFns.push(pre);
|
||||
}
|
||||
if (post) {
|
||||
post.require = directive.require;
|
||||
postLinkingFns.push(post);
|
||||
}
|
||||
}
|
||||
|
||||
function compositeLinkFn(childLinkingFn, scope, linkNode) {
|
||||
var attrs, element, i, ii;
|
||||
|
||||
function getControllers(require, element) {
|
||||
var value, retrievalMethod = 'data', optional = false;
|
||||
if (isString(require)) {
|
||||
while((value = require.charAt(0)) == '^' || value == '?') {
|
||||
require = require.substr(1);
|
||||
if (value == '^') {
|
||||
retrievalMethod = 'inheritedData';
|
||||
}
|
||||
optional = optional || value == '?';
|
||||
}
|
||||
value = element[retrievalMethod]('$' + require + 'Controller');
|
||||
if (!value && !optional) {
|
||||
throw Error("No controller: " + require);
|
||||
}
|
||||
return value;
|
||||
} else if (isArray(require)) {
|
||||
value = [];
|
||||
forEach(require, function(require) {
|
||||
value.push(getControllers(require, element));
|
||||
});
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
/* directiveLinkingFn */
|
||||
function compositeLinkFn(/* nodesetLinkingFn */ childLinkingFn,
|
||||
scope, linkNode, rootElement, boundTranscludeFn) {
|
||||
var attrs, element, i, ii, linkingFn, controller;
|
||||
|
||||
if (templateNode === linkNode) {
|
||||
attrs = templateAttrs;
|
||||
|
|
@ -452,22 +608,59 @@ function $CompileProvider($provide) {
|
|||
}
|
||||
element = attrs.$element;
|
||||
|
||||
if (newScopeDirective && isObject(newScopeDirective.scope)) {
|
||||
forEach(newScopeDirective.scope, function(mode, name) {
|
||||
(LOCAL_MODE[mode] || wrongMode)(name, mode,
|
||||
scope.$parent || scope, scope, attrs);
|
||||
});
|
||||
}
|
||||
|
||||
if (controllerDirectives) {
|
||||
forEach(controllerDirectives, function(directive) {
|
||||
var locals = {
|
||||
$scope: scope,
|
||||
$element: element,
|
||||
$attrs: attrs,
|
||||
$transclude: boundTranscludeFn
|
||||
};
|
||||
|
||||
|
||||
forEach(directive.inject || {}, function(mode, name) {
|
||||
(LOCAL_MODE[mode] || wrongMode)(name, mode,
|
||||
newScopeDirective ? scope.$parent || scope : scope, locals, attrs);
|
||||
});
|
||||
|
||||
controller = directive.controller;
|
||||
if (controller == '@') {
|
||||
controller = attrs[directive.name];
|
||||
}
|
||||
|
||||
element.data(
|
||||
'$' + directive.name + 'Controller',
|
||||
$controller(controller, locals));
|
||||
});
|
||||
}
|
||||
|
||||
// PRELINKING
|
||||
for(i = 0, ii = preLinkingFns.length; i < ii; i++) {
|
||||
try {
|
||||
preLinkingFns[i](scope, element, attrs);
|
||||
linkingFn = preLinkingFns[i];
|
||||
linkingFn(scope, element, attrs,
|
||||
linkingFn.require && getControllers(linkingFn.require, element));
|
||||
} catch (e) {
|
||||
$exceptionHandler(e, startingTag(element));
|
||||
}
|
||||
}
|
||||
|
||||
// RECURSION
|
||||
childLinkingFn && childLinkingFn(scope, linkNode.childNodes);
|
||||
childLinkingFn && childLinkingFn(scope, linkNode.childNodes, undefined, boundTranscludeFn);
|
||||
|
||||
// POSTLINKING
|
||||
for(i = 0, ii = postLinkingFns.length; i < ii; i++) {
|
||||
try {
|
||||
postLinkingFns[i](scope, element, attrs);
|
||||
linkingFn = postLinkingFns[i];
|
||||
linkingFn(scope, element, attrs,
|
||||
linkingFn.require && getControllers(linkingFn.require, element));
|
||||
} catch (e) {
|
||||
$exceptionHandler(e, startingTag(element));
|
||||
}
|
||||
|
|
@ -490,14 +683,15 @@ function $CompileProvider($provide) {
|
|||
* * `M`: comment
|
||||
* @returns true if directive was added.
|
||||
*/
|
||||
function addDirective(tDirectives, name, location) {
|
||||
function addDirective(tDirectives, name, location, maxPriority) {
|
||||
var match = false;
|
||||
if (hasDirectives.hasOwnProperty(name)) {
|
||||
for(var directive, directives = $injector.get(name + Suffix),
|
||||
i=0, ii = directives.length; i<ii; i++) {
|
||||
try {
|
||||
directive = directives[i];
|
||||
if (directive.restrict.indexOf(location) != -1) {
|
||||
if ( (maxPriority === undefined || maxPriority > directive.priority) &&
|
||||
directive.restrict.indexOf(location) != -1) {
|
||||
tDirectives.push(directive);
|
||||
match = true;
|
||||
}
|
||||
|
|
@ -540,15 +734,15 @@ function $CompileProvider($provide) {
|
|||
}
|
||||
|
||||
|
||||
function compileTemplateUrl(directives, beforeWidgetLinkFn, tElement, tAttrs, rootElement,
|
||||
replace) {
|
||||
function compileTemplateUrl(directives, /* directiveLinkingFn */ beforeWidgetLinkFn,
|
||||
tElement, tAttrs, rootElement, replace, transcludeFn) {
|
||||
var linkQueue = [],
|
||||
afterWidgetLinkFn,
|
||||
afterWidgetChildrenLinkFn,
|
||||
originalWidgetNode = tElement[0],
|
||||
asyncWidgetDirective = directives.shift(),
|
||||
// The fact that we have to copy and patch the directive seems wrong!
|
||||
syncWidgetDirective = extend({}, asyncWidgetDirective, {templateUrl:null}),
|
||||
syncWidgetDirective = extend({}, asyncWidgetDirective, {templateUrl:null, transclude:null}),
|
||||
html = tElement.html();
|
||||
|
||||
tElement.html('');
|
||||
|
|
@ -574,12 +768,13 @@ function $CompileProvider($provide) {
|
|||
}
|
||||
|
||||
directives.unshift(syncWidgetDirective);
|
||||
afterWidgetLinkFn = applyDirectivesToNode(directives, tElement, tAttrs);
|
||||
afterWidgetChildrenLinkFn = compileNodes(tElement.contents());
|
||||
afterWidgetLinkFn = /* directiveLinkingFn */ applyDirectivesToNode(directives, tElement, tAttrs, transcludeFn);
|
||||
afterWidgetChildrenLinkFn = /* nodesetLinkingFn */ compileNodes(tElement.contents(), transcludeFn);
|
||||
|
||||
|
||||
while(linkQueue.length) {
|
||||
var linkRootElement = linkQueue.pop(),
|
||||
var controller = linkQueue.pop(),
|
||||
linkRootElement = linkQueue.pop(),
|
||||
cLinkNode = linkQueue.pop(),
|
||||
scope = linkQueue.pop(),
|
||||
node = templateNode;
|
||||
|
|
@ -590,8 +785,8 @@ function $CompileProvider($provide) {
|
|||
replaceWith(linkRootElement, jqLite(cLinkNode), node);
|
||||
}
|
||||
afterWidgetLinkFn(function() {
|
||||
beforeWidgetLinkFn(afterWidgetChildrenLinkFn, scope, node);
|
||||
}, scope, node);
|
||||
beforeWidgetLinkFn(afterWidgetChildrenLinkFn, scope, node, rootElement, controller);
|
||||
}, scope, node, rootElement, controller);
|
||||
}
|
||||
linkQueue = null;
|
||||
}).
|
||||
|
|
@ -599,15 +794,17 @@ function $CompileProvider($provide) {
|
|||
throw Error('Failed to load template: ' + config.url);
|
||||
});
|
||||
|
||||
return function(ignoreChildLinkingFn, scope, node, rootElement) {
|
||||
return /* directiveLinkingFn */ function(ignoreChildLinkingFn, scope, node, rootElement,
|
||||
controller) {
|
||||
if (linkQueue) {
|
||||
linkQueue.push(scope);
|
||||
linkQueue.push(node);
|
||||
linkQueue.push(rootElement);
|
||||
linkQueue.push(controller);
|
||||
} else {
|
||||
afterWidgetLinkFn(function() {
|
||||
beforeWidgetLinkFn(afterWidgetChildrenLinkFn, scope, node);
|
||||
}, scope, node);
|
||||
beforeWidgetLinkFn(afterWidgetChildrenLinkFn, scope, node, rootElement, controller);
|
||||
}, scope, node, rootElement, controller);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -759,3 +956,24 @@ var PREFIX_REGEXP = /^(x[\:\-_]|data[\:\-_])/i;
|
|||
function directiveNormalize(name) {
|
||||
return camelCase(name.replace(PREFIX_REGEXP, ''));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Closure compiler type information
|
||||
*/
|
||||
|
||||
function nodesetLinkingFn(
|
||||
/* angular.Scope */ scope,
|
||||
/* NodeList */ nodeList,
|
||||
/* Element */ rootElement,
|
||||
/* function(Function) */ boundTranscludeFn
|
||||
){}
|
||||
|
||||
function directiveLinkingFn(
|
||||
/* nodesetLinkingFn */ nodesetLinkingFn,
|
||||
/* angular.Scope */ scope,
|
||||
/* Node */ node,
|
||||
/* Element */ rootElement,
|
||||
/* function(Function) */ boundTranscludeFn
|
||||
){}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
'use strict';
|
||||
|
||||
function $ControllerProvider() {
|
||||
this.$get = ['$injector', function($injector) {
|
||||
this.$get = ['$injector', '$window', function($injector, $window) {
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name angular.module.ng.$controller
|
||||
* @requires $injector
|
||||
*
|
||||
* @param {Function} Class Constructor function of a controller to instantiate.
|
||||
* @param {Object} scope Related scope.
|
||||
* @param {Function|string} Class Constructor function of a controller to instantiate, or
|
||||
* expression to read from current scope or window.
|
||||
* @param {Object} locals Injection locals for Controller.
|
||||
* @return {Object} Instance of given controller.
|
||||
*
|
||||
* @description
|
||||
|
|
@ -19,8 +20,14 @@ function $ControllerProvider() {
|
|||
* a service, so that one can override this service with {@link https://gist.github.com/1649788
|
||||
* BC version}.
|
||||
*/
|
||||
return function(Class, scope) {
|
||||
return $injector.instantiate(Class, {$scope: scope});
|
||||
return function(Class, locals) {
|
||||
if(isString(Class)) {
|
||||
var expression = Class;
|
||||
Class = getter(locals.$scope, expression, true) || getter($window, expression, true);
|
||||
assertArgFn(Class, expression);
|
||||
}
|
||||
|
||||
return $injector.instantiate(Class, locals);
|
||||
};
|
||||
}];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ function $FormFactoryProvider() {
|
|||
|
||||
function formFactory(parent) {
|
||||
var scope = (parent || formFactory.rootForm).$new();
|
||||
$controller(FormController, scope);
|
||||
$controller(FormController, {$scope: scope});
|
||||
return scope;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -280,7 +280,7 @@ function $RouteProvider(){
|
|||
copy(next.params, $routeParams);
|
||||
next.scope = parentScope.$new();
|
||||
if (next.controller) {
|
||||
$controller(next.controller, next.scope);
|
||||
$controller(next.controller, {$scope: next.scope});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -136,20 +136,36 @@ function $RootScopeProvider(){
|
|||
* the scope and its child scopes to be permanently detached from the parent and thus stop
|
||||
* participating in model change detection and listener notification by invoking.
|
||||
*
|
||||
* @params {boolean} isolate if true then the scoped does not prototypically inherit from the
|
||||
* parent scope. The scope is isolated, as it can not se parent scope properties.
|
||||
* When creating widgets it is useful for the widget to not accidently read parent
|
||||
* state.
|
||||
*
|
||||
* @returns {Object} The newly created child scope.
|
||||
*
|
||||
*/
|
||||
$new: function() {
|
||||
var Child = function() {}; // should be anonymous; This is so that when the minifier munges
|
||||
// the name it does not become random set of chars. These will then show up as class
|
||||
// name in the debugger.
|
||||
var child;
|
||||
Child.prototype = this;
|
||||
child = new Child();
|
||||
$new: function(isolate) {
|
||||
var Child,
|
||||
child;
|
||||
|
||||
if (isFunction(isolate)) {
|
||||
// TODO: remove at some point
|
||||
throw Error('API-CHANGE: Use $controller to instantiate controllers.');
|
||||
}
|
||||
if (isolate) {
|
||||
child = new Scope();
|
||||
child.$root = this.$root;
|
||||
} else {
|
||||
Child = function() {}; // should be anonymous; This is so that when the minifier munges
|
||||
// the name it does not become random set of chars. These will then show up as class
|
||||
// name in the debugger.
|
||||
Child.prototype = this;
|
||||
child = new Child();
|
||||
child.$id = nextUid();
|
||||
}
|
||||
child['this'] = child;
|
||||
child.$$listeners = {};
|
||||
child.$parent = this;
|
||||
child.$id = nextUid();
|
||||
child.$$asyncQueue = [];
|
||||
child.$$watchers = child.$$nextSibling = child.$$childHead = child.$$childTail = null;
|
||||
child.$$prevSibling = this.$$childTail;
|
||||
|
|
@ -277,7 +293,7 @@ function $RootScopeProvider(){
|
|||
* `'Maximum iteration limit exceeded.'` if the number of iterations exceeds 100.
|
||||
*
|
||||
* Usually you don't call `$digest()` directly in
|
||||
* {@link angular.module.ng.$compileProvider.directive.ng:controller controllers} or in
|
||||
* {@link angular.module.ng.$compileProvider.directive.ng:controller controllers} or in
|
||||
* {@link angular.module.ng.$compileProvider.directive directives}.
|
||||
* Instead a call to {@link angular.module.ng.$rootScope.Scope#$apply $apply()} (typically from within a
|
||||
* {@link angular.module.ng.$compileProvider.directive directives}) will force a `$digest()`.
|
||||
|
|
|
|||
|
|
@ -760,7 +760,7 @@ var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interp
|
|||
var BRACE = /{}/g;
|
||||
return function(scope, element, attr) {
|
||||
var numberExp = attr.count,
|
||||
whenExp = attr.when,
|
||||
whenExp = element.attr(attr.$attr.when), // this is becaues we have {{}} in attrs
|
||||
offset = attr.offset || 0,
|
||||
whens = scope.$eval(whenExp),
|
||||
whensExpFns = {};
|
||||
|
|
|
|||
|
|
@ -207,7 +207,12 @@ describe('$compile', function() {
|
|||
forEach(parts, function(value, key){
|
||||
if (value.substring(0,3) == 'ng-') {
|
||||
} else {
|
||||
list.push(value.replace('=""', ''));
|
||||
value = value.replace('=""', '');
|
||||
var match = value.match(/=(.*)/);
|
||||
if (match && match[1].charAt(0) != '"') {
|
||||
value = value.replace(/=(.*)/, '="$1"');
|
||||
}
|
||||
list.push(value);
|
||||
}
|
||||
});
|
||||
return '<' + list.join(' ') + '>';
|
||||
|
|
@ -864,6 +869,7 @@ describe('$compile', function() {
|
|||
|
||||
|
||||
describe('scope', function() {
|
||||
var iscope;
|
||||
|
||||
beforeEach(module(function($compileProvider) {
|
||||
forEach(['', 'a', 'b'], function(name) {
|
||||
|
|
@ -878,6 +884,31 @@ describe('$compile', function() {
|
|||
}
|
||||
};
|
||||
});
|
||||
$compileProvider.directive('iscope' + uppercase(name), function(log) {
|
||||
return {
|
||||
scope: {},
|
||||
compile: function() {
|
||||
return function (scope, element) {
|
||||
iscope = scope;
|
||||
log(scope.$id);
|
||||
expect(element.data('$scope')).toBe(scope);
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
$compileProvider.directive('tiscope' + uppercase(name), function(log) {
|
||||
return {
|
||||
scope: {},
|
||||
templateUrl: 'tiscope.html',
|
||||
compile: function() {
|
||||
return function (scope, element) {
|
||||
iscope = scope;
|
||||
log(scope.$id);
|
||||
expect(element.data('$scope')).toBe(scope);
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
$compileProvider.directive('log', function(log) {
|
||||
return function(scope) {
|
||||
|
|
@ -894,37 +925,80 @@ describe('$compile', function() {
|
|||
}));
|
||||
|
||||
|
||||
it('should correctly create the scope hierachy properly', inject(
|
||||
function($rootScope, $compile, log) {
|
||||
element = $compile(
|
||||
'<div>' + //1
|
||||
'<b class=scope>' + //2
|
||||
'<b class=scope><b class=log></b></b>' + //3
|
||||
'<b class=log></b>' +
|
||||
'</b>' +
|
||||
'<b class=scope>' + //4
|
||||
'<b class=log></b>' +
|
||||
'</b>' +
|
||||
'</div>'
|
||||
)($rootScope);
|
||||
expect(log).toEqual('LOG; log-003-002; 003; LOG; log-002-001; 002; LOG; log-004-001; 004');
|
||||
it('should allow creation of new isolated scopes', inject(function($rootScope, $compile, log) {
|
||||
element = $compile('<div><span iscope><a log></a></span></div>')($rootScope);
|
||||
expect(log).toEqual('LOG; log-002-001; 002');
|
||||
$rootScope.name = 'abc';
|
||||
expect(iscope.$parent).toBe($rootScope);
|
||||
expect(iscope.name).toBeUndefined();
|
||||
}));
|
||||
|
||||
|
||||
it('should not allow more then one scope creation per element', inject(
|
||||
it('should allow creation of new isolated scopes', inject(
|
||||
function($rootScope, $compile, log, $httpBackend) {
|
||||
$httpBackend.expect('GET', 'tiscope.html').respond('<a log></a>');
|
||||
element = $compile('<div><span tiscope></span></div>')($rootScope);
|
||||
$httpBackend.flush();
|
||||
expect(log).toEqual('LOG; log-002-001; 002');
|
||||
$rootScope.name = 'abc';
|
||||
expect(iscope.$parent).toBe($rootScope);
|
||||
expect(iscope.name).toBeUndefined();
|
||||
}));
|
||||
|
||||
|
||||
it('should correctly create the scope hierachy properly', inject(
|
||||
function($rootScope, $compile, log) {
|
||||
element = $compile(
|
||||
'<div>' + //1
|
||||
'<b class=scope>' + //2
|
||||
'<b class=scope><b class=log></b></b>' + //3
|
||||
'<b class=log></b>' +
|
||||
'</b>' +
|
||||
'<b class=scope>' + //4
|
||||
'<b class=log></b>' +
|
||||
'</b>' +
|
||||
'</div>'
|
||||
)($rootScope);
|
||||
expect(log).toEqual('LOG; log-003-002; 003; LOG; log-002-001; 002; LOG; log-004-001; 004');
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
it('should allow more then one scope creation per element', inject(
|
||||
function($rootScope, $compile, log) {
|
||||
$compile('<div class="scope-a; scope-b"></div>')($rootScope);
|
||||
expect(log).toEqual('001; 001');
|
||||
})
|
||||
);
|
||||
|
||||
it('should not allow more then one isolate scope creation per element', inject(
|
||||
function($rootScope, $compile) {
|
||||
expect(function(){
|
||||
$compile('<div class="scope-a; scope-b"></div>');
|
||||
}).toThrow('Multiple directives [scopeA, scopeB] asking for new scope on: ' +
|
||||
'<' + (msie < 9 ? 'DIV' : 'div') + ' class="scope-a; scope-b ng-scope">');
|
||||
}));
|
||||
$compile('<div class="iscope-a; scope-b"></div>');
|
||||
}).toThrow('Multiple directives [iscopeA, scopeB] asking for isolated scope on: ' +
|
||||
'<' + (msie < 9 ? 'DIV' : 'div') +
|
||||
' class="iscope-a; scope-b ng-isolate-scope ng-scope">');
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
it('should not allow more then one isolate scope creation per element', inject(
|
||||
function($rootScope, $compile) {
|
||||
expect(function(){
|
||||
$compile('<div class="iscope-a; iscope-b"></div>');
|
||||
}).toThrow('Multiple directives [iscopeA, iscopeB] asking for isolated scope on: ' +
|
||||
'<' + (msie < 9 ? 'DIV' : 'div') +
|
||||
' class="iscope-a; iscope-b ng-isolate-scope ng-scope">');
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
it('should treat new scope on new template as noop', inject(
|
||||
function($rootScope, $compile, log) {
|
||||
element = $compile('<div scope-a></div>')($rootScope);
|
||||
expect(log).toEqual('001');
|
||||
}));
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1193,4 +1267,359 @@ describe('$compile', function() {
|
|||
})
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('locals', function() {
|
||||
it('should marshal to locals', function() {
|
||||
module(function($compileProvider) {
|
||||
$compileProvider.directive('widget', function(log) {
|
||||
return {
|
||||
scope: {
|
||||
attr: 'attribute',
|
||||
prop: 'evaluate',
|
||||
bind: 'bind',
|
||||
assign: 'accessor',
|
||||
read: 'accessor',
|
||||
exp: 'expression',
|
||||
nonExist: 'accessor',
|
||||
nonExistExpr: 'expression'
|
||||
},
|
||||
link: function(scope, element, attrs) {
|
||||
scope.nonExist(); // noop
|
||||
scope.nonExist(123); // noop
|
||||
scope.nonExistExpr(); // noop
|
||||
scope.nonExistExpr(123); // noop
|
||||
log(scope.attr);
|
||||
log(scope.prop);
|
||||
log(scope.assign());
|
||||
log(scope.read());
|
||||
log(scope.assign('ng'));
|
||||
scope.exp({myState:'OK'});
|
||||
expect(function() { scope.read(undefined); }).
|
||||
toThrow("Expression ''D'' not assignable.");
|
||||
scope.$watch('bind', log);
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
inject(function(log, $compile, $rootScope) {
|
||||
$rootScope.myProp = 'B';
|
||||
$rootScope.bi = {nd: 'C'};
|
||||
$rootScope.name = 'C';
|
||||
element = $compile(
|
||||
'<div><div widget attr="A" prop="myProp" bind="{{bi.nd}}" assign="name" read="\'D\'" ' +
|
||||
'exp="state=myState">{{bind}}</div></div>')
|
||||
($rootScope);
|
||||
expect(log).toEqual('A; B; C; D; ng');
|
||||
expect($rootScope.name).toEqual('ng');
|
||||
expect($rootScope.state).toEqual('OK');
|
||||
log.reset();
|
||||
$rootScope.$apply();
|
||||
expect(element.text()).toEqual('C');
|
||||
expect(log).toEqual('C');
|
||||
$rootScope.bi.nd = 'c';
|
||||
$rootScope.$apply();
|
||||
expect(log).toEqual('C; c');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('controller', function() {
|
||||
it('should inject locals to controller', function() {
|
||||
module(function($compileProvider) {
|
||||
$compileProvider.directive('widget', function(log) {
|
||||
return {
|
||||
controller: function(attr, prop, assign, read, exp){
|
||||
log(attr);
|
||||
log(prop);
|
||||
log(assign());
|
||||
log(read());
|
||||
log(assign('ng'));
|
||||
exp();
|
||||
expect(function() { read(undefined); }).
|
||||
toThrow("Expression ''D'' not assignable.");
|
||||
this.result = 'OK';
|
||||
},
|
||||
inject: {
|
||||
attr: 'attribute',
|
||||
prop: 'evaluate',
|
||||
assign: 'accessor',
|
||||
read: 'accessor',
|
||||
exp: 'expression'
|
||||
},
|
||||
link: function(scope, element, attrs, controller) {
|
||||
log(controller.result);
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
inject(function(log, $compile, $rootScope) {
|
||||
$rootScope.myProp = 'B';
|
||||
$rootScope.bi = {nd: 'C'};
|
||||
$rootScope.name = 'C';
|
||||
element = $compile(
|
||||
'<div><div widget attr="A" prop="myProp" bind="{{bi.nd}}" assign="name" read="\'D\'" ' +
|
||||
'exp="state=\'OK\'">{{bind}}</div></div>')
|
||||
($rootScope);
|
||||
expect(log).toEqual('A; B; C; D; ng; OK');
|
||||
expect($rootScope.name).toEqual('ng');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should get required controller', function() {
|
||||
module(function($compileProvider) {
|
||||
$compileProvider.directive('main', function(log) {
|
||||
return {
|
||||
priority: 2,
|
||||
controller: function() {
|
||||
this.name = 'main';
|
||||
},
|
||||
link: function(scope, element, attrs, controller) {
|
||||
log(controller.name);
|
||||
}
|
||||
};
|
||||
});
|
||||
$compileProvider.directive('dep', function(log) {
|
||||
return {
|
||||
priority: 1,
|
||||
require: 'main',
|
||||
link: function(scope, element, attrs, controller) {
|
||||
log('dep:' + controller.name);
|
||||
}
|
||||
};
|
||||
});
|
||||
$compileProvider.directive('other', function(log) {
|
||||
return {
|
||||
link: function(scope, element, attrs, controller) {
|
||||
log(!!controller); // should be false
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
inject(function(log, $compile, $rootScope) {
|
||||
element = $compile('<div main dep other></div>')($rootScope);
|
||||
expect(log).toEqual('main; dep:main; false');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should require controller on parent element',function() {
|
||||
module(function($compileProvider) {
|
||||
$compileProvider.directive('main', function(log) {
|
||||
return {
|
||||
controller: function() {
|
||||
this.name = 'main';
|
||||
}
|
||||
};
|
||||
});
|
||||
$compileProvider.directive('dep', function(log) {
|
||||
return {
|
||||
require: '^main',
|
||||
link: function(scope, element, attrs, controller) {
|
||||
log('dep:' + controller.name);
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
inject(function(log, $compile, $rootScope) {
|
||||
element = $compile('<div main><div dep></div></div>')($rootScope);
|
||||
expect(log).toEqual('dep:main');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should have optional controller on current element', function() {
|
||||
module(function($compileProvider) {
|
||||
$compileProvider.directive('dep', function(log) {
|
||||
return {
|
||||
require: '?main',
|
||||
link: function(scope, element, attrs, controller) {
|
||||
log('dep:' + !!controller);
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
inject(function(log, $compile, $rootScope) {
|
||||
element = $compile('<div main><div dep></div></div>')($rootScope);
|
||||
expect(log).toEqual('dep:false');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should support multiple controllers', function() {
|
||||
module(function($compileProvider) {
|
||||
$compileProvider.directive('c1', valueFn({
|
||||
controller: function() { this.name = 'c1'; }
|
||||
}));
|
||||
$compileProvider.directive('c2', valueFn({
|
||||
controller: function() { this.name = 'c2'; }
|
||||
}));
|
||||
$compileProvider.directive('dep', function(log) {
|
||||
return {
|
||||
require: ['^c1', '^c2'],
|
||||
link: function(scope, element, attrs, controller) {
|
||||
log('dep:' + controller[0].name + '-' + controller[1].name);
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
inject(function(log, $compile, $rootScope) {
|
||||
element = $compile('<div c1 c2><div dep></div></div>')($rootScope);
|
||||
expect(log).toEqual('dep:c1-c2');
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('transclude', function() {
|
||||
it('should compile get templateFn', function() {
|
||||
module(function($compileProvider) {
|
||||
$compileProvider.directive('trans', function(log) {
|
||||
return {
|
||||
transclude: 'element',
|
||||
priority: 2,
|
||||
controller: function($transclude) { this.$transclude = $transclude; },
|
||||
compile: function(element, attrs, template) {
|
||||
log('compile: ' + angular.mock.dump(element));
|
||||
return function(scope, element, attrs, ctrl) {
|
||||
log('link');
|
||||
var cursor = element;
|
||||
template(scope.$new(), function(clone) {cursor.after(cursor = clone)});
|
||||
ctrl.$transclude(function(clone) {cursor.after(clone)});
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
inject(function(log, $rootScope, $compile) {
|
||||
element = $compile('<div><div high-log trans="text" log>{{$parent.$id}}-{{$id}};</div></div>')
|
||||
($rootScope);
|
||||
$rootScope.$apply();
|
||||
expect(log).toEqual('compile: <!-- trans: text -->; HIGH; link; LOG; LOG');
|
||||
expect(element.text()).toEqual('001-002;001-003;');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should support transclude directive', function() {
|
||||
module(function($compileProvider) {
|
||||
$compileProvider.directive('trans', function() {
|
||||
return {
|
||||
transclude: 'content',
|
||||
replace: true,
|
||||
scope: true,
|
||||
template: '<ul><li>W:{{$parent.$id}}-{{$id}};</li><li ng-transclude></li></ul>'
|
||||
}
|
||||
});
|
||||
});
|
||||
inject(function(log, $rootScope, $compile) {
|
||||
element = $compile('<div><div trans>T:{{$parent.$id}}-{{$id}}<span>;</span></div></div>')
|
||||
($rootScope);
|
||||
$rootScope.$apply();
|
||||
expect(element.text()).toEqual('W:001-002;T:001-003;');
|
||||
expect(jqLite(element.find('span')[0]).text()).toEqual('T:001-003');
|
||||
expect(jqLite(element.find('span')[1]).text()).toEqual(';');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should transclude transcluded content', function() {
|
||||
module(function($compileProvider) {
|
||||
$compileProvider.directive('book', valueFn({
|
||||
transclude: 'content',
|
||||
template: '<div>book-<div chapter>(<div ng-transclude></div>)</div></div>'
|
||||
}));
|
||||
$compileProvider.directive('chapter', valueFn({
|
||||
transclude: 'content',
|
||||
templateUrl: 'chapter.html'
|
||||
}));
|
||||
$compileProvider.directive('section', valueFn({
|
||||
transclude: 'content',
|
||||
template: '<div>section-!<div ng-transclude></div>!</div></div>'
|
||||
}));
|
||||
return function($httpBackend) {
|
||||
$httpBackend.
|
||||
expect('GET', 'chapter.html').
|
||||
respond('<div>chapter-<div section>[<div ng-transclude></div>]</div></div>');
|
||||
}
|
||||
});
|
||||
inject(function(log, $rootScope, $compile, $httpBackend) {
|
||||
element = $compile('<div><div book>paragraph</div></div>')($rootScope);
|
||||
$rootScope.$apply();
|
||||
|
||||
expect(element.text()).toEqual('book-');
|
||||
|
||||
$httpBackend.flush();
|
||||
$rootScope.$apply();
|
||||
expect(element.text()).toEqual('book-chapter-section-![(paragraph)]!');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should only allow one transclude per element', function() {
|
||||
module(function($compileProvider) {
|
||||
$compileProvider.directive('first', valueFn({
|
||||
scope: {},
|
||||
transclude: 'content'
|
||||
}));
|
||||
$compileProvider.directive('second', valueFn({
|
||||
transclude: 'content'
|
||||
}));
|
||||
});
|
||||
inject(function($compile) {
|
||||
expect(function() {
|
||||
$compile('<div class="first second"></div>');
|
||||
}).toThrow('Multiple directives [first, second] asking for transclusion on: <' +
|
||||
(msie <= 8 ? 'DIV' : 'div') + ' class="first second ng-isolate-scope ng-scope">');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should remove transclusion scope, when the DOM is destroyed', function() {
|
||||
module(function($compileProvider) {
|
||||
$compileProvider.directive('box', valueFn({
|
||||
transclude: 'content',
|
||||
scope: { name: 'evaluate', show: 'accessor' },
|
||||
template: '<div><h1>Hello: {{name}}!</h1><div ng-transclude></div></div>',
|
||||
link: function(scope, element) {
|
||||
scope.$watch(
|
||||
function() { return scope.show(); },
|
||||
function(show) {
|
||||
if (!show) {
|
||||
element.find('div').find('div').remove();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}));
|
||||
});
|
||||
inject(function($compile, $rootScope) {
|
||||
$rootScope.username = 'Misko';
|
||||
$rootScope.select = true;
|
||||
element = $compile(
|
||||
'<div><div box name="username" show="select">user: {{username}}</div></div>')
|
||||
($rootScope);
|
||||
$rootScope.$apply();
|
||||
expect(element.text()).toEqual('Hello: Misko!user: Misko');
|
||||
|
||||
var widgetScope = $rootScope.$$childHead;
|
||||
var transcludeScope = widgetScope.$$nextSibling;
|
||||
expect(widgetScope.name).toEqual('Misko');
|
||||
expect(widgetScope.$parent).toEqual($rootScope);
|
||||
expect(transcludeScope.$parent).toEqual($rootScope);
|
||||
|
||||
var removed = 0;
|
||||
$rootScope.$on('$destroy', function() { removed++; });
|
||||
$rootScope.select = false;
|
||||
$rootScope.$apply();
|
||||
expect(element.text()).toEqual('Hello: Misko!');
|
||||
expect(removed).toEqual(1);
|
||||
expect(widgetScope.$$nextSibling).toEqual(null);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ describe('$controller', function() {
|
|||
};
|
||||
|
||||
var scope = {},
|
||||
ctrl = $controller(MyClass, scope);
|
||||
ctrl = $controller(MyClass, {$scope: scope});
|
||||
|
||||
expect(ctrl.$scope).toBe(scope);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -53,6 +53,15 @@ describe('Scope', function() {
|
|||
$rootScope.a = 123;
|
||||
expect(child.a).toEqual(123);
|
||||
}));
|
||||
|
||||
it('should create a non prototypically inherited child scope', inject(function($rootScope) {
|
||||
var child = $rootScope.$new(true);
|
||||
$rootScope.a = 123;
|
||||
expect(child.a).toBeUndefined();
|
||||
expect(child.$parent).toEqual($rootScope);
|
||||
expect(child.$new).toBe($rootScope.$new);
|
||||
expect(child.$root).toBe($rootScope);
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue