VueJs - preventDefault() on form submission - vue.js

I need to submit a form programmatically, but I need it to preventDefault as well.
Right now I have the following:
submit() {
this.$refs.form.submit()
}
It is working fine, but I cannot prevent default on the submit which in the end, refreshes the page.

Short answer
You can add the .prevent modifier to the #submit (or any other v-on you're using), like this:
<form #submit.prevent="myMethod">
<button type="submit"></button>
</form>
In the case of submitting a form, this will prevent the default behavior of refreshing the page.
Long answer
There are several ways to modify events.
From the Vue 3 docs:
It is a very common need to call event.preventDefault() or
event.stopPropagation() inside event handlers. Although we can do this
easily inside methods, it would be better if the methods can be purely
about data logic rather than having to deal with DOM event details.
To address this problem, Vue provides event modifiers for v-on. Recall
that modifiers are directive postfixes denoted by a dot.
<!-- the click event's propagation will be stopped -->
<a #click.stop="doThis"></a>
<!-- the submit event will no longer reload the page -->
<form #submit.prevent="onSubmit"></form>
<!-- modifiers can be chained -->
<a #click.stop.prevent="doThat"></a>
<!-- just the modifier -->
<form #submit.prevent></form>
<!-- use capture mode when adding the event listener -->
<!-- i.e. an event targeting an inner element is handled here before being handled by that element -->
<div #click.capture="doThis">...</div>
<!-- only trigger handler if event.target is the element itself -->
<!-- i.e. not from a child element -->
<div #click.self="doThat">...</div>
Another option:
Sometimes we also need to access the original DOM event in an inline statement handler. You can pass it into a method using the special $event variable:
<button #click="warn('Form cannot be submitted yet.', $event)">
Submit
</button>
// ...
methods: {
warn: function (message, event) {
// now we have access to the native event
if (event) {
event.preventDefault()
}
alert(message)
}
}
Cheers :)

Didn't quite understand #Armin Ayari's answer, for instance why the code would have to be in the methods object? Anyway in Vue this is what worked for me:
<form ref="form" #submit.prevent="myMethod">
<button type="submit"></button>
</form>
This blocked the page from refreshing and called myMethod instead.
You don't even need the ref. Understood this is an old question, but I found myself here after debugging, and found my form tags were simply mis-placed.

I don't know if I understood your question correctly but you can prevent the default behavior of your form like this:
this.$refs.form.addEventListener("submit", (event) => {
event.preventDefault()
});
Maybe this can help you:
new Vue({
el: '#app',
data: {},
methods: {
submit () {
this.$refs.form.addEventListener('submit', event => {
event.preventDefault()
})
},
alert () {
alert('hello')
}
}
})
<body>
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.17/dist/vue.js"></script>
<div id='app'>
<div class="form-wrapper" #click='submit'>
<form ref='form' #submit='alert'>
<input type="text">
<button type='submit'>Submit</button>
</form>
</div>
</div>
</body>

I think it's dona help!!
<form method="POST" action="http::localhost:8080/" #submit.prevent="submit_login($event)">
// enter yours inputs here
</form>
submit_login(e) {
if (true) {
e.target.submit();
},
},

After some proper investigation with not a single answer here being related to the original question.
I have found your solution, however it isn't VueJS specific, referencing this article: Javascript e.preventDefault(); not working on submit()
Answer
Your programmatic way to execute submit this.$refs.form.submit() isn't correct if you want to properly preventDefault() or even run other functions.
You need to run this.$refs.form.requestSubmit(), this replicates the functionality as if you would've had a child <button> run the clicked event.

First, don't use preventDefault method. I will illustrate this problem on jQuery:
$('#myForm').on('submit', function (event) {
// step 1.
// stop current action, prevent submitting
event.preventDefault()
// step 2.
// validate inputs
// some validation code
// step 3.
// everything ok, submit it
this.submit()
})
Where is problem with this code? When you submit this form programatically in step 3., it will be again captured and will stop at step 1. again. So, you will be never able to submit this form. Solution:
$('#myForm').on('submit', function (event) {
// step 1.
// validate inputs
// some validation code
// this code will be always executed
// before this form will be submitted
// step 2.
// then do something like this
// continue submitting form and exit
// this callback with returning true
if (inputsAre === 'ok') return true
// if inputs are not ok, program continues
// with following line, which ends this
// callback with false and form will be not submitted
return false
})
I hope you got the point. So, I think what you need is not the preventDefault method, but return true or return false in your doSomething method called on #submit event.

Related

Vue JS - Trigger Method if input lost focus

Quick question about Vue JS.
On my website, I have got a shopping cart and the user can enter any quantity. I am saving this quantity to the database table when he enters it...
issue is, the input field keeps firing save method every single digit of user types. For example, if the user types 123, the save method called 3 times. 1 and 12 and 123
I just want to call this save method when this input loses the focus, so I can save only once, not every single digit.
Component
Vue.component('product-counter', {
props: ['quantity'],
data: function () {
return {
count: this.quantity
}
},
template: `
<div class="input-group ">
<input v-bind:value="quantity" v-on:input.focus="$emit('input', $event.target.value)" type="text" class="form-control col-2">
</div>
`
})
Component Call
<product-counter
v-bind:productcode="item.productCode"
v-bind:quantity="item.quan"
v-on:input="item.quan=updateItemQuantity($event,item.productCode)"
v-on:increment-quantity="item.quan=updateItemQuantity(item.quan,item.productCode)"
v-on:decrement-quantity="item.quan=updateItemQuantity(item.quan,item.productCode)"></product-counter>
Vue: Method
"updateItemQuantity": function (totalquantity, pcode) {
if (totalquantity != '') {
... Update Database...
}
}
You're listening to the input event, which is triggered every time the value of the input changes, so every time a character is typed or removed.
Instead, you should listen to the blur event, which only fires when an input loses focus.
You can pass this along through your component, the same way you pass through the input event.
TLDR: Couple UpdateItemQuantity to v-on:blur instead of v-on:input, and make sure to $emit the blur event from your products-counter component.
Tip: Separate the client-side state (item.quan) and your server-side 'state' (your database) into two different methods. You want the value to reflect what the user is typing in real-time (input), which conflicts with what you want for updating the database (not real-time, only on blur). Otherwise you may get cases where users can't see what they type, as item.quan is never updated.
I think you just need to use #change instead of #input
It could be that you should use blur event.
Vue:
new Vue({
el: "#app",
data: {
quantity: ''
},
methods: {
printConsole: function () {
console.log('blured');
}
}
})
html:
<div id='app'>
<input v-bind:value="quantity" v-on:blur="printConsole" type="text" class="form-control col-2">
</div>
see jsfiddle for reference: https://jsfiddle.net/erezka/h8g62xfr/11/
blur will emit your change only after you focus out of the input

v-model not always updating in Vue

Short question
The v-model which binds a string to an input field won't update in some cases.
Example
I am using Vue within a Laravel application. This is the main component which contains two other components:
<template>
<div>
<select-component
:items="items"
#selectedItem="updateSelectedItems"
/>
<basket-component
:selectedItems="selectedItems"
#clickedConfirm="confirm"
#clickedStopAll="stopAll"
/>
<form ref="chosenItemsForm" method="post">
<!-- Slot for CSRF token-->
<slot name="csrf-token"></slot>
<input type="text" name="chosenItems" v-model="selectedItemsPipedList" />
</form>
</div>
</template>
<script>
export default {
props: ["items"],
data: function() {
return {
selectedItems: [],
selectedItemsPipedList: ""
};
},
methods: {
updateSelectedItems: function(data) {
this.selectedItems = data;
this.selectedItemsPipedList = this.selectedItems
.map(item => item.id)
.join("|");
},
confirm() {
this.$refs.chosenItemsForm.submit();
},
stopAll() {
this.updateSelectedItems([]);
this.confirm();
}
}
};
</script>
The method updateSelectedItems is called from the select-component and it works fine. In the end, the selectedItemsPipedList contains the selected items from the select-component, which looks like "1|2|3" and this value is bound to the input field in the chosenItemsForm. When the method confirm is called from the basket-component, this form is posted to the Laravel backend and the post request contains the chosen items as piped list. So far, so good.
The method stopAll is called from the basket-component and it will remove all the selected items from the array. Therefore it will call the method updateSelectedItems with an empty array, which will clear the selectedItems array and then clear the selectedItemsPipedList. After that, confirm is called which will post the form again. But, the post value still contains the selected items (e.g. '1|2|3'), instead of "". It looks like the v-model in my form is not updated, which is strange because it does work when selecting items. Why is it working when adding items, and doesn't when removing all items?
I believe you have a timing issue here. The value of the properties haven't been propagated to the DOM yet, so the form submission is incorrect. Try this instead:
stopAll() {
this.updateSelectedItems([]);
//NextTick waits until after the next round of UI updates to execute the callback.
this.$nextTick(function() {this.confirm()});
}

Callback after list leave animation and DOM update - VueJS

To do something after the DOM has been updated by Vue, you use the $nextTick binding.
To do something after a css transition has completed, you can use transitionend event.
I have a dynamic list in which things are added and removed by user actions. When removed, there is a CSS animation and then I need to check the state of the DOM immediately after the element is gone.
I was thinking that the $nextTick after the transitionend would be the state of the DOM immediately after the list item is removed, but it is not.
I need to do something after the transition has ended and the element from a list has been removed from the DOM.
Right now I have:
<transition-group class="message-bus" tag="ul" name="message-bus">
<li v-for="message in messages" v-bind:key="message.id" :ref="message.id">
<div>{{message.text}}</div>
<a class="dismiss-message" #click="dismissMessage(message)">×</a>
</li>
</transition-group>
dismissMessage(message){
const vm = this;
this.$refs[message.id][0].addEventListener("transitionend", function(){
vm.$nextTick(function(){
//This is called, but appears to be called before the element is actually removed from the DOM
//I need to query the DOM immediately after this element is removed
});
});
this.messages.splice(this.messages.indexOf(message), 1);
}
In the mounted function, I have added a MutationObserver that appears to be working as needed. I'll put this here as an answer as it does technically work and may be helpful to others, but I'm still interested in a better answer if Vue has something built in for this.
mounted(){
const vm = this;
const listItemRemoved = new MutationObserver(function(e){
if (e[0].removedNodes.length){
console.log("Removed");
}
});
listItemRemoved.observe(this.$el, {childList: true});
}
Perhaps you could use a custom directive. Perform the actions you need inside the unbind hook ..
created() {
this.vm = this
},
directives: {
foo: {
unbind(el, binding) {
// Here you can perform the actions you need.
// You can access the Vue instance using binding.value (eg: binding.value.$el)
}
}
},
And in your template ..
<transition-group class="message-bus" tag="ul" name="message-bus">
<li v-for="message in messages" v-bind:key="message.id" :ref="message.id" v-foo="vm">
<div>{{message.text}}</div>
<a class="dismiss-message" #click="dismissMessage(message)">×</a>
</li>
</transition-group>

When does binding to ref attribute become valid in Aurelia?

This is a follow up to this question: Access a DOM element in Aurelia
Is there a hook in the Screen Activation Lifecycle which allows me to run code after ref bindings have been set up? Currently it seems like there is a period of time after the activate hook is called when the ref bindings are not set up yet and then at some point they get activated. I tested this by adding a <div ref="myDiv"></div> to near the bottom of welcome.html in a cloned version of the latest (v0.13.0) skeleton-navigation repo and testing the existence of the reference in the view-model like this:
export class Welcome{
heading = 'Welcome to the Aurelia Navigation App!';
firstName = 'John';
lastName = 'Doe';
testMyDiv() {
console.log("Getting my div")
console.log(this.myDiv)
}
get fullName(){
this.testMyDiv()
return `${this.firstName} ${this.lastName}`;
}
welcome(){
alert(`Welcome, ${this.fullName}!`);
}
}
A snippet of the bottom of the template...
<button type="submit" class="btn btn-default">Submit</button>
</form>
<div ref="myDiv"></div>
</section>
</template>
This is a snapshot of what I see in the console...
welcome.js:10 Getting my div
welcome.js:11 undefined
welcome.js:10 Getting my div
welcome.js:11 undefined
welcome.js:10 Getting my div
welcome.js:11 <div ref=​"myDiv" class=​"au-target">​</div>​
welcome.js:10 Getting my div
welcome.js:11 <div ref=​"myDiv" class=​"au-target">​</div>​
(continues)
The print outs like this goes on indefinitely. You can see that fullName() is being called regularly to update the screen if the name changes (I assume this is the dirty checking)... but you can see that at the beginning there is a period when the referenced div is NOT valid as a property of the view-model, and then it IS valid. Can someone explain this? Is there a way to hook into the view-model after the ref becomes valid?
In general, bindings are processed and available after the bind callback. However, in this case since you need to access the DOM element, you will need the ViewModel to be bound and attached to the view, so use the attached callback.
class ViewModel {
bind() {
this.refItem == undefined; // true
}
attached() {
this.refItem == undefined; // false
}
}
As you noted in the comments, more information on the activator callbacks is available here: http://aurelia.io/docs.html#extending-html

How to control order of view activation in Durandal

I am building an application with Durandal 2.0.
My shell view looks like this:
<div>
<header id="nav" data-bind="compose: 'viewmodels/nav', activate: false">
</header>
<section id="content" class="main container-fluid" data-bind="router: { transition: 'entrance' }, activate: false" style="overflow: auto">
</section>
<footer>
<!--ko compose: {view: 'footer'} --><!--/ko-->
</footer>
</div>
In the nav section, I want to have my tabs and a drop-down list of users (both of which are retrieved from a web service). Selecting a user from the dropdown will navigate to a new URL which will update the content section. (the route looks something like localhost/#user1/tab2).
Problem: I need to know the selected user from the nav section before I can retrieve the data for the content section, but the content section is activating before I have retrieved the users.
This is really only an issue for the initial page load, since the user list is only retrieved once.
Is there a way to tell the content section to wait until the nav section is done loading?
Is there a better way to go about this than what I'm doing?
The nav activate function looks like this:
function activate(context) {
return dataservice.getUsers().then(function () {
//do stuff
});
});
This activate function gets called first, and dataservice.getUsers() is called, but then the activate function of the content module gets called before the "do stuff" part happens (and before the data from the getUsers call is returned in the dataservice). Maybe there's a problem with my promises?
Edit
I've put together a dFiddle with some actual code that shows what I'm talking about: http://jerrade.github.io/dFiddle-2.0/#test/dashboard
The code is here: https://github.com/jerrade/dFiddle-2.0
nav.js
function activate(context) {
console.log('Nav View Activated');
if (vm.impersonateUsername == undefined)
vm.impersonateUsername = getUsernameFromWindowLocation();
return dataservice.getPageDetailForEmployee(vm.loggedInUsername, vm).then(function () {
console.log("Page details retrieved");
// I want to do something here before the dashboard activates.
});
}
dashboard.js
function activate(username) {
console.log('Dashboard View Activated');
//vm.username = nav.selectedImpersonateEmployee().Username;
return dataservice.getDashboard(nav.impersonateUsername, dashboard);
}
Open the page and watch the console. You'll see (among other things)
Nav View Activated
Dashboard View Activated
Page details retrieved
What I really want is for the Page details to be retrieved before the Dashboard view activates. I've actually rejiggered things so that this isn't currently a problem anymore, but it may crop again down the road.
It doesn't seem like what I'm trying to do should be this complicated. Unless I'm pounding a square peg into a round hole here?
The easiest thing to do would be to add an observable called something like isUserLoaded on your view model. Then, apply an if binding around the content section:
<div>
<header id="nav" data-bind="compose: 'viewmodels/nav', activate: false">
</header>
<!-- ko if: isUserLoaded -->
<section id="content" class="main container-fluid" data-bind="router: { transition: 'entrance' }, activate: false" style="overflow: auto">
</section>
<!-- /ko -->
<footer>
<!--ko compose: {view: 'footer'} --><!--/ko-->
</footer>
</div>
Once you've loaded the user, you can update the observable to true and your content binding should fire.
Edit
If that does't work it sounds like your router must still be resolving the content view model (which may point to a routing problem? Hard to say without seeing your entire solution).
Anyway, if its not a routing issue, then you could employ a solution where your content model returns an unresolved promise from its activate method; then resolve that promise when the user is loaded. For example, something along these lines:
userModel.js:
define([], function () {
// single instance to track the loaded user
var userModel = {
selectedUser: ko.observable()
};
return userModel;
});
navModel.js:
define(["dataService", "userModel"], function(dataService, userModel) {
// view model for the nav bar
var navModel = {
// model definition
};
// load the user
navModel.activate = function() {
return dataService.getUsers().then(function(response) {
// Push the user to the userModel
userModel.selectedUser(response.user);
});
};
return navModel;
});
contentModel.js:
define(["userModel"], function (userModel) {
var contentModel = {
// model definition
};
contentModel.activate = function () {
// Prevent activation until user is resolved
var deferred = $.Deferred();
var subscription = userModel.selectedUser.subscribe(function (user) {
if (!user)
return;
subscription.dispose();
deferred.resolve();
});
return deferred.promise();
};
return contentModel;
});
If you don't want to use an observable for the userModel.selectedUser property, you could instead use another deferred, or fire an event when the user is loaded, etc etc. Whatever floats your boat!