I am using a 'required' and 'email' validators on the 'email' input field. The validators make use of parsers and formatters and they work fine. However, I also have some validation on 'bind' event.
Here is the directive:
angular.module('simpleRepairApp')
.directive('uniqueEmail', function (Checker) {
return {
require:'ngModel',
restrict:'A',
link:function ($scope, element, attrs, model) {
var last = '';
var current = '';
element.bind('blur', function() {
current = element.val();
console.log(current, last);
if(current !== last){
Checker.email({ email:current }).then(
function(response) {
model.$setValidity('uniqueEmail', response.available);
}
);
}
last = current;
});
}
};
});
I need to check if the email already exists in the database or not after user clicks out of the field (I do not want to check upon every key press or change).
The problem I am having is, after the unique validation is performed, it shows the unique error message, but after you type to correct the email, the model value stays undefined. You have to click out of the input, then the value in the model in defined again.
Anyone can help me solve this, it is driving me nuts!:)
WORKING SOLUTION:
angular.module('simpleRepairApp')
.directive('emailUnique', function () {
return {
restrict: 'E',
require: ['form', 'ngModel'],
scope: {
'form': '=form',
'model': '=ngModel',
'labelClass': '#',
'inputClass': '#'
},
compile: function(element, attrs)
{
if (!attrs.labelClass) { attrs.labelClass = 'col-sm-4'; }
if (!attrs.inputClass) { attrs.inputClass = 'col-sm-8'; }
attrs.required = attrs.required == 'true';
},
controller: function($scope, Checker) {
$scope.checkEmail = function() {
var email = $scope.form.email.$viewValue;
var checkField = $scope.form.emailCheck;
Checker.email({ email:email }).then(
function(response) {
checkField.$setValidity('unique', response.available);
$scope.form.$setValidity('check', true);
checkField.hasVisit = true;
}
);
};
$scope.setUnchecked = function() {
console.log($scope.form);
$scope.form.emailCheck.hasVisited = false;
$scope.form.$setValidity('check', false);
$scope.form.emailCheck.$setValidity('unique', true);
};
},
template: '<div ng-class="{ \'has-error\' : !form.email.$valid || !form.emailCheck.$valid }">' +
'<label class="{{ labelClass }} control-label required" for="email" translate>E-mail</label>' +
'<div class="{{ inputClass }}">' +
'<input name="email" class="form-control" type="email" id="email" ng-model="model" ng-change="setUnchecked()" ng-blur="checkEmail()" ng-required="true" autocomplete="off">' +
'<div class="help-block" ng-show="(!form.email.$valid || !form.emailCheck.$valid) && !form.email.$pristine">' +
'<div ng-messages="form.email.$error">' +
'<div ng-message="required"><span translate>Please enter your e-mail.</span></div>' +
'<div ng-message="email"><span translate>Please enter a valid e-mail.</span></div>' +
'</div> ' +
'<div ng-messages="form.emailCheck.$error">' +
'<div ng-message="check"><span translate>E-mail will be checked upon blur.</span></div>' +
'<div ng-message="unique"><span translate>This e-mail is already in use.</span></div>' +
'</div> ' +
'</div>' +
'<input name="emailCheck" type="hidden" class="form-control" ng-model="checked">' +
'<div class="help-block" ng-messages="form.emailCheck.$error" ng-show="!form.emailCheck.$valid">' +
'</div>' +
'</div>' +
'</div>'
};
});
I think you might be complicating things a bit by making a directive, why don't you simply add a watcher to your controller?
Note: I'm not familiar if any of these methods will work for you, but I'm adding them as an illustrative purpose of not binding to the "blur" event, but rather have the events get triggered when the MODEL changes. Which is what angular is designed to do. I'm also making an assumption that your Checker is handling the promises correctly
First alternative method (using $watch)
<form name="myForm" ng-controller="ExampleController">
<input type="email" name="input" ng-model="text" required />
</form>
Then in your ng-app controller:
function isNullOrUndefined(value) {
return (value == 'undefined' || value == null);
}
//listener on the ng-model='text', waiting for the data to get dirty
$scope.$watch('text', function (newValue, oldValue) {
if (newValue != oldValue)
{
var truthyVal = !(isNullOrUndefined(newValue));
if (truthyVal) {
//angular's way of checking validity as well
//$scope.myForm.text.$valid
Checker.email({ email:current }).then(
function(response) {
model.$setValidity('textErr', response.available);
}
}
}
}, true);
Second alternative method (using ng-change):
<form name="myForm" ng-controller="ExampleController">
<input type="email" name="input" ng-model="text" ng-change="checkEmail()" required />
</form>
$scope.checkEmail = function(){
Checker.email({ email:current }).then(
function(response) {
model.$setValidity('textErr', response.available);
}
}
Because you are binding on the blur event, the validation won't work the way you want. You will have to click out of the field before that function can run. If you indeed want it to validate when they finish typing the email, then you will have to bind to some sort of key event.
My suggestion is to bind to keyup, and only do the server-side check when the email address appears to be of valid syntax.
Related
I tried using jquery validate but i've spent more than 4 hours to search how to solve my problem but couldn't find it. The problem is when I tried using jquery validate for filesize in multidimensional array, it doesn't work. It can still submit the form and not showing the error message.
Here is it my field
var numberIncr = 1;
$('#add-berkas').click(function () {
var box_html = $('<div class="text-box form-group row">\n' +
' <div class="col-sm-8">\n' +
' <input type="text" name="berkas['+numberIncr+'][nama]" placeholder="Nama File" class="form-control" required>\n' +
' </div>\n' +
' <div class="col-sm-4">\n' +
' <input type="file" name="berkas['+numberIncr+'][file]" id="berkasfile'+numberIncr+'" accept="application/pdf" required/>\n' +
' <button id="remove-berkas" class="btn btn-sm btn-danger remove-box" type="button"><i class="fa fa-trash"></i></button>\n' +
' </div>\n' +
' </div>');
$('.text-box:last').after(box_html);
box_html.fadeIn('slow');
numberIncr++;
});
And this is the validate
$.validator.addMethod('filesize', function (value, element, param) {
return this.optional(element) || (element.files[0].size <= param)
}, 'File size must be less than {0}');
var berkas = $('input[name^="berkas"]');
berkas.filter('input[name$="[file]"]').each(function() {
$(this).rules("add", {
extension: "pdf", filesize:1048576,
messages: "Berkas must be PDF and less than 1MB"
});
});
$("#form").validate({
rules: {
surat: {extension: "pdf", filesize: 1048576, },
},
messages: {
surat: "Surat must be PDF and less than 1MB",
},
errorPlacement: function(error,element){
showErrorMessage(error.text());
},
submitHandler: function(form) {
form.submit();
},
highlight: function(element, errorClass) {
return false;
}
});
Your problem is caused by presumably only calling this code once on page load, when the fields don't yet exist...
berkas.filter('input[name$="[file]"]').each(function() {
$(this).rules("add", {
extension: "pdf", filesize:1048576,
messages: "Berkas must be PDF and less than 1MB"
});
});
There are no matching fields at the time you call this code. The whole point of this method is for you to dynamically add the rules after you create each field.
You must call it immediately after adding a new field. Put it inside the click handler near the bottom.
var numberIncr = 1;
$('#add-berkas').click(function () {
var box_html = $('<div class="text-box form-group row">\n' +
.....
' </div>');
$('.text-box:last').after(box_html);
box_html.fadeIn('slow');
// add rules to new field here
$('[name="berkas[' + numberIncr + '][file]"]').rules("add", {
extension: "pdf", filesize:1048576,
messages: "Berkas must be PDF and less than 1MB"
});
numberIncr++;
});
You wouldn't need an .each() since you only create one field on each click. Just target the new field and add the rule.
I have a text box in my Application, i am using change-on-blur custom directive to validate that field on tab out that is, if i type ABCD initially in that text box then i delete D character and again i type D character so custom change-on-blur will call only one time.
For some issues i am using ng-model-options="{updateOn: 'blur'}" ng-change="validate(data)" instead ng-blur for that element. So if i type ABCD it will call the function, again i delete and type D character it will call that function again. Is there custom ng-change directive to call only once.
I am struggling with this for last 2 days. Kindly help
Html :
<input type="text" ng-model="data.taxNo" id="taxNo"
ngomitsplchar class="form-control taxNo"
maxlength="15" style="text-transform: uppercase;"
ng-paste="$event.preventDefault();"
ng-model-options="{updateOn: 'blur'}"
ng-change="validate(data)"
tabindex="5" spellcheck="false" autocomplete="nope">
JS:
$scope.validate = function (obj) {
alert("2"); // Prompting alert 2 times when using ng-change
// Prompting alert 1 times when using change-on-blur
}
app.directive('changeOnBlur', function($timeout) {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, elm, attrs, ngModelCtrl) {
if (attrs.type === 'radio' || attrs.type === 'checkbox')
return;
var expressionToCall = attrs.changeOnBlur;
var oldValue = null;
elm.bind('focus',function() {
//scope.$apply(function() {
oldValue = elm.val();
//});
});
elm.bind('blur', function() {
$timeout(function() {
var newValue = elm.val();
if (newValue !== oldValue){
scope.$eval(expressionToCall);
}
//alert('changed ' + oldValue);
});
});
}
};
});
I have found the solution. Need to change from ng-model-options="{updateOn: 'blur'}" to ng-model-options="{updateOn: '**change blur**'}". So its restricted duplicate call when changing same value in text box by using ng-change="validate(data)"
<input type="text" ng-model="data.taxNo" id="taxNo"
ngomitsplchar class="form-control taxNo"
maxlength="15" style="text-transform: uppercase;"
ng-paste="$event.preventDefault();"
ng-model-options="{updateOn: 'change blur'}"
ng-change="validate(data)"
tabindex="5" spellcheck="false" autocomplete="nope">
I have this form:
business-validation is a custom directive whose code is:
var CREDIT_CARD_REGEX = /^\d{0,24}$/;
angular.module('directives').directive('creditCard', [
function () {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, ctrl) {
scope.$watch(attrs.creditCard, function (newValue) {
return ctrl.$setViewValue(ctrl.$viewValue);
});
return ctrl.$parsers.push(function (viewValue) {
var newValue;
newValue = ctrl.$modelValue;
element.validateCreditCard(function (result) {
if (result.card_type &&
result.luhn_valid &&
result.length_valid &&
CREDIT_CARD_REGEX.test(element.val())) {
element.attr("data-card-type", result.card_type.name);
ctrl.$setValidity('creditCard', true);
newValue = viewValue;
}
else {
element.removeAttr("data-card-type");
ctrl.$setValidity('creditCard', false);
}
}, { accept: ['visa', 'mastercard'] });
return newValue;
});
}
};
}]);
I need the value of myForm.anInputName.$error.creditCard at the controller and for that purpose I've made this attempts and also something like:
<input type="hidden" ng-model="IsCreditCardValid" name="IsCreditCardValid" value="myForm.anInputName.$error.creditCard" />
and
$scope.$watch("IsCreditCardValid", function (newValue, oldValue) {
alert('theChangeHasBeen:' + oldValue + ' -> ' + newValue);
});
In order to $watch IsCreditCardValid at the controller.
This paragraph is shown:
<p class="help-block" ng-if="smyForm.anInputName.$error.creditCard && !myForm.anInputName.$error.required">Wrong credit card number!</p>
but the hidden input never gets the expected value although using the same condition. Why this last hidden field doesn't get updated and the $watch in never triggered?
EDIT
If I do
myForm.anInputName.$valid: {{myForm.anInputName.$valid}}
The value is updated on screen, but the hidden field doesn't change its value.
ngModel works only on listed input types, and hidden isn't among them. The internals of input directive make a hint that it won't work automatically on every possible input:
var inputType = {
...
'hidden': noop,
'button': noop,
'submit': noop,
'reset': noop,
'file': noop
}
This makes sense because hidden inputs don't need two-way binding (they are unnecessary in Angular app, too). If you want to use them anyway, then
<input type="hidden" name="IsCreditCardValid" value="{{ IsCreditCardValid }}" />
Trying to find the best practices with AngularJS. Here's the deal:
There are two different pages with forms where each of them have their own form fields. But there is a one common functionality on both of the forms: they have an autocomplete field which the user can use to select multiple email addresses that exist in the system.
The selected email addresses are stored to the model/scope so that they can be shown on the HTML page. Here's an example:
<div ng-controller="MyCtrl">
<form ng-submit="selectCurrentEmail()" novalidate>
<input type="text"
class="form-control"
ng-model="responsiblePerson" />
<input type="submit" value="Add" />
<div ng-repeat="email in formData.selectedEmails">
<div>
{{email}} x
</div>
</div>
</form>
</div>
and the angularjs part:
var myApp = angular.module('myApp', []);
function MyCtrl($scope) {
$scope.formData = {selectedEmails: []};
$scope.selectEmail = function(email) {
if (email != null && $.inArray(email, $scope.formData.selectedEmails) == -1) {
$scope.formData.selectedEmails.push(email);
$scope.responsiblePerson = null;
}
};
$scope.removeEmail = function(email) {
var index = $.inArray(email, $scope.formData.selectedEmails);
if (index != -1) {
$scope.formData.selectedEmails.splice(index, 1);
}
};
$scope.selectCurrentEmail = function() {
$scope.selectEmail($scope.responsiblePerson);
};
}
http://jsfiddle.net/PqpYj/
(doesn't contain the autocomplete since it's not the main issue here..)
This all works fine, but I don't want to repeat the same logic in both of the controllers. What I would like to have is a service or a base controller that can take care of setting and removing the selected email addresses. And when the user is done, the scope would have just the selected email addresses.
So, do you think there's a good way to generalize the three functions in the scope? Any ideas making this better?
Because this is a UI element, I would put the logic into a Directive.
myApp.directive('mailSelector', function() {
return {
scope: {
emails: '='
},
template: '<form ng-submit="selectCurrentEmail()" novalidate>' +
'<input type="text"'+
' class="form-control"'+
' ng-model="responsiblePerson" />'+
'<input type="submit" value="Add" ng-click="selectCurrentEmail()" />'+
'<div ng-repeat="email in emails">'+
' <div>' +
' {{email}} x' +
' </div>' +
'</div>' +
'</form>',
link: function($scope, $element, $attrs) {
$scope.selectCurrentEmail = function() {
$scope.selectEmail($scope.responsiblePerson);
}
$scope.selectEmail = function(email) {
if (email != null && $.inArray(email, $scope.emails) == -1) {
$scope.emails.push(email);
$scope.responsiblePerson = null;
}
}
$scope.removeEmail = function(email) {
var index = $.inArray(email, $scope.emails);
if (index != -1) {
$scope.emails.splice(index, 1);
}
};
}
};
});
Controllers can retrieve a list of emails from Directive via emails parameter defined with the directive.
I have created a JSFiddle here.
To share previously input email addresses for auto-completion, I would modify the directive to use a Service, which holds the list of previously input email addresses.
So, instead of having a form in the HTML, I decided to create the form on fly and append it to another element (in my case a <section>, but that will be an option in the near future).
I'm using this method:
var formWrapper = ['<div class="content-login">','</div>'];
var form = [
'<form name="login-form" class="login-form" method="post" action="#">',
'<fieldset class="login-fields">',
'<fieldset class="username-wrapper">',
'<label for="username" class="user-img"><img src="assets/gfx/user.png" alt="Username" /></label>',
'<input type="text" name="username" class="username" placeholder="Username" value="" autocomplete="off" />',
'</fieldset>',
'<fieldset class="password-wrapper">',
'<label for="password" class="pass-img"><img src="assets/gfx/password.png" alt="Password" /></label>',
'<input type="password" name="password" class="password" placeholder="Password" value="" autocomplete="off" />',
'</fieldset>',
'<fieldset class="login-wrapper">',
'<button type="submit" name="login" class="login">Login</button>',
'</fieldset>',
'</fieldset>',
'</form>'
];
setTimeout(function () {
$(formWrapper.join('')).appendTo('section').hide();
$(form.join('')).appendTo('.content-login');
$('.content-login').fadeIn('slow');
}, 1500);
This way I have a nice fade in effect and it will give me the opportunity to change whatever I want after I fully develop it.
But my question is in fact the following: I have a form, so of course I will use Ajax to submit it, and I already have the script for that. The thing is now, when I click on the button, the .click event does not occur, it only takes me to the default action of the form which is "#" in my case. Why is that ?
Here is the other part of the script, for a better understanding :
$('.login-form .login').click(function(){
if($('input.username').val() == "" || $('input.password').val() == "")
{
console.log('Please enter Username & Password');
$('.login-form').effect("shake", { distance: 40, times: 2 }, 100);
return false;
}
else {
$('.login-fields').fadeOut();
$('.login-form').spin("login", "#ffffff");
$.ajax
({
type: 'POST',
url: 'assets/class/login/process.php',
dataType: 'json',
data:
{
username: $('input.username').val(),
password: $('input.password').val()
},
success:function(data)
{
if(!(data.lockdown == true)) {
if(data.error === true) {
console.log(data.message);
$('.login-form').spin(false);
$('.login-fields').fadeIn();
$('.login-form').effect("shake", { distance: 40, times: 2 }, 100);
}
else {
console.log(data.message);
$('.login-form').spin(false);
$('.login-fields').fadeIn();
$('.content-login').fadeOut();
var structure = [
'<div class="after-login">',
'<div class="inside">',
'<div class="row-one">',
'<h1>',data.message,'</h1>',
'</div>',
'<div class="row-two">',
'<a class="cancel" href="',links.cancel,'?logout">Cancel</a>',
'<a class="continue" href="',links.proceed,'">Continue</a>',
'</div>',
'</div>',
'</div>'
];
setTimeout(function () {
$(structure.join('')).appendTo('section').fadeIn('slow');
}, 1500);
}
}
else {
console.log(data.message);
$('.login-form').spin(false);
$('.content-login').fadeOut();
var structure = [
'<div class="system-lockdown">',
'<div class="inside">',
'<div class="row-one">',
'<h1>',data.message,'</h1>',
'</div>',
'<div class="row-two">',
'<a class="back" href="',links.goback,'">Back</a>',
'</div>',
'</div>',
'</div>'
];
setTimeout(function () {
$(structure.join('')).appendTo('section').fadeIn('slow');
}, 1500);
}
},
error:function(XMLHttpRequest,textStatus,errorThrown)
{
console.log('A PHP error triggered this, check your PHP log file for more information');
}
});
return false;
}
});
$.click() will only work on elements that have been created before the handler was created.
Instead, use $.live() instead:
$('.login-form .login').live('click', function() {
// Your code
});
If you're using jQuery 1.7 or above, you can also use $.on() in a similar way:
$('.login-form .login').on('click', function() {
// Your code
});
The preferred way to handle events with dynamically added content is with on()—or delegate, since you're on jQuery 1.6
$(document).delegate('.login-form .login', 'click', function(){
});
Note that this will listen to every single click anywhere in your document. Ideally you'd like to identify some more narrow container from which all clicks will come, and listen to that. So if all these clicks will be coming from your section, you'd do this
$("section").delegate('.login-form .login', 'click', function(){
});