<!DOCTYPE html>
<html ng-app="plunker">
<head>
<link data-require="bootstrap-css@3.2.0" data-semver="3.2.0" rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" />
<script data-require="angular.js@1.3.0-beta.5" data-semver="1.3.0-beta.5" src="https://code.angularjs.org/1.3.0-beta.5/angular.js"></script>
<script data-require="jquery@*" data-semver="2.1.1" src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script data-require="bootstrap@3.1.1" data-semver="3.1.1" src="//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body ng-controller="MainCtrl">
<br>
<div class="form-group">
<label for="mask1" class="col-xs-12 control-label">
<h3>Mask example</h3>
</label>
<label for="mask" class="col-xs-3 control-label">Input mixed</label>
<div class="col-xs-9">
<input type="text" mask="8;3" ng-model="value1" required id="mask1" class="form-control">
</div>
</div>
<div class="col-xs-12">
<br>
</div>
<div class="form-group">
<label for="mask2" class="col-xs-3 control-label">Input numeric</label>
<div class="col-xs-9">
<input type="text" mask="8;3" numeric ng-model="value2" required id="mask2" class="form-control">
</div>
</div>
<div class="col-xs-12">
<br><div class="col-xs-12">
<br>
</div><div class="col-xs-12">
<br>
</div>
</div>
<div class="form-group">
<label for="mask3" class="col-xs-3 control-label">Example IP Address</label>
<div class="col-xs-9">
<input type="text" mask="3;3;3;3" numeric ng-model="value3" required id="mask3" class="form-control">
</div>
</div>
</body>
</html>
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
});
app.directive('mask', function() {
return {
require: 'ngModel',
link: function(scope, elm, attrs, ctrl) {
var modelTmp = attrs.ngModel + ".tmp";
var parts = attrs.mask.split(";");
var strPlaceholder = '';
var intMaxChars = 0;
var pauseFormat = false;
var stepSymbol = "."; //possible signs: [.?*+^$[\]\\(){}|\/-] and space
// prepare placeholder string
for (var i = 0; i < parts.length; i++) {
for (var j = 0; j < parseInt(parts[i]); j++) {
strPlaceholder += '#';
if (j < parseInt(parts[i]) - 1) {
strPlaceholder += ' ';
}
}
intMaxChars += parseInt(parts[i]);
// at a part's end add additional chars for the step
if (i < parts.length - 1) {
strPlaceholder += ' ';
strPlaceholder += stepSymbol;
strPlaceholder += ' ';
}
}
//Use HTML5 placeholder attribute if possible
var placeholderSupport = 'placeholder' in document.createElement("input");
if (placeholderSupport) attrs.$set("placeholder", strPlaceholder);
elm.on('keydown', function(e) {
var strNew = '';
if (e.keyCode == 8) { //backspace pressed
if (scope[modelTmp] !== null && scope[modelTmp].length > 0) {
strNew = scope[modelTmp].substring(0, scope[modelTmp].length - 1);
}
var viewValue = formatOutputString(strNew);
pauseFormat = true; //do not format in $parsers.unshift
ctrl.$setViewValue(viewValue);
ctrl.$render();
scope[modelTmp] = strNew; //write clean string to data
scope.$digest();
pauseFormat = false;
e.preventDefault(); //stop event
}
});
elm.on('blur', function(e) {
if (scope[modelTmp] === '') {
pauseFormat = true; //stop formatting in $parsers.unshift
ctrl.$setViewValue('');
ctrl.$render();
scope.$digest();
pauseFormat = false;
e.preventDefault(); //stop event
}
});
elm.on('focus', function(e) {
if (scope[modelTmp] === '') {
var viewValue = formatOutputString('');
pauseFormat = true; //stop formatting in $parsers.unshift
ctrl.$setViewValue(viewValue);
ctrl.$render();
scope.$digest();
pauseFormat = false;
e.preventDefault(); //stop event
}
});
ctrl.$parsers.unshift(function(viewValue) {
var strippedViewValue = viewValue;
var viewValueNew = '';
strippedViewValue = strippedViewValue.replace(/[^A-Z^a-z^0-9]+/g, '');
if (attrs.numeric === "") { // allow only numeric chars if attribute is found
strippedViewValue = strippedViewValue.replace(/\D+/g, '');
}
if (pauseFormat) { // blur or focus event clears content if empty (and trigger $parsers.unshift)
setValidity(false); // as we are sure field is empty it is invalid
return;
}
viewValueNew = formatOutputString(strippedViewValue); // get formatted string
if (viewValueNew.indexOf('_') != -1) { //set input validity
setValidity(false);
} else {
setValidity(true);
}
if (scope[modelTmp] === strippedViewValue) { //'net' content from tmp and viewValue are identical
var v = viewValue.replace(/\s+/g, '').replace(/([.?*+^$[\]\\(){}|\/-])/g, ''); //remove spaces & symbols
if (v.length != intMaxChars) { // a different length indicate presence of special characters i.e. '%^?_
ctrl.$setViewValue(viewValueNew); // so we set newly formatted viewValue once again
ctrl.$render();
}
return viewValueNew; // and then return to avoid loops
}
scope[modelTmp] = strippedViewValue; //write clean string to data
ctrl.$setViewValue(viewValueNew);
ctrl.$render();
return viewValueNew;
});
function formatOutputString(strippedViewValue) {
var intSeqNo = 0; //counter for position of the char within the user entered string
var viewValueNew = '';
for (var p = 0; p < parts.length; p++) {
for (var i = 0; i < parseInt(parts[p]); i++) {
var strPart = strippedViewValue.substr(intSeqNo, parseInt(parts[p]));
if (i < strPart.length) {
viewValueNew += strPart[i];
} else {
viewValueNew += '_';
}
if (i < parseInt(parts[p]) - 1) {
viewValueNew += ' ';
}
}
intSeqNo += parseInt(parts[p]);
// at a parts end add symbol for the step
if (p < parts.length - 1) { // except the last one
viewValueNew += ' ';
viewValueNew += stepSymbol;
viewValueNew += ' ';
}
}
console.log(viewValueNew);
return viewValueNew;
}
function setValidity(isComplete) {
if (isComplete) {
ctrl.$setValidity('mask', true);
} else {
ctrl.$setValidity('mask', false);
}
}
}
};
})
/* Styles go here */
.ng-invalid.ng-dirty:focus,
.ng-invalid.ng-pristine:focus {
outline: 0;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px rgba(205, 10, 10, 0.6);
}
input.ng-invalid.ng-dirty,
input.ng-invalid.ng-pristine {
background-color: #fef1ec;
border-color: #cd0a0a;
color: #cd0a0a;
}