Wednesday, July 13, 2011

Filter keyboard input of JSF input components

jQuery keyfilter plugin is a good choice if you want to filter input by specified regular expression. It allows you to input only specified characters via regular expression. Assume, your input field has an ID "myinput" and you want to restrict any user input to hexadecimal characters. This task can be done as follows
 
$('#myinput').keyfilter(/[0-9a-f]/i);
 
You can also pass a test function instead of regular expression. It expects two arguments: event target (DOM element) and incoming character. If the return value is true, the input character is valid and allowed, otherwise - not.
 
$('#myinput').keyfilter(function(c) {
    return c != 'a';
});
 
Easy? There are a lot of predefined masks too - for numeric, alphanumeric, etc. signs. Do we have something like in JSF? No. Well, there is a component MaskedInput in PrimeFaces, but it's restricted to a fix length. Length of the input value can not be variable and there is a very small set of signs to configure input masks. Conclusion - poor component :-). Keyfilter covers MaskedInput and offers more flexibility. Can we implement the keyfilter functionality in JSF? Yes, ideally as client behavior. The aim of this post is not this implementation. The aim of this topic is to show how to attach this functionality to an existing JSF component (hard-coded). I would like to take PrimeFaces AutoComplete. I would also like to show a tooltip if user input some disallowed characters. Let's start. We extend the component class by adding new resources - CSS and JS files for jQuery tooltip (qTip2), keyfilter plugins and an own extension for the AutoComplete widget.
@ResourceDependencies({
    @ResourceDependency(library="primefaces", name="forms/forms.css"),
    @ResourceDependency(library="primefaces", name="jquery/ui/jquery-ui.css"),
    @ResourceDependency(library="primefaces", name="jquery/jquery.js"),
    @ResourceDependency(library="primefaces", name="jquery/ui/jquery-ui.js"),
    @ResourceDependency(library="primefaces", name="core/core.js"),
    @ResourceDependency(library="primefaces", name="autocomplete/autocomplete.js"),
    @ResourceDependency(library = "css", name = "jquery.qtip.css"),
    @ResourceDependency(library = "js", name = "tooltip/jquery.qtip.js"),
    @ResourceDependency(library = "js", name = "autocomplete/jquery.keyfilter.js"),
    @ResourceDependency(library = "js", name = "autocomplete/autocomplete.js")
})
public class AutoComplete extends org.primefaces.component.autocomplete.AutoComplete {
}
In the AutoCompleteRenderer.java we add these lines to encodeScript()
// get tooltip text for invalid characters
String tooltipText = MessageUtils.getMessageText(facesContext,
    facesContext.getApplication().getMessageBundle(), "autocomplete.invalidChars");
if (tooltipText != null) {
    tooltipText = tooltipText.replaceAll("'", "\\\\'");
} else {
    // fallback
    tooltipText = "Input character is not allowed";
}
writer.write(",tooltipText:'" + tooltipText + "'");
I used here an own utility class MessageUtils to get a message from message bundle. The most complicated step is the writing of an extended widget.
PrimeFaces.widget.ExtendedAutoComplete = function(id, cfg) {
    this.superWidget = PrimeFaces.widget.AutoComplete;
    this.superWidget(id, cfg);
    var _self = this;

    // initialize flag whether the current input character is valid
    this.jq.data("allowedChar", true);

    // define tooltip
    this.jq.qtip({
        content: {
            text: cfg.tooltipText
        },
        style: {
            widget: true,
            classes: 'ui-tooltip-rounded ui-tooltip-shadow'
        },
        position: {
            my: 'bottom center',
            at: 'top center'
        },
        events: {
            show: function(event, api) {
                try {
                    if (_self.jq.data("allowedChar")) {
                        event.preventDefault();
                    }
                } catch(e) {
                    // IE can throw an error
                }
            },
            hide: function(event, api) {
                _self.jq.data("allowedChar", true);
            }
        },
        show: {
            event: false
        },
        hide: {
            event: 'blur'
        }
    });

    // filter input
    this.filterKeys(cfg);
}

jQuery.extend(PrimeFaces.widget.ExtendedAutoComplete.prototype, PrimeFaces.widget.AutoComplete.prototype);

PrimeFaces.widget.ExtendedAutoComplete.prototype.filterKeys = function(cfg) {
    var _self = this;

    _self.jq.keyfilter(function(c) {
        var isAllow = _self.allowChar(c);
        _self.jq.data("allowedChar", isAllow);

        if (cfg.pojo) {
            _self.jq.unbind('keyup');
        }

        if (isAllow) {
            // hide tooltip
            _self.jq.qtip('hide');

            if (cfg.pojo) {
                _self.jq.keyup(function(e) {
                    if (e.keyCode != 13) {
                        jQuery(_self.jqh).val(jQuery(this).val());
                    }
                });
            }
        } else {
            // show tooltip
            _self.jq.qtip('show');
        }

        return isAllow;
    });
}

PrimeFaces.widget.ExtendedAutoComplete.prototype.allowChar = function(c) {
    return c != '%' && c != '?' && c != '&';
}
I wrote here a test function instead of regular expression to prevent % ? & signs to be input. The result looks pretty fine

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.