jQuery.map - Practical uses for the function? - javascript

I am trying to get a better understanding of the jQuery.map function.
So in general terms .map takes a array and "maps" it to another array of items.
easy example:
$.map([0,1,2], function(n){
return n+4;
});
results in [4,5,6]
I think I understand what it does. I want to under why would someone need it. What is the practical use of this function? How are you using this in your code?

Mapping has two main purposes: grabbing properties from an array of items, and converting each item into something else.
Suppose you have an array of objects representing users:
var users = [
{ id: 1, name: "RedWolves" },
{ id: 2, name: "Ron DeVera" },
{ id: 3, name: "Jon Skeet" }
];
Mapping is a convenient way to grab a certain property from each item. For instance, you can convert it into an array of user IDs:
var userIds = $.map(users, function(u) { return u.id; });
As another example, say you have a collection of elements:
var ths = $('table tr th');
If you want to store the contents of those table headers for later use, you can get an array of their HTML contents:
var contents = $.map(ths, function(th) { return th.html(); });

$.map is all about converting items in a set.
As far as the DOM, I often use it to quickly pluck out values from my elements:
var usernames = $('#user-list li label').map(function() {
return this.innerHTML;
})
The above converts the <label> elements inside a list of users to just the text contained therein. Then I can do:
alert('The following users are still logged in: ' + usernames.join(', '));

Map is a high-order function, that enables you to apply certain function to a given sequence, generating a new resulting sequence containing the values of each original element with the value of the applied function.
I often use it to get a valid selector of all my jQuery UI panels for example:
var myPanels = $('a').map(function() {
return this.hash || null;
}).get().join(',');
That will return a comma separated string of the panels available in the current page like this:
"#home,#publish,#request,#contact"
And that is a valid selector that can be used:
$(myPanels);// do something with all the panels

Example:
$.map($.parseJSON(response), function(item) {
return { value: item.tagName, data: item.id };
})
Here server will be returning the "response" in JSON format, by using $.parseJSON it is converting JSON object to Javascript Object array.
By using $.map for each object value it will call the function(item) to display the result value: item.tagName, data: item.id

Here's one thing you could use it for.
$.map(["item1","item2","item3"], function(n){
var li = document.createElement ( 'li' );
li.innerHTML = n;
ul.appendChild ( li );
return li;
});

Recently I discovered an excellent example of .map in a practical setting.
Given the question of How to append options to a selectbox given this array (or another array):
selectValues = { "1": "test 1", "2": "test 2" };
this StackOverflow answer uses .map:
$("mySelect").append(
$.map(selectValues, function(v,k) {
return $("<option>").val(k).text(v);
})
);

Converting code values to code text would be a possible use. Such as when you have a select list and each indexed value has a corresponding code text.

Related

array.sort not working on array of objects

I got the following object array:
var arr = [{
2: {
1: { name: "test" },
2: { name: "apple" }
},
3: {
1: { name: "banana" },
2: { name: "pear" }
}
}];
Just some sample data. Now, I got 3 textareas:
<textarea id="first"></textarea>
<textarea id="second"></textarea>
<textarea id="third"></textarea>
And I have the following custom-made function:
function sort(alt)
{
arr.sort(function (a,b)
{
console.log(a);
if (a[2].name < a[2].name)
return (alt) ? 1 : -1;
if (a[2].name > a[2].name)
return (alt) ? -1 : 1;
return 0;
});
}
It should sort the array of objects by name, ascending or descending according to parameter. Now, I got 2 problems. This way I append all the values to the textareas:
for (var key in arr[0])
{
var obj = arr[0][key];
$(ID).append(obj[2].name + '\n');
}
The first time, that code will be executed without running sort. The second time, sort will be executed with false as parameter, than that code will be executed. The third time sort will be executed with true as parameter, than that code will be executed. However, the output of all textboxes is exactly the same.
This is a link to the jsfiddle:
http://jsfiddle.net/JoshB1997/gow4vzsc/
Also, the console.log(a) doesn't get printed in the console.
So variable arr is an array but as far as I can see it contains only one object.
You're trying to sort directly onto the array, since it only has one object it will simply never sort because there is nothing to sort.
You'll want to access arr[0] which is the object containing the actual objects you want to sort however the Object prototype doesn't contain any of the array functions so you cannot call sort on it even tho technically an Array is an Object an Array inherits from Object and not the other way around so the methods from Object are available to Array but not the other way around.
Also, you're trying to compare the same a[2].name with itself so it'll always be false since it's equal, not > or <.
In your case I extract all the name properties from every nested object you have like this (considering the usage of the original arr):
var compare = [];
var alt = false;
for (k in arr[0]) {
if (arr[0].hasOwnProperty(k)) {
for (l in arr[0][k])
if (arr[0][k].hasOwnProperty(l))
compare.push(arr[0][k][l].name);
compare.sort(function(a, b) {
if (a == b)
return 0;
else if (a < b)
return alt ? 1 : -1
else
return alt ? -1 : 1
});
Now you can use the compare array to output the sorted names correctly.
Also - your construction seems overly complex? It has objects and within them are nested objects but you're only sorting and displaying names, is there any reason this structure has to be maintained?
If not I would highly recommend you simplify this to just be an array of names, the loop I made above is far from beautiful and I'd rather have you not use it since it assumes that the outmost object is an object filled with other objects that all have the name property. This code could still break without an extra arr[0][k][l].hasOwnProperty('name').
Either way, the compare array simply contains all the names and it easily sortable with the default sort if you don't make things to complex for yourself.
I suggest you to use http://underscorejs.org/ that contains quite really useful function to transform from object to arrays.
For example in this case you can use something like http://underscorejs.org/#values
let values = _.values(arr[0]);
Now values is an array that contains your two object (2,3) in the form
values = [
{
1: {
name: "test"
},
2: {
name: "apple"
}
},
{
1: {
name: "banana"
},
2: {
name: "pear"
}
}
]
and here you can call your sort function
There is my demo on your code with underscore.js http://jsfiddle.net/gow4vzsc/3/
EDIT: If you cant/wont to include an entire library you can write your code for get the values:
values = [];
for(key in arr[0]){
values.push(arr[0][key]);
}
Here a demo without underscore.js http://jsfiddle.net/3L7ttu2r/1/

Get the difference between two arrays of objects

I've got two arrays of objects, the difference between them is only that arrayAfter will have an element added:
var arrayBefore = [
{"name":"Alan","height":"171","weight":"66"},
{"name":"Ben","height":"182","weight":"90"}
];
var arrayAfter= [
{"name":"Alan","height":"171","weight":"66"},
{"name":"Ben","height":"182","weight":"90"},
{"name":"Chris","height":"163","weight":"71"}
];
"name" is always unique!
How can I find out which one is the element that has been added? I've tried ending up using nested for loops, but this seems overcomplicated.
I've also found the this nice idea:
var diff = $(arrayAfter).not(arrayBefore ).get();
However, that does not seem to work on arrays of objects straight ahead.
Is there some easy way to get the difference?
If only the name indicates uniqueness, you can do:
//Get a list of all the names in the before array
var beforeNames = arrayBefore.map(function(person) { return person.name });
//Filter the after array to only contain names not contained in the before array
var uniqueObjects = arrayAfter.filter(function(person) {
return beforeNames.indexOf(person.name) === -1;
});
console.log(uniqueObjects); //[{"name":"Chris","height":"163","weight":"71"}]
Demo: http://jsfiddle.net/tehgc8L5/
For a generic method you can combine Array.prototype.filter() with Array.prototype.reduce() which iterates over the object keys:
arrayAfter.filter(function(after) {
return !arrayBefore.reduce(function(found, before) {
if (!found) {
found = true;
for (key in before) {
if (before.hasOwnProperty(key)) {
found = found && (before[key] === after[key]);
}
}
}
return found;
}, false);
}); //[{name: "Chris", height: "163", weight: "71"}]
You can use Array.prototype.filter and filter out those elements in the previous array.
var differences = arrayAfter.filter(function(el) {
return arrayBefore.indexOf(el) === -1;
});
I believe jQuery will have nothing that will directly solve your problem here. Your problem being comparing objects for equality.
I am assuming that name is unique. If not, for this method you will need a unique identifier for data. If you absolute do not have one then you could concat all data to get one.
// first make a map of objects in before
var before = {};
arrayBefore.forEach(function(o){
before[o.name] = o;
});
// now we get the elements of after that do not exist in our hashmap
var result = arrayAfter.filter(function(o){
return !(o.name in before);
});
You can obviously wrap this up in a general function.

Check if an object has a key in javascript

I have two arrays of objects, and I want to filter the first one according to whats on the second one. Here's an example:
var ary1 = [{id: 23, title: 'blabla'},{id:43, title: 'bleble'}, {id:54, title:'blibli'}];
var ary2 = [{id:23},{id:54}, {id:65}];
So in this case what I want to return is an array with the objects that have id's 23 and 54 of the first array, with all its possible properties (in this case, title).
Could you give me any hint that could help me?
Get a list of the indexes you want to search on using map:
var indexes = ary2.map(function (el) {
return el.id;
});
filter the results based on the list of indexes:
var result = ary1.filter(function (el) {
return indexes.indexOf(el.id) > -1;
});
DEMO
This might help you.
Loop through ary2, building up an array of each id value (let's call this array existingIds).
After that loop, now loop through ary1. For each item in ary1, check to see if the id value exists in the existingIds array that we just built up. If it does, append the current item to a result array.
I could write the code for you, but it will be a better learning experience if you first try this yourself :)
Might as well make use of some functional programming built into javascript.
filteredResults = ary1.filter(function(ele){
return (ary2.map(function(idobj){return idobj.id;}).indexOf(ele.id)>-1)
})
filter(function) will iterate through each element of an array, passing it through a callback function. From within that callback iff a true is returned, that value is kept. If false, that value is filtered out.
Also map(function) will iterate through each element of an array passing a callback value as well. All values returned from map callback will be injected into the result. So we can take the id from each element in ary2 and return it in the map function.
var ary1 = [{id: 23, title: 'blabla'},{id:43, title: 'bleble'}, {id:54, title:'blibli'}];
var ary2 = [{id:23},{id:54}, {id:65}];
//Filter for the available ID's, store the resulting objects in a new array
filteredResults = ary1.filter(function(ele){
//map creates an array of just ID's
return (ary2.map(function(idobj){return idobj.id;}).indexOf(ele.id)>-1)
})
//now do whatever you were planning on doing with your results/
var res = document.getElementById("results");
filteredResults.forEach(function(ele){
res.innerHTML+="<li>{id:"+ele.id + ",title:" +ele.title+"}</li>"
})
console.log(filteredResults);
<ul id="results"></ul>
try this:
var ary1 = [{id: 23, title: 'blabla'},{id:43, title: 'bleble'}, {id:54, title:'blibli'}];
var ary2 = [{id:23},{id:54}, {id:65}];
var newary=[];
for(x in ary1){
for(y in ary2){
if(ary1[x].id == ary2[y].id){
newary.push(ary1[x]);
}
}
}
console.log(newary);// here newary will be your return newary;

Most efficient and clean way to push a value to an array only if it does not exist yet

Suppose an array named myArray containing several values but no duplicates.
Suppose I want to push a value into it only if it won't lead to duplicates presence.
How I determinate duplicates => by comparing value's id.
I thought about Lodash#uniq to do the trick:
myArray.push(aNewValue);
myArray = _.uniq(myArray,function(item){
return item.id;
});
However, I don't like the reassignment to the array and especially the fact that I have to push before checking...
Is there a more "functional" way to achieve it while being very short?
I don't want to iterate through the array explicitly in order to apply the check.
That's why I attempted to use Lodash.
You can check the presence of an item before adding it :
if(myArray.indexOf(aNewValue) == -1) {
myArray.push(aNewValue);
}
The most efficient way to do this is generally to use an object for uniqueness, because an object can have at most one key of a certain value. However, this is restricted to strings and things that stringify, since only strings can be object keys.
There are two approaches here. If you are using your array often, then you should keep parallel structures - an object for uniqueness check, an array for arrayness of it.
If you don't need your array often, i.e. you want to push a bunch of things and then have an array be unique, you can just use the object, and transform it into an array when you need it (which is somewhat expensive, so you only want to do it once, but still cheaper than manipulating two different structures all the time).
The first approach is illustrated here:
function Set() {
this.presence = {};
this.array = [];
};
Set.prototype.push = function(key, value) {
if (this.presence[key]) return;
this.presence[key] = true;
this.array.push(value);
};
var a = new Set();
a.push(3, { id: 3, value: "SOMETHING" });
a.push(7, { id: 7, value: "SOMETHING ELSE" });
a.push(3, { id: 3, value: "SOMETHING" });
console.log(a.array); // => only 2 elements
The second, here:
function Set() {
this.store = {};
};
Set.prototype.push = function(key, value) {
this.store[key] = value;
};
Set.prototype.array = function() {
var that = this;
return Object.keys(this.store).map(function(key) { return that.store[key]; })
};
...
console.log(a.array()); // note the newly added parentheses :)
Both of these are still cheaper than looking for presence inside the array using indexOf, even more so when you do your own iterating, except very much maybe in case the array is very short.
You could use Array.prototype.some() to find out if the value is already part of the array, e.g.:
if( myArray.some(function (elem) { return elem.id == newValue.id }) )
myArray.push(newValue);
You can't really get away with not looping through the array, though.

BackboneJS: iterate on model attribute and change value

I want to make a function with functionality like toJSON()'s functionality which returns and edits model.
My question is how to iterate on model's attribute and edit the specific value of the attribute you selected.
If have a model e.g:
Item = Backbone.Model.extend({
defaults: {
name: '',
amount: 0.00
},
toHTML: function(){
// i think this is the place where
// where i can do that function?
//
console.log(this.attribute)
}
});
var item = new Item;
item.set({name: 'Pencil', amount: 5}):
item.toJSON();
-> {name: 'Pencil', amount: 5}
// this is the function
item.toHTML();
-> {name: 'Pencil', amount: 5.00}
You can iterate over an object using a for ... in loop and then use toFixed to format the number:
toHTML: function() {
var attrs = { }, k;
for(k in this.attributes) {
attrs[k] = this.attributes[k];
if(k === 'amount')
attrs[k] = attrs[k].toFixed(2);
}
return attrs;
}
Note that amount will come out as a string but that's the only way to get 5.00 rather than 5 to come out. I'd probably leave the formatting up to the template and not bother with this toHTML implementation.
Demo: http://jsfiddle.net/ambiguous/ELTe5/
If you want to iterate over a model's attributes, use the attributes hash:
// Inside your model's method
for(attr in this.attributes){
console.log(attr, this.attributes[attr]);
}
Here's a jsFiddle using your example code.
Though the answers provided here are correct and would do what you want. But I think the better way would be to use the underscore functions for this purpose.
for simple looping you can use
_.each(list, iteratee, [context])
_.each(model.attributes, function(item, index, items){
console.log(item);
console.log(index);
})
you can also use specialized functions as per your specific need. Like if you are want to have a new result array as a result of applying some function on every element of your list, map can be the best option for you.
_.map(list, iteratee, [context])
var newList = _.map(model.attributes, function(item, index, list){
return item * 5;
})
I would recommend you to go through the documentation of underscore and backbone for the best function for your need.

Categories

Resources