2

I have this (very simplified) HTML:

<label>Quantity: <input name="quantity" type="number" data-bind="textInput: quantity"/></label>
<label>Euros: <input name="wholeNumber" type="number" data-bind="textInput: wholeNumber"/></label>

And this (also very simplified) Knockout model:

function ViewModel() {
    self = this;
    cents = ko.observable();
    quantity = ko.observable();
    wholeNumber = ko.computed({
        read: function() {
            return (self.cents() / 100).toFixed(2); 
        },
        write: function(value) {
            self.cents(Math.round(value * 100));
        }
    });
}

ko.applyBindings(ViewModel);

See this fiddle for a running example: https://jsfiddle.net/KHFn8/5569/

When I run this code the value of the quantity does not refresh while typing, but the value of the wholeNumber does change after each keystroke.

I have two questions about this:

  1. Since both fields have a two way binding why does this only happen in the case of the computed observable (wholeNumber), but not in the case of a normal observable?
  2. How can I prevent the updating of the field from happening while I'm still typing. I can use wholeNumber.extend({ rateLimit: { timeout: 500, method: "notifyWhenChangesStop" } });, but that's an suboptimal solution.
Koen Peters
  • 12,798
  • 6
  • 36
  • 59
  • Could you please clarify the purpose of the computed being binded to the input area? Will it be used for filtering of user input? – Serj Zaharchenko Apr 17 '15 at 16:23
  • The reason is that the user enters the price in whole euro's with the cents as decimals but that internally I must use a cents representation. – Koen Peters Apr 18 '15 at 19:01
  • Then you perhaps could use the following: make your input field be bounded to the observable `wholeNumber` and add pureComputed `cents` which would be basically your old setter. Pure computed has some performance and memory benefits compared to plain computed, provided it is pure function i.e. has no side effects (doesn't change others except himself) and has no hidden state. https://jsfiddle.net/jnue087v/ for working code sample and http://knockoutjs.com/documentation/computed-pure.html for the details of pureComputed. – Serj Zaharchenko Apr 18 '15 at 19:54
  • thanks you for the proposal. The problem is that this way the wholeNumber isn't dependant on cents so changing cents (which happens when initially loading the cents value using ajax) won't result in an updated wholeNumber value. What I'd like (I think) is that the field won't be updated until it looses focus. So that wholenumber DOES write to cents, but blocks updates form cents until the field loses focus. – Koen Peters Apr 18 '15 at 20:09
  • 1
    If I understood the problem properly, "turning the tables" on your original code would be sufficient: just make cents `pureComputed` and wholeNumber `observable` and bind them together - https://jsfiddle.net/uvmqk4sd/1/. – Serj Zaharchenko Apr 18 '15 at 20:22
  • Yes, that could work I guess. When setting the cents it will actually result in storing it in the wholeNumber and if I want to use cents for calculations then that is also not a problem. Thanks! I'd mark it as the correct answer if it weren't a comment. – Koen Peters Apr 18 '15 at 21:46

3 Answers3

1

As an answer to your first question on why it does happen with computed binds but not with normal observables:

Let's consider wholeNumber binding. Every time the value of the input field changes, dom-to-computable binding executes setter write, which in his turn updates cents; because the computed wholeNumber depends on cents observable it is subscribed to all its changes, so the change of cents causes computable-to-dom binding to execute read and update the input (and moves the cursor to the the end of the input field). Here is a slightly updated jsfiddle, with logging of getter/setter calls (https://jsfiddle.net/mdydarfb/)

In the case of an ordinary observable, every change of the input field updates only the state of the binded observable without that "chain reaction".

As an answer to the second qustion:

If I understood the problem properly, "turning the tables" on your original code would be sufficient: just make cents pureComputed and wholeNumber observable and bind them together - http://jsfiddle.net/uvmqk4sd/1

Koen Peters
  • 12,798
  • 6
  • 36
  • 59
Serj Zaharchenko
  • 2,621
  • 1
  • 17
  • 20
-1
  1. About quantity property. If you want to update it when wholeNumber is changing you need to change your wholeNumber computed definition. Because now it changes only cents:

    write: function(value) {
        self.cents(Math.round(value * 100));
    }
    
  2. Regarding wholeNumber.extend({ rateLimit: { timeout: 500, method: "notifyWhenChangesStop" } }); Why do you think it's suboptimal? It's "official" recipe from the documentation - http://knockoutjs.com/documentation/rateLimit-observable.html

Vladimir Posvistelik
  • 3,843
  • 24
  • 28
  • 1
    Thank you for your answer. It's not that I want quantity to change when changing wholeNumber, it's more about WHY its input value does not change while typing it the quantity field itself like the way the wholeNumber changes. Or maybe it does, but I just don't see it because the value doesn't differ from what I type. The rateLimit approach is suboptimal because the cursor is placed at the end of the input field when the ,xx cents part is added. That's annoying. – Koen Peters Apr 17 '15 at 08:51
  • Take a look at updated jsfiddle - https://jsfiddle.net/KHFn8/5570/ It seems like `quantity` binding is ok. Maybe I misunderstood the question? – Vladimir Posvistelik Apr 17 '15 at 08:55
  • Yes, the quantity is working fine, that's not the issue. Please reread my previous comment I changed the wording so maybe my question is more clear now. – Koen Peters Apr 17 '15 at 08:57
  • I got it. In this case I'd recommend you to "play" with cursor position. For example like in this discussion: http://stackoverflow.com/questions/26263169/how-to-retain-cursor-position-when-updating-a-knockout-js-observable-in-an-exten – Vladimir Posvistelik Apr 17 '15 at 09:12
  • Okay, that could work. It just feels hacky. I was wondering if there is something else that I'm doing wrong since the "normal" observable does not have this behavior. – Koen Peters Apr 17 '15 at 09:23
-1

Becauase it has any process in computed function about quantity. You must add quantity in function. For example;

wholeNumber = ko.computed({
    read: function() {
        return (self.cents() / 100 * self.quantity()).toFixed(2);
    },
    write: function(value) {
        self.cents(Math.round(value * 100));
    }
});
Mustafa KIRILMAZ
  • 131
  • 1
  • 3
  • 7