I am trying to loop over an object and check if a certain property is inside any other certain parts of the object. I am trying to see if the first level.parentSearch is inside any of the other firstlevel.filters array (of objects).
So my object looks like this for example :
var currentfilters = [{
"id": "topics",
"name": "Topics",
"filters": [{
"id": "isSubTopic",
"label": "isSubTopic",
"search": "isSubTopic",
"num": 15
}]
}, {
"id": "isSubTopic",
"name": "isSubTopic",
"parentSearch": "isSubTopic",
"filters": [{
"id": "subtopicFilter",
"label": "subtopicFilter",
"search": "subtopicFilter",
"num": 2
}, {
"id": "subtopicFilter1",
"label": "subtopicFilter1",
"search": "subtopicFilter1",
"num": 2
}]
}, {
"id": "notSubTopic",
"name": "notSubTopic",
"parentSearch": "uniueParentSearch",
"filters": [{
"id": "notSubTopic1",
"label": "notSubTopic1",
"search": "notSubTopic1",
"num": 5
}]
}
];
So what I am trying to achieve is to loop over this object and modify it a little bit (if necessary) and return it. What I am trying to do is check the first level if the .parentSearch property is inside any of the other objects .filter array as a .search property. So in this example isSubTopic would be what I am looking for, because it is inside the Topics filters array.
This is my first time trying these kind of problem, so if I am missing anything please let me know. I figured since I want to return a modified object, it would be good to reduce this object and check inside. The part I am struggling with is the bit that checks if my current parentSearch (in the reduce loop) is in any other objects filter array (under the .search property). I have lodash to mess around with so I have tried both _.find and _.has, but I think I am not approaching this correctly. Any/all input would be greatly appreciated!
Here is what I was trying it with : https://jsfiddle.net/0fttkyey/32/
function checkIfSubtopic(memo, value, key) {
if(value.parentSearch) {
//check if value.parentSearch is in any of the value.filters
console.log("find?", _.find(currentfilters, value.parentSearch));
if(_.find(currentfilters, value.parentSearch)){
console.log("is subtopic?");
} else {
console.log("not sub topic");
}
}
return memo;
}
Interestingly, your currentfilters is not the same in the jsfiddle you provide, so it gives no result.
If I understand correctly, what you try to achieve is:
For each "filter" in currentfilters array, check if parentSearch member is truthy.
If so, loop through all other filters.
For each of these filters, loop through its filters array member.
If the search member of one of the objects in this filters array is equal to parentSearch value, keep the current "filter".
So you have 3 nested loops.
The 2 outer loops iterate over the same currentfilters array, except that the 2nd (inner) one skips the filter which parentSearch value is being searched for.
As soon as parentSearch value is found, break loops 2 and 3 (the 2 inner-most) and go to next item of 1st (outer-most) loop.
Without using lodash and building a new result array (instead of modifying currentfilters array in place which may yield unexpected results), you would have for example:
var i = 0,
j,
ifiltermax = currentfilters.length,
currentParentSearch,
currentFiltersArray,
k,
result = [];
for (; i < ifiltermax; i += 1) { // Loop 1.
currentParentSearch = currentfilters[i].parentSearch;
if (currentParentSearch) { // If `parentSearch` is truthy.
loop_j: for (j = 0; j < ifiltermax; j += 1) { // Loop 2.
if (j != i) { // Skip current filter which `parentSearch` is being searched for.
currentFiltersArray = currentfilters[j].filters;
for (k = 0; k < currentFiltersArray.length; k += 1) { // Loop 3.
if (currentFiltersArray[k].search === currentParentSearch) {
result.push(currentfilters[i]); // Keep current "filter".
console.log("Found " + currentParentSearch + " (from item #" + i + ") in item #" + j + ", filter #" + k);
break loop_j; // Break loops 2 and 3.
}
}
console.log("Did not find " + currentParentSearch + " (from item #" + i + ") in any other filter.");
}
}
}
}
console.log(result);
Updated jsfiddle: https://jsfiddle.net/0fttkyey/71/ (with currentfilters from the question above, instead of the one in jsfiddle version 32).
Related
I am practising with vainilla JS, fetching some data from a public API, and manipulating the DOM with them.
I am finding a problem i am not sure how to solve.
The object has some null values in some fields , but other times that field contain some information.
When I run a loop over it, it always breaks when it reaches one field with a null value.
I would like to know what the best approach should be.
One might be to "clean" the null values from the object, and store the "new cleaned" object in a variable . But guess that´s not the way to go if the API were big.
My second idea would be to include an if condition in the loop, saying to jump to the next one, if the value found is === null , something like this :
function createSomeCards(myObject) {
for (let i=0; i < myObject.lenght; i++) {
if (value === null) { i = i+1 } else { i = i }
here would go the rest of the code to build the cards
i don`t know if that is the approach (even if my code is waz wrong), or should be different.
The object has an structure similar to this :
myObject = [
{
"Habitat Impacts": "Area closures ",
"Image Gallery": [
{
"src": "anImage.jpg",
"alt": "some text",
"title": "more text"
},
{
"src": null,
"alt": "additional text",
"title": "a title here"
},
{
"src": "otherImg.jpg",
"alt": "imgInfor",
"title": null
}
],
"Management": null,
"Scientific Name": "Urophycis tenuis",
"Species Illustration Photo": {
"src": null,
"alt": "Illustration",
"title": ""
},
"Ecosystem Services": null,
"Servings": "1",
"last_update": "05/19/2021 - 13:04"
}]
I update the comment with the JS code in which I am finding the issue with the Null values
function createCards(myObject) {
let cardContainer = document.getElementById('cardContainer');
for (let i = 0; i < myObject.length; i++) {
aInfoButtonLeft.onclick = function () {
let divSlidAct = document.getElementById('sliderImg')
let imgSlidAct= document.createElement('img');
imgSlidAct.setAttribute('src', myObject[i]['Image Gallery'][i]['src']);
imgSlidAct.append(divSliderActive);
}
}
}
I am going to leave my orignal answer under this one because I think it still relevant.
you have an array in an array and you are not looping over the second array "Image Gallery". Adding a second loop lets you get all the images.
I added a check to see if the src was null. this should stop any errors you get about src being null.
function createCards(myObject) {
let cardContainer = document.getElementById('cardContainer');
for (let i = 0; i < myObject.length; i++) {
for (let x=0; x < myObject[i]["Image Gallery"].length; x++) {
if (myObject[i]['Image Gallery'][x]["src"] === null) {
// the source is empty do nothing
}else{
aInfoButtonLeft.onclick = function () {
let divSlidAct = document.getElementById('sliderImg')
let imgSlidAct= document.createElement('img');
imgSlidAct.setAttribute('src', myObject[i]['Image Gallery'][x]["src"]);
imgSlidAct.append(divSliderActive);
}
}
}
}
}
ORIGNAL ANSWER
You have 2 1 issue.
you have an array in an array and you are not looping over the second array "Image Gallery"
2. the 'src' field is an attribute of an object and not an index of an array.
The fact that you have null values doesn't matter. the loop will continue.
See the code below with comments.
let myObject = [
{
"Habitat Impacts": "Area closures ",
"Image Gallery": [
{
"src": "anImage.jpg",
"alt": "some text",
"title": "more text"
},
{
"src": null,
"alt": "additional text",
"title": "a title here"
},
{
"src": "otherImg.jpg",
"alt": "imgInfor",
"title": null
}
],
"Management": null,
"Scientific Name": "Urophycis tenuis",
"Species Illustration Photo": {
"src": null,
"alt": "Illustration",
"title": ""
},
"Ecosystem Services": null,
"Servings": "1",
"last_update": "05/19/2021 - 13:04"
}];
//loop over my object
for (let i=0; i < myObject.length; i++) {
//loop over the image gallery
for (let x=0; x < myObject[i]["Image Gallery"].length; x++) {
console.log("src: " + myObject[i]["Image Gallery"][x].src); //notice it .src and not ["src"]
console.log("alt: " +myObject[i]["Image Gallery"][x].alt);
console.log("title: " +myObject[i]["Image Gallery"][x].title);
//I commented out this section so the snippet would work
/*
aInfoButtonLeft.onclick = function () {
let divSlidAct = document.getElementById('sliderImg')
let imgSlidAct= document.createElement('img');
imgSlidAct.setAttribute('src', myObject[i]['Image Gallery'][x].src);
imgSlidAct.append(divSliderActive);
*/
}
}
Please change the question title, because you're not looping over an object where there's some null values, but you're looping over an array that only has non-null values in your example. The item of that loop can have a property that is sometimes null. That's a different issue.
You asked about the best approach.
"clean" the null values and store the "new cleaned" object in a variable
This is not necessary and yes, this would blow up your memory usage and would consume time
include an if condition in the loop, saying to jump to the next one, if the null value found
if (item.property === null) {
// jumping to the next item of the loop
continue;
}
// do the processing
You can either jump to the next (continue) or you don't do the processing for that item (you do the processing only if not null) like this:
if (item.property !== null) {
// do the processing
}
You could even be safer if checking properties by hasOwnProperty() like here: https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty
I recommend using functional programming and filtering the array by using https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter like this - this results in a much clearer code:
// for this you could also use myObject.forEach(), but that's not the question here
for (let i = 0; i < myObject.length; i++) {
// filtering image gallery for items that ...
myObject[i]['Image Gallery'] //
// have a non-null src
.filter(item => item.src !== null) //
// and do for each item that following...
.forEach(item => {
console.log('src: ' + item.src); //notice it .src and not ["src"]
console.log('alt: ' + item.alt);
console.log('title: ' + item.title);
});
}
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!
I'm trying to using this JavaScript code:
var aStopWords = new Array ("a", "the", "blah"...);
(code to make it run, full code can be found here: https://jsfiddle.net/j2kbpdjr/)
// sText is the body of text that the keywords are being extracted from.
// It's being separated into an array of words.
// remove stop words
for (var m = 0; m < aStopWords.length; m++) {
sText = sText.replace(' ' + aStopWords[m] + ' ', ' ');
}
to get the keywords from a body of text. It works quite well, however, the issue I'm having is that it only seems to iterate through and ignore one instance of the words in the array aStopWords.
So if I have the following body of text:
how are you today? Are you well?
And I put var aStopWords = new Array("are","well") then it seems it will ignore the first instance of are, but still show the second are as a keyword. Whereas it will completely remove / ignore well from the keywords.
If anyone can help ignore all instances of the words in aStopWords from the keywords, I'd greatly appreciate it.
You can easily do this like this.
First, it splits the text into keywords. Then, it goes through all the keywords. While going through, it checks if it is a stopword. If so, it will be ignored. If not, the occurrence number of this keyword in the result object will be increased.
Then, the keywords are in a JavaScript object in the following form:
{ "this": 1, "that": 2 }
Objects are not sortable in JavaScript, but Arrays are. So, a remapping to the following structure is necessary:
[
{ "keyword": "this", "counter": 1 },
{ "keyword": "that", "counter": 2 }
]
Then, the array can be sorted by using the counter attribute. With the slice() function, only the top X values can be extracted from the sorted list.
var stopwords = ["about", "all", "alone", "also", "am", "and", "as", "at", "because", "before", "beside", "besides", "between", "but", "by", "etc", "for", "i", "of", "on", "other", "others", "so", "than", "that", "though", "to", "too", "trough", "until"];
var text = document.getElementById("main").innerHTML;
var keywords = text.split(/[\s\.;:"]+/);
var keywordsAndCounter = {};
for(var i=0; i<keywords.length; i++) {
var keyword = keywords[i];
// keyword is not a stopword and not empty
if(stopwords.indexOf(keyword.toLowerCase()) === -1 && keyword !== "") {
if(!keywordsAndCounter[keyword]) {
keywordsAndCounter[keyword] = 0;
}
keywordsAndCounter[keyword]++;
}
}
// remap from { keyword: counter, keyword2: counter2, ... } to [{ "keyword": keyword, "counter": counter }, {...} ] to make it sortable
var result = [];
var nonStopKeywords = Object.keys(keywordsAndCounter);
for(var i=0; i<nonStopKeywords.length; i++) {
var keyword = nonStopKeywords[i];
result.push({ "keyword": keyword, "counter": keywordsAndCounter[keyword] });
}
// sort the values according to the number of the counter
result.sort(function(a, b) {
return b.counter - a.counter;
});
var topFive = result.slice(0, 5);
console.log(topFive);
<div id="main">This is a test to show that it is all about being between others. I am there until 8 pm event though it will be late. Because it is "cold" outside even though it is besides me.</div>
I am pushing Objects to a $localStorage array for persistence. I also check this array to see if an object is present before adding / removing an Object (if present it should splice if not present then it will push).
When I refresh my page the data returned from $localStorage doesn't seem to be the same as it was pre-refresh as my check function doesn't work, despite it looking EXACTLY the same on inspection.
Objects being pushed are structured like this:
{
"createdAt": "2015-04-24T10:21:21.649Z",
"difficulty": "Hard",
"exerciseDescription": "Lie on your back on a bench and take hold",
"exerciseID": "3101",
"exerciseName": "Bench Press",
"images": [8679, 8680, 8682],
"tags": ["Barbell", "Horizontal Flexion", "Extension", "Strength", "Chest", "Triceps", "Shoulder", "Elbow, Wrist & Hand"],
"updatedAt": "2015-09-09T20:14:59.681Z",
"words": ["bench", "press", "chest"],
"objectID": "ak6t7ukQdY",
"_highlightResult": {
"exerciseName": {
"value": "Bench Press",
"matchLevel": "none",
"matchedWords": []
}
}
}
Check if object if present (toggle add/remove)
$scope.addExerciseToProgramme = function(exercise) {
if (!$localStorage.Programme) {
$localStorage.Programme = [];
}
var index = $localStorage.Programme.indexOf(exercise);
if (index > -1) {
$localStorage.Programme.splice(index, 1);
} else {
$localStorage.Programme.push(exercise);
}
}
Function to watch/load $localStorage
$scope.$watch(function() {
return $localStorage.Programme
}, function(programme) {
$scope.programme = programme;
});
ng-class to check if exercise is in programme
<i class="exercise-add-indicator ion-ios-checkmark-outline" ng-class="{'orange': programme.indexOf(exercise) > -1}"></i>
Problem
There are two problems with this:
Following refresh, the ng-class doesn't conditionally add the class depending on the content of my $scope.programme
The addExerciseToProgramme function doesn't respect the indexOf check and pushes the exercise object to the array regardless!
Array.prototype.indexOf() uses strict equality: An expression comparing Objects is only true if the operands reference the same Object.
You shouldn't use this when working with localStorage.
When saving an object to localStorage it's turned into a string. When retrieved it's turned into an object again.
This will however be a new object, even if it looks exactly the same.
For example, this will yield false:
var object1 = { id: 1 };
var object2 = { id: 1 };
console.log(object1 === object2);
To get it working you can implement a custom function that retrieves the index based on the value of a property of your choosing. Note that it should be unique.
For example:
$scope.getExerciseIndex = function(exercise) {
var index = -1;
if (!$scope.programme) return index;
for (var i = 0; i < $scope.programme.length; i++) {
if ($scope.programme[i].exerciseID !== exercise.exerciseID) continue;
index = i;
break;
}
return index;
};
$scope.exerciseExists = function(exercise) {
var index = $scope.getExerciseIndex(exercise);
return index > -1;
};
$scope.addExerciseToProgramme = function(exercise) {
if (!$localStorage.Programme) {
$localStorage.Programme = [];
}
var index = $scope.getExerciseIndex(exercise);
if (index > -1) $localStorage.Programme.splice(index, 1);
else $localStorage.Programme.push(exercise);
};
HTML:
... ng-class="{'orange': exerciseExists(exercise) }" ...
Demo: http://plnkr.co/edit/R6TEisvQ7gkDcwBgw0D1?p=preview
I need to make a nested JSON out of two flat structured arrays which are kind of linked together, I am able to get the first level but not able to nest it in javascript.
Below is the problem statement.
I have 2 arrays:
data = ["HSP - FS", "FS-M", "Lo Co", "GO A", "CQM","whatever"];
type = ["Epic","Sub - Epic","Sub - Epic","Epic","Sub - Epic","Story"];
the type array and data array are linked together and with type and description on the same index like
type[0] which is data[0], so it will be "Epic: HSP-FS", but this does not end here, this Epic has child elements also which are Sub - Epics and their description is placed under the same array element, so type[1] which is "Sub-Epic" has its data in data[1] which is "FS-M" and so on till next "Epic" is found in the type array.
So JSON would be like
{0:{
Epic: "HSP - FS",
Sub-Epic:"FS-M",
Sub-Epic:"Lo Co"
},
1:{
Epic: "GO A",
Sub-Epic:"CQM",
Story:"whatever"
}}
Structure of array and JSON is fixed, and even array can have any number of Epics, Sub Epics or story but the order will be maintained as in all the child of Epics will be there until next occurrence of Epic happens.
Issue is I am not able to write loop to get the output..I have till now tried something like iterating and joining these two arrays
Thank you in advance for help.
You can't have repeated named properties in a JSON object, but you can use arrays instead:
var array = [];
for(var i=0; i < data.length; ++i)
{
var t = type[i];
var d = data[i];
if (t == "Epic")
{
array.push({ "Epic": d});
}
else
{
if(typeof(array[array.length-1][t]) == "undefined")
{
array[array.length-1][t] = [];
}
array[array.length-1][t].push(d)
}
}
This is the resulting JSON:
[{
"Epic": "HSP - FS",
"Sub - Epic": ["FS-M","Lo Co"]
},
{
"Epic": "GO A",
"Sub - Epic": ["CQM"],
"Story": ["whatever"]
}]