Pull to refresh

Маскирование числовых значений с использованием autoNumeric и Knockout

Reading time5 min
Views7.4K
В общем передо мной встала задача — переписать один из контролов, построенный на репиттере и сделать его легче, отзывчивее для клиента. При этом решил использовать knockout. Внутри для отображения цифровых данных использовались DevExpress'овские текстовые поля, они очень удобны и служили исправно, но тут встал вопрос, а как же при замене на обычные текстовые поля, я смогу добавить маску.



Начало



Для того чтобы решить данную задачу, я стал искать, какие же есть нормальные плагины jquery или библиотеки, которые позволят просто и быстро решить задачу. В итоге я нашел две библиотеки:



Анализ



Посмотрев на возможности библиотек, я точно смог сказать, что буду использовать autoNumeric, так как у него есть возможность не показывать маску ввода, а обрабатывать вводимое значение на лету. Его поведение схоже с поведением текстового поля DevExpress. У плагина jquery.maskedinput, к сожалению, такой возможности не обнаружил. Есть только возможность ввода данных по строгой маске, которая при этом появляется в текстовом поле, информируя пользователя о предстоящем формате ввода. Для моего случая такая обработка не подходит.

Применение



Ну что ж, с выбором библиотеки маскирования данных в текстовом поле, я определился, но вот как же я буду подвязывать маску для полей. Сначала была мысль, установить для всех необходимых полей, использующих единый формат ввода, определенный класс, а затем при помощи jquery скормить библиотеке autoNumeric. Но мне показалось, что это решение, будет не очень удобным.
Тогда я подумал, а почему бы не воспользоваться мощью knockout! Так как knockout, позволяет реализовывать кастомные обработчики для связывания данных, то я решил создать именно такой обработчик и указывать его в атрибуте data-bind="".

Немного об autoNumeric


Привожу значение некоторых полезных методов, используемых мной при решении задачи:

  • Метод autoNumeric() — позволяет активировать авто-маскирование на указанном элементе, посредством selector, к примеру вот так: $('.autonum').autoNumeric();
  • Метод autoNumericGet(); — позволяет получить значение, которое может использоваться в арифметических операциях (т.е. значение освобожденное от разделителей, декорирующих вводимые данные). К примеру: $(selector).autoNumericGet();
  • Метод autoNumericSet(value); — обрабатывает значение value, декорируя его необходимыми разделителями, для красоты вывода чисел. К примеру $(selector).autoNumericSet(value);


Привожу значение опций, передаваемых в метод autoNumeric() в качестве параметра:

  • aSep — указывает, какой разделитель будет использоваться для сотен
  • aDec — указывает, какой разделитель будет использоваться для дробной части
  • mDec — указывает, сколько цифр будет указываться после разделителя дробной части


Пример передачи, используемых мной опций, для конфигурирования autoNumeric:

$('.autonum').autoNumeric({ aSep: ',', aDec: '.', mDec: 0 });


* Это означает что autoNumeric будет использовать ',' для разделителя сотен целой части и '.' в качестве разделителя дробной части. А так же кол-во цифр после дробного разделителя будет ровняться 0 (т.е. будут отображаться целые числа).

Реализация с использованием knockout


Реализация кастомного обработчика связывания данных для autoNumeric


 ko.bindingHandlers.numberMaskedValue = {
        init: function(element, valueAccessor, allBindingsAccessor) {
            //попытка получения опций, если они не указаны, 
            //то устанавливаются опции по умолчанию
            var options = allBindingsAccessor().autoNumericOptions || {
                aSep: ',',
                aDec: '.',
                mDec: 0
            };
            
            //привязка html элемента и применение опций для маскирования
            $(element).autoNumeric(options);
            
            //подпись на событие 'focusout' элемента, 
            //при котором будет производиться обновление observable свойства 
            ko.utils.registerEventHandler(element, 'focusout', function() {
                var observable = valueAccessor();
                //Вызов метода получения значения пригодного для арифметических операций
                value = $(element).autoNumericGet();
                observable(isNaN(value) ? 0 : value);
            });
        },
        update: function(element, valueAccessor) {
            var value = ko.utils.unwrapObservable(valueAccessor());
            //установка значения для отображения, в декорированном виде, пользователю
            $(element).autoNumericSet(value);
        }
    };


Выше приведенная реализация позволяет вот таким образом задействовать данный обработчик:

<input type="text" data-bind="numberMaskedValue: countOfFriends"/>


или с указанием опций:

<input type="text" data-bind="numberMaskedValue: countOfFriends, autoNumericOptions:{aSep: ',', aDec: '.', mDec: 3}"/>


В данном примере обработчика, обновление значения будет происходит при событии 'focusout'. Для пользователя, введенное значение будет выглядеть красиво, а в реальном observable свойстве, оно будет оставаться пригодным для арифметической обработки. Таким образом, я могу сразу решить две проблемы: привязка нужного (передача) контрола autoNumeric, и передача (обновление) значения в observable свойстве.

Полный код примера
Html


<div data-bind="foreach: humans">
    <div class="block-of-data">
        <div>
            <label class="label label-info">id</label>
            <span data-bind="text: id"></span>
        </div>
        <div>
            <label class="label label-info">Name</label>
            <span data-bind="text: name"></span>  
        </div>
        <div>
            <label class="label label-info">Count of friends</label>
            <input type="text" data-bind="numberMaskedValue: countOfFriends"/>
        </div>
        <div>
            <label class="label label-interest">Real value "Count of friends"</label>
            <span data-bind="text: countOfFriends"></span>
        </div>
    </div>
<div>


JavaScript


$(function() {
    ko.bindingHandlers.numberMaskedValue = {
        init: function(element, valueAccessor, allBindingsAccessor) {
            var options = allBindingsAccessor().autoNumericOptions || {
                aSep: ',',
                aDec: '.',
                mDec: 0
            };
            $(element).autoNumeric(options);
            ko.utils.registerEventHandler(element, 'focusout', function() {
                var observable = valueAccessor();
                value = $(element).autoNumericGet();
                observable(isNaN(value) ? 0 : value);
            });
        },
        update: function(element, valueAccessor) {
            var value = ko.utils.unwrapObservable(valueAccessor());
            $(element).autoNumericSet(value);
        }
    };

    function Human(idv, namev, countOfFriendsv) {
        var id = ko.observable(idv),
            name = ko.observable(namev),
            countOfFriends = ko.observable(countOfFriendsv);

        return {
            id: id,
            name: name,
            countOfFriends: countOfFriends
        }
    }

    function HumansModel() {
        humans = ko.observableArray([new Human(1,'Alex', 1234), new Human(2,'Bob',12457)]);        
    }
    
    ko.applyBindings(new HumansModel())
});


Css


.block-of-data{
    border: solid 1px black;
    margin:10px;
    padding: 5px;
    background-color:#ffffaa;
    -webkit-border-radius: 10px;
    -moz-border-radius: 10px;
    border-radius: 10px;
}

.label {
    padding: 1px 4px 2px;
    -webkit-border-radius: 3px;
    -moz-border-radius: 3px;
    border-radius: 3px;
}
.label {
    font-size: 10.998px;
    font-weight: bold;
    line-height: 14px;
    color: white;
    text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
    white-space: nowrap;
    vertical-align: baseline;
    background-color: #999;
}

.label-info {
    background-color: #3A87AD;
}

.label-interest {
    background-color: #ff7722;
}




Ссылки




Спасибо за внимание!
Tags:
Hubs:
Total votes 5: ↑5 and ↓0+5
Comments0

Articles