AngularJS Form Validation inside an ng-repeat - validation

So I am trying to validate the input of one item inside of an ng-repeat. For examples sake lets say that I have 5 items (1,2,3,4,5) and I only want to validate the form if the 4th item is selected.
I have used ng-pattern before to validate forms, but not one that had a dropdown menu to select item.name
I have included the regex I would like the 4th item to be validated with inside the ng-pattern.
<div>
<select name="name" ng-model="item.name" ng-options="item for item in items" required></select>
</div>
<div>
<input name="results" type="text" ng-model="item.results" ng-pattern="/^\d\d\d\/\d\d\d/" required>
</div>
Any suggestions as to the correct way to validate this situation would be greatly appreciated. I have thought about creating a directive to validate this, but that feels like is an overly complicated solution to this since I would not use the directive more than once in this app.
//////////////////////////////////////////////////
It wouldn't let me answer my own question so here is the answer I figured out.
What I ended up having to do was use ng-pattern and pass it a function.
<input name="results" type="text" ng-model="vital.results" ng-pattern="vitalRegEx()" required>
Here is the controller code
$scope.item4RegEx = /^\d{2,3}\/\d{2,3}$/;
$scope.itemRegEx = function() {
if($scope.item && $scope.item.name === "fourth item")
return $scope.item4RegEx;
else return (/^$/);
};

or else...
add ng-change directive on the select dropdown which calls a Controller method and that controller method sets a flag whether to validate form or not.
eg.
<select ng-change="checkIfFormShouldbeValidated()" ng-model="item.name"></select>
// Inside controller
$scope.checkIfFromShouldBeValidated = function(){
if( $scope.item.name == 4th Item ) $scope.shouldValidate = true;
else $scope.shouldValidate = false;
};
$scope.formSubmit = function(){
if(($scope.shouldValidate && form.$valid) || (!$scope.shouldValidate)){
// Submit Form
}
};
See if it helps.

I wrote this recursive function inside my controller to check the validity of all child scopes.
function allValid(scope) {
var valid = true;
if (scope.$$childHead) {
valid = valid && allValid(scope.$$childHead);
}
if (scope.$$nextSibling) {
valid = valid && allValid(scope.$$nextSibling);
}
if (scope.scorePlannerForm) {
valid = valid && scope.myForm.$valid;
}
return valid;
}
Then in my controller I check this with the controller scope.
function formSubmit() {
if (allValid($scope)) {
// perform save
}
}

Related

Laravel : send value of check box to controller without posting?

I'm using Laravel for my application.
I have made a PRINT button on my HTML page which is simply calling a route, to be able to send it throught DOMPDF to print it to PDF.
Print
Now, in my Controller, I would like to get the value of a radio button which has been created in the HTML this way
<div class="col-lg-7 selectie">
<input type="radio" name="factuur_selectie" id="factuur_selectie" value="1" checked> Alle facturen&nbsp
<input type="radio" name="factuur_selectie" id="factuur_selectie" value="2"> Betaalde facturen
<input type="radio" name="factuur_selectie" id="factuur_selectie" value="3"> Onbetaalde facturen
</div>
In my controller I can not find the way to get the value of the checkbox, I suppose because I'm not doing a submit ?
How would I be able to get the value of the radio button please?
public function printFacturen(Request $request){
}
I already tried three following ways, but it is not working :
$fields = Input::get('factuur_selectie');
$value = $request->get('factuur_selectie');
$request->input('factuur_selectie');
Bestregards,
Davy
You need to grab the value of factuur_selectie using JavaScript and add it to the generated URL, something like:
var val = document.querySelector('#factuur_selectie:checked').value;
var btn = document.querySelector('.btn.btn-default');
var url = btn.getAttribute('href');
btn.setAttribute('href', url + '?factuur_selectie=' + val);
Then you should be able to retrieve factuur_selectie from your controller.
Probably you are going to need to update the value everytime an option is selected. In that case you can retrieve the value from then event itself:
var btn = document.querySelector('.btn.btn-default');
document.querySelector('.selectie').addEventListener('change', function(event) {
var val = event.target.value;
var url = btn.getAttribute('href');
var pos = url.indexOf('?');
// If URL already contains parameters
if(pos >= 0) {
// Remove them
url = url.substring(0, pos);
}
btn.setAttribute('href', url + '?factuur_selectie=' + val);
});
Here you have a working example.

AngularJS: integrating with server-side validation

I have an angular app that contains a save button taken from the examples:
<button ng-click="save" ng-disabled="form.$invalid">SAVE</button>
This works great for client side validation because form.$invalid becomes false as user fixes problems, but I have an email field which is set invalid if another user is registered with same email.
As soon as I set my email field invalid, I cannot submit the form, and the user has no way to fix that validation error. So now I can no longer use form.$invalid to disable my submit button.
There must be a better way
This is another case where a custom directive is your friend. You'll want to create a directive and inject $http or $resource into it to make a call back to the server while you're validating.
Some pseudo code for the custom directive:
app.directive('uniqueEmail', function($http) {
var toId;
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, elem, attr, ctrl) {
//when the scope changes, check the email.
scope.$watch(attr.ngModel, function(value) {
// if there was a previous attempt, stop it.
if(toId) clearTimeout(toId);
// start a new attempt with a delay to keep it from
// getting too "chatty".
toId = setTimeout(function(){
// call to some API that returns { isValid: true } or { isValid: false }
$http.get('/Is/My/EmailValid?email=' + value).success(function(data) {
//set the validity of the field
ctrl.$setValidity('uniqueEmail', data.isValid);
});
}, 200);
})
}
}
});
And here's how you'd use it in the mark up:
<input type="email" ng-model="userEmail" name="userEmail" required unique-email/>
<span ng-show="myFormName.userEmail.$error.uniqueEmail">Email is not unique.</span>
EDIT: a small explanation of what's happening above.
When you update the value in the input, it updates the $scope.userEmail
The directive has a $watch on $scope.userEmail it set up in it's linking function.
When the $watch is triggered it makes a call to the server via $http ajax call, passing the email
The server would check the email address and return a simple response like '{ isValid: true }
that response is used to $setValidity of the control.
There is a in the markup with ng-show set to only show when the uniqueEmail validity state is false.
... to the user that means:
Type the email.
slight pause.
"Email is not unique" message displays "real time" if the email isn't unique.
EDIT2: This is also allow you to use form.$invalid to disable your submit button.
I needed this in a few projects so I created a directive. Finally took a moment to put it up on GitHub for anyone who wants a drop-in solution.
https://github.com/webadvanced/ng-remote-validate
Features:
Drop in solution for Ajax validation of any text or password input
Works with Angulars build in validation and cab be accessed at formName.inputName.$error.ngRemoteValidate
Throttles server requests (default 400ms) and can be set with ng-remote-throttle="550"
Allows HTTP method definition (default POST) with ng-remote-method="GET"
Example usage for a change password form that requires the user to enter their current password as well as the new password.:
<h3>Change password</h3>
<form name="changePasswordForm">
<label for="currentPassword">Current</label>
<input type="password"
name="currentPassword"
placeholder="Current password"
ng-model="password.current"
ng-remote-validate="/customer/validpassword"
required>
<span ng-show="changePasswordForm.currentPassword.$error.required && changePasswordForm.confirmPassword.$dirty">
Required
</span>
<span ng-show="changePasswordForm.currentPassword.$error.ngRemoteValidate">
Incorrect current password. Please enter your current account password.
</span>
<label for="newPassword">New</label>
<input type="password"
name="newPassword"
placeholder="New password"
ng-model="password.new"
required>
<label for="confirmPassword">Confirm</label>
<input ng-disabled=""
type="password"
name="confirmPassword"
placeholder="Confirm password"
ng-model="password.confirm"
ng-match="password.new"
required>
<span ng-show="changePasswordForm.confirmPassword.$error.match">
New and confirm do not match
</span>
<div>
<button type="submit"
ng-disabled="changePasswordForm.$invalid"
ng-click="changePassword(password.new, changePasswordForm);reset();">
Change password
</button>
</div>
</form>
I have created plunker with solution that works perfect for me. It uses custom directive but on entire form and not on single field.
http://plnkr.co/edit/HnF90JOYaz47r8zaH5JY
I wouldn't recommend disabling submit button for server validation.
Ok. In case if someone needs working version, it is here:
From doc:
$apply() is used to enter Angular execution context from JavaScript
(Keep in mind that in most places (controllers, services)
$apply has already been called for you by the directive which is handling the event.)
This made me think that we do not need: $scope.$apply(function(s) { otherwise it will complain about $digest
app.directive('uniqueName', function($http) {
var toId;
return {
require: 'ngModel',
link: function(scope, elem, attr, ctrl) {
//when the scope changes, check the name.
scope.$watch(attr.ngModel, function(value) {
// if there was a previous attempt, stop it.
if(toId) clearTimeout(toId);
// start a new attempt with a delay to keep it from
// getting too "chatty".
toId = setTimeout(function(){
// call to some API that returns { isValid: true } or { isValid: false }
$http.get('/rest/isUerExist/' + value).success(function(data) {
//set the validity of the field
if (data == "true") {
ctrl.$setValidity('uniqueName', false);
} else if (data == "false") {
ctrl.$setValidity('uniqueName', true);
}
}).error(function(data, status, headers, config) {
console.log("something wrong")
});
}, 200);
})
}
}
});
HTML:
<div ng-controller="UniqueFormController">
<form name="uniqueNameForm" novalidate ng-submit="submitForm()">
<label name="name"></label>
<input type="text" ng-model="name" name="name" unique-name> <!-- 'unique-name' because of the name-convention -->
<span ng-show="uniqueNameForm.name.$error.uniqueName">Name is not unique.</span>
<input type="submit">
</form>
</div>
Controller might look like this:
app.controller("UniqueFormController", function($scope) {
$scope.name = "Bob"
})
Thanks to the answers from this page learned about https://github.com/webadvanced/ng-remote-validate
Option directives, which is slightly less than I do not really liked, as each field to write the directive.
Module is the same - a universal solution.
But in the modules I was missing something - check the field for several rules.
Then I just modified the module https://github.com/borodatych/ngRemoteValidate
Apologies for the Russian README, eventually will alter.
I hasten to share suddenly have someone with the same problem.
Yes, and we have gathered here for this...
Load:
<script type="text/javascript" src="../your/path/remoteValidate.js"></script>
Include:
var app = angular.module( 'myApp', [ 'remoteValidate' ] );
HTML
<input type="text" name="login"
ng-model="user.login"
remote-validate="( '/ajax/validation/login', ['not_empty',['min_length',2],['max_length',32],'domain','unique'] )"
required
/>
<br/>
<div class="form-input-valid" ng-show="form.login.$pristine || (form.login.$dirty && rv.login.$valid)">
From 2 to 16 characters (numbers, letters and hyphens)
</div>
<span class="form-input-valid error" ng-show="form.login.$error.remoteValidate">
<span ng:bind="form.login.$message"></span>
</span>
BackEnd [Kohana]
public function action_validation(){
$field = $this->request->param('field');
$value = Arr::get($_POST,'value');
$rules = Arr::get($_POST,'rules',[]);
$aValid[$field] = $value;
$validation = Validation::factory($aValid);
foreach( $rules AS $rule ){
if( in_array($rule,['unique']) ){
/// Clients - Users Models
$validation = $validation->rule($field,$rule,[':field',':value','Clients']);
}
elseif( is_array($rule) ){ /// min_length, max_length
$validation = $validation->rule($field,$rule[0],[':value',$rule[1]]);
}
else{
$validation = $validation->rule($field,$rule);
}
}
$c = false;
try{
$c = $validation->check();
}
catch( Exception $e ){
$err = $e->getMessage();
Response::jEcho($err);
}
if( $c ){
$response = [
'isValid' => TRUE,
'message' => 'GOOD'
];
}
else{
$e = $validation->errors('validation');
$response = [
'isValid' => FALSE,
'message' => $e[$field]
];
}
Response::jEcho($response);
}

Pass multiple values during change event of <select>

Dropdown list 1:
<select name="ddlArea" onchange="showState(this.value)">
Dropdown list 2:
<select name="ddlFType" onchange="showState(this.value)">
I want to get selected values of both dropdown lists in showState function. I want to invoke same function as the ajax request will be processing on different page which requires both the parameters Area and FType.
Function:
function showState()
{
var area_value = document.getElementById("ddlArea").value;
var ftype= document.getElementById("ddlFType").value;
if(document.getElementById("ddlFlat").value!="-1" )
{
xmlHttp=GetXmlHttpObject()
if (xmlHttp==null)
{
alert ("Browser does not support HTTP Request")
return
}
var url="vacant_do.jsp"
url=url+"?area_id="+area_value+"ftype"+ftype
xmlHttp.onreadystatechange=stateChange
xmlHttp.open("GET",url,true)
xmlHttp.send(null)
}
else
{
alert("Please Select Area Name");
}
}
function stateChange()
{
if (xmlHttp.readyState==4 || xmlHttp.readyState==200)
{
document.getElementById("ddlFlat").innerHTML=xmlHttp.responseText
}
}
Since you are just trying to invoke an AJAX Query with 2 parameters, I wouldn't push in the value through the onChange.
Combo -Box 1:-
<select name="ddlArea" id="ddlArea" onchange="showState()">
Combo -Box 2:-
<select name="ddlFType" id="ddlFType" onchange="showState()">
and then:
function showState() {
var area = document.getElementById('ddlArea').value;
var fType= document.getElementById('ddlFType').value;
....invokeAJAX request....
}
You COULD do this via some sort of prototype/closure method, but really, you already know where to get the information from, and it's not going to change on the page

Issue with validation on Razor View in MVC

I am building wizard step demo application with MVC3 and using razor view engine as begineer level.
I came across on problem with validation when hide & show control through javascript.
Please look my code section as per below
<div class="editor-field">
#Html.EditorFor(model => model.CheckName2)
#Html.ValidationMessageFor(model => model.CheckName2)
</div>
my javascript function as per below, hide & show on some condition
// attach nextStep button handler
$("#next-step").click(function () {
var $step = $(".wizard-step:visible"); // get current step
//check if URL2 is having any content
var val = $("#URL2").val();
if (val == "") {
$("#CheckName2").hide();
//want to remove validation here
}
else {
$("#CheckName2").show();
//want to add validation here
}
var validator = $("form").validate(); // obtain validator
var anyError = false;
$step.find("input").each(function () {
if (!validator.element(this)) { // validate every input element inside this step
anyError = true;
}
});
if (anyError)
return false; // exit if any error found
How can i handle validation here?
thanks in advance.
Force validation of your form with $("#IdOfFormYouEdit").validate(); or $("#IdOfTextbox").validate(); to validate only one element.
This is possible if you have put validation attributes on your view model classes. If you don't you can add validation on an element by adding the data-val attributes. For example:
<input type="text" data-val="true" data-val-number="The field someField must be a number." data-val-required="SourceId required" />

Correct way to bind an mvc3 radiobutton to a model

I have a view that contains a radiobutton list for my terms and conditions of the site.
e.g.
Yes
#Html.RadioButtonFor(model => model.TermsAndConditions, "True")
No
#Html.RadioButtonFor(model => model.TermsAndConditions, "False",
new { Checked = "checked" })
</div>
#Html.ValidationStyledMessageFor(model => model.TermsAndConditions)
All is ok if the user completes the form without any errors however if I do serverside validation and the page is refreshed I lose the selection that the user made for the radiobutton and the selected radio goes back to the default false field.
How am I meant to be binding the radiobutton so if a user selects true this value is maintained even after serverside validation?
Any suggestions would be great!
For the short answer, you need to do three things:
Remove the new { Checked = "checked" } from the second radio button. This hard-coded checked value will override all of the magic.
When you return your ViewResult from the controller action, give it an instance of your model class where TermsAndConditions is false. This will provide the default false value you need in order to have the false radio button preselected for you.
Use true and false as the values for your radio buttons instead of "True" and "False". This is because your property is of type bool. Strictly speaking, you coincidentally chose the correct string representations for true and false, but the value parameter for the RadioButtonFor method is of type object. It's best to pass in the actual type you want to compare to rather than converting it to a string yourself. More on this below.
Here's what's going on in depth:
The framework wants to do all of this for you automatically, but you did those first two things incorrectly which makes you have to fight with the framework to get the behavior you want.
The RadioButtonFor method calls .ToString() on the value of the property you specified and compares it to the .ToString() of the value you passed in when creating the radio button. If they are equal, then it internally sets isChecked = true and ends up rendering checked="checked" in the HTML. This is how it decides which radio button to check. It simply compares the value of the radio button to the value of the property and checks the one that matches.
You can render radio buttons for pretty much any property this way and it will magically work. Strings, ints, and even enum types all work! Any object that has a ToString method that returns a string which uniquely represents the object's value will work. You just have to make sure you're settings the radio button's value to a value that your property might actually have. The easiest way to do this is just to pass in the value itself, not the string representation of the value. Let the framework convert it to a string for you.
(Since you happened to pass in the correct string representations of true and false, then those values will work as long as you fix your two actual mistakes, but it's still wise to pass in the actual values and not their strings.)
Your first real mistake was hard-coding Checked = "checked" for the "No" radio button. This will override what the framework is trying to do for you and results in this radio button always being checked.
Obviously you want the "No" radio button to be preselected, but you have to do it in a way that's compatible with everything above. You need to give the view an instance of your model class where TermsAndConditions is set to false, and let it "bind" that to the radio buttons. Normally, a controller action which responds to the initial GET request of a URL doesn't give the View an instance of the model class at all. Typically, you just return View();. However, since you want a default value selected, you must provide the view with a instance of your model that has TermsAndConditions set to false.
Here is some source code illustrating all of this:
Some sort of Account class that you probably already have. (Your View's model):
public class Account
{
public bool TermsAndConditions { get; set; }
//other properties here.
}
Some methods in your controller:
//This handles the initial GET request.
public ActionResult CreateAccount()
{
//this default instance will be used to pre-populate the form, making the "No" radio button checked.
var account = new Account
{
TermsAndConditions = false
};
return View( account );
}
//This handles the POST request.
[HttpPost]
public ActionResult CreateAccount( Account account )
{
if ( account.TermsAndConditions )
{
//TODO: Other validation, and create the account.
return RedirectToAction( "Welcome" );
}
else
{
ModelState.AddModelError( "TermsAndConditionsAgreement", "You must agree to the Terms and Conditions." );
return View( account );
}
}
//Something to redirect to.
public ActionResult Welcome()
{
return View();
}
The entire View:
#model Account
#{
ViewBag.Title = "Create Account";
}
#using ( Html.BeginForm() )
{
<div>
<span>Do you agree to the Terms and Conditions?</span>
<br />
#Html.RadioButtonFor( model => model.TermsAndConditions, true, new { id = "TermsAndConditions_true" } )
<label for="TermsAndConditions_true">Yes</label>
<br />
#Html.RadioButtonFor( model => model.TermsAndConditions, false, new { id = "TermsAndConditions_false" } )
<label for="TermsAndConditions_false">No</label>
<br />
#Html.ValidationMessage( "TermsAndConditionsAgreement" )
</div>
<div>
<input id="CreateAccount" type="submit" name="submit" value="Create Account" />
</div>
}
BONUS: You'll notice that I added a little extra feature to the radio buttons. Rather than just use plain text for the radio button labels, I used the HTML label element with the for attribute set to the IDs of the each radio button. This lets users click on the label to select the radio button instead of having to click on the radio button itself. This is standard HTML. For this to work I had to set manual IDs on the radio buttons, otherwise they would both get the same ID of just "TermsAndConditions", which wouldn't work.
There are a few things you need to do here in order to ensure the user's selection is maintained after server side validation.
a) Bind the "checked" property of each radio to your model in the view, for example:
Yes
#Html.RadioButtonFor(model => model.TermsAndConditions, "True", model.TermsAndConditions == true ? new { Checked = "checked" } : null)
No
#Html.RadioButtonFor(model => model.TermsAndConditions, "False", model.TermsAndConditions == false ? new { Checked = "checked" } : null)
b) To define the initial default value when the view is first displayed, initialise the model returned to the view in the GET request (in the controller action), for example:
public ActionResult SomeForm()
{
return View(new SomeModel { TermsAndConditions = false });
}
b) Ensure in your [HttpPost] controller action that you return the model when the validation fails, for example:
[HttpPost]
public ActionResult SomeForm(SomeModel model)
{
if (!ModelState.IsValid)
return View(model);
// Do other stuff here
}
This way when the view is rendered in the response after validation fails, it will have the actual model state that was passed in (thus maintaining the user's selection).
I can't really tell since you haven't shown your code, but I suspect that if you're failing on server side validation you're just returning the raw view. When it fails, you need to populate the view with the model that was submitted, same as if you were returning any other validation errors. Otherwise you'll get the default model values (which will always be false for the registration boolean).
Maybe you could post your server side code?
Here I am offering another more complex example.
public enum UserCommunicationOptions
{
IPreferEmailAndSMS = 1,
IPreferEmail = 2,
IPreferSMS = 3
}
Html
#model UserProfileView
// Some other code
<div class="form-group">
<label class="col-lg-2 control-label">Communication</label>
<div class="col-lg-10">
<div class=" col-xs-">
#if (Model.UserCommunicationOption.ToString() == UserCommunicationOptions.IPreferEmailAndSMS.ToString())
{
#Html.RadioButtonFor(x => x.UserCommunicationOption, (int)UserCommunicationOptions.IPreferEmailAndSMS, new { #checked = "checked" })
}
else
{
#Html.RadioButtonFor(x => x.UserCommunicationOption, (int)UserCommunicationOptions.IPreferEmailAndSMS)
}
<label class=" control-label" for="#Model.UserCommunicationOption">I Prefer Email And SMS</label>
</div>
<div class=" col-xs-">
#if (Model.UserCommunicationOption.ToString() == UserCommunicationOptions.IPreferEmail.ToString())
{
#Html.RadioButtonFor(x => x.UserCommunicationOption, (int)UserCommunicationOptions.IPreferEmail, new { #checked = "checked" })
}
else
{
#Html.RadioButtonFor(x => x.UserCommunicationOption, (int)UserCommunicationOptions.IPreferEmail)
}
<label class=" control-label" for="#Model.UserCommunicationOption">I Prefer Email</label>
</div>
<div class=" col-xs-">
#if (Model.UserCommunicationOption.ToString() == UserCommunicationOptions.IPreferSMS.ToString())
{
#Html.RadioButtonFor(x => x.UserCommunicationOption, (int)UserCommunicationOptions.IPreferSMS, new { #checked = "checked" })
}
else
{
#Html.RadioButtonFor(x => x.UserCommunicationOption, (int)UserCommunicationOptions.IPreferSMS)
}
<label class=" control-label" for="#Model.UserCommunicationOption">#DLMModelEntities.Properties.Resource.IPreferSMS</label>
</div>
</div>
</div>
Model
[Required(ErrorMessageResourceName = "Communications", ErrorMessageResourceType = typeof(Resource))]
[Display(Name = "Communications", ResourceType = typeof(DLMModelEntities.Properties.Resource))]
public UserCommunicationOptions UserCommunicationOption { get; set; }
GET
var client = AppModel.Clients.Single(x => x.Id == clientId);
if (Convert.ToBoolean(client.IsEmailMessage) && Convert.ToBoolean(client.IsSMSMessage))
{
model.UserCommunicationOption = UserCommunicationOptions.IPreferEmailAndSMS;
}
else if (Convert.ToBoolean(client.IsEmailMessage))
{
model.UserCommunicationOption = UserCommunicationOptions.IPreferEmail;
}
else if ( Convert.ToBoolean(client.IsSMSMessage))
{
model.UserCommunicationOption = UserCommunicationOptions.IPreferSMS;
}
POST
[HttpPost]
public ActionResult MyProfile(UserProfileView model)
{
// Some code
var client = AppModel.Clients.Single(x => x.Id == clientId);
if (model.UserCommunicationOption == UserCommunicationOptions.IPreferEmail)
{
client.IsSMSMessage = false;
client.IsEmailMessage = true;
}
else if (model.UserCommunicationOption == UserCommunicationOptions.IPreferEmailAndSMS)
{
client.IsSMSMessage = true;
client.IsEmailMessage = true;
}
else if (model.UserCommunicationOption == UserCommunicationOptions.IPreferSMS)
{
client.IsSMSMessage = true;
client.IsEmailMessage = false;
}
AppModel.SaveChanges();
//Some code
}
Database
Webpage
I had a similar issue and solved the problem by setting a ViewData value in controller to keep track of what the user had selected.

Resources