Validate submitted form and display errors gracefully - express

I am trying to validate user submitted form and display meaningful errors, but I have a lot of problems. First of all, my form (event_form.ejs):
<% if (typeof errors !== 'undefined') { %>
<div class="message"><%= errors %></div>
<% } %>
<form method="POST" action="/submit-create-event" id="create-event">
<input type="text" class="title" name="title" placeholder="title">
<input type="text" class="date" autocomplete="off" name="date" placeholder="date">
<select class="type" name="type">
<option value="road">Road</option>
<option value="mtb">MTB</option>
</select>
<input type="text" class="city" name="city" placeholder="city">
<select class="level" name="level">
<option value="beginner">Beginner</option>
<option value="advanced">Advanced</option>
<option value="pro">Pro</option>
</select>
<input type="text" class="start-time timepicker" autocomplete="off" data-time-format="H:i" name="startTime" placeholder="start time">
<input type="text" class="start_location" name="startLocation" placeholder="start location">
<input type="text" class="distance" name="distance" placeholder="distance">
<input type="submit" class="create-event-submit">
</form>
On submit, in my eventController.js I have function to handle submit.
//Helper function to handle event form submit.
module.exports.submitEventForm = function(req, res) {
const event = new eventModel.event({
title: req.body.title,
date: req.body.date,
type: req.body.type,
city: req.body.city,
level: req.body.level,
start_time: req.body.startTime,
start_location: req.body.startLocation,
distance: req.body.distance
});
event.save(function(err, data) {
if (err) {
console.log(err);
//I tried this, kinda works but pretty sure a bad practice
res.render('forms/create_event_form', {errors: err})
} else {
res.render('status/success');
}
});
};
As you can see, on event.save() if I have errors, I console.log() them at the moment. What I tried doing was rendering once again my event creation form and displaying errors (as visible in my .ejs file). Pretty sure it should not be done like that.
This is my model file with one validator
var mongoose = require('mongoose')
var validate = require('mongoose-validator')
var Schema = mongoose.Schema;
var titleValidator = [
validate({
validator: 'isAlphanumeric',
message: 'Title should be alphanumeric'
})
]
var eventSchema = new Schema({
title: {type: String, required: true, validate: titleValidator},
date: {type: String },
type: {type: String },
city: {type: String },
level: {type: String },
start_time: {type: String },
start_location: {type: String },
distance: {type: Number },
});
var Event = mongoose.model('Event', eventSchema);
module.exports.eventSchema = eventSchema;
module.exports.event = Event;
I created titleValidator, and if my form fails, it prints out something like this:
My questions is: How can I properly send my validator message to the template and display it? How to handle multiple messages once more validators are created, as errors are not returned in array manner, meaning I cannot loop through them?

Kinda found a solution. To make error messages more readable and usable, I used Mongoose error helper. It prints out all error messages neatly. However, when getting package via npm there is a bug in code and a pull request is created for a solution, but was never merged. Just make that minor change manually.
I couldn't find another way of rendering the same form with errors, so I sticked to this:
event.save(function(err) {
if (err) {
res.render('forms/create_event_form', {errors: errorHelper(err, next)});
} else {
res.render('status/success');
}
});
and in my .ejs template I print out errors as follows:
<% if (typeof errors !== 'undefined') {
errors.forEach(function(err) { %>
<div class="message"><%= err %></div>
<% })} %>
Hope this helps someone, cheers.

Related

asp.net core: how to apply a ModelState error when a form is submitted via AJAX

I am trying to display the errors associated with a form.
The form has a validation performed on the client (works fine) and on the server (that's my issue). The actual submission code is done through an ajax call:
$.ajax({
type: "POST",
url: url,
dataType: "text",
data: fiscalYearData,
success: function () {
showSuccess('Saved successfully');
closeForm();
},
error: function (error) {
showError('An error occured');
}
});
Unfortunately, that is not satifying. In my controller, I'm returning a ModelSatte with all relevant errors provided:
if (ModelState.ErrorCount > 0)
return BadRequest(ModelState);
// save the modified entity
await fiscalYearService.SaveFiscalYearAsync(fy, SecurityContext);
return Ok();
How can I modify the ajax error handling function to use the ModelState to display the error in the form itself?
Edit I apparently need to clarify what I would like to do:
I receive a ModelState containing errors error back from the ajax call. I would like to display these errors in the form in the same way they would have been displayed if I had submitted the form via a form submit button and page refresh: fields corresponding tot the error's keys should be marked in red with the corresponding error text displayed in the appropriate error labels.
You can pass ModelState error message to ajax,and put error.responseText into form.
Action:
if (ModelState.ErrorCount > 0) {
//Customize your error message
string messages = string.Join("; ", ModelState.Values
.SelectMany(x => x.Errors)
.Select(x => !string.IsNullOrWhiteSpace(x.ErrorMessage) ? x.ErrorMessage : x.Exception.Message.ToString()));
return BadRequest(messages);
}
ajax:
$.ajax({
type: "POST",
url: url,
dataType: "text",
data: fiscalYearData,
success: function () {
showSuccess('Saved successfully');
closeForm();
},
error: function (error) {
showError(error.responseText);
}
});
Update:
If you want to validate like form post,I think you don't need to use ModelState,you can use $("#xxform").valid() in js.So that only when the model is valid,we will use ajax to post data to action.Here is a demo:
Model:
public class TestModelState {
[Required]
public string FirstName { get; set; }
[Required]
public string MiddleName { get; set; }
[Required]
public string LastName { get; set; }
}
View:
<form id="myform" method="post">
<div class="form-group">
<label asp-for="FirstName" class="control-label"></label>
<input asp-for="FirstName" class="form-control" />
<span asp-validation-for="FirstName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="MiddleName" class="control-label"></label>
<input asp-for="MiddleName" class="form-control" />
<span asp-validation-for="MiddleName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="LastName" class="control-label"></label>
<input asp-for="LastName" class="form-control" />
<span asp-validation-for="LastName" class="text-danger"></span>
</div>
<input type="submit" value="submit" />
</form>
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
<script>
$('#myform').submit(function (e) {
e.preventDefault();
// check if the input is valid using a 'valid' property
if ($("#myform").valid()) {
$.ajax({
type: "POST",
url: "/Test1/TestModelState",
data: $("#myform").serialize(),
success: function (data) {
//showSuccess('Saved successfully');
//closeForm();
},
error: function (error) {
//console.log(error.responseText);
}
});
}
})
</script>
result:

v-if not working for form validation and displaying errors

I'm trying to validate a simple form that contains two fields:
A select box
A file field
If one of the fields aren't filled in, a div (containing an error label) should be rendered next to the corresponding input field.
The problem: My 'error divs' aren't rendered when pushing data to the errors object (if the form is invalid).
Please note my console.log statement, that tells me that my error object has a key 'file' and a key 'selectedSupplier'.
Side note: I'm following this example: https://v2.vuejs.org/v2/cookbook/form-validation.html
Differences are, that I'd like to show error labels next to the corresponding field and that I'm setting errors in my errors object, instead of a simple array. So what could I be doing wrong?
Thanks.
This is my Main.vue file:
<template>
<div>
<form #submit="upload">
<div class="mb-8">
<h1 class="mb-3 text-90 font-normal text-2xl">Import Order Csv</h1>
<div class="card">
<div class="flex border-b border-40">
<div class="w-1/5 px-8 py-6">
<label for="supplier_id" class="inline-block text-80 pt-2 leading-tight">Supplier</label>
</div>
<div class="py-6 px-8 w-1/2">
<select v-model="selectedSupplier" id="supplier_id" name="supplier_id" ref="supplier_id" class="w-full form-control form-input form-input-bordered">
<option v-for="supplier in suppliers" v-bind:value="supplier.id">{{ supplier.name }}</option>
</select>
<div v-if="errors.hasOwnProperty('selectedSupplier')" class="help-text error-text mt-2 text-danger">
Required.
</div>
</div>
</div>
<div class="flex border-b border-40">
<div class="w-1/5 px-8 py-6">
<label for="csv_file" class="inline-block text-80 pt-2 leading-tight">File</label>
</div>
<div class="py-6 px-8 w-1/2">
<input id="csv_file" type="file" name="file" ref="file" #change="handleFile">
<div v-if="errors.hasOwnProperty('file')" class="help-text error-text mt-2 text-danger">
Required.
</div>
</div>
</div>
</div>
</div>
<div class="flex items-center">
<button type="submit" class="btn btn-default btn-primary inline-flex items-center relative">Import</button>
</div>
</form>
</div>
</template>
<script>
export default {
mounted() {
this.listSuppliers();
},
data() {
return {
errors: [],
file: '',
suppliers: [],
};
},
methods: {
checkForm() {
if (!this.selectedSupplier) {
this.errors.selectedSupplier = 'Supplier required';
}
if (!this.file) {
this.errors.file = 'File required';
}
},
listSuppliers() {
const self = this;
Nova.request()
.get('/tool/import-order-csv/suppliers')
.then(function (response) {
self.suppliers = response.data.data;
})
.catch(function (e) {
self.$toasted.show(e, {type: "error"});
});
},
handleFile: function (event) {
this.file = this.$refs.file.files[0];
},
upload: function (event) {
this.checkForm();
if (this.errors.hasOwnProperty('selectedSupplier') || this.errors.hasOwnProperty('file')) {
console.log(this.errors); // this actually shows both errors!
event.preventDefault();
}
const formData = new FormData();
formData.append('file', this.file);
formData.append('supplier_id', this.$refs.supplier_id.value);
const self = this;
Nova.request()
.post('/tool/import-order-csv/upload',
formData,
{
headers: {
'Content-Type': 'multipart/form-data'
}
}
).then(function (response) {
self.$toasted.show(response.data.message, {type: "success"});
})
.catch(function (e) {
self.$toasted.show(e.response.data.message, {type: "error"});
});
}
}
}
</script>
Apparently I had to use v-show instead of v-if, because v-if would be 'lazy' and will not render my error-div when the errors var gets filled.
It's working now, but not 100% sure if this is the best way, as I found another tutorial where v-if is used for form validation.(https://medium.com/#mscherrenberg/laravel-5-6-vue-js-simple-form-submission-using-components-92b6d5fd4434)
I was getting the same error, this is how I solved the problem,
<div v-if="errors.field1.length > 0 ? true : false"> // true or false
If you fix the code like this it will work
The reason might because the way you reassign object is not reactive, which not trigger v-if to re-calculate
this.errors.selectedSupplier = 'Supplier required';
this.errors.file = 'File required';
If you still want to use v-if , try change to this approach
this.errors = {...this.errors, selectedSupplier: 'Supplier required' }
this.errors = {...this.errors, file: 'File required' }
The way I handle my errors with VueJS is through lists and their length attribute.
I have an errors object in my data that looks like this:
errors: {
field1: [],
field2: [],
}
Then, when I submit the form, I will:
Empty all the lists for the errors (ie clearing the previous errors)
.push() new errors in the right lists (and .push() makes the Vue reactive)
Finally, in my form, my respective errors divs are displayed based on the length of the list:
<div class="error" v-if="errors.field1.length > 0">
use a v-for to display all the errors from the list
</div>
Hope it helps

Vee-Validate custom date validation

I was wondering if there is anyway you can write a custom date validation using vee-validate plugin where the end date cannot be less than the start date? I have looked high and low, and there is nowhere I can find a definite answer to this.
If there is no way to implement this, then I can make do without it, however, right now what I have implemented in my template for my start date is:
<input type="text" id="startDate" name="startDate" class="form-control" v-model="startDate" v-validate="'required|date_format:DD-MM-YYYY'" :class="{'input': true, 'is-danger': errors.has('startDate') }">
<label class="mb-0" for="startDate">Start Date</label>
<span v-show="errors.has('startdate')" class="text-danger"><center>{{ errors.first('startdate') }}</center></span>
My script looks like this:
export default {
name: 'App',
data: () => ({
task: '',
startDate: '',
startTime: '',
endDate: '',
endTime: '',
description: 'test'
}),
methods: {
validateBeforeSubmit() {
this.$validator.validateAll().then((result) => {
if (result) {
// eslint-disable-next-line
alert('Form Submitted!');
return;
}
alert('Correct them errors!');
});
}
}
};
But there is no validation that is showing up. I think I am missing something in my script but I am not sure how to implement the date into there. Any help would be greatly appreciated.
First, it's maybe some typo error but in your template you use startDate and startdate lowercased.
Then, to answer your question, it's possible to define a custom validator with a date comparison with vee-validate.
As your chosen date format "DD-MM-YYYY" is not a valid javascript Date format, the string dates need to be rewritten into a valid format to make it work.
Vue.use(VeeValidate)
new Vue({
el: "#app",
data() {
return {
startDate: '',
endDate: '',
}
},
created() {
let self = this
this.$validator.extend('earlier', {
getMessage(field, val) {
return 'must be earlier than startDate'
},
validate(value, field) {
let startParts = self.startDate.split('-')
let endParts = value.split('-')
let start = new Date(startParts[2], startParts[1] -1, startParts[0]) // month is 0-based
let end = new Date(endParts[2], endParts[1] -1, endParts[0])
return end > start
}
})
},
methods: {
validateBeforeSubmit() {
this.$validator.validateAll().then((result) => {
if (result) {
alert('Form Submitted!');
return;
}
alert('Correct them errors!');
});
}
}
})
.is-danger, .text-danger {
color: red;
}
<script src="https://unpkg.com/vee-validate#2.0.0-rc.19/dist/vee-validate.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>
<div id="app">
<div>
<input type="text" name="startDate" v-model="startDate"v-validate="'required|date_format:DD-MM-YYYY'" :class="{'input': true, 'is-danger': errors.has('startDate') }">
<label class="mb-0" for="startDate">Start Date</label>
<span v-show="errors.has('startDate')" class="text-danger">{{ errors.first('startDate') }}</span>
</div>
<div>
<input type="text" name="endDate" v-model="endDate" v-validate="'required|date_format:DD-MM-YYYY|earlier'" :class="{'input': true, 'is-danger': errors.has('endDate') }">
<label class="mb-0" for="endDate">End Date</label>
<span v-show="errors.has('endDate')" class="text-danger">{{ errors.first('endDate') }}</span>
</div>
<button #click="validateBeforeSubmit">Save</button>
</div>
Note: i put the custom validator inside the created hook for the example but you can put it inside any file you want in your projet. Just import it correctly as the documentation recommends.

not able to add user input into a rest api using store

This is my mainpage where I have a form which I want to integrate with an API so that I can save my data there:
<div>
<span>userId</span> <input type="text" v-model="info.userId">
</div>
<br>
<div>
<span>id</span> <input type="text" v-model="info.id">
</div>
<br>
<div>
<span>title</span> <input type="text" v-model="info.title">
</div>
<br>
<div>
<span>body</span> <input type="text" v-model="info.body" >
</div>
<br>
<input type="submit" #click="addtoAPI">
This is mainpage.js code where i have declared the the object named "info" which will hold all the fileds of my form and there is also the "addtoAPI" method which basically access the store's action
data(){
return{
info:{
userId:'',
id:'',
title:'',
body:''
}
}
},
methods:{
addtoAPI(){
this.$store.dispatch('addtoapi',this.info)
}
This is my store.js code where I have added an action named addtoapi which basically stores data in respective fields
actions: {
addtoapi :({commit},info) =>{
let newuser={
userId:this.info.userId,
id:this.info.id,
title:this.info.title,
body:this.info.body
}
console.log(newuser);
axios.post('https://jsonplaceholder.typicode.com/posts/',newuser)
.then((response) =>{
console.log(response);
})
.catch((error) => {
console.log(error);
})
}
}
Now when I am running this I am getting this error please help me with this because I have already added userId.
store.js?adc6:50 Uncaught TypeError: Cannot read property 'userId' of undefined
at Store.addtoapi (store.js?adc6:50)
at Array.wrappedActionHandler (vuex.esm.js?358c:704)
at Store.dispatch (vuex.esm.js?358c:426)
at Store.boundDispatch [as dispatch] (vuex.esm.js?358c:332)
at VueComponent.addtoAPI (Mainpage.js?4af6:45)
at invoker (vue.esm.js?efeb:2027)
at HTMLInputElement.fn._withTask.fn._withTask (vue.esm.js?efeb:1826)
In your store's actions you've made a mistake, you're trying to access to property info of this (that's why it throw an error with undefined) :
let newuser= {
userId:this.info.userId,
id:this.info.id,
title:this.info.title,
body:this.info.body
}
Need to be replaced by this:
let newuser = {
userId: info.userId,
id: info.id,
title: info.title,
body: info.body
}

Watch date input value and use it with v-model

I tried for 4 days to get a stupid date from the input, that the user selected. Looks probably simple but believe me that I ask here because I run out of solutions.
I have a page/component AddNewEvent and inside this input where the user adds the date and time. I have to get it in my v-model so I can send it back to database.
I use bootstrap 4 input type="datetime-local" to get the date and time. I tried to use some vue plugins for date but all are based on bootstrap 3 and in the project is bootstrap 4.
Inside template:
<div class="form-group">
<label class="eda-form-label" for="event-name">Event Name <span>(Max characters number is 60)</span></label>
<input type="text" class="eda-form-input" v-model="newEvent.eventName">
</div>
<div class="form-group">
<label class="eda-form-label" for="exampleTextarea">Event Description <span>(Max characters number is 2000)</span></label>
<textarea class="eda-form-input" rows="3" v-model="newEvent.description"></textarea>
</div>
<div class="form-group">
<div class="row">
<div class="col-6 ">
<label class="eda-form-label" for="start-time">START TIME</label>
<input class="form-control" type="datetime-local" v-model="dateNow">
</div>{{createdDate(value)}}
<div class="col-6">
<label class="eda-form-label" for="end-time">END TIME</label>
<input class="form-control" type="datetime-local" v-model="dateEnd">
</div>
</div>
</div>
In the script:
data() {
return {
dateNow: '',
value: '',
oldValue: ''
}
},
watch: {
dateNow(val, oldVal) {
this.value = val;
this.oldValue = oldVal;
}
},
methods: {
createEvent(){
axios.post("/event", this.newEvent,
{'headers':{'X-AUTH-TOKEN': localStorage.token}},
{'headers':{'Content-Type': 'application/json'}})
.then((response) => {
alertify.success("Success! You added a new the user");
this.$router.push('/events');
})
.catch((response) => {
alertify.error();
})
},
}
If I use in the input, how is right now, v-model="dateNow" works. I can see when I select date, time am/pm shows me the seleted date. But I have to use it like this
v-model="newEvent.dateNow"
v-model="newEvent.dateEnd"
so I can add it to newEvent and send the whole obj back to database.
createdDate is a function that transforms the date in a real date. It's only for testing, because I have to send the date in ms back to database.
Someone please show me what I do wrong, because I'm not so very advance in vuejs.
First you need to initialize dateNow varable in datetime-locale:
data() {
return {
dateNow: new Date().toLocaleString(),
value: '',
oldValue: ''
}
},
It support this format: "2017-09-18T08:30".
I think this will solve your problem.
And also you need to check Browser compatibility, as it is not supported in IE, Firefox and Safari
or you can try:
<input type="datetime-local" :value="dateValue" #input="updateValue($event.target.value)" >
in script:
data() {
return {
dateValue: new Date().toLocaleString()
};
},
methods: {
updateValue: function(value) {
this.dateValue= value;
this.$emit('input', value);
}
}
Try this