How to tie together ko.validation errors with related viewmodel field names
Asked Answered
A

6

7

I'm using Knockout.Validation and I'd like to be able to display an error summary where each line displays the error message (obviously!) and also the field name on the view model related to it, e.g.

  • Age - please enter a number
  • Date of birth - please enter a proper date

So far I've got a validatedObservable wrapping my view model, and this puts an errors array on my viewmodel automatically, containing all my errors. However I can't see any easy way to retrieve which field each error relates to.

I know I could traverse the view model myself, building up an errors collection of my own from the isValid property - is this my only option though?

Once I have the field name, I can map my validation summary to the related "friendly" label for that field (e.g. "Date of birth" rather than "DateOfBirth").

Here's a simplified version of the code I have so far:

ViewModel

function PersonModel(){
   var self = this;
   self.Age = ko.observable().extend({ number: true});

   self.DateOfBirth = ko.observable({ date: true});             
   self.Validate = function() {                           
       if (!self.isValid()) {                                         
          self.errors.showAllMessages();        
          return false;          
       }
       return true;
    };    

ko.validation.init({
                grouping: { deep: true, observable: true },
                registerExtenders: true,
                messagesOnModified: true,
                insertMessages: true
            });

ko.applyBindings(ko.validatedObservable(new PersonModel()));

Html

<ul data-bind='foreach: model().errors' class="message">
    <li>
           <span data-bind='text:  $data'></span>
    </li>
</ul>

Many Thanks

Accumbent answered 8/8, 2013 at 15:0 Comment(1)
Could you share the relevant code you have so far?Croat
S
7

You can use custom validation message for any variable.

emailAddress: ko.observable().extend({
    required: { message: 'Email Address: Required field.' }
}),
Scintillometer answered 8/8, 2013 at 18:46 Comment(2)
this falls apart when you have two of the same thing being validated - for instance what about a billing and shipping address which both have the same fieldsCamarena
They shouldn't be the same observables, knockout validation requires that you extend each individual (observable), which will be validated. You can possibly use onlyif validation rule for postal addresses or some other custom rules github.com/Knockout-Contrib/Knockout-Validation/wiki/….Enoch
T
7

You can do the following:

  • Add a friendlyName extender to provide a friendly name to your observables
  • Hack the binding handler that shows the messages

Friendly Name:

Simplest knockout extender ever:

ko.extenders.friendlyName = function (obs, options) {
    obs.friendlyName = options;
}

Usage: self.Age = ko.observable(3).extend({ friendlyName: "My Age" });

Display message hack:

Knockout validation plugin creates a bindinghandler validationMessage to display error messages. You can use it directly (with some html markup) or let the plugin handle the messages with the configuration option insertMessages.

Here I just edit the html it creates (the original binding handler is still called) to take the friendlyName into account:

var originalValidationMessageUpdate= ko.bindingHandlers.validationMessage.update;
ko.bindingHandlers.validationMessage.update = 
    function (element, valueAccessor, allBindingAccessor, viewModel, 
              bindingContext) {
        if (originalValidationMessageUpdate)
            originalValidationMessageUpdate(element, valueAccessor, 
                                            allBindingAccessor, viewModel,
                                            bindingContext);
        var prepend = (ko.isObservable(valueAccessor()) 
                            && valueAccessor().friendlyName)
                                  ? valueAccessor().friendlyName + " - "
                                  : "";

        $(element).html(prepend + $(element).html());
        // ^^^^ This is the line that is actually useful
    }

Note: I did not create friendlyName as an observable since I guess it will not be updated, but it is possible to do it.

Demo

Tallyman answered 20/6, 2014 at 13:43 Comment(2)
I'm going to go with this idea I think, so thanks for that. It's a shame they can't put something in Ko.Validation to help though - might suggest it!Accumbent
@Accumbent Well, you could contribute :)Insightful
R
3

I have found this works well - type it in the console (F12):

for(var propertyName in PersonModel()) {
  console.log(ko.validation.group(PersonModel()[propertyName])())
}

This will give you something like:

[]
[]
[]
[This field is required.]
[]
[]

You can then tie up the problem field to the property in your model. In this example, the problem would be the 4th property.

I hope this helps someone.

Ramires answered 30/1, 2016 at 19:53 Comment(1)
ugly, but works, what's with knockout validation do not have the name for a validated observable?Anthraquinone
C
2

Unfortunately you will need to specify messages for each validator unless you do something entirely custom such as GôTô has suggested.

Think about it... you haven't specified a display name for you fields so how is ko validation supposed to know a field name for the validation error message?

The simplest way:

self.Age = ko.observable().extend({ 
   number: {
      params: true,
      message: 'Age - please enter a number'
   },
   required: { 
      params: true,
      message: 'Age is required'
   }
});

If it justifies the effort, you may want to invest in extending the default messaging behaviour else its easier to just specify the messages for each validator on each property.

Cavalcade answered 8/8, 2013 at 15:0 Comment(3)
I think that does work for simple view models, where the fields are static, but I'm actually coding a framework which has metadata for the various fields, including display/friendly name. So I really need something at the framework level which applies ko.extenders to my (dynamic) objects based on this meta data, and to avoid duplication, this needs to be done as generically as possible. Therefore I'll probably go with something based on GôTô 's idea, but with the addition of also adding the friendly name to the errors collection too.Accumbent
@Accumbent your original question was almost a year ago - what were you doing in the meantime?Camarena
Server validation for one thing! :) I saw validation on the server as the highest priority and additional client-side validation as a nice-to-have. As it's a framework that I've been developing, I've been working on implementations of new apps using the framework.Accumbent
D
1

This minimal overhead approach is ideal for my needs:

ko.validation.rules["required"].message = "{0} required";

usage:

self.driverName = ko.observable().extend({ required: "Driver Name" });

rendered result:

Driver Name required

Note: stepping through knockout.validation.js showed me it's crucial to modify the default ko.validation.rules[] PRIOR to any ko.observable().extend() calls.

Detrude answered 29/3, 2016 at 17:41 Comment(0)
A
1

While this doesn't answer the "friendly" label portion of this question, nor does it result in an array of validation errors, it does build up an object to let you see where in a huge viewmodel validation errors are hiding. The question mentions traversing through the viewmodel to build up the errors, so I figured I'd share the recursive function I built for doing that with some help from Lodash.

function getErrors(koVm, maxDepth, currentDepth) {
    if ((currentDepth || 0) < (maxDepth || 5)) {
        var obj = _.pickBy(_.mapValues(koVm, function(value) {
            if (value) {
                if (_.isFunction(value.error)) {
                    return value.error();
                }

                return getErrors(value, maxDepth, (currentDepth || 0) + 1);
            }

            return null;
        }));

        return _.keysIn(obj).length ? obj : null;
    }

    return null;
}

It's not great for showing errors to users (unless you flatten the constructed object), but it's been a great help when debugging and trying to figure out what part of a huge model isn't valid.

Alcahest answered 15/2, 2019 at 23:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.