I am using custom angular directive for showing validation. Directive code is as below
angularFormsApp.directive('showErrors',['$timeout', function ($timeout) {
return {
restrict: 'A',
require: '^form',
link: function (scope, el, attrs, formCtrl) {
// find the text box element, which has the 'name' attribute
var inputEl = el[0].querySelector("[name]");
// convert the native text box element to an angular element
var inputNgEl = angular.element(inputEl);
// get the name on the text box so we know the property to check
// on the form controller
var inputName = inputNgEl.attr('name');
var helpText = angular.element(el[0].querySelector(".help-block"));
// only apply the has-error class after the user leaves the text box
inputNgEl.bind('blur', function () {
el.toggleClass('has-error', formCtrl[inputName].$invalid);
helpText.toggleClass('hide', formCtrl[inputName].$valid);
});
scope.$on('show-errors-event', function () {
el.toggleClass('has-error', formCtrl[inputName].$invalid);
});
scope.$on('hide-errors-event', function () {
$timeout(function () {
el.removeClass('has-error');
}, 0, false);
});
}
}
}]);
and Html is as below
<div class="container" id="login-form">
<img src="assets/img/logo-big.png">
<div class="row">
<div class="col-md-4 col-md-offset-4">
<div class="panel panel-default">
<div class="panel-heading">
<h2>Login Form</h2>
</div>
<div class="panel-body">
<form name="loginForm" class="form-horizontal" novalidate>
<div class="form-group mb-md" show-errors>
<div class="col-xs-12">
<div class="input-group">
<span class="input-group-addon">
<i class="ti ti-user"></i>
</span>
<input type="text" class="form-control" placeholder="Username" autocomplete="Off" ng-required="true" name="username" autofocus ng-model="loginUser.username">
</div>
<span class="help-block" ng-if="loginForm.username.$error.required">Username is required</span>
</div>
</div>
<div class="form-group mb-md" show-errors>
<div class="col-xs-12" >
<div class="input-group">
<span class="input-group-addon">
<i class="ti ti-key"></i>
</span>
<input type="password" class="form-control" placeholder="Password"
name="password"
ng-model="loginUser.password" autocomplete="Off"
ng-required="true">
</div>
<span class="help-block" ng-if="loginForm.password.$error.required">Password is required</span>
</div>
</div>
<div class="form-group mb-n">
<div class="col-xs-12">
Forgot password?
<div class="checkbox-inline icheck pull-right p-n">
<label for="">
<input type="checkbox"></input>
Remember me
</label>
</div>
</div>
</div>
</form>
</div>
<div class="panel-footer">
<div class="clearfix">
Register
Login
</div>
</div>
</div>
</div>
</div>
</div>
Controller code :
var loginController = function ($scope, $window, $routeParams, $uibModal, $location, $filter, $rootScope, DataService, SharedProperties) {
$rootScope.bodylayout = 'focused-form animated-content';
$scope.loginUser = {
username: "",
password:""
}
$scope.load = function () {
//getAppointmentInfo();
};
$scope.SubmitLoginForm = function () {
$scope.$broadcast('show-errors-event');
if ($scope.loginForm.$invalid)
return;
}
}
angularFormsApp.controller("loginController", ["$scope", "$window", "$routeParams", "$uibModal", "$location", "$filter", "$rootScope", "DataService", "SharedProperties", loginController]);
Now When I open form below input control validation span is displaying by default . When I click on Login button then its showing in red and working fine.
problem is it shouldn't show by default when Page is opend.. Please see image below
Instead of this
loginForm.password.$error.required
try this
(loginForm.$submitted || loginForm.username.$dirty) && loginForm.password.$error.required
Take a look at the ng-messages directive. Its fairly elegant. Example:
<form name="myForm">
<input type="text" ng-model="field" name="myField" required minlength="5" />
<div ng-messages="myForm.myField.$error">
<div ng-message="required">You did not enter a field</div>
<div ng-message="minlength">The value entered is too short</div>
</div>
</form>
You can then combine it with any form validation. Just place the error messages from the validators onto the elements $error object and they are automatically rendered in your UI.
I ended up here as part of my search for an issue. In my case I was using a directive with a changing minimum value like this:
ngOnChanges(changes: SimpleChanges) {
if (changes.minDate && this.control) {
this.control.updateValueAndValidity({ onlySelf: true });
}
}
This means that the form will not be updated, I removed onlySelf and it worked correctly.
this.control.updateValueAndValidity();
Just leaving this as a breadcrumb in case someone else does something similar.
Related
From the repo: https://github.com/jmsherry/kitchenapp
Live at: https://kitchenapp2.herokuapp.com
If you click the 'contact us' link in the bottom right corner of the screen a modal appears. The modal is coded like:
<div class="modal-container">
<button type="button" class="close" aria-label="Close" ng-click="$close(null)">
<span aria-hidden="true">×</span>
</button>
<div class="content">
<h1>Contact Us</h1>
<div class="contact-form">
<form ng-submit="vm.sendMessage(this)" novalidate name="contactForm">
<div class="form-group" ng-if="vm.user && vm.user.email">
<p class="faux-label">From: <span class="faux-value">{{vm.user.email}}</span></p>
</div>
<div class="form-group" ng-class="{ 'has-error' : contactForm.from.$invalid && !contactForm.from.$pristine }" ng-if="!vm.user || !vm.user.email">
<label for="subject">From</label>
<input type="email" name="from" id="from" class="form-control" placeholder="From" ng-model="vm.user.email" ng-maxlength="30" ng-required="true" autofocus>
<ul ng-if="contactForm.from.$invalid && !contactForm.from.$pristine" ng-messages="contactForm.from.$error" class="help-block list-unstyled">
<li ng-message="maxlength">The subject must be less than 30 characters.</li>
<li ng-message="email">Not a valid email. Please check carefully and try again...</li>
</ul>
</div>
<div class="form-group" ng-class="{ 'has-error' : contactForm.subject.$invalid && !contactForm.subject.$pristine }">
<label for="subject">Subject</label>
<input type="text" name="subject" id="subject" class="form-control" placeholder="Subject" ng-model="vm.email.subject" ng-maxlength="30">
<ul ng-if="contactForm.subject.$invalid && !contactForm.subject.$pristine" ng-messages="contactForm.subject.$error" class="help-block list-unstyled">
<li ng-message="maxlength">The subject must be less than 30 characters.</li>
</ul>
</div>
<div class="form-group" ng-class="{ 'has-error' : contactForm.message.$invalid && !contactForm.message.$pristine }">
<label for="message">Message</label>
<textarea name="message" id="message" class="form-control" placeholder="Message..." ng-model="vm.email.body" ng-required="true" ng-minlength="10" ng-maxlength="500"></textarea>
<ul ng-if="contactForm.message.$invalid && !contactForm.message.$pristine" ng-messages="contactForm.message.$error" class="help-block list-unstyled">
<li ng-message="required">A description is required.</li>
<li ng-message="minlength">The name must be longer than 10 characters.</li>
<li ng-message="maxlength">The name must be less than 500 characters.</li>
</ul>
</div>
<!-- <div class="form-group"> -->
<div class="checkbox">
<label for="cc_self">
<input type="checkbox" id="cc_self" name="cc_self" ng-model="vm.email.cc_self">Send a copy to my email.
</label>
</div>
<!-- </div> -->
<div class="form-group">
<div class="pull-right">
<button type="button" class="btn btn-default" ng-click="$close(null)">Cancel</button>
<button type="submit" class="btn btn-primary" ng-disabled="contactForm.$invalid">Send</button>
</div>
</div>
</form>
</div>
</div>
</div>
Controller looks like:
angular.module('kitchenapp.directives')
.controller('KaFooterCtrl', ['$scope', '$q', '$log', '$modal', 'Email', 'Auth', 'toastr', function ($scope, $q, $log, $modal, Email, Auth, toastr) {
var vm = this,
$user;
function contact(day) {
$log.log('contact fn', arguments);
$modal.open({
templateUrl: '/views/modals/contact-modal.html',
controller: function modalController($modalInstance, $scope) {
$scope.vm = this;
$scope.vm.email = vm.email;
$scope.vm.user = vm.user;
$scope.vm.sendMessage = function () {
$modalInstance.close();
$q.when(vm.user, function (user) {
var $emailSent = Email.sendEmail(vm.email, vm.user);
$q.when($emailSent, function (res) {
toastr.success('Email sent! Thank you for your interest...');
}, function () {
toastr.error('We\'re sorry your email hasn\'t been sent, please try again later...');
});
});
event.preventDefault();
event.stopPropagation();
};
},
contollerAs: 'vm'
});
}
vm.year = new Date().getFullYear();
vm.email = {};
$user = Auth.getUser();
$q.when($user, function (user) {
vm.user = user;
});
vm.contact = contact;
vm.name = 'KaFooterCtrl';
}])
.directive('kaFooter', function () {
return {
restrict: 'E',
replace: true,
templateUrl: 'directives/footer/footer.html',
scope: {
companyName: '#',
companyEmail: '#'
},
controller: 'KaFooterCtrl as vm',
bindToController: true
};
});
}());
For some reason the first element focusses once, but any further interaction and the elements are not focussable by touch or mouse. Any clues why?
Thanks
So, it transpires that this is a problem when ng-touch is used. ng-touch auto-blurs the fields (https://github.com/angular/angular.js/pull/11212). A fix is on the way, but for now the user #jdhiro suggests using a directive to stop propagation of the touchend event outside of your modal. See https://github.com/angular-ui/bootstrap/issues/2017#issuecomment-39515923 for more
I have this dynamic form which entries I would like to save. The problem is that the saved entry is like this [{"name":"Noora","":{"Gender":"Woman","Address":"filler address"}}] I'm curios why the app saves the info after name like a nameless list. Name is hardcoded input and the other two (Gender and Address) can be dynamically added when using the program.
Here is the form entry html part:
<form>
<h2>Sign in:</h2>
<div class="form-group">
<label for="eventInput">Name</label>
<input style="width: 200px;" type="text" class="form-control" id="eventInput" data-ng-model="newEntry.name">
</div>
<div data-ng-repeat="field in event.fields">
<div class="form-group">
<label for="{{$index + 1}}">{{field.name}}</label>
<input style="width: 200px;" type="text" class="form-control" id="{{$index + 1}}" data-ng-model="newEntry.[field.name]">
</div>
</div>
<span>{{entries}}</span>
<div class='wrapper text-center'>
<div class="form-group">
<div class="col-lg-4 col-lg-offset-4">
<button style="width: 100px;" data-ng-click="addEntry()" class="btn btn-primary">Enroll</button>
<p></p>
<button style="width: 100px;" data-ng-click="back()" class="btn btn-primary">Back</button>
</div>
</div>
</div>
</form>
and here is the controller:
App.controller('aboutController', function($scope, $location, $routeParams, eventService) {
$scope.event = eventService.getCustomers()[$routeParams.id];
$scope.back = function() {
$location.path('/');
};
$scope.addEntry = function() {
$location.path('/');
$scope.event.entries.push($scope.newEntry);
};
});
I would like to either be able to name the child list or just record the entries into a continuous list. Any idea how would it be possible?
Br,
Norri
I have this html form:
<div class="modal-body">
<form name="addAdminForm">
<div class="form-group addPopupLabel">
<div class="container-fluid-full" id="email3">
<input placeholder="Email" type="text" ng-model="model.email" required />
</div>
<div class="container-fluid-full">
<input placeholder="Password (at last 6 characters)" type="password" ng-model="model.password1" id="pw1" name="pw1" required />
</div>
<div class="container-fluid-full">
<input placeholder="Confirm password" type="password" ng-model="model.password2" id="pw2" name="pw2" required password-compare="pw1" />
</div>
<div class="container-fluid-full">
<label>Admin</label>
<input type="radio" class="form-control" name="role" ng-model="model.role" ng-value="userRoles.admin">
</div>
<div class="container-fluid-full">
<label>CS</label>
<input type="radio" class="form-control" name="role" ng-model="model.role" ng-value="userRoles.cs">
</div>
<div>
<span class="errormessage" style="color:red">{{errormessage}}</span>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button class="btn btn-primary" ng-disabled="addAdminForm.$invalid" ng-click="createForm.$invalid || ok()">Submit</button>
<button class="btn btn-warning" ng-click="cancel()">Cancel</button>
</div>
The problem: button stay on disable mode while one of the fields is on focus.
How can i solve it by FormController?
The addAdminForm is scoped within the form. So one option is to move the buttons within the form, or move the form element so that it wraps the buttons. I would prefer this since it is dead simple and most times doable.
If it is not possible for some reason, you can make a directive to export the $invalid flag of a form to a variable of the outer scope. A simple implementation would be:
app.directive('bindValidity', ['$parse', function($parse) {
return {
restrict: 'A',
scope: false,
controller: ['$scope', '$attrs', function($scope, $attrs) {
var assign = $parse($attrs.bindValidity).assign;
if (!angular.isFunction(assign)) {
throw new Error('the expression of bindValidity is not settable: ' + $attrs.bindValidity);
}
this.setFormController = function(formCtrl) {
if (!formCtrl) {
throw new Error('bindValidity requires one of <form> or ng-form');
}
$scope.$watch(
function () {
return formCtrl.$invalid;
},
function (newval) {
assign($scope, newval);
}
);
};
}],
require: ['?form', '?ngForm', 'bindValidity'],
link: function (scope, elem, attrs, ctrls) {
var formCtrl, bindValidity;
formCtrl = ctrls[0] || ctrls[1];
bindValidity = ctrls[2];
bindValidity.setFormController(formCtrl);
}
};
}]);
Use it as:
<div class="modal-body">
<form name="addAdminForm" bind-validity="some.variable">
...
</form>
<div class="modal-footer">
<button ... ng-disabled="some.variable" ng-click="some.variable || ok()">Submit</button>
Here's Fiddle Link
Update--> works great with angular 1.3 by setting ng-model-options but i am using 1.2 :(
Here's Fiddle Link
Currently when user click on submit error comes up and goes away once he entered valid input. But What i want is not to hide the errors as user enter valid info.If user re-enter the correct data errors should go away only on clicking submit button only.
HTML
<div ng-app="app" ng-controller="MainCtrl">
<div class="panel-group">
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">Order Form</h4>
</div>
<div class="panel-body">
<ng-include src="'form.html'"></ng-include>
</div>
</div>
</div>
<!-- kept seperate from the bootstrap markup to keep this example clean -->
<script type="text/ng-template" id="form.html">
<form name="form" valid-submit="sendForm()" novalidate>
<!-- call name-->
<div class="form-group clearfix" ng-class="{
'has-error': form.$submitted && form.orderNumber.$invalid,
'has-success': form.$submitted && form.orderNumber.$valid}">
<label class="col-sm-2 control-label" for="callName">Order Number</label>
<div class="col-sm-5">
<input id="orderNumber" name="orderNumber" class="form-control" type="text" ng-model="auth.orderNumber" ng-minlength="7" ng-maxlength="10" ng-pattern="/^[0-9]*$/" required ></input>
<div class="alert alert-danger" ng-show="form.$submitted && form.orderNumber.$error.required">Order Number required</div>
<span ng-show="form.$submitted && form.orderNumber.$error.pattern" class="help-block">Order number contains only numbers</span>
<span ng-show="form.$submitted && form.orderNumber.$error.minlength " class="help-block">Order number is too short</span>
<span ng-show="form.$submitted && form.orderNumber.$error.maxlength" class="help-block">Order number is too long</span>
</div>
</div>
<!-- last name-->
<div class="form-group clearfix" ng-class="{
'has-error': form.$submitted && form.confirmorderNumber.$invalid,
'has-success': form.$submitted && form.confirmorderNumber.$valid}">
<label class="col-sm-2 control-label" for="confirmorderNumber">Confirm Order Number</label>
<div class="col-sm-5">
<input id="lastName" name="confirmorderNumber" class="form-control" type="text" ng-model="person.lastName" data-match="auth.orderNumber" required></input>
<div class="alert alert-danger" ng-show="form.$submitted && form.confirmorderNumber.$error.required">required</div>
<span ng-show="form.$submitted && form.confirmorderNumber.$error.match" class="help-block">Order numbers must match</span>
</div>
</div>
<!-- form controls-->
<div class="form-group">
<button type="submit" class="btn btn-primary">Submit!</button>
</div>
</form>
</script>
</div>
js
var app = angular.module('app', []);
// directive that prevents submit if there are still form errors
app.directive('validSubmit', [ '$parse', function($parse) {
return {
// we need a form controller to be on the same element as this directive
// in other words: this directive can only be used on a <form>
require: 'form',
// one time action per form
link: function(scope, element, iAttrs, form) {
form.$submitted = false;
// get a hold of the function that handles submission when form is valid
var fn = $parse(iAttrs.validSubmit);
// register DOM event handler and wire into Angular's lifecycle with scope.$apply
element.on('submit', function(event) {
scope.$apply(function() {
// on submit event, set submitted to true (like the previous trick)
form.$submitted = true;
// if form is valid, execute the submission handler function and reset form submission state
if (form.$valid) {
fn(scope, { $event : event });
//form.$submitted = false;
}
});
});
}
};
}
]);
app.directive('match', function () {
return {
require: 'ngModel',
restrict: 'A',
scope: {
match: '='
},
link: function(scope, elem, attrs, ctrl) {
scope.$watch(function() {
var modelValue = ctrl.$modelValue || ctrl.$$invalidModelValue;
return (ctrl.$pristine && angular.isUndefined(modelValue)) || scope.match === modelValue;
}, function(currentValue) {
ctrl.$setValidity('match', currentValue);
});
}
};
});
// handle form submission when the form is completely valid
app.controller('MainCtrl', function($scope) {
$scope.sendForm = function() {
alert('form valid, sending request...');
};
});
I have two tiny projects, both use essentially the same setup, one returns a model value one returns the model value as undefined. At this point I am lost as to why these two examples are behaving differently. I checked the failing example, and the model correctly populates
Successful Behavior
Partial
<div class="row">
<div class="col-md-4 .col-md-offset-4 content-pane no-margin">
<h4>Input Your Inquiry</h4>
<form ng-submit="submit(invoice)">
<div class="control-group">
<div class="controls">
<div class="input-prepend">
<span class="add-on">Invoice Number</span>
<input id="who" type="text" class="input-xlarge" ng-model="invoice.number">
</div>
</div>
</div>
<div class="control-group">
<div class="controls">
<div class="input-prepend">
<span class="add-on">PO Number</span>
<input id="who" type="text" class="input-xlarge" ng-model="invoice.po">
</div>
</div>
</div>
<input type="submit">
</form>
</div>
<div ng-if="status" class="col-md-4 .col-md-offset-4 content-pane no-margin">
<h4>Invoice Status</h4>
<div>{{status}}</div>
</div>
</div>
App.js
app.config(function($routeProvider){
$routeProvider
.when('/',
{
templateUrl: 'app/partials/inquiry-input-form.html',
controller: 'MainCtrl'
})
.otherwise(
{
templateUrl: 'app/partials/404.html'
})
});
app.controller('MainCtrl', function($scope,formsResults) {
$scope.submit = function() {
var invoiceNum = $scope.invoice.number;
var po = $scope.invoice.po;
formsResults.getInvoiceStatus(po,invoiceNum).success(function(data) {
var status = data.records;
if (angular.isUndefined(status)) {
$scope.status = 'Pending Reciept or Entry';
} else {
$scope.status = status[0].field_124208[0];
}
});
};
});
app.factory('formsResults', function($http) {............
Returning Model as undefined in controller
Partial
<tabset>
<tab active=workspace.active>
<tab-heading>
Search All Fields
</tab-heading>
<div>
<br /><br />
<form class="form-horizontal" ng-submit="submitAll(searches)">
<div class="form-group">
<div class="col-md-4">
<input id="textinput" name="textinput" type="text" class="form-control input-md" ng-model="searches.term">
<span class="help-block">Enter the term you wish to search on.</span>
{{searches.term}}
</div>
</div>
<!-- Button -->
<div class="form-group">
<div class="col-md-4">
<button id="singlebutton" name="singlebutton" class="btn btn-primary" type="submit">Search</button>
</div>
</div>
</form>
</div>
</tab>
<tab>
<tab-heading>
Search By Field
</tab-heading>
<div>
<br /><br />
<form class="form-horizontal" ng-submit="submitSpecific(search)">
<div class="form-group">
<div class="col-md-4">
<select id="selectbasic" name="selectbasic" class="form-control">
<option value="4">Country</option>
</select>
<span class="help-block">Enter the field you wish to search on.</span>
</div>
</div>
<!-- Text input-->
<div class="form-group">
<div class="col-md-4">
<input id="textinput" name="textinput" type="text" class="form-control input-md">
<span class="help-block">Enter the term you wish to search on.</span>
</div>
</div>
<div class="form-group">
<div class="col-md-4">
<button id="singlebutton" name="singlebutton" class="btn btn-primary" type="submit">Search</button>
</div>
</div>
</form>
</div>
</tab>
</tabset>
App.js
var app = angular.module ('app',['ngRoute','ngSanitize','ui.bootstrap','dialogs.controllers','dialogs']);
app.config(function($routeProvider){
//http://docs.angularjs.org/tutorial/step_07
$routeProvider
.when('/',
{
templateUrl: 'app/partials/home_tpl.html',
controller: 'HomeCtrl'
})
.when('/search',
{
templateUrl: 'app/partials/search-form.html',
controller: 'SearchCtrl'
})
.when('/add-sop',
{
templateUrl: 'app/partials/support-process.html',
controller: 'EditSOPCtrl'
})
.otherwise(
{
redirectTo: '/'
})
});
app.controller('HomeCtrl', function($scope) {
});
app.controller('SearchCtrl', function($scope,formsSopSearchCriteria) {
$scope.submitAll = function() {
var searchTerm = $scope.searches.term;
console.log(searchTerm);
var searchPackage={
"offset":0,
"limit":0,
"fields":["record_id","dt_created","created_by","dt_updated","updated_by",149654,149655,149692],
"filters":{"and":[{"field_id":"149655","operator":"contains","value":searchTerm}]},
"sort":{}
};
searchPackage=JSON.stringify(searchPackage);
$scope.searchPackage = searchPackage;
formsSopSearchCriteria.getMatches(searchPackage).success(function(data) {
$scope.sopRecords = data.records;
});
};
$scope.submitSpecific = function() {
var searchPackage = ""
searchPackage=JSON.stringify(searchPackage);
$scope.searchPackage = searchPackage;
formsSopSearchCriteria.getMatches(searchPackage).success(function(data) {
$scope.sopRecords = data.records;
});
};
});
app.factory('formsSopSearchCriteria',function($http) {........
Any ideas? My understanding is that it as an issue of scope. But in the failing example, the partial I provide is mapped to the SearchCtrl in the routeProvider. As a result the model should be available in the controller, just like the example that is working. I suspect I must be missing something, but I am not clear what I am missing.
You need to create $scope.searches as an empty object on initialization:
app.controller('SearchCtrl', function($scope,formsSopSearchCriteria) {
$scope.searches = {};
Or for clarity:
$scope.searches =
{
term: ''
};
If you don't, it will not be created until the first input is entered (I believe it is the $parse service that the ng-model directive uses that creates the searches object in this case).
The problem is that the object might not be created in the scope you want it to.
The reason it works in the first case is because there are no child scopes, so the invoice object is created in the controller scope.
In the second case there is one or many child scopes (probably from the tabs, I haven't dug any deeper into it), so the searches object is created in one of those instead.
Due to how prototypical inheritance works in Javascript, if you predefine the objects in the controllers' scopes, the properties will be created correctly.