0
votes

I'm trying to do some very simple validation using the knockout validation plugin. I want to validate if at least one text field has text and at least one checkbox is checked. All bindings work correctly and knockout itself is awesome so far. I've tested native validation rules and they work with messaging. I just can't get the validation to work for these 2 rules.

I realize I can check for empty values very easily with jQuery but I would really like to utilize knockout.

The model (without validation because I haven't found anything that works yet):

var SearchForm = function(collections) {

    // main search fields
    this.fullRecord = ko.observable();
    this.title = ko.observable();
    this.author = ko.observable();

    // collections to search
    var sources = [];
    $.each(collections, function(index,collection) {
        sources.push(new Source(collection));
    });

    this.sources = ko.observableArray(sources);

    // Error handling vars
    this.errors = ko.validation.group(this);

};

var Source = function(collection) {
    $.extend(this,collection);
    this.id = "collection-"+this.code;
    this.selected = ko.observable(true);
};

Here I'm just creating a list of source objects from collection data that comes from the server. That data is irrelevant since I'm only concerned with the observable 'selected' property.

The markup:

<div id="advanced-controls" class="row">
    <div class="col-sm-8">
        <fieldset id="search-fields">
            <div class="form-group">
                <label for="fullrecord" class="control-label">Keywords:</label>
                <input type="text" id="fullrecord" class="form-control" name="fullrecord"  placeholder="Full Record Search" data-bind="value:fullRecord" />
            </div>
            <div class="form-group">
                <label for="title" class="control-label">Title:</label>
                <input type="text" id="title" name="title" class="form-control" data-bind="value:title"/>
            </div>
            <div class="form-group">
                <label for="author" class="control-label">Author:</label>
                <input type="text" id="author" name="author" class="form-control" data-bind="value:author"/>
            </div>
            <div class="form-group">
                <button id="advanced-search-submit" class="btn btn-primary" data-bind="click:search">Search</button>
                <button id="advanced-search-reset" class="btn" data-bind="click: clear">Clear All</button>
            </div>
        </fieldset>
    </div>
    <div class="col-sm-4">
        <fieldset data-bind="foreach: sources">
            <div class="form-group">
                <input type="checkbox" name="collections" data-bind="attr:{ id:id, value:code }, checked:selected, click: $parent.clearRequiredSourceError ">
                <label data-bind="attr:{ for:id }, text: name"></label>
            </div>
        </fieldset>
    </div>
</div>

In the validation function before submitting:

 // If there's any knockout validation errors
    if (model.errors().length > 0) {
        model.errors.showAllMessages();
        isValid = false;
    }    

I've tried setting a custom validation extension on the observable array of sources like this:

this.sources = ko.observableArray(sources).extend({
        validation: {
            validator : function (sources) {
                var anySelected = false;
                $(sources).each(function(){
                   anySelected = this.selected();
                });
                return anySelected;
            },
            message: 'At least one source is required to search.'
        }
    });

But that doesn't fire when the checkboxes are clicked, only when the array is changed ~ push, pop, etc. Yes I have the config set correctly:

ko.validation.configure({
        grouping: {
            deep: true,
            observable: true
        }
    });

This seems like it should be very simple to achieve. Maybe my brain is just fried from diving into the whole knockout world this week. Any suggestions are greatly appreciated. Thanks in advance!

1

1 Answers

2
votes

Forgive me for not reading your entire question, as it is very long, but I am curious if you need Knockout validation for this or if you are looking for something like this -

var selectedOption = ko.observable();
var selectionsOk = ko.computed(function () {
    ((!!field1()|| !!field1()|| !!field1())&&!!selectedOption())
});

Where selectedOption is a list of radio buttons, and once one is selected returns the value, and you could either use an observableArray to contain each of your fields so it is dynamic or you list the fields out and make sure that at least one of them has a value. The !! will evaluate your observable as true or false, true would be returned unless the observables' value was null, undefined, '', or false

The selectionOk computed could be used to prevent clicking some button to proceed or inversely for displaying an error message until the conditions are met.