The Problem
I have a search component and component which implements the search component. When I type something in the search bar after 1/2 second of not typing (debounce) the server should be hit and the results should be returned.
The solution i am trying to implement comes from this post on Stackoverflow
The code
This leads me to the following code.
I have search.vue
<template>
<label for="search">
<input
id="search"
class="w-full py-2 px-1 border-gray-900 border"
type="text"
name=":searchTitle"
v-model="searchFilter"
:placeholder="searchPlaceholder"
autocomplete="off"
v-on:keydown="filteredDataset"
/>
</label>
</template>
<script>
import {debounce} from 'lodash';
export default {
props: {
searchPlaceholder: {
type: String,
required: false,
default: ''
},
searchName: {
type: String,
required: false,
default: 'search'
}
},
data() {
return {
searchFilter: '',
}
},
methods: {
filteredDataset() {
console.log('event fired');
this.$emit('searchValue', this.searchFilter);
}
},
}
</script>
And product.vue
<template>
<div>
<div class="my-4">
<search
search-placeholder=""
search-name=""
v-on:searchValue="filterValue = $event"
v-model="productsFiltered"
>
</search>
<div class="flex w-full py-1 border px-2 my-2" v-for="product in productsFiltered"> (...)
</div>
</div>
</div>
</div>
</template>
<script>
import {debounce} from 'lodash';
export default {
data() {
return {
products: [],
filterValue: '',
filteredProducts: ''
}
},
computed: {
productsFiltered: {
get(){
console.log('getter called');
return this.filteredProducts;
},
set: _.debounce(function(){
console.log('setter called');
if (this.filterValue.length < 1) {
this.filteredProducts = [];
}
axios.get(`${apiUrl}search/` + this.filterValue)
.then(response => {
this.products = response.data.products;
const filtered = [];
const regOption = new RegExp(this.filterValue, 'ig');
for (const product of this.products) {
if (this.filterValue.length < 1 || product.productname.match(regOption)) {
filtered.push(product);
}
}
this.filteredProducts = filtered;
});
}, 500)
}
},
}
</script>
The result
The result is that the setter in the computed property in product.vue does not get called and no data is fetched from the server. Any ideas on how to solve this?
Your first code block imports debounce but does not use it. It also declares a prop, searchName, that isn't used. These aren't central issues, but clutter makes it harder to figure out what's going on.
Your second code block uses v-model but does not follow the required conventions for getting v-model to work with components:
the component must take a prop named value
the component must emit input events to signal changes to value
You have the component emit searchValue events, and handle them with a v-on that sets a data item. You seem to expect the v-model to call the setter, but as I noted, you haven't hooked it up to do so.
From what's here, you don't even really need to store the input value. You just want to emit it when it changes. Here's a demo:
const searchComponent = {
template: '#search-template',
props: {
searchPlaceholder: {
type: String,
required: false,
default: ''
}
},
methods: {
filteredDataset(searchFilter) {
console.log('event fired');
this.$emit('input', searchFilter);
}
}
};
new Vue({
el: '#app',
data() {
return {
products: [],
filterValue: '',
filteredProducts: ''
}
},
components: {
searchComponent
},
computed: {
productsFiltered: {
get() {
console.log('getter called');
return this.filteredProducts;
},
set: _.debounce(function() {
console.log('setter called');
if (this.filterValue.length < 1) {
this.filteredProducts = [];
}
setTimeout(() => {
console.log("This is the axios call");
this.filteredProducts = ['one','two','three'];
}, 200);
}, 500)
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>
<template id="search-template">
<label for="search">
<input
id="search"
class="w-full py-2 px-1 border-gray-900 border"
type="text"
name=":searchTitle"
:placeholder="searchPlaceholder"
autocomplete="off"
#input="filteredDataset"
/>
</label>
</template>
<div id="app">
<div class="my-4">
<search-component search-placeholder="enter something" v-model="productsFiltered">
</search-component>
<div class="flex w-full py-1 border px-2 my-2" v-for="product in productsFiltered"> (...)
</div>
</div>
</div>
Related
I am doing the search engine section in VueJS and Laravel, but I have a problem that does not allow me to advance in the other sections. The search engine opens and everything but when I write it only sends the first letter or 2 but not all of them like this in this image:
image of the data you send
the data that I write
After that it shows me the following error in console:
Uncaught (in promise) NavigationDuplicated: Avoided redundant navigation to current location: "/search?q=th"
Now showing my search engine code:
<template>
<div class="form_MCycW">
<form autocomplete="off" #sumbit.prevent>
<label class="visuallyhidden" for="search">Search</label>
<div class="field_2KO5E">
<input id="search" ref="input" v-model.trim="query" name="search" type="text" placeholder="Search for a movie, tv show or person..." #keyup="goToRoute" #blur="unFocus">
<button v-if="showButton" type="button" aria-label="Close" #click="goBack">
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 15 15"><g fill="none" stroke="#fff" stroke-linecap="round" stroke-miterlimit="10" stroke-width="1.5"><path d="M.75.75l13.5 13.5M14.25.75L.75 14.25"/></g></svg>
</button>
</div>
</form>
</div>
</template>
<script>
import { mapState } from 'vuex';
export default {
data() {
return {
query: this.$route.query.q ? this.$route.query.q : ''
}
},
computed: {
showButton() {
return this.$route.name === 'search';
},
...mapState({
search: state => state.event.fromPage
})
},
mounted() {
this.$refs.input.focus();
},
methods: {
goToRoute() {
if (this.query) {
this.$router.push({
name: 'search',
query: { q: this.query },
});
} else {
this.$router.push({
path: this.fromPage,
});
}
},
goBack() {
this.query = '';
this.$router.push({
path: '/',
});
},
unFocus (e) {
if (this.$route.name !== 'search') {
const target = e.relatedTarget;
if (!target || !target.classList.contains('search-toggle')) {
this.query = '';
this.$store.commit('closeSearch');
}
}
}
}
}
</script>
This is the other section of the search engine:
<template>
<main class="main">
<div class="listing">
<div class="listing__head"><h2 class="listing__title">{{ title }}</h2></div>
<div class="listing__items">
<div class="card" v-for="(item, index) in data.data" :key="index">
<router-link :to="{ name: 'show-serie', params: { id: item.id }}" class="card__link">
<div class="card__img lazyloaded"><img class="lazyload image_183rJ" :src="'/_assets/img/covers/posters/' + item.poster" :alt="item.name"></div>
<h2 class="card__name">{{ item.name }}</h2>
<div class="card__rating">
<div class="card__stars"><div :style="{width: item.rate * 10 + '%'}"></div></div>
<div class="card__vote">{{ item.rate }}</div>
</div>
</router-link>
</div>
</div>
</div>
</main>
</template>
<script>
import { mapState } from 'vuex';
let fromPage = '/';
export default {
name: "search",
metaInfo: {
bodyAttrs: {
class: 'page page-search'
}
},
computed: {
...mapState({
data: state => state.search.data,
loading: state => state.search.loading
}),
query() {
return this.$route.query.q ? this.$route.query.q : '';
},
title() {
return this.query ? `Results For: ${this.query}` : '';
},
},
async asyncData ({ query, error, redirect }) {
try {
if (query.q) {
this.$store.dispatch("GET_SEARCH_LIST", query.q);
} else {
redirect('/');
}
} catch {
error({ message: 'Page not found' });
}
},
mounted () {
this.$store.commit('openSearch');
this.$store.commit('setFromPage', fromPage);
if (this.data.length == 0 || this.data === null) {
this.$store.dispatch("GET_SEARCH_LIST", this.query);
}
setTimeout(() => {
this.showSlideUpAnimation = true;
}, 100);
},
beforeRouteEnter (to, from, next) {
fromPage = from.path;
next();
},
beforeRouteUpdate (to, from, next) {
next();
},
beforeRouteLeave (to, from, next) {
const search = document.getElementById('search');
next();
if (search && search.value.length) {
this.$store.commit('closeSearch');
}
}
};
</script>
In my routes section it is defined as follows:
{
name: 'search',
path: '/search',
component: require('../views/' + themeName + '/control/search/index').default
}
It is supposed to be a real-time search engine. I would appreciate your help in solving this problem...
What you need is a debounce. What it does is that it wait or delay till the user had finished typing before the model get updated or before you send it to the server.
An example of how it works is here
Here is a package for it.
https://github.com/vuejs-tips/v-debounce
I have made this code, I want to facilitate user to dynamically enter text and display fields which have values, but I'm unable to get the result when I run it laravel+VueJs component view. below is my code
<template>
<div>
<div>
<label>Name</label>
<input type="text" #change="addRow">
</div>
<div> <label>Email</label>
<input type="text" #change="addRow1">
</div>
<div v-for="row in rows" :key="row.id">
<button-counter :id="row.id" :value="row.value"></button-counter>
</div>
</div>
<script type="text/javascript">
Vue.component('button-counter', {
props: {
value: {
default: ''
}
},
template: '<input type="text" style="margin-top: 10px;" v-model="value" >',
})
export default {
mounted() {
console.log('Component mounted.')
},
data: {
rows: [],
count:0
},
methods: {
addRow: function () {
var txtCount=1;
id='txt_'+txtCount;
this.rows.push({ value:'MyName' , description: "textbox1", id });
},
addRow1: function () {
var txtCount=1;
id='txt2_'+txtCount;
this.rows.push({ value: "myEmail", description: "textbox2", id });
}
}
}
data should be a function that returns an object holding data you want to manipulate, and you need to define the id as a variable, here's a working Vue SFC
<template>
<div>
<div>
<label>Name</label>
<input type="text" #change="addRow" />
</div>
<div>
<label>Email</label>
<input type="text" #change="addRow1" />
</div>
<div v-for="row in rows" :key="row.id">
<button-counter :id="row.id" :value="row.value"></button-counter>
</div>
</div>
</template>
<script type="text/javascript">
Vue.component("button-counter", {
props: {
value: {
default: ""
}
},
template: '<input type="text" style="margin-top: 10px;" v-model="value" >'
});
export default {
data() {
return {
rows: [],
count: 0
};
},
methods: {
addRow: function() {
var txtCount = ++this.count;
let id = "txt_" + txtCount;
this.rows.push({ value: "MyName", description: "textbox1", id });
},
addRow1: function() {
var txtCount = ++this.count;
let id = "txt2_" + txtCount;
this.rows.push({ value: "myEmail", description: "textbox2", id });
}
}
};
</script>
Hope this helps
My page exist of a table where I can add new rows. If you want to add a new row a pop-up window appear where the new values can be added.
This new data is then saved to the database after submitting. If I again want to add a new row the input fields, they should be cleared.
The method I use, is working but isn't very clear.
Note: My code shows only a part of the input fields, to make it more clear. My pop-up window actually contains 20 input fields.
I would like to clear them all at once instead of clearing them one by one (like I am doing now).
Because I am already doing this for defining the v-model, pushing the new data to the database directly on the page and via post axios request.
Is there a cleaner way to do this?
Thanks for any input you could give me.
This is my code:
html part
<div class="col-2 md-2">
<button class="btn btn-success btn-sx" #click="showModal('add')">Add New</button>
<b-modal :ref="'add'" hide-footer title="Add new" size="lg">
<div class="row" >
<div class="col-4">
<b-form-group label="Category">
<b-form-input type="text" v-model="newCategory"></b-form-input>
</b-form-group>
</div>
<div class="col-4">
<b-form-group label="Name">
<b-form-input type="text" v-model="newName" placeholder="cd4"></b-form-input>
</b-form-group>
</div>
<div class="col-4">
<b-form-group label="Amount">
<b-form-input type="number" v-model="newAmount" ></b-form-input>
</b-form-group>
</div>
</div>
<div class="row" >
<div class="col-8">
</div>
<div class="col-4">
<div class="mt-2">
<b-button #click="hideModal('add')">Close</b-button>
<b-button #click="storeAntibody(antibodies.item)" variant="success">Save New Antibody</b-button>
</div>
</div>
</div>
</b-modal>
</div>
js part
<script>
import { async } from 'q';
export default {
props: ['speciedata'],
data() {
return {
species: this.speciedata,
newCategory: '',
newName: '',
newAmount:'',
}
},
computed: {
},
mounted () {
},
methods: {
showModal: function() {
this.$refs["add"].show()
},
hideModal: function(id, expId) {
this.$refs['add'].hide()
},
addRow: function(){
this.species.push({
category: this.newCategory,
name: this.newName,
amount: this.newAmount,
})
},
storeSpecie: async function() {
axios.post('/specie/store', {
category: this.newCategory,
name: this.newName,
amount: this.newAmount,
})
.then(this.addRow())
// Clear input
.then(
this.newName = '',
this.newCategory = '',
this.newAmount = '',
)
.then(this.hideModal('add'))
},
}
}
</script>
in your data of vuejs app , you have to set one object for displaying modal data like modalData then to reset data you can create one function and set default value by checking type of value using loop through modalData object keys
var app = new Vue({
el: '#app',
data: {
message:"Hi there",
modalData:{
key1:"value1",
key2:"value2",
key3:"value3",
key4:5,
key5:true,
key6:"val6"
}
},
methods: {
resetModalData: function(){
let stringDefault="";
let numberDefault=0;
let booleanDefault=false;
Object.keys(this.modalData).forEach(key => {
if(typeof(this.modalData[key])==="number"){
this.modalData[key]=numberDefault;
}else if(typeof(this.modalData[key])==="boolean") {
this.modalData[key]=booleanDefault;
}else{
// default type string
this.modalData[key]=stringDefault;
}
});
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<div id="app">
{{modalData}}
<br/>
<button #click="resetModalData">Reset Modal Data</button>
</div>
update : in your case :
data:{
species: this.speciedata,
modalData:{
newCategory: '',
newName: '',
newAmount:''
}
},
and after storing data :
storeSpecie: async function() {
axios.post('/specie/store', {
category: this.newCategory,
name: this.newName,
amount: this.newAmount,
})
.then(()=>{
this.addRow();
this.resetModalData();
this.hideModal('add')
}
},
In native Javascript you get the reset() method.
Here is how it is used :
document.getElementById("myForm").reset();
It will clear every input in the form.
I'm developing chat app on laravel using vue.js and i'm new to vue.js.
but i'm getting below mentioned error, please help me solve this
Error1 :
[Vue warn]: Property or method "contact" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property
Error2 :
[Vue warn]: Error in render: "TypeError: Cannot read property 'avatar' of undefined"
/ ChatApp.vue file/
<template>
<div class="chat-app">
<Conversation :contact="selectedContact" :messages="messages"/>
<ContactsList :contacts="contacts"/>
</div>
</template>
<script>
import Conversation from './Conversation.vue';
import ContactsList from './ContactsList.vue';
export default {
props: {
user: {
type: Object,
required: true
}
},
data(){
return {
selectedContact: null,
messages: [],
contacts: []
};
},
mounted(){
axios.get('/contacts')
.then((response) => {
this.contacts = response.data;
});
},
methods: {
startConversationWith(contact) {
axios.get(`/conversation/$(contact.id)`)
.then((response) => {
this.messages = response.data;
this.selectedContact = contact;
})
}
},
components: { ContactsList, Conversation }
};
</script>
/ ContactsList.vue file/
<template>
<div class="contacts-list">
<ul v-if="contacts.length > 0">
<li v-for:"(contact, index) In contacts" :key="contact.id"
#click="selectContact(index, contact)" :class="{ 'selected' : index ==
selected}">
<div class="avatar">
<img :src="contact.avatar" :alt="contact.name">
</div>
<div class="contact">
<p class="name">{{ contact.name }}</p>
<p class="email">{{ contact.email }}</p>
</div>
</li>
</ul>
</div>
</template>
<script>
export default {
props: {
contacts: {
type: Array,
default: []
}
},
data() {
return {
selected: 0
};
},
methods: {
selectContact(index, contact) {
this.selected = index;
this.$emit('selected', contact);
}
}
};
</script>
/ conversation.vue /
<template>
<div class="conversation">
<h1>{{ contact ? contact.name : 'Select a contact' }}</h1>
<MessagesFeed :contact="contact" :messages="messages" />
<MessageComposer #send="sendMessage" />
</div>
</template>
<script>
import MessagesFeed from './MessagesFeed.vue';
import MessageComposer from './MessageComposer.vue';
export default {
props: {
contact: {
type: Object,
default: null
},
messages: {
type: Array,
default: []
}
},
methods:{
sendMessage(text) {
console.log(text);
}
},
components: {MessagesFeed, MessageComposer}
};
</script>
#Akash you can use it this way :
data() {
return {
contactsNotEmpty:false
}
},
// ...
mounted() {
axios.get('/contacts')
.then((response) => {
this.contacts = response.data;
this.contactsNotEmpty = true;
});
}
<ul v-if="contactsNotEmpty">
...
</ul>
also you may check this vuejs article about what is happening : https://vuejs.org/2016/02/06/common-gotchas/#Why-isn%E2%80%99t-the-DOM-updating
I guess you can do it like that:
<ul v-if="contacts.length > 0">
<li v-for="contact in contacts" :key="contact.id">
<div class="avatar">
<img :src="contact.avatar" :alt="contact.name">
</div>
<div class="contact">
<p class="name">{{ contact.name }}</p>
<p class="email">{{ contact.email }}</p>
</div>
</li>
</ul>
The first error is throwing in your Conversation.vue, you were passing data to a child component, you have to make sure it is defined as a prop in that component
export default{
props: ['contact']
};
and if it is defined, your prop type is not expecting a null value. I would change it to string and on rendering in my ChatApp.vue, I would set selectedContact to string.Empty||''
For the second error, the avatar key is missing from your response object. You have to check if it is present first before accessing it. Kindly refer to El Alami Anas's answer above.
I have a Vue component that makes a post request, and then outputs the returned html.
Sometimes, the html that is returned by the post contains Vue directives.
Is there a way to have Vue parse the returned html before it is output?
(In the longer term, I will rewrite this as a pure Vue solution, with the post request returning data rather than html. I'm after a short term solution if its possible).
EDIT:
Here's my stab based on thanksd's suggestion but I'm not sure how to bind the new Vue instance to an html element.
<template>
<div>
<input type="text" class="form-control" v-model="value" #change="getResults" ></input>
<div>
<template v-bind="results"></template>
</div>
</div>
</template>
<script>
import{eventHub} from '../utils/event.js'
export default {
data : function(){
return {
value : '',
results : {}
}
},
methods:{
getResults(){
if(this.value.length < 3){return;}
this.$http.post('/ajax/search',{search:this.value}).then((response)=>{
this.results = Vue({template:response.body});
});
},
},
}
After the post request returns you could create a new Vue instance, passing the html as the template and binding it to an element in your current Vue instance's template:
<template>
<div>
<input type="text" class="form-control" v-model="value" #change="getResults" ></input>
<div>
<div id="results"></div>
</div>
</div>
</template>
<script>
export default {
data() {
return { value: '' }
},
methods: {
getResults() {
if (this.value.length < 3) {
return;
}
this.$http.post('/ajax/search', { search: this.value }).then((response) => {
new Vue({ el: '#results', template: response.body });
});
}
}
}
</script>
Or as #Bert pointed out, you could add a <component> tag to your template and pass its definition via the is prop:
<template>
<div>
<input type="text" class="form-control" v-model="value" #change="getResults" ></input>
<component :is="results"/>
</div>
</template>
<script>
export default {
data() {
return {
value: '',
results: null
}
},
methods: {
getResults() {
if (this.value.length < 3) {
return;
}
this.$http.post('/ajax/search', { search: this.value }).then((response) => {
this.results = { template: response.body };
});
}
}
}
</script>