Integrate jquery masked input as angularjs directive - angularjs

I wish to use masking in form inputs. I have created a directive uiMask which takes predefined masking formats like DoB or zip. In order to initiate masking, I apply the masking in directive's link function. And to update the model I manually trigger the digest cycle using $apply on keyup. Is this approach correct?
angular.module('formApp', [])
.controller("DemoFormController",['$scope',function($scope){
}])
.directive('uiMask', [
function () {
return {
require:'ngModel',
scope: {
type : "#uiMask"
},
controller: function($scope){
$scope.dob = "99/99/9999";
$scope.zip = "99999";
},
link:function ($scope, element, attrs, controller) {
var $element = $(element[0]);
$element.mask($scope.$eval($scope.type));
/* Add a parser that extracts the masked value into the model but only if the mask is valid
*/
controller.$parsers.push(function (value) {
var isValid = value.length && value.indexOf("_") == -1;
return isValid ? value : undefined;
});
/* When keyup, update the view value
*/
element.bind('keyup', function () {
$scope.$apply(function () {
controller.$setViewValue(element.val());
});
});
}
};
}
]);
<!doctype html>
<html ng-app="formApp">
<head>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.26/angular.min.js"></script>
<script src="http://code.jquery.com/jquery-1.10.2.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery.maskedinput/1.3.1/jquery.maskedinput.js"></script>
</head>
<body>
<div ng-controller="DemoFormController" class="container">
{{employee | json}}
<input type="text" class="form-control" id="dob" name="dob" placeholder="Date of Birth" ui-mask="dob" ng-model="employee.dob">
<input type="text" class="form-control" id="zip" placeholder="Zip" ui-mask="zip" ng-model="employee.zip" >
</div>
</body>
</html>

I did something similar with putting a jQuery "plugin" (uniform.js) into an angular directive. I think what might help is if you attached a $watch to the element. Here is what I did for the uniform directive:
(function () {
'use strict';
angular.module('pfmApp').directive('uniform', function($timeout) {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attrs, ngModel) {
$(element).addClass('uniform').uniform();
//$(element).uniform.update();
scope.$watch(function() { return ngModel.$modelValue }, function() {
$timeout(jQuery.uniform.update, 0);
});
}
}
});
}());
I hope this helps or perhaps points you in the right direction.

Related

Angular directive compile: "RangeError: Maximum call stack size exceeded"

I want to add an 'ng-pattern' directive to an input element through a custom directive. I don't want to do it in the templates directly, but it looks I'm getting in a infinite loop.
I tried to set first the 'html' and compile the element after (Angular compile in directive seems to go into infinite loop) but scope is undefined. I don't know if it's related with replacing element's content.
Should i create a new scope? Do I'm missing something?
Thanks in advance!
var myHtml = iElem[0].outerHTML;
iElem.replaceWith(myHtml);
var compiledElement = $compile(iElem)(iElem.scope());
HTML:
<input type="text" ng-model="personal.testNumber_string" my-model="personal.testNumber" dot-to-comma>
Directive:
function dotToCommaConverter($compile) {
return {
require: 'ngModel',
restrict: 'A',
scope: {
myModel: '='
},
controllerAs: 'dot2Comma',
controller: function($scope) {
this.myModel = $scope.myModel;
},
compile: function(tElem, tAttrs) {
return {
pre: function(scope, iElem, iAttrs) {
},
post: function(scope, iElem, iAttrs, modelCtrl) {
iElem.attr('ng-pattern', '/^-?[0-9]+(?:\,[0-9]+)?$/');
var compiledElement = $compile(iElem)(iElem.scope());
iElem.replaceWith(compiledElement);
modelCtrl.$setViewValue(String(scope.dot2Comma.myModel).replace('.', ','));
modelCtrl.$render();
modelCtrl.$parsers.push(function(inputValue) {
var transformedInput = inputValue.replace(/[^0-9,.-]/g, '');
transformedInput = transformedInput.replace('.', ',');
transformedInput = transformedInput.replace(' ', '');
if (transformedInput !== inputValue) {
modelCtrl.$setViewValue(transformedInput);
modelCtrl.$render();
}
if (!isNaN(Number(transformedInput.replace(',', '.')))) {
scope.myModel = Number(transformedInput.replace(',', '.'));
} else {
scope.myModel = undefined;
}
return transformedInput;
});
}
};
}
};
}
I needed to remove my own directive from the Html content before re-compiling again, that's what caused the infinite loop.
iElem.removeAttr('dot-to-comma');
iElem.attr('ng-pattern', '/^-?[0-9]+(?:\,[0-9]+)?$/');
iElem.attr('ng-blur', 'dot2Comma.myBlurFunction()');
var compiledElement = $compile(iElem)(scope);
iElem.replaceWith(compiledElement);
here is an sample directive which replace dots with commas in a textbox :
script.js
angular.module('app', []);
angular.module('app')
.controller('ExampleController', ['$scope', function($scope) {
$scope.my = { number: '123.456' };
}]);
angular.module('app')
.directive('dotToComma', function() {
return {
restrict: 'A',
link: function (scope, element, attrs) {
scope.$watch(attrs.ngModel, function (value) {
var newValue = value.replace('.', ',');
element.val(newValue);
});
}
}
});
index.html
<html lang="en" ng-app="app">
<head>
<meta charset="utf-8">
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.1/angular.min.js"></script>
<script src="script.js"></script>
</head>
<body>
<form ng-controller="ExampleController">
<p>scope.my.number = {{my.number}}</p>
<label>In this textbox, dots will automatically be replaced with commas, even if you change its value :</label>
<input type="text" ng-model="my.number" dot-to-comma>
</form>
</body>
</html>
Here is a plunker : https://plnkr.co/edit/X6Fi0tnjBXKKhbwH0o2q?p=preview
Hope it helps !

Creating custom field validation in angular

I have the following code/plunkr and for some reason the form.name.$invalid is always true (see the span just after the input box). Angular's documentation doesn't say much about how the $invalid value is set. I have a hunch it has something to do with in the javascript I have ctrl.$error.taken = true/false and just having an object on the $error object set's $invalid to true. Can someone who knows how angular works behind the scenes help me out?
I want the "Name must be alpha-numeric" error to display if the name doesn't match the regex, but I want the "Name is already taken" error to display if the name is one from the list. I also what the text "error" to display if the field is one of those two errors. All of these things are working except the word "error" is always displayed. I'm trying to follow angular's standards of using $invalid.
View:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Example - example-forms-async-validation-production</title>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.min.js"></script>
<script src="script.js"></script>
</head>
<body ng-app="form-example1">
<form name="form" class="css-form" novalidate>
<div>
Username:
<input type="text" ng-model="name" name="name" username />
<span ng-show="form.name.$invalid">error</span>
<br />{{name}}<br />
<span ng-show="form.name.$error.badContent">Name must be alpha-numeric</span>
<span ng-show="form.name.$error.taken">Name is already taken!</span>
</div>
</form>
</body>
</html>
Script:
(function(angular) {
'use strict';
var app = angular.module('form-example1', []);
var NAME_REGEXP = /^([a-zA-Z0-9])*$/;
app.directive('username', function() {
return {
require: 'ngModel',
link: function(scope, elm, attrs, ctrl) {
var names = ['Jim', 'John', 'Jill', 'Jackie'];
ctrl.$validators.username = function(modelValue, viewValue) {
if (ctrl.$isEmpty(modelValue)) {
return true; // consider empty model valid
}
ctrl.$error.taken = names.indexOf(modelValue) > -1;
ctrl.$error.badContent = !NAME_REGEXP.test(modelValue);
return !ctrl.$error.taken && !ctrl.$error.badContent;
};
}
};
});
})(window.angular);
Plunkr:
https://plnkr.co/edit/LBRR14PpjAQigafOVTgQ?p=preview
Use separate directives one to validate alphanumeric, and one for the username already taken issue.
$ctrl.validators.alphaNumeric exposes $error.alphaNumeric on the FormController. You don't have to manipulate the value on $error.xxx, it is set automatically based on the validator's return value.
Please check the plunkr.
Also refer to https://code.angularjs.org/1.5.8/docs/api/ng/type/ngModel.NgModelController
Though I have not verified this, but I believe you now should have ng-invalid-alpha-numeric error class, owing to the fact that now form.$error.alphaNumeric is set.
JS
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.model = {};
});
app.directive('validateAlphaNumeric', function () {
var REGEX = /^([a-zA-Z0-9])*$/;
return {
require: 'ngModel',
link: function (scope, element, attrs, ctrl) {
ctrl.$validators.alphaNumeric = function (modelValue, viewValue) {
if (REGEX.test(viewValue)) {
return true
}
return false;
};
}
};
});
app.directive('validateUserNotTaken', function () {
return {
require: 'ngModel',
link: function (scope, element, attrs, ctrl) {
var names = ['Jim', 'John', 'Jill', 'Jackie'];
ctrl.$validators.userNotTaken = function (modelValue, viewValue) {
if (names.indexOf(modelValue) == -1) {
return true
}
return false;
};
}
};
});
HTML
<body ng-controller="MainCtrl">
<form name="myForm">
<input type="text" validate-alpha-numeric validate-user-not-taken ng-model="model.value1">
<p ng-if="myForm.$error.alphaNumeric">Should be alphanumeric</p>
<p ng-if="myForm.$error.userNotTaken">User Exists!</p>
<p ng-if="myForm.$error.alphaNumeric || myForm.$error.userNotTaken">Error</p>
</form>
</body>
Working plunkr: https://plnkr.co/edit/MZ7DMW?p=preview

directive reacting to attribute change

I have a directive, with an attribute :
html :
<my-directive id="test" myattr="50"></my-directive>
js :
myApp.directive('myDirective', function() {
var link = function(scope, element, attrs) {
scope.$watch('myattr', function(value) {
element.attr('myattr', value);
});
scope.change = function() {
// some code
};
};
return {
restrict: 'E',
template: '<input type="text" ng-change="change()" ng-model="myattr"/>',
scope: {myattr: '='},
link: link
};
});
My goal would be to keep myattr and the value of the input equal. With element.attr('myattr', value) I can force myattr to have the correct value, but how am I supposed to update the input when myattr changes?
For example, in this jsfiddle, when clicking on the button, I try to do :
$('#test').attr('myattr', Math.random() * 100);
But I can't find a way to 'catch' the change from within the directive.
I would like some help modifying the jsfiddle so that :
the function change is called after the jquery call.
the value of the input is always equal to myattr
You need to store the value of myattr as a property on a scope not as a value on an attribute.
Your directive is correct you need to also attach a controller.
var myApp = angular.module('myApp', []);
myApp.controller('MainController', function ($scope) {
$scope.calculate = function () {
// your logic here
alert($scope.val);
}
});
myApp.directive('myDirective', function() {
var link = function(scope, element, attrs) {
scope.change = function() {
console.log("change " + scope.myattr);
};
};
return {
restrict: 'E',
template: '<input type="text" ng-change="change()" ng-model="myattr"/>',
scope: {
myattr: '='
},
link: link
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="MainController">
My Value: {{val}} <br/>
<button type="button" ng-click="calculate()">ok</button>
<my-directive id="test" myattr="val"></my-directive>
</div>
</div>

AngularJS custom directive, cant access ngModel on DOM load

I'm using Laravel5 and creating a custom directive for angular novalidation forms. Now I can't access ngmodel placed data. My goal is to first show person's name, and then save his name, but name is not showing at DOM load...
Main problem I found is on $watch, but I don't know what to change in it.
Here's my codepen
AngularJS code:
(function() {
'use strict';
angular.module('app', [])
.controller('ctrl', mainController)
.directive('myInput', myInput);
function mainController() {
var vm = this;
vm.data = {
email: 'test#tesst.com',
name: 'Nickelson'
};
}
function myInput() {
function tempFunc(element, attrs) {
var templateWithVars = '<input type="inputType" ng-model="fieldModelName" name="fieldModelName" ng-required="required"/>';
var template = templateWithVars
.replace("inputType", attrs.type)
.replace("exampleName", attrs.example)
.replace(/fieldModelName/g, getFieldModelName(attrs));
return template;
}
function getFieldModelName(attrs) {
var objectAndField = attrs.ngModel;
var names = objectAndField.split('.');
var fieldModelName = names.pop();
return fieldModelName;
}
return {
replace: false,
scope: {},
template: tempFunc,
require: ['^form', "ngModel"],
link: function (scope, element, attrs, ctrls) {
scope.form = ctrls[0];
var ngModel = ctrls[1]; // This part is null
var model = getFieldModelName(attrs);
scope.$watch(model, function (newVal, oldVal) {
ngModel.$setViewValue(scope[model]);
});
}
}
}
})();
HTML:
<html ng-app="app">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
</head>
<body ng-controller="ctrl as vm">
<br />
email: {{ vm.data.email }}
<br />
name: {{ vm.data.name }} // Nothing showing in here!!!
<br />
<h2>Edit</h2>
<form novalidation name="vm.form">
<my-input ng-model="vm.data.name"
type="text"
example="vm.data.name"> // My Input by default is empty
</my-input>
<br />
<br />
<input type="submit" ng-show="vm.form.$valid" value="Save Name"/>
</form>
</body>
</html>
You're assigning the ng-model to your directive in a funky way. It's making it such that it does work, but the way it initializes actually overwrites the original ng-model with the blank input.
If you change your $watch function to :
if (scope[model] !== undefined) ngModel.$setViewValue(scope[model]);
It'll only update the ngModel if it's not undefined, which it is initially.
Here's an updated codepen.
Note that you'll actually have to make changes to the input before ngModel is defined (even if you just type a space and backspace, it'll be fine).
I fix my problem. I don't exacly needed place ngmodel from require: ["ngModel"], but just add it in scope (scope: { ngModel: '=' }), and after that I add it strate to tempFunc, like:
var templateWithVars = 'input type="inputType" ng-model="ngModel" name="fieldModelName" ng-required="required"/>';
And It kind of fix it...

Input autofocus attribute

I have places in my code where I have this:
<input data-ng-disabled="SOME_SCOPE_VARIABLE" />
I would like to be able to use it like this too:
<input data-ng-autofocus="SOME_SCOPE_VARIABLE" />
Or even better, mimicking how ng-style is done:
<input data-ng-attribute="{autofocus: SOME_SCOPE_VARIABLE}" />
Does this exist in the current version of AngularJS? I noticed in the code there's a BOOLEAN_ATTR which gets all the attr's that AngularJS supports. I don't want to modify that in fear of changing versions and forgetting to update.
Update: AngularJS now has an ngFocus directive that evaluates an expression on focus, but I mention it here for the sake of completeness.
The current version of AngularJS doesn't have a focus directive, but it's in the roadmap. Coincidentally, we were talking about this on the mailing list yesterday, and I came up with this:
angular.module('ng').directive('ngFocus', function($timeout) {
return {
link: function ( scope, element, attrs ) {
scope.$watch( attrs.ngFocus, function ( val ) {
if ( angular.isDefined( val ) && val ) {
$timeout( function () { element[0].focus(); } );
}
}, true);
element.bind('blur', function () {
if ( angular.isDefined( attrs.ngFocusLost ) ) {
scope.$apply( attrs.ngFocusLost );
}
});
}
};
});
Which works off a scope variable as you requested:
<input type="text" ng-focus="isFocused" ng-focus-lost="loseFocus()">
Here's a fiddle: http://jsfiddle.net/ANfJZ/39/
You can do this with the built-in ngAttr attribute bindings.
<input ng-attr-autofocus="{{SOME_SCOPE_VARIABLE}}">
The autofocus attribute will be added if SOME_SCOPE_VARIABLE is defined (even if it's false), and will be removed if it's undefined. So I force falsy values to be undefined.
$scope.SOME_SCOPE_VARIABLE = someVar || undefined;
This directive should do the trick:
angular.module('utils.autofocus', [])
.directive('autofocus', ['$timeout', function($timeout) {
return {
restrict: 'A',
scope: {'autofocus':'='}
link : function($scope, $element) {
$scope.$watch 'autofocus', function(focus){
if(focus){
$timeout(function() {
$element[0].focus();
});
}
}
}
}
}]);
Taken from here: https://gist.github.com/mlynch/dd407b93ed288d499778
scope.doFocus = function () {
$timeout(function () {
document.getElementById('you_input_id').focus();
});
};
Create a directive like this
.directive('autoFocus', ['$timeout', function ($timeout) {
return {
restrict: 'A',
link: function ($scope, $element) {
$timeout(function () {
$element[0].focus();
});
}
}
<input type="text" auto-focus class="form-control msd-elastic" placeholder="">
What I did is using regular autofocus on my inputs: <input autofocus>
And then I set the focus on the first visible input with autofocus when angular is ready:
angular.element(document).ready(function() {
$('input[autofocus]:visible:first').focus();
});
Hope this helps.
I did it with two custom directives, something like this:
(function(angular) {
'use strict';
/* #ngInject */
function myAutoFocus($timeout) {
return {
restrict: 'A',
link: function(scope, element) {
$timeout(function() {
element[0].focus();
}, 300);
}
};
}
function myFocusable() {
return {
restrict: 'A',
link: function(scope, element, attrs) {
var focusMethodName = attrs.myFocusable;
scope[focusMethodName] = function() {
element[0].focus();
};
}
};
}
angular
.module('myFocusUtils', [])
.directive('myAutoFocus', myAutoFocus)
.directive('myFocusable', myFocusable);
}(angular));
If you add attribute my-auto-focus to an element, it will receive focus after 300ms. I set the value to 300 instead of 0 to let other async components to load before setting the focus.
The attribute my-focusable will create a function in the current scope. This function will set focus to the element when called. As it creates something in the scope, be cautious to avoid overriding something.
This way you don't need to add something to Angular's digest cycle (watch) and can do it entirely in the view:
<input my-focusable="focusOnInput"></input>
<button ng-click="focusOnInput()">Click to focus</button>
I created a JSFiddle to show the myFocusable directive: http://jsfiddle.net/8shLj3jc/
For some reason I don't know, the myAutoFocus directive does not work in JSFiddle, but it works in my page.
<!DOCTYPE html>
<html>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
<body>
<div ng-app="myApp" ng-controller="namesCtrl">
<div ng-repeat="x in names">
<input ng-attr-focus={{$first}} value="{{x.name + ', ' + x.country }}" />
</div>
</div>
<script>
var myApp = angular.module('myApp', []);
myApp.controller('namesCtrl', function($scope) {
$scope.names = [
{name:'x1',country:'y1'},
{name:'x2',country:'y2'},
{name:'x3',country:'y3'}
];
});
myApp.directive("focus", function(){
return {
restrict: "A",
link: function link(scope, element, attrs) {
if(JSON.parse(attrs.focus)){
element[0].focus();
}
}
};
});
</script>
</body>
</html>
had created above custom directive for one of my use case.
always focusses on first input element.
works for ajax data, browser back/forward buttons.
Tested on chrome and firefox(default autofocus is not supported here)
JSON.parse is used to parse string "true" returned from html to boolean true in JS.
another way to use attrs.focus === "true" for if condition.
so without $timeout you can also use auto focus like this -
<input type="text" ng-show="{{condition}}" class='input-class'></input>
angular.element(document).ready(function(){
angular.element('.input-class')[0].focus();
});
Combining whar others mentioned above:
JS Code:
myApp.directive('ngAutofocus', ['$timeout', function ($timeout) {
var linker = function ($scope, element, attrs) {
$scope.$watch('pageLoaded', function (pageLoaded) {
if (pageLoaded) {
$timeout(function () {
element[0].focus();
});
}
});
};
return {
restrict: 'A',
link: linker
};
}]);
HTML:
<input type="text" ng-model="myField" class="input-block-level edit-item" ng-autofocus>
Set pageLoaded to true from your initial load method of the page get:
var loadData = function () {
..
return $http.get(url).then(function (requestResponse) {
$scope.pageLoaded = true;
......
}

Resources