1

I have a knockout model with 44 observable. Users selects values for them and clicks on the submit button to store the selected values in the database. I want to disable the button if a value is not selected for one of the observable.

var TestModel = function() {
    self.Feedback1 = ko.observable();
    self.Feedback2 = ko.observable();
    ..
    self.Feedback44 = ko.observable();
    self.IsEnabled = ko.observable(false);
    self.Feedback1.subscribe(function(){
       if (self.Feedback1() != undefined && .... self.Feedback44() != undefined) {
          self.IsEnabled(true);
       } else {
          self.IsEnabled(false); 
       } 
   };
   .
   .
    self.Feedback44.subscribe(function(){
       if (self.Feedback1() != undefined && .... self.Feedback44() != undefined) {
          self.IsEnabled(true);
       } else {
          self.IsEnabled(false); 
       } 
   };


};

<button data-bind="click: SubmitEvaluation,enable:IsEnabled">Submit Evaluation</button>
shresthaal
  • 675
  • 4
  • 13
  • 28

4 Answers4

1

Add references to your properties into an array:

self.submitableProperties = [self.Feedback1, self.Feedback2, ..., self.Feedback44];

Then, scanning your properties becomes much easier.

for (var i = 0; i < self.submitableProperties.length; i++) {
    self.submitableProperties[i].subscribe(checkEnabled);
}

function checkEnabled() {
    var anyUndefined = false;
    for (var i = 0; i < self.submitableProperties.length; i++) {
        if (self.submitableProperties[i]() === undefined) {
            anyUndefined = true;
            return;
        }
    }
    self.IsEnabled(!anyUndefined);
}
Nathan
  • 1,675
  • 17
  • 25
  • Depending on your model, you may be able to loop through your properties instead of explicitly adding them to an array. http://stackoverflow.com/questions/85992/how-do-i-enumerate-the-properties-of-a-javascript-object – Nathan Jan 08 '14 at 20:19
  • I think this is not the best way to do things, every feedback field would then need to be added to the array (not very dynamic) – pardahlman Jan 09 '14 at 13:00
1

To accomplish what you're trying to do with least amount of code I would create a computed which loops through all observables of your viewmodel.

        self.isEnabled = ko.computed(function() {
            for (p in self) {
                if (ko.isObservable(self[p])) {
                    if (self[p]() === null || self[p]() === undefined)
                        return false;
                }
            }
            return true;
        });
1

I think that the solution that Marius suggest is the cleanest approach. However, it is a bit daft to check if a observable value is null or undefined -- what if one of the feedback observables are empty string, or if there is another observable that does not have to do with the form but is undefined? In both those cases, you will have a problem with the proposed solution. So, we want to detect the feedback-observables and check them. There are are a few ways you can do that, here are a few:

  1. Naming convention, all feedback properties names contains "Feedback" as a substring
  2. Programmatically add the feedback observables into the array (if there is no particular reason for having them named)
  3. Add all feedback properties in an object that is a property of you view model

In the jsfiddle below I went with the first approach http://jsfiddle.net/pardahlman/dz7UH/

self.AllQuestionsAnswered = ko.computed(function () {
    for (p in self) {
        if (ko.isObservable(self[p])) {
            var isFeedback = p.indexOf('Feedback') != -1;
            if (!isFeedback) {
                continue;
            }
            var valueOfObservable = self[p]();
            if (!valueOfObservable) {
                return false;
            }

        }
    }

    return true;
});
pardahlman
  • 1,394
  • 10
  • 22
1

A great way to do this, and could help in many other parts of your app is to use the knockout.validation library:

https://github.com/Knockout-Contrib/Knockout-Validation

I have created a fiddle that demonstrates how this could be applied to your scenario.

http://jsfiddle.net/jiggle/2xAS7/

HTML:

<fieldset>
    <legend>User: <span data-bind='text: errors().length'></span> errors</legend>
    <label>Feedback 1: <input data-bind='value: feedback1' required/></label>
    <label>Feedback 2: <input data-bind='value: feedback2' required/></label>    

    <label>
        Subscriptions: 
        <select data-bind='value: feedback3, options: feedback3Options, optionsCaption: "Choose one..."' required></select>
    </label>
   <label>Feedback 4: <input data-bind='value: feedback4' required/></label>   
    <label>Feedback 5: <input data-bind='value: feedback5' required/></label>   
    <label>Feedback 6: <input data-bind='value: feedback6' required/></label>   
        <label>Feedback 7: <input data-bind='value: feedback7' required/></label>   
</fieldset>
<button type="button" data-bind='click: submit'>Submit</button>

        <button type="button" data-bind='click: submit, enable:isComplete'>Submit (disabled if not complete)</button>

        <button type="button" data-bind='click: submit, enable:errors().length===0'>Submit (disabled if not complete)</button>

<br />
<br />

       Display inline error messages? <input type="checkbox" data-bind='click: toggleInlineErrors,checked:showInlineErrors'/>

Code:

ko.validation.rules.pattern.message = 'Invalid.';


ko.validation.configure({
    registerExtenders: true,
    messagesOnModified: true,
    insertMessages: true,
    parseInputAttributes: true,  //this is 'true' to read the 'required' attribute in your html
    messageTemplate: null
});


var viewModel = {
    feedback1: ko.observable(),  //required attribute is set in the HTML
    feedback2: ko.observable(),  //required attribute is set in the HTML
    feedback3: ko.observable(),  //required attribute is set in the HTML
    feedback4: ko.observable(),  //required attribute is set in the HTML
    feedback5: ko.observable(),  //required attribute is set in the HTML
    feedback6: ko.observable(),  //required attribute is set in the HTML
    feedback7: ko.observable().extend({ required: true }), //or add the required attribute when you create the observable
    feedback3Options: ['Technology', 'Music'],

    submit: function () {
        if (viewModel.errors().length == 0) {
            alert('Thank you.');
        } else {

            viewModel.errors.showAllMessages(this.showInlineErrors());
            alert('Please check your submission.');
        }
    }
};

viewModel.showInlineErrors = ko.observable(true);
viewModel.errors = ko.validation.group(viewModel);

viewModel.isComplete = ko.computed(function(){
    return viewModel.errors().length === 0
});

viewModel.toggleInlineErrors = function () {  
    viewModel.errors.showAllMessages(false);
    return true;
};

addEventListener('load', function () {
ko.applyBindings(viewModel);
});

The key is the ko.validation.group(viewModel), which groups all your observable into one group that is constantly (observable) being evaluated as you make changes to your viewmodel.

That being said, you can then data-bind: 'enable: viewModel.errors.length===0, click:yourClickHandler' or create a computed observable that returns true if there are no errors.

The fiddle demonstrates both options.

It's an excellent library, and very powerful, the fiddle I created also demonstrates how you can toggle the inline errors, and just give an overall message (when you submit), or highlight the missing entries inline so the user can see where the missing selections are (probably very helpful, if you have 44 items the user needs to complete!).

Hope it helps

(just include kendo.validation.js (from the github reference above) to your page/bundle/module and you can start using it)

John Lucas
  • 1,618
  • 14
  • 19