VueJs - How to update bind value where :value="text"? - javascript

I am trying to update string on change inside a textarea but it does not work.
Vue component:
<template>
<div>
<textarea :value="text" #change="changed = true" #keyup="changed = true"></textarea>
<div class="buttons">
<button #click.prevent="saveChanges">Save Changes</button>
<button #click.prevent="discardChanges">Discard Changes</button>
</div>
</div>
</template>
<script>
export default {
name: "Editor",
props: ["fileText"],
data() {
return {
changed: false,
text: this.fileText,
sendText: "",
};
},
computed: {
getCurrentText() {
return this.text;
}
},
methods: {
emitToParent() {
this.$parent.$emit("custom-event", this.changed);
},
saveChanges() {
if (this.changed) {
console.log("I changed");
this.sendText = this.getCurrentText;
this.changed = false;
this.$emit("close");
console.log(this.sendText);
} else {
this.$emit("close");
}
},
discardChanges() {
this.text = this.fileText;
this.$emit("close");
},
},
};
</script>
I proceed to make a change inside the text area, then click on the Save Changes button, this.changed is set to true, console.log("I changed"); fires up, but console.log(this.sendText); returns the same, unmodified text.
As a whole, I am trying to edit text inside a file. The above component gets fileText from it's parent component and I would like to send back to the parent a text that has been modified (if it has been modified). Basically updating the text inside a file, if the text changes.
This is a raw version and will change accordinly.

DEMO
Instead of :value="text" try to use v-model="text"
Like this:
<textarea v-model="text" #change="changed = true" #keyup="changed = true"></textarea>

Related

Clear v-model input from method in Vue2

I have a form with two datepickers and a button to clear each one of them as follows:
<template>
<form>
<div>
<datetime id="someDate" v-model="fields.some_date"></datetime>
<button #click.prevent="clearSomeDate()">X</button>
</div>
<div>
<datetime id="anotherDate" v-model="fields.another_date"></datetime>
<button #click.prevent="clearAnotherDate()">X</button>
</div>
</form>
</template>
<script>
export default {
data() {
return {
fields: {
some_date: null,
another_date: null
},
};
},
methods: {
clearSomeDate() {
this.fields.some_date = null;
},
clearAnotherDate() {
this.fields.another_date = null;
},
},
}
</script>
And works pretty well, but it's not so much reusable.
Is there a way to achieve this with a single clearField() function and pass the model as a parameter or something? Should I do my own custom component to make it work?
You could completely get rid of the methods by just doing the assignment directly in the template:
<div>
<datetime id="someDate" v-model="fields.some_date"></datetime>
<button #click.prevent="fields.some_date = null">X</button>
</div>
That way you have the clearing logic directly next to the model.
If you want to make it reusable you could also extract it into a separate component:
<template>
<div>
<datetime v-bind="$attrs" :value="value" v-on="eventHandlers"></datetime>
<button #click.prevent="$emit('input', null)">X</button>
</div>
</template>
<script>
export default {
name: "datetime-with-x",
model: { prop: "value", event: "input" },
props: ["value"],
inheritAttrs: false,
computed: {
eventHandlers() {
return {
...this.$listeners,
input: ev => this.$emit('input', ev)
};
}
}
};
</script>
and then use it in your component like this:
<template>
<form>
<datetime-with-x id="someDate" v-model="fields.some_date" />
<datetime-with-x id="anotherDate" v-model="fields.another_date" />
</form>
</template>
<script>
import DatetimeWithX from "./datetime-with-x";
export default {
name: "your-form",
components: { DatetimeWithX },
data() {
return {
fields: {
some_date: null,
another_date: null
}
};
}
};
</script>
You could pass the field property name as parameter
clearField(name) {
this.fields[name] = null;
}
and call it with argument
<button #click.prevent="clearField('some_date')">X</button>
Though a cleaner approach would be to build another reusable component and bind it with v-model
<div>
<datetime :value="value" #change="$emit('input', $event)"></datetime>
<button #click.prevent="$emit('input', null)">X</button>
</div>

how to bind value of v-for to v-if

I'm working with BootstrapVue. To my problem: I have a v-for in my template in which I have two buttons.
Looping over my v-for my v-if doesn't generate unique IDs and than after clicking one button each button will be triggered (from Open me! to Close me! and other way around).
How can I manage to get each button only triggers itself and doesn't affect the other?
I think I have to use my n of my v-for but I actually don't know how to bind this to a v-if..
Thanks in advance!
<template>
<div>
<div v-for="n in inputs" :key="n.id">
<b-button v-if="hide" #click="open()">Open me!</b-button>
<b-button v-if="!hide" #click="close()">Close me! </b-button>
</div>
<div>
<b-button #click="addInput">Add Input</b-button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
id: null,
inputs: [{
id: 0
}],
hide: true,
};
},
methods: {
open() {
this.hide = false
},
close() {
this.hide = true
},
addInput() {
this.inputs.push({
id: this.id += 1;
})
}
}
};
</script>
Everything seems to look fine. In order to handle each button triggers,
you can maintain an object like so:
<script>
export default {
data() {
return {
inputs: [{id: 0, visible: false}],
};
},
methods: {
open(index) {
this.inputs[index].visible = false
},
close(index) {
this.inputs[index].visible = true
},
addInput() {
this.inputs.push({id: this.inputs.length, visible: false});
}
}
};
</script>
and your template should be like
<template>
<div>
<div v-for="(val, index) in inputs" :key="val.id">
<b-button v-if="val.visible" #click="open(index)">Open me!</b-button>
<b-button v-if="!val.visible" #click="close(index)">Close me! </b-button>
</div>
</div>
</template>
Edit:
You don't need to insert an id every time you create a row, instead can use the key as id. Note that the inputs is an object and not array so that even if you want to delete a row, you can just pass the index and get it removed.
I would create an array of objects. Use a boolean as property to show or hide the clicked item.
var app = new Vue({
el: '#app',
data: {
buttons: []
},
created () {
this.createButtons()
this.addPropertyToButtons()
},
methods: {
createButtons() {
// Let's just create buttons with an id
for (var i = 0; i < 10; i++) {
this.buttons.push({id: i})
}
},
addPropertyToButtons() {
// This method add a new property to buttons AFTER its generated
this.buttons.forEach(button => button.show = true)
},
toggleButton(button) {
if (button.show) {
button.show = false
} else {
button.show = true
}
// We are changing the object after it's been loaded, so we need to update ourselves
app.$forceUpdate();
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<template>
<div>
<div v-for="button in buttons" :key="button.id">
<button v-if="button.show" #click="toggleButton(button)">Open me!</button>
<button v-if="!button.show" #click="toggleButton(button)">Close me! </button>
</div>
</div>
</template>
</div>

Why is the Vue.js input value not updating?

I have a Vue.js text-input component like the following:
<template>
<input
type="text"
:id="name"
:name="name"
v-model="inputValue"
>
</template>
<script>
export default {
props: ['name', 'value'],
data: function () {
return {
inputValue: this.value
};
},
watch: {
inputValue: function () {
eventBus.$emit('inputChanged', {
type: 'text',
name: this.name,
value: this.inputValue
});
}
}
};
</script>
And I am using that text-input in another component as follows:
<ul>
<li v-for="row in rows" :key="row.id">
<text-input :name="row.name" :value="row.value">
</text-input>
</li>
</ul>
Then, within the JS of the component using text-input, I have code like the following for removing li rows:
this.rows = this.rows.filter((row, i) => i !== idx);
The filter method is properly removing the row that has an index of idx from the rows array, and in the parent component, I can confirm that the row is indeed gone, however, if I have, for example, two rows, the first with a value of 1 and the second with a value of 2, and then I delete the first row, even though the remaining row has a value of 2, I am still seeing 1 in the text input.
Why? I don't understand why Vue.js is not updating the value of the text input, even though the value of value is clearly changing from 1 to 2, and I can confirm that in the parent component.
Maybe I'm just not understanding how Vue.js and v-model work, but it seems like the value of the text input should update. Any advice/explanation would be greatly appreciated. Thank you.
You cannot mutate values between components like that.
Here is a sample snippet on how to properly pass values back and forth. You will need to use computed setter/getter. Added a button to change the value and reflect it back to the instance. It works for both directions.
<template>
<div>
<input type="text" :id="name" v-model="inputValue" />
<button #click="inputValue='value2'">click</button>
</div>
</template>
<script>
export default {
props: ['name', 'value'],
computed: {
inputValue: {
get() {
return this.value;
},
set(val) {
this.$emit('updated', val);
}
}
}
}
</script>
Notice that the "#updated" event updates back the local variable with the updated value:
<text-input :name="row.name" :value="row.value" #updated="item=>row.value=item"></text-input>
From your code you are trying to listen to changes.. in v-model data..
// Your Vue components
<template>
<input
type="text"
:id="name"
:name="name"
v-model="inputValue"
>
</template>
<script>
export default {
props: ['name', 'value'],
data: function () {
return {
inputValue: ""
};
},
};
</script>
If You really want to listen for changes..
<ul>
<li v-for="row in rows" :key="row.id">
<text-input #keyup="_keyUp" :name="row.name" :value="row.value">
</text-input>
</li>
</ul>
in your component file
<template>...</template>
<script>
export default {
props: ['name', 'value'],
data: function () {
return {
inputValue: ""
};
},
methods : {
_keyUp : () => {// handle events here}
};
</script>
check here for events on input here
To bind value from props..
get the props value, then assign it to 'inputValue' variable
it will reflect in tthe input element

How correctly transfer my "methods" from component to vuex? Where did I make a mistake?

When you click on the "add" button, you should check whether some text is entered into the input and, if so, then some object should be added with the text entered into the input and then save in the localstorage. When you restart the program, this object should return to the page. It is also possible to delete an object by clicking on the delete button For the component, this code is working.
Here is how it works.
Now I need to transfer everything to vuex. But I can't do this right. When I enter the text in the input in the console, I get the error "Error in v-on handler:" TypeError: e.target is undefined ". And as soon as I remove the focus from the input, the text entered there will disappear. Also, I cannot use v-model as it is not supported by framework 7
How it works now
My code in component
<f7-block strong>
<f7-block-title>Some items</f7-block-title>
<f7-block v-for="(cat, n) in compCats">
<span>{{ cat }}</span>
<f7-button fill color="red" #click="removeCat(n)">Delete Cat</f7-button>
</f7-block>
<f7-list form>
<f7-list-input
:value="compNewCats"
#input="newCatOnInput"
type="text"
placeholder="Заметка"
></f7-list-input>
<f7-button fill color="blue" #click="addCat">Add some item</f7-button>
</f7-list>
</f7-block>
<script>
export default {
computed:{
compCats(){
return this.$store.state.cats;
},
compNewCats(){
return this.$store.state.newCat;
}
},
mounted() {
if (localStorage.getItem('cats')) {
try {
this.cats = JSON.parse(localStorage.getItem('cats'));
} catch(e) {
localStorage.removeItem('cats');
}
}
},
methods: {
addCat(e) {
this.$store.commit('addNewCat');
},
newCatOnInput(e){
this.$store.commit('newCatInput', e.target.value);
},
removeCat(n){
this.$store.comit('removeSomeCat');
}
}
}
</script>
My code in VUEX
export default new Vuex.Store({
state: {
cats:[],
newCat: null
},
mutations: {
addNewCat(state) {
if (!this.newCat) {
return;
}
this.state.cats.push(this.state.newCat);
this.state.newCat = '';
this.saveCats();
},
removeSomeCat(x) {
this.state.cats.splice(x, 1);
this.saveCats();
},
saveCats(state) {
const parsed = JSON.stringify(this.state.cats);
localStorage.setItem('cats', parsed);
},
newCatInput(payload) {
this.newCat = payload;
},
}
}
});

Clearing input in vuejs form

Just completed a todolist tutorial.
When submitting the form the input field doesn't clear.
After trying both:
document.getElementById("todo-field").reset();
document.getElementById("#todo-field").value = "";
The input field properly clears but it also deletes the todo.
It seems to delete the input field before it has time to push the new todo in the todos.text array.
Would love some input guys! Thanks!!
<template>
<form id="todo-field" v-on:submit="submitForm">
<input type="text" v-model="text">
</form>
<ul>
<li v-for="todo in todos">
<input class="toggle" type="checkbox" v-model="todo.completed">
<span :class="{completed: todo.completed}" class="col-md-6">
<label #dblclick="deleteTodo(todo)">
{{todo.text}}
</label>
</span>
</li>
</ul>
<script>
export default {
name: 'todos',
data () {
return {
text: '',
todos: [
{
text:'My Todo One',
completed: false
},
{
text:'My Todo Two',
completed: false
},
{
text:'My Todo Three',
completed: false
}
]// End of array
}
},
methods: {
deleteTodo(todo){
this.todos.splice(this.todos.indexOf(todo),1);
},
submitForm(e){
this.todos.push(
{
text: this.text,
completed: false
}
);
//document.getElementById("todo-field").reset();
document.getElementById("#todo-field").value = "";
// To prevent the form from submitting
e.preventDefault();
}
}
}
</script>
These solutions are good but if you want to go for less work then you can use $refs
<form ref="anyName" #submit="submitForm">
</form>
<script>
methods: {
submitForm(){
// Your form submission
this.$refs.anyName.reset(); // This will clear that form
}
}
</script>
What you need is to set this.text to an empty string in your submitForm function:
submitForm(e){
this.todos.push(
{
text: this.text,
completed: false
}
);
this.text = "";
// To prevent the form from submitting
e.preventDefault();
}
Remember that binding works both ways: The (input) view can update the (string) model, or the model can update the view.
Assuming that you have a form that is huge or simply you do not want to reset each form field one by one, you can reset all the fields of the form by iterating through the fields one by one
var self = this;
Object.keys(this.data.form).forEach(function(key,index) {
self.data.form[key] = '';
});
The above will reset all fields of the given this.data.form object to empty string. Let's say there are one or two fields that you selectively want to set to a specific value in that case inside the above block you can easily put a condition based on field name
if(key === "country")
self.data.form[key] = 'Canada';
else
self.data.form[key] = '';
Or if you want to reset the field based on type and you have boolean and other field types in that case
if(typeof self.data.form[key] === "string")
self.data.form[key] = '';
else if (typeof self.data.form[key] === "boolean")
self.data.form[key] = false;
For more type info see here
A basic vuejs template and script sample would look as follow
<template>
<div>
<form #submit.prevent="onSubmit">
<input type="text" class="input" placeholder="User first name" v-model="data.form.firstName">
<input type="text" class="input" placeholder="User last name" v-model="data.form.lastName">
<input type="text" class="input" placeholder="User phone" v-model="data.form.phone">
<input type="submit" class="button is-info" value="Add">
<input type="button" class="button is-warning" #click="resetForm()" value="Reset Form">
</form>
</div>
</template>
See ow the #submit.prevent="onSubmit" is used in the form element. That would by default, prevent the form submission and call the onSubmit function.
Let's assume we have the following for the above
<script>
export default {
data() {
return {
data: {
form: {
firstName: '',
lastName: '',
phone: ''
}
}
}
},
methods: {
onSubmit: function() {
console.log('Make API request.')
this.resetForm(); //clear form automatically after successful request
},
resetForm() {
console.log('Reseting the form')
var self = this; //you need this because *this* will refer to Object.keys below`
//Iterate through each object field, key is name of the object field`
Object.keys(this.data.form).forEach(function(key,index) {
self.data.form[key] = '';
});
}
}
}
</script>
You can call the resetForm from anywhere and it will reset your form fields.
For reset all field in one form you can use event.target.reset()
const app = new Vue({
el: '#app',
data(){
return{
name : null,
lastname : null,
address : null
}
},
methods: {
submitForm : function(event){
event.preventDefault(),
//process...
event.target.reset()
}
}
});
form input[type=text]{border-radius:5px; padding:6px; border:1px solid #ddd}
form input[type=submit]{border-radius:5px; padding:8px; background:#060; color:#fff; cursor:pointer; border:none}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.6/vue.js"></script>
<div id="app">
<form id="todo-field" v-on:submit="submitForm">
<input type="text" v-model="name"><br><br>
<input type="text" v-model="lastname"><br><br>
<input type="text" v-model="address"><br><br>
<input type="submit" value="Send"><br>
</form>
</div>
Markup
<template lang="pug">
form
input.input(type='text' v-model='formData.firstName')
input.input(type='text' v-model='formData.lastName')
button(#click='resetForm') Reset Form
</template>
Script
<script>
const initFromData = { firstName: '', lastName: '' };
export default {
data() {
return {
formData: Object.assign({}, initFromData),
};
},
methods: {
resetForm() {
// if shallow copy
this.formData = Object.assign({}, initFromData);
// if deep copy
// this.formData = JSON.parse(JSON.stringify(this.initFromData));
},
},
};
</script>
Read the difference between a deep copy and a shallow copy HERE.
I use this
this.$refs['refFormName'].resetFields();
this work fine for me.
This solution is only for components
If we toggle(show/hide) components using booleans then data is also removed.
No need to clean the form fields.
I usually make components and initialize them using booleans.
e.g.
<template>
<button #click="show_create_form = true">Add New Record</button
<create-form v-if="show_create_form" />
</template>
<script>
...
data(){
return{
show_create_form:false //making it false by default
}
},
methods:{
submitForm(){
//...
this.axios.post('/submit-form-url',data,config)
.then((response) => {
this.show_create_form= false; //hide it again after success.
//if you now click on add new record button then it will show you empty form
}).catch((error) => {
//
})
}
}
...
</script>
When use clicks on edit button then this boolean becomes true and after successful submit I change it to false again.
I had a situation where i was working with a custom component and i needed to clear the form data.
But only if the page was in 'create' form state, and if the page was not being used to edit an existing item. So I made a method.
I called this method inside a watcher on custom component file, and not the vue page that uses the custom component. If that makes sense.
The entire form $ref was only available to me on the Base Custom Component.
<!-- Custom component HTML -->
<template>
<v-form ref="form" v-model="valid" #submit.prevent>
<slot v-bind="{ formItem, formState, valid }"></slot>
</v-form>
</template>
watch: {
value() {
// Some other code here
this.clearFormDataIfNotEdit(this)
// Some other code here too
}
}
... some other stuff ....
methods: {
clearFormDataIfNotEdit(objct) {
if (objct.formstate === 'create' && objct.formItem.id === undefined) {
objct.$refs.form.reset()
}
},
}
Basically i checked to see if the form data had an ID, if it did not, and the state was on create, then call the obj.$ref.form.reset() if i did this directly in the watcher, then it would be this.$ref.form.reset() obvs.
But you can only call the $ref from the page which it's referenced.
Which is what i wanted to call out with this answer.
This is how I do it in Vue 3.
html:
<input type="text" v-model="state.name">
js:
import {reactive} from "vue";
const state = reactive({
name: ""
})
axios.post('/contact', state)
.then(res => {
if (res.status == 200) {
state.name = ""
}
})
Response status 200 being a successful submission of the form input. state.name is reactive and will be set to "" if the submission is successful.
if your using vue.js v-form you can simply do like
this.form.reset()
Documentation
Vform - Documentation

Categories