diff options
| -rw-r--r-- | CHANGELOG.md | 2 | ||||
| -rw-r--r-- | docs/angular.element.ngdoc | 4 | ||||
| -rw-r--r-- | src/Angular.js | 20 | ||||
| -rw-r--r-- | src/Compiler.js | 15 | ||||
| -rw-r--r-- | src/jqLite.js | 9 | ||||
| -rw-r--r-- | src/widgets.js | 29 | ||||
| -rw-r--r-- | test/AngularSpec.js | 8 | ||||
| -rw-r--r-- | test/BinderSpec.js | 2 | ||||
| -rw-r--r-- | test/CompilerSpec.js | 4 | ||||
| -rw-r--r-- | test/jqLiteSpec.js | 8 |
10 files changed, 57 insertions, 44 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 579dd61b..57665b9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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/> diff --git a/docs/angular.element.ngdoc b/docs/angular.element.ngdoc index 83680d1f..2ce007fd 100644 --- a/docs/angular.element.ngdoc +++ b/docs/angular.element.ngdoc @@ -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. diff --git a/src/Angular.js b/src/Angular.js index 87be29a7..6e5786ec 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -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; diff --git a/src/Compiler.js b/src/Compiler.js index 77c83846..78d7a2b0 100644 --- a/src/Compiler.js +++ b/src/Compiler.js @@ -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}; }; diff --git a/src/jqLite.js b/src/jqLite.js index 8a507212..c4a10a0b 100644 --- a/src/jqLite.js +++ b/src/jqLite.js @@ -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 diff --git a/src/widgets.js b/src/widgets.js index ce877c71..d58e7789 100644 --- a/src/widgets.js +++ b/src/widgets.js @@ -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); }; }); diff --git a/test/AngularSpec.js b/test/AngularSpec.js index 8ff0631d..8753d887 100644 --- a/test/AngularSpec.js +++ b/test/AngularSpec.js @@ -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'); diff --git a/test/BinderSpec.js b/test/BinderSpec.js index a1b9be14..ce2a9d3a 100644 --- a/test/BinderSpec.js +++ b/test/BinderSpec.js @@ -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]); diff --git a/test/CompilerSpec.js b/test/CompilerSpec.js index 1a93ac78..f1e1c607 100644 --- a/test/CompilerSpec.js +++ b/test/CompilerSpec.js @@ -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); }); }; }; diff --git a/test/jqLiteSpec.js b/test/jqLiteSpec.js index c5e92c0f..e42e9f14 100644 --- a/test/jqLiteSpec.js +++ b/test/jqLiteSpec.js @@ -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>'); |
