Aurelia validating nested objects - aurelia

I'm trying to validate an object property of an Aurelia ViewModel.
ViewModel
#autoinject
class AddUserForm {
user: User;
controller: ValidationController;
constructor(controllerFactory: ValidationControllerFactory) {
this.controller = controllerFactory.createForCurrentScope();
}
validate() {
this.controller.validate.then(res => {
console.log(res.valid);
})
}
}
ValidationRules
.ensure((u: User) => u.id).displayName('User').required()
.on(AddUserForm)
ViewModel -> View
<template>
<form click.trigger="validate()">
<input type="text" value.bind="user.id & validate" />
</form>
</template>
User
class User {
id: string
}
The issue I'm having is that the validator is not picking up the nested user object. I'm I missing something to get this working? I read the docs and it seems like this should work. I'm using version ^1.0.0 of the plugin.

The problem is in your ValidationRules:
ValidationRules
.ensure((u: User) => u.id).displayName('User').required()
.on(AddUserForm)
needs to be
ValidationRules
.ensure((u: User) => u.id).displayName('User').required()
.on(User)
Then to get the controller to run this rule you either need to include "& validate" somewhere in your value.bind for that property, like this:
<input value.bind="user.id & validate" />
or before you call controller.validate(), add the entire object to the controller like this:
this.controller.addObject(this.user);
I use .addObject all the time because it causes validation to run on properties that aren't included in your markup, and I find I prefer that.

This caused an error when I tried it:
validate() {
this.controller.validate(res => {
console.log(res.valid);
})
}
.validate() expects a ValidateInstruction, in your example you're giving (res: any) => void. I would try changing to this instead:
this.controller.validate().then(res => {
console.log(res.valid);
});
Leaving .validate() undefined will cause it to validate all objects and bindings, and .then() will execute after that validation has completed.
This worked for me when I tried it in my test project.
If I misunderstood your question and this alone does not solve it however, you could also try assigning the User objects id to a property in AddUserForm like this:
public userId = this.user.id;
And changing your ValidationRules and view accordingly:
ViewModel
ValidationRules
.ensure((u: AddUserForm) => u.userId)
.displayName("User")
.required()
.on(this);
View
<template>
<form click.delegate="validate()">
<input type="text" value.bind="userId & validate" />
</form>
</template>

Related

Call action method from view to automatically populate form value

I have a form which is used to create a resource, and it has two fields to be populated. When a user enters a value into the first field, I want to automatically call back to an action method on the server which will determine the value to use in the second field, without the user having to submit the form.
For example:
Full Name - User enters this value
Username - View calls server with the value specified in Full Name, server calculates value to be used, server passes value back to view, view presents the value.
Is it possible to do this in MVC core, and if so, can you please point the right direction?
I've been reading up on remote validation, and feel that I could probably use (or abuse) it in order to achieve the functionality looking for, but I'd imagine there's a property way to do this.
Any pointers appreciated.
Remote Validation can only do the validation without submitting the form, but It can't assign value to another field. In your case. It's actually very simple. You can use the js onchange event listen to the first field, in the event, use ajax to access the background. and then fill the returned value into the second field in the callback function. Below is a simple test
View:
<span>Full Name</span>
<input type="text" id="FullName" name="FullName" />
<span>User Name</span>
<input type="text" id="UserName" name="UserName" />
#section scripts{
<script>
$("#FullName").on("change", function () {
$.ajax({
type: "post",
url: "/User/GetUserName",
data: {
fullname: $("#FullName").val()
},
success: function (result) {
$("#UserName").val(result);
}
})
})
</script>
}
Controller:
[HttpPost]
public string GetUserName(string fullname)
{
var username = fullname.Split(" ");
return username.First();
}
Result:
#mj1313's answer was extremely helpful, but I didnt like the idea of having some loose Javascript kicking around in my views, especially since I may need to use this same functionality in multiple views.
I ended up converting the script into a global function, and calling it from within the onchange event like so.
JS Function
(in site.js)
function fullNameToUserName (fullNameId, usernameId) {
$.ajax({
type: "get", // GET rather than POST
url: "/User/GetUserNameFromFullName",
data: {
fullname: $(fullNameId).val()
},
success: function (result) {
$(usernameId).val(result);
}
})
}
Action method
[HttpGet("GetUserNameFromFullName")] // GET rather than POST
public IActionResult GetUserNameFromFullName(string fullName)
{
var username = fullName.Split(" ");
return Ok(username.First());
}
Model
public class UserModel
{
public string FullName { get; set; }
public string Username { get; set; }
}
View
#model MvcApp.Models.UserModel
#Html.DisplayNameFor(m => m.FullName)
#Html.TextBoxFor(m => m.FullName, new
{
onchange = $"fullNameToUserName({#Html.IdFor(m => m.FullName)}, {#Html.IdFor(m => m.Username)});"
})
#Html.DisplayNameFor(m => m.Username)
#Html.TextBoxFor(m => m.Username)
The only problem I have with this approach is that the call to fullNameToUserName(fullNameId, usernameId) is not strongly-typed, and if this function is called from multiple views, it's likely to be miss-typed at some point.
While #mj1313's answer was great and pointed me in the right direction, I wasn't fully satisfied with the approach. Personally, I prefer this approach as it's slightly more reusable and keeps my view's more lean.

Angular6 Material - using Stepper with Input with a custom ErrorStateMatcher

This seems like it should be pretty straight forward... within a stepper, you're collecting info, and you want to make sure an email is an email. But it seems like the shared 'form' tag causes some issues where the error checker gets messed up and doesn't work?
Further clarification... the issue seems to actually be in the following tag element...
formControlName="emailCtrl"
When I remove this line, and remove it's sibling line from the .ts (emailCtrl: ['', Validators.required],) the error check starts working. However, that means that the stepper can't verify that this step is required.
How can I make sure the stepper validates an entry and at the same time make sure that the ErrorStateMatcher works?
Here is my combined HTML...
<mat-step [stepControl]="infoFormGroup">
<form [formGroup]="infoFormGroup">
<ng-template matStepLabel>Profile Information</ng-template>
<div>
<!-- <form class="emailForm"> -->
<mat-form-field class="full-width">
<input matInput placeholder="Username" [formControl]="emailFormControl"
formControlName="emailCtrl"
[errorStateMatcher]="infoMatcher">
<mat-hint>Must be a valid email address</mat-hint>
<mat-error *ngIf="emailFormControl.hasError('email') && !emailFormControl.hasError('required')">
Please enter a valid email address for a username
</mat-error>
<mat-error *ngIf="emailFormControl.hasError('required')">
A username is <strong>required</strong>
</mat-error>
</mat-form-field>
<!-- </form> -->
</div>
<button mat-button matStepperPrevious>Back</button>
<button mat-button matStepperNext>Next</button>
</form>
</mat-step>
As you can see, I have commented out the nested 'form' for the email slot. In testing, I have tried it commented and not commented out. Either way, the error checking doesn't work right.
Here are some of the pertinent .ts snippets...
import { FormControl, FormGroupDirective, NgForm, Validators } from '#angular/forms';
import { FormBuilder, FormGroup } from '#angular/forms';
import { ErrorStateMatcher } from '#angular/material/core';
export class Pg2ErrorStateMatcher implements ErrorStateMatcher {
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
const isSubmitted = form && form.submitted;
return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted));
}
}
...
export class Pg2Dialog {
...
emailFormControl = new FormControl('', [
Validators.required,
Validators.email,
]);
infoMatcher = new Pg2ErrorStateMatcher();
...
this.infoFormGroup = this._formBuilder.group({
emailCtrl: ['', Validators.required],
});
I believe I figured this out. the ErrorStateMatcher requires a named form control. In this case, it's emailFormControl. This is declared as the following...
emailFormControl = new FormControl('', [
Validators.required,
Validators.email,
]);
Also, the stepper requires a named form group, that in itself declares a new form control. In this case, it was emailCtrl. It was declared as the following...
this.infoFormGroup = this._formBuilder.group({
emailCtrl: ['', Validators.required],
});
To have the stepper form control utilize the ErrorStateMatcher form control, simply drop the square brackets inside the .group assignment and assign emailFormControl to the emailCtrl. Like this...
this.infoFormGroup = this._formBuilder.group({
emailCtrl: this.emailFormControl
});
I tested this in a different code section with a similar problem and it worked in both places!

Aurelia: validating form with reusable validatable custom element

Short question: How can I validate a parent form when the validation is part of child custom elements?
Long version:
I built a reusable custom element which includes validation which is working like I expect it to do:
validated-input.html:
<template>
<div class="form-group" validate.bind="validation">
<label></label>
<input type="text" value.bind="wert" class="form-control" />
</div>
</template>
validated-input.js:
import { bindable, inject } from 'aurelia-framework';
import { Validation } from 'aurelia-validation';
#inject(Validation)
export class ValidatedInputCustomElement {
#bindable wert;
constructor(validation) {
this.validation = validation.on(this)
.ensure('wert')
.isNotEmpty()
.isGreaterThan(0);
}
}
I will have some forms that will use this custom element more than once in the same view (can be up to 8 or 12 times or even more). A very simplified example could look like this:
<template>
<require from="validated-input"></require>
<form submit.delegate="submit()">
<validated-input wert.two-way="val1"></validated-input>
<validated-input wert.two-way="val2"></validated-input>
<validated-input wert.two-way="val3"></validated-input>
<button type="submit" class="btn btn-default">save</button>
</form>
</template>
In the corresponding viewmodel file I would like to ensure that the data can only be submitted if everything is valid. I would like to do something like
this.validation.validate()
.then(() => ...)
.catch(() => ...);
but I don't understand yet how (or if) I can pull the overall validation into the parent view.
What I came up with up to now is referencing the viewmodel of my validated-input like this:
<validated-input wert.two-way="val1" view-model.ref="vi1"></validated-input>
and then to check it in the parent like this:
this.vi1.validation.validate()
.then(() => ...)
.catch(() => ...);
but this would make me need to call it 8/12/... times.
And I will probably have some additional validation included in the form.
Here is a plunkr with an example:
https://plnkr.co/edit/v3h47GAJw62mlhz8DeLf?p=info
You can define an array of validation objects (as Fabio Luz wrote) at the form level and then register the custom element validations in this array. The validation will be started on the form submit.
The form code looks like:
validationArray = [];
validate() {
var validationResultsArray = [];
// start the validation here
this.validationArray.forEach(v => validationResultsArray.push(v.validate()));
Promise.all(validationResultsArray)
.then(() => this.resulttext = "validated")
.catch(() => this.resulttext = "not validated");
}
validated-input.js gets a new function to register the validation
bind(context) {
context.validationArray.push(this.validation);
}
The plunker example is here https://plnkr.co/edit/X5IpbwCBwDeNxxpn55GZ?p=preview
but this would make me need to call it 8/12/... times.
And I will probably have some additional validation included in the form.
These lines are very important to me. In my opinion (considering that you do not want to call 8/12 times, and you also need an additional validation), you should validate the entire form, instead of each element. In that case, you could inject the validation in the root component (or the component that owns the form), like this:
import { Validation } from 'aurelia-validation';
import { bindable, inject } from 'aurelia-framework';
#inject(Validation)
export class App {
val1 = 0;
val2 = 1;
val3 = 2;
resulttext = "";
constructor(validation) {
this.validation = validation.on(this)
.ensure('val1')
.isNotEmpty()
.isGreaterThan(0)
.ensure('val2')
.isNotEmpty()
.isGreaterThan(0)
.ensure('val3')
.isNotEmpty()
.isGreaterThan(0);
//some additional validation here
}
validate() {
this.validation.validate()
.then(() => this.resulttext = "valid")
.catch(() => this.resulttext = "not valid");
}
}
View:
<template>
<require from="validated-input"></require>
<form submit.delegate="validate()" validation.bind="validation">
<validated-input wert.two-way="val1"></validated-input>
<validated-input wert.two-way="val2"></validated-input>
<validated-input wert.two-way="val3"></validated-input>
<button type="submit" class="btn btn-default">validate</button>
</form>
<div>${resulttext}</div>
</template>
Now, you can re-use the validated-input component in other places. And of course, you probably have to rename it, because the name validated-input does not make sense in this case.
Here is the plunker example https://plnkr.co/edit/gd9S2y?p=preview
Another approach would be pushing all the validation objects into an array and then calling a function to validate all validations objects, but that sounds strange to me.
Hope it helps!

Pass data from view to controller method

I need to use the name of the current view in a method in my controller
I am able to get the name with the code below in my view.
I will like to pass this #ViewData["pageName"] to my MakeChange action result
in my controller. Each time I step through the MakeChange method all I get
is "object reference not set to an instance of an object"
How can I pass data from my view to controller method ?
#ViewData["pageName"] = #Path.GetFileName(Server.MapPath(VirtualPath))
public ActionResult MakeChange(string lang)
{
string getPageName = ViewData["pageName"].ToString();
return RedirectToAction(getPageName, "Home");
}
You can't pass data from view to controller using ViewData. You can use ViewData to pass data from Controller to your view.
To achieve what you want, you can do as follows:
<input type='hidden' name='lang' value='#Path.GetFileName(Server.MapPath(VirtualPath))' />
<input type='submit' value='send'>
Ps: you should put the input's inside a form tag.
Path.GetFileName(Server.MapPath(VirtualPath)) will give you the razor view name with extension (Ex : index.cshtml). You can not use that with RedirectToAction method as RedirectToAction method is looking for an action method name. You need to trim down the file extension part before using it.
To send this to the controller action, you can keep the value inside a hidden field inside your form. When user posts the form, it will be available in your HttpPost action method. You need to make sure that there is a parameter which has same name as the hidden field's name value.
#using (Html.BeginForm())
{
<input type="text" name="lang" value="English" />
<input type="hidden" name="pageName"
value="#Path.GetFileName(Server.MapPath(VirtualPath))" />
<input id="BtnAdd" type="submit" value="Save" />
}
So your action method will be
public ActionResult MakeChange(string lang,string pageName)
{
var viewName=pageName;
//Get rid of the extension.
viewName = viewName.Replace(".cshtml","");
return RedirectToAction(viewName , "Home");
}
Even if you are doing an ajax post, it will still work, just serialize your form and send it
$("#BtnAdd").click(function(e){
e.preventDefault();
var _this = $(this);
$.post("#Url.Action("MakeChange","Home")",_this.closest("form").serialize(),
function(response){
//do something with response
});
});
There are more clean ways of getting the view name without the file extension trimming approach we did. Take a look at this answer.

Orchard Module - How to return strongly typed Model rathen than dynamic from Driver

I created a ContactUs module that sends email when user click on Submit button.
Everything works perfectly. However, I am curious if it is possible to return a strongly typed Model rather than dynamic class.
For example, following is my Drivers\ContactUsDriver.cs Display function:
protected override DriverResult Display(ContactUsPart part, string displayType, dynamic shapeHelper)
{
return ContentShape("Parts_ContactUs",
() => shapeHelper.Parts_ContactUs(
Name: part.Name));
}
As you can see, above is returning a dynamic Parts_ContactUs.
Now, here's snapshot of my Views\Parts\ContactUs.cshtml:
#model dynamic
#using (Html.BeginForm("Send", "ContactUs", new { area = "ContactUs" }, FormMethod.Post))
{
<fieldset>
<legend>Contact Us</legend>
<div id="contact-us" class="area">
#Html.TextBox("Name", "")
</div>
<div id="submitArea" class="button">
<input type="submit" value="Submit Message">
</div>
</fieldset>
}
As you can see above the View is bound to #model dynamic. As a result, I have to do following
#Html.TextBox("Name", "")
Is there a way I can bind to Model say ContactUsModel and thus do following instead?
#Html.TextBoxFor(m => m.Name)
Particularly, I am interested so I can write a jquery validation with DataAnnotation attribute.
It's perfectly possible. Just provide a desired model type as your first argument when creating a shape:
protected override DriverResult Display(
ContactUsPart part,
string displayType,
dynamic shapeHelper)
{
return ContentShape("Parts_ContactUs",
() => shapeHelper.Parts_ContactUs(typeof(MyClass), Name: part.Name));
}