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.
To migrate simply remove the call to $init() and move any code you had before $init() to the
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/>

View file

@ -44,10 +44,6 @@ raw DOM references.
version:
- `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.
@returns {Object} jQuery object.

View file

@ -803,17 +803,18 @@ function merge(src, dst) {
</pre>
*
* @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:
*
* * `scope` - {@link angular.scope scope} A scope to bind to. If none specified, then a new
* root scope is created.
* * `element` - {@link angular.element element} Element to use as the template. If none
* specified then reuse the element from `angular.compile(element)`. If `true`
* then clone the `angular.compile(element)`. The element must be either the same
* element as `angular.compile(element)` or an identical clone to
* `angular.compile(element)`. Using an element with differnt structure will cause
* unpredictable behavior.
* * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the
* `template` and call the `cloneAttachFn` allowing the caller to attach the
* clonned elements to the DOM at the approriate place. The `cloneAttachFn` is
* called as: <br/> `cloneAttachFn(clonedElement, scope)`:
*
* * `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:
*
@ -1006,7 +1007,7 @@ function toKeyValue(obj) {
function angularInit(config){
if (config.autobind) {
// 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');
if (config.css)
@ -1048,8 +1049,7 @@ function bindJQuery(){
if (jQuery) {
jqLite = jQuery;
extend(jQuery.fn, {
scope: JQLitePrototype.scope,
cloneNode: JQLitePrototype.cloneNode
scope: JQLitePrototype.scope
});
} else {
jqLite = jqLiteWrap;

View file

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

View file

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

View file

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

View file

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

View file

@ -417,7 +417,7 @@ describe('Binder', 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();
var d1 = jqLite(x.view[0].childNodes[1]);
var d2 = jqLite(x.view[0].childNodes[2]);

View file

@ -47,7 +47,7 @@ describe('compiler', function(){
};
var template = compiler.compile(e);
expect(log).toEqual("found");
scope = template(angular.scope(), e).scope;
scope = template(angular.scope()).scope;
expect(e.hasClass('ng-directive')).toEqual(true);
expect(log).toEqual("found:init");
});
@ -84,7 +84,7 @@ describe('compiler', function(){
var template = this.compile(element);
return function(marker) {
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);
});
});
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(){
it('should find child by name', function(){
var root = jqLite('<div><div>text</div></div>');