Search code examples
javascriptvalidationknockout.jsqtipknockout-validation

Knockout Validation and Qtip


I currently use Jquery Validation and Qtip together to deal with the actual validation and displaying of information to the screen using the nice tooltip style notifications upon validation errors using the errorPlacement component of the validation options.

Currently each viewModel has its own custom method for setting up and kicking off the validation and callbacks, however I was trying to look at a nicer way of doing this, be it adding a custom binding to setup my validation rules via the data-bindings or an alternative way, but still yielding the same results (i.e the errorPlacement is triggered when a validation error occurs and tells Qtip to display the error for the given element).

Now before I started making one myself I just checked online and found Knockout Validation, which I initially thought was a great idea, I could apply my validation logic directly to the data within my viewModel and then just find some sort of callback to get Qtip to kick in, however it seems there is no callback that I can find documented. The library seems to do everything I want for the validation side of things, just not for the displaying side of things. I looked through the source code and examples but couldn't see anything other than ko.validation.group(viewModel) which would give me an observable containing the errors, but I am not sure if I could use this the same way as I was expecting.

Here is an example of how my current validation happens:

/*globals $ ko */
function SomeViewModel() {

    this.SetupValidation = function () {
        var formValidationOptions = {
            submitHandler: self.DoSomethingWhenValid,
            success: $.noop,
            errorPlacement: function (error, element) {
                if (!error.is(':empty'))
                { qtip.DoSomethingToDisplayValidationErrorForElement(element, error); }
                else
                { qtip.DoSomethingToHideValidationErrorForElement(element); }
            }
        };

        $(someForm).validate(formValidationOptions);
        this.SetupValidationRules();
    };

    this.SetupValidationRules = function() {
        $(someFormElement1).rules("add", { required: true, minlength: 6, maxlength: 20, alphaNumeric: true });
        $(someFormElement2).rules("add", { required: true, minlength: 6, maxlength: 20 });
        $(someFormElement3).rules("add", { required: true, email: true, });
    };
}

I currently am sure I can remove the need for the validation rules method by adding a custom binding so I can set the validation in the data-bind, however if possible I would like to use the same sort of callback approach with the existing Knockout-Validation binding.


Solution

  • I haven't used Knockout-Validation specifically but I have written something similar in the past. A quick glance at the source shows that each extended observable gets a sub-observable isValid. This could be used to hide show messages in your markup using conventional knockout visible bindings.

    To get QTip to work a custom binding could subscribe to this isValid property and perform the necessary initialization to show/hide QTip when triggered.

    EDIT

    Here is an example to get you started

    http://jsfiddle.net/madcapnmckay/hfcj7/

    HTML:

    <!-- Note that you have to reference the "qtipValMessage" binding -->
    <!-- using the "value" binding alone is not enough                -->
    <input data-bind="value: emailAddress, qtipValMessage : emailAddress" />
    

    JS:

    ko.bindingHandlers.qtipValMessage = {
        init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
            var observable = valueAccessor(), $element = $(element);
           if (observable.isValid) {
                observable.isValid.subscribe(function(valid) {
                    if (!valid) {
                        $element.qtip({
                            overwrite: true,
                            content: {
                                text: observable.error
                            }
                         });
                     } else {
                         $element.qtip("destroy");
                     }
               });
           }
        }
    };