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!
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
Please can someone teach me the method to swap two different data on click
For example, we have a data
data() {
return {
data_one: [
{
name: "Simo",
age: "32"
}
],
data_two: [
{
name: "Lisa",
age: "25"
}
],
}
},
then I want to put this data on v-for, and I want to add two buttons to swap from data_one and data_two and display data_one as default.
<button #click="change_to_data_one">Data 1<button>
<button #click="change_to_data_two">Data 2<button>
<li v-for="item in data_one">{{item.name}} - {{item.age}}<li>
🙏🙏🙏🙏🙏
Try to use computed property:
new Vue({
el: "#demo",
data() {
return {
data_one: [{name: "Simo", age: "32"}],
data_two: [{name: "Lisa", age: "25"}],
selected: "data_one"
}
},
computed: {
showData(){
return this[this.selected]
}
},
methods: {
swap(val) {
this.selected = val
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo">
<button #click="swap('data_one')">Data 1</button>
<button #click="swap('data_two')">Data 2</button>
<ul>
<li v-for="(item, i) in showData" :key="i">{{item.name}} - {{item.age}}</li>
</ul>
</div>
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.
I have a problem with binding checkboxes using Vuex. On checkbox I use v-model with variable which has getter and setter to set or get value in store, the problem is that I get wrong data in store and I don't understand what cause the problem. Checkboxes bind to store property and this property must contain array of id's from checkboxes, but when I click checkbox more than one time it rewrite or remove store values. Can anyone help me to understand why does this happens? Link to jsFiddle.
The code
const store = new Vuex.Store({
state: {
checkboxes: {},
checked: {}
},
mutations: {
setCheckboxes(state, dataObj){
console.log(dataObj);
state.checkboxes = dataObj.data;
let firstElem = dataObj.data[Object.keys(dataObj.data)[0]];
state.checked[firstElem.parent_id] = [firstElem.id];
console.log(state.checked);
},
setTreeState(state, dataObj){
state.checked[dataObj.id] = dataObj.value;
console.log(state.checked);
}
}
});
Vue.component('checkboxTree', {
template: "#checkboxTree",
});
Vue.component('checkboxToggle', {
template: "#checkboxToggle",
data(){
return {
store
}
},
computed: {
value:{
get(){
return store.state.checked[this.checkbox.parent_id];
},
set(val){
store.commit({
type: 'setTreeState',
id: this.checkbox.parent_id,
value: val
});
},
},
},
props: ['checkbox']
});
const app = new Vue({
el: "#app",
store,
data: {
checkboxData: {
...
},
},
mounted(){
this.$store.commit({
type: 'setCheckboxes',
data: this.checkboxData
});
}
})
Template
<div id="app">
<checkbox-tree :checkboxData="checkboxData"></checkbox-tree>
</div>
<template id="checkboxTree">
<div>
<p>checkbox tree</p>
<form>
<ul>
<li v-for="checkbox in $store.state.checkboxes">
<checkbox-toggle :checkbox="checkbox"></checkbox-toggle>
</li>
</ul>
</form>
</div>
</template>
<template id="checkboxToggle">
<div>
<label>{{ checkbox.id }}</label>
<input type="checkbox"
:value="checkbox.id"
:id="'checkbox-' + checkbox.id"
:name="'checkbox-' + checkbox.id"
v-model="value"
>
</div>
</template>
Okay, assuming you want checked to contain ids of selected objects, I had to restructure your code significantly:
const removeFromArray = (array, value) => {
const newArray = [...array];
const index = newArray.indexOf(value);
if (index > -1) {
newArray.splice(index, 1);
return newArray;
}
return array;
}
const store = new Vuex.Store({
state: {
checkboxes: {},
checked: [],
},
mutations: {
addToChecked(state, id) {
state.checked.push(id);
},
removeFromChecked(state, id) {
const newArray = removeFromArray(state.checked, id);
state.checked = newArray;
},
setCheckboxes(state, data) {
state.checkboxes = data;
},
}
});
Vue.component('checkboxTree', {
template: "#checkboxTree",
computed: {
checkboxes() {
return this.$store.state.checkboxes;
},
},
});
Vue.component('checkboxToggle', {
template: "#checkboxToggle",
computed: {
value:{
get(){
return this.$store.state.checked.indexOf(this.checkbox.id) > -1;
},
set(val){
const mutation = val ? 'addToChecked' : 'removeFromChecked';
this.$store.commit(mutation, this.checkbox.id);
},
},
},
props: ['checkbox'],
});
const app = new Vue({
el: "#app",
store,
data: {
checkboxData: {
"5479": {
"id": 5479,
"title": "Место оказания услуг",
"type": "checkbox",
"dependencies": "",
"description": "",
"parent_id": 5478,
"npas": ""
},
"5480": {
"id": 5480,
"title": "Способы оказания услуг",
"type": "checkbox",
"dependencies": "",
"description": "",
"parent_id": 5478,
"npas": "50"
},
"5481": {
"id": 5481,
"title": "Объем и порядок содействия Заказчика в оказании услуг",
"type": "checkbox",
"dependencies": "",
"description": "",
"parent_id": 5478,
"npas": "54"
},
}
},
computed: {
stateRaw() {
return JSON.stringify(this.$store.state, null, 2);
},
},
mounted() {
this.$store.commit('setCheckboxes', this.checkboxData);
const firstElementKey = Object.keys(this.checkboxData)[0];
const firstElement = this.checkboxData[firstElementKey];
this.$store.commit('addToChecked', firstElement.id);
}
})
<script src="https://unpkg.com/vue"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/3.0.1/vuex.js"></script>
<div id="app">
<checkbox-tree :checkboxData="checkboxData"></checkbox-tree>
<pre v-text="stateRaw"></pre>
</div>
<template id="checkboxTree">
<div>
<p>checkbox tree</p>
<form>
<ul>
<li v-for="checkbox in checkboxes">
<checkbox-toggle :checkbox="checkbox"></checkbox-toggle>
</li>
</ul>
</form>
</div>
</template>
<template id="checkboxToggle">
<div>
<label>{{ checkbox.id }}</label>
<input
type="checkbox"
:value="checkbox.id"
:id="'checkbox-' + checkbox.id"
:name="'checkbox-' + checkbox.id"
v-model="value">
{{value}}
</div>
</template>
Using this code as an example, you can populate checked however you want to.
Also, a jsfiddle link for you: https://jsfiddle.net/oniondomes/ckj7mgny/
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