Related
I want to check a radio button in my protractor script and the html is as follows:
<input type="radio" ng-model="relation.type" value="personal" class="ng-valid ng-dirty ng-valid-parse ng-touched" name="7">
How can I write an expression chosing this radio button over another in a list of radio buttons? another button that I do not want to choose looks like this:
<input type="radio" ng-model="relation.type" value="employee" class="ng-pristine ng-untouched ng-valid" name="8">
so what is unique between the two is the "value".
Something like this maybe?
var expectedRadio = element.all(by.model('relation.type'));
expectedRadio.each((eachRadio) => {
eachRadio.getAttribute('value').then((valueText) => {
if (valueText == "personal")
{
//Do what you need
}
});
});
You can use like this.
element.all(by.model('relation.type')).get(1).getAttribute('value');
element.all(by.model('relation.type"')).get(2).getAttribute('value');
element.all(by.model('relation.type')).first().getAttribute('value');
element.all(by.model('relation.type"')).last().getAttribute('value');
Please also refer protractor documentation here
Try this :
try {
let btn = element(by.xpath('/html/body/app-root/app-login/div[3]/label[2]'));
browser.executeScript('arguments[0].click()', btn).then(async() => {
console.log(' Radio Btn has been clicked.');
});
} catch (error) {
console.log('Button is not clickable due ' + error);
}
When I trigger the onClick even the event keeps triggering for about 1000+ times. I can't seem to figure where this is coming from. I have changed the onClick to an onMouseover to see if it keeps triggering but then the event only triggers once.
I'm using : react 0.13.3
Any idea's?
var React = require('react');
var AppActions = require('../../actions/app-actions.js');
var FileAmount = React.createClass({
getInitialState: function() {
return {
amount : this.props.amount,
config : this.props.config
};
},
handleClick: function(e){
var name = e.target.name;
if(name === 'decrease'){
if(this.state.amount > 1){
this.setState({
amount : (this.state.amount - 1)
});
AppActions.updateAmount(this.props.index, (this.state.amount - 1))
}
}else{
this.setState({
amount : (this.state.amount + 1)
});
AppActions.updateAmount(this.props.index, (this.state.amount + 1))
}
},
handleChange: function(e){
var amount = e.target.value;
this.setState({
amount : amount
});
AppActions.updateAmount(this.props.index, amount)
},
render: function() {
var config = this.state.config
return (
<div className="file-amount">
<span className="file-amount-text"> {config.filelist_quantity}: {this.state.amount} {config.filelist_pieces}</span>
<div className="file-amount-fields">
<i className="file-amount-decrease icon" name="decrease" onClick={this.handleClick} />
<input className="file-amount-input" type="number" value={this.state.amount} onChange={this.handleChange} />
<i className="file-amount-increase icon" name="increase" onClick={this.handleClick} />
</div>
</div>
);
}
});
module.exports = FileAmount;
I left a comment on the original post, but on second inspection, it looks quite possible that you pass down props.amount from a Flux Store. If that is the case you're creating an infinite loop.
handleClick increments state.amount, then after the AppAction is called, the Store updates the component with props.amount, then the onChange fires because it is tied to state.amount and then onChange changes state.amount and changes props.amount when it calls AppActions.updateAmount.
Every time props or state are updated, React will call the render() method. If there is any way that props or state get updated while the render() executes, then you are likely going to run into an infinite loop.
Perhaps adding a e.preventDefault(); to your handleClick method will stop this loop from being started.
I removed the added javascript for the browser-sync proxy settings. That somehow screwed around with my react.
When submitting a form in AngularJS and use the browser remember password functionality, and in a subsequent login attempt you let the browser fill in the login form with the username and password, the $scope model won't be changed based on the autofill.
The only dirty hack I found is to use the following directive:
app.directive("xsInputSync", ["$timeout" , function($timeout) {
return {
restrict : "A",
require: "?ngModel",
link : function(scope, element, attrs, ngModel) {
$timeout(function() {
if (ngModel.$viewValue && ngModel.$viewValue !== element.val()) {
scope.apply(function() {
ngModel.$setViewValue(element.val());
});
}
console.log(scope);
console.log(ngModel.$name);
console.log(scope[ngModel.$name]);
}, 3000);
}
};
}]);
The problem is that the ngModel.$setViewValue(element.val()); doesn't change the model nor the view based on the element.val() returned value. How can I accomplish that?
Apparently this is a known issue with Angular and is currently open
I'm not sure what you could do here besides some sort of work around like you're trying. It seems you're on the right track. I couldn't get my browser to try to remember a password for your plunk, so I'm not sure if this will work but have a look:
app.directive('autoFillSync', function($timeout) {
return {
require: 'ngModel',
link: function(scope, elem, attrs, ngModel) {
var origVal = elem.val();
$timeout(function () {
var newVal = elem.val();
if(ngModel.$pristine && origVal !== newVal) {
ngModel.$setViewValue(newVal);
}
}, 500);
}
}
});
<form name="myForm" ng-submit="login()">
<label for="username">Username</label>
<input type="text" id="username" name="username" ng-model="username" auto-fill-sync/><br/>
<label for="password">Password</label>
<input type="password" id="password" name="password" ng-model="password" auto-fill-sync/><br/>
<button type="submit">Login</button>
</form>
I think you just need to simplify your approach a bit. The one thing I definitely recommend is to check ngModel.$pristine and make sure you're not overwriting some poor user's input. Also, 3 seconds is probably too long. You shouldn't have to call $apply() in a $timeout, BTW, it should queue a $digest for you automatically.
The real catch: Will your browser beat Angular to execution? What about my browser?
This is probably an unwinnable war, which is why Angular (or Knockout) hasn't been able to solve it readily. There's no guarantee of the state of the data in your input at the time of the directive's initial execution. Not even at the time of Angular's initialization.... So it's a tricky problem to solve.
Here is a solution that is far less hacky than other solutions presented and is semantically sound AngularJS: http://victorblog.com/2014/01/12/fixing-autocomplete-autofill-on-angularjs-form-submit/
myApp.directive('formAutofillFix', function() {
return function(scope, elem, attrs) {
// Fixes Chrome bug: https://groups.google.com/forum/#!topic/angular/6NlucSskQjY
elem.prop('method', 'POST');
// Fix autofill issues where Angular doesn't know about autofilled inputs
if(attrs.ngSubmit) {
setTimeout(function() {
elem.unbind('submit').submit(function(e) {
e.preventDefault();
elem.find('input, textarea, select').trigger('input').trigger('change').trigger('keydown');
scope.$apply(attrs.ngSubmit);
});
}, 0);
}
};
});
Then you simply attach the directive to your form:
<form ng-submit="submitLoginForm()" form-autofill-fix>
<div>
<input type="email" ng-model="email" ng-required />
<input type="password" ng-model="password" ng-required />
<button type="submit">Log In</button>
</div>
</form>
You don't have to use a $timeout or anything like this. You can use an event system.
I think it's more Angularish and does not depend on jQuery or custom event catching.
For example on your submit handler:
$scope.doLogin = function() {
$scope.$broadcast("autofill:update");
// Continue with the login.....
};
And then you can have an autofill directive like this:
.directive("autofill", function () {
return {
require: "ngModel",
link: function (scope, element, attrs, ngModel) {
scope.$on("autofill:update", function() {
ngModel.$setViewValue(element.val());
});
}
}
});
Finally, your HTML will be like:
<input type="text" name="username" ng-model="user.id" autofill="autofill"/>
No need to hack anymore! Angular dev tbosch made a polyfill that triggers a change event when the browser changes form fields without triggering a change event:
https://github.com/tbosch/autofill-event
For now they won't build this into the Angular code, as this is a bugfix for the browser, and also works without Angular (e.g. for plain jQuery apps).
"The polyfill will check for changes on document load and also when an input is left (only in the same form). However, you can trigger the check manually if you want to.
The project has unit tests as well as semi automatic tests, so we finally have a place to collect all the different use case together with the required browser settings.
Please note: This polyfill works with plain AngularJS apps, with AngularJS/jQuery apps but also with plain jQuery apps that do not use Angular."
It can be installed with:
bower install autofill-event --save
Add the script autofill-event.js after jQuery or Angular in your page.
This will do the following:
after DOMContentLoaded: check all input fields
a field is left: check all other fields in the same form
API (to manually trigger the check):
$el.checkAndTriggerAutoFillEvent(): Execute the check for all DOM elements in the given jQuery / jQLite element.
How it works
Remember all changes to input elements by the user (listening for change events) and also by JavaScript (by intercepting $el.val() for jQuery / jQLite elements). That changed value is stored on the element in a private property.
Checking an element for auto fill: Compare the current value of the element with the remembered value. If it's different, trigger a change event.
Dependencies
AngularJS or jQuery (works with either one or both)
More info and source on the github page.
Original Angular Issue #1460 on Github can be read here.
Dirty code, check if issue https://github.com/angular/angular.js/issues/1460#issuecomment-18572604 is fixed before using this code.
This directive triggers events when field is filled, not only before submit (it's necessary if you have to handle input before submit)
.directive('autoFillableField', function() {
return {
restrict: "A",
require: "?ngModel",
link: function(scope, element, attrs, ngModel) {
setInterval(function() {
var prev_val = '';
if (!angular.isUndefined(attrs.xAutoFillPrevVal)) {
prev_val = attrs.xAutoFillPrevVal;
}
if (element.val()!=prev_val) {
if (!angular.isUndefined(ngModel)) {
if (!(element.val()=='' && ngModel.$pristine)) {
attrs.xAutoFillPrevVal = element.val();
scope.$apply(function() {
ngModel.$setViewValue(element.val());
});
}
}
else {
element.trigger('input');
element.trigger('change');
element.trigger('keyup');
attrs.xAutoFillPrevVal = element.val();
}
}
}, 300);
}
};
});
Seems like clear straight ahead solution. No jQuery needed.
UPDATE:
Model is updated only when model value isn't equal to actual input
value.
Checking doesn't stop on first autofill. In case if you wish to use
another account for example.
app.directive('autofillable', ['$timeout', function ($timeout) {
return {
scope: true,
require: 'ngModel',
link: function (scope, elem, attrs, ctrl) {
scope.check = function(){
var val = elem[0].value;
if(ctrl.$viewValue !== val){
ctrl.$setViewValue(val)
}
$timeout(scope.check, 300);
};
scope.check();
}
}
}]);
Solution 1 [Using $timeout]:
Directive:
app.directive('autoFillSync', function($timeout) {
return {
require: 'ngModel',
link: function(scope, elem, attrs, model) {
var origVal = elem.val();
$timeout(function () {
var newVal = elem.val();
if(model.$pristine && origVal !== newVal) {
model.$setViewValue(newVal);
}
}, 500);
}
};
});
HTML:
<form name="myForm" ng-submit="login()">
<label for="username">Username</label>
<input type="text" id="username" name="username" ng-model="username" auto-fill-sync/><br/>
<label for="password">Password</label>
<input type="password" id="password" name="password" ng-model="password" auto-fill-sync/><br/>
<button type="submit">Login</button>
</form>
Solution 2 [Using angular events]:
Ref: Becko's answer
Directive:
app.directive("autofill", function () {
return {
require: "ngModel",
link: function (scope, element, attrs, ngModel) {
scope.$on("autofill:update", function() {
ngModel.$setViewValue(element.val());
});
}
};
});
HTML:
<form name="myForm" ng-submit="login()">
<label for="username">Username</label>
<input type="text" id="username" name="username" ng-model="username" autofill/><br/>
<label for="password">Password</label>
<input type="password" id="password" name="password" ng-model="password" autofill/><br/>
<button type="submit">Login</button>
</form>
Solution 3 [Using relay method calls]:
Directive:
app.directive('autoFill', function() {
return {
restrict: 'A',
link: function(scope,element) {
scope.submit = function(){
scope.username = element.find("#username").val();
scope.password = element.find("#password").val();
scope.login();//call a login method in your controller or write the code here itself
}
}
};
});
HTML:
<form name="myForm" auto-fill ng-submit="submit()">
<label for="username">Username</label>
<input type="text" id="username" name="username" ng-model="username" />
<label for="password">Password</label>
<input type="password" id="password" name="password" ng-model="password" />
<button type="submit">Login</button>
</form>
Well, the easiest way it's to emulate the browser's behavior, so if there is a problem with the change event, just fire it yourself. Much simpler.
Directive:
yourModule.directive('triggerChange', function($sniffer) {
return {
link : function(scope, elem, attrs) {
elem.on('click', function(){
$(attrs.triggerChange).trigger(
$sniffer.hasEvent('input') ? 'input' : 'change'
);
});
},
priority : 1
}
});
HTML:
<form >
<input data-ng-model="user.nome" type="text" id="username">
<input data-ng-model="user.senha" type="password" id="password" >
<input type="submit" data-ng-click="login.connect()" id="btnlogin"
data-trigger-change="#password,#username"/>
</form>
You can do some variations, like putting the directive on the form and firing the event on all inputs with the .dirty class on form submit.
This is jQuery way :
$(window).load(function() {
// updates autofilled fields
window.setTimeout(function() {
$('input[ng-model]').trigger('input');
}, 100);
});
This is Angular way :
app.directive('autofill', ['$timeout', function ($timeout) {
return {
scope: true,
require: 'ngModel',
link: function (scope, elem, attrs, ctrl) {
$timeout(function(){
$(elem[0]).trigger('input');
// elem.trigger('input'); try this if above don't work
}, 200)
}
}
}]);
HTML
<input type="number" autofill />
Here's another workaround that's less hacky, but requires some extra code in the controller.
HTML:
<form ng-submit="submitForm()" ng-controller="FormController">
<input type="text" ng-model="username" autocomplete-username>
<input type="submit">
</form>
Directive (CoffeeScript):
directives.directive 'autocompleteUsername', ->
return (scope, element) ->
scope.getUsername = ->
element.val()
Controller:
controllers.controller 'FormController', [->
$scope.submitForm = ->
username = $scope.getUsername?() ? $scope.username
# HTTP stuff...
]
This is the only solution I've found that allowed all of my Angular' validations to work as designed including disable/enable of submit button. Installs with bower and 1 script tag. Bazinga!
https://github.com/tbosch/autofill-event
Changing the model value, instead of using a timeout function worked for me.
Here is my code:
module.directive('autoFill', [ function() {
return {
require: 'ngModel',
link:function(scope, element, attr, ngModel) {
var origVal = element.val();
if(origVal){
ngModel.$modelValue = ngModel.$modelValue || origVal;
}
}
};
}]);
One-liner workaround in the submit handler (requires jQuery):
if (!$scope.model) $scope.model = $('#input_field').val();
I force a $setValue(val()) on submit: (this works without jQuery)
var ValidSubmit = ['$parse', function ($parse) {
return {
compile: function compile(tElement, tAttrs, transclude) {
return {
post: function postLink(scope, element, iAttrs, controller) {
var form = element.controller('form');
form.$submitted = false;
var fn = $parse(iAttrs.validSubmit);
element.on('submit', function(event) {
scope.$apply(function() {
var inputs = element.find('input');
for(var i=0; i < inputs.length; i++) {
var ele = inputs.eq(i);
var field = form[inputs[i].name];
field.$setViewValue(ele.val());
}
element.addClass('ng-submitted');
form.$submitted = true;
if(form.$valid) {
fn(scope, {$event:event});
}
});
});
scope.$watch(function() { return form.$valid}, function(isValid) {
if(form.$submitted == false) return;
if(isValid) {
element.removeClass('has-error').addClass('has-success');
} else {
element.removeClass('has-success');
element.addClass('has-error');
}
});
}
}
}
}
}]
app.directive('validSubmit', ValidSubmit);
I am very new to Angularjs, but I found a simple solution to that problem=>
Force Angular to reevaluate expression... by changing it!
(of course you need to remember the initial value to revert to initial state)
Here is the way it goes in your controller function for submitting the form:
$scope.submit = function () {
var oldpassword = $scope.password;
$scope.password = '';
$scope.password = oldpassword;
//rest of your code of the submit function goes here...
where of course, the value entered in your password input has been set by windows and not by user.
You can try this code :
yourapp.directive('autofill',function () {
return {
scope: true,
require: 'ngModel',
link: function (scope, elem, attrs, ctrl) {
var origVal = elem.val();
if (origVal != '') {
elem.trigger('input');
}
}
}
});
A minor modification to this answer (https://stackoverflow.com/a/14966711/3443828): use an $interval instead of a $timeout so you don't have to race the browser.
mod.directive('autoFillSync', function($interval) {
function link(scope, element, attrs, ngModel) {
var origVal = element.val();
var refresh = $interval(function() {
if (!ngModel.$pristine) {
$interval.cancel(refresh);
}else{
var newVal = element.val();
if (origVal !== newVal) {
ngModel.$setViewValue(newVal);
$interval.cancel(refresh);
}
}
}, 100);
}
return {
require: 'ngModel',
link: link
}
});
This is the solution I ended up using in my forms.
.directive('autofillSync', [ function(){
var link = function(scope, element, attrs, ngFormCtrl){
element.on('submit', function(event){
if(ngFormCtrl.$dirty){
console.log('returning as form is dirty');
return;
}
element.find('input').each(function(index, input){
angular.element(input).trigger('input');
});
});
};
return {
/* negative priority to make this post link function run first */
priority:-1,
link: link,
require: 'form'
};
}]);
And the form's template will be
<form autofill-sync name="user.loginForm" class="login-form" novalidate ng-submit="signIn()">
<!-- Input fields here -->
</form>
This way I was able to run any parsers/formatters I have on my ng-model and have the submit functionality transparent.
Solution without directives:
.run(["$window", "$rootElement", "$timeout", function($window, $rootElement, $timeout){
var event =$window.document.createEvent("HTMLEvents");
event.initEvent("change", true, true);
$timeout(function(){
Array.apply(null, $rootElement.find("input")).forEach(function(item){
if (item.value.length) {
item.$$currentValue = item.value;
item.dispatchEvent(event);
}
});
}, 500);
}])
This is a simple fix that works for all the cases I've tested in both Firefox and Chrome. Note that with the top answer (directive w/ timeout) I had issues with -
Browser back / forward buttons, don't re-fire page load events (so the fix doesn't apply)
Loading of credentials some time after page load. e.g. in Firefox, double click on the login box and select from stored credentials.
Need a solution that updates before form submission since I disable the Login button until valid input provided
This fix is obviously very dumb and hacky, but it works 100% of the time -
function myScope($scope, $timeout) {
// ...
(function autoFillFix() {
$timeout(function() {
$('#username').trigger('change');
$('#password').trigger('change');
autoFillFix(); }, 500);
})();
}
None of these solutions worked for my use case. I have some form fields that use ng-change to watch for change. Using $watch is no help as it is not triggered by autofill. Since I have no submit button there is no easy way to run some of the solutions and I was not successful using intervals.
I ended up disabling autofill - not ideal but a lot less confusing to the user.
<input readonly onfocus="this.removeAttribute('readonly');">
Found the answer here
If you are using jQuery you could do this on form submit:
HTML:
<form ng-submit="submit()">
<input id="email" ng-model="password" required
type="text" placeholder="Your email">
<input id="password" ng-model="password" required
type="password" placeholder="Password">
</form>
JS:
$scope.submit = function() {
$scope.password = $('#password').val();
}
If you want to keep it simple just get the value using javascript
In your angular js controller :
var username = document.getElementById('username').value;
With AngularJS, I can use ng-pristine or ng-dirty to detect if the user has entered the field. However, I want to do client-side validation only after the user has left the field area. This is because when a user enters a field like e-mail or phone, they will always get an error thrown until they've completed typing out their full e-mail, and this is not an optimal user experience.
Example
UPDATE:
Angular now ships with a custom blur event:
https://docs.angularjs.org/api/ng/directive/ngBlur
From version 1.3.0 this can easily be done with $touched, which is true after the user has left the field.
<p ng-show="form.field.$touched && form.field.$invalid">Error</p>
https://docs.angularjs.org/api/ng/type/ngModel.NgModelController
Angular 1.3 now has ng-model-options, and you can set the option to { 'updateOn': 'blur'} for example, and you can even debounce, when the use is either typing too fast, or you want to save a few expensive DOM operations (like a model writing to multiple DOM places and you don't want a $digest cycle happening on every key down)
https://docs.angularjs.org/guide/forms#custom-triggers and https://docs.angularjs.org/guide/forms#non-immediate-debounced-model-updates
By default, any change to the content will trigger a model update and
form validation. You can override this behavior using the
ngModelOptions directive to bind only to specified list of events.
I.e. ng-model-options="{ updateOn: 'blur' }" will update and validate
only after the control loses focus. You can set several events using a
space delimited list. I.e. ng-model-options="{ updateOn: 'mousedown
blur' }"
And debounce
You can delay the model update/validation by using the debounce key
with the ngModelOptions directive. This delay will also apply to
parsers, validators and model flags like $dirty or $pristine.
I.e. ng-model-options="{ debounce: 500 }" will wait for half a second
since the last content change before triggering the model update and
form validation.
I solved this by expanding on what #jlmcdonald suggested. I created a directive that would automatically be applied to all input and select elements:
var blurFocusDirective = function () {
return {
restrict: 'E',
require: '?ngModel',
link: function (scope, elm, attr, ctrl) {
if (!ctrl) {
return;
}
elm.on('focus', function () {
elm.addClass('has-focus');
scope.$apply(function () {
ctrl.hasFocus = true;
});
});
elm.on('blur', function () {
elm.removeClass('has-focus');
elm.addClass('has-visited');
scope.$apply(function () {
ctrl.hasFocus = false;
ctrl.hasVisited = true;
});
});
elm.closest('form').on('submit', function () {
elm.addClass('has-visited');
scope.$apply(function () {
ctrl.hasFocus = false;
ctrl.hasVisited = true;
});
});
}
};
};
app.directive('input', blurFocusDirective);
app.directive('select', blurFocusDirective);
This will add has-focus and has-visited classes to various elements as the user focuses/visits the elements. You can then add these classes to your CSS rules to show validation errors:
input.has-visited.ng-invalid:not(.has-focus) {
background-color: #ffeeee;
}
This works well in that elements still get $invalid properties etc, but the CSS can be used to give the user a better experience.
I managed to do this with a pretty simple bit of CSS. This does require that the error messages be siblings of the input they relate to, and that they have a class of error.
:focus ~ .error {
display:none;
}
After meeting those two requirements, this will hide any error message related to a focused input field, something that I think angularjs should be doing anyway. Seems like an oversight.
This seems to be implemented as standard in newer versions of angular.
The classes ng-untouched and ng-touched are set respectively before and after the user has had focus on an validated element.
CSS
input.ng-touched.ng-invalid {
border-color: red;
}
Regarding #lambinator's solution... I was getting the following error in angular.js 1.2.4:
Error: [$rootScope:inprog] $digest already in progress
I'm not sure if I did something wrong or if this is a change in Angular, but removing the scope.$apply statements resolved the problem and the classes/states are still getting updated.
If you are also seeing this error, give the following a try:
var blurFocusDirective = function () {
return {
restrict: 'E',
require: '?ngModel',
link: function (scope, elm, attr, ctrl) {
if (!ctrl) {
return;
}
elm.on('focus', function () {
elm.addClass('has-focus');
ctrl.$hasFocus = true;
});
elm.on('blur', function () {
elm.removeClass('has-focus');
elm.addClass('has-visited');
ctrl.$hasFocus = false;
ctrl.$hasVisited = true;
});
elm.closest('form').on('submit', function () {
elm.addClass('has-visited');
scope.$apply(function () {
ctrl.hasFocus = false;
ctrl.hasVisited = true;
});
});
}
};
};
app.directive('input', blurFocusDirective);
app.directive('select', blurFocusDirective);
It might work for you to write a custom directive that wraps the javascript blur() method (and runs a validation function when triggered); there's an Angular issue that has a sample one (as well as a generic directive that can bind to other events not natively supported by Angular):
https://github.com/angular/angular.js/issues/1277
If you don't want to go that route, your other option would be to set up $watch on the field, again triggering validation when the field is filled out.
To pick up further on the given answers, you can simplify input tagging by using CSS3 pseudo-classes and only marking visited fields with a class to delay displaying validation errors until the user lost focus on the field:
(Example requires jQuery)
JavaScript
module = angular.module('app.directives', []);
module.directive('lateValidateForm', function () {
return {
restrict: 'AC',
link: function (scope, element, attrs) {
$inputs = element.find('input, select, textarea');
$inputs.on('blur', function () {
$(this).addClass('has-visited');
});
element.on('submit', function () {
$inputs.addClass('has-visited');
});
}
};
});
CSS
input.has-visited:not(:focus):required:invalid,
textarea.has-visited:not(:focus):required:invalid,
select.has-visited:not(:focus):required:invalid {
color: #b94a48;
border-color: #ee5f5b;
}
HTML
<form late-validate-form name="userForm">
<input type="email" name="email" required />
</form>
based on #nicolas answer.. Pure CSS should the trick, it will only show the error message on blur
<input type="email" id="input-email" required
placeholder="Email address" class="form-control" name="email"
ng-model="userData.email">
<p ng-show="form.email.$error.email" class="bg-danger">This is not a valid email.</p>
CSS
.ng-invalid:focus ~ .bg-danger {
display:none;
}
Here is an example using ng-messages (available in angular 1.3) and a custom directive.
Validation message is displayed on blur for the first time user leaves the input field, but when he corrects the value, validation message is removed immediately (not on blur anymore).
JavaScript
myApp.directive("validateOnBlur", [function() {
var ddo = {
restrict: "A",
require: "ngModel",
scope: {},
link: function(scope, element, attrs, modelCtrl) {
element.on('blur', function () {
modelCtrl.$showValidationMessage = modelCtrl.$dirty;
scope.$apply();
});
}
};
return ddo;
}]);
HTML
<form name="person">
<input type="text" ng-model="item.firstName" name="firstName"
ng-minlength="3" ng-maxlength="20" validate-on-blur required />
<div ng-show="person.firstName.$showValidationMessage" ng-messages="person.firstName.$error">
<span ng-message="required">name is required</span>
<span ng-message="minlength">name is too short</span>
<span ng-message="maxlength">name is too long</span>
</div>
</form>
PS. Don't forget to download and include ngMessages in your module:
var myApp = angular.module('myApp', ['ngMessages']);
ng-model-options in AngularJS 1.3 (beta as of this writing) is documented to support {updateOn: 'blur'}. For earlier versions, something like the following worked for me:
myApp.directive('myForm', function() {
return {
require: 'form',
link: function(scope, element, attrs, formController) {
scope.validate = function(name) {
formController[name].isInvalid
= formController[name].$invalid;
};
}
};
});
With a template like this:
<form name="myForm" novalidate="novalidate" data-my-form="">
<input type="email" name="eMail" required="required" ng-blur="validate('eMail')" />
<span ng-show="myForm.eMail.isInvalid">Please enter a valid e-mail address.</span>
<button type="submit">Submit Form</button>
</form>
Use field state $touched The field has been touched for this as shown in below example.
<div ng-show="formName.firstName.$touched && formName.firstName.$error.required">
You must enter a value
</div>
You can dynamically set the has-error css class (assuming you're using bootstrap) using ng-class and a property on the scope of the associated controller:
plunkr: http://plnkr.co/edit/HYDlaTNThZE02VqXrUCH?p=info
HTML:
<div ng-class="{'has-error': badEmailAddress}">
<input type="email" class="form-control" id="email" name="email"
ng-model="email"
ng-blur="emailBlurred(email.$valid)">
</div>
Controller:
$scope.badEmailAddress = false;
$scope.emailBlurred = function (isValid) {
$scope.badEmailAddress = !isValid;
};
If you use bootstrap 3 and lesscss you can enable on blur validation with the following less snippet:
:focus ~ .form-control-feedback.glyphicon-ok {
display:none;
}
:focus ~ .form-control-feedback.glyphicon-remove {
display:none;
}
.has-feedback > :focus {
& {
.form-control-focus();
}
}
outI used a directive. Here is the code:
app.directive('onBlurVal', function () {
return {
restrict: 'A',
link: function (scope, element, attrs, controller) {
element.on('focus', function () {
element.next().removeClass('has-visited');
element.next().addClass('has-focus');
});
element.on('blur', function () {
element.next().removeClass('has-focus');
element.next().addClass('has-visited');
});
}
}
})
All my input control has a span element as the next element, which is where my validation message is displayed and so the directive as an attribute is added to each input control.
I also have (optional).has-focus and has-visited css class in my css file which you see being referenced in the directive.
NOTE: remember to add 'on-blur-val' exactly this way to your input control without the apostrophes
By using ng-focus you can achieve your goal. you need to provide ng-focus in your input field. And while writing your ng-show derivatives you have to write a logic not equal too. Like the below code:
<input type="text" class="form-control" name="inputPhone" ng-model="demo.phoneNumber" required ng-focus>
<div ng-show="demoForm.inputPhone.$dirty && demoForm.inputPhone.$invalid && !demoForm.inputPhone.$focused"></div>
We can use onfocus and onblur functions. Would be simple and best.
<body ng-app="formExample">
<div ng-controller="ExampleController">
<form novalidate class="css-form">
Name: <input type="text" ng-model="user.name" ng-focus="onFocusName='focusOn'" ng-blur="onFocusName=''" ng-class="onFocusName" required /><br />
E-mail: <input type="email" ng-model="user.email" ng-focus="onFocusEmail='focusOn'" ng-blur="onFocusEmail=''" ng-class="onFocusEmail" required /><br />
</form>
</div>
<style type="text/css">
.css-form input.ng-invalid.ng-touched {
border: 1px solid #FF0000;
background:#FF0000;
}
.css-form input.focusOn.ng-invalid {
border: 1px solid #000000;
background:#FFFFFF;
}
</style>
Try here:
http://plnkr.co/edit/NKCmyru3knQiShFZ96tp?p=preview
I have Google Autocomplete set up for a text field of an HTML form, and it's working perfectly.
However, when the list of suggestions appear, and you use the arrows to scroll and select using enter, it submits the form, though there are still boxes to fill in. If you click to select a suggestion it works fine, but pressing enter submits.
How can I control this? How can I stop enter from submitting the form, and instead be the selection of a suggestion from autocomplete?
Thanks!
{S}
You can use preventDefault to stop the form being submitted when enter is hit, I used something like this:
var input = document.getElementById('inputId');
google.maps.event.addDomListener(input, 'keydown', function(event) {
if (event.keyCode === 13) {
event.preventDefault();
}
});
Using the Google events handling seems like the proper solution but it's not working for me. This jQuery solution is working for me:
$('#inputId').keydown(function (e) {
if (e.which == 13 && $('.pac-container:visible').length) return false;
});
.pac-container is the div that holds the Autocomplete matches. The idea is that when the matches are visible, the Enter key will just choose the active match. But when the matches are hidden (i.e. a place has been chosen) it will submit the form.
I've amalgamated the first two answers from #sren and #mmalone to produce this:
var input= document.getElementById('inputId');
google.maps.event.addDomListener(input, 'keydown', function(e) {
if (e.keyCode == 13 && $('.pac-container:visible').length) {
e.preventDefault();
}
});
works perfectly on the page. prevents the form from being submitted when the suggestion container (.pac-container) is visible. So now, an option from the autocomplete dropdown is selected when the users presses the enter key, and they have to press it again to submit the form.
My main reason for using this workaround is because I found that if the form is sent as soon as an option is selected, via the enter key, the latitude and longitude values were not being passed fast enough into their hidden form elements.
All credit to the original answers.
This one worked for me:
google.maps.event.addDomListener(input, 'keydown', e => {
// If it's Enter
if (e.keyCode === 13) {
// Select all Google's dropdown DOM nodes (can be multiple)
const googleDOMNodes = document.getElementsByClassName('pac-container');
// Check if any of them are visible (using ES6 here for conciseness)
const googleDOMNodeIsVisible = (
Array.from(googleDOMNodes).some(node => node.offsetParent !== null)
);
// If one is visible - preventDefault
if (googleDOMNodeIsVisible) e.preventDefault();
}
});
Can be easily converted from ES6 to any browser-compatible code.
The problem I had with #sren's answer was that it blocks the submit event always. I liked #mmalone's answer but it behaved randomly, as in sometimes when I hit ENTER to select the location, the handler ran after the container is hidden. So, here's what I ended up doing
var location_being_changed,
input = document.getElementById("js-my-input"),
autocomplete = new google.maps.places.Autocomplete(input),
onPlaceChange = function () {
location_being_changed = false;
};
google.maps.event.addListener( this.autocomplete,
'place_changed',
onPlaceChange );
google.maps.event.addDomListener(input, 'keydown', function (e) {
if (e.keyCode === 13) {
if (location_being_changed) {
e.preventDefault();
e.stopPropagation();
}
} else {
// means the user is probably typing
location_being_changed = true;
}
});
// Form Submit Handler
$('.js-my-form').on('submit', function (e) {
e.preventDefault();
$('.js-display').text("Yay form got submitted");
});
<p class="js-display"></p>
<form class="js-my-form">
<input type="text" id="js-my-input" />
<button type="submit">Submit</button>
</form>
<!-- External Libraries -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="//maps.googleapis.com/maps/api/js?sensor=false&libraries=places"></script>
The flag ensures that if the location is being changed & user hits enter, the event is blocked. Eventually the flag is set to false by google map's place_changed event, which then allows the form to be submitted on hitting the enter key.
Here's a simple code that worked well for me (uses no jquery).
const googleAutcompleteField = this.renderer.selectRootElement(this.elem.nativeElement);
this.selectOnEnter(googleAutcompleteField);
This piece of code, to follow the code above, is used to implement google maps autocomplete (with or without the Enter key functionality sought in this question):
this.autocomplete = new google.maps.places.Autocomplete(googleAutcompleteField, this.googleMapsOptions);
this.autocomplete.setFields(['address_component', 'formatted_address', 'geometry']);
this.autocomplete.addListener('place_changed', () => {
this.zone.run(() => {
this.googleMapsData.emit([this.autocomplete.getPlace()]);
})
})
selectOnEnter (called above in the first piece of code) defined:
selectOnEnter(inputField) {
inputField.addEventListener("keydown", (event) => {
const selectedItem = document.getElementsByClassName('pac-item-selected');
if (event.key == "Enter" && selectedItem.length != 0) {
event.preventDefault();
}
})
}
This code makes the google maps autocomplete field select whichever item user selects with the down arrow keypress. Once user selects an option with a press of the Enter key, nothing happens. User has to press Enter again to trigger onSubmit() or other command
You can do it in vanilla :
element.addEventListener('keydown', function(e) {
const gPlaceChoices = document.querySelector('.pac-container')
// No choices element ?
if (null === gPlaceChoices) {
return
}
// Get choices visivility
let visibility = window.getComputedStyle(gPlaceChoices).display
// In this case, enter key will do nothing
if ('none' !== visibility && e.keyCode === 13) {
e.preventDefault();
}
})
I tweaked Alex's code, because it broke in the browser. This works perfect for me:
google.maps.event.addDomListener(
document.getElementById('YOUR_ELEMENT_ID'),
'keydown',
function(e) {
// If it's Enter
if (e.keyCode === 13) {
// Select all Google's dropdown DOM nodes (can be multiple)
const googleDOMNodes = document.getElementsByClassName('pac-container');
//If multiple nodes, prevent form submit.
if (googleDOMNodes.length > 0){
e.preventDefault();
}
//Remove Google's drop down elements, so that future form submit requests work.
removeElementsByClass('pac-container');
}
}
);
function removeElementsByClass(className){
var elements = document.getElementsByClassName(className);
while(elements.length > 0){
elements[0].parentNode.removeChild(elements[0]);
}
}
I've tried the above short answers but they didn't work for me, and the long answers I didn't want to try them, so I've created the following code which worked pretty well for me. See Demo
Suppose this is your form:
<form action="" method="">
<input type="text" name="place" id="google-places-searchbox" placeholder="Enter place name"><br><br>
<input type="text" name="field-1" placeholder="Field 1"><br><br>
<input type="text" name="field-2" placeholder="Field 2"><br><br>
<button type="submit">Submit</button>
</form>
Then the following javascript code will solve the problem:
var placesSearchbox = $("#google-places-searchbox");
placesSearchbox.on("focus blur", function() {
$(this).closest("form").toggleClass('prevent_submit');
});
placesSearchbox.closest("form").on("submit", function(e) {
if (placesSearchbox.closest("form").hasClass('prevent_submit')) {
e.preventDefault();
return false;
}
});
And here is how the full code looks like in the HTML page (Note that you need to replace the YOUR_API_KEY with your google api key):
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Prevent form submission when choosing a place from google places autocomplete searchbox</title>
</head>
<body>
<form action="" method="">
<input type="text" name="place" id="google-places-searchbox" placeholder="Enter place name"><br><br>
<input type="text" name="field-1" placeholder="Field 1"><br><br>
<input type="text" name="field-2" placeholder="Field 2"><br><br>
<button type="submit">Submit</button>
</form>
<!-- jQuery -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<!-- Google Maps -->
<!-- Note that you need to replace the next YOUR_API_KEY with your api key -->
<script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=places"
async defer></script>
<script>
var input = document.getElementById("google-places-searchbox");
var searchBox = new google.maps.places.SearchBox(input);
var placesSearchbox = $("#google-places-searchbox");
placesSearchbox.on("focus blur", function() {
$(this).closest("form").toggleClass('prevent_submit');
});
placesSearchbox.closest("form").on("submit", function(e) {
if (placesSearchbox.closest("form").hasClass('prevent_submit')) {
e.preventDefault();
return false;
}
});
</script>
</body>
</html>
$("#myinput").on("keydown", function(e) {
if (e.which == 13) {
if($(".pac-item").length>0)
{
$(".pac-item-selected").trigger("click");
}
}
Use $('.pac-item:first').trigger('click'); if you want to select first result