0

I want to place knockout validation for my html column "Step No" to ensure value should be unique and increament without gaps from 1 to total number of Steps.

example html:

    <table>
      <tbody data-bind="foreach:dataValues">
        <tr>
          <td>
            <input type="text" data-bind="value: StepNo" />
          </td>
          <td>
            <input type="text" data-bind="value: Details" />    
          </td>
         <td>
            <input type="text" data-bind="value: Address" />    
          </td>
        </tr>
      </tbody>
    </table>

JavaScript:

function UniqueViewModel(){
    var dataValues=ko.observableArray([{StepNo:1,Details:"test1",Address:"address1"},
                                       {StepNo:2,Details:"test2",Address:"address2"},
                                       {StepNo:3,Details:"test3",Address:"address3"},
                                       {StepNo:4,Details:"test4",Address:"address4"}])

    }
ko.applyBindings(new UniqueViewModel());
delixfe
  • 2,471
  • 1
  • 21
  • 35
  • Your code won't run. 'dataValues' need to be a property ( ***this.*** dataValues = ko.observableArray([...]);) – delixfe Jan 27 '13 at 12:58

1 Answers1

0

Using knockout-validation, you can create a rule for your observableArray:

var data =  [
    { StepNo: 1, Details: "test1", Address: "address1" }, 
    { StepNo: 1, Details: "test2", Address: "address2" }, 
    { StepNo: 3, Details: "test3", Address: "address3" }, 
    { StepNo: 4, Details: "test4", Address: "address4" }, 
];

function UniqueViewModel(initialData) {
     var self = this;
     self.dataValues = ko.observableArray(initialData).extend({
         validation: {
             validator: function (val, someOtherVal) {
                 var sorted = val.sort(function (a, b) {
                       return a.StepNo - b.StepNo; 
                 }), max = val.length;

                 for(var i = 0; i < max; i++) {
                     if(sorted[i].StepNo !== i+1) {
                         return false;
                     }
                 }
                 return true;
             },
             message: 'Your error message.',
             params: true
         }
     });
 }

var viewModel = new UniqueViewModel(data);
// show error
viewModel.dataValues.isModified(true);

 ko.applyBindings(viewModel);

And then you can show the message somewhere with:

<p data-bind="validationMessage: dataValues"></p>

Fiddle: http://jsfiddle.net/delixfe/AUYhy/

The problem is that will only work once or better only if you run the validation manually again. There are two reasons:

The first is that the validation will only trigger again if the observable changes. That is the observableArray 'dataValues' in that case. And observableArrays only change when changeing the underlying array i.e adding or removing elements. But it will not fire any changed event on changes to the elements.

The second problem is that the data in dataValues consists of plain JavaScript objects. They are no ko.observables. Therefore there is no two-way binding (updateing the input will not update the object's value).

You can solve the second problem by using ko.observable for your data:

var data = [
    { StepNo: ko.observable(1), Details: "test1", Address: "address1" }, 
    { StepNo: ko.observable(1), Details: "test2", Address: "address2" }, 
    { StepNo: ko.observable(3), Details: "test3", Address: "address3" }, 
    { StepNo: ko.observable(4), Details: "test4", Address: "address4" }
];

To solve the first issue you have to make sure to react on changes of the observableArray and the StepNo. Therefore you can use an ko.computed.

function UniqueViewModel(initialData) {
    var self = this;

    self.dataValues = ko.observableArray(initialData);

    self.dataValuesStepsValid = ko.computed(function () {
        // the call to self.dataValues() subscribes to the observableArray
        // and the call to element.StepNo() inside the mapping to each element
        var numbers = ko.utils.arrayMap(self.dataValues(), function (element) {
            return element.StepNo();
        });
        var max = numbers.length;
        numbers.sort(function (a, b) {
            return a - b;
        });

        for (var i = 0; i < max; i++) {
            // when changing the data in the table we get strings
            if (numbers[i] != i + 1) {
                return false;
            }
        }
        return true;
    });
}


var viewModel = new UniqueViewModel(data);

ko.applyBindings(viewModel);

You can show the message like that:

<p data-bind="ifnot: dataValuesStepsValid">Your error message</p>

The following fiddle shows that: http://jsfiddle.net/delixfe/UzDZd/

Alternatively, you could attach a validation rule to the computed.

delixfe
  • 2,471
  • 1
  • 21
  • 35