最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How to mask input with an Angularjs directive - Stack Overflow

programmeradmin1浏览0评论

I've been trying to create an AngularJS directive that will accept user input and display it as bullet points, just like a password input would.

This is what I have so far:

expose.link = function(scope, element, attributes, controller) {

    var maskValue = function(value) {
        // replace all characters with the mask character
        return (value || "").replace(/[\S]/g, "\u2022");
    }

    controller.$parsers.push(function(value) {
        return maskValue(value);
    });

}

Which does this:

View: asdf Model: ****

But I actually need it to do this:

View: **** Model: asdf

I've also tried this:

controller.$formatters.push(function(value) {
    return maskValue(value);
});

But this only works if the model is changed from my code. I need it to work as the user types into the input field.

I feel like this could work if there was a way that I could manually trigger the $formatters to run, but I couldn't find a way to do this. There may be something obvious I'm missing though.

I've been trying to create an AngularJS directive that will accept user input and display it as bullet points, just like a password input would.

This is what I have so far:

expose.link = function(scope, element, attributes, controller) {

    var maskValue = function(value) {
        // replace all characters with the mask character
        return (value || "").replace(/[\S]/g, "\u2022");
    }

    controller.$parsers.push(function(value) {
        return maskValue(value);
    });

}

Which does this:

View: asdf Model: ****

But I actually need it to do this:

View: **** Model: asdf

I've also tried this:

controller.$formatters.push(function(value) {
    return maskValue(value);
});

But this only works if the model is changed from my code. I need it to work as the user types into the input field.

I feel like this could work if there was a way that I could manually trigger the $formatters to run, but I couldn't find a way to do this. There may be something obvious I'm missing though.

Share Improve this question asked Jun 9, 2015 at 6:54 user1000952user1000952 5
  • Why don't you just give the input field type="password"? – Guinn Commented Jun 9, 2015 at 7:04
  • Good suggestion, but that's what is currently implemented. However Chrome insists on prompting to save the user's password in the latest version of Chrome regardless of hacks such as putting other hidden password fields or removing the password field from the form before submission. Also this isn't a password field anyway... the input field in question is a CVC for a credit card, which I just need to be masked - not act as a real 'password' field. – user1000952 Commented Jun 9, 2015 at 7:10
  • Hmm, how about an ng-onChange function on the input field, pass the ng-model to the function and replace characters of the model with * while saving the actual value to a different variable which is ng-model of a hidden input field? – Guinn Commented Jun 9, 2015 at 7:22
  • Must admit i never tried, so might aswell be too slow or not work at all, just thinking along with ya here – Guinn Commented Jun 9, 2015 at 7:22
  • I had implemented something similar to that, but the view will be full of asterisks on subsequent calls and there's no way to reverse **** back into asdf. Sure it can be done one character at at time, but if the user selects halfway into the **** and removes two characters at once, there's no way of knowing which characters to remove when all you can pare is asdf and **. That was probably as clear as mud. – user1000952 Commented Jun 9, 2015 at 7:37
Add a ment  | 

3 Answers 3

Reset to default 3

In the end the solution was pretty simple...

angular.module("core.application.main")
.directive("core.application.main.directive.mask",
    ["$pile",
    function($pile) {

        // Mask user input, similar to the way a password input type would
        // For example: transform abc123 into ******
        // Use this when you need to mask input, but without all the baggage that es with the
        // 'password' input type (such as the browser offering to save the password for the user)
        //
        // ----- POSSIBLE USE CASE -----
        // CVC input field for a credit card
        //
        // ----- HOW IT WORKS -----
        // When the input field gains focus, it's cloned and the clone is changed to a 'password' type
        // The original input field is hidden at this point and the clone is shown
        // When the user types into the clone the real input field's model will be kept up to date
        // When the cloned input field loses focus, it's removed from the page and the original input
        // is shown. The model value is masked for display in this field using the $formatters

        var expose = {};
        expose.require = "ngModel";
        expose.restrict = "A";

        expose.link = function(scope, element, attributes, controller) {

            var maskedInputElement;

            var maskValue = function(value) {
                // replace all characters with the mask character
                return (value || "").replace(/[\S]/g, "\u2022");
            };

            var createMaskedInputElement = function() {
                if (! maskedInputElement || ! maskedInputElement.length) {
                    maskedInputElement = element.clone(true);
                    maskedInputElement.attr("type", "password"); // ensure the value is masked
                    maskedInputElement.removeAttr("name"); // ensure the password save prompt won't show
                    maskedInputElement.removeAttr("core.application.main.directive.mask"); // ensure an infinite loop of clones isn't created
                    maskedInputElement.bind("blur", function() {
                        element.removeClass("ng-hide");
                        maskedInputElement.remove();
                        maskedInputElement = null;
                    });
                    $pile(maskedInputElement)(scope);
                    element.after(maskedInputElement);
                }
            };

            element.bind("focus", function() {
                createMaskedInputElement();
                element.addClass("ng-hide");
                maskedInputElement[0].focus();
            });

            controller.$formatters.push(function(value) {
                // ensure the displayed value is still masked when the clone is hidden
                return maskValue(value);
            });

        };

        return expose;
    }
]);

And the test for it...

describe("Mask Directive", function() {
    var scope;
    var element;
    beforeEach(module("core.application.main"));
    beforeEach(inject(function($rootScope, $pile) {
        scope = $rootScope.$new();
        /* jshint multistr: true */
        element = angular.element(" \
            <input name='cvc' type='text' ng-model='cvc' class='test' core.application.main.directive.mask> \
        ");
        $pile(element)(scope);
    }));

    it("should create a masked clone of the element when its focussed", function() {
        var clonedElement;
        element.triggerHandler("focus");
        clonedElement = element.next();
        expect(clonedElement.attr("type")).toEqual("password");
        expect(clonedElement.attr("name")).toEqual(null);
        expect(clonedElement.attr("core.application.main.directive.mask")).toEqual(null);
        expect(clonedElement.attr("ng-model")).toEqual("cvc");
        expect(clonedElement.hasClass("test")).toEqual(true);
        expect(element.hasClass("ng-hide")).toEqual(true);
    });

    it("should not create a masked clone if the mask has already been created", function() {
        expect(element.next().length).toEqual(0); // the clone shouldn't exist yet
        expect(element.next().next().length).toEqual(0); // another potential clone shouldn't exist yet
        element.triggerHandler("focus");
        expect(element.next().length).toEqual(1); // a clone should have been created
        element.triggerHandler("focus");
        expect(element.next().next().length).toEqual(0); // another clone should not have been created
    });

    it("should remove the masked input on blur and show the element", function() {
        var clonedElement;
        element.triggerHandler("focus");
        clonedElement = element.next();
        clonedElement.triggerHandler("blur");
        expect(element.next().length).toEqual(0);
        expect(element.hasClass("ng-hide")).toEqual(false);
    });

    it("should keep the element's model up to date based on the masked elements value", function() {
        var clonedElement;
        element.triggerHandler("focus");
        clonedElement = element.next();
        clonedElement.val("test").triggerHandler("input");
        expect(scope.cvc).toEqual("test");
        expect(element.val()).toEqual("••••");
    });

});

If you want to use this then you'll need to rename "core.application.main.directive.mask" to whatever namespacing method you use.

A colleague questioned the need for this directive, suggesting to just use the plain password field without the 'name' attribute... unfortunately something needs the 'name' attribute, otherwise the validation on the field won't work in Angular.

Is there any problem with making the type of input element as password ?

<input type="password" data-ng-model="maskedValue">

I guess this will do the trick. Or is there any other requirement that I have missed ?

this is another directive: you can save password in a variable in controller scope and then mask input value to star:

module.directive("customPassword", function () {
    return {
        restrict: "A",
        scope:{
            customPassword: '='
        },
        require: "ngModel",
        link: function (scope, element, attrs, ngModel) {
            ngModel.$parsers.unshift(function (value) {
                if(!value || value.length==0)
                    scope.customPassword = "";
                else if (value.length <scope.customPassword.length)
                    scope.customPassword = scope.customPassword.substring(0, scope.customPassword.length-1);
                else
                    scope.customPassword = scope.customPassword +value.substring(value.length-1);

                var star = (value || "").replace(/[\S]/g, "\u2022");
                element.context.value = star;
            });
        }
    };
});

and in view:

<input type="text" class="form-control" id="testpass" name="testpass" 
    ng-model="testpass"  custom-password="passValue" >
发布评论

评论列表(0)

  1. 暂无评论