Правила форматирования чисел в KnockoutJS

У меня есть viewModel с кучей чисел с большим количеством десятичных знаков. Если мои привязки выглядят так:

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

Тогда, конечно, выход имеет все десятичные знаки и очень нечитабель. Изменение привязок, чтобы выглядеть так, решает проблему, но очень многословно и "шумно":

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

Обратите внимание, что это один небольшой фрагмент и нужно добавить .toFixed(1), каждое место, где я привязываю число, приводит к гораздо более грязной разметке, чем показано здесь.

Для всех, кроме чисел, переопределение toString было эффективным способом контролировать то, как выглядит вывод. Любые предложения по способу сказать нокаутом один раз, каким-то центральным способом для моей страницы, какую функцию использовать для преобразования чисел в строки, прежде чем они будут добавлены в вывод?

В этом отношении, имея общий способ сказать нокауту, как форматировать любой тип значения, кажется, что это было бы полезно. Overriding Date.prototype.toString работает, но чувствует себя немного тяжело, поскольку это может повлиять на другие применения .toString, кроме как только нокаута.

Ответ 1

Есть несколько способов, которыми вы можете справиться с ситуацией, подобной этой. Вы можете выбрать, чтобы обратиться к нему через привязки или вставить его в свою модель просмотра.

Если ваша модель представления создается плагином сопоставления, и вы не хотите настраивать способ ее создания, вы можете рассмотреть возможность использования настраиваемой привязки, которая является оболочкой для привязки текста для обработки форматирования.

Что-то вроде (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  
};

Конечно, было бы возможно создать еще более общую привязку (formattedText), которая либо проверила значение, и отформатировала его, используя некоторые переопределяемые значения по умолчанию, либо разрешила вам передавать некоторые параметры форматирования ({ type: "numeric", precision: 2 }).

Для вашего сценария кажется, что первый вариант может быть хорошим выбором. Однако, если вы хотите вставить его в свою модель просмотра, вы можете создать специальное наблюдаемое, которое может вернуть как форматированную, так и необработанную версию значения.

Это может быть что-то вроде (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;   
}

Теперь вы можете потенциально связать с myValue и myValue.raw в зависимости от ваших потребностей. В противном случае вы можете перевернуть его и вернуть исходное значение по умолчанию и выставить a formatted dependObservable. Когда такой объект преобразуется в JSON, он потеряет любой из "суб-наблюдаемых", поэтому, если вы отправляете эти данные обратно на сервер, который может быть рассмотрен.

Вы можете снова сделать его более универсальным и создать formattedObservable, который принимает некоторую информацию о том, как отформатировать объект.

Наконец, 1.3 beta предлагает API extenders. Вы могли бы сделать что-то подобное выше, например: (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;
};

Затем примените его к наблюдаемому, например: var myValue = ko.observable(1.223123).extend({numeric: 1});

Вы могли бы иметь расширитель также просто добавить formatted dependObservable в target вместо того, чтобы возвращать сам independentObservable.

Ответ 2

Так как нокаут теперь поддерживает extenders, я бы использовал их вместо пользовательских привязок. Связывание будет выглядеть примерно так:

<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>

В этом случае вы должны написать расширитель format. Примеры приведены в документации для нокаутов.

Ответ 3

Чтобы форматировать валюту и проценты, я создал свою собственную привязку numeralformat.js для использования с numeral.min.js, найденным в http://adamwdraper.github.com/Numeral-js/

numeralformat.js(Вдохновленный dateformat.js и 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 %)"));
    }
};

Примеры привязок в представлении.

        <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>

Ответ 4

Опираясь на принятый ответ выше. Я форсировал RP Niemeyers скрипку, чтобы добавить форматирование запятой. Итак, если у вас есть 10001.232, это будет формат 10,001.232. Очень важно, если вы работаете с ценами. Опять же, это только строится на ответе.

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

Ответ 5

Я подошел к форматированию с помощью плагина jQuery Globalize. Вот моя версия обработчиков форматирования, textFormatted и valueFormatted являются оболочками для привязки текста и значения соответственно.

Использование будет:

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

Можно также указать культуру. Но я думаю, что такой контроль не должен принадлежать HTML, хотя он может быть полезен во время разработки или отладки...

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

Значения для свойства pattern/binding должны быть любыми подходящими форматами, которые Globalize.format( value, format, [locale] ) function format param ожидает. То же самое относится к свойству culture property/binding, которое будет использоваться в необязательном параметре locale. Глобализация ссылки.

Определения привязки:

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

Ответ 6

Если это просто отображение локализованного номера текстовой привязки, очень простой способ - использовать toLocaleString()

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

Для получения дополнительной информации посетите страницу.