0

I have a directive which has a mask and validation for an input control. It's basically a time input where the user can input something like 45 (45 mins) or 2.5 (2.5 hours => 150 minutes). The input is a type=number when editing but a type=text when displaying the result using a filter.

(Only increments of 15 mins are allowed, and the filter is applied on the blur event)

All works fine EXCEPT that the initial value, when I click into the textbox, gets lost. In the focus event, the scope.minutes value is undefined, where this should be the 90 value set in the controller. I can't figure out why.

angular.module('demoApp', [])
	.controller('MainController', MainController)
  .filter("hoursMinutes", function () {
            return function (mins) {
                if (mins == 0) return "0 mins";
                var hours = ((mins - (mins % 60)) / 60);
                var minutes = (mins % 60);
                return (hours > 0 ? hours + " hr" + (hours === 1 ? "" : "s") : "") + (minutes > 0 ? " " + minutes + " min" + (minutes === 1 ? "" : "s") : "");
            }
        })
  .directive("ixTimeEntry", function ($filter) {
            return {
                restrict: "A",
                require: 'ngModel',
                scope: {
                    minutes: "=",
                    filter: "@",
                    inputFormat: "@"
                },
                link: function (scope, element, attr, ngModel) {
                    
                    // could set a max attribute, so mins can't be more than 1440 (a day)?

                    var inputFormat = scope.inputFormat || "minutes";
                    var filter = scope.filter || "hoursMinutes";

                    // for DOM -> model validation
                    ngModel.$parsers.unshift(function (value) {
                        var result = validate(value);
                        ngModel.$setValidity('ixTimeEntry', result.valid);
                        return result.value;
                    });

                    // for model -> DOM validation
                    ngModel.$formatters.unshift(function (value) {
                        ngModel.$setValidity('ixTimeEntry', true);
                        return $filter(filter)(value);
                    });

                    function validate(input) {
                        var result = { valid: false, value: input };
                        if (input === undefined) return result;
                        input = +input;
                        if (input === 0) return result;
                        if (input < 0) return result;

                        if (inputFormat === "minutes") {
                            // entering as minutes:
                            // if 15, 30, 45, 60, 75, 90, etc       => treat as minutes
                            // if .25, .5, .75, 1, 1.25, ...12      => treat as hours
                            // else (e.g. 13, 14, 16, 17, 18, etc)  => invalid 
                            if (input % 15 === 0) result = { valid: true, value: input };
                            if (input % .25 === 0 && input <= 12) result = { valid: true, value: input * 60 };
                        } else if (inputFormat === "hours") {
                            // entering as hours:
                            // if .25, .5, .75, 1, 1.25, etc        => treat as hours
                            // else                                 => invalid
                            if (input % .25 === 0) result = { valid: true, value: input * 60 };
                        } else {
                            throw "Invalid inputFormat in timeEntry";
                        }

                        return result;
                    }

                    function addMask(text) {
                        return $filter(filter)(text);
                    }

                    function removeMask(text) {
                        if (inputFormat === "hours")
                            return +text / 60;
                        return text;
                    }

                    element.val(addMask(scope.minutes));

                    element.bind("blur", function () {
                        element.attr("type", "text");
                        scope.$apply(function () {
                            var value = validate(element.val()).value;
                            scope.minutes = value;
                            element.val(addMask(value));
                        });
                    });

                    element.bind("focus", function () {
                        element.attr("type", "number");
                        scope.$apply(function () {
                            element.val(removeMask(scope.minutes));
                        });
                    });
                }
            };
        });;


function MainController() {
	var vm = this;
  vm.minutes1 = 90;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>

<div ng-app="demoApp" ng-controller="MainController as vm">
<form id="timeForm" name="timeForm" >
<input type="text" class="form-control" name="time" placeholder="time" ix-time-entry input-format="minutes"  minutes="vm.minutes" filter="hoursMinutes" ng-required="true" ng-model="vm.minutes1" />
</form>
<br>
Model: {{vm.minutes1}}<br>
Valid: {{timeForm.time.$valid}}<br>
</div>

1 Answer 1

1

You only set vm.minutes1 = 90; in your MainController. It works if you set vm.minutes = 90; as well.

angular.module('demoApp', [])
	.controller('MainController', MainController)
  .filter("hoursMinutes", function () {
            return function (mins) {
                if (mins == 0) return "0 mins";
                var hours = ((mins - (mins % 60)) / 60);
                var minutes = (mins % 60);
                return (hours > 0 ? hours + " hr" + (hours === 1 ? "" : "s") : "") + (minutes > 0 ? " " + minutes + " min" + (minutes === 1 ? "" : "s") : "");
            }
        })
  .directive("ixTimeEntry", function ($filter) {
            return {
                restrict: "A",
                require: 'ngModel',
                scope: {
                    minutes: "=",
                    filter: "@",
                    inputFormat: "@"
                },
                link: function (scope, element, attr, ngModel) {
                    
                    // could set a max attribute, so mins can't be more than 1440 (a day)?

                    var inputFormat = scope.inputFormat || "minutes";
                    var filter = scope.filter || "hoursMinutes";

                    // for DOM -> model validation
                    ngModel.$parsers.unshift(function (value) {
                        var result = validate(value);
                        ngModel.$setValidity('ixTimeEntry', result.valid);
                        return result.value;
                    });

                    // for model -> DOM validation
                    ngModel.$formatters.unshift(function (value) {
                        ngModel.$setValidity('ixTimeEntry', true);
                        return $filter(filter)(value);
                    });

                    function validate(input) {
                        var result = { valid: false, value: input };
                        if (input === undefined) return result;
                        input = +input;
                        if (input === 0) return result;
                        if (input < 0) return result;

                        if (inputFormat === "minutes") {
                            // entering as minutes:
                            // if 15, 30, 45, 60, 75, 90, etc       => treat as minutes
                            // if .25, .5, .75, 1, 1.25, ...12      => treat as hours
                            // else (e.g. 13, 14, 16, 17, 18, etc)  => invalid 
                            if (input % 15 === 0) result = { valid: true, value: input };
                            if (input % .25 === 0 && input <= 12) result = { valid: true, value: input * 60 };
                        } else if (inputFormat === "hours") {
                            // entering as hours:
                            // if .25, .5, .75, 1, 1.25, etc        => treat as hours
                            // else                                 => invalid
                            if (input % .25 === 0) result = { valid: true, value: input * 60 };
                        } else {
                            throw "Invalid inputFormat in timeEntry";
                        }

                        return result;
                    }

                    function addMask(text) {
                        return $filter(filter)(text);
                    }

                    function removeMask(text) {
                        if (inputFormat === "hours")
                            return +text / 60;
                        return text;
                    }

                    element.val(addMask(scope.minutes));

                    element.bind("blur", function () {
                        element.attr("type", "text");
                        scope.$apply(function () {
                            var value = validate(element.val()).value;
                            scope.minutes = value;
                            element.val(addMask(value));
                        });
                    });

                    element.bind("focus", function () {
                        element.attr("type", "number");
                        scope.$apply(function () {
                            element.val(removeMask(scope.minutes));
                        });
                    });
                }
            };
        });;


function MainController() {
	var vm = this;
  vm.minutes = 90;
  vm.minutes1 = 90;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>

<div ng-app="demoApp" ng-controller="MainController as vm">
<form id="timeForm" name="timeForm" >
<input type="text" class="form-control" name="time" placeholder="time" ix-time-entry input-format="minutes"  minutes="vm.minutes" filter="hoursMinutes" ng-required="true" ng-model="vm.minutes1" />
</form>
<br>
Model: {{vm.minutes1}}<br>
Valid: {{timeForm.time.$valid}}<br>
</div>

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.