Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 473
Grofit Avatar asked Feb 17 '12 14:02

Grofit


2 Answers

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");
                 }
           });
       }
    }
};
like image 180
madcapnmckay Avatar answered Sep 21 '22 03:09

madcapnmckay


I had been editing madcapnmckay's post, but the differences have become significant enough that I think a new answer is needed.

It is heavily based off of madcapnmckay's post, but it fixes a bug pointed out by MorganTiley. The original only works if the user has modified the observable. If they haven't then the code never gets fired. So, I've modified it so that it fires the tooltip code when it gets created, in addition to when it changes.

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

The one downside is that the tooltip will display on hover before knockout validation has been run (example, you have a "required" validation on a field, before you press submit a tooltip will display saying the field is required, but the field will not highlight in pink). Once you change the field however, the tooltip will disappear if the field is valid.

My app was not using qtip, but rather Twitter Bootstrap Tooltip, so here is the code for that as well.

ko.bindingHandlers.invalidTooltip = {
    init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
        var observable = valueAccessor(), $element = $(element);
        if (observable.isValid) {
            var updateTooltip = function (valid) {
                if (!valid) {
                    $element.attr("data-original-title", observable.error);
                    $element.tooltip();

                } else {
                    $element.tooltip("destroy");
                }
            }
            updateTooltip();
            observable.isValid.subscribe(updateTooltip);
        }
    }
};
like image 31
viggity Avatar answered Sep 21 '22 03:09

viggity