CautemocSg/ng-ip-address

View on GitHub
src/ngIpAddress.js

Summary

Maintainability
D
2 days
Test Coverage
(function() {
    'use strict';
    angular
    .module('ng-ip-address', [])
    .directive('ngIpAddress', ngIpAddress);

    function ngIpAddress() {
        return {
            restrict: 'A',
            require: '?ngModel',
            scope: {
                config: '=?ngIpConfig'
            },
            link: function(scope, element, attrs, ngModelCtrl) {
                if (!ngModelCtrl) {
                    return;
                }

                // Initialize configuration
                var allowPort = false;
                var requirePort = false;

                // 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');
                // Match colons
                var regexColon = new RegExp(':', 'g');
                // Match leading colon
                var regexLeadingColon = new RegExp('^:', 'g');

                // Initialize caret position tracker
                var curPos = 0;

                // Initialize ctrl/cmd tracker
                var ctrlDown = false;

                // Shallow watch the members of the config object and evaluate changes
                scope.$watchCollection('config', evalConfiguration);

                // 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 evalConfiguration(newConfig) {
                    // If configuration is truthy...
                    if (newConfig) {
                        // Require port if it is set
                        requirePort = typeof(newConfig.requirePort) === 'boolean' && newConfig.requirePort;
                        // If port is either required or optional, allow port
                        allowPort = requirePort || ((typeof(newConfig.allowPort) === 'boolean') && newConfig.allowPort);
                    } else {
                        // Otherwise, reset the options to false
                        allowPort = false;
                        requirePort = false;
                    }
                    // If port is allowed or the value is falsey...
                    if (allowPort || !ngModelCtrl.$viewValue) {
                        // Trigger parser to check validity
                        ngModelCtrl.$parsers[0](ngModelCtrl.$viewValue);
                    } else {
                        // Otherwise, remove colons and replace view value
                        ngModelCtrl.$setViewValue(ngModelCtrl.$viewValue.replace(regexColon, ''));
                    }
                }

                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 === 58 && !allowPort)
                        || event.which > 58 && !(ctrlDown && (event.which === 99 || event.which === 118 || event.which === 120))) {
                        // Stop key press from propagating
                        event.preventDefault();
                    }
                }

                function evalInput(val) {
                    // If val is falsey (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;

                    // Initialize port holder
                    var port = null;

                    // 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 user configured options to allow ports...
                        if (allowPort) {
                            // If the current section is not the fourth...
                            if (i < 3) {
                                // Delete any colons
                                arrayVal = arrayVal.replace(regexColon, '');
                            } else {
                                // Delete colon if it is leading (ex. 1.1.1.:)
                                arrayVal = arrayVal.replace(regexLeadingColon, '');
                                // If there is a port...
                                if (arrayVal.indexOf(':') !== -1) {
                                    // Break the segment into ip segment and ports
                                    var segmentArray = arrayVal.split(':');
                                    // Set the ip segment
                                    arrayVal = segmentArray[0];
                                    // Set the port, delete leading zeroes and remove any number after the fifth
                                    port = segmentArray[1].replace(regexLeadingZero, '').substring(0, 5);
                                    // If the port is empty, isn't a number, or the value is greater than max
                                    if (!port || !isNumeric(arrayVal) || port > 65535) {
                                        // Set validity tracker to false
                                        validationResult = false;
                                    }
                                }
                                // Otherwise, if port is required...
                                else if (requirePort) {
                                    // Set validity tracker to false
                                    validationResult = false;
                                }
                            }
                        }
                        // 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 isn't a number or 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('.');

                    // If the port is not null...
                    if (port !== null) {
                        // Attach it to the final value
                        newVal += ':' + port
                    }

                    // 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);
                }
            }
        };
    }
}());