Input field loos focus in Vue when has no results - javascript

I have Vue application. And inside, I have some input field. If this field has any results, the buttons forward and backward are visible, else not.
My problem is, that when I type inside input field, when I type something that has no results, input loose focus. (see snippet)
Hot to solve this?
new Vue({
el: '#app',
data: {
input: "",
items: [{
'id': 123,
'name': 'item1'
},
{
'id': 124,
'name': 'item2'
},
{
'id': 128,
'name': 'item3'
},
{
'id': 237,
'name': 'item4'
}
]
},
computed: {
search_list_of_workorders: function() {
var self = this;
var search_string = this.input.toLowerCase();
// apply filter
var array = this.search_filter_array(this.items, search_string);
return array.slice(0, 10).map(a => a.id);
},
number_of_search_results: function() {
return this.search_list_of_workorders.length
},
display_results_buttons: function() {
return this.number_of_search_results > 0
},
},
methods: {
search_filter_array: function(array, search_string) {
return array.filter(function(el) {
var id_filter = el.id.toString().includes(search_string);
return id_filter;
});
},
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.2.6/vue.min.js"></script>
<div id="app">
<button type="button" v-if="display_results_buttons">
Back
</button>
<div v-if="display_results_buttons">({{ number_of_search_results }})</div>
<input placeholder="Search" type="text" list="searchDropDown" id="searchInput" name="selectEventInput" v-model="input" />
<datalist id="searchDropDown">
<option v-for="(item, index) in search_list_of_workorders" :value="item" :key="`optionEvents_${index}`" >
</option>
</datalist>
<button type="button" v-if="display_results_buttons">
Forward
</button>
</div>

Use v-show instead of v-if. This will fix your issue perfectly!
The main difference:
v-if: Only renders the element to the DOM if the expression passes.
v-show: Renders all elements to the DOM and then uses the CSS display property to hide elements if the expression fails.
Use cases:
v-show: expensive initial load, cheap toggling,
v-if: cheap initial load, expensive toggling.
In your case, toggling is mandatory and it can be required many times, so v-show is a better solution. Also, it doesn't require re-rendering and will fix the focus-losing issue as well.
new Vue({
el: '#app',
data: {
input: "",
items: [{
'id': 123,
'name': 'item1'
},
{
'id': 124,
'name': 'item2'
},
{
'id': 128,
'name': 'item3'
},
{
'id': 237,
'name': 'item4'
}
]
},
computed: {
search_list_of_workorders: function() {
var self = this;
var search_string = this.input.toLowerCase();
// apply filter
var array = this.search_filter_array(this.items, search_string);
return array.slice(0, 10).map(a => a.id);
},
number_of_search_results: function() {
return this.search_list_of_workorders.length
},
display_results_buttons: function() {
return this.number_of_search_results > 0
},
},
methods: {
search_filter_array: function(array, search_string) {
return array.filter(function(el) {
var id_filter = el.id.toString().includes(search_string);
return id_filter;
});
},
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.2.6/vue.min.js"></script>
<div id="app">
<button type="button" v-show="display_results_buttons">
Back
</button>
<div v-show="display_results_buttons">({{ number_of_search_results }})</div>
<input placeholder="Search" type="text" list="searchDropDown" id="searchInput" name="selectEventInput" v-model="input" />
<datalist id="searchDropDown">
<option v-for="(item, index) in search_list_of_workorders" :value="item" :key="`optionEvents_${index}`" >
</option>
</datalist>
<button type="button" v-show="display_results_buttons">
Forward
</button>
</div>

Related

Search function not searching for list of movies [duplicate]

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

How do I get values from multiple vue select instances?

I have 2 arrays of objects like below. I need to create a select for every questions element, and I need to have the options connected with the selections by the id. In this case I need to have 2 selects, the first one will have 1000, 5000 and 10000 as options meanwhile the second select will have yes and no as options
const questions = [{
'id': 1,
'question': 'KM'
},
{
'id': 2,
'question': 'Works'
}
]
const selections = [{
'question_id': 1,
'value': 1000
},
{
'question_id': 1,
'value': 5000
},
{
'question_id': 1,
'value': 10000
},
{
'question_id': 2,
'value': 'yes'
},
{
'question_id': 2,
'value': 'no'
}
]
I made it like this in vue but the issue is that I can't save the data in the v-model as I'm returning the values from cars() and not a variable specified in data()
<div class="form-group" v-for="item in questions" v-bind:key="item.question">
<h5 v-if="showQuestion(item.id)">{{ item.question }}</h5>
<div class="tour-options-select" v-if="showQuestion(item.id)">
<select id="select-suggestions" name="tour-options-dropdown" class="tour-options-dropdown" v-model="questions.value">
<option v-for="item1 in cars(item.id)" v-bind:key="item1.id" :value="item1.display_name">{{ item1.display_name }}</option>
</select>
</div>
</div>
Ultimately, I just need to know, how do I get the value when I have a structure like the one I defined above?
If you have an array in data() to store your selected options, you can use v-model to dynamically bind with an element in that array if you give it an index:
new Vue({
el: '#app',
data: {
questions: [{
'id': 1,
'question': 'KM'
},
{
'id': 2,
'question': 'Works'
}
],
selections: [{
'question_id': 1,
'value': 1000
},
{
'question_id': 1,
'value': 5000
},
{
'question_id': 1,
'value': 10000
},
{
'question_id': 2,
'value': 'yes'
},
{
'question_id': 2,
'value': 'no'
}
],
selected: [],
},
methods: {
cars: function(id) {
return this.selections.reduce((arr, currSel) => {
if (currSel.question_id == id)
arr.push(currSel);
return arr;
}, []);
},
}
});
<script src="https://unpkg.com/vue#2.6.12/dist/vue.min.js"></script>
<div id="app">
<div v-for="(question, index) in questions" :name="question.question" :key="question.id">
<select v-model="selected[index]">
<option v-for="option in cars(question.id)" :key="option.question_id" :value="option.value">
{{ option.value }}
</option>
</select>
</div>
<p>Selected:</p>
<pre>{{ $data.selected }}</pre>
</div>
Another approach would be to use events to handle the changes, calling a custom function each time the user makes a selection, eg using #change:
new Vue({
el: '#app',
data: {
questions: [{
'id': 1,
'question': 'KM'
},
{
'id': 2,
'question': 'Works'
}
],
selections: [{
'question_id': 1,
'value': 1000
},
{
'question_id': 1,
'value': 5000
},
{
'question_id': 1,
'value': 10000
},
{
'question_id': 2,
'value': 'yes'
},
{
'question_id': 2,
'value': 'no'
}
],
},
methods: {
cars: function(id) {
return this.selections.reduce((arr, currSel) => {
if (currSel.question_id == id)
arr.push(currSel);
return arr;
}, []);
},
}
});
<script src="https://unpkg.com/vue#2.6.12/dist/vue.min.js"></script>
<div id="app">
<div v-for="(question, index) in questions" :name="question.question" :key="question.id">
<select #change="console.log('Option', $event.target.value, 'chosen for Q', question.id)">
<option selected disabled>Select...</option>
<option v-for="option in cars(question.id)" :key="option.question_id" :value="option.value">
{{ option.value }}
</option>
</select>
</div>
</div>
This way will give you more freedom to store or process the data as you wish, but you'll have to do it manually.

Vue filter state array

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!

Vue.js filter a list

I am new to Vue.js. I'm trying to display a filtered list based on the value of another object. The select sets the value of the object called activeSet. That value is the key in the sets object. If that keys value is greater than 0 I want to display it in the list and sort by it. Here is my code:
JS
var vm = new Vue({
el: '#app',
data: {
activeSet: 'all',
computed: {
activeContents: songs.filter(function(song) {
return (song[activeSet] > 0);
})
},
songs: {
a: {
title: 'Hound Dog',
all: 0,
set1: 2
},
b: {
title: 'Long Gone',
all: 1,
set1: 0
},
b: {
title: 'Novermber Rain',
all: 2,
set1: 3
}
},
sets: {
all: {
name: 'All Songs'
},
set1: {
name: 'Acoustic'
}
}
}
})
HTML
<div id="app">
<select v-model="activeSet">
<option v-for="(set,key) in sets" :value="key">{{set.name}}</option>
</select>
<ul id="menu">
<li v-for="(song,key) in activeContents" :id="key" :is-active="song.show" :key="key" #click="activeSong=key">{{ song.title }}</li>
</ul>
</div>
Fiddle
Here is the fiddle
Sidenote: If the value is above 0 it needs to be included and then sorted by that value. I haven't even dived into sorting yet so bonus points if you can sort by the value of those that are greater than 0.
Computed properties are not defined in data, they have their own section in the Vue definition. Additionally, you should use this to reference data properties inside the computed.
If I understand the question correctly, this should work for sorting and filtering.
console.clear()
var vm = new Vue({
el: '#app',
data: {
activeSet: 'all',
songs: {
a: {
title: 'Hound Dog',
all: 0,
set1: 2
},
b: {
title: 'Long Gone',
all: 3,
set1: 0
},
c: {
title: 'Novermber Rain',
all: 2,
set1: 3
}
},
sets: {
all: {
name: 'All Songs'
},
set1: {
name: 'Acoustic'
}
}
},
computed: {
activeContents(){
// setup
let set = this.activeSet
let filter = k => this.songs[k][set] > 0
let sorter = (a,b) => this.songs[a][set] - this.songs[b][set]
// filter and sort
let selectedKeys = Object.keys(this.songs)
.filter(filter)
.sort(sorter)
// build new object
return selectedKeys.reduce((acc, k) => {
acc[k] = this.songs[k]
return acc
}, {})
}
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.js"></script>
<div id="app">
<select v-model="activeSet">
<option v-for="(set,key) in sets" :value="key">{{set.name}}</option>
</select>
<ul id="menu">
<li v-for="(song,key) in activeContents" :id="key" :is-active="song.show" :key="key" #click="activeSong=key">{{ song.title }}</li>
</ul>
</div>

Filter list with Vue.js

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

Categories