angular select $touched validation - angularjs

I have a form with select and a button that is enabled when the form is validated. It works fine with input box. However, it the $touched does not seem to be working properly for the select. The button is enabled even when select is touched. It is supposed to turn invalid when select is touched. It only turns invalid and the button is disabled when I select an option and then select the default value. I want it to work when select is touched and user the mouse pointer away.
Here is my html:
<form role="form" name="frameVersionEditor" novalidate class="form-horizontal col-md-12">
<div class="row">
<div class="form-group col-lg-2 col-lg-push-1" ng-class="{'has-error' : frameVersionEditor.distributor.$invalid && frameVersionEditor.distributor.$touched}">
<label>Distributor</label>
<select name="distributor" data-ng-model="myDistr" data-ng-options="distributors.key as distributors.value for distributors in distributorOptions" class="form-control" required>
<option value="">Select Distributor</option>
</select>
<span ng-show="frameVersionEditor.distributor.$error.required && frameVersionEditor.distributor.$touched" class="help-block">Please select a distributor</span>
</div>
</div>
</form>
<button data-ng-click="generate()" ng-disabled="frameVersionEditor.$invalid">Generate</button>
Here is my controller:
var myApp = angular.module('myApp',[]);
myApp.controller('myController', ['$scope', function($scope) {
$scope.myDistr = [];
$scope.distributors =
[
{
'key': '0',
'value': 'A'
},
{
'key': '1',
'value': 'B'
},
{
'key': '2',
'value': 'C'
}
];
$scope.generate = function() {
//Do something
};
}]);

I had much the same issue just a short time ago. I solved it by adding a few things to the select field and another entry to the array of possible selections.
First, you want to add an empty or invalid selection to the list of possible selections, the best place is in the [0] slot and you'll see why further down.
vm.distributors = [ "Select Distributor", "A", "B", "C" ];
Next, you need to add and update some Angular Directives for your select field. You want the ng-valid to tell Angular what is acceptable here. You also need to specify that you are starting the field with a "select something else" value and that it is not valid for that value to be selected during submit. Adding a name and id are generally best practice items. The last bit to add is the ng-required directive, since you used novalidate on the form, but it is required that this form item change to something valid. The end result is below:
<div class="form-group col-lg-2 col-lg-push-1" ng-class="{'has-error' : vm.myDistr == vm.distributors[0] && frameVersionEditor.$dirty
|| frameVersionEditor.distributor.$pristine && frameVersionEditor.distributor.$touched}">
<label>Distributor</label>
<select data-ng-model="vm.myDistr" id="myDistr" name="myDistr" ng-init="vm.distributors[0]" class="select form-control skinny" ng-required="true"
data-ng-options="d for d in vm.distributors" ng-valid="vm.myDistr != vm.distributors[0]"></select>
<p class="required" ng-show="vm.myDistr == vm.distributors[0]">*</p>
<p class="good" ng-show="vm.myDistr != vm.distributors[0]">✔</p>
<span ng-show="vm.myDistr == vm.distributors[0] && frameVersionEditor.$dirty || frameVersionEditor.distributor.$pristine
&& frameVersionEditor.distributor.$touched" class="help-block">Please select a distributor</span>
This also was updated to illustrate the simplest implementation (since you are using 0-4 as keys, you don't really need them, just use a basic array). I also changed your code to coincide with John Papa's best practices and added in a red asterisk (for invalid selections) and a green check mark (for a correct selection), as is another good practice (show success as well as failure) in the case of non-English-speaking users.
You also do not need the beginning option you specified originally since my updates handle that nicely.
By request, here is a Plunker.
I hope this helps.
-C§

I have made up this fiddle and everything seems to work fine.
<div ng-app="myApp" ng-controller="myController">
<h1>Select a Distributor</h1>
<form name="form" class="form-horizontal col-md-12" novalidate>
<div class="row">
<div class="form-group col-lg-2 col-lg-push-1" ng-class="{ 'has-error': form.distributor.$invalid && form.distributor.$touched }">
<label>Distributor</label>
<select name="distributor" ng-model="mySelection" ng-options="d.key as d.value for d in myDistr" class="form-control" required>
<option value="">Select Distributor</option>
</select>
<span ng-show="form.distributor.$error.required" class="help-inline">Please select a distributor</span>
</div>
</div>
</form>
<hr/><hr/>
<button class="btn btn-primary" data-ng-click="generate()" ng-disabled="form.$invalid || form.distributor.$touched">Generate</button>
</div>
I have the suspect that maybe the $touched event is not what you think.
Please give me some feedback if I'm wrong!

Related

Input validation: Validation message and ng-disabled buttons not working as expected

I have the following code which I'm trying to validate using AngularJS:
<div ng-form="transWizard" novalidate>
<div style="word-wrap:break-word; padding-top:4px; padding-left:14px">
<p style="font-family:'MetricWeb-Regular'; font-size:17.5px;"><span style="font-family:'MetricWeb-Semibold'">Question {{carousel.currentQuestionIndex+1}}:</span> {{carousel.currentQuestionObject.question}}</p>
<ul style="padding-left:30px;">
<li ng-repeat="query in carousel.currentQuestionObject.choices" style="padding-bottom:5px;">
<input class="TWInputField" name="inputname"
type="{{carousel.currentQuestionObject.inputType}}" id="{{query.id}}"
ng-model="query.selected" ng-change="carousel.changeOnSelection(query.selected, query.id)"
value="" ng-value="true" ng-required="{{carousel.currentQuestionObject.inputType === 'radio' || carousel.currentQuestionObject.inputType === 'text' ? true : false}}">
<label for="{{query.id}}" style="font-family:'MetricWeb-Regular';font-size:17px;cursor:pointer"> {{query.question}}</label>
</li>
</ul>
<p class="msg-required" ng-show="transWizard.inputname.$invalid">
Input is required.
</p>
</div>
<div class="carousel-wizard-btn-container">
<div class="carousel-wizard-buttons" ng-click="carousel.wizardPrevious()" ng-hide="carousel.currentQuestionIndex == 0">Previous</div>
<div class="carousel-wizard-buttons" ng-click="carousel.wizardNext()" ng-hide="carousel.currentQuestionIndex == carousel.wizardQuestionSet.length - 1" ng-disabled="transWizard.$invalid">Next</div>
<div class="carousel-wizard-buttons" ng-click="carousel.showResults()" ng-show="carousel.currentQuestionIndex == carousel.wizardQuestionSet.length - 1" ng-disabled="transWizard.$invalid">Finish</div>
</div>
</div>
As you can see, I'm not using the <form> tag, but I've read that it's possible to validate even without using forms thanks to the ng-form directive.
My conditions are to require input only if type is radio or text.
However, when I executed this, I got the following:
ng-disabled is not working for the Next button. I can still click it even though I haven't selected any answers for the required sections.
Validation message only disappears when I click the last item in a set of radio buttoned-questions.
Am I missing something here?
Please help. Thank you.
your validation will not work as expected because u are using same name for input controls in ng-repeat.
you can use ng-form inside ng-repeat like this.
<div ng-form="transWizard" novalidate>
<li ng-repeat="query in carousel.currentQuestionObject.choices" style="padding-bottom:5px;">
<ng-form name="innerForm" >
<input class="TWInputField" name="inputname" type=" {{carousel.currentQuestionObject.inputType}}" id="{{query.id}}" ng-model="query.selected" ng-change="carousel.changeOnSelection(query.selected, query.id)" value="" ng-value="true" ng-required="{{carousel.currentQuestionObject.inputType === 'radio' || carousel.currentQuestionObject.inputType === 'text' ? true : false}}">
<label for="{{query.id}}" style="font-family:'MetricWeb-Regular';font-size:17px;cursor:pointer"> {{query.question}}</label>
<p class="msg-required" ng-show="innerForm.inputname.$invalid">
Input is required.
</p>
</ng-form>
</li>
</div>

Select at least one checkbox validation

I have the array of checkboxes from which I want to select at least one checkbox and want to display the validation error
How can I do that? Here is my code
<div class="form-group" ng-class="{ 'has-error' : submitted && form.field.$invalid }">
<div class="col-md-12"><label for="field" >Select at leat one</label></div>
<div class="col-md-2" data-ng-repeat="i in [1,2,3,4]">
<label><input type="checkbox" name="field[$index]" value="{{i}}" data-ng-model="form.field[$index]" required/>Choice {{i+1}}</label>
</div>
<div ng-show="submitted && form.field.$invalid" class="help-block">
<p ng-show="form.field.$error.required">Please select at least one choice<p>
</div>
</div>
</div>
You can use native javascript functions to iterate over your array. First that come in mind are Array.prototype.some() and Array.prototype.filter().
With some, you can determine if one item in your array validates true and with filter you can find all the elements that pass a custom expression and check if the newly created array has a certain length.
E.g.:
var atleastOne = this.form.field.some(function(element) {
return element;
});
if(atleastOne) {
window.alert("validated with some");
}
var filtered = this.form.field.filter(function(element) {
if(element) { return element; }
});
if(filtered.length > 0) {
window.alert('validated with filter');
}
You can flag the variables that show the errors based on the results of your validation. A working demo can be found here.
You can create your checkbox list object array properly then bind it. Please find the demo
<div ng-controller="ChckbxsCtrl">
<div ng-repeat="chk in items">
<div style="width:500px;height:100px;border:1px solid #000;">
<B>{{chk.group}}</B><br>
<label ng-repeat="vale in chk.values">
<input type="checkbox" ng-model="vale.val" />
{{vale.label}}</label>
</div>
<span ng-show="chk.isValid">Select at least one option from {{chk.group}}</span>
</br> </div>
</br>

Creating dynamic forms with angularjs

So Im creating dynamic forms with angularjs. And when the form is sent, I delete it from the array. But for some odd reason forms validation rules somehow "stick" to the next form. For example. I send the first form, The second form now gets validated if the third form has valid answers, and so on if the 4th form has valid answers the 3th form will be valid. What could be the possible reasons for this?
This is pretty much striped down code to the basics of what I have
<div ng-repeat="item in ItemsAdder.items track by $index" ng-form="item.itemForm">
<div class="form-group control-group">
<label for="category" class="col-sm-2 control-label">{{trans('adsAdd.category')}}</label>
<select ng-options="category.name for category in categories track by category.id" ng-init="item.category=categories[0]" ng-model="item.category"></select>
</div>
<div class="form-group control-group" ng-class="{ 'has-error' : item.itemForm.price.$invalid && !item.itemForm.price.$pristine }">
<label for="price" class="col-sm-2 control-label">Price</label>
<input ng-model="item.price" ng-class="{ 'has-error' : item.itemForm.price.$invalid && !item.itemForm.price.$pristine }" required type="number" ng-trim="false" name="price">
<p ng-show="item.itemForm.price.$error.number && !item.itemForm.price.$pristine" class="help-block">{{trans('items.add.priceNeedsToBeNumber')}}</p>
<p ng-show="item.itemForm.price.$error.required && !item.itemForm.price.$pristine" class="help-block">{{trans('items.add.priceNeeded')}}</p>
</div>
<div class="form-group control-group" ng-class="{ 'has-error' : item.itemForm.description.$invalid && !item.itemForm.description.$pristine }">
<label for="description" class="col-sm-2 control-label inputLabel">Description</label>
<textarea ng-minlength="3" ng-class="{ 'has-error' : item.itemForm.description.$invalid && !item.itemForm.description.$pristine }" ng-model="item.description" name="description" required class="inputInput" style="max-width: 100%;"></textarea>
<p ng-show="item.itemForm.description.$error.required && !item.itemForm.description.$pristine" class="help-block">{{trans('items.add.descriptionNeeded')}}</p>
</div>
<button ng-click="ItemsAdder.send($index)" ng-disabled="item.itemForm.$invalid">{{trans('adsAdd.send')}}</button>
</div>
And my send function:
ItemsAdderFactory.prototype.send = function ($index) {
var self = this;
var responsePromise = $http.post("",this.items[$index]);
responsePromise.success(function (data, status, headers, config) {
self.items.splice($index, 1);
});
responsePromise.error(function (data, status, headers, config) {
alert('There was an error, please try again.');
});
};
Btw I have the ng-form="" as item.ItemForm so I could access the form through items when a Send All Button is pressed, and it checks what forms are valid and only sends those. If there Is a different or a propper way of doing it, Im all ears.
So guys, I found out the answer. And I'm going to leave it here if anyone else gets this.
The problem was because I was using track by $index in the ng-repeat and I guess the form validation wanted to stick around because the index didn't change.
So don't use track by $index in ng-repeat if your having these problems.
When you delete the submitted from the array ItemsAdder.items remember that the ng-repeat has two way binding so it resets the array and now the 1st index item becomes the 0th index item.
This is best guess about the problem you are facing.

angular ng-options does not render the right value in IE 11

I have two dropdowns with ng-options. One for displaying primary brokers and other for secondary brokers.
The Mark-up is as follows:-
<div class="col-md-4">
<div class="form-group">
<label>Primary Broker: </label>
<button type="button" class="btn-help" tabindex="-1" data-popover="" data-content="Lead JLL broker for the opportunity">
<span class="circle-help">?</span>
</button>
<select class="form-control"
ng-options="jllContact.JllUserId as jllContact.Name for jllContact in controller.primaryJllBrokers"
ng-model="controller.primaryJllContactId"
id="primaryJllContact" data-field='{"name": "Primary Broker", "attribute": "PrimaryJllContact"}'
ng-change="controller.setPrimaryJllContact()"
ng-class="{'req': controller.data.isFieldRequired('PrimaryJllContact'), 'has-error': controller.data.isFieldValid('PrimaryJllContact') == false}"
ng-disabled="!controller.isEnabled('JllContacts')||!controller.canEditBrokerAllocation()">
<option value="">Select Primary Broker</option>
</select>
</div>
</div>
#* Secondary Broker*#
<div class="col-md-4">
<div class="form-group">
<label>Secondary Broker: </label>
<button type="button" class="btn-help" tabindex="-1" data-popover="" data-content="Secondary JLL broker for the opportunity">
<span class="circle-help">?</span>
</button>
<select class="form-control"
ng-options="jllContact.JllUserId as jllContact.Name for jllContact in controller.secondaryJllBrokers"
ng-model="controller.data.SecondaryJllContactId"
id="secondaryJllContact" data-field='{"name": "Secondary Broker", "attribute": "SecondaryJllContact"}'
ng-change="controller.handleSecondaryJllContactChange()"
ng-class="{'req': controller.data.isFieldRequired('SecondaryJllContact'), 'has-error': controller.data.isFieldValid('SecondaryJllContact') == false}"
ng-disabled="!controller.isEnabled('JllContacts')||!controller.canEditBrokerAllocation()">
<option value="">Select Secondary Broker</option>
</select>
</div>
When I change the secondary broker, as seen in the markup I call a method called
handleSecondaryJllContactChange. The method is as follows.
handleSecondaryJllContactChange:()=>
primaryJllContactId = this.primaryJllContactId
this.refreshPrimaryBrokers((primaryBrokers)=> this.primaryJllContactId = primaryJllContactId)
refreshPrimaryBrokers:(callback)->
this.primaryJllBrokers = _.where(this.data.JllContacts, (jllc)=> jllc.Role.ID == 21 && jllc.JllUserId != this.data.SecondaryJllContactId)
if callback? && callback!= undefined && this.primaryJllBrokers!=null && this.primaryJllBrokers != undefined
callback(this.primaryJllBrokers)
As seen from the code, we are setting the ng-model "controller.primaryJllContactId" for the first drop down after the collection controller.primaryJllBrokers has been refreshed.
However, the drop down still shows instead of the correct option in IE 11.
NOTE:
On examining the console, we did notice that ng-model for the first
dropdown controller.primaryJllContactId has the right value. It just
doesn't display correctly in the drop down.
This is happenning only in IE 11. It works perfectly in chrome.
Couple of things we have tried:-
Instead of ng-change="controller.handleSecondaryJllContactChange()" in the second dropdown we tried to have a watch on controller.secondaryJllBrokers
and call the method controller.handleSecondaryJllContactChange().
The second route which we took was based on the assumption that we are setting the ng-model before options are rendered. Hence we took the $timeout
approach. In the handleSecondaryJllContactChange() method shown above we did this in the second line:-
this.refreshPrimaryBrokers((primaryBrokers)=> this.$timeout(=> this.primaryJllContactId = primaryJllContactId))
None of these two approaches worked. Any ideas why this is happening and what is the way to know when options have finished rendering.
Thanks in Advance !!!!

Ng-model does not update view

I have searched thoroughly everywhere and can't seem to find the solution to my problem.
I am trying to make a form to fill in to create new task object onto projects on the website like a scrum backlog or something in Angular and angular-ui.
I use Angular-UI for typeahead functionality for when adding team members to a task, so available members on a project pop-up. I have seen guys with similar problems, but nothing seems to solve it for me. Below is the HTML and the controller. Sorry for the long markup, but I suspect it has to do something with nesting the controllers and mixing the scopes, so I'm including everything relevant.
<div ng-controller="SubmitCreateTaskController">
<div class="modal fade" id="taskModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">
<span aria-hidden="true">×</span>
<span class="sr-only">Close</span>
</button>
<h4 class="modal-title" id="myModalLabel">New Task</h4>
</div>
<div class="modal-body">
<form role="form" method="post" name="create-task">
<div class="form-group">
<label for="taskname">Task Name</label>
<input type="text" class="form-control" id="taskname" ng-model="taskForm.taskName" placeholder="Task Name">
</div>
<div class="form-group">
<label for="taskdesc">Description</label>
<textarea class="form-control" id="taskdesc" size="3" ng-model="taskForm.taskDescription" placeholder="Enter a short description here..." rows="2"></textarea>
</div>
<div class="form-group">
<label for="assigneddate">Assigned Date</label>
<input type="text" class="form-control" id="assigneddate" ng-model="taskForm.assignedDate">
</div>
<div ng-controller="TypeAheadController">
<div class="form-group">
<label for="contributors">Add Contributors</label>
<input id="contributors" type="text" class="form-control" ng-model="contrib.selected" typeahead="member for member in contrib.stream_members | filter:$viewValue"
typeahead-editable="false" typeahead-on-select="contrib.onSelect($item)">
</div>
<div class="form-group">
<label for="users">Contributors:</label>
<textarea class="form-control" disabled="disabled" id="users" ng-model="contrib.entered" rows="1"></textarea>
</div>
</div>
<div class="form-group">
<label for="taskcomments">Comments</label>
<textarea class="form-control" id="taskcomments" ng-model="taskForm.comment" placeholder="Comments" rows="2"></textarea>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary" ng-click="taskForm.submit()">Add Task</button>
<button type="button" class="btn btn-danger" data-dismiss="modal">Cancel</button>
</div>
</form>
</div>
</div>
</div>
</div>
Controllers:
.controller('SubmitCreateTaskController', ['$scope', '$http', function($scope, $http) {
$scope.taskForm = {};
$scope.taskForm.taskName = '';
$scope.taskForm.taskDescription = '';
$scope.taskForm.assignedDate = new Date();
$scope.taskForm.contributors = [];
$scope.taskForm.comment = '';
$scope.taskForm.submit = function(item, event) {
var formData = {name: $scope.taskForm.taskName,
description: $scope.taskForm.taskDescription,
status: 'open',
assigned: $scope.taskForm.assignedDate,
completed: null,
contributors: $scope.taskForm.contributors,
comments: [{body: $scope.taskForm.comment,
user: 'RACHE User',
date: new Date()
}]
};
var postURL = '../create_task/' + $scope.stream_name;
$http.post(postURL, formData)
.success(function(){
taskForm = {}; // empty the form of previous input values
alert('New Task Created for ' + $scope.stream_name);
})
.error(function(res) {
alert(res.data);
});
};
}])
.controller('TypeAheadController', ['$scope', '$http', function($scope, $http) {
$scope.contrib = {};
$scope.contrib.selected = '';
$scope.contrib.stream_members = undefined;
$scope.contrib.entered = [];
$http.get('/stream_members/' + $scope.stream_name)
.then(function(res) {
$scope.contrib.stream_members = res.data.stream_members;
});
$scope.contrib.onSelect = function($item) {
$scope.contrib.selected = '';
$scope.contrib.entered.push($item);
console.log($scope.contrib.entered);
};
This last bit is the important part in 'TypeAheadController'. I am trying to add the poped-up team members to a textarea just below which would be sent to the DB later. That is why I have bound the actual typeahead input box to 'contrib.select' and the "display/post" box to 'contrib.entered', so the search can be continued after adding one member, the search box is cleared, selected member is added to box below, new search can begin. In the mark up I call the last (onSelect) function in the typeahead-on-select callback. This so that I can clear the input box and another member can be easily added without deleting manually. IN this function the entered member should be appended to the array that is bound to below "display/post" box. This happens, since the console log shows good value, the search/input field gets cleared, so the function gets called as well.
The view does not get updated with the updated array bound to display box however. I have tried EVERYTHING. I have googled around and found it here that dot notation needs to be used as Angular can't update with primitives, but this hasn't helped either.
Everything works fine, the logic is good, values are good when I print them out, pop-up works and clears up as expected, but The damn textarea under it does not get updated.
Any suggestions? I have been stuck on this for almost a day now and I am really frustrated by this. I would be ever so greatfull for any help!
Thanks guys in advance!
Textarea do use the value attribute, your code isn't working because contrib. entered isn't between the tags, but because you can not bind the textarea to an array (it needs to bind to a string).
look at this plunker, if you do this in your html:
<textarea class="form-control" disabled="disabled" id="users" rows="1" ng-model="contrib.enteredString"></textarea>
and add this line at the end of your typeahead controller:
$scope.contrib.enteredString = $scope.contrib.entered.toString();
then the text area will update
Textareas don't use the ng-value as their value is contained within the tag. Remove the ng-model and put the value between the tags.
<textarea class="form-control" disabled="disabled" id="users" rows="1">{{contrib.entered}}</textarea>

Resources