Within multiple forms in my app, I am trying to use the same directive which is an email input field complete with validation (it sources its value from a list of recently used emails but that is not pertinent to this question). Because the form in which the directive is used is different depending on where I use it, the ng-messages attribute needs to be dynamically rendered.
I have managed to get ng-messages to dynamically set itself, but it is not working as expected. Here is the code:
form.html
<form name="joinForm">
<rt-recents-menu ng-model="vm.roomName"/>
</form>
directive.coffee
recents = angular.module 'rtRecents', ['ionic', 'ngMessages']
.factory('rtRecentsService', require './recentsService')
.directive('rtRecentsMenu', (rtRecentsService, $ionicActionSheet) ->
restrict: 'E'
template: require './recentsMenu.html'
scope:
ngModel: '=?'
require: ['?ngModel', '^form']
transclude: false
replace: false
link: (scope, element, attrs, ctrls) ->
ngModel = ctrls[0]
formCtrl = ctrls[1]
scope.formName = formCtrl.$name
module.exports = recents.name
directive.html
<div class="email-line">
<input type="email"
name="roomName"
required
ng-model="ngModel"
ng-keydown="onKeyDown()"/>
<div id="email-error-messages" ng-messages="{{formName}}.roomName.$error" multiple role="alert">
<div ng-message="required" class="warn" translate>VALIDATION.EMAIL_REQUIRED_ERROR</div>
<div ng-message="email" class="warn" translate>VALIDATION.EMAIL_INVALID_ERROR</div>
</div>
</div>
In my case, I have a that displays each of the returned rows from a db query. And each row had its own form within this repeat loop.
I ran into two problems:
The ngMessage errors didn't display correctly because they didn't seem to be tied to the right fields/form.
I couldn't disable the "save" button per-form. The $invalid didn't seem to work.
Here's what works (trimmed decorators, etc so you only see the essential points):
<div ng-repeat="row in rows | orderBy: 'sortBy'"
ng-init="formName = 'siteEdit' + row.id">
<form name="{{formName}}" ng-submit="view.startSave(row.id)">
<!-- I have "control" buttons at the top of my form to save, cancel, etc. here are the two related to saving -->
<span class="glyphicon glyphicon-save pull-right q-palette-color-2" ng-disabled="view.isPageBusy()"
ng-show="! this[formName].$invalid" ng-click="view.startSave(row.id)" title="Save"/>
<span class="glyphicon glyphicon-ban-circle pull-right q-palette-accent-2"
ng-show="this[formName].$invalid" style="padding-left:10px;cursor: pointer; cursor: hand;" data-toggle="tooltip" title="Correct Errors To Save"/>
<label for="siteName{{row.id}}"> Name:</label>
<input id="siteName{{row.id}}"
class="form-control input"
type="text"
placeholder="name"
ng-disabled="view.isPageBusy()"
ng-model="row.name"
name="name"
required />
<div ng-messages="formName.name.$error">
<div ng-message="required">This field is required</div>
</div>
.....
</form>
</div>
Key points:
ng-init in the ng-repeat builds the formName variable.
form validation for save/etc uses this[formName].$invalid
Field validation is referenced in the format of formName..$error
Something to note:
I couldn't get the ng-disable to actually disable the "button" based on it being invalid or not (the ng-click executed no matter what, maybe because I didn't use <button> but used <span> w/ glyphicon). So I opted to have one displayed when you can save, the other displayed when you can't. Works better this way since the person can visually see they can't save, and if they hover over they'll see they need to correct the form first.
Related
I am trying to set the select field in the view to be required. So I'm having the below tags in my view.html
<form name="homeForm">
<div class="form-group col-md-6 md-padding">
<div>
<label style="font-size: medium">Laboratory Name</label>
<select name="labName" class="form-control" ng-model="request.labName" required>
<option ng-repeat="lab in labList" value="{{lab.id}}">{{lab.value}}</option>
</select>
<div style="color:maroon" ng-messages="homeForm.labName.$error"
ng-if="homeForm.requestTypeName.$touched">
<div ng-message="required">This field is required</div>
</div>
</div>
I was hoping the This field is required will show up only when there is no data selected. But irrespective of the field has data or not This field is required shows up like below
Is there something I am missing here
You need to set the same input name in your input and in your ng-message. Doing this, you don't even need to have ng-if. For example:
<form name="form">
<input name="fullname" type="text" ng-model="fullname" required/>
<div ng-messages="form.$submitted && form.fullname.$error">
<p ng-message="required" class="help-block">This field is required</p>
</div>
</form>
In this case I am using form.$submitted. You can remove that, replace it with touched/dirty or whatever. The principle is the same.
The ng-messages directive is in a separate library module and must be included as a dependency.
Example
<script src="//unpkg.com/angular/angular.js"></script>
<script src="//unpkg.com/angular-messages/angular-messages.js"></script>
JS
angular.module("app",["ngMessages"])
For more information, see AngularJS ngMessages Module API Reference.
You are using homeForm.requestTypeName in the ng-if, and there is no input control named requestTypeName, i guess you must use homeForm.labName as you use in the ng-message attribute.
Basically, change the ng-if to ng-if="homeForm.labName.$touched"
TL;DR;
You could use as well the homeForm.labName.$error.required for checking if actually the end user has passed away that field without entering some input.
Source: https://docs.angularjs.org/api/ng/directive/ngRequired
I'm learning the use of the directives in AngularJS. I have a password.html file with a input password and then I have created my own directive 'passwordRequirement' to force the user to meet these requirements when creating a user.
This is the code of password.html:
<div class="form-group">
<label for="user_password">Password</label>
<input type="password" data-ng-change="passwordChanged();" data-ng-model="user.password" class="form-control" id="user_password" name="user_password"
placeholder="Password" required>
<div data-ng-show="enteredPassword">
<h4>For your security, the password needs to meet the following requirements</h4>
<password-requirement binding="passwordContainLowercase">Password must contain at least a lowercase character</password-requirement>
<br>
<password-requirement binding="passwordContainUpperCase">Password must contain at least uppercase character</password-requirement>
<br>
<password-requirement binding="passwordLengthValid">Password length must be between 12 and 20 characters</password-requirement>
<br>
<password-requirement binding="passwordContainDigit">Password must contain at least a digit</password-requirement>
</div>
</div>
As each requirement is binded to a different property in the scope, in the definition of the directive I have specified in the scope the variable "binding".
See the code of the definition of the directive:
app.directive('passwordRequirement',function()
{
return {
restrict: 'E',
scope:{binding:'#'},
templateUrl:'partials/content/common/passwordRequirement.html',
transclude:true
};
});
And then the html template for the password requirement (which consists in a label plus a checkbox to be checked or not depending if the requirement is met or not):
<div style="display:inline-block;">
<span data-ng-transclude></span>
<input type="checkbox" data-ng-disabled="true" data-ng-model="binding" data-ng-checked="binding">
{{binding}}
</div>
As you can see I want bind the ng-model to the variable I have specified in password.html. However if I print the value of the bind property using {{binding}}, it prints the correct values. But if I inspect the source code of the page I see:
<input type="checkbox" data-ng-disabled="true" data-ng-model="binding" data-ng-checked="binding" class="ng-pristine ng-valid" disabled="disabled" checked="checked">
Which means that in the page the variable binding it is not being interpreted so that it is not working properly. Why? I cannot do data-ng-model={{binding}} , so what is the solution for this?
Is this doable in AngularJs? If so, what is the right way to achieve that? I though it was the way I'm doing but apparently is not.
Thank you very much in advance.
Try to use two-way binding '=' instead of text binding '#' in your directive:
app.directive('passwordRequirement',function()
{
return {
restrict: 'E',
scope:{binding:'='},// here is the change
templateUrl:'partials/content/common/passwordRequirement.html',
transclude:true
};
});
I have an Angular-based form for which I want to apply specific validation:
<form name="createProject" novalidate>
<span ng-show="createProject.projectName.$invalid">Please enter a name for the project.</span></br>
<input type="text" ng-model="newProject.project.name" name="projectName" placeholder="Project Name" required>
<div ng-repeat="topic in newProject.project.topics_attributes">
<span>Topic {{$index + 1}}</span>
<input type="text" ng-model="topic.name" placeholder="Topic Name">
<input type="text" ng-model="topic.feed_size">
<div ng-repeat="topic_source in topic.topic_sources_attributes">
<span>Topic {{$parent.$index + 1}} Source {{$index + 1}}</span>
<select ng-model="topic_source.platform" ng-options="platform for platform in platforms" topic-source-required parent="$parent.topic.name">
<option value="">-- platform --</option>
</select>
<input type="text" ng-model="topic_source.keywords" placeholder="Topic Keywords" topic-source-required parent="$parent.topic.name">
<button ng-show="$last" ng-click="addTopicSource($parent.topic)">Add Topic Source</button>
</div>
</div>
<button ng-click="addTopic()">New Topic</button>
<input type="submit" ng-click="addProject()" ng-class="{ disabled: createProject.$invalid }" ng-disabled="createProject.$invalid" value="Add Project">
</form>
Back
In the form, a project can have either zero or multiple topics. Each topic must have at least one topic source. Basically, I want to create validation that checks if a topic is being added (added being defined as having a name) and if so require that it has at least one topic_source with platform and keyword fields entered associated with it.
Is this even possible? I'm not very familiar with Angular custom validation so I'm not sure how to begin implementing something like this.
**Edit**
Current progress on custom validation directive:
angular.module('dashboard').directive 'topicSourceRequired', ($parse) ->
require: "ngModel"
link: (scope, elem, attrs, ctrl) ->
topicName = $parse attrs.parent
scope.$watch topicName, (topicName) ->
ctrl.$parsers.unshift checkForExistence if topicName
checkForExistence = (topicSource) ->
if topicSource
ctrl.$setValidity "topicSourceRequired", true
else
ctrl.$setValidity "topicSourceRequired", false
topicSource
Ok, getting closer for this validation. Right now the validation seems to be checking if the two topic_source fields have values independent of whether or not the parent's name field has a value, AND the topic_source fields only register as invalid if a field has been entered and then erased.
How do I make sure the checkForExistence function is only called when topicName has a non empty string value? Second, how do I make sure blank fields are invalid without having to modify them somehow?
You need to create a custom directive that requires ngModel which gives you access to the ngModelController (look at the require option when creating a directive).
Basically, you can listen for changes to the value by registering with the $viewChangeListeners, read the current value from $viewValue, and set the validation through the controller function
$setValidity(validationErrorKey, isValid);
The validationErrorKey you set is the a boolean value on the $erros object of the controller.
Take a look at this page in the docs to get you going:
https://docs.angularjs.org/api/ng/type/ngModel.NgModelController
I have the following markup. The function isn't called when you click on the icon and there are no errors logged to the console.
Online search hasn't shown others with this issue, so either it's a feature of the 'input append' markup or I've got something basic wrong that I'm just not seeing.
I'm using bootstrap 2.3 and angularjs 1.2.13
<div class="input-append input-block-level">
<input type="text" name="myFieldName" ng-model="model[field]"
class="input-block-level ng-pristine ng-valid ng-valid-required"
placeholder="My Field Name" ng-required="false">
<a class="btn add-on" ng-click="aCtrlFunc('my field name')">
<i class="icon-search"></i>
</a>
</div>
The form works, the model is updated as expected when typing into the field and on form submit. Why doesn't this button click work?
update
Mystery solved. It's a scope issue, like this one: https://stackoverflow.com/a/16489532/149060
The form fields are generated by a directive who's templates include ngRepeats. I didn't think of it because I didn't think it was using an isolate scope.
Given this code:
<div ng-controller="MyCtrl">
<form ng-submit="onSubmitted()">
Header inputs:
<input type="name" ng-model="sample" required/>
<input type="name" ng-model="sampleX" required/>
<input type="submit" value="This submit triggers validation. But I wanted to put this button at the end of the page"/>
</form>
<hr/>
Some other form here. Think line items
<hr />
<a class="btn" ng-click="/* what could should be put here, so this can trigger the firt form's validation, then submit? */">Wanted this submit button to trigger the validation+submit on the form in which this button doesn't belong</a>
</div>
var app = angular.module('myApp', []);
function MyCtrl($scope) {
$scope.onSubmitted = function() {
alert('submitted!');
};
}
I want the last button to trigger the validation(then submit when things are valid) on first form. As of now, only the button inside the form can trigger that form's validation and submission. Is there any possible way for a button outside the form to do that?
Live test: http://jsfiddle.net/dzjV4/1/
You can create directive which you can then attach to <a class="btn".... Check this jsfiddle
http://jsfiddle.net/dzjV4/2/
Note that I added to <input type='submit' id='clickMe'... and linked it with link at the bottom <a class='btn' linked="clickMe"...
for (control of $scope.[form name].$$controls) {
control.$setDirty();
control.$validate();
}
You can try the above codes. Make it running before submit.
Ideally there'd be a programmatic way to cause validation to re-run across a form. I have not investigated that completely but had a situation that required multiple controls to be re-validated based on different data in the scope -- without the user interacting with the individual controls. This arose because the form had two action buttons which each required different validation rules be in play when they were clicked.
The UI requirement changed before I fully implemented forcing re-validation but before it did I got most of what I needed by copying and then re-setting the form's data. This forced re-validation across the form within the current scope. Basically, it's along the lines of the following (not tested, but taken from the code that was working). In this case the form's data was bound to the properties in one object.
var formData = $parse(<form's model>);
var dataCopy = angular.copy( formData($scope) );
formData.assign( $scope, dataCopy );
This may or may not be acceptable, but if you can get away with the SUBMIT button being disabled until the form is completed, you can do this:
<form name="formName">
<input ng-required="true" />
</form>
<button ng-click="someFunction()" ng-disabled="formName.$invalid" />
It's also worth noting that this works in IE9 (if you're worried about that).
Give your form a name:
<div ng-controller="MyCtrl">
<form name="myForm">
<input name="myInput" />
</form>
</div>
So you can access your form validation status on your scope.
app.controller('MyCtrl', function($scope) {
$scope.myForm.$valid // form valid or not
$scope.myForm.myInput // input valid or not
// do something with myForm, e.g. display a message manually
})
angular doc
There is no way to trigger browser form behavior outside of a form. You have to do this manually.
Since my form fields only show validation messages if a field is invalid, and has been touched by the user:
<!-- form field -->
<div class="form-group" ng-class="{ 'has-error': rfi.rfiForm.stepTwo.Parent_Suffix__c.$touched && rfi.rfiForm.stepTwo.Parent_Suffix__c.$invalid }">
<!-- field label -->
<label class="control-label">Suffix</label>
<!-- end field label -->
<!-- field input -->
<select name="Parent_Suffix__c" class="form-control"
ng-options="item.value as item.label for item in rfi.contact.Parent_Suffixes"
ng-model="rfi.contact.Parent_Suffix__c" />
<!-- end field input -->
<!-- field help -->
<span class="help-block" ng-messages="rfi.rfiForm.stepTwo.Parent_Suffix__c.$error" ng-show="rfi.rfiForm.stepTwo.Parent_Suffix__c.$touched">
<span ng-message="required">this field is required</span>
</span>
<!-- end field help -->
</div>
<!-- end form field -->
I was able to use this code triggered by a button to show my invalid fields:
// Show/trigger any validation errors for this step
angular.forEach(vm.rfiForm.stepTwo.$error, function(error) {
angular.forEach(error, function(field) {
field.$setTouched();
});
});
// Prevent user from going to next step if current step is invalid
if (!vm.rfiForm.stepTwo.$valid) {
isValid = false;
}