aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorIgor Minar2013-02-11 12:25:34 -0800
committerIgor Minar2013-02-11 12:29:55 -0800
commitec54712ff3dab1ade44f94fa82d67edeffa79a1d (patch)
tree5d2445493fa56073a6231ca80c88ff4a6e2a56ba
parent23dd78f8a463e03df77e505f445923d1946c9ecf (diff)
downloadangular.js-ec54712ff3dab1ade44f94fa82d67edeffa79a1d.tar.bz2
fix(angular.forEach): correctly iterate over objects with length prop
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
-rw-r--r--src/Angular.js26
-rw-r--r--test/AngularSpec.js73
2 files changed, 97 insertions, 2 deletions
diff --git a/src/Angular.js b/src/Angular.js
index 2affd5ef..4685dbde 100644
--- a/src/Angular.js
+++ b/src/Angular.js
@@ -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) {
@@ -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 {
diff --git a/test/AngularSpec.js b/test/AngularSpec.js
index 09bc902f..e29bb16b 100644
--- a/test/AngularSpec.js
+++ b/test/AngularSpec.js
@@ -257,7 +257,7 @@ describe('angular', function() {
function MyObj() {
this.bar = 'barVal';
this.baz = 'bazVal';
- };
+ }
MyObj.prototype.foo = 'fooVal';
var obj = new MyObj(),
@@ -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']);
+ });
});