Knockout-Contrib / Knockout-Contrib/Knockout-Validation

isValid reporting incorrectly when bound inside ko.foreach

Open
#590 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
JavaScript
Stars
1k
Forks
366
PR merge metrics
No merged PRs in 30d

Description

When using knockout.validation the isValid property will incorrectly report its status but only when it is "inside" a ko.foreach.

Using the code snippet below, if you run and enter the following values (q,z,10) in the same textbox,you will get the following output:

  • self.InvoiceAmount is q and self.InvoiceAmount.isValid is true
  • self.InvoiceAmount is z and self.InvoiceAmount.isValid is false
  • self.InvoiceAmount is 10 and self.InvoiceAmount.isValid is false

As you can see, the validation rule is for a number and q should not report true as well as 10 should not report false.

It appears the order of execution is messed up because InvoiceAmount.subcribe is executed prior to InvoiceAmount.isValid.subscribe.

When the view model is just a simple property, the results are as expected:

  • self.item is q and self.item.isValid is false
  • self.item is z and self.item.isValid is false
  • self.item is 10 and self.item.isValid is true
ko.bindingHandlers.numeral = {
            init: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
                ko.bindingHandlers.value.init(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext);
            },
            update: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
                var value = ko.utils.unwrapObservable(valueAccessor()),
                    format = ko.utils.unwrapObservable(allBindingsAccessor().format) || ko.bindingHandlers.numeral.defaultFormat,
                    formattedValue = Number(value) ? numeral(value).format(format) : value;
                $(element).val(formattedValue);
            },
            defaultFormat: "0,0.00"
        };

        var viewModel = function(param) {

            var self = this;

            self.item = ko.observable().extend({ number: true });
            self.item.subscribe(function(value) {
                $("#log").append("<p>self.item is " + value + " and self.item.isValid is " + self.item.isValid() + "</p>");
            });

            self.items = ko.mapping.fromJS([]);

            ko.mapping.fromJS(param, {
                key: function(data) {
                    return data.CustomerId;
                },
                create: function(options) {
                    return new itemViewModel(options.data);
                }
            }, self.items);

        };

        var itemViewModel = function(item) {

            var self = this;

            ko.mapping.fromJS(item, {}, self);

            self.InvoiceAmount.extend({ number: true });

            self.InvoiceAmount.subscribe(function(value) {
                $("#log").append("<p>self.InvoiceAmount is " + value + " and self.InvoiceAmount.isValid is " + self.InvoiceAmount.isValid() + "</p>");
            });
        }

        $(function() {
            var vm = new viewModel([{ "InvoiceAmount": 10 }, { "InvoiceAmount": 20 }, { "InvoiceAmount": 30 }, { "InvoiceAmount": 40 }]);
            ko.applyBindings(vm);
        });
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/numeral.js/1.5.3/numeral.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout-validation/2.0.3/knockout.validation.js"></script>

<div>
  single item:<br/>
<input type="text" data-bind="numeral: item"/>
  </div>

<div>
  foreach:<br/>
<table>
    <tbody data-bind="foreach: items">
    <tr>
        <td><input type="text" data-bind="numeral: InvoiceAmount"/></td>
    </tr>
    </tbody>
</table>
</div>

<div id="log"></div>

I also submitted this to stack overflow hoping someone could help me out and there is a working snippet:

http://stackoverflow.com/questions/33194218/when-using-knockout-validation-isvalid-reporting-incorrectly-when-bound-inside

Thanks!

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the supplied JavaScript and HTML reproduction, comparing the standalone item with InvoiceAmount inside ko.foreach. Trace the order of InvoiceAmount and isValid subscriptions while entering q, z, and 10. Done means the validation state matches the number rule in both cases and the regression is covered by a reproducible test.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript
Domain
frontend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.