diff --git a/src/Angular.js b/src/Angular.js index a91bf04cf927..aeb5acd76cc4 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -463,12 +463,14 @@ function map(obj, iterator, context) { * @function * * @description - * Determines the number of elements in an array or number of properties of an object. + * Determines the number of elements in an array, number of properties of an object or string + * length. * * Note: this function is used to augment the Object type in angular expressions. See * {@link angular.Object} for more info. * - * @param {Object|Array} obj Object or array to inspect. + * @param {Object|Array|string} obj Object, array or string to inspect. + * @param {boolean} [ownPropsOnly=false] Count only "own" properties in an object * @returns {number} The size of `obj` or `0` if `obj` is neither an object or an array. * * @example @@ -485,18 +487,21 @@ function map(obj, iterator, context) { * * */ -function size(obj) { +function size(obj, ownPropsOnly) { var size = 0, key; - if (obj) { - if (isNumber(obj.length)) { - return obj.length; - } else if (isObject(obj)){ - for (key in obj) + + if (isArray(obj) || isString(obj)) { + return obj.length; + } else if (isObject(obj)){ + for (key in obj) + if (!ownPropsOnly || obj.hasOwnProperty(key)) size++; - } } + return size; } + + function includes(array, obj) { for ( var i = 0; i < array.length; i++) { if (obj === array[i]) return true; diff --git a/src/jqLite.js b/src/jqLite.js index cda8ec7019a9..5c97043dbae3 100644 --- a/src/jqLite.js +++ b/src/jqLite.js @@ -66,7 +66,7 @@ function JQLite(element) { div.innerHTML = '
 
' + element; // IE insanity to make NoScope elements work! div.removeChild(div.firstChild); // remove the superfluous div JQLiteAddNodes(this, div.childNodes); - this.remove(); // detach the elements form the temporary DOM div. + this.remove(); // detach the elements from the temporary DOM div. } else { JQLiteAddNodes(this, element); } @@ -136,8 +136,7 @@ function JQLiteAddNodes(root, elements) { ? elements : [ elements ]; for(var i=0; i < elements.length; i++) { - if (elements[i].nodeType != 11) - root.push(elements[i]); + root.push(elements[i]); } } } diff --git a/src/widgets.js b/src/widgets.js index 91b0fcf24202..1137a265803f 100644 --- a/src/widgets.js +++ b/src/widgets.js @@ -913,19 +913,12 @@ angularWidget('@ng:repeat', function(expression, element){ childCount = children.length, lastIterElement = iterStartElement, collection = this.$tryEval(rhs, iterStartElement), - is_array = isArray(collection), - collectionLength = 0, + collectionLength = size(collection, true), + fragment = (element[0].nodeName != 'OPTION') ? document.createDocumentFragment() : null, + addFragment, childScope, key; - if (is_array) { - collectionLength = collection.length; - } else { - for (key in collection) - if (collection.hasOwnProperty(key)) - collectionLength++; - } - for (key in collection) { if (collection.hasOwnProperty(key)) { if (index < childCount) { @@ -934,6 +927,7 @@ angularWidget('@ng:repeat', function(expression, element){ childScope[valueIdent] = collection[key]; if (keyIdent) childScope[keyIdent] = key; lastIterElement = childScope.$element; + childScope.$eval(); } else { // grow children childScope = createScope(currentScope); @@ -946,14 +940,26 @@ angularWidget('@ng:repeat', function(expression, element){ children.push(childScope); linker(childScope, function(clone){ clone.attr('ng:repeat-index', index); - lastIterElement.after(clone); - lastIterElement = clone; + + if (fragment) { + fragment.appendChild(clone[0]); + addFragment = true; + } else { + //temporarily preserve old way for option element + lastIterElement.after(clone); + lastIterElement = clone; + } }); } - childScope.$eval(); index ++; } } + + //attach new nodes buffered in doc fragment + if (addFragment) { + lastIterElement.after(jqLite(fragment)); + } + // shrink children while(children.length > index) { children.pop().$element.remove(); diff --git a/test/AngularSpec.js b/test/AngularSpec.js index f585ffb7d168..a8489a1b8544 100644 --- a/test/AngularSpec.js +++ b/test/AngularSpec.js @@ -110,6 +110,36 @@ describe('angular', function(){ }); }); + + describe('size', function() { + it('should return the number of items in an array', function() { + expect(size([])).toBe(0); + expect(size(['a', 'b', 'c'])).toBe(3); + }); + + it('should return the number of properties of an object', function() { + expect(size({})).toBe(0); + expect(size({a:1, b:'a', c:noop})).toBe(3); + }); + + it('should return the number of own properties of an object', function() { + var obj = inherit({protoProp: 'c', protoFn: noop}, {a:1, b:'a', c:noop}); + + expect(size(obj)).toBe(5); + expect(size(obj, true)).toBe(3); + }); + + it('should return the string length', function() { + expect(size('')).toBe(0); + expect(size('abc')).toBe(3); + }); + + it('should not rely on length property of an object to determine its size', function() { + expect(size({length:99})).toBe(1); + }); + }); + + describe('parseKeyValue', function() { it('should parse a string into key-value pairs', function() { expect(parseKeyValue('')).toEqual({});