Best approach for value conversion in KnockoutJS?
Asked Answered
E

2

7

I have been trying to figure out the right approach to displaying/editing percentage values in Knockout-JS (and more generally how I should create reusable components like these).

My ViewModel has an observable value which is a percentage stored as a fractional number, e.g. 0.5 to represent 50%. I would like to display and edit the value in the percentage format (e.g. '50') so the users don't get confused (they get confused easily).

writeable computed

I was able to a simple version by setting up a writeable computed function: see http://jsfiddle.net/Quango/fvpjN/

However, this is not very re-usable, as it would need to be reimplemented for each value. I experimented with an extender but this effectively masked the underlying value and so made it unusable.

BindingHandlers

I think what I need is a binding handler, so instead of writing

<input data-bind="value: commission" /> 

I would write

<input data-bind="percentage: commission" />

I had a look at the code in the "value" bindingHandler in knockout.js, but there is a lot of code there for the binding and I don't want to replicate that.

So my questions are:

  1. is there a good/standard/template way to do this sort of value conversion?

  2. if not, is there a way to re-use the "value" binding without having to copy and paste the existing code?

Elbert answered 31/1, 2013 at 17:9 Comment(0)
C
15

I always wanted to write an extender. So here comes another answer to your question that is implemented via a knockout extender.

I'm still undecided wether I like this one or the one with the Percentage class better.

HTML

<input data-bind="value: p1.percentage, valueUpdate: 'afterkeydown'"></input>%
= <span data-bind="text: p1"></span>

JavaScript

ko.extenders.percentage = function(target, option) {
    target.percentage = ko.computed({
        read: function() {
            return this() * 100;
        },
        write: function(value) {
            this(value/100);
        },
        owner: target
    });
    return target;
};

var model = {
    p1: ko.observable(0.5).extend({'percentage': true})
}

ko.applyBindings(model)

Live demo

http://jsfiddle.net/DWRLr/

Caruthers answered 31/1, 2013 at 18:1 Comment(1)
You are a star! Both answers are excellent but I think your extender is exactly what I was blundering about trying to find. It's simple, re-usable and self-explanatory. I could re-use this design as a generic value converter.Elbert
C
3

How about wrapping the fraction value and the percentage value together so it becomes a reusable component:

HTML

<input data-bind="value: p1.value, valueUpdate: 'afterkeydown'"></input>
<h1><span data-bind="text: p1.frac"></span> = <span data-bind="text: p1"></span></h1>

JavaScript

function Percentage(frac) {
    this.frac = ko.observable(frac);
    this.value = ko.computed({
        read: function() {
            return this.frac() * 100;
        },
        write: function(value) {
            this.frac(value/100);
        },
        owner: this
    });
}

Percentage.prototype.toString = function() {
    return this.value() + '%';
}

var model = {
    p1: new Percentage(0.5)
}

ko.applyBindings(model)

Live demo

http://jsfiddle.net/9kCkm/1/

Caruthers answered 31/1, 2013 at 17:43 Comment(1)
I prefer this answer. Less steps to remember further and it worked in the first shotDonegan

© 2022 - 2024 — McMap. All rights reserved.