VueJS - Element UI Input Component - Event handler "input" error - javascript

I'm trying to create a custom component with VueJS & Element-UI and I'm getting a very annoying error when trying to enter data into the input field.
Below are the files & the contents related to the issue:
components.js file:
Vue.component('yetti-input', {
props: ['value'],
template: '<el-input ref="input" v-bind:value="value" v-on:input="parseValue($event.target.value)"></el-input>',
methods: {
parseValue (value) {
this.$emit('input', value)
}
}
})
index.vue file:
<template>
<div>
<div class="login-form">
<yetti-form>
<yetti-input v-model="login.email"></yetti-input>
</yetti-form>
</div>
</div>
</template>
<script>
export default {
data () {
return {
login: {
email: '',
password: ''
}
}
}
}
</script>
Error I'm receiving in the Console:
Please point out if I'm being a fool, however I cannot for the life of me figure out what is going on.
Cheers,
Tim

Okay, I solved my problem.
Interestingly, the $event is the input value being provided when using el-input.
Rather than have: v-on:input="parseValue($event.target.value)"
I removed target.value and I had my value.
v-on:input="parseValue($event)"
Not sure if I've done the wrong thing by VueJS here. However, this has resolved my issue.

Related

Vue2: Use form component with input type textarea to display AND edit data (without directly manipulating props)

I am building an MVP and this is the first time I do web development. I am using Vue2 and Firebase and so far, things go well.
However, I ran into a problem I cannot solve alone. I have an idea how it SHOULD work but cannot write it into code and hope you guys can help untangle my mind. By now I am incredibly confused and increasingly frustrated :D
So lets see what I got:
Child Component
I have built a child component which is a form with three text-areas. To keep it simple, only one is included it my code snippets.
<template>
<div class="wrap">
<form class="form">
<p class="label">Headline</p>
<textarea rows="2"
v-model="propHeadline"
:readonly="readonly">
</textarea>
// To switch between read and edit
<button
v-if="readonly"
#click.prevent="togglemode()">
edit
</button>
<button
v-else
type="submit"
#click.prevent="togglemode(), updatePost()"
>
save
</button>
</form>
</div>
</template>
<script>
export default {
name: 'PostComponent'
data() {
return {
readonly: true
}
},
props: {
propHeadline: {
type: String,
required: true
}
},
methods: {
togglemode() {
if (this.readonly) {
this.readonly = false
} else {
this.readonly = true
}
},
updatePost() {
// updates it to the API - that works
}
}
}
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
And my parent component:
<template>
<div class="wrap">
<PostComponent
v-for="post in posts"
:key="post.id"
:knugHeadline="post.headline"
/>
</div>
</template>
<script>
import PostComponent from '#/components/PostComponent.vue'
export default {
components: { PostComponent },
data() {
return {
posts: []
}
},
created() {
// Gets all posts from DB and pushes them in array "posts"
}
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
Current Status
So far, everything works. I can display all posts and when clicking on "edit" I can make changes and save them. Everything gets updated to Firebase - great!
Problem / Error Message
I get the following error message:
[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value.
As the error says I should use a computed property based on the props value. But how can I achieve that?
Solution Approach
I believe I have to use a computed getter to return the prop value - how to do that?
And then I have to use the setter to emit an event to the parent to update the value so the prop passes it back down - how to do that?
I have found bits and pieces online but by now all I see is happy families passing around small packages of data...
Would be really thankful for a suggestion on how to solve this one! :)
Thanks a lot!
This error shows because of your v-model on texterea which mutate the prop, but in vue it is illegal to mutate props :
<textarea rows="2"
v-model="propHeadline"
:readonly="readonly">
</textarea>
So, what you could do is to use this created() lifecycle hook and set the propHeadline prop as data :
<script>
export default {
name: 'PostComponent'
data() {
return {
readonly: true,
headline: ""
}
},
props: {
propHeadline: {
type: String,
required: true
}
},
created() {
this.headline = this.propHeadline
}
}
</script>
An then, update the new variable on your textarea :
<textarea rows="2"
v-model="headline"
:readonly="readonly">
</textarea>

How to DRY up VueJS + Vuelidate server side validation error code?

I'm working on a VueJS project (using the Quasar framework). When building a form component, for example to edit user settings, I have frontend validation, to check required fields etc. using vuelidate, and I want to also show backend validation errors. Currently my setup (for a sample form) is as follows:
script:
export default {
name: 'UserEditForm',
mixins: [formServerValidation],
data: function () {
return {
form: {
...
name: this.$store.state.currentUser.attributes.name,
currentPassword: 'winnerwinner',
serverErrors: {
// Todo: Autogenerate all server errors
}
}
}
},
methods: {
onSubmit () {
const name = this.form.name
const currentPassword = this.form.currentPassword
this.clearServerErrors()
this.$store.dispatch('updateUser', { name, currentPassword, }).then((result) => {
console.log("Server update success")
}).catch(err => {
console.log("Server update error")
const mappedErrors = this.mapServerErrors(err.response.data.errors)
merge(this.form.serverErrors, mappedErrors)
this.$refs.form.validate()
})
},
serverError: function (fieldName) {
return (value, vm) => {
return !(
Object.prototype.hasOwnProperty.call(vm, 'serverErrors') &&
Object.prototype.hasOwnProperty.call(vm.serverErrors, fieldName)
)
}
}
},
validation: {
form: {
...
name: {
required,
serverError: this.serverError('name')
},
currentPassword: {
required,
serverError: this.serverError('currentPassword')
},
...
}
}
}
template:
<template>
<q-form #submit="onSubmit" ref="form">
...
<q-input
field-name="name"
type="text"
v-model="form.name"
label="Name"
lazy-rules
#input="clearServerError('name', $event)"
:rules="[
val => $v.form.name.required || 'Enter name',
val => $v.form.name.serverError || form.serverErrors.name,
]" />
<q-input
field-name="currentPassword"
type="password"
v-model="form.currentPassword"
label="Current password*"
lazy-rules
#input="clearServerError('currentPassword', $event)"
:rules="[
val => $v.form.currentPassword.required || 'Confirm password',
val => $v.form.currentPassword.serverError || form.serverErrors.currentPassword
]"
/>
...
<div>
<q-btn label="Save" type="submit" color="primary" />
</div>
</q-form>
</template>
This all works perfectly fine, however it seems to be very WET (not DRY). I have to define the following things for each field manually:
serverError: this.serverError('name') in validation
A rule val => $v.form.name.serverError || form.serverErrors.name in the template
#input="clearServerError('name', $event)" in the template
But I know I want to do this for every input in my form component, so it feels very repetitive to do this manually for every input component. Is there a correct "Vue-ish" way to DRY this up?
One thing I tried is to find all input fields by traversing all descendants of my form component using $children. Then:
I tried to tackle 1., by defining these server errors dynamically by looping over all input components.
2. is harder to tackle since you can not directly update the property :rules since Vue will override it on a new render of the parent component. (This also throws a warning, saying you should not directly update properties, but use v-model etc.)
I tried to tackle 3. by defining input event listeners dynamically from my Form component using the $on method on the actual input components.
The DRY-up for 1 and 3 seem to work, but is it the best way of doing it? And what is a correct approach of drying up 2?

vue.js v-model not updating if vue-range-slider component on page

I have a problem with the structure of my Vue.js components, but I don't understand what it is.
This is my app.js:
require('./bootstrap');
window.Vue = require('vue');
Vue.component('search', require('./components/Search').default);
Vue.component('graph', require('./components/Graph').default);
Vue.component('account', require('./components/Account').default);
Vue.component('design-theme', require('./components/DesignTheme').default);
const app = new Vue({
el: '#app',
data: {
},
methods: {
},
mounted: function () {
},
computed: {
}
});
So I don't have any methods or anything here, it is all in the four individual components. Each component works fine on its own, but when there is more than one in a page, something is off. Consider the Search.vue component. It simply sends an axios request to the server on keyup and shows a list of results:
<template>
<div class="search basic-search">
<input type="text" v-model="search_string" v-on:keyup="search" class="form-control search" placeholder="Search" value=""/>
<div :class="['search-results', active === true ? 'active' : '']">
<div class="search-result" v-for="result in search_results" v-on:click="submit(result.id)">
{{ result.name }}
</div>
</div>
</div>
</template>
<script>
export default {
data: function() {
return {
search_string : '',
search_results : [],
active : false
};
},
methods : {
search : function() {
const axios_data = {
_token : $('meta[name="csrf-token"]').attr('content'),
str : this.search_string
};
axios.post('/search', axios_data).then(response => {
if(response.data.success){
this.search_results = response.data.stocks;
this.active = true;
}
});
},
submit : function(stock_id) {
document.location = "/graphs/" + stock_id;
}
}
}
</script>
This works fine if the Graph.vue component is not included on the page. But, if it is, then search_str always remains empty, even though the search method is called on keyup.
There are no errors in the console - it's just that search_string remains empty when I type (as does the input field).
Perhaps I don't understand something on a conceptual level in Vue.js, but I can't figure out the relation here, or how to adapt the code to this situation.
This is the problematic part of the Graph component, if this is removed then the search works OK.
<vue-range-slider v-model="range" :min="0" :max="100" v-on:drag-end="updateRange"></vue-range-slider>
This is the component in question:
https://github.com/xwpongithub/vue-range-slider
Another interesting side effect (that I just noticed) is that, when this component is on the page, it is impossible to select text with the mouse. It seems like the component is somehow hijacking events, but I don't understand how.
As you identified correctly, the Vue Range Slider component is intercepting the events. There is an open merge request on their github page.
As suggested in the referenced issues, you should change this line in your package.json file:
"vue-range-component": "^1.0.3",
To this one:
"vue-range-component": "Anoesj/vue-range-slider#master",
However, since this is not the default branch of the plugin, you should frequently check the issue on github and switch back to the official branch as soon as the merge request passes.

Vue JS - Problem with computed property not updating

I am quite new with VueJS and I have been having trouble lately with some computed properties which do not update as I would like. I've done quite some research on Stack Overflow, Vue documentation and other ressources but i haven't found any solution yet.
The "app" is basic. I've got a parent component (Laundry) which has 3 child components (LaundryMachine). The idea is to have for each machine a button which displays its availability and updates the latter when clicked on.
In order to store the availability of all machines, I have a data in the parent component (availabilities) which is an array of booleans. Each element corresponds to a machine's availability.
When I click on the button, I know the array availibities updates correctly thanks to the console.log. However, for each machine, the computed property "available" does not update is I would want it to and I have no clue why.
Here is the code
Parent component:
<div id="machines">
<laundry-machine
name="AA"
v-bind:machineNum="0"
v-bind:availableArray="this.availabilities"
v-on:change-avlb="editAvailabilities"
></laundry-machine>
<laundry-machine
name="BB"
v-bind:machineNum="1"
v-bind:availableArray="this.availabilities"
v-on:change-avlb="editAvailabilities"
></laundry-machine>
<laundry-machine
name="CC"
v-bind:machineNum="2"
v-bind:availableArray="this.availabilities"
v-on:change-avlb="editAvailabilities"
></laundry-machine>
</div>
</div>
</template>
<script>
import LaundryMachine from './LaundryMachine.vue';
export default {
name: 'Laundry',
components: {
'laundry-machine': LaundryMachine
},
data: function() {
return {
availabilities: [true, true, true]
};
},
methods: {
editAvailabilities(index) {
this.availabilities[index] = !this.availabilities[index];
console.log(this.availabilities);
}
}
};
</script>
Child component:
<template>
<div class="about">
<h2>{{ name }}</h2>
<img src="../assets/washing_machine.png" /><br />
<v-btn color="primary" v-on:click="changeAvailability">
{{ this.availability }}</v-btn>
</div>
</template>
<script>
export default {
name: 'LaundryMachine',
props: {
name: String,
machineNum: Number,
availableArray: Array
},
methods: {
changeAvailability: function(event) {
this.$emit('change-avlb', this.machineNum);
console.log(this.availableArray);
console.log('available' + this.available);
}
},
computed: {
available: function() {
return this.availableArray[this.machineNum];
},
availability: function() {
if (this.available) {
return 'disponible';
} else {
return 'indisponible';
}
}
}
};
</script>
Anyway, thanks in advance !
Your problem comes not from the computed properties in the children, rather from the editAvailabilities method in the parent.
The problem is this line in particular:
this.availabilities[index] = !this.availabilities[index];
As you can read here, Vue has problems tracking changes when you modify an array by index.
Instead, you should do:
this.$set(this.availabilities, index, !this.availabilities[index]);
To switch the value at that index and let Vue track that change.

binding a ref does not work in vue.js?

When I v-bind a element-ref with :ref="testThis" it stops working it seems. Compare this version which works:
<template>
<div>
<q-btn round big color='red' #click="IconClick">
YES
</q-btn>
<div>
<input
ref="file0"
multiple
type="file"
accept=".gif,.jpg,.jpeg,.png,.bmp,.JPG"
#change="testMe"
style='opacity:0'
>
</div>
</div>
</template>
<script>
import { QBtn } from 'quasar-framework'
export default {
name: 'hello',
components: {
QBtn
},
data () {
return {
file10: 'file0'
}
},
methods: {
IconClick () {
this.$refs['file0'].click()
},
testMe () {
console.log('continue other stuff')
}
}
}
</script>
With this one which DOES NOT work:
<template>
<div>
<q-btn round big color='red' #click="IconClick">
YES
</q-btn>
<div>
<input
:ref="testThis"
multiple
type="file"
accept=".gif,.jpg,.jpeg,.png,.bmp,.JPG"
#change="testMe"
style='opacity:0'
>
</div>
</div>
</template>
<script>
import { QBtn } from 'quasar-framework'
export default {
name: 'hello',
components: {
QBtn
},
data () {
return {
file10: 'file0'
}
},
methods: {
IconClick () {
this.$refs['file0'].click()
},
testThis () {
return 'file0'
},
testMe () {
console.log('continue other stuff')
}
}
}
</script>
The first one works. The second one throws an error:
TypeError: Cannot read property 'click' of undefined
at VueComponent.IconClick
As I would like to vary the ref based on a list-index (not shown here, but it explains my requirement to have a binded ref) I need the binding. Why is it not working/ throwing the error?
In the vue docs I find that a ref is non-reactive: "$refs is also non-reactive, therefore you should not attempt to use it in templates for data-binding."
I think that matches my case.
My actual problem 'how to reference an item of a v-for list' is NOT easily solved not using a binded ref as vue puts all similar item-refs in an array, BUT it loses (v-for index) order.
I have another rather elaborate single file component which works fine using this piece of code:
:ref="'file' + parentIndex.toString()"
in an input element. The only difference from my question example is that parentIndex is a component property.
All in all it currently is kind of confusing as from this it looks like binding ref was allowed in earlier vue version.
EDIT:
Triggering the method with testThis() does work.
If you want to use a method, you will need to use the invocation parentheses in the binding to let Vue know you want it to bind the result of the call and not the function itself.
:ref="testThis()"
I think the snippet below works as you expect it to. I use a computed rather than a method.
new Vue({
el: '#app',
data() {
return {
file10: 'file0'
}
},
computed: {
testThis() {
return 'file0';
}
},
methods: {
IconClick() {
this.$refs['file0'].click()
},
testMe() {
console.log('continue other stuff')
}
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="app">
<q-btn round big color='red' #click="IconClick">
YES
</q-btn>
<div>
<input :ref="testThis" multiple type="file" accept=".gif,.jpg,.jpeg,.png,.bmp,.JPG" #change="testMe" style='opacity:0'>
</div>
</div>

Categories

Resources