I'm populating an object from an O365 REST call, then filtering using multi-select dropdowns in a SPfx FluentUI Detailslist app. My code is appending the results from any of the values from each of the dropdowns versus narrowing down the results to meet all the filter conditions.
Sample Codepen: https://codepen.io/detailcode/pen/VwmdZoo
<!DOCTYPE html><html><body>
<select id="lstTitle" multiple size="4">
<option value="" selected>(Select Multi Values)</option>
<option value="Apple">Apple</option>
<option value="Pear">Pear</option>
<option value="Grape">Grape</option>
<option value="Banana">Banana</option>
<option value="Beet">Beet</option>
<option value="Grapefruit">Grapefruit</option>
<option value="Lemon">Lemon</option>
</select>
<select id="lstColor" multiple size="4">
<option value="" selected>(Select Multi Values)</option>
<option value="Red">Red</option>
<option value="Green">Green</option>
<option value="Yellow">Yellow</option>
</select>
<select id="lstTaste" multiple size="4">
<option value="" selected>(Select Multi Values)</option>
<option value="Sweet">Sweet</option>
<option value="Sour">Sour</option>
<option value="Bitter">Bitter</option>
</select>
<input id="butFilter" type="button" value="Filter" onclick="butFilter_Click()" />
<p>All Results:</p>
<div id="cssResults1" style="padding:3px 10px;border:1px solid #000;">No data</div>
<p>Single Select Result:</p>
<div id="cssResults2" style="padding:3px 10px;border:1px solid #000;">No data</div>
<p>Multi Select Result: (Selecting Apple, Pear, Grape, Red, Green, Sweet, Sour) <strong>Desired result being records 1, 2, 4, 5, 6</strong></p>
<div id="cssResults3" style="padding:3px 10px;border:1px solid #000;">No data</div>
<script>
let data = [
{ 'id': 1, 'title': 'Apple', 'color': 'Red', 'taste': 'Sweet', },
{ 'id': 2, 'title': 'Apple', 'color': 'Green', 'taste': 'Sour', },
{ 'id': 3, 'title': 'Apple', 'color': 'Yellow', 'taste': 'Sweet', },
{ 'id': 4, 'title': 'Pear', 'color': 'Green', 'taste': 'Sweet', },
{ 'id': 5, 'title': 'Grape', 'color': 'Red', 'taste': 'Sweet', },
{ 'id': 6, 'title': 'Grape', 'color': 'Green', 'taste': 'Sour', },
{ 'id': 7, 'title': 'Banana', 'color': 'Yellow', 'taste': 'Sweet', },
{ 'id': 8, 'title': 'Beet', 'color': 'Red', 'taste': 'Bitter', },
{ 'id': 9, 'title': 'Grapefruit', 'color': 'Yellow', 'taste': 'Bitter', },
{ 'id': 10, 'title': 'Lemon', 'color': 'Yellow', 'taste': 'Sour', },
]
//var result1 = data.filter(e => e.color == 'Yellow' && e.taste == 'Sweet');
console.log(data);
document.getElementById("cssResults1").innerHTML = "<p>" + JSON.stringify(data) + "<p>";
function butFilter_Click() {
var result2 = data.filter(e =>
e.title == (document.getElementById("lstTitle").value != "" ? document.getElementById("lstTitle").value : e.title) &&
e.color == (document.getElementById("lstColor").value != "" ? document.getElementById("lstColor").value : e.color) &&
e.taste == (document.getElementById("lstTaste").value != "" ? document.getElementById("lstTaste").value : e.taste));
document.getElementById("cssResults2").innerHTML = "<p>" + JSON.stringify(result2) + "<p>";
console.log(result2); // LOG SINGLE SELECT RESULT
const valTitle = Array.apply(null, document.getElementById("lstTitle").options).filter(option => option.selected).map(option => option.value);
const valColor = Array.apply(null, document.getElementById("lstColor").options).filter(option => option.selected).map(option => option.value);
const valTaste = Array.apply(null, document.getElementById("lstTaste").options).filter(option => option.selected).map(option => option.value);
console.log(valTitle); // LOG lstTitle MULTI VALUES
console.log(valColor); // LOG lstColor MULTI VALUES
console.log(valTaste); // LOG lstTaste MULTI VALUES
// INCLUSIVELY ADDS TO RESULT VERSUS MATCHING ALL KEY VALUES
let tempContat = [];
let temp;
for(var i = 0; i < valTitle.length; i++) {
temp = data.filter(e => e.title == (valTitle[i] != "" ? valTitle[i] : e.title))
tempContat = tempContat.concat(temp);
}
for(var i = 0; i < valColor.length; i++) {
temp = data.filter(e => e.color == (valColor[i] != "" ? valColor[i] : e.color))
tempContat = tempContat.concat(temp);
}
for(var i = 0; i < valTaste.length; i++) {
temp = data.filter(e => e.taste == (valTaste[i] != "" ? valTaste[i] : e.taste))
tempContat = tempContat.concat(temp);
}
var result3 = tempContat;
document.getElementById("cssResults3").innerHTML = "<p>" + JSON.stringify(result3) + "<p>";
console.log(result3); // LOG MULTI SELECT RESULT
}
</script>
</body></html>
Updated: How do I filter an object to be inclusive (opposite of results3) in the final results? When selecting the fruits (Apple, Pear, Grape), the colors (Red, Green), and the tastes (Sweet, Sour), the expected results should be IDs 1, 2, 4, 5, and 6. If each search criteria is not meet, it should be excluded like in a search engine filter.
The issue is that each for loop you have is independent of the predicate used to filter out objects. To fix this you need to bring the predicates all into the same scope so that things that should be omitted do not get added in due to the scope of the current predicate. For example:
for(var i = 0; i < valTitle.length; i++) {
// this predicate will remove all things relating to titles
temp = data.filter(e => e.title == (valTitle[i] != "" ? valTitle[i] : e.title))
tempContat = tempContat.concat(temp);
}
// these for loops are independent and all re-use the same 'data' so its possible for the previous loop's omitted objects to be filtered in again
// because the filter constraint in the next for loop doesn't include the previous ones constraints
for(var i = 0; i < valColor.length; i++) {
// this filter's predicate is unaware that 'e.title' of some type should be omitted, it only knows to filter out colors of some type `valColor[i]`
temp = data.filter(e => e.color == (valColor[i] != "" ? valColor[i] : e.color))
// here some titles that should be omitted are added in because it matched on the color from the predicate above
tempContat = tempContat.concat(temp);
}
for(var i = 0; i < valTaste.length; i++) {
temp = data.filter(e => e.taste == (valTaste[i] != "" ? valTaste[i] : e.taste))
tempContat = tempContat.concat(temp);
}
Because of this its also possible to add in duplicates since, the filter is based on the data
which never changes and is all the available things to be filtered.
To fix you need to combine the predicates, below I did that by using 1 filter and housing the predicates all together.
const result3 = data.filter((e) => {
// element must have at least one of these titles to be filtered in
if (!valTitle.some((title) => e.title == (title != "" ? title : e.title))) {
return false;
}
// and
// element must have at least one of these colors to be filtered in
if (!valColor.some((color) => e.color == (color != "" ? color : e.color))) {
return false;
}
// and
// element must have at least one of these tastes to be filtered in
if (!valTaste.some((taste) => e.taste == (taste != "" ? taste : e.taste))) {
return false;
}
return true;
});
You can do something similar to the solution above.