I have a function with a rather convoluted object in this format:
function getNBATeamsESPNByAbbrev(abbrev)
{
var json = {
"sports":[
{
"name":"basketball",
"id":40,
"uid":"s:40",
"leagues":[
{
"name":"National Basketball Assoc.",
"abbreviation":"nba",
"id":46,
"uid":"s:40~l:46",
"groupId":7,
"shortName":"NBA",
"teams":[
{
"id":1,
"uid":"s:40~l:46~t:1",
"location":"Atlanta",
"name":"Hawks",
"abbreviation":"ATL",
},
{
"id":2,
"uid":"s:40~l:46~t:2",
"location":"Boston",
"name":"Celtics",
"abbreviation":"BOS",
"color":"006532",
},
]
}
]
}
],
"resultsOffset":0,
"resultsLimit":50,
"resultsCount":30,
"timestamp":"2014-03-22T23:42:43Z",
"status":"success"
}
obj = _.find(json.sports[0].leagues[0].teams, function(obj) { return obj.abbreviation == abbrev })
if (obj !== undefined)
{
var team = new Object();
team.abbrev = abbrev;
team.location = obj.location;
team.nickname = obj.name;
return team;
}
}
It can be easier seen at this example JSFiddle. So I have the team abbreviation, and I want to pull back the team object as a whole (this is a stripped down version, leaving only the necessary details). This seems to work fine. However, another case has arisen, one in which I need to pull back the team object based on its location + " " + name. So I tried to do the same thing using underscore.js, passing in the parameter name, and changing the predicate in ._find to return obj.location + " " + obj.name == name. For example, I'd pass in Atalnta Hawks as name and expect to return the relevant team object. Here's a very similar JSFiddle demonstrating the change. But, now it can't seem to find the team object I want to pull. Is it because such a string concatenation isn't allowed in underscore.js, or is there something stupid I'm missing?
Line 50, you have:
team.abbrev = obj.abbrev;
and it should be
team.abbrev = obj.abbreviation;
Related
I'm trying to get all the contents from an array.
This is the function that extracts the data for display via innerHTML:
window.location.href = 'gonative://contacts/getAll?callback=contacts_callback';
function contacts_callback(obj) {
var contactinfo = obj.contacts.map(({givenName}) => givenName) + " " +
obj.contacts.map(({familyName}) => familyName) + " " + " (" +
obj.contacts.map(({organizationName}) => organizationName) + ") " +
obj.contacts.map(({phoneNumbers.phoneNumber}) => phoneNumbers.phoneNumber) + "<br>";
document.getElementById("demo").innerHTML = contactinfo;
}
This is an example of what the input looks like when there are only 2 contacts:
{"success":true,"contacts":[
{
"emailAddresses":[],
"phoneNumbers":
[
{
"label":"unknown",
"phoneNumber":"XXX-XXXXXXX"
}
],
"givenName":"John",
"organizationName":"Apple",
"familyName":"Appleseed",
},
{
"emailAddresses":[],
"phoneNumbers":
[
{
"label":"unknown",
"phoneNumber":"XXX-XXXXXXX"
}
],
"givenName":"John",
"organizationName":"Apple",
"familyName":"Appleseed",
},
]
}
I just want the result to be listed as:
John Appleseed (Apple) XXX-XXXXXXX
John Appleseed (Apple) XXX-XXXXXXX
Two issues:
You are displaying all given names, then all family names, ...etc, each with a separate .map() call. Instead only perform one .map() call on the array and then display the properties for each iterated object.
phoneNumbers.phoneNumber is not a correct reference. phoneNumbers is an array, so you should iterate it.
Also:
Template literals make it maybe a bit easier to build the string
You can use .join("<br>") to glue the lines together with line breaks.
Here is a corrected version:
function contacts_callback(obj) {
var contactinfo = obj.contacts.map(o =>
`${o.givenName} ${o.familyName} (${o.organizationName}) ${
o.phoneNumbers.map(n => n.phoneNumber)
}`)
.join("<br>");
document.getElementById("demo").innerHTML = contactinfo;
}
// Demo
var obj = {"success":true,"contacts":[{"emailAddresses":[],"phoneNumbers":[{"label":"unknown","phoneNumber":"XXX-XXXXXXX"}],"givenName":"John","organizationName":"Apple","familyName":"Appleseed",},{"emailAddresses":[],"phoneNumbers":[{"label":"unknown","phoneNumber":"XXX-XXXXXXX"}],"givenName":"John","organizationName":"Apple","familyName":"Appleseed",},]};
contacts_callback(obj);
<div id="demo"></div>
It is hard to give an answer since is hard to tell what you can have in your phoneNumbers array and if you will also display one line for each phone number in that array.
I'll do something like this:
function contacts_callback(obj) {
let arrayContacts = [];
// Iterate over all your contacts
obj.contacts.forEach(item => {
// Iterate over each contact's phone numbers
item.phoneNumbers.forEach(phone => {
// Building your string with string interpolation and pushing to result array
// You could also add <br> or other tags needed here
arrayContacts.push(`${item.givenName} ${item.familyName} (${item.organizationName}) ${phone.phoneNumber}`);
});
});
// Return your array, use it in your innerHTNL, etc.
return arrayContacts;
}
If Your obj is called "obj", than:
const result = obj.contacts.map(contact =>{
return `${contact.givenName} ${contact.familyName} (${contact.organizationName}) ${contact.phoneNumbers[0].phoneNumber}`
}
this code will give back an array of informations that U asked, but if user has more than 1 phone number, it will take only first from the list
I'm writing a program (in JavaScript) that will determine the most suitable car based on a user's needs. I have 4 objects, each one a different car. For simplification purposes I will provide one object as an example. Assume there are 3 other objects with the same properties in my code (Fiat, Audi, BMW, etc.).
var chevy = {
make: "Chevy",
model: "Bel Air",
year: 1957,
color: "red",
passengers: 2,
convertible: false,
mileage: 1021
};
The goal is to pass each object as an argument to a function, and return a boolean value based on conditionals. Here is the function:
function prequal(car) {
if (car.mileage > 10000) {
return false;
}
else if (car.year > 1960) {
return false;
}
else {
return true;
}
}
And calling the function:
var worthALook = prequal(taxi);
if (worthALook) {
console.log("You gotta check out this " + taxi.make + " " + taxi.model);
}
else {
console.log("You should really pass on the " + taxi.make + " " + taxi.model);
}
Do I have to call each object seperately? Or is there a simplified way of calling the function for all 4 objects at once? I'm new to this and working through this problem spiked my curiosity.
Thanks!!
EDIT: Sorry for the rambling but I seem to have figured out a solution. I am getting the desired output by using a nested function:
function worthALook(car) {
var shouldYouBuy = prequal(car);
if (shouldYouBuy) {
console.log("You gotta check out this " + car.model);
}
else {
console.log("You should really pass on this " + car.model);
}
}
calling the original 'prequal' function (see above) inside the 'worthALook' function outputs:
You should really pass on this Taxi
You should really pass on this Cadillac
You gotta check out this Bel Air
You should really pass on this 500
After each object I called the worthALook function like so:
worthALook(chevy);
worthALook(fiat);
etc.
I received my desired output but does my code seem like overkill?
Thanks!
You could put the objects in an Array and filter what's worth a look. Return that object instead of a boolean.
function worthALook(){
return ([].filter.call(arguments, prequal) || [false])[0];
/* remove [0] from the above line and change [false] to false if you
want to return multiple cars that satisfy a condition. Use indexes if you do that */
}
var car = worthALook(chevy, fiat, audi, bmw); // pass inas many as you want
if(car) // check if it's a car
console.log("You gotta check out this " + taxi.make + " " + taxi.model);
The return statement is used with || so even if none of the cars satisfy the condition, then you could return false. Also, we are using arguments object, so you can pass in as many car objects to the worthALook function, which will make use of prequal function to filter 'em.
I have the following JSON -
{
"node1":[
{
"one":"foo",
"two":"foo",
"three":"foo",
"four":"foo"
},
{
"one":"bar",
"two":"bar",
"three":"bar",
"four":"bar"
},
{
"one":"foo",
"two":"foo",
"three":"foo",
"four":"foo"
}
],
"node2":[
{
"link":"baz",
"link2":"baz"
},
{
"link":"baz",
"link2":"baz"
},
{
"link":"qux",
"link2":"qux"
},
]
};
I have the following javascript that will remove duplicates from the node1 section -
function groupBy(items, propertyName) {
var result = [];
$.each(items, function (index, item) {
if ($.inArray(item[propertyName], result) == -1) {
result.push(item[propertyName]);
}
});
return result;
}
groupBy(catalog.node1, 'one');
However this does not account for dupicates in node2.
The resulting JSON I require is to look like -
{
"node1":[
{
"one":"foo",
"two":"foo",
"three":"foo",
"four":"foo"
},
{
"one":"bar",
"two":"bar",
"three":"bar",
"four":"bar"
}
],
"node2":[
{
"link":"baz",
"link2":"baz"
},
{
"link":"qux",
"link2":"qux"
},
]
};
However I cannot get this to work and groupBy only returns a string with the duplicates removed not a restructured JSON?
You should probably look for some good implementation of a JavaScript set and use that to represent your node objects. The set data structure would ensure that you only keep unique items.
On the other hand, you may try to write your own dedup algorithm. This is one example
function dedup(data, equals){
if(data.length > 1){
return data.reduce(function(set, item){
var alreadyExist = set.some(function(unique){
return equals(unique, item);
});
if(!alreadyExist){
set.push(item)
}
return set;
},[]);
}
return [].concat(data);
}
Unfortunately, the performance of this algorithm is not too good, I think somewhat like O(n^2/2) since I check the set of unique items every time to verify if a given item exists. This won't be a big deal if your structure is really that small. But at any rate, this is where a hash-based or a tree-based algorithm would probably be better.
You can also see that I have abstracted away the definition of what is "equal". So you can provide that in a secondary function. Most likely the use of JSON.stringify is a bad idea because it takes time to serialize an object. If you can write your own customized algorithm to compare key by key that'd be probably better.
So, a naive (not recommended) implementation of equals could be somewhat like the proposed in the other answer:
var equals = function(left, right){
return JSON.stringify(left) === JSON.stringify(right);
};
And then you could simply do:
var res = Object.keys(source).reduce(function(res, key){
res[key] = dedup(source[key], equals);
return res;
},{});
Here is my version:
var obj = {} // JSON object provided in the post.
var result = Object.keys(obj);
var test = result.map(function(o){
obj[o] = obj[o].reduce(function(a,c){
if (!a.some(function(item){
return JSON.stringify(item) === JSON.stringify(c); })){
a.push(c);
}
return a;
},[]); return obj[o]; });
console.log(obj);//outputs the expected result
Using Array.prototype.reduce along with Array.prototype.some I searched for all the items being added into the new array generated into Array.prototype.reduce in the var named a by doing:
a.some(function(item){ return JSON.stringify(item) === JSON.stringify(c); })
Array.prototype.some will loop trough this new array and compare the existing items against the new item c using JSON.stringify.
Try this:
var duplicatedDataArray = [];
var DuplicatedArray = [];
//Avoiding Duplicate in Array Datas
var givenData = {givenDataForDuplication : givenArray};
$.each(givenData.givenDataForDuplication, function (index, value) {
if ($.inArray(value.ItemName, duplicatedDataArray) == -1) {
duplicatedDataArray.push(value.ItemName);
DuplicatedArray.push(value);
}
});
With a javascript json object like this:
var data = {
blog : {
title: "my blog",
logo: "blah.jpg",
},
posts : [
{
title: "test post",
content: "<p>testing posts</p><br><p>some html</p>"
},
]
}
var lookup = "blog.title" //this gets generated from a template file
Now I know you can do something like, but these don't quite do what I need:
console.log(data['blog']); //works
console.log(data.blog.title); //works
console.log(data['blog']['title']); //works, but i dont know the depth of the lookup
But I need to be able to do something like the code below because I can't hardcode the structure, it gets generated and stored in lookup each time. Do I have to build this functionality using string cutting and recursion?? I really hope not
console.log(data['blog.title']); //does not work
console.log(data[lookup]); //does not work
EDIT....
Okay, possibly found a workaround. I don't know if this is safe or recommended practice, so comments on that would be great. Or alternative methods. So combining this with the code above.
var evaltest = "var asdf ="+JSON.stringify(data)+";\n"
evaltest += "asdf."+lookup
console.log(eval(evaltest)) //returns correctly "my blog" as expected
You could use dottie https://www.npmjs.org/package/dottie, which allows you to deep traverse an object using strings
var values = {
some: {
nested: {
key: 'foobar';
}
}
}
dottie.get(values, 'some.nested.key'); // returns 'foobar'
dottie.get(values, 'some.undefined.key'); // returns undefined
you could use:
data['blog']['title']
I've experimented with a couple ways of doing this including eval and using a dictionary lookup with switch(exp.length). This is the final version (comments stripped) I created:
var deepRead = function (data, expression) {
var exp = expression.split('.'), retVal;
do {
retVal = (retVal || data)[exp.shift()] || false;
} while (retVal !== false && exp.length);
return retVal || false;
};
//example usage
data = {
a1: { b1: "hello" },
a2: { b2: { c2: "world" } }
}
deepRead(data, "a1.b1") => "hello"
deepRead(data, "a2.b2.c2") => "world"
deepRead(data, "a1.b2") => false
deepRead(data, "a1.b2.c2.any.random.number.of.non-existant.properties") => false
Here's the Gist with full commenting: gist.github.com/jeff-mccoy/9700352. I use this to loop over several thousand items and have had no issues with deep-nested data. Also, I'm not wrapping in try/catch anymore due to the (small) performance hit: jsPerf.
I'm using Backbone.js/Underscore.js to render a HTML table which filters as you type into a textbox. In this case it's a basic telephone directory.
The content for the table comes from a Collection populated by a JSON file.
A basic example of the JSON file is below:
[{
"Name":"Sales and Services",
"Department":"Small Business",
"Extension":"45446",
},
{
"Name":"Technical Support",
"Department":"Small Business",
"Extension":"18800",
},
{
"Name":"Research and Development",
"Department":"Mid Market",
"Extension":"75752",
}]
I convert the text box value to lower case and then pass it's value along with the Collection to this function, I then assign the returned value to a new Collection and use that to re-render the page.
filterTable = function(collection, filterValue) {
var filteredCollection;
if (filterValue === "") {
return collection.toJSON();
}
return filteredCollection = collection.filter(function(data) {
return _.some(_.values(data.toJSON()), function(value) {
value = (!isNaN(value) ? value.toString() : value.toLowerCase());
return value.indexOf(filterValue) >= 0;
});
});
};
The trouble is that the function is literal. To find the "Sales and Services" department from my example I'd have to type exactly that, or maybe just "Sales" or "Services". I couldn't type "sal serv" and still find it which is what I want to be able to do.
I've already written some javascript that seems pretty reliable at dividing up the text into an array of Words (now updated to code in use).
toWords = function(text) {
text = text.toLowerCase();
text = text.replace(/[^A-Za-z_0-9#.]/g, ' ');
text = text.replace(/[\s]+/g, ' ').replace(/\s\s*$/, '');
text = text.split(new RegExp("\\s+"));
var newsplit = [];
for (var index in text) {
if (text[index]) {
newsplit.push(text[index]);
};
};
text = newsplit;
return text;
};
I want to loop through each word in the "split" array and check to see if each word exists in one of the key/values. As long as all words exist then it would pass the truth iterator and get added to the Collection and rendered in the table.
So in my example if I typed "sal serv" it would find that both of those strings exist within the Name of the first item and it would be returned.
However if I typed "sales business" this would not be returned as although both the values do appear in that item, the same two words do not exist in the Name section.
I'm just not sure how to write this in Backbone/Underscore, or even if this is the best way to do it. I looked at the documentation and wasn't sure what function would be easiest.
I hope this makes sense. I'm a little new to Javascript and I realise I've dived into the deep-end but learning is the fun part ;-)
I can provide more code or maybe a JSFiddle if needed.
Using underscore's any and all make this relatively easy. Here's the gist of it:
var toWords = function(text) {
//Do any fancy cleanup and split to words
//I'm just doing a simple split by spaces.
return text.toLowerCase().split(/\s+/);
};
var partialMatch = function(original, fragment) {
//get the words of each input string
var origWords = toWords(original + ""), //force to string
fragWords = toWords(fragment);
//if all words in the fragment match any of the original words,
//returns true, otherwise false
return _.all(fragWords, function(frag) {
return _.any(origWords, function(orig) {
return orig && orig.indexOf(frag) >= 0;
});
});
};
//here's your original filterTable function slightly simplified
var filterTable = function(collection, filterValue) {
if (filterValue === "") {
return collection.toJSON();
}
return collection.filter(function(data) {
return _.some(_.values(data.toJSON()), function(value) {
return partialMatch(value, filterValue);
});
});
};
Note: This method is computationally pretty inefficient, as it involves first looping over all the items in the collection, then all the fields of each item, then all words in that item value. In addition there are a few nested functions declared inside loops, so the memory footprint is not optimal. If you have a small set of data, that should be OK, but if needed, there's a number of optimizations that can be done. I might come back later and edit this a bit, if I have time.
/code samples not tested