Props being parsed undefined? - vue.js

Have this very simple vue component where I am trying to accept a search string as a prop
Vue.component('search-person', {
props: ['searchStr'],
created(){
console.log(this)
},
template: `<div class="container">
<form class="search-person-form">
<input type="text" class="search-person-input" placeholder="Search person" v-model="searchStr">
<div v-if="!searchStr.length">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 32 32" class="search-person-icon">
<title>Search</title>
<path d="M32 31.060l-12.233-12.24c1.777-1.992 2.863-4.634 2.863-7.53 0-6.259-5.074-11.333-11.333-11.333s-11.333 5.074-11.333 11.333c0 6.259 5.074 11.333 11.333 11.333 2.896 0 5.538-1.086 7.541-2.873l-0.011 0.010 12.233 12.24zM1.293 11.333c0-5.523 4.477-10 10-10s10 4.477 10 10c0 5.509-4.454 9.977-9.958 10h-0.002c-0 0-0 0-0 0-5.531 0-10.017-4.472-10.040-9.998v-0.002z"/>
</svg>
</div>
<div v-else >
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 32 32" class="search-person-icon pointer">
<title>cross</title>
<path d="M26 6.96l-0.96-0.96-9.040 9.040-9.040-9.040-0.96 0.96 9.040 9.040-9.040 9.040 0.96 0.96 9.040-9.040 9.040 9.040 0.96-0.96-9.040-9.040 9.040-9.040z"/>
</svg>
</div>
</form>
</div>`
})
I am then trying to use the component and parse a search string that is part of the data as a prop
<search-person :searchStr="personSearchStr"/>
personSearchStr is a part of the data object of the component that is nesting the search-person component but in my search-person component the searchStr prop is undefined

Following up on my comment, I created an example scenario with a Search Input component and it's Parent container.
The initial value of the search input (child) comes from a prop set in the parent. The search input watches the prop, so when the parent value is changed (via an input in the parent in my example), the child updates it's local copy. This happens as the user types (no form submission in parent).
When the search input (child) value is changed and the child form is submitted, an event is emitted to send the child value to update the parent.
Parent.vue
<template>
<div class="parent">
<h4>Parent Component</h4>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="prop-value">Enter new prop value</label>
<input type="text" class="form-control" id="prop-value" v-model="parentSearch">
</div>
<hr>
</div>
</div>
<search-input :searchFromParent="parentSearch" #update-search-event="updateSearch" />
</div>
</template>
<script>
import SearchInput from './SearchInput.vue'
export default {
components: {
SearchInput
},
data() {
return {
parentSearch: 'Search value from parent prop'
}
},
methods: {
updateSearch(searchFromChild) {
this.parentSearch = searchFromChild;
}
}
}
</script>
SearchInput.vue
<template>
<div class="search-input">
<h5>Search Input Component (child)</h5>
<div class="row">
<div class="col-md-6">
<form #submit.prevent="submitForm">
<div class="form-group">
<input type="text" class="form-control" id="search-input" v-model="localSearch">
</div>
<button type="submit" class="btn btn-secondary">Submit search value to parent</button>
</form>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
searchFromParent: {
type: String,
required: true
}
},
data() {
return {
localSearch: this.searchFromParent
}
},
watch: {
searchFromParent(newValue) {
this.localSearch = newValue;
}
},
methods: {
submitForm() {
this.$emit('update-search-event', this.localSearch)
}
}
}
</script>

Related

How to access prop by property name from in vuejs

I want to access the error message returned from my api if error exists for its corresponding input field. With what I have tried, all errors returned from the api shows below all input fields as in the image below.
Below are the parent and child vue components.
InputField.vue
<template>
<div class="form-group basic">
<div class="input-wrapper">
<label class="label" :for="name">{{label}}</label>
<input :type="type"
class="form-control"
:class="errorClassObject()"
:id="name" :placeholder="placeholder"
:v-model="value" #input="updateField">
<i class="clear-input">
<ion-icon name="close-circle" role="img" class="md hydrated" aria-label="close circle"></ion-icon>
</i>
</div>
<div v-text="errorMessage(name)" class="input-info text-danger">Error here</div>
</div>
</template>
<script>
export default {
name: "InputField",
props: [
'name', 'value', 'type', 'label', 'placeholder', 'errors'
],
data: function() {
return {
}
},
computed: {
hasError: function(){
return this.errors
//return this.errors.name throws an error name is undefined
}
},
methods: {
updateField (){
this.$emit('input', event.target.value)
},
errorMessage(name){
if(this.hasError){
return this.errors;
//return this.errors.name throws undefined name
}
},
errorClassObject (){
return{
'error-field': this.hasError
}
}
}
}
</script>
<style scoped>
</style>
ChangePassword.vue
<template>
<form #submit.prevent="onSubmit">
<form-input
required
name="current_password"
type="password"
label="Current Password"
:errors="errors"
placeholder="Enter current password"
v-model="passwordForm.current_password"
/>
<form-input .../>
<form-input.../>
<div class="form-button-group transparent">
<button type="submit" class="btn btn-primary btn-block btn-lg">Update Password</button>
</div>
</form>
</template>
Presently, if there are any errors from the request, the response is returned in the format below
You could get access to it using the square brackets like :
errorMessage(name){
if(this.hasError){
return this.errors[name];
}
},

Bind class item in the loop

i want to bind my button only on the element that i added to the cart, it's working well when i'm not in a loop but in a loop anything happen. i'm not sure if it was the right way to add the index like that in order to bind only the item clicked, if i don't put the index every button on the loop are binded and that's not what i want in my case.
:loading="isLoading[index]"
here the vue :
<div class="container column is-9">
<div class="section">
<div class="columns is-multiline">
<div class="column is-3" v-for="(product, index) in computedProducts">
<div class="card">
<div class="card-image">
<figure class="image is-4by3">
<img src="" alt="Placeholder image">
</figure>
</div>
<div class="card-content">
<div class="content">
<div class="media-content">
<p class="title is-4">{{product.name}}</p>
<p class="subtitle is-6">Description</p>
<p>{{product.price}}</p>
</div>
</div>
<div class="content">
<b-button class="is-primary" #click="addToCart(product)" :loading="isLoading[index]"><i class="fas fa-shopping-cart"></i> Ajouter au panier</b-button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
here the data :
data () {
return {
products : [],
isLoading: false,
}
},
here my add to cart method where i change the state of isLoading :
addToCart(product) {
this.isLoading = true
axios.post('cart/add-to-cart/', {
data: product,
}).then(r => {
this.isLoading = false
}).catch(e => {
this.isLoading = false
});
}
You can change your isLoading to an array of booleans, and your addToCart method to also have an index argument.
Data:
return {
// ...
isLoading: []
}
Methods:
addToCart(product, index) {
// ...
}
And on your button, also include index:
#click="addToCart(product, index)"
By changing isLoading to an empty array, I don't think isLoading[index] = true will be reactive since index on isLoading doesn't exist yet. So you would use Vue.set in your addToCart(product, index) such as:
this.$set(this.isLoading, index, true)
This will ensure that changes being made to isLoading will be reactive.
Hope this works for you.
add on data productsLoading: []
on add to cart click, add loop index to productsLoading.
this.productsLoading.push(index)
after http request done, remove index from productsLoading.
this.productsLoading.splice(this.productoading.indexOf(index), 1)
and check button with :loading=productsLoading.includes(index)
You can create another component only for product card,
for better option as show below
Kindly follow this steps.
place the content of card in another vue component as shown below.
<!-- Product.vue -->
<template>
<div class="card">
<div class="card-image">
<figure class="image is-4by3">
<img src="" alt="Placeholder image">
</figure>
</div>
<div class="card-content">
<div class="content">
<div class="media-content">
<p class="title is-4">{{product.name}}</p>
<p class="subtitle is-6">Description</p>
<p>{{product.price}}</p>
</div>
</div>
<div class="content">
<b-button class="is-primary" #click="addToCart(product)" :loading="isLoading"><i class="fas fa-shopping-cart"></i> Ajouter au panier</b-button>
</div>
</div>
</div>
</templete>
<script>
export default {
name: "Product",
data() {
return {
isLoading: false
}
},
props: {
product: {
type: Object,
required: true
}
},
methods: {
addToCart(product) {
this.isLoading = true
axios.post('cart/add-to-cart/', {
data: product,
}).then(r => {
this.isLoading = false
}).catch(e => {
this.isLoading = false
});
}
}
}
</script>
Change your component content as shown below.
<template>
<div class="container column is-9">
<div class="section">
<div class="columns is-multiline">
<div class="column is-3" v-for="(product, index) in computedProducts">
<product :product="product" />
</div>
</div>
</div>
</div>
</templete>
<script>
import Product from 'path to above component'
export default {
components: {
Product
}
}
</script>
so in the above method you can reuse the component in other components as well.
Happy coding :-)

Vue passing a event handler to a component to another component - Invalid handler for event "updatefilters": got undefined

I have some nested components. Here is my top level component that sites in the page HTML:
<advanced-options-model ... #updatefilters="updateFilters"></advanced-options-model>
Notice how updateFilters is passed into the component. Here is the advanced-options-model component and its dependent filter-checkboxes:
var filterCheckBoxes = {
template: `
<div>
<fieldset v-for="name in names">
<legend v-text="name"></legend>
<label v-for="...">
<input type="checkbox" ... #change="$emit('updatefilters')">
{{value}} ({{count}})
</label>
</fieldset>
</div>
`,
props: ["updatefilters", ...]
};
var advancedOptionsModal = {
components: {
"filter-checkboxes": filterCheckBoxes
},
template: `
<div class="modal" ...>
<div class="modal-dialog" role="document">
<div class="modal-content">
...
<div class="modal-body">
<filter-checkboxes ... #updatefilters="updatefilters"></filter-checkboxes>
</div>
...
</div>
</div>
</div>
`,
props: ["updatefilters", ...]
};
Vue.component('advanced-options-model', advancedOptionsModal);
Actually, if I remove the #updatefilters="updatefilters" in advancedOptionsModal I don't see any errors so the issue seems to be in this template. However, within this template I pass this same function to the filter-checkboxes component:
<filter-checkboxes ... #updatefilters="updatefilters"></filter-checkboxes>
Here is the exact error message I'm seeing in inspector:
vue.js:616 [Vue warn]: Invalid handler for event "updatefilters": got undefined
found in
---> <FilterCheckboxes>
<AdvancedOptionsModel>
<Root>
First, you're not passing updateFilters into advanced-options-model component, you're binding updateFilters method on updatefilters event.
To bind parameters to component you should use v-bind:... or just :.... Shorthand #... is for v-on:....
If you want to "bubble" updatefilters event from filter-checkboxes component to your root component you should do it like this:
var filterCheckBoxes = {
template: `
<div>
<fieldset v-for="name in names">
<legend v-text="name"></legend>
<label v-for="...">
<input type="checkbox" ... #change="$emit('updatefilters')">
{{value}} ({{count}})
</label>
</fieldset>
</div>
`
};
var advancedOptionsModal = {
components: {
"filter-checkboxes": filterCheckBoxes
},
template: `
<div class="modal" ...>
<div class="modal-dialog" role="document">
<div class="modal-content">
...
<div class="modal-body">
<filter-checkboxes ... #updatefilters="$emit('updatefilters')"></filter-checkboxes>
</div>
...
</div>
</div>
</div>
`
};
Vue.component('advanced-options-model', advancedOptionsModal);
And somewhere else:
<advanced-options-model ... #updatefilters="updateFilters"></advanced-options-model>
methods: {
updateFilters() {
// do something
}
}

Data sent from one Vue component to another remains reactive

I have an input-component which has a form which collects start and finish times, job number and a select option.
This is attached to a data property with v-model.
This is then emitted with Event.$emit('addedData', this.hours)
In the display-component the Event.$on takes this data and checks an attribute and based on the check adds it to another data property (array) with this.todays_hours.push().
The template then displays this reactively using v-for in the template.
To this point all works fine. However when I then attempt to add another line of hours the hours already displayed change reactively with the input.
As my input-component also posts to a database with axios if I reload the page all is displayed correctly.
input-component
<template>
<div>
<div class="row">
<div class="col-2">
<input hidden="" v-model="hours.day">
</div>
<div class="col-2" >
<input type="time" v-model="hours.start">
</div>
<div class="col-2" >
<input type="time" v-model="hours.finish">
</div>
<div class="col-2" >
<input type="number" v-model="hours.job_number">
</div>
<div class="col-2" >
<select v-model="hours.climbing">
<option selected="selected" value="0">No</option>
<option value="1">Yes</option>
</select>
</div>
<div class="col-2" >
<button #click="onSave" class="btn-success btn-sm">Save</button>
</div>
</div>
<hr>
</div>
</template>
<script>
export default {
name: 'InputHoursComponent',
props: ['employeeId', 'dayCheck', 'weekEnding'],
data() {
return {
hours: {
start: "",
finish: "",
job_number: "",
climbing: 0,
day: this.dayCheck
},
climbing_select: ['No', 'Yes'],
}
},
methods: {
onSave()
{
axios.post('/payroll', {
employee_id: this.employeeId,
week_ending: this.weekEnding,
start: this.hours.start,
finish: this.hours.finish,
job_number: this.hours.job_number,
climbing: this.hours.climbing,
day: this.dayCheck
})
.then(response => {})
.catch(e => {this.errors.push(e)});
let data = this.normalizeProp(this.hours, s, true)
Event.$emit('onAddedEntry', data);
console.log("passed data:", this.hours);
}
}
}
</script>
display-component
<template>
<div>
<div v-for="item in todays_hours">
<div class="row">
<div class="col-2">
<div hidden="" ></div>
</div>
<div class="col-2" >
<div v-text="item.start"></div>
</div>
<div class="col-2" >
<div v-text="item.finish"></div>
</div>
<div class="col-2" >
<div v-text="item.job_number"></div>
</div>
<div class="col-2" >
<div v-text="(item.climbing)?'Yes':'No'"></div>
</div>
<div class="col-2" >
<button #click="onEdit" class="btn-warning btn-sm mb-1">Edit</button>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'DisplayHoursComponent',
props: ['dayCheck', 'hoursWorked'],
data() {
return {
hours_list: this.hoursWorked,
todays_hours: []
}
},
mounted() {
for (var i = 0; i < this.hours_list.length; i++) {
if (this.hours_list[i].day === this.dayCheck) {
this.todays_hours.push(this.hours_list[i])
}
}
Event.$on('onAddedEntry', (check) => {
if(check.day === this.dayCheck){
this.todays_hours.push(check);
}
})
},
methods: {
onEdit()
{
}
}
}
</script>
Can someone please help me?
Try pushing a copy of check instead of check itself.
Event.$on('onAddedEntry', (check) => {
if(check.day === this.dayCheck){
this.todays_hours.push({...check});
}
})
You could also make the copy when you emit the event instead.

Show child component when promise data is exists and also render the data in child omponent

I am trying to implement search component for my application, parent component have the search text box and button. When the user provide some value i want to send the data to api and show the result in child component. I am bit confused where to call the api and also how to populate the data in child component. Also, initially my child component should not render in the parent component, when the search get some result then it can render. Please help me how to implement a search functionality in vue js 2.
Parent Component
<template>
<div><h3> Search </h3></div>
<div class="row">
<form role="search">
<div class="form-group col-lg-6 col-md-6">
<input type="text" v-model="searchKey" class="form-control">
</div>
<div class="col-lg-6 col-md-6">
<button type="button" id="btn2" class="btn btn-danger btn-md" v-on:click="getInputValue">Search</button>
</div>
</form>
</div>
<result :searchdataShow='searchData'></result>
</template>
<script>
import resultView from './result'
export default {
components: {
'result': resultView
},
data () {
return {
searchKey: null,
searchData: null
}
},
methods: {
getInputValue: function(e) {
console.log(this.searchKey)
if(this.searchKey && this.searchKey != null) {
this.$http.get('url').then((response) => {
console.log(response.data)
this.searchData = response.data
})
}
}
}
</script>
Search Result component(child component)
<template>
<div>
<div class="row"><h3> Search Results</h3></div>
</div>
</template>
<script>
export default {
props: ['searchdataShow']
}
</script>
Create a boolean variable that keeps track of your ajax request, i usually call it loading, or fetchedData, depending on the context. Before the ajax call, set it to true, after the call, set it to false.
Once you have this variable working, you can then conditionally render the result component with v-if. I like to show a loading icon with the corresponding v-else.
Also your template doesn't seem to have a root element, which is required.
<template>
<div><h3> Search </h3></div>
<div class="row">
<form role="search">
<div class="form-group col-lg-6 col-md-6">
<input type="text" v-model="searchKey" class="form-control">
</div>
<div class="col-lg-6 col-md-6">
<button type="button" id="btn2" class="btn btn-danger btn-md" v-on:click="getInputValue">Search</button>
</div>
</form>
</div>
<result v-if="!loading" :searchdataShow='searchData'></result>
<div v-else>loading!!</div>
</template>
<script>
import resultView from './result'
export default {
components: {
'result': resultView
},
data () {
return {
loading: false,
searchKey: null,
searchData: null
}
},
methods: {
getInputValue: function(e) {
console.log(this.searchKey)
this.loading = true;
if(this.searchKey && this.searchKey != null) {
this.$http.get('url').then((response) => {
console.log(response.data)
this.loading = false;
this.searchData = response.data
})
}
}
}
</script>