UPDATE - Thanks for all the great answers and incredibly fast response. I've learned a great deal from the suggested solutions. I ultimately chose the answer I did because the outcome was exactly as I asked, and I was able to get it working in my application with minimal effort - including the search function. This site is an invaluable resource for developers.
Probably a simple task, but I can't seem to get this working nor find anything on Google. I am a Javascript novice and complex JSON confuses the hell out of me. What I am trying to do is make a PhoneGap Application (Phone Directory) for our company. I'll try to explain my reasoning and illustrate my attempts below.
I have JSON data of all of our employees in the following format:
[
{
"id":"1",
"firstname":"John",
"lastname":"Apple",
"jobtitle":"Engineer"
},
{
"id":"2",
"firstname":"Mark",
"lastname":"Banana",
"jobtitle":"Artist"
},
... and so on
]
The mobile framework (Framework 7) that I am using offers a "Virtual List" solution which I need to take advantage of as our directory is fairly large. The virtual list requires you to know the exact height of each list item, however, you can use a function to set a dynamic height.
What I am trying to do is create "headers" for the alphabetical listing based on their last name. The JSON data would have to be restructured as such:
[
{
"title":"A"
},
{
"id":"1",
"firstname":"John",
"lastname":"Apple",
"jobtitle":"Engineer"
},
{
"title":"B"
},
{
"id":"2",
"firstname":"Mark",
"lastname":"Banana",
"jobtitle":"Artist"
},
... and so on
]
I've been able to add key/value pairs to existing objects in the data using a for loop:
var letter, newLetter;
for(var i = 0; i < data.length; i++) {
newLetter = data[i].lastname.charAt(0);
if(letter != newLetter) {
letter = newLetter
data[i].title = letter;
}
}
This solution changes the JSON, thus outputting a title bar that is connected to the list item (the virtual list only accepts ONE <li></li> so the header bar is a div inside that bar):
{
"id":"1",
"firstname":"John",
"lastname":"Apple",
"jobtitle":"Engineer",
"title":"A"
},
{
"id":"1",
"firstname":"Mike",
"lastname":"Apricot",
"jobtitle":"Engineer",
"title":""
}
This solution worked until I tried implementing a search function to the listing. When I search, it works as expected but looks broken as the header titles ("A", "B", etc...) are connected to the list items that start the particular alphabetical section. For this reason, I need to be able to separate the titles from the existing elements and use them for the dynamic height / exclude from search results.
The question: How can I do a for loop that inserts [prepends] a NEW object (title:letter) at the start of a new letter grouping? If there is a better way, please enlighten me. As I mentioned, I am a JS novice and I'd love to become more efficient programming web applications.
var items = [
{ "lastname":"Apple" },
{ "lastname":"Banana" },
{ "lastname":"Box" },
{ "lastname":"Bump" },
{ "lastname":"Can" },
{ "lastname":"Switch" }
];
var lastC = null; //holds current title
var updated = []; //where the updated array will live
for( var i=0;i<items.length;i++) {
var val = items[i]; //get current item
var firstLetter = val.lastname.substr(0,1); //grab first letter
if (firstLetter!==lastC) { //if current title does not match first letter than add new title
updated.push({title:firstLetter}); //push title
lastC = firstLetter; //update heading
}
updated.push(val); //push current index
}
console.log(updated);
Well right now you have an array of objects - prefixing the title as its own object may be a bit confusing - a better structure may be:
[
{
title: "A",
contacts: [
{
"id":"1",
"firstname":"John",
"lastname":"Apple",
"jobtitle":"Engineer",
"title":"A"
}
]
Given your current structure, you could loop and push:
var nameIndexMap = {};
var newContactStructure = [];
for (var i = 0; i < data.length; i++) {
var letter = data[i].lastname.charAt(0);
if (nameIndexMap.hasOwnProperty(letter)) {
//push to existing
newContactStructure[nameIndexMap[letter]].contacts.push(data[i])
} else {
//Create new
nameIndexMap[letter] = newContactStructure.length;
newContactStructure.push({
title: letter,
contacts: [
data[i]
]
});
}
}
newContactStructure will now contain your sorted data.
Demo: http://jsfiddle.net/7s50k104/
Simple for loop with Array.prototype.splice will do the trick:
for (var i = 0; i < data.length; i++) {
if (i == 0 || data[i-1].lastname[0] !== data[i].lastname[0]) {
data.splice(i, 0, {title: data[i].lastname[0]});
i++;
}
}
Demo. Check the demo below.
var data = [
{"lastname":"Apple"},
{"lastname":"Banana"},
{"lastname":"Bob"},
{"lastname":"Car"},
{"lastname":"Christ"},
{"lastname":"Dart"},
{"lastname":"Dog"}
];
for (var i = 0; i < data.length; i++) {
if (i == 0 || data[i-1].lastname[0] !== data[i].lastname[0]) {
data.splice(i, 0, {title: data[i].lastname[0]});
i++;
}
}
alert(JSON.stringify( data, null, 4 ));
Related
So I have a Table made from some json data...
{
"AKH":{
"name": "Amonkhet",
"code": "AKH"
"cards": [
{
"artist": "Izzy",
"cmc": 3,
"colorIdentity": [
"W"
],
"colors": [
"White"
],
"id": "df3a6e0336684c901358f3ff53ec82ff5d7cdb9d",
"imageName": "gideon of the trials",
"layout": "normal",
"loyalty": 3,
"manaCost": "{1}{W}{W}",
"multiverseid": 426716,
"name": "Gideon of the Trials",
"number": "14",
"rarity": "Mythic Rare",
"subtypes": [
"Gideon"
],
"text": "+1: Until your next turn, prevent all damage target permanent would deal.\n0: Until end of turn, Gideon of the Trials becomes a 4/4 Human Soldier creature with indestructible that's still a planeswalker. Prevent all damage that would be dealt to him this turn.\n0: You get an emblem with \"As long as you control a Gideon planeswalker, you can't lose the game and your opponents can't win the game.\"",
"type": "Planeswalker — Gideon",
"types": [
"Planeswalker"
]
},
The Table row ends up looking like this for each of the cards. at the moment I only Attach the ID, Card name, and Mana Cost to each row
<td><a href="#" onclick="showInfo(this.id)"
id="df3a6e0336684c901358f3ff53ec82ff5d7cdb9d">Gideon of the Trials</a></td>
Now I want to search through these cards. (Keep in mind there are over 17,000 different cards that will be on this list) I can get it to find the things.. But I'm having several different issues... Either it finds them all but doesn't hide the rest of the list, or it hides the whole list and only displays one of the found cards.
So question A... What am I missing to make the search work correctly?
$(document).on('change', 'input[type=checkbox]', function() {
var lis = $('.cardsRow')
$('input[type=checkbox]').filter(':checked').each(function(){
filterKeyB = $(this).attr('id')
filterKeyA = $(this).attr('name')
$.each(json, function(setCode, setListing) {
$.each(setListing.cards,function(cardNum, cardListing){
var x = Object.keys(cardListing)
var y = Object.keys(cardListing).map(function (key){
return cardListing[key]
})
for (i = 0; (i < x.length); i++) {
if(x[i] === filterKeyA){
if (y[i] instanceof Array){
var holder = y[i]
var valueArr =[]
for(var k = 0; k < holder.length; k++){
valueArr = holder.join('|').toLowerCase().split('|')
var foundIt = valueArr.includes(filterKeyB)
}
}else{
var stringy = y[i]
var stringyA= stringy.toLowerCase().replace(/\s/g, '')
if (stringyA === filterKeyB){
var foundIt = true
}
}
if(foundIt === true){
$winner = cardListing.name
for (k = 0; (k < lis.length); k++){
if (lis[k].innerText.indexOf($winner) != -1) {
$(lis[k]).show()
}
}
}
}
}
})
Question B... Since you are already here... Would it be better practice to attach the data that can be searched to the element itself? Maybe just the most searched (Like Name and Mana) and have more advanced queries go through the data again?
I don't understand why the code isn't working or even how it works, it looks like it references some functions that aren't defined in the sample. But I can share with you a really simple/intuitive way to filter stuff, I hope you find it useful.
Native filter method is so useful for what you're trying to do, it takes a callback that takes current element as an arg and returns true or false, if true, the element is included in the new array it produces.
But filter only takes one function, and you have many filters, so let's make a function that combines many filter Fns together into one fn, so you can pass them in all at once:
const combineFilters = (...fns) => val => fns.reduce((prev, curr) => prev || curr(val), false);
OK, how about storing the names of the filter functions as keys in an object so we can reference them using a string? That way we could give each checkbox an ID corresponding to the name of the filter function they are supposed to apply, and makes things really easy to implement (and read):
const filterFns = {
startsWithG(card) {
return card.name[0] === 'G';
},
//etc.
};
OK, time to get the IDs of all the checkboxes that are clicked, then map them into an array of functions.
const filters = $('input[type=checkbox]')
.filter(':checked')
.map((e, i) => $(i).attr('id'))
.get()
.map(fnName => filterFns[fnName])
(Assume the relevant data is stored in a var called...data.) We can use combineFilters combined with filters (array of Fns) to activate all of the relevant filters, then map the resulting array of matching objects into the HTML of your choosing.
const matches = data.cards
.filter(combineFilters(...filters))
.map(card => `<div>${card.name}</div>` );
Then time to update DOM with your matches!
As others have noted, if you need to do any more complicated filtering on objects or arrays, lodash library is your friend!
Here is my JSON code. I'm storing this json in an array.
total: {
limited: {
things: "451",
platforms: [
{
count: "358",
id: "Windows"
},
{
count: "44",
id: "X11"
},
{
count: "42",
id: "Macintosh"
},
{
count: "2",
id: "Linux"
},
{
count: "1",
id: "iPhone"
},
{
count: "1",
id: "iPod"
}
]
},
}
When i want to show the count of things in total > limited > things, I'm using the below code and it's working fine.
document.getElementById( "limited" ).value = arr.total.limited.things;
It's showing the 'things' value in 'limited' div area.
But I want to show the count of the particular id in platforms.
total > limited > platforms > id > windows.
How can i show the value of particular id from above json?
document.getElementById( "limited" ).value = arr.total.limited.platforms[0].count;
is showing the count but, the order of platforms always change, so we don't know where the windows is in the order exactly to use the above method.
How can we show the count of particular id from above json?
Also, how can we combine particular multiple id's count? for example, how to know all the count of Macintosh, iphone & ipod count combined?
Thanks.
You can filter the array to look for the Windows entry. Then, when you've got an array with only one element, access the count property of the the first element:
arr.total.limited.platforms.filter(
function(el) { return el.id == "Windows"; })[0].count
Getting the sum of counts for multiple platforms could be done like this by using the Array.map function:
// here, arr is the structure you describe in your question, and platf is an
// array of all desired platforms
function combinedCount(arr, platf) {
// for each element of the list of platforms in `arr`, we check
// if its id is inside the list of desired platforms,
// and return either its count or 0
var x = arr.total.limited.platforms.map(function(el) {
return (platf.indexOf(el.id) != -1) ? parseInt(el.count) : 0; });
// now x is an array of counts for the relevant platforms and
// 0 for all others, so we can just add its elements and return the sum
var count = 0;
for (var i = 0; i < x.length; i++) count += x[i];
return count;
}
You'd use it like this:
combinedCount(arr, ["Windows", "Linux"])
// returns 360
you can iterate over the elements of the platforms array, an look for the one you need.
for (var p in total.element.platform) {
if (total.element.platform[p].id == "iPhone") {
alert(total.element.platform[p].count)
}
}
If its possible you can structure your "platforms" as an object, that makes it possible to adress the platforms by key, this way you dont need to iterate over the entire array
A hacky solution to that could be to run arr.total.limited.things.platforms through a for loop and check the id tag to see if it's windows, if it is return value.
If you're using underscore.js ->
_.each(arr.total.limited.things.platforms, function(x) {
if (x.id === "Windows") {
document.getElementById( "limited" ).value = x.value;
}
});
or a good old fashioned for loop will work too! Using this method, you can change the if statement to check for the wanted iphone/etc. ids and then increment the value.
Another solution to this could be to change the way your JSON is stored. So instead of making platforms an array, you could make it an object:
platforms{
'windows: { id = '', value = 1 },
etc. and then just call by key!
I have a json array contains many elements. A part of the array is given:
var some_array = {
"0":{
"picture":"qwerty.jpg",
"textofPicture":"comment for Picture 5",
"picNo":1,
"id":25,
"uid0":125,
"uid1":123,
"uid2":126,
"uid3":127,
"uid4":124,
"u0":"149",
"u1":"80",
"u2":"71",
"u3":"108",
"u4":"158",
"accepted":false,
"su":"",
"point":0
},
"1":{
"picture":"qwerty.jpg",
"textofPicture":"comment for Picture 3",
"picNo":2,
"id":23,
"uid0":113,
"uid1":117,
"uid2":116,
"uid3":114,
"uid4":115,
"u0":"62",
"u1":"58",
"u2":"115",
"u3":"138",
"u4":"106",
"accepted":false,
"su":"",
"point":0
}
}
I want to count how many accepted key's value is true. I am sure there is good way to do this. I do not want to dive into loops.
One way to obtain the count you're looking for is like this
var count = 0;
var some_array = [
0 : {
accepted : false
},
1 : {
accepted : true
}
];
for (var i in some_array) {
if (some_array[i].accepted === true) {
count++;
}
}
return count;
Let me know if this helps and makes since to you. if need be i can make plunker for a visual.
I have the following code in an array for Javascript JSON:
params = {
"fighters": [
{
"name": "Muhammad Ali",
"nickname": "The Greatest"
},
{
"name": "Chuck Liddell",
"nickname": "The Iceman"
}
]
};
Now I have "N" variable data "name" and "nickname" from a database SQLITE.
The idea is to show all the "nick" and "nickname" that exist from the database iteratively.
How I can fill it?
I tested with a FOR that runs all the arrangements and I charge them, for it did something like this:
params = {
"fighters": [ show[i] ]
};
It does not work.
I hope I explained correctly.
Thanks.
That will retrieve all fighters names:
var len = params.fighters.length;
for (var i = 0; i < len; i++) {
console.log( params.fighters[i].name );
}
To change fighters names values do the following:
var len = params.fighters.length;
for (var i = 0; i < len; i++) {
params.fighters[i].name = 'Your_Fighter_Name';
}
Hopefully i have helped you.
assuming we're in the fetchAll(function (error, rows) {}) callback using node-sqlite (meaning rows is an array of objects, with the keys in the objects being the column names):
var params = { "fighters" : [] };
rows.forEach(function (row) {
params.fighters.push({name: row.name, nickname: row.nickname});
});
Remember, JSON is subset of JS, it is not it's own language. It's NOT a programming language. You can't "do" anything in JSON, as it is not a programming language. You can't iterate, recurse, fill... you can do that with JS.
Sorry for repeating myself, but apparently a lot of people mistake JSON for something it isn't. I'm not assuming you do, but it can't be said too often ;-) How you want to send the JSON I can't tell you, as there is no information in your question. If you want to do this via a byte-based protocol such as HTTP you might want to use JSON.stringify() to get a textual representation of your object that you can send over the network.
See my version of your fiddle: http://jsfiddle.net/dpL4c/2/ .
From this query to the Wikipedia API:
http://en.wikipedia.org/w/api.php?action=query&prop=links&format=json&plnamespace=0& pllimit=10&titles=List%20of%20television%20programs%20by%20name
I get a JSON structure, e.g.:
var data = {
"query": {
"pages": {
"1536715": {
"pageid": 1536715,
"ns": 0,
"title": "List of television programs by name",
"links": [
{
"ns": 0,
"title": "$1.98 Beauty Show"
},
{
"ns": 0,
"title": "''Dance Academy''"
}
]
}
}
},
"query-continue": {
"links": {
"plcontinue": "1536715|0|15\/Love"
}
}
}
I want to work with the elements of the "links" array. However, based on the existence of the element "pageid": 1536715, I suspect the name of the nested object "1536715" is a dynamic value that may change. I am reluctant to use this name to access the "links" array, e.g. query.pages.1536715.links.
Is there any way to "step" past this object, i.e. with a wild card query.pages.*.links? Failing that, can I iterate the children of pages to get this object? I am using jQuery 1.7.2 for this project, in case you can suggest any helpful methods from there.
Yes, you will need to loop over it. See also the very similiar question parse json string from wikimedia using jquery. You could also receive a list of result pageids using the indexpageids parameter.
if (data && data.query && data.query.pages)
var pages = data.query.pages;
else
// error: No pages returned / other problems!
for (var id in pages) { // in your case a loop over one property
var links = pages[id].links
if (links)
for (i=0; i<links.length; i++) {
// do what you want with links[i]
}
else
// error: No links array returned for whatever reasons!
}
This should get you there...
var links, i, pageID;
for (pageID in query.pages) {
links = query.pages[pageID].links;
for (i=0; i<links.length; i++) {
link = links[i];
link.ns; link.title // handle link obj here.
}
}