I have two entities Environnment and ServeurApplicatif that have a oneToMany relationship. I have a dynamic form with a button to add a new form each time a user wants to associate a ServeurApplicatif to on Environnement entity.When submitting the expected result is the persistence of both the environnement Entity and the ServeurApplicatif entities associated with it.
However in my case i get a 400 http error : your request is synthatically incorrect, whenever i try to submit the form.
here is the javascript code that adds the child entity related form dynamically :
<script type="text/javascript">
$(document).ready(function() {
var index = ${fn:length(env.serveurApplicatifs)};
console.log("index value :"+index);
$("#addServ").off("click").on("click",function() {
$(this).before(function() {
var html = '<div id="serveurApplicatifs'+index+'.wrapper" style="display: none;">';
html += '<input type="hidden" id="serveurApplicatifs'+index+'.idserv" name="serveurApplicatifs['+index+'].idserv" ></input>';
html += '<p><strong>Serveur Applicatif 1 : </strong></p>';
html += '<div class="form-group"><label for="serveurApplicatifs'+index+'.port">Port :</label><input class ="form-control" type="text" id="serveurApplicatifs'+index+'.port" name="serveurApplicatifs['+index+'].port" ></input></div>';
html += '<div class="form-group"><label for="serveurApplicatifs'+index+'.compte">Compte :</label><input class ="form-control" type="text" id="serveurApplicatifs'+index+'.compte" name="serveurApplicatifs['+index+'].compte" ></input></div>';
html += '<div class="form-group"><label for="serveurApplicatifs'+index+'.pwd">Mot de passe :</label><input class ="form-control" type="text" id="serveurApplicatifs'+index+'.pwd" name="serveurApplicatifs['+index+'].pwd" ></input></div>';
html += '<div class="form-group"><label for="serveurApplicatifs'+index+'.adresse">Adresse :</label><input class ="form-control" type="text" id="serveurApplicatifs'+index+'.adresse" name="serveurApplicatifs['+index+'].adresse" ></input></div>';
html += 'Supprimer';
html += "</div>";
return html;
});
$("#serveurApplicatifs"+index+"\\.wrapper").show();
index++;
return false;
});
$("a.serveurApplicatifs.remove").off("click").on("click",function() {
var index2remove = $(this).data("index");
$("#serveurApplicatifs"+index2remove+"\\.wrapper").hide();
$("#serveurApplicatifs"+index2remove+"\\.remove").val("1");
return false;
})
the Spring form tags in the jsp :
<c:url value="/envs/save" var="saveUrl"/>
<form:form action="${ saveUrl }" method="POST" modelAttribute="env">
<div class="form-group">
<form:input type="hidden" class="form-control" id="idEnv" placeholder="id" path="idEnv"></form:input><span id="star">*</span>
</div>
<!-- ------------- -->
<div class="form-group">
<label for="nom">Nom</label>
<form:input type="text" class="form-control" id="nom" placeholder="Nom" path="nom"></form:input><span>*</span>
<form:errors path="nom" cssClass="error"></form:errors>
</div>
<div class="form-group">
<label for="plateforme">Platforme</label>
<form:input type="text" class="form-control" id="plateform" placeholder="Plateforme" path="platforme"></form:input>
<form:errors path="platforme" cssClass="error"></form:errors>
</div>
<div class="form-group">
<label for="typologie">Typologie</label>
<form:input type="text" class="form-control" id="typologie" placeholder="Typologie" path="typologie"></form:input>
<form:errors path="typologie" cssClass="error"></form:errors>
</div>
<div class="form-group">
<label for="bd">Base de donnée</label>
<form:input type="text" class="form-control" id="bd" placeholder="Bdd" path="bd"></form:input>
<form:errors path="bd" cssClass="error"></form:errors>
</div>
<div class="form-group">
<label for="version">Version</label>
<form:input type="text" class="form-control" id="version" placeholder="Version" path="version"></form:input>
<form:errors path="version" cssClass="error"></form:errors>
</div>
<div class="form-group">
<label for="machine_bd">Serveur de base de données</label>
<form:input type="text" class="form-control" id="machine_bd" placeholder="Machine Bdd" path="machineBd"></form:input>
<form:errors path="machineBd" cssClass="error"></form:errors>
</div>
<div class="form-group">
<label for="port_bd">Port Base de données</label>
<form:input type="text" class="form-control" id="port_bd" placeholder="Port bdd" path="portBd"></form:input>
<form:errors path="portBd" cssClass="error"></form:errors>
</div>
<div class="form-group">
<label for="version_bd">Version de base de données</label>
<form:input type="text" class="form-control" id="version_bd" placeholder="Version bdd" path="versionBd"></form:input>
<form:errors path="versionBd" cssClass="error"></form:errors>
</div>
<div class="form-group">
<label for="type_bd">Type de base de données</label>
<form:input type="text" class="form-control" id="type_bd" placeholder="Type bdd" path="typeBd"></form:input>
<form:errors path="typeBd" cssClass="error"></form:errors>
</div>
<div class="form-group">
<label for="depart">Environnement de départ</label>
<form:radiobutton value ="1" id="depart" path="depart" label="Oui"></form:radiobutton>
<form:radiobutton value ="0" id="depart" path="depart" label="Non"></form:radiobutton>
<form:errors path="depart" cssClass="error"></form:errors>
</div>
<div class="form-group">
<label for="repert">Repert</label>
<form:input type="text" class="form-control" id="repert" placeholder="Repert" path="repert"></form:input>
<form:errors path="repert" cssClass="error"></form:errors>
</div>
<div class="form-group">
<label for="port_ftp">Port FTP</label>
<form:input type="text" class="form-control" id="port_ftp" placeholder="Port FTP" path="portFtp"></form:input>
<form:errors path="portFtp" cssClass="error"></form:errors>
</div>
<div class="form-group">
<label for="exclu">Exclu</label>
<form:radiobutton value ="1" id="exclu" path="exclu" label="Oui"></form:radiobutton>
<form:radiobutton value ="0" id="exclu" path="exclu" label="Non"></form:radiobutton>
<form:errors path="exclu" cssClass="error"></form:errors>
</div>
<div class="form-group">
<label for="envsPrec">Précedent</label>
<form:select class="form-control" id="envsPrec" path="environnements2" items="${ envsPrecSuiv }" itemLabel="nom" itemValue="idEnv"/>
<form:errors path="environnements2" cssClass="error"></form:errors>
</div>
<div class="form-group">
<label for="envsSuiv">Suivant</label>
<form:select class="form-control" id="envsSuiv" path="environnements1" items="${ envsPrecSuiv }" itemLabel="nom" itemValue="idEnv"/>
<form:errors path="environnements1" cssClass="error"></form:errors>
</div>
<c:forEach varStatus="loop" var="serveurApplicatifs" items="${ env.serveurApplicatifs }">
<c:choose>
<c:when test="${env.serveurApplicatifs[loop.index].remove eq 1 }">
<div id="serveurApplicatifs${loop.index }.wrapper" style="display: none;">
</c:when>
<c:otherwise>
<div id="serveurApplicatifs${ loop.index }.wrapper">
</c:otherwise>
</c:choose>
<div class="form-group">
<form:input type="hidden" class="form-control" id="idApp" path="env.serveurApplicatifs[${loop.index}].idserv"></form:input>
</div>
<div class="form-group">
<label for="portApp">Port serveur applicatif</label>
<form:input type="text" class="form-control" id="portApp" placeholder="Port" path="env.serveurApplicatifs[${loop.index}].port"></form:input>
</div>
<div class="form-group">
<label for="compteApp">Compte serveur applicatif</label>
<form:input type="text" class="form-control" id="compteApp" placeholder="Compte" path="env.serveurApplicatifs[${loop.index}].compte"></form:input>
</div>
<div class="form-group">
<label for="pwdApp">Mot de passe serveur applicatif</label>
<form:input type="text" class="form-control" id="pwdApp" placeholder="Mot de passe" path="env.serveurApplicatifs[${loop.index}].pwd"></form:input>
</div>
<div class="form-group">
<label for="adresseApp">Adresse serveur applicatif</label>
<form:input type="text" class="form-control" id="adresseApp" placeholder="Adresse" path="env.serveurApplicatifs[${loop.index}].adresse"></form:input>
</div>
<c:choose>
<c:when test="${serveurApplicatifs[loop.index].remove eq 1}"><c:set var="hiddenValue" value="1"></c:set></c:when>
<c:otherwise><c:set var="hiddenValue" value="0"></c:set> </c:otherwise>
</c:choose>
<form:hidden path="serveurApplicatifs[${loop.index}].remove" value="${hiddenValue}"/>
Supprimer
</div>
</c:forEach>
<button type="button" class="btn btn-default" id="addServ">Ajouter Un Serveur d'application</button>
<button type="submit" class="btn btn-default" value="Save">Submit</button>
</form:form>
The Environnement entity class :
#Entity
#NamedQuery(name="Environnement.findAll", query="SELECT e FROM Environnement e")
public class Environnement implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="id_env")
private int idEnv;
private String bd;
private int depart;
private int exclu;
#Column(name="machine_bd")
private String machineBd;
#Column(name="mdp_bdd")
private String mdpBdd;
private String nom;
private String platforme;
#Column(name="port_bd")
private int portBd;
#Column(name="port_ftp")
private int portFtp;
private String repert;
#Column(name="type_bd")
private String typeBd;
private String typologie;
#Column(name="utilisateur_bdd")
private String utilisateurBdd;
private String version;
#Column(name="version_bd")
private String versionBd;
//bi-directional many-to-many association to Composant
#ManyToMany
#JoinTable(
name="env_comp"
, joinColumns={
#JoinColumn(name="id_env")
}
, inverseJoinColumns={
#JoinColumn(name="id_comp")
}
)
private List<Composant> composants;
//bi-directional many-to-many association to Environnement
#LazyCollection(LazyCollectionOption.FALSE)
#ManyToMany
#JoinTable(
name="lien_environnement"
, joinColumns={
#JoinColumn(name="id_env_suiv")
}
, inverseJoinColumns={
#JoinColumn(name="id_env_prec")
}
)
private List<Environnement> environnements1;
//bi-directional many-to-many association to Environnement
#LazyCollection(LazyCollectionOption.FALSE)
#ManyToMany
#JoinTable(
name="lien_environnement"
, joinColumns={
#JoinColumn(name="id_env_prec")
}
, inverseJoinColumns={
#JoinColumn(name="id_env_suiv")
}
)
private List<Environnement> environnements2;
//bi-directional many-to-one association to ServeurApplicatif
#LazyCollection(LazyCollectionOption.FALSE)
#OneToMany(mappedBy="environnement")
private List<ServeurApplicatif> serveurApplicatifs;
the ServeurApplicatif entity class :
#Entity
#Table(name="serveur_applicatif")
#NamedQuery(name="ServeurApplicatif.findAll", query="SELECT s FROM ServeurApplicatif s")
public class ServeurApplicatif implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private int idserv;
private String adresse;
private String compte;
private int port;
private String pwd;
#Transient
private Integer remove;
//bi-directional many-to-one association to Environnement
#ManyToOne
#JoinColumn(name="id_env")
private Environnement environnement;
Controller Method used to persist the entities :
#RequestMapping(value="/envs/save",method=RequestMethod.POST)
public ModelAndView saveEnvironnement(#ModelAttribute(value="env")Environnement env) {
//Environnement envFinal = this.envService.validate(env);
this.envService.gererServeur(env);
/*for (ServeurApplicatif servApp : env.getServeurApplicatifs()) {
}*/
envService.saveOrUpdate(env);
for (ServeurApplicatif servApp : env.getServeurApplicatifs()) {
servAppService.saveOrUpdate(servApp);
}
return new ModelAndView("redirect:/envs");
}
gererServeur method in EnvironnementService classe :
#Override
public List<ServeurApplicatif> gererServeur(Environnement env) {
// TODO Auto-generated method stub
List<ServeurApplicatif> serveurRemoved = new ArrayList<ServeurApplicatif>();
if(env.getServeurApplicatifs() != null) {
for (Iterator<ServeurApplicatif> iterator = serveurRemoved.iterator(); iterator
.hasNext();) {
ServeurApplicatif serveurApplicatif = iterator.next();
if(serveurApplicatif.getRemove() == 1) {
serveurRemoved.add(serveurApplicatif);
iterator.remove();
}
else {
serveurApplicatif.setEnvironnement(env);
}
}
}
return serveurRemoved;
}
Note that i'm using the solution shown here to manage the dynamic form :
Spring 3 MVC: one-to-many within a dynamic form (add/remove on create/update)
Edit :
i tried declaring the line var index = ${fn:length(env.serveurApplicatifs)}; outside of the document ready function but it didn't help, the console.log returns 0 when loading the form but gets incremented each time i clic the button to add a new form for ServeurApplicatif,the form data sent to the server is as follows :
idEnv:0
nom:
platforme:
typologie:
bd:
version:
machineBd:
portBd:0
versionBd:
typeBd:
depart:0
repert:
portFtp:0
exclu:0
_environnements2:1
_environnements1:1
serveurApplicatifs[0].idserv:
serveurApplicatifs[0].port:14523
serveurApplicatifs[0].compte:test
serveurApplicatifs[0].pwd:test
serveurApplicatifs[0].adresse:test
serveurApplicatifs[1].idserv:
serveurApplicatifs[1].port:14523
serveurApplicatifs[1].compte:test
serveurApplicatifs[1].pwd:test
serveurApplicatifs[1].adresse:test
_csrf:2d856fad-16f3-4ae4-a254-47922a695c17
you will notice that the idserv doesn't get automatically populated unlike idenv. both of these IDs are auto-increment fields in the DB, the other blank fields are nullable so the problem isn't there.
Thanks in advance.
I found what was wrong.
Basically the idserv wasn't getting the default value of 0 so that
spring can handle the request to the server and hibernate can insert the ServeurApplicatif entity alongside the Environnment entity when saveOrUpdate is invoked, so i added a value="0" to the javascript code responsible for creating form inputs for ServeurApplicatifs on the fly and now it works as expected.
Related
I have an app where students can register and login and having a field called city.
I'm trying to make a dropdown of cities so the student can pick which city he wants from there. It's a long list so I made a function in StudentService which will return array of strings and I'm trying to send it to the form using model.attribute but it's not working.
I tried using ajax and JQuery with external json file of the cities and it still didn't work even after I looked several questions here. If you have any idea how it is best to handle it please let me know.
here is the code so far and what I tried:
Service:
public class StudentService implements StudentInterfaceService {
// regular function of get, create, delete, update
public String [] getCities() {
String [] cities = { "lots of cities here };
return cities;
}
}
Controller:
public class RegistrationController {
#Autowired
private StudentService studentService;
public RegistrationController(StudentService studentService){
super();
this.studentService = studentService;
}
#ModelAttribute("student")
public StudentRegistration studentInfo() {
return new StudentRegistration();
}
#GetMapping
public String showRegistrationForm(Model model) {
String [] cities = studentService.getCities();
model.addAttribute("cities", cities);
return "registration";
}
#PostMapping
public String registerStudent(#ModelAttribute("student") StudentRegistration studentInfo) {
studentService.saveStudent(studentInfo);
return "redirect:/registration?success";
}
}
Html:
<!DOCTYPE html>
<html xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="ISO-8859-1">
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<!-- success message -->
<div th:if="${param.success}">
<div class="alert alert-info">You've successfully registered
to our awesome app!</div>
</div>
<h1>Registration</h1>
<form th:action="#{/registration}" method="post" th:object="${student}">
<div class="form-group">
<label class="control-label" for="username"> Username </label>
<input id="username" class="form-control" th:field="*{username}"
required autofocus="autofocus" />
</div>
<div class="form-group">
<label class="control-label" for="studentName"> Student Name </label> <input
id="studentName" class="form-control" th:field="*{studentName}"
required autofocus="autofocus" />
</div>
<div class="form-group">
<label class="control-label" for="password"> Password </label> <input
id="password" class="form-control" type="password"
th:field="*{password}" required autofocus="autofocus" />
</div>
<div class="form-group">
<select th:field="*{city}" th:each="city: ${cities}">
<option th:value="city" th:text="city"></option>
</select>
</div>
<div class="form-group">
<label class="control-label" for="phoneNum"> Phone Number </label> <input
id="phoneNum" class="form-control" th:field="*{phoneNum}" required
autofocus="autofocus" />
</div>
<div class="form-group">
<label class="control-label"> Gender: </label>
<select th:field="*{gender}">
<option th:value="'Male'" th:text="Male"></option>
<option th:value="'Female'" th:text="Female"></option>
</select>
</div>
<div class="form-group">
<button type="submit" class="btn btn-success">Register</button>
<span>Already registered? <a href="/" th:href="#{/login}">Login
here</a></span>
</div>
</form>
</div>
</div>
</div>
</body>
</html>
I also tried using ajax in this way and it didn't work:
<script>
$.ajax({
url:'/cities',
type:'GET',
dataType: 'json',
success: function( json ) {
$.each(json, function(i, value) {
$('#cities').append($('<option>').text(value).attr('value', value.value));
});
}
});
</script>
EDIT:
Student model have these fields:
private String username;
private String studentName;
private String password;
private String city;
private String phoneNum;
private String gender;
I have a database server name recommendation list coming from the backend, where "driverName" and "connectionURL" for that driver should auto-populate once the user picks a server.
I know it's possible to accomplish it with javascript, but I'm new to the language.
Could someone please assist me?
<div class="form-group">
<form:label path="databaseServer">Database Server:</form:label>
<form:select path="databaseServer" class="form-control">
<form:option value="None" label="Please Select a Database Server"/>
<form:options items="${command.databaseServerMap}" />
</form:select>
</div>
<div class="form-group">
<form:label path="driverName">Driver Name:</form:label>
<form:input path="driverName" class="form-control" readonly="true"/>
</div>
<div class="form-group">
<form:label path="connectionURL">connection URL:</form:label>
<form:input path="connectionURL" class="form-control" readonly="true"/>
</div>
These are the maps I'm working with on the backend.
public Map<String, String> getDatabaseServerMap() {
Map<String, String> driverList = new LinkedHashMap<>();
driverList.put("MySQL", "MySQL");
driverList.put("Postgres", "Postgres");
return driverList;
}
[1]: https://i.stack.imgur.com/HKUWd.png
This javaScript code was effective.
<script>
$(function () {
$('.selectDatabase').change(function(){
// console.log( $(this).val() );
const driverName=$('.driverName');
const connectionURL = $('.connectionURL');
if($(this).val()==='MySQL'){
driverName.val('com.microsoft.sqlserver.jdbc.SQLServerDriver');
connectionURL.val('jdbc:sqlserver://localhost:1433;databaseName=database');
driverName.attr('readonly',true);
connectionURL.attr('readonly',true);
}else if($(this).val()==='Other'){
driverName.val('');
connectionURL.val('');
driverName.attr('readonly',false);
connectionURL.attr('readonly',false);
}else{
driverName.val('');
connectionURL.val('');
driverName.attr('readonly',true);
connectionURL.attr('readonly',true);
}
});
});
</script>
<div class="form-group">
<form:label path="databaseServer">Database Server:</form:label>
<form:select path="databaseServer" class="form-control selectDatabase">
<form:option value="None" label="Please Select a Database Server"/>
<form:options items="${command.databaseServerMap}" />
</form:select>
</div>
<div class="form-group">
<form:label path="driverName">Driver Name:</form:label>
<form:input path="driverName" class="form-control driverName" readonly="true"/>
</div>
I'm trying to make a field required, if a specific option is selected from a select.
What I have so far:
ViewModel:
public enum RequestType
{
PaidLeaveOfAbsence = 1,
WorkFromHome = 2,
SickLeave = 3,
BriefLeaveOfAbsence = 4
}
public class RequestFormViewModel
{
public RequestType SelectedRequestType { get; set; }
public DateTime FromDate { get; set; }
public DateTime ToDate { get; set; }
[RequieredIf("SelectedRequestType")]
public string Comment { get; set; }
}
CustomAttribute:
public class RequieredIfAttribute : ValidationAttribute, IClientModelValidator
{
private readonly string _otherProperty;
public RequieredIfAttribute(string otherProperty)
{
_otherProperty = otherProperty;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
string comment = (string)value;
RequestType selectedRequestType = (RequestType)validationContext.ObjectType.GetProperty(_otherProperty).GetValue(validationContext.ObjectInstance, null);
if (string.IsNullOrEmpty(comment) && selectedRequestType == RequestType.BriefLeaveOfAbsence)
{
return new ValidationResult("Comment is requiered.");
}
return ValidationResult.Success;
}
public void AddValidation(ClientModelValidationContext context)
{
MergeAttribute(context.Attributes, "data-val", "true");
MergeAttribute(context.Attributes, "data-val-required-if", "Comment is requiered.");
MergeAttribute(context.Attributes, "data-val-other", "#" + _otherProperty);
}
private static bool MergeAttribute(IDictionary<string, string> attributes, string key, string value)
{
if (attributes.ContainsKey(key))
{
return false;
}
attributes.Add(key, value);
return true;
}
}
HTML:
<div class="row">
<div class="col-0 col-md-2"></div>
<div class="col-12 col-md-4">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="SelectedRequestType" class="control-label"></label>
<select asp-for="SelectedRequestType" asp-items="Html.GetEnumSelectList<RequestType>()" class="form-control">
<option selected="selected" value="">Select a request</option>
</select>
<span asp-validation-for="SelectedRequestType" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="FromDate" class="control-label"></label>
<input asp-for="FromDate" class="form-control" type="text" value="" id="fromDate" autocomplete="off" />
<span asp-validation-for="FromDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ToDate" class="control-label"></label>
<input asp-for="ToDate" class="form-control" type="text" value="" id="toDate" autocomplete="off" />
<span asp-validation-for="ToDate" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
<div class="col-12 col-md-4">
<div class="form-group">
<label asp-for="Comment" class="control-label">Comment</label>
<textarea asp-for="Comment" class="form-control" id="comment" rows="3"></textarea>
<span asp-validation-for="Comment" class="text-danger"></span>
</div>
</div>
<div class="col-0 col-md-2"></div>
Generated HTML:
<select class="form-control" data-val="true" id="SelectedRequestType" name="SelectedRequestType">
<option selected="selected" value="">Select a request</option>
<option value="1">PaidLeaveOfAbsence</option>
<option value="2">WorkFromHom</option>
<option value="3">SickLeave</option>
<option value="4">BriefLeaveOfAbsence</option>
</select>
...
<div class="form-group">
<label class="control-label" for="Comment">Comment</label>
<textarea class="form-control" id="comment" rows="3" data-val="true" data-val-other="#SelectedRequestType" data-val-required-if="Comment is required." name="Comment"></textarea>
<span class="text-danger field-validation-valid" data-valmsg-for="Comment" data-valmsg-replace="true"></span>
</div>
The server side validation works fine. I'm stuck on adding client side validation, so far I have this:
validator.js
jQuery.validator.addMethod("required-if",
function (value, element, param) {
var otherProp = $($(element).data('val-other'));
console.log(otherProp);
if (!value.trim() && otherProp.val() == 4) {
return false;
}
return true;
}
)
jQuery.validator.unobtrusive.adapters.add("required-if", ["other"],
function (options) {
console.log(options);
options.rules["required-if"] = "#" + options.params.other;
options.messages["required-if"] = options.message;
});
I've put some console.log()s but they are never executed. (I did preserve the log in chrome).
Most of the google searches are from the ASP.NET MVC that implement IClientValidatable interface and are not very useful. I'm using ASP.NET Core 2.2.0.
I did read the microsoft docs and the link they provided on custom adapters for unusual validators.
Questions:
How can I achieve the expected behavior this way? What am I doing wrong and how can I fix it?
What are my other options? Should I just make a separate client side validation with the jQuery Validation Plugin? I don't like the idea of 2 separate places for validation.
Can someone explain to me why the console.log()s inside the javascript functions are never executed? I have custom validators for FromDate and ToDate and they are executed there. The only difference is that I use
jQuery.validator.unobtrusive.adapters.addBool instead of jQuery.validator.unobtrusive.adapters.add.
You can have your FormViewModel extend IValidatableObject. Once you do that implement Validate method. There you can have custom validation based on values in your Model. Something like:
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if(SelectedRequestType == RequestType.PaidLeaveOfAbsence)
{
// Check if field is not null and return
yield return new ValidationResult(
"SomeField should be present.",
new[] { "SomeField" });
}
}
You can make above syntax more elegant with use of pattern matching
You can find more about model validation at this link
The comment section was outside the form, so the validation would never happen.
The answer was found in the link from my original post.
Important note: jQuery Validate requires your input elements to be
inside of a <form> element in order to be validated.
This is what we have in the interface:
I pasted the parts of the code what I thought that are relevant, but maybe something more is required.
How it works
When the button is pushed, the userController.js save method is invoked. In the controller there is a $resource and the $save method is "connected" with the create method in UserController.java, and there is persisted.
The problem
In the interface I have three inputs (dd, mm, yy) and what I want to persist is a User with a java.time.LocalDate. How and where should I do the map/transformation of these three inputs to convert then in a LocalDate? Because obviously, the way the User is defined in the .js and the way is defined in .java are differents.
In the frontend
user.html
<div class="form-group">
<label class="col-sm-2 control-label">Date of Birth</label>
<div class="col-sm-10">
<div class="form-inline">
<div class="form-group">
<label class="sr-only" for="txt_day">Enter Day</label>
<div class="col-sm-2">
<input type="text" id="txt_day" ng-model="user.birthdate.day" class="form-control" placeholder="DD" required maxlength="2"
data-validation-required-message="Day is required">
</div>
</div>
<div class="form-group">
<label class="sr-only" for="txt_month">Enter Month</label>
<div class="col-sm-2">
<input type="text" id="txt_month" ng-model="user.birthdate.month" class="form-control" placeholder="MM" required
maxlength="2" data-validation-required-message="Month is required">
</div>
</div>
<div class="form-group">
<label class="sr-only" for="txt_year">Enter Year</label>
<div class="col-sm-2 ">
<input type="text" id="txt_year" ng-model="user.birthdate.year" class="form-control" placeholder="YY" required
maxlength="4" data-validation-required-message="Year is required">
</div>
</div>
</div>
</div>
</div>
userController.js
$scope.user = new UserService();
$scope.save = function() {
$scope.user.$save(function() {
$location.path('/');
});
};
UserService.js
return $resource('rest/user/:action', {},....
In the backend
UserController.java
#POST
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public User create(User user) {
LOGGER.info("create(): " + user);
return this.userDao.save(user);;
}
Entity
#Column(nullable = false)
#Convert(converter = LocalDatePersistenceConverter.class)
private LocalDate birthDate;
Well, honestly I see that your application is exposing your domain entities to outside through rest service. I wouldn't suggest to do in order to ensure separation of concern principle. This issue you are now having is because of that. If adding a services/dto layers is a bit cucumber in your application, one workaround could be:
#Entity
public class User{
#Column(nullable = false)
#Convert(converter = LocalDatePersistenceConverter.class)
private LocalDate birthDate;
#Transient
private birthDay
#Transient
private birthMonth
#Transient
private birth birthYear
..
#PrePersist
protected void prePersist()
{
birthDate = new LocalDate(birthDay, birthMonth, birthYear)
}
}
So your entity gets populated from your javascript component and the jpa provider makes the tweaking creating a jodatime object.
Hope this works
I have a page which allows an admin to add multiple users to the database at once, with usernames and passwords. I use remote username checking, so in my account models I have this:
public class RegisterModel
{
[Required]
[Display(Name = "User name")]
[System.Web.Mvc.Remote("doesUserNameExist", "Account", HttpMethod = "POST", ErrorMessage = "User name already exists. Please enter a different user name.")]
public string UserName { get; set; }
// ...etc
}
In my account controller I have this:
[HttpPost]
public JsonResult doesUserNameExist(string UserName)
{
var user = Membership.GetUser(UserName);
return Json(user == null);
}
Now this works nicely on a single form, with one user being created, using the mvc html helpers. The message saying that a username is in use shows up as I'm typing it into the field. But on this form which doesn't use the mvc helpers, instead pure html and javascript, it doesn't show up:
#model List<PicsWebApp.Models.UserRegisterModel>
#{
ViewBag.Title = "NewRep";
}
<div class="col-lg-12">
<h1>First-Time Setup</h1>
</div>
<legend></legend>
<div class="col-lg-12">
<h3>Add Representatives <small>You can add more representatives later</small></h3>
#using (Html.BeginForm("NewRep", "Admin", new { jobid = ViewData["jobid"] }, FormMethod.Post, new { #class = "form" }))
{
#Html.ValidationSummary(true)
<p>
Skip
<input type="submit" class="btn btn-primary" />
<button type="button" class="btn btn-default" id="add">Add Rep</button>
</p>
<div id="fieldform" class="col-lg-12">
<div id="container0" class="col-lg-4" style="background-color: #eee; border: 4px solid white;">
<h4 style="text-align: center">New Rep</h4>
<div class="form-group">
<label>Representative Name</label><input type="text" class="form-control" name="models[0].RepName" />
</div>
<div class="form-group">
<label>Contact Number</label><input type="text" class="form-control" name="models[0].ContactNumber" />
</div>
<div class="form-group">
<label>Rep Username</label><input type="text" class="form-control" name="models[0].UserName" />
<input class="text-box single-line" data-val="true" data-val-remote="User name already exists. Please enter a different user name." data-val-remote-additionalfields="*.UserName" data-val-remote-type="POST" data-val-remote-url="/Account/doesUserNameExist" data-val-required="The User name field is required." id="UserName" name="models[0].UserName" type="text">
<span class="field-validation-valid" data-valmsg-for="UserName" data-valmsg-replace="true"></span>
</div>
<div class="form-group">
<label>Location</label><select id="select-0" class="form-control" name="models[0].Location"></select>
</div>
<div class="form-group">
<label>Rep Password</label><input type="password" class="form-control" name="models[0].Password" />
</div>
<div class="form-group">
<label>Confirm Password</label><input type="password" class="form-control" name="models[0].ConfirmPassword" />
</div>
</div>
</div>
}
</div>
<script src="https://code.jquery.com/jquery.js"></script>
<script type="text/javascript">
var selectValues = {
"Johannesburg": "Johannesburg",
"Pretoria": "Pretoria",
"Cape Town": "Cape Town",
"Pretoria": "Pretoria",
"Durban": "Durban",
"Centurion": "Centurion"
};
$.each(selectValues, function (key, value) {
$('select')
.append($('<option>', { value: key })
.text(value))
.prop("selectedIndex", -1);
});
var i = 1;
$("#add").click(function () {
var inputfield = '<div class="col-lg-4" id="' + container + i + '" style="background-color: #eee; border: 4px solid white;"><h4 style="text-align: center">New Rep</h4><div class="form-group"><label>Representative Name</label><input type="text" class="form-control" name="models[' + i + '].RepName" /></div>' +
'<div class="form-group"><label>Contact Number</label><input type="text" class="form-control" name="models[' + i + '].ContactNumber" /></div>' +
'<div class="form-group"><label>Rep Username</label><input type="text" class="form-control" name="models[' + i + '].UserName" /></div>' +
'<div class="form-group"><label>Location</label><select id="select-' + i + '" class="form-control" name="models[' + i + '].Location"></select></div>' +
'<div class="form-group"><label>Rep Password</label><input type="password" class="form-control" name="models[' + i + '].Password" /></div>' +
'<div class="form-group"><label>Confirm Password</label><input type="password" class="form-control" name="models[' + i + '].ConfirmPassword"/></div></div>'
$('#fieldform').append(inputfield)
$.each(selectValues, function (key, value) {
$('#select-' + i)
.append($('<option>', { value: key })
.text(value))
.prop("selectedIndex", -1);
});
i++
});
</script>
I understand I would have to add validation attributes to the username input fields but I tried that and it still didn't work. Any ideas?
You could validate in your controller (when the ajax request is sent) using the Validator class. This class allows you to call Validator.TryValidateObject to validate your model's data annotations such as [Required].
Then once this has passed/failed return the constructed json data with success/error messages.
Here is the article that really helped me on this:
http://odetocode.com/blogs/scott/archive/2011/06/29/manual-validation-with-data-annotations.aspx