Component Autocomplete VueJS - javascript

I want to create an autocomplete component, so I have the following code.
<Input v-model="form.autocomplete.input" #on-keyup="autocomplete" />
<ul>
<li #click="selected(item, $event)" v-for="item in form.autocomplete.res">
{{item.title}}
</li>
</ul>
autocomplete(e){
const event = e.path[2].childNodes[4]
if(this.form.autocomplete.input.length > 2){
this.Base.get('http://localhost:3080/post/search/post', {
params: {
q: this.form.autocomplete.input
}
})
.then(res => {
this.form.autocomplete.res = res.data
if(this.form.autocomplete.res.length > 0)
event.style.display = 'block'
})
}
},
selected(item, e){
this.form.autocomplete.item = item
console.log(e)
}
however, how would I do to have the return after selecting my item in the main file?
Ex:
Home.vue
<Autocomplete :url="www.url.com/test" />
When selecting the item I want from my autocomplete, how do I get the return from it and store it in that file, as if I were using v-model?

As Vue Guide said:
Although a bit magical, v-model is essentially syntax sugar for
updating data on user input events, plus special care for some edge
cases.
Then at Vue Using v-model in Components,
the <input> inside the component must:
Bind the value attribute to a value prop
On input, emit its own custom input event with the new value
Then follow above guide, one simple demo will be like below:
Vue.config.productionTip = false
Vue.component('child', {
template: `<div>
<input :value="value" #input="autocomplete($event)"/>
<ul v-show="dict && dict.length > 0">
<li #click="selected(item)" v-for="item in dict">
{{item.title}}
</li>
</ul>
</div>`,
props: ['value'],
data() {
return {
dict: [],
timeoutControl: null
}
},
methods: {
autocomplete(e){
/*
const event = e.path[2].childNodes[4]
if(this.form.autocomplete.input.length > 2){
this.Base.get('http://localhost:3080/post/search/post', {
params: {
q: this.form.autocomplete.input
}
})
.then(res => {
this.form.autocomplete.res = res.data
if(this.form.autocomplete.res.length > 0)
event.style.display = 'block'
})
}*/
clearTimeout(this.timeoutControl)
this.timeoutControl = setTimeout(()=>{
this.dict = [{title:'abc'}, {title:'cde'}]
}, 1000)
this.$emit('input', e.target.value)
},
selected(item){
this.selectedValue = item.title
this.$emit('input', item.title)
}
}
})
new Vue({
el: '#app',
data() {
return {
testArray: ['', '']
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<child v-model="testArray[0]"></child>
<child v-model="testArray[1]"></child>
<div>Output: {{testArray}}</div>
</div>

Related

Searching for numbers in an array - js

The search is working properly. The main problem occurs when I want to delete it but the previous numbers won't be back. In this code, I'm applying the changes directly to the main variable but I shall not. What is the way?
new Vue({
el: '#app',
data() {
return {
list: [],
search: '',
}
},
mounted() {
for(let i = 1300; i <= 1400; i++) {
const _n = i.toString()
this.list.push(_n)
}
},
methods: {
handleSearch() {
this.list = this.list.filter(i => i.includes(this.search));
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<input #input="handleSearch()" v-model="search" placeholder="search number" />
<ul>
<li v-for="(item, i) in list" :key="i">{{item}}</li>
</ul>
</div>
The issue happens because you nested filter the same list, but you need to have a list with full items and another list with filtered items, and iterate on the filtered items, not the main list.
I suggest you use computed property with the filtered list, instead of firing input event because you already use v-model to control the input
new Vue({
el: '#app',
data() {
return {
list: [],
search: '',
}
},
mounted() {
for(let i = 1300; i <= 1400; i++) {
const _n = i.toString()
this.list.push(_n)
}
},
computed: {
filteredList() {
return this.list.filter(i => i.includes(this.search));
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<input v-model="search" placeholder="search number" />
<ul>
<li v-for="(item, i) in filteredList" :key="i">{{item}}</li>
</ul>
</div>
YOu have list where you filter the items and overwrite list with the new filtered result.
When you move a char, you apply same same on list, but those items were already removed right? No way to get them back.
Use a second list, originalList that you will use to filter, but do not overwrite so you can always refer to the original values
Vue.config.devtools = false;
new Vue({
el: '#app',
data() {
return {
list: [],
originalList: [],
search: '',
}
},
mounted() {
for(let i = 1300; i <= 1400; i++) {
this.originalList.push(i.toString())
}
this.list = [ ...this.originalList ];
},
methods: {
handleSearch() {
this.list = this.originalList.filter(i => i.includes(this.search));
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<input #input="handleSearch()" v-model="search" placeholder="search number" />
<ul>
<li v-for="(item, i) in list" :key="i">{{item}}</li>
</ul>
</div>

Vue - passing data via Event.$emit

I have a question about passing data through Event.$emit. I have a parent that has a click event (appSelected) like this
<template>
<v-app>
<v-container>
<v-select :items="results" item-text="name" item-value="id" v-model="selectedOption" #change="appSelected"
:results="results"
label="Choose Application"
dense
></v-select>
</v-container>
</v-app>
</template>
In that component I have a method that emits the data. This is the id - this.selectedOption
appSelected() {
//alert("This is the id" + this.selectedOption);
Event.$emit('selected', this.selectedOption);
In my child component I would like to pass and use the value but can't seem to get it to work. I have a v-if on the top level div that if it sees a true it will show all below. That works great, but I need the id that's being passed.
This works for showing the div, but need to pass and use the id also. How can I pass the and use the id?
Event.$on('selected', (data) => this.appselected = true);
<template>
<div v-if="appselected">
Choose the Client Source
<input type="text" placeholder="Source client" v-model="query"
v-on:keyup="autoComplete"
v-on-clickaway="away"
#keydown.esc="clearText" class="form-control">
<span class="instructiontext"> Search for id, name or coid</span>
<div class="panel-footer" v-if="results.length">
<ul class="list-group">
<li class="list-group-item" v-for="result in results">
{{ result.name + "-" + result.oid }}
</li>
</ul>
</div>
</div>
</template>
<script>
import axios from 'axios'
import { directive as onClickaway } from 'vue-clickaway';
export default{
directives: {
onClickaway: onClickaway,
},
data(){
return {
add_id: '',
onSelecton: '',
input_disabled: false,
selected: '',
query: '',
results: [],
appselected: false
}
},
methods: {
getClient(name) {
this.query = name;
this.results = [];
},
clearText(){
this.query = '';
},
away: function() {
// Added for future use
},
autoComplete(){
this.results = [];
if(this.query.length > 2){
axios.get('/getclientdata',{params: {query: this.query}}).then(response => {
this.results = response.data;
});
}
}
},
created() {
Event.$on('selected', (data) => this.appselected = true);
console.log(this.appselected);
}
}
</script>
Thanks for your help in advance!
Change your listener to do something with the emitted value. I see the parent already has a selected variable that you maybe intend to use for this purpose:
Event.$on('selected', (selectedOption) => {
this.selected = selectedOption;
this.appselected = true;
});
The selectedOption is the value emitted from the child.

Vue.js shared input data across components

I have 4 components that display different data. In each component I have a navigation bar that contains an input, which I use to filter the data in the component, like this:
computed: {
filteredItems() {
if (this.search !== '') {
return this.allManufacturers.filter(item => {
return item.id.toUpperCase().startsWith(this.search.toUpperCase()) === true
})
}
return this.cars.filter(item => {
return item.id.toUpperCase().startsWith(this.search.toUpperCase()) === true
})
},
},
The computed property is different in the 4 components.
I want to move the navigation bar to a component and filter using the input in the navigation bar rather than duplicating the code in each component
Create a navigation bar component that will $emit a search event containing the searched value. It only has a template:
<template>
<div>
<input #input="$emit('search', $event)" />
</div>
</template>
You can use that in any of your other components like this:
<template>
<div>
<navigation #search="newSearch($event.target.value)"></navigation>
{{ filteredItems }}
</div>
</template>
data() {
return {
search: ''
}
},
computed: {
filteredItems () {
if (this.search !== '') {
return this.allManufacturers.filter(item => {
return item.id.toUpperCase().startsWith(this.search.toUpperCase()) === true
})
}
return this.cars.filter(item => {
return item.id.toUpperCase().startsWith(this.search.toUpperCase()) === true
})
},
},
methods: {
newSearch(text) {
this.search = text;
}
}

Vue.js computed property loses its reactivity when passed through an event

I have a Modal component in my main app that gets passed content via an event whenever a modal has to be shown. Modal content is always a list with an action associated with each item, like "select" or "remove":
Vue.component('modal', {
data() {
return {
shown: false,
items: [],
callback: ()=>{}
}
},
mounted() {
EventBus.$on('showModal', this.show);
},
template: `<ul v-if="shown">
<li v-for="item in items">
{{ item }} <button #click="callback(item)">Remove</button>
</li>
</ul>`,
methods: {
show(items, callback) {
this.shown = true;
this.items = items;
this.callback = callback;
}
}
});
Sadly, when passing a computed property to that modal like in the component below, the reactive link gets broken -> if the action is "remove", the list is not updated.
Vue.component('comp', {
data() {
return {obj: {a: 'foo', b: 'bar'}}
},
computed: {
objKeys() {
return Object.keys(this.obj);
}
},
template: `<div>
<button #click="showModal">Show Modal</button>
<modal></modal>
</div>`,
methods: {
remove(name) {
this.$delete(this.obj, name);
},
showModal() {
EventBus.$emit('showModal', this.objKeys, this.remove);
}
}
});
See the minimal use case in this fiddle: https://jsfiddle.net/christophfriedrich/cm778wgj/14/
I think this is a bug - shouldn't Vue remember that objKeys is used for rendering in Modal and update it? (The forwarding of the change of obj to objKeys works.) If not, what am I getting wrong and how could I achieve my desired result?
You have the modal working with its own copy of items:
template: `<ul v-if="shown">
<li v-for="item in items">
{{ item }} <button #click="callback(item)">Remove</button>
</li>
</ul>`,
methods: {
show(items, callback) {
this.shown = true;
this.items = items;
this.callback = callback;
}
}
That copy is made once, upon the call to show, and what you are copying is just the value of the computed at the time you emit the showModal event. What show receives is not a computed, and what it assigns is not a computed. It's just a value.
If, anywhere in your code, you made an assignment like
someDataItem = someComputed;
the data item would not be a functional copy of the computed, it would be a snapshot of its value at the time of the assignment. This is why copying values around in Vue is a bad practice: they don't automatically stay in sync.
Instead of copying values around, you can pass a function that returns the value of interest; effectively a get function. For syntactic clarity, you can make a computed based on that function. Then your code becomes
const EventBus = new Vue();
Vue.component('comp', {
data() {
return {
obj: {
a: 'foo',
b: 'bar'
}
}
},
computed: {
objKeys() {
return Object.keys(this.obj);
}
},
template: `<div>
<div>Entire object: {{ obj }}</div>
<div>Just the keys: {{ objKeys }}</div>
<button #click="remove('a')">Remove a</button>
<button #click="remove('b')">Remove b</button>
<button #click="showModal">Show Modal</button>
<modal></modal>
</div>`,
methods: {
remove(name) {
this.$delete(this.obj, name);
},
showModal() {
EventBus.$emit('showModal', () => this.objKeys, this.remove);
}
}
});
Vue.component('modal', {
data() {
return {
shown: false,
getItems: null,
callback: () => {}
}
},
mounted() {
EventBus.$on('showModal', this.show);
},
template: `<div v-if="shown">
<ul v-if="items.length>0">
<li v-for="item in items">
{{ item }} <button #click="callback(item)">Remove</button>
</li>
</ul>
<em v-else>empty</em>
</div>`,
computed: {
items() {
return this.getItems && this.getItems();
}
},
methods: {
show(getItems, callback) {
this.shown = true;
this.getItems = getItems;
this.callback = callback;
}
}
});
var app = new Vue({
el: '#app'
})
<script src="//unpkg.com/vue#latest/dist/vue.js"></script>
<div id="app">
<comp></comp>
</div>
You are passing a value to a function, you are not passing a prop to a component. Props are reactive, but values are just values. You include modal in the template of comp, so rework it to take (at least) items as a prop. Then it will be reactive.
I would recommend having the remove process follow the emit-event-and-process-in-parent rather than passing a callback.
const EventBus = new Vue();
Vue.component('comp', {
data() {
return {
obj: {
a: 'foo',
b: 'bar'
}
}
},
computed: {
objKeys() {
return Object.keys(this.obj);
}
},
template: `<div>
<div>Entire object: {{ obj }}</div>
<div>Just the keys: {{ objKeys }}</div>
<button #click="remove('a')">Remove a</button>
<button #click="remove('b')">Remove b</button>
<button #click="showModal">Show Modal</button>
<modal :items="objKeys" event-name="remove" #remove="remove"></modal>
</div>`,
methods: {
remove(name) {
this.$delete(this.obj, name);
},
showModal() {
EventBus.$emit('showModal');
}
}
});
Vue.component('modal', {
props: ['items', 'eventName'],
data() {
return {
shown: false,
}
},
mounted() {
EventBus.$on('showModal', this.show);
},
template: `<div v-if="shown">
<ul v-if="items.length>0">
<li v-for="item in items">
{{ item }} <button #click="emitEvent(item)">Remove</button>
</li>
</ul>
<em v-else>empty</em>
</div>`,
methods: {
show(items, callback) {
this.shown = true;
},
emitEvent(item) {
this.$emit(this.eventName, item);
}
}
});
var app = new Vue({
el: '#app'
})
<script src="//unpkg.com/vue#latest/dist/vue.js"></script>
<div id="app">
<comp></comp>
</div>

How to filter async computed data in Vuejs

I'm using asyncComputed in a Vuejs 2 app, and I need to filter a list of items that I'm fetching from an api.
This is what I have so far:
<template>
<div class="items">
<input type="text" v-model="searchKey">
<li v-for="item in items">
<span>{{item.name}}</span>
</li>
</div>
</template>
<script>
import Items from '#/api/items';
export default {
name: 'items',
data: {
searchKey: ''
}
asyncComputed: {
items: {
async get() {
const items = await Items.getAll();
return items.data;
},
default: []
}
},
methods: {},
components: {}
}
</script>
In this situation, is it possible to use a filter with the pipe operator, or do I need to create an entirely new filtered list and use that instead, like:
computed: {
filteredItems: function() {
return this.items.filter( ( item ) => item.name.toLowerCase().includes( this.searchKey.toLowerCase() );
}
}
Also, if I need to create a new filtered list, how can I do that with asynchronous data?
You can do it without any of the plugins, like this:
<template>
<div class="items">
<input type="text" v-model="searchKey">
<select v-model="filterType">
<option disabled value="">FilterBy</option>
<option>name</option>
<option>id</option>
</select>
<li v-for="item in filteredItems">
<span>{{item.name}}</span>
</li>
</div>
</template>
<script>
import Items from '#/api/items';
export default {
name: 'items',
data: {
searchKey: '',
items: null,
filterType:''
}
methods: {
async fetchItems(){
const items = await Items.getAll();
this.items = Items.data;
}
},
computed: {
filteredItems(){
if(this.filterType === 'name'){
return this.items.filter( ( item ) => item.name.toLowerCase().includes( this.searchKey.toLowerCase() );
}
if(this.filterType === 'id'){
return this.items.filter( ( item ) => item.id.includes( this.searchKey );
}
if(!this.filterType){
return this.items;
}
}
},
created(){
this.fetchItems();
}
}
</script>
You can even use a select input to select the criteria by which the list is to be filtered, in this case by id or by name.

Categories

Resources