Define custom parameters inside javascript compare functions in JavaScript? - javascript

I have an array of objects that needs sorting by last name, however the object only has firstname and fullname parameters, so for last name I have to use fullname.replace((firstname + " "), ""). Example array below.
const names = [
{
firstname: "John",
fullname: "John Doe"
},
{
firstname: "Amber",
fullname: "Amber Smith"
},
{
firstname: "Michael",
fullname: "Michael Smith"
},
{
firstname: "Jessica",
fullname: "Jessica Kelly Charles"
}
]
While I can use the "replace" every time inside a sort compare function, I'd much rather have something like this:
names.sort(function(a, b) {
const lastname = fullname.replace((firstname+ " "), "");
if (a.lastname < b.lastname) {
return -1;
} else if (a.lastname > b.lastname) {
return 1;
}
return 0;
});
Obviously lastname comes up as undefined.
This has been rather hard to google for, and I think I'm missing some JavaScript basics here, but would greatly apprecite your help in helping me learn to write better code.

Your best bet is to modify the source of the array so it stores lastname upon collection.
If you can't do that:
Unless you do a prep pass through your array adding a lastname property, you'll have to compute it each and every time your sort callback is called, for both a and b.
names.sort((a, b) => {
const alast = a.fullname.replace(a.firstname + " "), "");
const blast = b.fullname.replace(b.firstname + " "), "");
return alast.localeCompare(blast);
});
(Note I used localeCompare, which is always a better choice for names and other natural language strings than < and >. For instance, ask the French whether รง should really come after z as it does with < and >. ;-) )
That will recompute the lastname for the same object repeatedly, though, since the same object may be passed to sort (as either a or b) repeatedly. If you think that may be a problem, you could do that prep pass I mentioned:
// Build a map of entry to last names
const lastnames = new Map(names.map(entry => {
const lastname = entry.fullname.replace(entry.firstname + " ", "");
return [entry, lastname];
}));
// sort
names.sort((a, b) => {
return lastnames.get(a).localeCompare(lastnames.get(b));
});

You are just declaring a new variable trying to access it via object's property. You should map your data in the first place like:
const names = [
{
firstname: "John",
fullname: "John Doe"
},
{
firstname: "Amber",
fullname: "Amber Smith"
},
{
firstname: "Michael",
fullname: "Michael Smith"
},
{
firstname: "Jessica",
fullname: "Jessica Kelly Charles"
}
].map(person => ({
...person,
lastName: person.fullName.replace(person.firstName + ' ', '')
}));
After mapping your data like that, you can use .lastName in the place you need.
names.sort(function(a, b) {
if (a.lastName < b.lastName) {
return -1;
} else if (a.lastName > b.lastName) {
return 1;
}
return 0;
});

Related

How do I sort some HTML elements alphabetically?

function render() {
let lastContact = listadeContatos.slice(-1);
boxapp__contact.innerHTML += lastContact
.map((contact) => {
return `<div class="box-app__contacts-list" id=${
contact.id
} onclick="boxapp(this.id)">
<div class="contact__nameinitial">${contact.fistName[0].toUpperCase()}</div>
<div class="contact__name">${contact.fistName}</div>
</div>`;
}).join("");
const contacts = document.querySelector(".box-app__contacts-list");
const c = [...document.querySelectorAll(".contact__name")];
if (listadeContatos.length > 1) {
c.sort((a, b) => (a.innerText > b.innerText ? 1 : -1)).forEach((node) => {
contacts.appendChild(node);
});
}
}
I made a simple app, a list of contacts, and I wanted them to be sorted alphabetically when a new
contact is added and called for rendering, but nothing I've tried has worked. And the similar issues I encountered were not enough for me to find the way to a solution. I'm still learning Javascript. I would like someone to help me if they can. And sorry if there are any spelling mistakes. I'm using Google Translate to make this post.
Try using this sort function: c.sort((a, b) => (a.innerText < b.innerText? -1 : a.innerText > b.innerText? 1 : 0))
Have a function that sorts array of objects with .sort() and .localeCompare() first, then render the HTML with a seperate function. If the data is changed in any way, you use those two functions the same way every time. This pattern is less error prone as long as the data doesn't vary in keys or pattern.
Details are commented in example
const contactData = [{
firstName: "Brandon",
lastName: "Grewe",
phone: ["205-116-8677", "961-849-8402", "112-125-1509"],
email: ["sgrewe0#rediff.com"]
}, {
firstName: "Lyndsay",
lastName: "Courtes",
phone: ["559-585-5161", "244-413-5832", "467-871-0638"],
email: ["lcourtes1#sun.com"]
}, {
firstName: "Rose",
lastName: "Xavier",
phone: ["293-858-6159"],
email: ["gpopley2#uol.com.br", "gpopley2#npr.org"]
}, {
firstName: "Berni",
lastName: "Molen",
phone: ["944-271-6491", "911-107-2055", "429-553-3494"],
email: ["bmolen3#gnu.org"]
}, {
firstName: "Rose",
lastName: "Fereday",
phone: ["820-272-1567"],
email: ["rfereday4#naver.com"]
}, {
firstName: "Juliana",
lastName: "Fredi",
phone: ["268-283-9358", "981-219-0701", "326-164-3537"],
email: ["jfredi5#bigcartel.com", "jfredi5#exblog.jp"]
}, {
firstName: "Barry",
lastName: "Gatchel",
phone: ["762-132-3931", "840-627-2075", "654-204-6164"],
email: ["mgatchel6#com.com"]
}, {
firstName: "Jeth",
lastName: "Harwin",
phone: ["871-777-0358", "968-282-0040"],
email: ["jharwin7#yahoo.co.jp", "jharwin7#icq.com"]
}, {
firstName: "Ahmad",
lastName: "Scoon",
phone: ["392-928-5655"],
email: ["ascoon8#ask.com", "ascoon8#nyu.edu"]
}, {
firstName: "Kristine",
lastName: "Sleet",
phone: ["104-606-6882"],
email: ["ksleet9#fc2.com", "ksleet9#php.net"]
}];
// Pass in an array of objects
function arrange(array) {
// Returns an object of arrays -- each keyed to a letter A-Z
let letters = Object.fromEntries(
[...new Array(26)]
.map((_, i) => i + 65)
.map(n => [String.fromCharCode(n), []])
);
//console.log(letters);
/*
Takes the given array and gets the first letter of each firstName
property then finds the array with that letter inside the letters
object and adds the current object to it's array -- once in
the array, it is sorted alphabetically by firstName or
lastName should firstName be identical
*/
array.forEach(con => {
let alpha = con.firstName.charAt(0);
letters[alpha].push(con)
letters[alpha].sort(
(a, b) => a.firstName.localeCompare(b.firstName) == 0 ?
a.lastName.localeCompare(b.lastName) :
a.firstName.localeCompare(b.firstName)
)
});
//console.log(letters);
// Filters out any letters that have empty arrays
letters = Object.fromEntries(Object.entries(letters)
.filter(sub => sub[1].length > 0)
);
//console.log(letters);
return letters;
}
// Pass in the object returned from arrange() and an optional DOM object
function build(object, node = document.body) {
/*
For each key of object render a <fieldset> and add the letter
to it's <legend> and it's #id
*/
Object.keys(object).forEach(letter => {
const alphabet = `
<fieldset id='${letter}' class='letter'>
<legend>${letter}</legend>
</fieldset>`;
node.insertAdjacentHTML('beforeend', alphabet);
});
// Make an array of the <fieldset>s
const letters = [...document.querySelectorAll('.letter')];
/*
For each <fieldset> find the array of the object that
corresponds to the <fieldset>'s #id. For each object of the
sub-array interpolate the object's data within the template
literals, then render the template literal to the current <fieldset>
*/
letters.forEach(az => {
let group = object[az.id].map(contact => {
const fullName = `
<fieldset class='contact'>
<legend>
${contact.firstName}
${contact.lastName}
</legend>
<menu>`;
const numbers = contact.phone.map(tel => `
<li>Phone:
<output name='phone'>${tel}</output>
</li>`
).join('');
const address = contact.email.map(pop => `
<li>Email:
<output name='email'>${pop}</output>
</li>`
).join('');
return `${fullName}${numbers}
</menu><menu>${address}</menu>
</fieldset>`;
});
az.insertAdjacentHTML('beforeend', group.join(''));
});
}
const data = arrange(contactData);
build(data);

Get value from object using 'Array Path'

I need to extract a value from a record using a path defined in a Array of strings. I came up with the following solution. It works, but this code seems a little bit too complicated to understand, in my opinion. I'd like to know if is there a better way to check if a value is a primitive type and if anyone can think in a simpler way to do the job.
const record = {
firstName: "Joe Doe",
personalData: {
email: "joe.doe#test.com"
}
};
const path = ["personalData","email"];
const getJsonValueUsingPath = (record, path, index) => {
const isPrimitiveType =
Object(record[path[index]]) !== record[path[index]];
if (isPrimitiveType) {
return record[path[index]];
} else {
return getColumnValue(record[path[index]], path, index + 1);
}
};
I need this function because I'm using a Third Party lib that requires such functionality. Please don't say it's a bad idea to extract an object property value using an array of strings.
To simplify, you could remove the primitive-check and just assume that the path is correct and leads to the value that needs to be returned, no matter whether it is primitive or not.
Secondly, you can replace the loop with a reduce() call on the path.
const getValueUsingPath = (record, path) =>
path.reduce((record, item) => record[item], record);
const record = {
firstName: "Joe Doe",
personalData: {
email: "joe.doe#test.com"
}
};
const path = ["personalData","email"];
console.log(getValueUsingPath(record, path));
Not sure if this is what you were after. It's only a little update to what you have, but it provides an alternate way to detect primitives
const record = {
firstName: "Joe Doe",
personalData: {
email: "joe.doe#test.com"
}
};
const path = ["firstName", "personalData", "email"];
let primitives = ['string', 'number', 'bigint', 'boolean', 'undefined', 'symbol', 'null'];
const getJsonValueUsingPath = (rec, pa, index) => {
let item = rec[pa[index]];
//console.log(typeof item)
return primitives.includes((typeof item).toLowerCase()) ? item : getJsonValueUsingPath(item, path, index + 1)
}
console.log(getJsonValueUsingPath(record, path, 0));
console.log(getJsonValueUsingPath(record, path, 1));
lodash if you don't mind:
const _ = require('lodash');
const record = { firstName: "Joe Doe", personalData: { email: "joe.doe#test.com" } };
const path = ["personalData","email"];
_.get(record, path); // 'joe.doe#test.com'

Javascript - how to capitalize key.value within an object?

How do I capitalize the first letter of each contact's first name?
String.prototype.capitalize = function (string) { return string.charAt(0).toUpperCase() + string.slice(1);}
var contactList = {};
contactList.bobSmith = {
firstName: "bob",
lastName: "smith",
location: "new york"
};
contactList.johnDoe = {
firstName: "john",
lastName: "doe",
location: "san francisco"
};
var contact2 = contactList["johnDoe"].firstName;
contact1.capitalize();
contact2.capitalize();
console.log(contact1 + " " + contact2);
I get an error message that says "Uncaught TypeError: Cannot read property 'charAt' of undefined".
function () { return this.charAt(0).toUpperCase() + this.slice(1);}
Your String.prototype.capitalize function requires an argument string which you're not passing to it when called. I'd recommend avoiding adding methods to the String.prototype and just use a stand-alone function instead...
function capitalise(str) {
return str.slice(0,1).toUpperCase() + str.slice(1);
}
contact2 = capitalise(contact2);
//=> "John"
...or if you're looking to capitalise the value in the contactList object, then just...
contactList.johnDoe.firstName = capitalise(contact2);
Hope that helped. :)
I wouldn't make it a habit of popping methods into prototype. It's frowned upon because future versions of JavaScript could come out and put a method with the same name in the prototype and you would be screwed.
I didn't write it this way but you could put the capitalize function on to the contactList object and you would basically achieve the same thing your trying to do with the prototype.
var contactList = {};
contactList.names =[
{
firstName: "bob",
lastName: "smith",
location: "new york"
},
{
firstName: "john",
lastName: "doe",
location: "san francisco"
}];
function capitalizeName(name) {
var strCap = name[0].toUpperCase();
var tailTxt = name.substr(1, name.length);
fullTxt = strCap + tailTxt;
return fullTxt;
)
$.each( contactList.names, function(i){
capitalizeName(contactList.names[i].firstName);
});

Codeacademy contact list building 7/8... Not returning contact info

i'm doing the codeacademy class section "building a contact list" .. what is wrong here? keep getting error "Oops, try again. It looks like your search function doesn't return contact information for Steve."
(http://www.codecademy.com/courses/javascript-beginner-en-3bmfN/0/7)
var friends = {};
friends.bill = {
firstName: "Bill",
lastName: "Gates",
number: "(206) 555-5555",
address: ['One Microsoft Way', 'Redmond', 'WA', '98052']
};
friends.steve = {
firstName: "Steve",
lastName: "Jobs",
number: "(556) 555-5555",
address: ['178 martio', 'cocoa', 'CA', '95074']
};
var list = function(friends) {
for (var key in friends) {
console.log(key);
}
};
var search = function(friends) {
for (var key in friends) {
if (friends[key].firstName === "Bill" || friends[key].firstName === "Steve") {
console.log(friends[key]);
return friends[key];
} else {
console.log("couldn't find them");
}
}
};
The error is in the search function:
The instructions tell you:
Define a function search that takes a single argument, name. If the
argument passed to the function matches any of the first names in
friends, it should log that friend's contact information to the
console and return it.
In a nutshell, it is asking you to create a function where you provide the name of the person you are searching, while you are providing friends which is also a global variable.
The goal of the exercice seems to be that by using:
search("steve");
you should get as a result:
Object :
{ firstName: 'Steve',
lastName: 'Jobs',
number: '(556) 555-5555',
address: [ '178 martio', 'cocoa', 'CA', '95074' ] }
In your (current) search function you will get a result not from the needle (the search parameter) but from your own preferences, defined in your if condition:
if (friends[key].firstName === "Bill" || friends[key].firstName === "Steve")
Hence, what we are going to do, is:
set name as parameter
loop the friends global variable
check if friends[key].firstName is equal to the needle provided (name).
if so, we log it and return it.
Put all together:
var search = function(name) { // <-- note the name instead of friends.
for (var key in friends) {
if (friends[key].firstName === name) { // <-- note that if
console.log(friends[key]);
return friends[key];
} else {
console.log("couldn't find them");
}
}
};
And you're done!
http://prntscr.com/7kth5t
Good try anyway, you were pretty close to the solution.
If you still have any problem or need any clarification feel free to comment.
Use for listing:
list(friends);
and for search:
search(friends);

Javascript For...In syntax issue?

The search function in the following code is not working and I believe it has something to do with the For...In loop but I am new to JS and unsure why:
var friends = {
bill: {
firstName: "bill",
lastName: "smith",
number: 1,
address: ["1"]
},
steve: {
firstName: "steve",
lastName: "smith",
number: 2,
address: ["2"]
}
};
var list = function(list) {
for(var item in list) {
console.log(item);
}
};
var search = function(name) {
for(var friend in friends) {
if(friend.firstName === name) {
console.log(friend);
return friend;
}
}
};
search("steve");
The for in loop iterates over keys, not values.
friend is a string holding the name of each property.
To get the value, use friends[friend].
Great documentation of the for..in loop can be found on mdn. Where variable is assigned through each iteration to "a different property name".
You also may not need to loop through each friend. What if you changed your search function to use hasOwnProperty on the object:
var search = function(name) {
if(friends.hasOwnProperty(name)){
return friends[name];
}
};
This would check that you have a property of name in the object friends and return it. Here's a quick EXAMPLE.

Categories