web-dev-qa-db-fra.com

Règles de formatage des nombres dans KnockoutJS

J'ai un viewModel avec un tas de nombres avec beaucoup de décimales. Si mes fixations ressemblent à ceci:

    <tr>
        <td data-bind="text: Date"></td>
        <td data-bind="text: ActualWeight"></td>
        <td data-bind="text: TrendWeight"></td>
    </tr>

Ensuite, bien sûr, la sortie a toutes les décimales et est très illisible. Changer les liaisons pour ressembler à ceci résout le problème, mais est très bavard et "bruyant":

    <tr>
        <td data-bind="text: Date"></td>
        <td data-bind="text: ActualWeight().toFixed(1)"></td>
        <td data-bind="text: TrendWeight().toFixed(1)"></td>
    </tr>

Remarque, ceci est un petit extrait et devoir ajouter .toFixed (1) chaque fois que je lie un nombre conduit à un balisage beaucoup plus compliqué que ce qui est montré ici.

Pour tout sauf les nombres, remplacer la chaîne toString a été un moyen efficace pour moi de contrôler à quoi ressemble la sortie. Avez-vous des suggestions sur la façon de dire une fois le knockout, d'une manière centrale pour ma page, quelle fonction utiliser pour convertir les nombres en chaînes avant qu'ils ne soient ajoutés à la sortie?

D'ailleurs, avoir une manière générale de dire à KO comment formater n'importe quel type de valeur semble utile. Le remplacement de Date.prototype.toString fonctionne mais semble un peu lourd car il peut avoir un impact sur d'autres utilisations de .toString en plus des knockout.

60
Erv Walter

Il existe plusieurs façons de gérer une situation comme celle-ci. Vous pouvez choisir de l'adresser via des liaisons ou de l'insérer dans votre modèle de vue.

Si votre modèle de vue est créé par le plug-in de mappage et que vous ne souhaitez pas personnaliser la façon dont il est créé, vous pouvez envisager d'utiliser une liaison personnalisée qui est un wrapper à la liaison de texte pour gérer la mise en forme.

Quelque chose comme ( http://jsfiddle.net/rniemeyer/RVL6q/ ):

ko.bindingHandlers.numericText = {
    update: function(element, valueAccessor, allBindingsAccessor) {
       var value = ko.utils.unwrapObservable(valueAccessor()),
           precision = ko.utils.unwrapObservable(allBindingsAccessor().precision) || ko.bindingHandlers.numericText.defaultPrecision,
           formattedValue = value.toFixed(precision);

        ko.bindingHandlers.text.update(element, function() { return formattedValue; });
    },
    defaultPrecision: 1  
};

Il serait certainement possible de créer une liaison encore plus générique (formatedText) qui inspecterait la valeur et la formaterait en utilisant des valeurs par défaut remplaçables ou vous permettrait de passer des options de formatage ({ type: "numeric", precision: 2 }).

Pour votre scénario, il semble que la première option pourrait être un bon choix. Cependant, si vous souhaitez le pousser dans votre modèle de vue, vous pouvez créer un observable spécial qui peut renvoyer à la fois une version formatée et une version brute de la valeur.

Cela pourrait être quelque chose comme ( http://jsfiddle.net/rniemeyer/fetBG/ ):

function formattedNumericObservable(initialValue, precision) {
    var _raw = ko.observable(initialValue),
        precision = precision || formattedNumericObservable.defaultPrecision,        
        //the dependentObservable that we will return
        result = ko.dependentObservable({
            read: function() {
               return _raw().toFixed(precision); 
            },
            write: _raw
        });

        //expose raw value for binding
        result.raw = _raw;

        return result;   
}

Maintenant, vous pouvez potentiellement vous lier contre myValue et myValue.raw Selon vos besoins. Sinon, vous pouvez le retourner et renvoyer la valeur brute par défaut et exposer un formatted dependObservable. Lorsqu'un objet comme celui-ci est converti en JSON, il perdra l'un des "sous-observables", donc si vous renvoyez ces données à un serveur, cela pourrait être une considération.

Vous pouvez à nouveau le rendre plus générique et créer un formattedObservable qui contiendra des informations sur la façon de formater l'objet.

Enfin, la version 1.3 beta propose une API extenders. Vous pouvez faire quelque chose de similaire à ci-dessus comme: ( http://jsfiddle.net/rniemeyer/AsdES/ )

ko.extenders.numeric = function(target, precision) {
    var result = ko.dependentObservable({
        read: function() {
           return target().toFixed(precision); 
        },
        write: target 
    });

    result.raw = target;
    return result;
};

Ensuite, appliquez-le à un observable comme: var myValue = ko.observable(1.223123).extend({numeric: 1});

Vous pouvez également avoir l'extension simplement ajouter un formatted dependObservable à target au lieu de renvoyer le dependObservable lui-même.

94
RP Niemeyer

Puisque knockout prend désormais en charge extenders , je les utiliserais à la place des liaisons personnalisées. La liaison ressemblerait à ceci:

<tr>
    <td data-bind="text: Date.extend({format : 'date'})"></td>
    <td data-bind="text: ActualWeight.extend({format : 'weight'})"></td>
    <td data-bind="text: TrendWeight.extend({format : 'weight'})"></td>
</tr>

Vous devez écrire l'extension format dans ce cas. Des exemples sont fournis dans la documentation de désactivation.

22
molnarg

Pour formater la devise et le pourcentage, j'ai créé ma liaison numérique numeralformat.js à utiliser avec numeral.min.js trouvé à http://adamwdraper.github.com/Numeral-js/

numeralformat.js (inspiré par dateformat.js et moment.min.js)

var formatNumber = function (element, valueAccessor, allBindingsAccessor, format) {
    // Provide a custom text value
    var value = valueAccessor(), allBindings = allBindingsAccessor();
    var numeralFormat = allBindingsAccessor.numeralFormat || format;
    var strNumber = ko.utils.unwrapObservable(value);
    if (strNumber) {
        return numeral(strNumber).format(numeralFormat);
    }
    return '';
};

ko.bindingHandlers.numeraltext = {
    init: function (element, valueAccessor, allBindingsAccessor) {
        $(element).text(formatNumber(element, valueAccessor, allBindingsAccessor, "(0,0.00)"));  
    },
    update: function (element, valueAccessor, allBindingsAccessor) {
        $(element).text(formatNumber(element, valueAccessor, allBindingsAccessor, "(0,0.00)"));
    }
};

ko.bindingHandlers.numeralvalue = {
    init: function (element, valueAccessor, allBindingsAccessor) {
        $(element).val(formatNumber(element, valueAccessor, allBindingsAccessor, "(0,0.00)"));

        //handle the field changing
        ko.utils.registerEventHandler(element, "change", function () {
            var observable = valueAccessor();
            observable($(element).val());
        });        
    },
    update: function (element, valueAccessor, allBindingsAccessor) {
        $(element).val(formatNumber(element, valueAccessor, allBindingsAccessor, "(0,0.00)"));
    }
};

ko.bindingHandlers.percenttext = {
    init: function (element, valueAccessor, allBindingsAccessor) {
        $(element).text(formatNumber(element, valueAccessor, allBindingsAccessor, "(0.000 %)"));
    },
    update: function (element, valueAccessor, allBindingsAccessor) {
        $(element).text(formatNumber(element, valueAccessor, allBindingsAccessor, "(0.000 %)"));
    }
};

ko.bindingHandlers.percentvalue = {
    init: function (element, valueAccessor, allBindingsAccessor) {
        $(element).val(formatNumber(element, valueAccessor, allBindingsAccessor, "(0.000 %)"));

        //handle the field changing
        ko.utils.registerEventHandler(element, "change", function () {
            var observable = valueAccessor();
            observable($(element).val());
        });
    },
    update: function (element, valueAccessor, allBindingsAccessor) {
        $(element).val(formatNumber(element, valueAccessor, allBindingsAccessor, "(0.000 %)"));
    }
};

Exemples de liaisons dans View.

        <td><label>Available Commitment Balance:</label> </td>
        <td>
            <!-- ko with: SelectedLoan -->
            <span data-bind="numeraltext: AvailableCommitmentAmount"></span>            
            <!-- /ko -->
        </td>
        <td><label> % Interest Rate:</label></td>
        <td>
            <!-- ko with: SelectedLoan -->
            <input  data-bind="percentvalue: InterestRatePercent" />
            <!-- /ko -->
        </td>
        <td><label> $ Amount To Transfer:</label></td>
        <td>
            <!-- ko with: SelectedLoan -->
            <input class="inputsmall" data-bind="numeralvalue: FundsHeldTotalAmount" />
            <!-- /ko -->
        </td>
10
user2161006

Pour s'appuyer sur la réponse acceptée ci-dessus. J'ai bifurqué RP Niemeyers pour ajouter également une mise en forme de virgule. Donc, si vous avez 10001.232, le format sera 10.001.232. Assez important si vous travaillez avec des prix. Encore une fois, cela ne fait que renforcer la réponse.

JSFiddle

<div data-bind="numericText: myValue"></div>
<div data-bind="numericText: myValue, positions: 3"></div>
<div data-bind="numericText: myValue, positions: myPositions"></div>
<input data-bind="value: myPositions" />

<div>
    <br>
    just testing commas<br>
    <input type=text id="withComma" readonly/>
</div>
ko.bindingHandlers.numericText = {
    update: function(element, valueAccessor, allBindingsAccessor) {
       var value = ko.utils.unwrapObservable(valueAccessor());
       var positions= ko.utils.unwrapObservable(allBindingsAccessor().positions) || ko.bindingHandlers.numericText.defaultPositions;
       var formattedValue = value.toFixed(positions); 
       var finalFormatted = ko.bindingHandlers.numericText.withCommas(formattedValue);  

        ko.bindingHandlers.text.update(element, function() { return finalFormatted ; });
    },

    defaultPositions: 2,

    withCommas: function(original){
       original+= '';
     x = original.split('.');
    x1 = x[0];
    x2 = x.length > 1 ? '.' + x[1] : '';
    var rgx = /(\d+)(\d{3})/;
    while (rgx.test(x1)) {
        x1 = x1.replace(rgx, '$1' + ',' + '$2');
    }
    return x1 + x2;

    } 
};

var viewModel = {
    myValue: ko.observable(12673.554),
    myPositions: ko.observable(4)
};

ko.applyBindings(viewModel);

/*Just testing the function below, you don't need thsi....*/     



function addCommas(nStr)
{
    nStr += '';
    x = nStr.split('.');
    x1 = x[0];
    x2 = x.length > 1 ? '.' + x[1] : '';
    var rgx = /(\d+)(\d{3})/;
    while (rgx.test(x1)) {
        x1 = x1.replace(rgx, '$1' + ',' + '$2');
    }
    return x1 + x2;
}
var formatted = addCommas('1070781.493')
$('#withComma').val(formatted);
9
Stephen R.

J'ai abordé le formatage à l'aide du plug-in jQuery Globalize. Voici ma version des gestionnaires de formatage, textFormatted et valueFormatted sont des wrappers pour les liaisons de texte et de valeur respectivement.

L'utilisation sera:

<span data-bind="textFormatted: Amount, pattern: 'n'" />

Facultativement, la culture peut également être spécifiée. Mais je pense que ce type de contrôle ne devrait pas appartenir au HTML, bien qu'il puisse être utile lors du développement ou du débogage ...

<input data-bind="valueFormatted: Amount, pattern: 'n', culture: 'et'" type="text" />

Les valeurs de la propriété/liaison pattern doivent être des formats appropriés que le paramètre format de la fonction Globalize.format( value, format, [locale] ) attend. Il en va de même pour la propriété/liaison culture qui sera utilisée dans le paramètre facultatif locale. Globaliser la référence.

Définitions contraignantes:

(function() {

    function getFormatedOrPlainResult(value, allBindingsAccessor) {
        var pattern = allBindingsAccessor.get('pattern');

        if (pattern == null || !/\S*/.test(pattern)) {
            return value;
        }
        var valueToFormat = pattern === 'd' ? new Date(value) : value;
        return Globalize.format(valueToFormat, pattern, allBindingsAccessor.get('culture'));
    };

    ko.bindingHandlers.textFormatted = {
        init: ko.bindingHandlers.text.init,
        update: function(element, valueAccessor, allBindingsAccessor) {
            var result = getFormatedOrPlainResult(ko.unwrap(valueAccessor()), allBindingsAccessor);
            ko.bindingHandlers.text.update(element, function() { return result; });
        }
    };

    ko.bindingHandlers.valueFormatted = {
        init: function(element, valueAccessor, allBindingsAccessor) {
            var result = getFormatedOrPlainResult(ko.unwrap(valueAccessor()), allBindingsAccessor);
            ko.bindingHandlers.value.init(element, function() { return result; }, allBindingsAccessor);
        },
        update: function(element, valueAccessor, allBindingsAccessor) {
            var result = getFormatedOrPlainResult(ko.unwrap(valueAccessor()), allBindingsAccessor);
            ko.bindingHandlers.value.update(element, function() { return result; }, allBindingsAccessor);
        }
    };
}());
2
LauriSaar

S'il s'agit simplement d'afficher un numéro localisé d'une liaison de texte, un moyen très simple consiste à utiliser toLocaleString()

<tr>
  <td data-bind="text: ActualWeight().toLocaleString()"></td>
  <td data-bind="text: TrendWeight().toLocaleString()"></td>
</tr>

Pour plus d'informations, visitez page .

0
Lars Aicher