-1

First of all, my apologies if my question is too obvious but I my knowledge is limited and I don't get how to achieve what I am trying. I have a JSON file as source of the data (songs) and I am trying to filter that data based on several fields (level, name, artist, etc.).

Example of some data from the JSON:

[
  {"artist": "Black",
    "categories": "Arreglos",
    "date": 1639127185000,
    "level": "Fácil",
    "musicStyle": "Pop/Rock",
    "name": "Wonderful Life",
    "practice": "n/a",
    "preloadID": "Wonderful_Life",
    "subtitle": "Fácil",
  },
{"artist": "",
    "categories": "Arreglos",
    "date": 1587948049309,
    "image": "./images/arreglos/a_million_dreams.jpeg",
    "level": "Fácil",
    "musicStyle": "Film/TV",
    "name": "A million dreams",
    "preloadID": "AMillionDreams_Facil",
    "subtitle": "Fácil",
  },
{"artist": "",
    "categories": "Arreglos",
    "date": 1587948046688,
    "image": "./images/arreglos/a_million_dreams.jpeg",
    "level": "Principiante",
    "musicStyle": "Film/TV",
    "name": "A million dreams",
    "preloadID": "AMillionDreams_Principiante",
    "subtitle": "Principiante",
  },
{"artist": "Vanessa Carlton",
    "categories": "Arreglos",
    "date": 1602939064030,
    "level": "Fácil",
    "musicStyle": "Pop/Rock",
    "name": "A thousand miles",
    "preloadID": "AThousandMiles_Facil",
    "subtitle": "Fácil",
  },
{"artist": "Vanessa Carlton",
    "categories": "Arreglos",
    "date": 1602939064033,
    "level": "Muy fácil",
    "musicStyle": "Pop/Rock",
    "name": "A thousand miles",
    "preloadID": "AThousandMiles_MuyFacil",
    "subtitle": "Muy fácil",
    "tonality": ""
  },
]

And this is the script I have to try to filter the data.

let filteredItems = [];
let filterLevel=this.context.appActions.dataSlots['ds_LevelFilter'];
let filterStr=this.context.appActions.dataSlots['ds_SearchFilter'];
filterStr=filterStr.toLowerCase();
      
      items.forEach(item => {
        if (item["artist"].toLowerCase().includes(filterStr) || item["name"].toLowerCase().includes(filterStr) ) {
          filteredItems.push(item);
          }
      });
      
      items.forEach(item => {
        if (item["level"] == filterLevel) {
          filteredItems.push(item);
        }
      });
      
      items = filteredItems.sort((a, b) => {
              return new Date(b.date) - new Date(a.date);
            }).slice(0,this.context.appActions.dataSlots['ds_limitQuery']);

return items;

For filterStr, I have a text field where the user would write a search and if that is included in name or artist, it should return the resulted documents.

In filterLevel I have a picker with several values (Easy, Medium, etc. in Spanish) and it should be equal to the field "level" from the data.

I am not sure if the code shows what I am trying but if I use just the first if block (name and artist) it works perfectly. But if I add the second, it gives me an error of duplicated keys (it is a React project). I am guessing the script is not correct.

5
  • probably add another || item["level"] == filterLevel to the first forEach and remove the second Commented Dec 10, 2021 at 23:16
  • Thank you for your answer. I have tried to add that and for some reason it does not filter with "level" but it does with the other two fields. The snippet of code is:items.forEach(item => { if (item["artist"].toLowerCase().includes(filterStr) || item["name"].toLowerCase().includes(filterStr) || item["level"] == filterLevel) { filteredItems.push(item); } }); Commented Dec 10, 2021 at 23:34
  • please post sample data for filterStr, filterLevel, items and expected output Commented Dec 10, 2021 at 23:44
  • Thank you. I have edited the first post to include more info and details about filterStr and filterLevel. I hope it is better now. Commented Dec 11, 2021 at 0:34
  • JSON is a text format. You do not have JSON there, only an array of objects. Using the correct terminology aids in searching for answers. Commented Dec 11, 2021 at 4:31

1 Answer 1

0

Use an Object filter: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter

const songs = [
  {
    id: 1,
    name: 'Golden Hour',
    artist: 'Kacey Musgraves',
    level: "Fácil",
  },
  {
    id: 2,
    name: 'King Size Manger',
    artist: 'Josh Turner',
    level: "Fácil",
  },
  {
    id: 3,
    name: 'Legend',
    artist: 'Bob Marley',
    level: "Muy fácil",
  },
  {
    id: 4,
    name: 'Catch A Fire',
    artist: 'Bob Marley',
    level: "Muy fácil",
  },
  {
    id: 5,
    name: 'Fine Line',
    artist: 'Harry Styles',
    level: "Fácil",
  },
]

function filterSongs(filterStr = '', filterLevel = '') {

  return songs.filter(item => {
    const context = filterStr.toLowerCase()
    // Filter on level else ignore by always returning true.
    let result = filterLevel.length ? (item.level === filterLevel) : true
    
    // If result is false because level was set and did not match then skip filterStr check.
    // If result is true because level was ignored or matched then search if filterStr has value.
    if(result && filterStr.length) {
      result = item.artist.toLowerCase().includes(context) || item.name.toLowerCase().includes(context)
    }
    
    return result

  })

}

console.log('Search for Harry', filterSongs('Harry'))

console.log('Search for level Fácil', filterSongs('', 'Fácil'))

console.log('Search for Golden with level Fácil', filterSongs('Golden', 'Fácil'))

console.log('Search for Bob', filterSongs('Bob'))

How to implement the above code with your example:

let filterLevel = this.context.appActions.dataSlots['ds_LevelFilter'] ?? '';
let filterStr = this.context.appActions.dataSlots['ds_SearchFilter'] ?? '';
let filterLimit = this.context.appActions.dataSlots['ds_limitQuery'] ?? 15;

function filterSongs(filterStr = '', filterLevel = '') {

  return songs.filter(item => {
    const context = filterStr.toLowerCase()
    // Filter on level else ignore by always returning true.
    let result = filterLevel.length ? (item.level === filterLevel) : true
    
    // If result is false because level was set and did not match then skip filterStr check.
    // If result is true because level was ignored or matched then search if filterStr has value.
    if(result && filterStr.length) {
      result = item.artist.toLowerCase().includes(context) || item.name.toLowerCase().includes(context)
    }
    
    return result

  })

}

let filteredItems = filterSongs(filterStr, filterLevel)

return filteredItems.sort((a, b) => {
  return new Date(b.date) - new Date(a.date);
}).slice(0,filterLimit);
Sign up to request clarification or add additional context in comments.

4 Comments

Thank you so much for your answer. It seems to be the right path to take but as I am ashamed to say that my knowledge is so limited that I don't get to fit the function into my snippet. In my project, the variable "items" must contain at the end the array with the result but I am not capable to get it done mixing your suggestion with my scenario. How can I get the variable items to contain the result of the filtering if there is some values as input in filterStr or filterLevel?
@petoma Added implementation example to the end of my answer for you.
Thank you so much Marc! I just tried that changing a few things and it works wonderfully! I marked the answer as the working one and upvoted the answer. I am not very familiar with this forum so I am sorry if my question was not accurate. I keep learning in the way. Thanks again!
One more question if I may. If I want to add an additional filter like "musicStyle" to the function?

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.