src/ngIpAddress.vanilla.js
(function() {
'use strict';
angular
.module('ng-ip-address', [])
.directive('ngIpAddress', ngIpAddress);
function ngIpAddress() {
return {
restrict: 'A',
require: '?ngModel',
link: function(scope, element, attrs, ngModelCtrl) {
if (!ngModelCtrl) {
return;
}
// Initialize regex...
// Match leading zero
var regexLeadingZero = new RegExp('^0', 'g');
// Match leading period
var regexLeadingPeriod = new RegExp('^\\.', 'g');
// Match duplicate period
var regexDupePeriods = new RegExp('\\.\\.+', 'g');
// Initialize caret position tracker
var curPos = 0;
// Initialize ctrl/cmd tracker
var ctrlDown = false;
// Attach key evaluators to the element keydown and keyup events to check
// for when ctrl/cmd is being held down
element.bind('keydown', evalIfCtrlDown);
element.bind('keyup', evalIfCtrlUp);
// Attach key evaluator to the element keypress event
element.bind('keypress', evalKeyPress);
// Attach input evaluator to the input model parsers
ngModelCtrl.$parsers.push(evalInput);
function evalIfCtrlDown(event) {
// If the character code represents ctrl/cmd...
if (event.which === 17 || event.which === 91) {
// Toggle the tracker
ctrlDown = true;
}
}
function evalIfCtrlUp(event) {
// If the character code represents ctrl/cmd...
if (event.which === 17 || event.which === 91) {
// Toggle the tracker
ctrlDown = false;
}
}
function evalKeyPress(event) {
// If the character code is not allowed...
if ((event.which < 46 && event.which !== 0 && event.which !== 8 && event.which !== 13)
|| event.which === 47
|| event.which > 57 && !(ctrlDown && (event.which === 99 || event.which === 118 || event.which === 120))) {
// Stop key press from propagating
event.preventDefault();
}
}
function evalInput(val) {
// If val is falsy (undefined, empty string, etc)...
if (!val) {
// Set the field validity to true since it should be the responsibility of 'required' to stop blank entries
ngModelCtrl.$setValidity('ipAddress', true);
// Return value
return val;
}
var newVal = val;
// Set caret position tracker
curPos = element[0].selectionStart;
// Initialize validation result tracker
var validationResult = true;
// Remove leading period
newVal = newVal.replace(regexLeadingPeriod, '');
// Remove any duplicate periods
newVal = newVal.replace(regexDupePeriods, '.');
// Break the IP address into segments
var valArray = newVal.split('.');
// Eval length to be used later
var valArrayLength = valArray.length;
// If there are less than four IP segments...
if (valArrayLength < 4) {
// Set validity tracker to false
validationResult = false;
}
// Otherwise, if there are more than four IP segments...
else if (valArrayLength > 4) {
// Enforce 4 segment limit
valArray.length = 4;
// Update array length
valArrayLength = 4;
}
// For each segment...
for (var i = 0; i < valArrayLength; i++) {
// Eval array value to be used later so the array isn't getting accessed so often
var arrayVal = valArray[i];
// If the ip segment value is longer than one digit...
if (arrayVal.length > 1) {
// Delete leading zeroes and any number after the third
arrayVal = arrayVal.replace(regexLeadingZero, '').substring(0, 3);
// If the value is greater than 255...
if (!isNumeric(arrayVal) || arrayVal > 255) {
// Set validity tracker to false
validationResult = false;
}
}
// Otherwise, check if the value is empty...
else if (!arrayVal) {
// Set validity tracker to false
validationResult = false;
}
// Set the final value back to the segment
valArray[i] = arrayVal;
}
// Reassemble the segments
newVal = valArray.join('.');
// Set validity of field (will be displayed as class 'ng-valid-ip-address' or 'ng-invalid-ip-address')
ngModelCtrl.$setValidity('ipAddress', validationResult);
// Update the view if the new values differs from the entered value
if (newVal !== val) {
ngModelCtrl.$setViewValue(newVal);
ngModelCtrl.$render();
// Set the caret position back to where it was prior to re-render
element[0].setSelectionRange(curPos, curPos);
// Return new value
return newVal;
}
// Return value
return val;
}
// Determines if a passed value is numeric
function isNumeric(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
}
}
};
}
}());