Форматування ціни, або як я input переписував

    По роботі недавно зіткнувся з, начебто, тривіальним завданням — форматування ціни і розподіл її за розрядами.
Нічого складного вирішив я. Тим більше на просторах інтернету лежить вже купа готових рішень від простих і нудних (розгортаємо рядок, додаємо через кожні 3 символу прогалини і розгортаємо тому) до цілком цікавих (впевнений що цю регулярку багато хто бачив, але мова не про неї)
 
price.replace(/(\d)(?=(\d\d\d)+([^\d]|$))/g, '$1 ')

 
 Забігаючи вперед — це не розповідь про те як я намагався одним із стандартних методів виконати завдання, або як плодив милиці.
Перш ніж почати роботу я вивчив багато матеріалів і пів сотні бібліотек. Подібного функціоналу ніде не знайшов.
Сподіваюся це комусь знадобиться.

 
На очі траплялися навіть бібліотеки, для розбивання чисел за розрядами, але я вирішив зупиниться на вищезгаданій регулярке.
Повісив форматування на keyup , що може бути складніше?
 
Перше що не сподобалося тестувальникам це введення букв. так як подія висить на keyup то поки не відпустите клавішу буква з'являється на частку секунди. Якщо її тримати то у нас виходить в цьому полі низка з букв, яка пропадає по віджиманню клавіші.
Не проблема, подумав я і на keydown повісив
 
var code = event.keyCode;
if((code < 48 || code > 57) && (code < 96 || code > 105)) {
    event.preventDefault();
    return;
}

перша умова для числових клавіш зверху, а другий для NumPad
відмінно, тепер якщо натискати що або крім цифр — не буде нічого відбуватися.
Минув тиждень, я й думати забув про цей маленький форматується input як раптом на мене звалився список помилок по ньому.
На вскидку —
 
     
при редагуванні елемента в середині курсор переміщається в кінець поля (наслідок заміни значення новим відформатованим)
 не працюють клавіші вперед, назад
 не працює виділення
 не працюють backspace і delete
 shift + * теж не працюють, втім як і Ctrl
 
І ще багато багато чого пов'язаного з натисканнями на кнопки
не така вже й велика проблема подумав я і додав в keyup
 
if (
    code == 9 || // tab
    code == 27 || // ecs
    event.ctrlKey === true || // все что вместе с ctrl
    event.metaKey === true ||
    event.altKey === true || // все что вместе с alt
    event.shiftKey === true || // все что вместе с shift
    (code >= 112 && code <= 123) || // F1 - F12
    (code >= 35 && code <= 39)) // end, home, стрелки
 {
    return;
}

Для відстеження позицій курсора зробив 2 функції get / set-CursorPosition
і по кожному keyup
 
var cursor = $(this).getCursorPosition();
    $(this).val(priceFormatted(value));
    $(this).setCursorPosition(cursor);

 
Зайнявся тестуванням за все це коду і зрозумів, що не виходить відловити подія keyup при натисканні подвійних клавіш — наприклад Ctrl + A .
По ідеї весь текст повинен виділятися, але насправді відбувалося наступне. по keydown не відбувалося нічого (event.ctrlKey === true; return false ) і текст виділявся. За keyup текст форматувався заново і виділення скидалося.
На початку я намагався щось Намудрували з перевіркою минулого довжини значення і нової, але коли потрібно видалення символів (виділив і натиснув букву / цифру) все працювати відмовлялося.
У підсумку вирішено було відмовитися від keyup повністю, і перейти повністю на keydown .
Це не віщувало нічого доброго, тому що я дуже сильно сумнівався в кросбраузерності цього рішення, та й у цілому зчитувати коди кожної клавіші і додавати куди потрібно символи самому мені не дуже хотілося.
 
Вообщем що з усього цього вийшло.
 
Насамперед позначимо ті змінні які знадобляться в майбутньому в будь-якому випадку
 
var cursor = $(this).getCursorPosition();
    var code = event.keyCode;
    var startValue = $(this).val();

 
Спочатку потрібно визначити що за клавіша була натиснута
 
if ((code >= 48 && code <= 57)) {
        key = (code - 48);
    }
    else if ((code >= 96 && code <= 105 )) {
        key = (code - 96);
    } else {
        return false;
    }

Клавіші з кодом 48 — 57 це верхні цифри 0 — 9, і код 96 — 105 відповідає numpadовскім
Якщо інша клавіша натиснута то нічого не робимо.
У місце де був курсор вставляємо нове значення, форматуємо і переставляємо курсор.
 
var value = startValue.substr(0, cursor) + key + startValue.substring(cursor, startValue.length);
    $(this).val(priceFormatted(value));
    $(this).setCursorPosition(cursor + $(this).val().length - startValue.length);

 
Непогано, а що буде якщо виділити якийсь текст і спробувати написати число? Правильно, текст не віддалиться і нове число встане на місце старого
При кожному натисканні видалити виділений текст не важко — jquery плагін
 
$(this).delSelected();

 
Тепер повернемося до клавіш backspace і delete. Тут все теж досить просто
 
$(this).val(startValue.substr(0, cursor - 1) + startValue.substring(cursor, startValue.length)); // символ сзади
// или
$(this).val(startValue.substr(0, cursor) + startValue.substring(cursor + 1, startValue.length)); // символ спереди

Відповідно додавши перевірку на виділення, адже якщо виділити текст і натиснути backspace або delete то крім виділеного нічого не віддалиться.
Також потрібна була логіка роботи якщо курсор стоїть перед пропуском і користувач натискає backspace
Після всіх маніпуляцій натискання на backspase виглядало так
 
var delCount = $(this).delSelected();
    if (!delCount) {
        if (startValue[cursor - 1] === ' ') {
            cursor--;
        }
        $(this).val(startValue.substr(0, cursor - 1) + startValue.substring(cursor, startValue.length));
    }
    $(this).val(priceFormatted($(this).val()));
    $(this).setCursorPosition(cursor - (startValue.length - $(this).val().length - delCount));

 
Натискання на delete виглядав майже також, тільки більшість знаків складання / віднімання змінені на зворотні.
Увечері завдання знову повернулася до мене вже з новими звітами.
 
     
Можна вставляти в поле текст
 Можна перетягувати туди текст
 
 
Треба робити. Заборона на вставку реалізації піддався дуже легко
 
if (
    (event.metaKey === true && code == 86) ||
    (event.ctrlKey === true && code == 86) || // Ctrl+V | Shift+insert
    (event.shiftKey === true && code == 45)
) 
{
    return false;
}

І заборона на відкриття контекстного меню
 
.bind('contextmenu', function (event) {
    event.preventDefault();
})

 
Перетягування теж не віщувало особливих проблем.
 
.bind('drop', function (event) {
   // ...

 
І тут почалися цікаві речі в, як не дивно, хромі.
Він один відмовлявся обробляти правильно і якщо я у функції робив
 
event.preventDefault();
// или
return false;

Він залишав у інпут другий курсор, який не віддалився ніякими способами крім поновлення сторінки або консольного
 
$('...').val(''); // именно пустое

Проблему вирішив вкрай негарний шматок коду
 
.bind('drop', function (event) {
    var value = $(this).val();
    $(this).val(''); // хак для хрома
    // если убрать нижнюю строчку то не работает.
    // курсор удаляется только с удалением и заполнением поля заного.
    $(this).val(value);
    event.preventDefault();
})

 
Якщо хто-небудь стикався з такою проблемою і вирішив її отпишитесь ласка.
 
На цьому закінчилися мої пригоди з форматуванням ціни.
Поки 2 тижні — політ нормальний, багів не помічено, кроссбраузерность не порушена.
 
Тепер вирішив поділитися з усіма, тому що в інтернеті аналогів не виявив. Оформив все у вигляді jquery бібліотеки
 
Потикати і поклацати можна тут (jsfiddle)
а завантажити — тут (github)
    
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.