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({});