jQuery: Using string passed by $.each - javascript

This is my code:
<div class='a'>
<div class='b'>Test</div>
</div>
and
$(['.b']).each(function () {
console.log($('.a').find(this).text()); // Expecting to print "Test"
});
I expect the console.log to print Test, but it doesn't! Is this a jQuery bug?

There are a few problems here.
When you call jQuery with an array (like you're doing), jQuery expects it to be an array of DOM elements.
If you want to use $.each, use the static version that iterates over generic objects or arrays.
With both of those things said, there's stil an issue with using $.each with an array containing primitive values. The following code exhibits the same problem you were seeing:
$.each([".b"], function () {
console.log($('.a').find(this).text()); // Expecting to print "Test"
});
Inside the callback function for .each, this is a String object and not a string primitive. To understand this, we need to look at what .each is doing under the hood:
for (; i < length;) {
if (callback.apply(object[i++], args) === false) { // <----
break;
}
}
The important bit is the call to apply. According to the ECMAScript specification, when a primitive value is passed to apply, the value's toObject method is called:
If thisArg is null or undefined, the called function is passed the global object as the this value. Otherwise, the called function is passed ToObject(thisArg) as the this value.
This explains why your code was not working-- .find expects a string primitive and not a String object.
This is why the documentation for $.each actually mentions using this with primitive values:
(The value can also be accessed through the this keyword, but Javascript will always wrap the this value as an Object even if it is a simple string or number value.).
Therefore the way to fix the problem with your code is to leverage the element argument that's passed to the callback function:
$.each([".b"], function (_, item) {
console.log($('.a').find(item).text()); // Expecting to print "Test"
});

Don't use this inside each when looping over an array. It works fine on objects or collection of elemnts but fails with arrays.
Use second argument of each to access array element
$(['.b']).each(function (index, item) {
console.log($('.a').find(item).text()); // Expecting to print "Test"
});
DEMO http://jsfiddle.net/KbuZK/

That's because you're on the dark side of JavaScript.
In JavaScript, this is always made into an Object so that typeof this === "object", no matter what this was actually bound to. Even when a primitive string is bound to the this context (which you'd expect to give typeof this === "string"), this is actually a String object and typeof this === "object". Here's a more thorough explanation of this behaviour.
When iterating over arrays of non-objects, you should use the second argument of your callback function as value.
$(['.b']).each(function (index, value) {
console.log($('.a').find(value).text()); // Expecting to print "Test"
});

Related

Break down 1 liner javascript

I came across a JavaScript code that creates an array and fills it with numbers from 0-9.
The code is:
var arr = Array.apply(null, {length: 10}).map(Number.call, Number);
console.log(arr);
The code above creates an array and pushes numbers from 0-9 in it.
I do not understand how it is working. I know all the methods that are used in this like apply, map, and call but still don't understand how it is working.
Can anyone please explain the working of code by breaking it into chunks? Like how step-by-step code is being executed and how it is resulting in an array that has numbers from 0-9.
Firstly, your code creates an array of 10 elements, all of which being undefined using the below code:
Array.apply(null, {length: 10})
The above is like writing:
Array(undefined, undefined, undefined, undefined, ... 6 more times ...)
This works because the object {length: 10} is array-like as it has a length property. Usually the second argument to .apply() is an array of arguments that you want to call the function with. However, as you're passing an object in this case, JS takes the length property and calls the Array() function with 10 undefined arguments, as the keys of the {length: 10} object don't define values for properties 0 to 9.
Next, the array of 10 undefined values are mapped. When using .map(), the first argument is the mapping function and the second argument to the .map() method is the this argument, which indicates what this should refer to inside of the first mapping function. One way of looking at your map method is to rerwrite it like so:
.map((element, index, array) => Number.call(element, index, array))
The .map() method will iterate through all of your 10 undefined elements, and for each element your callback will be invoked with the index of that element. When using Number.call(), the this for the Number() function call is set to undefined (ie: element). This doesn't impact how the call to Number() behaves as it doesn't rely on any particular value for its execution. As a result, the above Number.call method is the same as using Number(index, array), where array is discarded, so it's simply performing Number(index).
The second argument to the .map() method is to define the this binding for the first mapping function that you pass into the .map() method. When you pass Number.call as the mapping function, you're passing a reference to the call method defined on Function.prototype.call. The call method only knows what function it should invoke based on its this value defined when calling the .call() method. As you're only passing the .map() method a reference to the call() function, and not invoking it with (), the this is lost, and so you need to explicitly define what the this value is. That can be done by passing through Number as the second argument to your map method call.

Filter an object array based on the property

The data I have stored is in a 2d Array.
One element of looks like below. (not an assignment operator)
someObjArray[5] === [{lastname:"foo", firstname:"bar", grade:10, userId:"foobar1234"},...]
For the particular above variable I want to filter on userId
I am attempting to do so like this.
var test = stuArray[5].filter(function(item) {
return item['userId'];
});
Resulting in:
test === [{lastname:"foo", firstname:"bar", grade:10, userId:"foobar1234"},...]
Where the desired results are
test === ["foobar1234",...]
I have also tried using a dot operator with the same results.
I don't think filter is what you're looking for here.
The function (non-anonymous in your case but you can also use anonymous functions) that you are passing into your filter method needs to return a true or a false. This is how the method "filters" your array - it gives you back an array whose elements pass the filter, or return true when passed as arguments into filter's function.
Note that this does not change the original array.
What you should use instead is the very similar map() function.
Note that map(), just like filter(), does not change the original array.
You can do it like this:
var someObjArray = [{lastname:"foo", firstname:"bar", grade:10, userId:"foobar1234"}];
console.log(someObjArray.map(s => s.userId));
Online demo (jsFiddle)

Adding a property to an Angular object

I am running an Angular app that pulls its data from a Web API service. The API returns the objects as JSON and the Angular service (via $http.get() ) returns them to the controller as an array of objects. Pretty normal stuff.
What I'd like to do is add a property to each of the returned objects called "Selected". This property is purely for GUI purposes (is the object selected or not in the UI) and doesn't need to be persisted in any way. I figured the easiest thing to do was loop through the returned array of objects and just add it. So my code looks like this:
function getData() {
myService.getData()
.then(function(data) {
$scope.myData = data.results;
// Add a "Selected" property to each object
$.each($scope.myData, function(item) {
item.Selected = false;
});
}
When it gets to the line that says, "item.Selected = false" it throw an error message, saying "Cannot assign to read-only property Selected".
It is unclear to me why "Selected" is read-only? I didn't know if maybe Angular does some funky object processing when it reads data? What am I doing wrong here? Or should I be approaching this a completely different way?
Note (I'd like to avoid having to make Selected a part of the original object, as it's not representative of anything related to that object).
to add property to an object use underscorejs,
"each _.each(list, iteratee, [context]) Alias: forEach
Iterates over a list of elements, yielding each in turn to an iteratee function. The iteratee is bound to the context object, if one is passed. Each invocation of iteratee is called with three arguments: (element, index, list). If list is a JavaScript object, iteratee's arguments will be (value, key, list). Returns the list for chaining."
"extend _.extend(destination, *sources)
Copy all of the properties in the source objects over to the destination object, and return the destination object. It's in-order, so the last source will override properties of the same name in previous arguments."
$scope.myData = data.results;
// Add a "Selected" property to each object
_.each($scope.myData, function(ent) {
_.extend(ent, {
Selected : false
});
});
Your debugger screenshot actually gives you a more useful error message than what you posted (emphasis mine):
Cannot assign to read only property 'Selected' of 0
This shows that instead of the object, you're getting a number as your item variable (0 in this case). Assigning properties to primitives in strict mode throws this "Cannot assign to read-only property" error. (Without strict mode, it just fails silently.)
As JcT pointed out in a comment, this is because $.each calls the function with 2 params passed, the index first, the value second. See the documentation of $.each:
callback
Type: Function( Integer indexInArray, Object value )
So even though you named your parameter item, it received the value of the current index instead. This means your code can be fixed by just adding this missing first parameter to your callback, like this:
function getData() {
myService.getData()
.then(function(data) {
$scope.myData = data.results;
// Add a "Selected" property to each object
$.each($scope.myData, function(index, item) { //index was added here!
item.Selected = false;
});
}

$.each() function jQuery on an object

I have a form and I am trying to build a custom validation for it by iterating over the elements and do whatever needs to be done.
This is my js code so far
$('#createClient').click(function (event) {
var formData = {
'clientbrandname': $("#createClientBrand").val(),
'surveyReferral': $("#createSurveyReferral").val(),
'GuestReferral': $("#createGuestReferral").val(),
'email': $("#createInviteEmail").val(),
'phone': $("#createPhoneNumber").val(),
'xampAccNumber': $('#createUserLimit').val(),
'logo': $('#createClientLogo').val(),
'Template': $('#createRefPage').val(),
'xampClient': $('#createAmplifierClient').val(),
'xampActive': $('#createAmpMembership').val(),
'sReferralID': $('#sReferralID').val(),
'gReferralID': $('#gReferralID').val()
};
$(formData).each(function () {
alert($(this).val());
});
});
With this code I am running into an error (i.nodeName is undefined) which I suspect is coming from the part where I am trying to use $(this).val()
Anyone can suggest a solution for this issue?
You are iterating over an object. Therefore you need to call the name value pair in the function.
The $.each() function is not the same as $(selector).each(), which is
used to iterate, exclusively, over a jQuery object. The $.each()
function can be used to iterate over any collection, whether it is an
object or an array.
JsFiddle
$.each( formData, function( name, value ) {
alert(name); //Alerts clientbrandname
alert(value); //Alerts the value from "$("#createClientBrand").val()"
});
Referring to the jQuery docs located HERE, it states
A generic iterator function, which can be used to seamlessly iterate
over both objects and arrays. Arrays and array-like objects with a
length property (such as a function's arguments object) are iterated
by numeric index, from 0 to length-1. Other objects are iterated via
their named properties.
You're trying to use this on an associative array, which cannot be itereated via a a numeric index.
The following is pulled from the jQuery docs page noted above..
var obj = { one: 1, two: 2, three: 3, four: 4, five: 5 };
$.each( obj, function( i, val ) {
$( "#" + i ).append( document.createTextNode( " - " + val ) );
});
What you really want is this:
$.each(formData, function(key, value) {
// ...
});
The function $ like in $(...) expects a node or an array of nodes. Then the object it returns has the method #each which iterates over the nodes.
Now there's a general purpose iterator - which the method mentioned above uses - that is $.each. This function expects any array-like object.
The {} object in JavaScript can behave like an array for this purpose.
Now I wonder, did you consider using serializeArray ?
When you pass your JavaScript object to the jQuery constructor ($(...)) you are creating a jQuery object instance. This instance has a length property, which causes the object to act array-like.
According to the jQuery documentation for $.each,
Arrays and array-like objects with a length property (such as a function's arguments object) are iterated by numeric index, from 0 to length-1.
This solution won't work for you because you don't have numerical indices.
Instead, you need to use the other form of the $.each function, which can be seen below:
//Just a generic representation of your JavaScript object.
var obj = {
myKey: 'myValue'
};
$.each(obj, function (key, value) {
console.log(key); //Outputs 'myKey'
console.log(value); //Outputs 'myValue'
});
Here, obj has not yet been turned into a jQuery object before iterating over it. It doesn't have a length property, so it won't be treated like an array. Now, the property name will be sent to the callback function as the first parameter, and the value of the property will be sent as the second parameter.

"too much recursion" error when calling JSON.stringify on a large object with circular dependencies

I have an object that contains circular references, and I would like to look at the JSON representation of it. For example, if I build this object:
var myObject = {member:{}};
myObject.member.child = {};
myObject.member.child.parent = myObject.member;
and try to call
JSON.stringify(myObject);
I get the error "too much recursion", not surprisingly. The "child" object has a reference to its "parent" and the parent has a reference to its child. The JSON representation does not have to be perfectly accurate, as I'm only using it for debugging, not for sending data to a server or serializing the object into a file or anything like that. Is there a way to tell JSON.stringify to just ignore certain properties (in this case the parent property of the child object), so that I would get:
{
"member" : {
"child" : {}
}
}
The closest I can think of is to use getChild() and getParent() methods instead of just members, because JSON.stringify ignores any properties that are functions, but I'd rather not do that if I don't have to.
You can pass a function as the second argument to stringify.
This function receives as arguments the key and value of the member to stringify.
If this function returns undefined, the member will be ignored.
alert(JSON.stringify(myObject, function(k, v) {
return (k === 'member') ? undefined : v;
}));
...or use e.g. firebug or use the toSource()-method, if you only want to see whats inside the object.
alert(myObject.toSource());
From the crockford implementation (which follows the ECMA specification):
If the stringify method sees an object that contains a toJSON method, it calls that method, and stringifies the value returned. This allows an object to determine its own JSON representation.
Then something like this should work just fine:
var myObject =
{
member: { child: {} }
}
myObject.member.child.parent = myObject.member;
myObject.member.child.toJSON = function ()
{
return 'no more recursion for you.';
};
console.log(JSON.stringify(myObject));​
http://jsfiddle.net/feUtk/

Categories