Conditionally add #submit.prevent to a form in Vue.js - vue.js

I'm trying to conditionally prevent HTML form submission using #submit.prevent. I have a generic form model that looks like this (minimized a bit):
<v-form v-model="valid">
<div>
<div v-for="(field, i) in fieldConfig" :key="`model-form-`+i">
<fe-label v-if="field.label">{{field.label}}</fe-label>
<component
:is="field.component"
v-bind="field.attrs"
v-model="item[field.field]"
/>
</div>
</div>
</v-form>
Called from a parent:
<model-form
idProperty="id"
#done="finishRename"
model="myModel"
:fieldConfig="fieldConfig"
:htmlSubmit="false"
/>
I've seen some similar things in my searching but nothing quite right, and I'm pretty raw when it comes to Vue. Any help is appreciated. Thanks!

Don't use submit.prevent in this scenario. Just use submit, and in your function, conditionally prevent.
onSubmit(e) {
if(condition) e.preventDefault();
}

As Andy pointed out, #submit.prevent should not be used for conditional form submission because the .prevent modifier automatically calls Event.preventDefault().
To conditionally call it, you'd have to remove the .prevent modifier, and evaluate the htmlSubmit flag beforehand within the event handler:
<v-form #submit="onSubmit" action="https://example.com">
export default {
methods: {
onSubmit(e) {
if (!this.htmlSubmit) {
e.preventDefault() // don't perform submit action (i.e., `<form>.action`)
}
}
}
}

Related

How to bind a click event to a button when component is mounted vue js?

I am using a third party app (vue slick carousel) and I need to bind a click event to below button
<button type="button" data-role="none" class="slick-arrow slick-next" style="display: block;">
<svg...</svg>
</button>
So everytime I click this button a function gets triggered. This is what I tried in mounted and created life cycle, but didn't bind click event to it.
// console.log(document.querySelector(".slick-next")); // this returns element stated above
document.querySelector(".slick-next").addEventListener("click", () => {
console.log("Works");
});
I tried using setAttribute hoped it works, but it didn't work either.
Any help is appreciated. Thank you in advance.
I suspect they're probably using stopPropagation on the button click inside the component then.
There might be another way around this depending on your needs:
<VueSlickCarousel #beforeChange="checkChange">
...
</VueSlickCarousel>
methods: {
checkChange(oldSlideIndex, newSlideIndex) {
if (newSlideIndex > oldSlideIndex) {
console.log("Next button clicked");
}
}
}
Another option might be to use the <template #nextArrow="{ currentSlide, slideCount }"></template> slot inside the Carousel tag and use your own button. You'd probably have to implement your own logic for setting the next slide if you went this route.
You must to use the events handling of vuejs. Vuejs have many events for multiple cases.
For example in your case, you can use the event v-bind:click or simply #click. In documentation of vue slick, show this examples, wich is essentially the same.
I attach an code snippet:
<template>
<VueSlickCarousel ref="carousel">
<div><h3>1</h3></div>
/*...*/
</VueSlickCarousel>
<button #click="showNext">show me the next</button>
</template>
<script>
export default {
methods: {
showNext() {
this.$refs.carousel.next()
},
},
}
</script>

Vue.js this.$refs empty due to v-if

I have a simple Vue component that displays an address, but converts into a form to edit the address if the user clicks a button. The address field is an autocomplete using Google Maps API. Because the field is hidden (actually nonexistent) half the time, I have to re-instantiate the autocomplete each time the field is shown.
<template>
<div>
<div v-if="editing">
<div><input ref="autocomplete" v-model="address"></div>
<button #click="save">Save</button>
</div>
<div v-else>
<p>{{ address }}</p>
<button #click="edit">Edit</button>
</div>
</div>
</template>
<script>
export default {
data() {
editing: false,
address: ""
},
methods: {
edit() {
this.editing = true;
this.initAutocomplete();
},
save() {
this.editing = false;
}
initAutocomplete() {
this.autocomplete = new google.maps.places.Autocomplete(this.$refs.autocomplete, {});
}
},
mounted() {
this.initAutocomplete();
}
}
I was getting errors that the autocomplete reference was not a valid HTMLInputElement, and when I did console.log(this.$refs) it only produced {} even though the input field was clearly present on screen. I then realized it was trying to reference a nonexistent field, so I then tried to confine the autocomplete init to only when the input field should be visible via v-if. Even with this, initAutocomplete() is still giving errors trying to reference a nonexistent field.
How can I ensure that the reference exists first?
Maybe a solution would be to use $nextTick which will wait for your DOM to rerender.
So your code would look like :
edit() {
this.editing = true;
this.$nextTick(() => { this.initAutocomplete(); });
},
Moreover if you try to use your this.initAutocomplete(); during mounting it cannot work since the $refs.autocomplete is not existing yet but I'm not sure you need it since your v-model is already empty.
I think it's because your "refs" is plural
<input refs="autocomplete" v-model="address">
It should be:
<input ref="autocomplete" v-model="address">

2 way binding is not working properly in vuejs

I have defined a vuejs component this way:
<template>
<form #submit.prevent="submit">
<textarea id="content" cols="30" rows="10" v-model="content">{{ content }}</textarea>
<button class="btn btn-lg btn-success" type="submit" #click="send()">
Send content
</button>
</form>
</template>
<script>
export default {
data() {
return {
content: '// Initial content'
}
},
methods: {
send() {
console.log('Content', this.content);
},
submit() {
return false;
}
},
mounted() {
console.log('Template init ', this.content);
}
}
</script>
When I click on send, the send method outputs the content of the textarea as expected. But when I change the content of the textarea with jquery:
$('#content').val(content);
and hit send, it doesn't update content in the template. "Send" outputs the old value.
can somebody please explain to me what's wrong here?
v-model is listening for an input event to trigger changing the value of its bound variable.
From the vue.js documentation:
v-model internally uses different properties and emits different
events for different input elements:
text and textarea elements use value property and input event;
checkboxes and radiobuttons use checked property and change event;
select fields use value as a prop and change as an event.
Using the JQuery val() method to select the element and change the value does not trigger an event that v-model is listening for, so the value of your bound variable does not change/update.
If you absolutely have to use JQuery to change the content, you could manually trigger an event that might also trigger the v-model binding to update:
$('#content').val(content).trigger('input');

What is the correct way to retrieve data from 2 or more identical components?

Evening. I've created a button which adds a component that has an input field inside. I might need to press that button few times so there would be 2-3 input fields that appear. Whenever I type the text I would like to send a request from the parent component but I don't know how to retrieve the data from every child component that has been created. Is this the time to start using vuex (never used it)?
ParentComponent.vue
<template>
<div>
<button class="btn btn-success" #click="addStep">Add step</button>
<div v-for="i in count">
<recipe-step v-bind:step-number="i"></recipe-step>
</div>
</div>
</template>
<script>
export default {
data() {
return {
count: 0
}
},
methods: {
addStep() {
this.count += 1;
}
}
}
</script>
StepComponent.vue
<template>
<div>
<div class="from-group">
<label for="step-input"></label>
<input id="step-input" v-model="text" type="text">
</div>
</div>
</template>
<script>
export default {
props: {
stepNumber: {
type: Number,
required: true
}
},
data() {
return {
step: this.stepNumber,
text: ""
}
}
}
</script>
No, you really don't need Vuex yet. As long as you are still dealing with parent-child-component communication, you should be fine. Vuex comes into play when components, spread across the hole component hierarchy, need to exchange information.
Now, you should do something like this:
Don't store the text in the child component. When the input changes, send a Custom Event right to the parent component. Note that
<input v-model="text">
is only syntax sugar for
<input :value="text" #input="text = $event">
Both have the same effect. That's way you can send the input event up to the parent, like this:
<input #input="$emit('input', $event)">
Add another prop to your child component called value which should replace text.
And now you can use v-model in the parent component:
<recipe-step v-model="text">
To store multiple values, just use an array in your data properties.
<recipe-step v-model="textArray[i]">
Vuex can help you on that, however if all you want is to get the input text value back to the parent with the minimum effort you can create a prop called value in the children and then pass it as v-model in the parent.
Since you have a v-for you could make it iterate over a list instead a counter and then pass some prop inside each item as v-model

Vue: computed vs data(), for vuex bindings?

(Note: I not am not asking about how to use watch).
I have this form template and want to bind it to some variables, for example objectvalue3, that are tracked in a Vuex store (note: different forms may be present on one page, so props.formname tells the form where to look).
<template>
<div>
Tracking formname_:{{formname_}}:
<form method="post" autocomplete="off">
<input type="text" name="objectvalue3" :value="objectvalue3" />
<input type="submit" class="btn btn-primary btn-success" value="Track" #click.prevent="write">
</form>
</div>
</template>
....
props: {
formname: {
type: String,
default: "main"
}
},
Tracking it in data does not work - i.e. the form does not get updated - it just keeps the value the vuex was initialized to :
data: function() {
return {
formname_: this.formname
,objectvalue3: this.$store.state.tracker[this.formname].objectvalue3
},
But using computed works.
computed: {
objectvalue3: function() {
return this.$store.state.tracker[this.formname].objectvalue3
}
I know I have to use computed when I need to do calculations. But there is not real calculation going on here. Unless, could it be the hash lookup via this.formname which tells the form which tracker attribute to look at that is causing a straight data to fail? Is this specific to vuex?
Try this instead:
data: function() {
return {
formname_: this.formname,
tracker: this.$store.state.tracker[this.formname]
},
Then:
<input type="text" name="objectvalue3" :value="tracker.objectvalue3" />
This should work because the data's tracker object is pointing to the one in the store, then whenever the value changes there, it'll also change in the tracker object.
computed works because it listens to changes in the variables used within it, while data doesn't because it applies the first value when executed and doesn't track changes.