Formatting prices, or how I rewrote input

    For work, I recently encountered a seemingly trivial task - formatting the price and dividing it by categories.
    I decided nothing complicated. Especially on the Internet, there are already a lot of ready-made solutions from simple and boring (expand the line, add spaces after every 3 characters and expand it back) to quite interesting ones (I'm sure that many people saw this regular season, but it's not about her)
    price.replace(/(\d)(?=(\d\d\d)+([^\d]|$))/g, '$1 ')
    


    Looking ahead is not a story about how I tried to perform a task using one of the standard methods, or how I produced crutches.
    Before starting work, I studied a lot of materials and half a hundred libraries. I did not find such functionality anywhere.
    I hope this is useful to someone.


    Libraries even came to my eyes to break down numbers by digits, but I decided to stop at the aforementioned regular season.
    Hung formatting on keyup , what could be more difficult?

    The first thing that testers did not like was the input of letters. since the event hangs on keyup, until the key is released, the letter appears for a split second. If you hold it, then we get a string of letters in this field, which disappears by pressing a key.
    Not a problem, I thought and hung on keydown
    var code = event.keyCode;
    if((code < 48 || code > 57) && (code < 96 || code > 105)) {
        event.preventDefault();
        return;
    }
    

    the first condition for the numeric keys is on top, and the second for NumPad is
    excellent, now if you press anything other than numbers, nothing will happen.
    A week passed, and I forgot to think about this small formatted input when suddenly a list of errors on it fell on me.
    Offhand -
    • when editing an element in the middle, the cursor moves to the end of the field (a consequence of replacing the value with a new formatted one)
    • forward, backward keys do not work
    • selection does not work
    • backspace and delete do not work
    • shift + * do not work either, but like Ctrl

    And there’s still a lot of things related to button clicks, it’s
    not such a big problem, I thought and added to 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;
    }
    

    To track the cursor positions, I made 2 get / set-CursorPosition functions
    and for each keyup
        var cursor = $(this).getCursorPosition();
        $(this).val(priceFormatted(value));
        $(this).setCursorPosition(cursor);
    


    I engaged in testing of all this code and realized that it is impossible to catch the event keyup by clicking the double keys - such as the Ctrl + A .
    In theory, the entire text should stand out, but in fact the following happened. nothing happened on keydown ( event.ctrlKey === true; return false ) and the text stood out. By keyup, the text was reformatted and the selection was reset.
    In the beginning, I tried to make up things with checking the past value length and the new one, but when you need to delete characters (highlight and press the letter / number) everything refused to work.
    As a result, it was decided to abandon keyup completely, and switch completely to keydown .
    This did not bode well, because I very much doubted the cross-browser compatibility of this solution, and in general I didn’t really want to read the codes of each key and add characters where needed.

    In general, what came of all this.

    First of all, we denote those variables that will come in handy in the future anyway
        var cursor = $(this).getCursorPosition();
        var code = event.keyCode;
        var startValue = $(this).val();
    


    First you need to determine what key was pressed
        if ((code >= 48 && code <= 57)) {
            key = (code - 48);
        }
        else if ((code >= 96 && code <= 105 )) {
            key = (code - 96);
        } else {
            return false;
        }
    

    The keys with the code 48 - 57 are the upper digits 0 - 9, and the code 96 - 105 corresponds to numpad keys.
    If another key is pressed, then we do nothing.
    In the place where the cursor was, insert the new value, format and rearrange the cursor.
        var value = startValue.substr(0, cursor) + key + startValue.substring(cursor, startValue.length);
        $(this).val(priceFormatted(value));
        $(this).setCursorPosition(cursor + $(this).val().length - startValue.length);
    


    Not bad, but what happens if you select some text and try to write a number? That's right, the text will not be deleted and the new number will replace the old one.
    Each time you click, deleting the selected text will not be difficult - jquery plugin
    $(this).delSelected();
    


    Now back to the backspace and delete keys. Everything here is also quite simple.
    $(this).val(startValue.substr(0, cursor - 1) + startValue.substring(cursor, startValue.length)); // символ сзади
    // или
    $(this).val(startValue.substr(0, cursor) + startValue.substring(cursor + 1, startValue.length)); // символ спереди
    

    Accordingly, adding a check for selection, because if you select the text and press backspace or delete, then nothing except the selected one will be deleted.
    Also needed was the logic of work if the cursor is in front of a space and the user presses backspace.
    After all the manipulations, clicking on backspase looked like this
        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));
    


    Pressing delete looked almost the same, only most of the addition / subtraction signs were reversed.
    In the evening, the task returned to me again with new reports.
    • You can insert text into the field
    • You can drag text there


    Need to do. The ban on inserting an implementation succumbed very easily
    if (
        (event.metaKey === true && code == 86) ||
        (event.ctrlKey === true && code == 86) || // Ctrl+V | Shift+insert
        (event.shiftKey === true && code == 45)
    ) 
    {
        return false;
    }
    

    And the ban on opening the context menu
    .bind('contextmenu', function (event) {
        event.preventDefault();
    })
    


    Dragging did not portend any special problems either.
    .bind('drop', function (event) {
       // ...
    


    And then interesting things started in, oddly enough, chrome.
    He alone refused to process correctly and if I did in the function
    event.preventDefault();
    // или
    return false;
    

    He left a second cursor in the input, which was not deleted by any means other than refreshing the page or console
    $('...').val(''); // именно пустое
    

    The problem was solved by an extremely ugly piece of code
    .bind('drop', function (event) {
        var value = $(this).val();
        $(this).val(''); // хак для хрома
        // если убрать нижнюю строчку то не работает.
        // курсор удаляется только с удалением и заполнением поля заного.
        $(this).val(value);
        event.preventDefault();
    })
    


    If anyone has encountered such a problem and decided to unsubscribe please.

    This ended my adventures with price formatting.
    While 2 weeks - the flight is normal, no bugs have been noticed, cross-browser compatibility is not broken.

    Now I decided to share it with everyone, because I did not find any analogues on the Internet. I designed everything in the form of a jquery library

    . You can poke and click here (jsfiddle)
    and download it here (github)

    Also popular now: