Enabling and Disabling Submit button if editable form content changes with Vue3 - javascript

Below is a runnable example, what the example is trying to emulate is a form where an address is editable.
The save button should be disabled when the form has not been changed since there is no reason to submit it in the first place if nothing was changed, if however a value changes, the save button should be enabled.
What would be a good way to do this in vue3, and one thing I wanted to add is that in the real world example I have quite a large form and each input is listed similar to below. I thought about using a watcher but I am confused how to handle this since I don't want to watch each individual value.
If I grouped all the data values into one form data object, then maybe I could use a deep watcher but I know that's also inefficient.
One other detail is when the user sees the form, the first name is john and the save button is disabled, if the user changed john to johnn the button would not be disabled anymore, but if the value was changed back from johnn to john the button should again be disabled since the form is back to it's initial state.
One way I thought about doing this is creating an initial value for each form input with v-model, for example:
Vue.createApp({
data() {
first_name: 'john',
initial_first_name: 'john'
}
})
But if it is quite a large form with many fields I am struggling to find the most efficient way to implement something like this because I have never done it before.
<!DOCTYPE html>
<html lang="en">
<head>
<title>Home</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<script src="https://unpkg.com/vue#3/dist/vue.global.js" defer></script>
<style>
.submit-btn {
color: #fff;
background: #000;
padding: 24px;
}
.submit-btn[disabled] {
color: #fff;
background: #999;
}
</style>
</head>
<body>
<div id="app">
<form #submit="onSubmit" id="test-form">
<label for="first_name">First Name</label>
<input id="first_name"
type="text"
v-model="first_name"
>
<label for="email">Email</label>
<input id="email"
type="email"
v-model="email"
>
<input type="submit"
value="save"
class="submit-btn"
:disabled=""
>
</form>
</div>
<script type="module">
Vue.createApp({
name: 'form_app',
data() {
return {
first_name: 'john',
email: 'john#gmail.com'
}
},
methods: {
onSubmit(e) {
e.preventDefault()
console.log('form submitted')
}
}
}).mount('#app')
</script>
</body>
</html>

You can create a reactive object with the form data using the reactive function, and then use a computed property to determine if the form has changed.
For example:
<template>
<form>
<label for="first_name">First Name</label>
<input id="first_name" type="text" v-model="form.first_name">
<button type="submit" :disabled="formUnchanged">Save</button>
</form>
</template>
<script>
import { reactive, computed } from 'vue';
export default {
setup() {
const form = reactive({
first_name: 'John',
});
const initialForm = JSON.stringify(form);
const formUnchanged = computed(() => {
return JSON.stringify(form) === initialForm;
});
return { form, formUnchanged };
},
};
</script>
You can use reactive to create a reactive form object with the form data, and computed to create a formUnchanged property that checks if the form object has changed from its initial state.

Related

Grab template form input in Vue 3 class components (like done in Angular)

I started vue 3 today, and opted for the class-based approach in the cli. I'm from the Angular background, so forgive me for thinking like Angular. Every example I see (even in the docs) is still using the Vue({...}) thing, however, I wanna do something like this (still thinking Angular-ish)
In angular, I can do this
<form #formData="ngForm" (ngSubmit)="onSubmit(formData.value)">
<input (ngModel)="name" name="name" placeholder="name">
</form>
Then in component
...
export class AppComponent {
public name!: string;
onSubmit(formData: string) {
console.log(formData)
}
}
What would be the vue 3 class components approach like the above?
I currently have this in vue 3
export default class Welcome extends Vue {
name!: string;
onSubmit(formData: any) {
console.log(formData)
}
}
<template>
<div>
<form #submit.prevent="onSubmit">
<p>
<label for="name">Name</label> <br>
<input type="text" placeholder="name" id="name" name="name" v-model="name"/>
</p>
<button type="submit">Send</button>
</form>
</div>
</template>
What changes do I need to do to the above to connect the form in template to the component?
Without a third party library there isn't the same type of functionality that angular provides. Angular is doing a bunch of additional things to enhance the form object for validation and value tracking, and Vue natively does not do that. However you could instead put your data properties in an object to group them together. That way when you need to access them in something like the submit event to perhaps send all the values to an API, you can simply refer to that object instead of having to handle/build each property separately:
Class:
export class AppComponent {
// create object with bound form properties
public values: { name: string; } = { name: '' };
onSubmit() {
console.log(this.values); // { name: '' }
// axios.post('/api', this.values).then(res => console.log(res.data));
}
}
Template:
<template>
<div>
<form v-on:submit.prevent="onSubmit">
<p>
<label for="name">Name</label> <br>
<input type="text" placeholder="name" id="name" name="name" v-model="values.name"/>
</p>
<button type="submit">Send</button>
</form>
</div>
</template>
That being said, if you do need advanced form features like validation, sanitization, and similar there are plenty of libraries that do it well.
Hopefully that helps!
The native FormData constructor takes a <form> element, which creates the values of the form for all named inputs (<input>s or <textarea>s with name attribute).
So you could update your onSubmit method to retrieve the form data values:
export default class Welcome extends Vue {
name!: string;
onSubmit(formData: any) {
const form = e.target
const formData = new FormData(form)
}
}
Example using the Options API:
<template>
<div>
<form #submit.prevent="onSubmit">
<p>
<label for="name">Name</label> <br>
<input type="text" placeholder="name" id="name" name="name" v-model="name"/>
</p>
<button type="submit">Send</button>
</form>
</div>
</template>
<script>
export default {
methods: {
onSubmit(e) {
const form = e.target
const formData = new FormData(form)
console.log({ formData: Array.from(formData.entries()) })
}
}
}
</script>
demo

How do I send data from a WYSIWYG to a database (Vue.js)

I just started using the Vue2Editor with the intention to replace the many forms that I use to send text and image data to my Firebase database.
My problem is that I cannot get it to add the data entered in the editor.
When using forms, I would just attach an event handler to the form itself and make a function that allowed the transfer.
Example:
<form #submit.prevent="addText">
<label for="fname">First name:</label>
<input type="text" id="fname" name="fname" v-model="fname">
</form>
<button type="submit" variant="success">Save</button>
But when using the Vue2Editor, I do not get any form tags.
I just get a "vue-editor" tag. I tried adding the event handler inside this tag, but nothing happens.
I don't get any errors, but the data is not transferred to the database upon submitting it.
This is the code:
<template>
<div class="container">
<div class="text_editor">
<h2>Add new content</h2>
<vue-editor #submit.prevent="addText" v-model="textblock" />
<button type="submit" class="textblock_btn" variant="success">Save</button>
</div>
</div>
</template>
<script>
import db from '#/firebase/init'
import Vue from "vue";
import Vue2Editor from "vue2-editor";
Vue.use(Vue2Editor);
export default {
name: 'textblock',
data () {
return {
textblock: null
}
},
methods: {
addText(){
db.collection('textblock').add({
textblock: this.textblock
}).then(() => {
this.$router.push({ name: 'Index' })
}).catch(err => {
console.log(err)
})
}
}
}
</script>
You can still wrap the component in a form as the WYSIWYG editor's data is bound to the v-model property.
<form #submit.prevent="addText">
<div class="text_editor">
<h2>Add new content</h2>
<vue-editor v-model="textblock" />
<button type="submit" class="textblock_btn" variant="success">Save</button>
</div>
</form>
Within the addText method you now have this.textblock with the appropriate data on form submission.

Clear a single form field in HTML

I am creating a simple HTML login page, but if I enter data into the fields it stays there when I refresh the page. I have tried
function pageInit(ID) {
this.browserbot.getCurrentWindow().document.getElementById(ID).value = '';
}
but this doesn't do anything (I placed it into onLoad on the inputs of the login.)
HTML:
`
<title>Login</title>
</head>
<body>
<form>
<fieldset>
<legend><h3>Please Login:</h3></legend>
<input type="text" placeholder="Username" name="userId" id="userId" onLoad="pageInit('userId');"><br>
<input type="password" placeholder="Password" name="passwd" id="passwd" onLoad="pageInit('passwd');"><br>
</fieldset>
</form>
</body>
CSS:
<style>
html {
font-family: sans-serif;
text-align: center;
}
a {
font-weight: normal;
}
a:hover {
font-weight: bold;
}
#userId, #passwd {
width: 30%;
height: 40px;
text-align: center;
}
</style>
JS:
<script>
function pageInit(ID) {
this.browserbot.getCurrentWindow().document.getElementById(ID).value = '';
}
</script>
As far as I can tell, the previous answers to not cover the full extent of the question. The original question requests a function to be called to clear the field. However, I'm going to address this in several different ways.
This can be achieved with no JavaScript at all, but simply setting the value attribute as below:
<input type="text" placeholder="Username" name="userId" id="userId" value="" />
<input type="password" placeholder="Password" name="passwd" id="passwd" value="" />
The above will ensure that the fields are clear when the page is loaded, but using only HTML. To do this via JavaScript, multiple things have to be taken into consideration. First, a function should be defined, which needs to be called when the page is loaded.
function clearValue(id) {
document.getElementById(id).value = "";
}
This will simply set the value to blank. However, this gets us back to the original issue. Setting onload for each element does not work, instead we must use window.onload.
window.onload = function() {
clearValue("userID");
clearValue("passwd");
}
This will clear each value one-by-one. However, there is an even better way to do this. JavaScript has built-in functions that make it easy to clear the entire form, or access the elements of the form by their name, even if they are the child of another element within the form. However, keep in mind that only valid input (includes textarea, etc...) fields can be accessed in this way.
So, assuming that the form's ID is myform, this would clear the entire form, no matter how many fields:
document.getElementById("myform").reset();
It's that simple. Using the form element, you can also access the fields by name, as mentioned above.
var f = document.getElementById("myform").elements;
f["userId"].value = "";
f["passwd"].value = "";
Using the above code makes it much quicker, especially if you have more fields.
Putting the JS together, it might look like this:
window.onload = function() {
// using function
clearValue("userID");
clearValue("passwd");
// or, reset entire form
document.getElementById("myform").reset();
// or, clear each field one-by-one
var f = document.getElementById("myform").elements;
f["userId"].value = "";
f["passwd"].value = "";
}
May be it will help you.
<input type="text" value="initial" id="field">
<button id="reset">reset</button>
<script type="text/javascript">
document.getElementById('reset').onclick= function() {
var field= document.getElementById('field');
field.value= field.defaultValue;
};
</script>
Set the input value to " " - in other words, nothing.
This way, the value will be cleared when the page loads.
Implment this like so:
<input value="">
If you'd rather use JS, add this to your onload event:
window.onload = myOnloadFunc;
function myOnloadFunc() {
document.getElementById('userId').value = ''
}

AngularJS: Target a form with Controller As syntax in an object

Note: I did look around here on SO for solutions, yet no one had the additional issue of the function being in an object.
I have a form in my Angular JS app:
<div ng-app="plunker">
<div ng-controller="PMTController as pmt">
<form name="myForm">
<div class="form-group">
<input type="text" class="form-control" />
</div>
<button class="btn btn-primary" ng-click="pmt.search.resetSearchForm()">Reset</button>
</form>
</div>
</div>
Further, I have a controller with an object:
app.controller('PMTController', function($log) {
var _this = this;
_this.search = {
resetSearchForm: function () {
$log.debug('test');
// how to target the form?
}
};
})
My ng-click works, as the log.debug works. But no amount of tweaking to target the form so that I can reset the entire thing (empty all the fields) works.
I can do $window.myForm.reset(); but how could I do this from angular?
Note please my main issue/question is how to correctly target the form from inside that resetSearchForm function in the search object.
Note I tried changing the form name to pmt.myForm or pmt.search.myForm to no avail.
I tried $setPristine and $setUntouched() but they don't seem to clear the fields.
I know I can assign a model and assign it to all the form controls, but this is for a prototype so I'd rather do a simple reset.
I made a pen: https://codepen.io/smlombardi/pen/YWOPPq?editors=1011#0
Here is my take on your codepen that will hopefully resolve the issue:
https://codepen.io/watsoncn/pen/YWOXqZ?editors=1011
Explanation:
Angular's documentation provides an example of a "Form Reset" button, but you can apply the same logic towards resetting after submission:
Documentation:https://docs.angularjs.org/guide/forms
with a plunker:
Live Example:https://plnkr.co/edit/?p=preview
The example shows the use of Angular's copy method that creates a deep copy of whatever you pass it as a parameter and assigns it to the ng-model that is put on a particular input field. In this case they simply pass it an empty master object.
You need to make sure to add an ng-model attribute to your inputs, then create a reset function that can run after submission. Another common option would be to simply set each input's ng-model to empty strings in the submission function, such as $scope.inputModel = ""
Is this what you were hoping for? I might have misunderstood the question. I will happily take another crack at it if there is still confusion.
To get the form in your controller you just need to name your form this way:
<form name="pmt.myForm">
Here's a complete demo:
(function() {
"use strict";
angular
.module('plunker', [])
.controller('PMTController', PMTController);
PMTController.$inject = ['$log'];
function PMTController($log) {
var _this = this;
_this.model = {};
_this.search = {
resetSearchForm: function() {
console.log(_this.myForm); // -> Form reference
_this.model = {};
}
};
}
})();
<!DOCTYPE html>
<html ng-app="plunker">
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.8/angular.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css">
</head>
<body ng-controller="PMTController as pmt">
<div class="col-md-12">
<form name="pmt.myForm">
<div class="form-group">
<input type="text" ng-model="pmt.model.example" class="form-control" />
<input type="text" ng-model="pmt.model.example2" class="form-control" />
<input type="text" ng-model="pmt.model.example3" class="form-control" />
</div>
<button class="btn btn-primary" ng-click="pmt.search.resetSearchForm()">Reset</button>
</form>
<hr> All fields:
<pre ng-bind="pmt.model | json"></pre>
</div>
</body>
</html>

Happy.js - adding a message below submit button

Awesome people of Stack Overflow,
I'm trying to use Happy.js for validating my form, and it works fine. However, since my form is long and the submit button is at the very bottom of the form, I need a way to let the user know that there a errors on the form.
I'd love some help adding this functionality. Maybe unhiding a div right below the submit button or something like that.
Here's the HTML:
<form name="data" action="#" method="POST" id="JumpstartForm">
<label for="f1" class="control-label">First Name<span class='required'>*</span> </label>
<input title="First Name" type="text" id="f1" name="First_Name" class="input-large" value="" size="25" />
<div class="centered"><input type="submit" id="submitSignup" value="Submit" class="green-button" /></div>
</form>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script src="http://www.democracy-nc.org/jumpstart/js/happy.js"></script>
<script src="http://www.democracy-nc.org/jumpstart/js/happy.methods.js"></script>
Here's the js bit:
$(document).ready(function () {
$('#JumpstartForm').isHappy({
fields: {
// reference the field you're talking about, probably by `id`
// but you could certainly do $('[name=name]') as well.
'#f1': {
required: true,
message: 'Please enter your first name'
},
}
});
});
I created a JSfiddle: http://jsfiddle.net/gcasalett/E9Lq7/1/
Live page: http://democracy-nc.org/jumpstart/index.html
First post, so please be kind. Thanks!
What you need it's to use the callback that the plugin provide, called unHappy that it's called when the validation fails for any reason.
For other options you can check the Happy.js mainpage http://happyjs.com/
$(document).ready(function () {
$('#JumpstartForm').isHappy({
fields: {
// reference the field you're talking about, probably by `id`
// but you could certainly do $('[name=name]') as well.
'#f1': {
required: true,
message: 'Please enter your first name'
},
unHappy: function () {
//here you can show a div, or scroll to the last error.
},
}
});
});
Updated jsFiddle : http://jsfiddle.net/E9Lq7/4/

Categories