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

#knockout.js

#knockout.js

Вопрос:

У меня есть 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 было для меня эффективным способом контролировать, как выглядит результат. Любые предложения о том, как указать knockout один раз каким-либо центральным способом для моей страницы, какую функцию использовать для преобразования чисел в строки, прежде чем они будут добавлены к выводу?

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

Ответ №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 resu<   
}
  

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

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

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

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

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

Ответ №2:

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

 <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 расширитель. Примеры приведены в документации knockout.

Комментарии:

1. Это кажется правильным направлением, но пример был бы хорош

2. Вы бы расширили их в viewmodel, а не в view.

3. На самом деле, для согласованности в app viewmodel, но для дизайнеров, чтобы иметь легкий доступ и гибкость, эта разметка представления имеет смысл. Хотя и не очень хорошо работает с Durandal.

Ответ №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 fiddle, чтобы добавить форматирование запятой. Итак, если у вас есть 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 свойства / привязки должны быть любым из подходящих форматов, которые ожидает параметр
Globalize.format( value, format, [locale] )
функции format .
То же самое касается culture свойства / привязки, которые будут использоваться в необязательном 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 resu< });
        }
    };

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

Ответ №6:

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

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

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

Ответ №7:

Давайте сделаем это проще. НАМНОГО проще:

Ключевое слово — unwrap , и обратите внимание на скобку после суммы. (Это не обязательно должно быть частью вашего объекта knockout.)

 <script type="text/javascript">
    function getDecimal(amount) {
        var value = ko.unwrap(amount());
        return parseFloat(value).toFixed(2);
    }
...

<span data-bind="text: getDecimal(ItemAmount)">
  

Кроме того, вам НЕ нужно разворачивать его, если вы сделаете это вместо этого (обратите внимание на скобки):

 function getDecimal(amount) {
    return parseFloat(amount).toFixed(2);
}
<span data-bind="text: getDecimal(ItemAmount())">