Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

Commit

Permalink
fix(angular.forEach): correctly iterate over objects with length prop
Browse files Browse the repository at this point in the history
Should handle JQLite, jQuery, NodeList and other objects like arrays
but not other generic objects or instances of user defined types
with length property.

Closes #1840
  • Loading branch information
IgorMinar committed Feb 11, 2013
1 parent a8e114f commit 8801d9c
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 2 deletions.
26 changes: 25 additions & 1 deletion src/Angular.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,30 @@ var /** holds major version number for IE or NaN for real browsers */
* @param {Object=} context Object to become context (`this`) for the iterator function.
* @returns {Object|Array} Reference to `obj`.
*/


/**
* @private
* @param {*} obj
* @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments, ...)
*/
function isArrayLike(obj) {
if (!obj || (typeof obj.length !== 'number')) return false;

// We have on object which has length property. Should we treat it as array?
if (typeof obj.hasOwnProperty != 'function' &&
typeof obj.constructor != 'function') {
// This is here for IE8: it is a bogus object treat it as array;
return true;
} else {
return obj instanceof JQLite || // JQLite
(jQuery && obj instanceof jQuery) || // jQuery
toString.call(obj) !== '[object Object]' || // some browser native object
typeof obj.callee === 'function'; // arguments (on IE8 looks like regular obj)
}
}


function forEach(obj, iterator, context) {
var key;
if (obj) {
Expand All @@ -101,7 +125,7 @@ function forEach(obj, iterator, context) {
}
} else if (obj.forEach && obj.forEach !== forEach) {
obj.forEach(iterator, context);
} else if (isObject(obj) && isNumber(obj.length)) {
} else if (isArrayLike(obj)) {
for (key = 0; key < obj.length; key++)
iterator.call(context, obj[key], key);
} else {
Expand Down
73 changes: 72 additions & 1 deletion test/AngularSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ describe('angular', function() {
function MyObj() {
this.bar = 'barVal';
this.baz = 'bazVal';
};
}
MyObj.prototype.foo = 'fooVal';

var obj = new MyObj(),
Expand All @@ -267,6 +267,77 @@ describe('angular', function() {

expect(log).toEqual(['bar:barVal', 'baz:bazVal']);
});


it('should handle JQLite and jQuery objects like arrays', function() {
var jqObject = jqLite("<p><span>s1</span><span>s2</span></p>").find("span"),
log = [];

forEach(jqObject, function(value, key) { log.push(key + ':' + value.innerHTML)});
expect(log).toEqual(['0:s1', '1:s2']);
});


it('should handle NodeList objects like arrays', function() {
var nodeList = jqLite("<p><span>a</span><span>b</span><span>c</span></p>")[0].childNodes,
log = [];


forEach(nodeList, function(value, key) { log.push(key + ':' + value.innerHTML)});
expect(log).toEqual(['0:a', '1:b', '2:c']);
});


it('should handle HTMLCollection objects like arrays', function() {
document.body.innerHTML = "<p>" +
"<a name='x'>a</a>" +
"<a name='y'>b</a>" +
"<a name='x'>c</a>" +
"</p>";

var htmlCollection = document.getElementsByName('x'),
log = [];

forEach(htmlCollection, function(value, key) { log.push(key + ':' + value.innerHTML)});
expect(log).toEqual(['0:a', '1:c']);
});


it('should handle arguments objects like arrays', function() {
var args,
log = [];

(function(){ args = arguments}('a', 'b', 'c'));

forEach(args, function(value, key) { log.push(key + ':' + value)});
expect(log).toEqual(['0:a', '1:b', '2:c']);
});


it('should handle objects with length property as objects', function() {
var obj = {
'foo' : 'bar',
'length': 2
},
log = [];

forEach(obj, function(value, key) { log.push(key + ':' + value)});
expect(log).toEqual(['foo:bar', 'length:2']);
});


it('should handle objects of custom types with length property as objects', function() {
function CustomType() {
this.length = 2;
this.foo = 'bar'
}

var obj = new CustomType(),
log = [];

forEach(obj, function(value, key) { log.push(key + ':' + value)});
expect(log).toEqual(['length:2', 'foo:bar']);
});
});


Expand Down

0 comments on commit 8801d9c

Please sign in to comment.