I have created a demo website to sell products to customers. This website uses filters/search/sort etc to ease navigating through different products. The issue I have is related to filtering and search. I want to make my filters such that they work on the result of search. I have attempted this using checkboxes and the computed properties in Vue.
HTML
<div id="app">
<h5>Search</h5>
<input type="text" v-model="search" placeholder="Search title.."/><br><br>
<h5>Filter</h5>
<li v-for="filter in possibleFilters" :key="filter">
<label>
<input type="checkbox" #change="toggleFilter(filter)" :checked="filters.includes(filter)">
<span >{{filter}}</span>
</label>
</li>
<div class="block" v-for="(product, index) in filteredSearch">
<hr>
<h3>{{product.name}}</h3>
<p>Price: £{{product.price}}</p>
<p>Location: {{product.location}}</p>
<hr>
</div>
</div>
JavaScript
new Vue({
el: "#app",
data: {
filters: [],
search: "",
products: [{
name: "milk",
location: "London",
price: 100
},
{
name: "oranges",
location: "Birmingham",
price: 80
},
{
name: "apples",
location: "Edinburgh",
price: 90
},
{
name: "bananas",
location: "Paris",
price: 120
},
{
name: "bread",
location: "Paris",
price: 110
},
{
name: "water",
location: "Oslo",
price: 90
},
{
name: "soda",
location: "London",
price: 90
},
{
name: "tea",
location: "Oslo",
price: 120
},
{
name: "bakedbeans",
location: "Oslo",
price: 140
}
],
},
methods: {
toggleFilter(newFilter) {
this.filters = !this.filters.includes(newFilter) ?
[...this.filters, newFilter] :
this.filters.filter(f => f !== newFilter)
}
},
computed: {
possibleFilters() {
return [...new Set(this.filteredSearch.map(x => x.location))]
},
filteredSearch() {
return this.products.filter(p => {
var searchProduct = p.name.toLowerCase().includes(this.search.toLowerCase());
var filterProduct = this.filters.length == 0 || this.filters.includes(p.location);
return searchProduct && filterProduct
})
}
},
})
The problem is I cannot select the filter more than once. The filter is based on the location, my goal is to be able to apply the filter more than once. At the moment I can only select one filter at a time.
i.e if I search for "l" it returns milk and apples, the filters shows London and Edinburgh, I can only select either London or Edinburgh but not both. If I select London, it should only show me "Milk" while still showing me the option of 'Edinburgh' and when I select both it should show me both "Milk" and "Apples"
A fiddle showing the problem:
https://jsfiddle.net/Calv7/L1vnqh63/9/
Any help will be appreciated. Thanks.
Does this solve your problem?
possibleFilters() {
return [...new Set(this.products.map(x => x.location))]
},
Here a quick and dirty solution based on your fiddle. Just to give you an idea how to separate both filters.
Try the solution here https://jsfiddle.net/4839spkx/
possibleFilters() {
// we have some input, so show all location filters available in the filtered by input products
if (this.search) {
const filteredByInput = this.products.filter(p => p.name.includes(this.search.toLowerCase()))
return [...new Set(filteredByInput.map(p => p.location))]
}
return [...new Set(this.filteredSearch.map(x => x.location))]
},
Since your using a computed property to dynamically generate your possible filters from the filtered products locations this will update everytime you update your filtered products. I would recommend you to create a new Array and to populate this Array with your data.
HTML:
...
<li v-for="filter in possibleFiltersArr" :key="filter">
<label>
<input type="checkbox" #change="toggleFilter(filter)" :checked="filters.includes(filter)">
<span>{{filter}}</span>
</label>
</li>
...
JS
...
data: {
posibleFiltersArr:[],
...
created(){
this.possibleFiltersArr=[...new Set(this.filteredSearch.map(x => x.location))]
},
...
You can set this array in the created() method and you could update this array after inputing some text on the searchbox.
Related
I just got started with Vue.js and here is what I'm doing: I am rendering a list of products, and each product has a name, a gender and a size. I'd like users to be able to filter products by gender, by using an input to type the gender.
var vm = new Vue({
el: '#product_index',
data: {
gender: "",
products: [{name: "jean1", gender: "women", size: "S"}, {name: "jean2", gender: "men", size: "S"}]
},
methods:{
updateGender: function(event){
this.gender = $(event.target).val()
}
}
}
)
<div v-for="product in products" v-if="...">
<p>{{product.name}}<p>
</div>
<input v-on:change="updateGender">
I managed to get the gender updated, but I have an issue with the filtering part. When the page loads, I don't want any filtering. In the documentation, they advise to use v-if but it doesn't seem compatible with this configuration.
If I use v-if, I could do:
v-if="product.gender == gender"
But again, this doesn't work when the page load because gender is empty.
I couldn't find a workaround for this.
How should I approach this issue ?
Use computed properties - something like this (Example bellow filter items by type)
const app = new Vue({
el: '#app',
data: {
search: '',
items: [
{name: 'Stackoverflow', type: 'development'},
{name: 'Game of Thrones', type: 'serie'},
{name: 'Jon Snow', type: 'actor'}
]
},
computed: {
filteredItems() {
return this.items.filter(item => {
return item.type.toLowerCase().indexOf(this.search.toLowerCase()) > -1
})
}
}
})
Template:
<div id="app">
<div v-for="item in filteredItems" >
<p>{{item.name}}</p>
</div>
<input type="text" v-model="search">
</div>
Demo: http://jsbin.com/dezokiwowu/edit?html,js,console,output
You can try v-if="!gender || product.gender == gender"
Just modified #Nora's answer.
You need to change in the template as:
<div id="product_index">
<div v-for="product in products" v-if="!gender || product.gender===gender">
<p>{{product.name}}<p>
</div>
<input v-on:change="updateGender">
</div>
and in JS file as:
var vm = new Vue({
el: '#product_index',
data: {
gender: "",
products: [{name: "jean1", gender: "women", size: "S"}, {name: "jean2", gender: "men", size: "S"}]
},
methods:{
updateGender: function(event){
this.gender = event.target.value
}
}
}
);
Working Demo: https://jsbin.com/qocuraquki/edit?html,js,console,output
computed: {
filteredItems() {
return this.allStartupData.filter(item => {
let byName =
item.name.toLowerCase().indexOf(this.search.toLowerCase()) > -1;
let byDescription =
item.description.toLowerCase().indexOf(this.search.toLowerCase()) >
-1;
if (byName === true) {
return byName;
} else if (byDescription === true) {
return byDescription;
}
});
}
}
and then u can iterate through filteredItems like e.g
<v-flex v-for="(obj,index) in filteredItems" :key="index" xs12 md4>
computed: {
filteredItems() {
return myObject.filter((val) => {
return val.some((val) => val.toString().toLowerCase().includes(this.searchString))
})
}}
Iterate over the Object as already described above
My component state has an array named concessions with 35 objects, here's the structure of one of those objects:
{
address:"Some street"
brands: [{
id: 1,
name: 'fiat'
}]
city:"Paris"
contact_name:""
email:""
id:1
latitude:"11.11111"
longitude:"22.22222"
name:"AGORA Cars"
opening_hours:"something"
phone:"969396973"
phone2:""
zipcode:"19100"
}
Now, I have a list rendered with all car brands and a checkbox for each one like this:
<div class="brands-filter col-10">
<span v-for="brand in brands" :key="brand.key" class="brand-card">
<div>
<input
type="checkbox"
:value="brand.name"
v-model="search_filters"
#click="filterConcessions()"
/>
<label class="form-check-label">{{brand.name}}</label>
</div>
</span>
</div>
Basically, for each clicked checkbox, I'm adding the brand to searched_filters and after that I want to filter the concessions array based on those filters.
In that click method, #click="filterConcessions()", I'm doing this:
filterConcessions: function () {
let concessions = this.concessions;
let search_filters = this.search_filters;
let filteredConcessions = [];
filteredConcessions = concessions.filter((concession) =>
concession.brands.some((brand) => search_filters.includes(brand.name))
);
this.concessions = filteredConcessions;
}
But, no matter what, it gives me an empty array.
Any advice?
It's because you need to use the #change event instead of #click.
Otherwise, search_filters isn't populated before filterConcessions is run:
new Vue({
el: "#app",
data: {
search_filters: [],
concessions: [{
address: "Some street",
brands: [{
id: 1,
name: 'fiat'
}],
city: "Paris",
contact_name: "",
email: "",
id: 1,
latitude: "11.11111",
longitude: "22.22222",
name: "AGORA Cars",
opening_hours: "something",
phone: "969396973",
phone2: "",
zipcode: "19100"
}]
},
methods: {
filterConcessions: function() {
let concessions = this.concessions;
let search_filters = this.search_filters;
let filteredConcessions = concessions.filter((concession) =>
concession.brands.some((brand) => search_filters.includes(brand.name))
);
console.log(filteredConcessions)
this.concessions = filteredConcessions;
}
}
});
Vue.config.productionTip = false;
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div class="brands-filter col-10" v-if="concessions[0]">
<span v-for="brand in concessions[0].brands" :key="brand.key" class="brand-card">
<div>
<input type="checkbox" :value="brand.name" v-model="search_filters" #change="filterConcessions()" />
<label class="form-check-label">{{brand.name}}</label>
</div>
</span>
</div>
</div>
After some search i figure how to solve this.
I've created a computed method:
computed: {
filteredConcessions() {
if (!this.search_filters.length) {
return this.concessions;
} else {
return this.concessions.filter((concession) =>
concession.brands.some((brand) =>
this.search_filters.includes(brand.name)
)
);
}
},
}
and at the for loop i iterate throw the "filteredConcessions":
<li v-for="concession in filteredConcessions" :key="concession.id" class="p-2">
And that solved my case!
Apologies if this is answered elsewhere. Been googling and searching on here without any luck. Closest answer I could find is here but it requires quite a bit of rework with the setup I've got.
I'm trying to filter an array based on multiple inputs of a user. My demo assumes two inputs but the actual app should approx be five. JSFiddle
HTML
<div id="app">
<h2>Continent</h2>
<div v-for="filter in filters.continent">
<input type="checkbox" :value="filter" v-model="search.continent"> {{filter}}
</div>
<h2>Budget</h2>
<div v-for="filter in filters.budget">
<input type="checkbox" :value="filter" v-model="search.budget"> {{filter}}
</div><br/>
<p>You would like to live in {{search.continent}} and your budget is {{search.budget}}</p>
<div v-for="country in filteredCountries">
{{country.name}}
</div>
</div>
JS
new Vue({
el: "#app",
data() {
return {
search: {
continent: [],
budget: []
},
filters: {
continent: ['Europe', 'Asia', 'North America'],
budget: ['Low', 'Medium', 'High'],
},
countries: [
{
name: "Sweden",
continent: "Europe",
budget: ["Medium", "High"],
},
{
name: "Hong Kong",
continent: "Asia",
budget: ["Medium", "Low"],
},
{
name: "Thailand",
continent: "Asia",
budget: ["Low"],
},
{
name: "Canada",
continent: "North America",
budget: ["Medium", "High"],
},
{
name: "US",
continent: "North America",
budget: ["Medium", "Low"],
}
]
}
},
computed: {
filteredCountries: function(){
return this.countries.filter((country) =>
this.search.budget.some(el => country.budget.includes(el)),
)
},
}
})
In my actual app I'm using different components for the results and filters with an event bus that sends a payload with search data that feeds into the filtered computed property.
I hope someone's able to point me in the right direction with an approach that hopefully doesn't require more complexity as additional (similar array based) filter options are added.
Still trying to get a grasp on Javascript so apologies for the newb question!
Edit: Baboo_'s answer is really close to what I want but it seems I may have overlooked two things now that I've tried his fiddle. Firstly, the filters should be able to accept an array of options. I've updated my Fiddle to show this.
The intended effect is that the filters are constantly updating the results like a sidebar filter. It's purely optional. I understand that my Fiddle doesn't assume so because everything's hidden from the get-go but my intention is to include a hidden filter that has everything checked in. Every other input is to simply just refine the results in real time.
You can filter the countries successively by budget and continent like this:
computed: {
filteredCountries: function(){
return this.countries
.filter(country => this.search.budget.includes(country.budget))
.filter(country => this.search.continent.includes(country.continent));
},
}
Here is the fiddle.
I just got started with Vue.js and here is what I'm doing: I am rendering a list of products, and each product has a name, a gender and a size. I'd like users to be able to filter products by gender, by using an input to type the gender.
var vm = new Vue({
el: '#product_index',
data: {
gender: "",
products: [{name: "jean1", gender: "women", size: "S"}, {name: "jean2", gender: "men", size: "S"}]
},
methods:{
updateGender: function(event){
this.gender = $(event.target).val()
}
}
}
)
<div v-for="product in products" v-if="...">
<p>{{product.name}}<p>
</div>
<input v-on:change="updateGender">
I managed to get the gender updated, but I have an issue with the filtering part. When the page loads, I don't want any filtering. In the documentation, they advise to use v-if but it doesn't seem compatible with this configuration.
If I use v-if, I could do:
v-if="product.gender == gender"
But again, this doesn't work when the page load because gender is empty.
I couldn't find a workaround for this.
How should I approach this issue ?
Use computed properties - something like this (Example bellow filter items by type)
const app = new Vue({
el: '#app',
data: {
search: '',
items: [
{name: 'Stackoverflow', type: 'development'},
{name: 'Game of Thrones', type: 'serie'},
{name: 'Jon Snow', type: 'actor'}
]
},
computed: {
filteredItems() {
return this.items.filter(item => {
return item.type.toLowerCase().indexOf(this.search.toLowerCase()) > -1
})
}
}
})
Template:
<div id="app">
<div v-for="item in filteredItems" >
<p>{{item.name}}</p>
</div>
<input type="text" v-model="search">
</div>
Demo: http://jsbin.com/dezokiwowu/edit?html,js,console,output
You can try v-if="!gender || product.gender == gender"
Just modified #Nora's answer.
You need to change in the template as:
<div id="product_index">
<div v-for="product in products" v-if="!gender || product.gender===gender">
<p>{{product.name}}<p>
</div>
<input v-on:change="updateGender">
</div>
and in JS file as:
var vm = new Vue({
el: '#product_index',
data: {
gender: "",
products: [{name: "jean1", gender: "women", size: "S"}, {name: "jean2", gender: "men", size: "S"}]
},
methods:{
updateGender: function(event){
this.gender = event.target.value
}
}
}
);
Working Demo: https://jsbin.com/qocuraquki/edit?html,js,console,output
computed: {
filteredItems() {
return this.allStartupData.filter(item => {
let byName =
item.name.toLowerCase().indexOf(this.search.toLowerCase()) > -1;
let byDescription =
item.description.toLowerCase().indexOf(this.search.toLowerCase()) >
-1;
if (byName === true) {
return byName;
} else if (byDescription === true) {
return byDescription;
}
});
}
}
and then u can iterate through filteredItems like e.g
<v-flex v-for="(obj,index) in filteredItems" :key="index" xs12 md4>
computed: {
filteredItems() {
return myObject.filter((val) => {
return val.some((val) => val.toString().toLowerCase().includes(this.searchString))
})
}}
Iterate over the Object as already described above
First of all i am very new to React JS. So that i am writing this question. I am trying this for three days.
What I have to do, make a list of category, like-
Category1
->Sub-Category1
->Sub-Category2
Categroy2
Category3
.
.
.
CategoryN
And I have this json data to make the listing
[
{
Id: 1,
Name: "Category1",
ParentId: 0,
},
{
Id: 5,
Name: "Sub-Category1",
ParentId: 1,
},
{
Id: 23,
Name: "Sub-Category2",
ParentId: 1,
},
{
Id: 50,
Name: "Category2",
ParentId: 0,
},
{
Id: 54,
Name: "Category3",
ParentId: 0,
},
];
I have tried many open source examples, but their json data format is not like mine. so that that are not useful for me. I have build something but that is not like my expected result. Here is my jsfiddle link what i have done.
https://jsfiddle.net/mrahman_cse/6wwan1fn/
Note: Every subcategory will goes under a category depend on "ParentId",If any one have "ParentId":0 then, it is actually a category, not subcategory. please see the JSON
Thanks in advance.
You can use this code jsfiddle
This example allows to add new nested categories, and do nested searching.
code with comments:
var SearchExample = React.createClass({
getInitialState: function() {
return {
searchString: ''
};
},
handleChange: function(e) {
this.setState({
searchString: e.target.value.trim().toLowerCase()
});
},
isMatch(e,searchString){
return e.Name.toLowerCase().match(searchString)
},
nestingSerch(e,searchString){
//recursive searching nesting
return this.isMatch(e,searchString) || (e.subcats.length && e.subcats.some(e=>this.nestingSerch(e,searchString)));
},
renderCat(cat){
//recursive rendering
return (
<li key={cat.Id}> {cat.Name}
{(cat.subcats && cat.subcats.length) ? <ul>{cat.subcats.map(this.renderCat)}</ul>:""}
</li>);
},
render() {
let {items} = this.props;
let {searchString} = this.state;
//filtering cattegories
if (searchString.length) {
items = items.filter(e=>this.nestingSerch(e,searchString))
console.log(items);
};
//nesting, adding to cattegories their subcatigories
items.forEach(e=>e.subcats=items.filter(el=>el.ParentId==e.Id));
//filter root categories
items=items.filter(e=>e.ParentId==0);
//filter root categories
return (
<div>
<input onChange={this.handleChange} placeholder="Type here" type="text" value={this.state.searchString}/>
<ul>{items.map(this.renderCat)}</ul>
</div>
);
}
});