61 votes

Règles de formatage des nombres dans KnockoutJS

J'ai un viewModel contenant un certain nombre de chiffres avec de nombreuses décimales. Si mes liaisons 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. Le fait de modifier les liaisons pour qu'elles ressemblent à ceci résout le problème, mais est très verbeux 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>

Notez qu'il s'agit d'un petit extrait et que le fait de devoir ajouter .toFixed(1) à chaque fois que je lie un nombre conduit à un balisage beaucoup plus désordonné que ce qui est montré ici.

Pour tout ce qui n'est pas des nombres, le remplacement de toString a été un moyen efficace pour moi de contrôler ce à quoi la sortie ressemble. Avez-vous des suggestions sur un moyen d'indiquer à knockout une fois, de manière centralisée pour ma page, quelle fonction utiliser pour convertir les nombres en chaînes de caractères avant qu'ils ne soient ajoutés à la sortie ?

D'ailleurs, il serait utile d'avoir un moyen général d'indiquer à knockout comment formater n'importe quel type de valeur. La substitution de Date.prototype.toString fonctionne mais semble un peu lourde car elle peut avoir un impact sur d'autres utilisations de .toString que celles de knockout.

93voto

RP Niemeyer Points 81663

Il y a plusieurs façons de gérer une situation comme celle-ci. Vous pouvez choisir de l'aborder par le biais des liaisons ou de la pousser dans votre modèle de vue.

Si votre modèle de vue est créé par le plugin 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 une enveloppe de la liaison de texte pour gérer le formatage.

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 (formattedText) qui inspecterait la valeur et la mettrait en forme en utilisant des valeurs par défaut modifiables ou qui vous permettrait de passer des options de mise en forme ( { type: "numeric", precision: 2 } ).

Pour votre scénario, il semble que la première option soit un bon choix. Cependant, si vous voulez le pousser dans votre modèle de vue, vous pouvez créer une observable spéciale 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 à myValue y myValue.raw en fonction de vos besoins. Sinon, vous pourriez retourner la valeur brute par défaut et exposer une fonction formatted dependentObservable. Lorsqu'un objet de ce type est converti en JSON, il perd toutes les "sub-observables". Si vous renvoyez ces données à un serveur, cela peut être un élément à prendre en compte.

Vous pourriez à nouveau le rendre plus générique et créer une formattedObservable qui prend en compte certaines informations sur la façon de formater l'objet.

Enfin, la version 1.3 beta offre une extenders API. Vous pourriez faire quelque chose de similaire à ce qui précède 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 pourriez faire en sorte que l'extension ajoute également un formatted dependentObservable à target au lieu de renvoyer le dependentObservable lui-même.

22voto

molnarg Points 1019

Puisque knockout supporte maintenant prolongateurs Je les utiliserais plutôt que des fixations personnalisées. La liaison ressemblerait à quelque chose comme 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 le format dans ce cas. Des exemples sont fournis dans la documentation sur le knockout.

13 votes

Cela semble être la bonne direction, mais un exemple serait le bienvenu.

2 votes

Vous les étendriez dans le viewmodel plutôt que dans la vue.

0 votes

En fait, pour des raisons de cohérence entre les modèles de vue des applications, mais aussi pour permettre aux concepteurs d'avoir un accès facile et une certaine flexibilité, ce balisage de vue est logique. Bien que cela ne fonctionne pas bien avec Durandal.

10voto

user2161006 Points 51

Pour formater les devises et les pourcentages, j'ai créé mon binding personnalisé numeralformat.js à utiliser avec numeral.min.js qui se trouve à l'adresse suivante http://adamwdraper.github.com/Numeral-js/

numeralformat.js (Inspiré de 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>

9voto

Stephen R. Points 84

Pour compléter la réponse acceptée ci-dessus. J'ai bifurqué sur RP Niemeyers fiddle pour ajouter le formatage des virgules. Donc, si vous avez 10001.232, le format sera 10 001.232. C'est très important si vous travaillez avec des prix. Encore une fois, ce n'est qu'un développement de 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);

2voto

LauriSaar Points 33

J'ai abordé le formatage en utilisant le plug-in jQuery Globalize. Voici ma version des gestionnaires de formatage, textFormatted y valueFormatted sont des enveloppes pour les liaisons de texte et de valeur respectivement.

L'utilisation sera :

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

En option, 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 pendant le développement ou le débogage...

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

Valeurs pour pattern doit être l'un des formats appropriés que l'on peut trouver sur le site Web de la Commission européenne. Globalize.format( value, format, [locale] ) de la fonction format attend. Il en va de même pour culture qui sera utilisé dans les options locale param. Globaliser la référence.

Définitions de reliure :

(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);
        }
    };
}());

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X