Changed the angular.compile(element)(scope[, cloneAttachNode])

This commit is contained in:
Misko Hevery 2011-02-13 16:13:21 -08:00
parent cdc093a463
commit c90abf057b
10 changed files with 57 additions and 44 deletions

View file

@ -12,7 +12,7 @@
recommended way to deal with initializing scope is to put it in the root constructor controller. recommended way to deal with initializing scope is to put it in the root constructor controller.
To migrate simply remove the call to $init() and move any code you had before $init() to the To migrate simply remove the call to $init() and move any code you had before $init() to the
root controller. root controller.
- Change API angular.compile(..) to angular.compile(element)([scope], [element/true]) - Change API angular.compile(..) to angular.compile(element)([scope], [cloneAttachFn])
<a name="0.9.11"><a/> <a name="0.9.11"><a/>

View file

@ -44,10 +44,6 @@ raw DOM references.
version: version:
- `scope()` - retrieves the current angular scope of the element. - `scope()` - retrieves the current angular scope of the element.
- `cloneNode()` - Clones the current node, ensuring identical structure. This is important since
the `clone()` method implemented by jQuery under some circumstances changes the DOM
structure, which then prevents proper application of compiled template to the cloned node.
__Always use `cloneNode()` when cloning previously compiled templates.__
@param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery. @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery.
@returns {Object} jQuery object. @returns {Object} jQuery object.

View file

@ -803,17 +803,18 @@ function merge(src, dst) {
</pre> </pre>
* *
* @param {string|DOMElement} element Element or HTML to compile into a template function. * @param {string|DOMElement} element Element or HTML to compile into a template function.
* @returns {function([scope][, element])} a template function which is used to bind element * @returns {function([scope][, cloneAttachFn])} a template function which is used to bind element
* and scope. Where: * and scope. Where:
* *
* * `scope` - {@link angular.scope scope} A scope to bind to. If none specified, then a new * * `scope` - {@link angular.scope scope} A scope to bind to. If none specified, then a new
* root scope is created. * root scope is created.
* * `element` - {@link angular.element element} Element to use as the template. If none * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the
* specified then reuse the element from `angular.compile(element)`. If `true` * `template` and call the `cloneAttachFn` allowing the caller to attach the
* then clone the `angular.compile(element)`. The element must be either the same * clonned elements to the DOM at the approriate place. The `cloneAttachFn` is
* element as `angular.compile(element)` or an identical clone to * called as: <br/> `cloneAttachFn(clonedElement, scope)`:
* `angular.compile(element)`. Using an element with differnt structure will cause *
* unpredictable behavior. * * `clonedElement` - is a clone of the originale `element` passed into the compiler.
* * `scope` - is the current scope with which the linking function is working with.
* *
* Calling the template function returns object: `{scope:?, view:?}`, where: * Calling the template function returns object: `{scope:?, view:?}`, where:
* *
@ -1006,7 +1007,7 @@ function toKeyValue(obj) {
function angularInit(config){ function angularInit(config){
if (config.autobind) { if (config.autobind) {
// TODO default to the source of angular.js // TODO default to the source of angular.js
var scope = compile(window.document)(null, createScope({'$config':config})), var scope = compile(window.document)(createScope({'$config':config})).scope,
$browser = scope.$service('$browser'); $browser = scope.$service('$browser');
if (config.css) if (config.css)
@ -1048,8 +1049,7 @@ function bindJQuery(){
if (jQuery) { if (jQuery) {
jqLite = jQuery; jqLite = jQuery;
extend(jQuery.fn, { extend(jQuery.fn, {
scope: JQLitePrototype.scope, scope: JQLitePrototype.scope
cloneNode: JQLitePrototype.cloneNode
}); });
} else { } else {
jqLite = jqLiteWrap; jqLite = jqLiteWrap;

View file

@ -93,14 +93,17 @@ Compiler.prototype = {
} }
} }
template = this.templatize(templateElement, index, 0) || new Template(); template = this.templatize(templateElement, index, 0) || new Template();
return function(scope, element){ return function(scope, cloneConnectFn){
scope = scope || createScope(); // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
element = element === true // and sometimes changes the structure of the DOM.
? templateElement.cloneNode() var element = cloneConnectFn
: (element ? jqLite(element) : templateElement); ? JQLitePrototype.clone.call(templateElement) // IMPORTAN!!!
: templateElement;
scope = scope || createScope();
element.data($$scope, scope); element.data($$scope, scope);
template.attach(element, scope);
scope.$element = element; scope.$element = element;
(cloneConnectFn||noop)(element, scope);
template.attach(element, scope);
scope.$eval(); scope.$eval();
return {scope:scope, view:element}; return {scope:scope, view:element};
}; };

View file

@ -72,7 +72,7 @@ function JQLite(element) {
} }
} }
function JQLiteCloneNode(element) { function JQLiteClone(element) {
return element.cloneNode(true); return element.cloneNode(true);
} }
@ -370,12 +370,15 @@ forEach({
return element.parentNode || null; return element.parentNode || null;
}, },
next: function(element) {
return element.nextSibling;
},
find: function(element, selector) { find: function(element, selector) {
return element.getElementsByTagName(selector); return element.getElementsByTagName(selector);
}, },
clone: JQLiteCloneNode, clone: JQLiteClone
cloneNode: JQLiteCloneNode
}, function(fn, name){ }, function(fn, name){
/** /**
* chaining functions * chaining functions

View file

@ -790,10 +790,10 @@ var ngSwitch = angularWidget('ng:switch', function (element){
forEach(cases, function(switchCase){ forEach(cases, function(switchCase){
if (!found && switchCase.when(childScope, value)) { if (!found && switchCase.when(childScope, value)) {
found = true; found = true;
var caseElement = switchCase.element.cloneNode();
element.append(caseElement);
childScope.$tryEval(switchCase.change, element); childScope.$tryEval(switchCase.change, element);
switchCase.template(childScope, caseElement); switchCase.template(childScope, function(caseElement){
element.append(caseElement);
});
} }
}); });
}); });
@ -886,11 +886,11 @@ angularWidget('a', function() {
</doc:scenario> </doc:scenario>
</doc:example> </doc:example>
*/ */
angularWidget("@ng:repeat", function(expression, element){ angularWidget('@ng:repeat', function(expression, element){
element.removeAttr('ng:repeat'); element.removeAttr('ng:repeat');
element.replaceWith(jqLite("<!-- ng:repeat: " + expression + " --!>")); element.replaceWith(jqLite('<!-- ng:repeat: ' + expression + ' --!>'));
var linker = this.compile(element); var linker = this.compile(element);
return function(reference){ return function(iterStartElement){
var match = expression.match(/^\s*(.+)\s+in\s+(.*)\s*$/), var match = expression.match(/^\s*(.+)\s+in\s+(.*)\s*$/),
lhs, rhs, valueIdent, keyIdent; lhs, rhs, valueIdent, keyIdent;
if (! match) { if (! match) {
@ -910,10 +910,9 @@ angularWidget("@ng:repeat", function(expression, element){
var children = [], currentScope = this; var children = [], currentScope = this;
this.$onEval(function(){ this.$onEval(function(){
var index = 0, var index = 0,
cloneElement,
childCount = children.length, childCount = children.length,
lastElement = reference, lastIterElement = iterStartElement,
collection = this.$tryEval(rhs, reference), collection = this.$tryEval(rhs, iterStartElement),
is_array = isArray(collection), is_array = isArray(collection),
collectionLength = 0, collectionLength = 0,
childScope, childScope,
@ -934,6 +933,7 @@ angularWidget("@ng:repeat", function(expression, element){
childScope = children[index]; childScope = children[index];
childScope[valueIdent] = collection[key]; childScope[valueIdent] = collection[key];
if (keyIdent) childScope[keyIdent] = key; if (keyIdent) childScope[keyIdent] = key;
lastIterElement = childScope.$element;
} else { } else {
// grow children // grow children
childScope = createScope(currentScope); childScope = createScope(currentScope);
@ -943,13 +943,14 @@ angularWidget("@ng:repeat", function(expression, element){
childScope.$position = index == 0 childScope.$position = index == 0
? 'first' ? 'first'
: (index == collectionLength - 1 ? 'last' : 'middle'); : (index == collectionLength - 1 ? 'last' : 'middle');
lastElement.after(cloneElement = element.cloneNode());
cloneElement.attr('ng:repeat-index', index);
linker(childScope, cloneElement);
children.push(childScope); children.push(childScope);
linker(childScope, function(clone){
clone.attr('ng:repeat-index', index);
lastIterElement.after(clone);
lastIterElement = clone;
});
} }
childScope.$eval(); childScope.$eval();
lastElement = childScope.$element;
index ++; index ++;
} }
} }
@ -957,7 +958,7 @@ angularWidget("@ng:repeat", function(expression, element){
while(children.length > index) { while(children.length > index) {
children.pop().$element.remove(); children.pop().$element.remove();
} }
}, reference); }, iterStartElement);
}; };
}); });

View file

@ -369,8 +369,10 @@ describe('angular', function(){
var scope = angular.scope(); var scope = angular.scope();
var template = jqLite('<div>{{greeting = "hello world"}}</div>'); var template = jqLite('<div>{{greeting = "hello world"}}</div>');
var templateFn = angular.compile(template); var templateFn = angular.compile(template);
var templateClone = template.cloneNode(); var templateClone = template.clone();
mvc = templateFn(scope, templateClone); mvc = templateFn(scope, function(clone){
templateClone = clone;
});
expect(template.text()).toEqual(''); expect(template.text()).toEqual('');
expect(mvc.view.text()).toEqual('hello world'); expect(mvc.view.text()).toEqual('hello world');
expect(mvc.view).toEqual(templateClone); expect(mvc.view).toEqual(templateClone);
@ -380,7 +382,7 @@ describe('angular', function(){
it('should link to cloned node and create scope', function(){ it('should link to cloned node and create scope', function(){
var scope = angular.scope(); var scope = angular.scope();
var template = jqLite('<div>{{greeting = "hello world"}}</div>'); var template = jqLite('<div>{{greeting = "hello world"}}</div>');
mvc = angular.compile(template)(scope, true); mvc = angular.compile(template)(scope, noop);
expect(template.text()).toEqual(''); expect(template.text()).toEqual('');
expect(mvc.view.text()).toEqual('hello world'); expect(mvc.view.text()).toEqual('hello world');
expect(mvc.scope.greeting).toEqual('hello world'); expect(mvc.scope.greeting).toEqual('hello world');

View file

@ -417,7 +417,7 @@ describe('Binder', function(){
}); });
it('BindClassEvenOdd', function(){ it('BindClassEvenOdd', function(){
var x = this.compile('<div><div ng:repeat="i in [0,1]" ng:class-even="\'e\'" ng:class-odd="\'o\'"/></div>'); var x = this.compile('<div><div ng:repeat="i in [0,1]" ng:class-even="\'e\'" ng:class-odd="\'o\'"></div></div>');
x.scope.$eval(); x.scope.$eval();
var d1 = jqLite(x.view[0].childNodes[1]); var d1 = jqLite(x.view[0].childNodes[1]);
var d2 = jqLite(x.view[0].childNodes[2]); var d2 = jqLite(x.view[0].childNodes[2]);

View file

@ -47,7 +47,7 @@ describe('compiler', function(){
}; };
var template = compiler.compile(e); var template = compiler.compile(e);
expect(log).toEqual("found"); expect(log).toEqual("found");
scope = template(angular.scope(), e).scope; scope = template(angular.scope()).scope;
expect(e.hasClass('ng-directive')).toEqual(true); expect(e.hasClass('ng-directive')).toEqual(true);
expect(log).toEqual("found:init"); expect(log).toEqual("found:init");
}); });
@ -84,7 +84,7 @@ describe('compiler', function(){
var template = this.compile(element); var template = this.compile(element);
return function(marker) { return function(marker) {
this.$onEval(function() { this.$onEval(function() {
marker.after(template(angular.scope(), true).view); marker.after(template(angular.scope(), noop).view);
}); });
}; };
}; };

View file

@ -300,6 +300,14 @@ describe('jqLite', function(){
expect(element.parent().length).toEqual(0); expect(element.parent().length).toEqual(0);
}); });
}); });
describe('next', function(){
it('should return next sibling', function(){
var element = jqLite('<div><b>b</b><i>i</i></div>');
var b = element.find('b');
var i = element.find('i');
expect(b.next()).toJqEqual([i]);
});
});
describe('find', function(){ describe('find', function(){
it('should find child by name', function(){ it('should find child by name', function(){
var root = jqLite('<div><div>text</div></div>'); var root = jqLite('<div><div>text</div></div>');