aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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']);
+ });
});