Configuring validation tooltips with angular-ui - angularjs

I am doing a simple form using angularjs with angular-ui. Somehow angular is inserting boostrap tooltips into my validation. But it is getting inserted automatically and I do not know how to control/customize or disable the tooltips. Here is my html:
<form name="frm" ng-submit="contact.contactUsSubmit(frm)">
<table id="tblContactUs">
<tr>
<td id="tblContactUsTitleTd">
Send us an email
</td>
</tr>
<tr>
<td>
<span class="LabelStyle1">Name:</span><br/>
<input type="text" tooltip-trigger="0" name="name" style="width: 75%" ng-model="contact.formInfo.name" required/><br/>
<span ng-show="frm.name.$dirty && frm.name.$error.required" class="errorMsg">Required!<br /></span>
<span class="LabelStyle1">Email:</span> <br/>
<input type="email" name="email" style="width: 75%" ng-model="contact.formInfo.email" required/><br/>
<span ng-show="frm.email.$dirty && frm.email.$error.required" class="errorMsg">Required!<br /></span>
<span ng-show="frm.email.$dirty && frm.email.$error.email" class="errorMsg">Not a valid email!<br /></span>
<span class="LabelStyle1">Message:</span> <br/>
<textarea name="message" rows="5" style="width: 100%" ng-model="contact.formInfo.message" required></textarea><br/>
<span ng-show="frm.email.$dirty && frm.name.$error.required" class="errorMsg">Required!</span>
</td>
</tr>
<tr>
<td style="text-align: right">
<button>Submit</button>
</td>
</tr>
</table>
</form>
:
But I am getting this:
Yes the tooltip is nice. But I didn't say I wanted it there. IF I want to use the tooltip for validation, I need to be able to customize the message (for different languages). I have googled the bejesus out the topic but I can't find answers as to how to configure it or to turn it off. I don't even know why its there in the first place. Any help would be much appreciated.

Those aren't bootstrap or even css related in any way.
They are html5 validation tooltips that are part of the browser itself.
If you don't want to use built in browser validation add the novalidate attribute to <form> tag.
<form name="frm" ng-submit="contact.contactUsSubmit(frm)" novalidate>
Reference MDN <form> docs

Thank you to charlietfl for putting me on the right track with "novalidate." The problem with novalidate, however, is that when you hit ng-submit none of the validation error messages showed up.
The complete solution was to add the following code to the form tag ("contact" an alias for "contactController":
<form name="frm" ng-submit="contact.formIsValid(frm) && contact.contactUsSubmit(frm)" novalidate>
In the controller the two functions went as follows:
formIsValid(frm) {
angular.forEach(frm.$error.required, function (field) {
field.$setDirty();
});
return frm.$valid;
}
contactUsSubmit(frm) {
var scope = this;
if (frm.$valid) {
//do $http post data etc here
}
return frm.$valid;
}
Now, if when someone hits submit, the field are made "dirty" and this causes the angular validation rules to fire. If the form is valid then the submit goes thru, if not then we are left with the validation errors.

Related

Values are duplicated into all input field which is created using the ng-repeat and ng-model

I am using the ng-repeat to create the input fields dynamically, everything seems to be working fine and the input fields are created but due to some reason when I enter some value into that created input field then due to two-way data binding the value is entered simultaneously in all the text fields that were created.
As per my knowledge, I am assigning the different ng-model to each of the created fields so I am not sure why the values are being copied. When I try to print the values which are assigned to the input field just above the field then I see the id is different wanted to know why all the fields are taking the same value and also how can I prevent it.
HTML Code:
<tr>
<td ng-repeat="extension in ExtensionList">
<!-- Here the value displayed are different -->
{{ formdata.extension.ExtensionVlaues }}
<input type="text" class="form-control" id="extension.ExtensionVlaues" ng-model="formdata.extension.ExtensionVlaues">
</td>
</tr>
Here is the screenshot from the front-end:
Here is the example of my ExtensionList: {"Field1":"","Field2":"","Field3":1,"ExtensionVlaues":2}
As per my knowledge, the given ng-model is different but still not working. Can anyone please help me with this? I tried few things like ng-model="formdata.extension[ExtensionVlaues]" but still no luck.
I tried to assign ng-model like this but it is not working:
ng-model="formdata.extension[ExtensionVlaues]"
Based on the below answer I tried 2 different things it but it's not working and getting the same issue. Please let me know how can I resolve this:
<td ng-repeat="(key, extension) in ExtensionList">
<input type="text" ng-model="formdata.extension.key">
</td>
<td ng-repeat="(key, extension) in ExtensionList">
<input type="text" ng-model="formdata.extension.ExtensionVlaues">
</td>
You are trying to use ExtensionList as an array. You need to use it as an object with ng-repeat:
<td ng-repeat="(key, extension) in ExtensionList">
<input type="text" ng-model="formdata[extension][key]">
</td>
<td ng-repeat="(key, extension) in ExtensionList">
<input type="text" ng-model="formdata[extension][ExtensionVlaues]">
</td>
Just incase anyone else also struck at this issue:
<span ng-repeat="(key, extension) in ExtensionList" class="form-inline">
<br/>
<span>
{{ extension.NameSpace + ":" + extension.LocalName }}
</span> 
<input type="text" class="form-control" id="extension.ExtensionVlaues" ng-model="ExtensionList.extension[key].FreeText" ng-blur="ExtensionText(ExtensionList.extension[key].FreeText, extension.ExtensionVlaues)">
</span>

Custom validation via child control

So I'm working inside a directive that contains it's own form element and buttons, however all the controls must be transcluded through. The model for this particular view contains a property for total capacity, and a property that is a collection of compartments (separate entity). Each compartment has it's own capacity. I already have a function that will show an error on the view if/when the Total Capacity is not equal to the combined capacity of all compartments. The problem here is, since all my controls are transcluded through (and I'm not supposed to modify the parent directive) I have no clue if/how I can use that same function to mark the form as invalid to disable the save button. I was wondering if there is a solution (hopefully one that doesn't involve custom directives or services) that would allow me to set the parent form invalid if an expression returns true.
** UPDATE **
Sorry guys, I think I explained it backwards the first time. So this would be a good representation of what is going on in the html. (Also I haven't used stackoverflow much before this so bear with me)
edit-page-directive:
<div>
<form name="editForm">
<ng-transclude>
</ng-transclude>
<a class="btn btn-success">Save</a>
<a class="btn btn-danger">Cancel</a>
</form>
</div>
View for this particular edit:
<edit-page>
<uib-tabset>
<uib-tab>
<!--Total Capacity input-->
<input type="text" numeric="{min:1, format:'#,###.#'}" ng-model-options="{updateOn: 'blur'}" class="form-control" id="tcCapacity" name="tcCapacity" data-ng-required="true" ng-model="vm.dataContext.entity.TotalCapacity" />
<!--End Total Capacity-->
</uib-tab>
<uib-tab>
<table>
<tr><thead><th>...</th><th>Capacity</th><th>(Buttons for compartment add/remove)</th></thead></tr>
<tr ng-repeat="compartment in vm.dataContext.entity.TrailerConfigCompartments">
<td width="200">{{compartment.Sequence}}</td>
<!--Important input under this-->
<td><input type="text" numeric="{min:0, format:'#,###.#'}" class="form-control" ng-model="compartment.Capacity" data-ng-required="true" /></td>
<!--Important input above-->
<td align="right" style="padding-right:30px;">
<a class="btn" style="padding: .7em; color: black;" ng-click="vm.addCompartment(compartment.Sequence + 1)">
<span uib-tooltip="New compartment at sequence {{compartment.Sequence + 1}}" class="btn-edit" style='margin-left:5px'><span class="glyphicon glyphicon-plus" style="margin-top:3px"></span></span>
</a>
<a class="btn" style="padding: .7em; color: black;" ng-click="vm.removeCompartment(compartment)">
<span uib-tooltip="Remove compartment" class="btn-edit" style='margin-left:5px'><span class="glyphicon glyphicon-minus" style="margin-top:3px"></span></span>
</a>
</td>
</tr>
</table>
</uib-tab>
</uib-tabset>
</edit-page>
If I understand you correctly you have something like
HTML
<div data-ng-controller="FormController as vm">
<form class="foo form">
<input type="text"> // some inputs
<input type="text"> // some inputs
<transcluded-directive>
<button class="foo button-to-disable">Do something</button> // button that should be disabled
</transcluded-directive>
</form>
</div>
JS
.controller("FormController", function($scope) {
var vm = this;
vm.validateTotalCapacity = function () {
// validation stuff
}
});
So I think you can do something like:
HTML
<div data-ng-controller="FormController as vm">
<form class="foo form {{vm.validateTotalCapacity() ? '' : 'form-has-errors'}}" >
<input type="text"> // some inputs
<input type="text"> // some inputs
<transcluded-directive>
<button class="foo button-to-disable">Do something</button> // button that should be disabled
</transcluded-directive>
</form>
</div>
Look I put your form validator in <form class="foo form"> and make condition for error class
CSS
.form-has-errors .button-to-disable {
pointer-events: none;
cursor: default;
opacity: 0.5
// or your custom disabled styles
}
UPDATE
I see, but I believe you could try this:
HTML
<div>
<form name="editForm" class="{{editForm.$valid ? '' : 'form-has-errors '}}">
<ng-transclude>
</ng-transclude>
<a class="btn btn-success">Save</a>
<a class="btn btn-danger">Cancel</a>
</form>
</div>
So I realized I had misinterpreted the customValidation piece of angularjs. I thought any directive I'd have to create for validation would have to be added to the form element itself. Just as well I thought it would be alot harder to set up than it actually is.
For future reference:
1.) Create a directive and restrict it to an attribute
2.) Require ngModel for this directive
3.) Set up your link function:
link: function(scope, elem, attrs, ngModel) {....}
4.) Add a function to the $validators object of the control you want to validate. Do this INSIDE of your link function. Ex:
link: function(scope, elem, attrs, ngModel) {
ngModel.$validators.validationFn = function(value) {
//Where value is the current value of the control
//In my case, where I want to compare value to the combined value of other
//compartments I would send in whatever data I wanted via the scope property of
//this directive and compare the two in this function
}
}
5.) Return true if control is valid and vice versa
And that's it.
If you want to access this validator to display an error message just:
ng-show="vm.arbitraryInput.$error.validationFn"
Keep in mind that now if it returns true, then the input is invalid.

Angular JS implement Search

I have a form with multiple fields. How do I implement search using angularjs? For example I have the following page:
<pre>
<input name="title">
<input name="isbn">
<input name="author">
<br/>
<input type="submit">
<!-- list the book information -->
Title Author ISBN
</pre>
I want the bottom list to display based on user input on the search fields from the server. Note:The user can input on mutiple fields. How do I accomplish the search when the user clicks the submit button?
Do I need to write one submit method. If so how can I get the values and do the query depending on the field is left blank or not?
Thanks
If your using angularJS you don't need a submit it will do it asynchronously. Just add an ng-model to your input and filter the results based on that. Something like this:
<input name="title" ng-model="title">
<input name="isbn" ng-model="isbn">
<input name="author" ng-model="author>
<div filter: title | filter:isbn | filter:author >
Title Author ISBN
</div>
Something like this
<form name='fred' novalidate='novalidate' ng-submit='submitFunc()'>
<input name='username' required='required' pattern='a-z'>
<input type='email'>
<input type='submit' ng-disabled='fred.$invalid'>
</form>
In your submitFunc you would do an $http request to get your data and filter it and then store the results in an array and do an ng-repeat in the view below the form to iterate over your results
A basic implementation should look like this:
the code:
app.controller('SampleController', ['$scope', function($scope) {
var search = function() {
yourAjaxMethod({author: $scope.author, isbn: $scope.isbn, title: $scope.title}).then(function(list) {
$scope.list = list;
});
}
$scope.$watch('author', search);
$scope.$watch('isbn', search);
$scope.$watch('title', search);
// the init loading
search();
}])
The markup:
<pre>
<input name="title">
<input name="isbn">
<input name="author">
<br/>
<!-- list the book information -->
<table>
<tr>
<th>Title</th>
<th>Author</th>
<th>ISBN</th>
</tr>
<tr ng-repeat="book in list">
<td ng-bind="book.title"></td>
<td ng-bind="book.author"></td>
<td ng-bind="book.isbn"></td>
</tr>
</table>

ng-form not available after manually deleting an item from collection of ng-repeat

Edit Here is the plunk for this question
I have a master detail form for an accounting transaction. The master portion just contains two fields name and Type. The detail portion can have two or more entries and each entry has AccountId, Debit and Credit fields. The form looks like
You can see that there is a delete button against each entry so if we have more than two entries we can delete any entry at random. The form html looks like following
<body data-ng-app="transactions">
<div data-ng-controller="transactionsController">
<form role="form" name="transactionForm" novalidate data-ng-submit="create()">
<div class="row">
<div class="col-md-2">
</div>
<div class="col-md-4">
<h2 class="form-login-heading">Create Transaction</h2>
<div data-ng-repeat="error in errors" class="alert alert-danger">
{{error[0]}}
</div>
<input type="text" name="name" class="form-control" placeholder="Name" data-ng-model="transaction.Name" required autofocus>
<div>
<span class="error" data-ng-show="transactionForm.name.$error.required && submitted">Please enter Name</span>
</div>
<input type="text" name="type" class="form-control" placeholder="Type" data-ng-model="transaction.Type" required>
<div>
<span class="error" data-ng-show="transactionForm.type.$error.required && submitted">Please enter Type</span>
</div>
<!--<input type="text" readonly name="number" class="form-control" placeholder="Number" data-ng-model="transaction.Number">
<input type="checkbox" data-ng-model="transaction.IsFinalized" /> <label>Finalize</label>-->
<table>
<tr>
<th>Account</th>
<th>Debit</th>
<th>Credit</th>
<th> </th>
</tr>
<tr data-ng-form="entryForm" data-ng-repeat="entry in transaction.Entries track by $index">
<td>
<input required name="accountId" data-ng-model="entry.AccountId" class="form-control" />
<span class="error" data-ng-show="entryForm.accountId.$error.required && submitted">Please select an account</span>
</td>
<td>
<input type="text" name="debit" data-ng-required="!entry.CreditAmount" class="form-control" placeholder="Debit" data-ng-model="entry.DebitAmount">
<span class="error" data-ng-show="entryForm.debit.$error.required && submitted">Debit is required</span>
</td>
<td>
<input type="text" data-ng-focus="checkAddRow($index)" name="credit" data-ng-required="!entry.DebitAmount" class="form-control" placeholder="Credit" data-ng-model="entry.CreditAmount">
<span class="error" data-ng-show="entryForm.credit.$error.required && submitted">Credit is required</span>
</td>
<td><button data-ng-show="transaction.Entries.length>2" class="btn btn-md btn-info " type="button" data-ng-click="deleteRow($index)">delete</button></td>
</tr>
<tr>
<td>Total</td>
<td><input readonly name="totalDebit" type="text" class="form-control" placeholder="Total Debit" data-ng-value="totalDebit()"></td>
<td><input readonly name="totalCredit" compare-to="totalDebit" type="text" class="form-control" placeholder="Total Credit" data-ng-value="totalCredit()"></td>
</tr>
<tr>
<td></td>
<td><b>Difference</b></td>
<td>
<input name="difference" readonly type="text" class="form-control" data-ng-value="difference()">
<!--<span class="error" data-ng-show="submitted && !differencezero">Difference should be 0</span>-->
</td>
</tr>
</table><br />
<button class="btn btn-md btn-info" type="submit">Create</button>
<button class="btn btn-md btn-info" data-ng-show="transaction.Entries.length<15" type="button" data-ng-click="addRow()">Add Row</button>
<div data-ng-hide="message == ''" class="alert alert-danger">
{{message}}
</div>
</div>
<div class="col-md-4">
</div>
<div class="col-md-2">
</div>
</div>
</form>
<style type="text/css">
.error {
color: red;
}
</style>
<pre>{{transactionForm.entryForm|json}}</pre>
</div>
I have the requirement that when focus is on Credit input of last entry the new entry should automatically added to the UI. I do it by using addRow and checkAddRow method on my controller. these methods are as follows
$scope.checkAddRow = function (index) {
if (index == $scope.transaction.Entries.length - 1) {
$scope.addRow();
}
}
$scope.addRow = function () {
entry = {
EntryTime: '',
DebitAmount: '',
CreditAmount: '',
AccountId: ''
};
$scope.transaction.Entries.push(entry);
console.log($scope.transactionForm);
}
$scope.deleteRow = function (index) {
$scope.transaction.Entries.splice(index, 1);
console.log($scope.transactionForm);
}
Again this part is just fine and works well. But I have another requirement that says that if last entry is not used it should not cause the form to invalidate. It should rather be removed from transaction.Entries collection and rest of the data should be saved normally. To achieve this, I have create function defined on $scope that looks like following
$scope.create = function () {
$scope.submitted = true;
if ($scope.transactionForm.entryForm && $scope.transactionForm.entryForm.$invalid && $scope.transactionForm.entryForm.$pristine) {
$timeout(function () {
$scope.deleteRow($scope.transaction.Entries.length - 1);
});
$timeout(function () {
console.log('From time out', $scope.transactionForm.$valid);
console.log($scope.transactionForm.$valid);
if (!$scope.transactionForm.$valid) return;
alert('data saved!');
console.log($scope.transactionForm);
//$scope.transactionForm.name.focus();
}, 200);
}
else {
if ($scope.transactionForm.$valid) {
alert('data saved 2');
}
}
}
You can see that what create function is doing. It is checking if entryForm (ng-form) is present in the main form (transactionForm) then it checks if entryForm is $invalid and $pristine if all these flags are true then, I delete the last entry from $scope.transaction.Entries and save the data (currently an alert to show data is saved) after $timeout. If I don't use timeout then the form is invalid so I have to wait for 200ms before I check the forms $valid flag after removing last row. But to my surprise when I remove last row from create function, there is no entryForm attached to the outer transactionForm. On the other hand If I delete entries using delete buttons present on UI, the entryForm is present inside the main transactionForm. Can anyone explain why is that. I have added <pre>{{transactionForm|json}}</pre> at the end to see when it is and when it is not available on main form. I have created a plunk to show what I mean. just add some data in two input fields of the master portion, enter some data in accountid field of both entries, when you reach the Credit input of second (last) entry, a new entry will be automatically added. Ignore that row and just push create button. The last entry will be removed and data will save but the entryForm will not be there anymore. I am not sure what I am doing wrong.
So, a problem here is that your definition of whether the form is valid or not depends on the state of the last row.
The last row could be of the following variety:
row fetched from the backend, but not new --> should only invalidate if not valid
row is new and $pristine --> should not invalidate
row is new, but $dirty (and still last) --> should only invalidate if not valid
You are trying to remove the last row and then re-evaluate the form for validity.
Approach it the other way - don't let the last row invalidate the form if it's in $pristine state:
Here's a simplified example:
<form name="transactionForm" ng-submit="submit()" novalidate>
<table>
<tr ng-form="entryForm" ng-repeat="transaction in transactions">
<td><input ng-model="transaction.account"
ng-required="transaction !== newLastEntry || entryForm.$dirty"></td>
<td><input ng-model="transaction.amount"
ng-required="transaction !== newLastEntry || entryForm.$dirty"
ng-focus="addEntryIfLast($last)" type="number"></td>
</tr>
</table>
</form>
Note $scope.newLastEntry here. It is set to the new empty (and last) entry. This happens when you add a new empty row:
function addEmptyEntry(){
$scope.newLastEntry = {};
$scope.transactions.push($scope.newLastEntry);
}
And so, ng-required is applied only if row is NOT new last OR otherwise $dirty.
Then, on submit, you can remove the last entry if it's in $pristine state and indeed the new last (as opposed to whatever existed before):
$scope.submit = function(){
var itemsToSubmit = angular.copy($scope.transactions);
if ($scope.transactionForm.$invalid) return;
if ($scope.transactionForm.entryForm &&
$scope.transactionForm.entryForm.$pristine &&
$scope.transactions[$scope.transactions.length - 1] === $scope.newLastEntry) {
itemsToSubmit.splice(itemsToSubmit.length - 1);
}
console.log(JSON.stringify(itemsToSubmit));
};
plunker

Validating nested form in angular

Having this ordinary (name attribute is requred by server) form with angular and can't figured out how to make validations work. What should i put into ng-show="TODO"
http://jsfiddle.net/Xk3VB/7/
<div ng-app>
<form ng-init="variants = [{duration:10, price:100}, {duration:30, price:200}]">
<div ng-repeat="variant in variants" ng-form="variant_form">
<div>
<label>Duration:</label>
<input name="variants[{{$index}}][duration]" ng-model="variant.duration" required />
<span ng-show="TODO">Duration required</span>
</div>
<div>
<label>Price:</label>
<input name="variants[{{$index}}][price]" ng-model="variant.price" />
<span ng-show="TODO">Price required</span>
</div>
</div>
</form>
</div>
ps: this is just piece of form, which is more complicated
Thanks
AngularJS relies on input names to expose validation errors.
Unfortunately, as of today it is not possible (without using a custom directive) to dynamically generate a name of an input. Indeed, checking input docs we can see that the name attribute accepts a string only.
Long story short you should rely on ng-form to validate dynamically created inputs. Something like :
<div ng-repeat="variant in variants" >
<ng-form name="innerForm">
<div>
<label>Duration:</label>
<input name="duration" ng-model="variant.duration" required />
<span ng-show="innerForm.duration.$error.required">Duration required</span>
</div>
<div>
<label>Price:</label>
<input name="price" ng-model="variant.price" required/>
<span ng-show="innerForm.price.$error.required">Price required</span>
</div>
</ng-form>
Working fiddle here
UPDATE : Base on your serverside requirement why not do something like that :
<input type="hidden" name="variants[{{$index}}][duration]" ng-model="variant.duration"/>
<input name="duration" ng-model="variant.duration" required />
The hidden input will be the one read by the server while the other one will be used to do the client side validation (later discarded by server). It s kind of an hack but should work.
PS : Be sure that your form is valid before actually submitting it. Can be done with ng-submit

Resources