AngularJs Bootstrap Datepicker Validation - angularjs

I am using the Bootstrap Datepicker (found here: http://mgcrea.github.io/angular-strap/#/datepicker) on an input field. By default, the datepicker allows a date to be entered. If it is valid, then it sets the date. Otherwise, it sets the date to the current date.
I wrote a directive that uses a regex to only characters for a date to be input.:
maskModelCtrl.$parsers.push(function(inputValue) {
var val = maskRenderLogic(inputValue, mask, maxVal);
setAndRender(maskModelCtrl, val);
return maskReturnLogic(val);
});
And I wrote a directive to standardize the input field for the datepicker with a template:
angular.module('form.validation').directive('dateMask', ['$parse', function($parse) {
return {
restrict: 'E',
scope: {
date: '='
},
template: '<input ng-model="date" regex-mask="[^0-9 /]" max-length="10" bs-datepicker data-date-format="mm/dd/yyyy" placeholder="mm/dd/yyyy" >',
replace: true
};
}]);
The problem is, the datepicker translates any keyboard input or date selections to the current date. (See plnkr: http://plnkr.co/edit/oCZnq0UOmaxC83Rv7YSs?p=preview) I don't think that these directives should be incompatible with each other.

I realize you've probably already considered this, but I'm writing an application that deals a lot with dates (inputs, modifications, etc) and I tested a lot of different date pickers.
The Angular-UI Bootstrap datepicker seems to be the most malleable and usable.
That being said, your directive should leverage $formatters and $parsers:
Markup:
<input date-validator type="text" ng-model="date"/>
Directive
app.directive('dateValidator', function() {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, elem, attrs, ctrl) {
var validate = function(value) {
//.. do validation logic ../
modifiedValue = value / 2;
// set the validity if needed
ctrl.$setValidity('customDate', false);
//return the modified value
return modifiedValue;
}
// formatters fire when the model directly changes
ctrl.$formatters.unshift(function(value) {
return validate(value);
});
// parsers fire when the model changes via input
ctrl.$parser.unshift(function(value) {
return validate(value);
});
}
}
});

Related

Value in ngModel not updated in Angular input view

I need to format the input values so I create a directive that use a template with require: 'ngModel' because I have to use ngModelController functions ($parsers, $formatters, etc.).
This is my HTML:
<div ng-model="myInputValue" amount-input-currency=""></div>
{{myInputValue}}
This is my directive:
.directive('amountInputCurrency', [function(){
return {
templateUrl: '../amountInputCurrency.tmpl.html',
require: 'ngModel',
restrict: 'A',
link: function(scope, elem, attrs, model) {
// ...
}
}
}
And this is my template:
<input type="text" ng-model="myInputValue">
The problem is that I can't updated the view after formatting the inserted value. For example if I write '1' I want change the value in this way:
model.$formatters.push(function(value) {
return value + '00';
}
Alternative I try to set an event in this other way:
<input type="text" ng-model="myInputValue" ng-blur="onBlur()">
scope.onBlur = function() {
model.$viewValue = model.$viewValue + '00';
// or model.$setViewValue(model.$viewValue + '00';);
model.$render();
};
The model.$viewValue changes, myInputValue (in the HTML with {{myInputValue}}) changes but not the value showed in the input box... which is the problem? Thanks!
----------------UPDATE----------------
Probably the problem is because I have 2 ng-model (one in the HTML and one in the template): https://github.com/angular/angular.js/issues/9296
How can I do? Both model refer to the same model...
Formatters change how model values will appear in the view.
Parsers change how view values will be saved in the model.
//format text going to user (model to view)
ngModel.$formatters.push(function(value) {
return value.toUpperCase();
});
//format text from the user (view to model)
ngModel.$parsers.push(function(value) {
return value.toLowerCase();
});
Try using $parsers to change the view to your desired value.
I hope this will help you.
Update:
angular.module('components', []).directive('formatParse', function() {
return {
restrict: 'E',
require: 'ngModel',
scope: { model: "=ngModel" },
template: '<input type="text" data-ng-model="model"></input><button type="button" data-ng-click="clickedView()">SetView</button><button type"button" data-ng-click="clickedModel()">SetModel</button>',
link: function ($scope, el, attrs, ngModelCtrl) {
format = "MMM Do, YYYY H:mm";
console.log($scope.model);
console.log(ngModelCtrl);
$scope.clickedView = function () {
ngModelCtrl.$setViewValue(ngModelCtrl.$viewValue);
};
$scope.clickedModel = function () {
$scope.model = 12; // Put here whatever you want
};
ngModelCtrl.$parsers.push(function (date) {
console.log("Parsing", date)
return date; // Put here the value you want to be in $scope.model
});
ngModelCtrl.$formatters.push(function (date) {
console.log("Formatting", date);
console.log($scope.model);
console.log(ngModelCtrl);
return +date * 2; // Put here what you want to be displayed when clicking setView (This will be in ngModelCtrl.$viewValue)
});
}
}
});
angular.module('someapp', ['components']);
Try using this code and tell if this helped to get the result you wanted.
If it does I suggest, to console.log the ngModelCtrl that you way you will understand more about the inner flow of angular.
In addition, just so you have some more information,
When you edit the input in the view the formatters function are fired to change the model accordingly.
If the value that has been entered is not valid you can return in your formatters function the ngModelCtrl.$viewValue to keep $scope.model with his old and true information.
When you change your scope variable (in your case $scope.model) the parsers functions will be fired to change the view value. (You don't need to use $render, you just need to decide when you want to change your $scope.model),
I suggest instead of using $setViewValue put the value you want in your scope variable and the parsers will act accordingly.

AngularJS Custom input control not performing validation

I created a custom input control and trying to perform certain validation on blur.
But its not performing as expected. I want to use template like below instead of using jquery specific element.bind('blur')
template: '<input type="text" ng-blur="performvalidation()">',
Complete fiddle here
Please guide or correct what am I doing wrong. Thanks.
If you want to create custom validators you should add them to the ngModelController's $validators field. e.g.
angular.module('app').directive('strongSecret', function() {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attr, ctrl) {
ctrl.$validators.uppercaseValidator = function(value) {
return /[A-Z]/.test(value);
}
ctrl.$validators.numberValidator = function(value) {
return /[0-9]/.test(value);
}
ctrl.$validators.sixCharactersValidator = function(value) {
return value.length === 6;
}
}
};
});
Also instead of giving your directive a template you should just use it on an input element
<input ng-model="strongSecret" strong-secret name="strongSecret"/>
if you don't want to show the errors until the user clicks away from the input field you could do this
<ul ng-if="sampleForm.strongSecret.$touched" class="error-msgs" ng-messages="sampleForm.strongSecret.$error">
...
</ul>
Working jsFiddle: https://jsfiddle.net/e81kee9z/2/

Angular - Cannot get custom directive to cooperate with ng-change

I have a very simple directive (named cooperate-with-ng-change) which requires ng-model and I'd like it to cooperate with ng-change.
Restated, I'd like to be able to do <cooperate-with-ng-change ng-model="model" ng-change="changeHandler()"></cooperate-with-ng-change>
However I'm noticing when changeHandler() is fired, model is the old value and not the new one.
Here's the directive definition object:
return {
restrict: "E",
require: ["ngModel"],
scope: {
"value": "=ngModel"
},
template: "<label>Cooperate with NgChange: <input type='text' ng-model='value' ng-model-options='{debounce: 500}' /></label>",
link: function(scope, element, attrs, ctrls) {
var inputNgModelCtrl = element.find("input").controller("ngModel");
var parentNgModelCtrl = ctrls[0];
Array.prototype.push.apply(inputNgModelCtrl.$viewChangeListeners, parentNgModelCtrl.$viewChangeListeners)
//if I wrap all the parentNgModelCtrl.$viewChangeListeners in a timeout and digest in changeHandler
//then message matches afterDebounce
/*Array.prototype.push.apply(inputNgModelCtrl.$viewChangeListeners,
parentNgModelCtrl.$viewChangeListeners.map(
function(listener) {
return function() {
setTimeout(listener, 1)
}
}))
*/
}
}
Here's a plunker link if you want to play with it
My guess is $viewChangeListeners gets called before the model value is set, however I can't find what might correspond for after the model value is set.

In AngularJS, how to force the re-validation of a field in a form when another value in the same form is changed?

I have a form with few fields, however a select and an input field are coupled: the validation on the input depends on which value the user chooses in the select field.
I'll try to clarify with an example. Let's say that the select contains names of planets:
<select id="planet" class="form-control" name="planet" ng-model="planet" ng-options="c.val as c.label for c in planets"></select>
in the input I apply custom validation via a custom directive named "input-validation":
<input id="city" input-validation iv-allow-if="planet==='earth'" class="form-control" name="city" ng-model="city" required>
where this is the directive:
.directive('inputValidation', [function() {
return {
require: 'ngModel',
restrict: 'A',
scope: {
ivAllowIf: '='
},
link: function(scope, elm, attrs, ctrl) {
ctrl.$parsers.unshift(function(viewValue) {
//input is allowed if the attribute is not present or the expression evaluates to true
var inputAllowed = attrs.ivAllowIf === undefined || scope.$parent.$eval(attrs.ivAllowIf);
if (inputAllowed) {
ctrl.$setValidity('iv', true);
return viewValue;
} else {
ctrl.$setValidity('iv', false);
return undefined;
}
});
}
};
}])
The full example can be examined in Plnkr: http://plnkr.co/edit/t2xMPy1ehVFA5KNEDfrf?p=preview
Whenever the select is modified, I need the input to be verified again. This is not happening in my code. What am I doing wrong?
I have done the same thing for validation of start-date on change of end-date. In the directive of start-date add watch for change of end-date and then call ngModel.$validate() in case end-date new value is defined.
scope.$watch(function () {
return $parse(attrs.endDate)(scope);
}, function () {
ngModel.$validate();
});
The important part to take is call to ngModel.$validate() inside the directive.
Note
you should use $validators for custom validations above to work. read here, $parsers is the old way - from angularjs 1.3 use $validators
FIXED PLUNKER LINK

using bootstrap-datepicker with angularjs. Need to find a way to update ng-model when a date is chosen

In short, I need to find a way to update ng-model when using bootstrap-datepicker. Here is a plunker I made to demonstrate what is going on http://plnkr.co/edit/nNTEM25I2xX2zRKOWbD1?p=preview. I've tried searching around and am fairly positive that I need to use a directive to pass a value to the model. Typing something in the text box will update the selected date model, but just using the datepicker does nothing. The below directive seemed like it should work but unfortunately it doesn't seem to have much of an effect.
app.directive('datepicker', function() {
return {
restrict : 'A',
require : 'ngModel',
link : function(scope, element, attrs, ngModelCtrl) {
$(function() {
element.datepicker({
dateFormat : 'dd/mm/yy',
onSelect : function(date) {
ngModelCtrl.$setViewValue(date);
element.datepicker("setDate", date);
scope.$apply();
}
});
});
}
}
});
An easy solution would be to just use another datepicker, but unfortunately due to restrictions on how many external libraries I can use this is the datepicker I have to use. Any insight would be greatly appreciated!!!
I strongly recommend using UI-Bootstrap or something similar.
But for those that need to use Bootstraps date-picker for whatever reason here is a starting place using your directive, with a few changes:
app.directive('datepicker', function() {
return {
restrict: 'A',
require: 'ngModel',
compile: function() {
return {
pre: function(scope, element, attrs, ngModelCtrl) {
// Initialize the date-picker
$(element).datepicker({
format: 'dd/mm/yyyy'
}).on('changeDate', function(ev) {
// Binds the changes back to the controller
// I also found that event.format() returns the string
// I wasn't aware of that. Sure beats using the date object and formatting yourself.
ngModelCtrl.$setViewValue(ev.format('dd/mm/yyyy'));
// I HATE using $apply, but I couldn't get it to work without
scope.$apply();
});
}
}
}
}
});
HTML:
<input type="text" datepicker="" ng-model="date" />
Very simple and straightforward and allows you to reuse, here is a working plunker

Resources