1

I am using accoungting.js plugin to format currency. The only problem is, it places cursor at the end of the value as soon as type anything and since I've kept 2 decimal value precision for currency it stops me from typing more. I did not find any source where this is used to edit values as soon as you type. How can I make this work? Any workaround or hack found?

<input autocomplete="off" type="text" id="Price"/>

JS

var options = {
    symbol : "£",
    decimal : ".",
    thousand: ",",
    precision : 2,
    format: "%s%v"
};

$("#Price")
  .on("input",
    function() {
      var _this = $(this);
      debugger;
      var val = _this.val().startsWith("£") ? _this.val().split("£")[1] : _this.val();
      var value=accounting.formatMoney(val,options);
      _this.val(value);
});

Demo

Guruprasad J Rao
  • 29,410
  • 14
  • 101
  • 200

2 Answers2

1

FULLY RE-EDITED
(for clarity)

This is, I think, a kind of a bug fix for accounting.js.

Problem was about cursor position when used on keyup (or on input event like here) in an input field.

In this solution, I switched back to keyup event, since - strangely- input event do not provide the keycode number.

Here is the final solution

Add this small script of mine (made partly from SO answers and some work, 1 year ago or so... Can't remember the sources)

// ---------------------------------------------------------------------------  What is the browser ?

var isOpera = !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0;
    // Opera 8.0+ (UA detection to detect Blink/v8-powered Opera)
var isFirefox = typeof InstallTrigger !== 'undefined';   // Firefox 1.0+
var isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0;
    // At least Safari 3+: "[object HTMLElementConstructor]"
var isChrome = !!window.chrome && !isOpera;              // Chrome 1+
var isIE = /*@cc_on!@*/false || !!document.documentMode; // At least IE6

function GetCursorPos (field) {
    // Initialize
    iCaretPos = 0;

    if (isIE){  //(document.selection) {

        // Set focus on the element
        field.focus();

        // To get cursor position, get empty selection range
        oSel = field.createTextRange();

        // Move selection start to 0 position
        oSel.moveStart('character', -field.value.length);

        // The caret position is selection length
        iCaretPos = oSel.text.length;
    }
    if (isChrome||isSafari){    //(field.selectionStart || field.selectionStart == '0')
        iCaretPos = field.selectionStart;
    }
    if (isFirefox){
        iCaretPos = field.selectionStart;
    }

    return iCaretPos;
}

function SetCursorPos (field,Pos) {
    // Set focus on the element
    field.focus();

    if (isIE){
        field.selectionStart=Pos;
    }
    if (isChrome||isSafari){
        field.setSelectionRange(Pos,Pos);
    }
    if (isFirefox){
        field.selectionStart=Pos;
    }
    return;
}

Then, precisely for accounting.js, the way to manage the onkeyup value update is:

$("#Price")
.on('keyup', function (event) {
    var val = this.value;
    var x = event.keyCode;                  // Get the keycode for backspace check
    var offset=0;                           // Set the offset to zero by default.

    if(((x>=96)&&(x<=105))||((x>=37)&&(x<=40))||(x==8)||(x==46)){   // Allow digits from 0 to 9 AND arrows AND backspace AND Delete

        var dotPos = val.indexOf(".");          // Check for dot position.
        var offsetComa = val.length%4;          // Variable used to check if a coma is to be added.

        // Offset conditions
        if(val.length==1){
            offset=1;                           // When the first digit is entered in the field, it's time to offset by 1 for the "£" to be added.
        }
        if(val.length>1){
            if(offsetComa==0){                  // when there is no remainder of val.length/4, it's time to offset 1 for the coma to be added.
                offset=1;
            }
            if((offsetComa==0)&&((x==46)||(x==8))){ // when there is no remainder of val.length/4, BUT WE REMOVED A CHARACTER.  offset -1 for the coma to be added.
                offset=-1;
            }
            if(dotPos==1){                      // If dot is right after "£" => user has backspaced!
                val="";                         // Consider val as empty!
            }
            if(dotPos==-1){                     // If no dot present reinsert it!
                val = val.slice(0, cursorPos-1) + "." + val.slice(cursorPos);
            }
            if(cursorPos==val.length-2){        // If inputting the second decimal, remove the possible third to avoid rounding.
                val = val.slice(0, cursorPos+1);
            }
            if(cursorPos==val.length-3){        // If inputting decimals, clear offset.
                offset=0;
                val = val.slice(0, val.length-1)
            }
        }

    }else{                                      // Removes any non-digit character
        if(x!=8){
            cursorPos = GetCursorPos(this);
            val = val.slice(0, cursorPos-1) + val.slice(cursorPos);


            if(val.charAt(cursorPos-1)=="."){   // decimal point
                cursorPos+=1;
            }

            this.value = val;
            SetCursorPos(this,cursorPos-1);
        }
    }

    var hasCurrencySymbol = val.startsWith('£');
    var formatted = accounting.formatMoney(val.substr(hasCurrencySymbol ? 1 : 0), options);
    if(formatted=="£0.00"){
        formatted=""                        // Empty the field instead of showing the problematic "£0.00"
    }

    cursorPos = GetCursorPos(this);         // Get previous cursor position
    this.value = formatted;                        // Return the value to field
    SetCursorPos(this,cursorPos+offset);    // Correct cursor position
});



It manages a calculated cursor offset onkeyup.
It also manages the backspace/delete «reverse offset».
Decimals can be inputted.

Bonus, it removes all non-digit characters instantly.
:D

Have a look at the final Fiddle here.

Louys Patrice Bessette
  • 33,375
  • 6
  • 36
  • 64
  • I would definitely go with this `blur` or `change` but I chose `input` event because I wanted it to change while typing and since the `input` event responds to `keyup`, `keydown`, `paste` events etc., and I strongly disagree with your statement **input is not an event**. You could check the **[answer here regarding input event](http://stackoverflow.com/a/17384341/2065039)** – Guruprasad J Rao Jun 15 '16 at 13:45
  • Well, your learn me something new! I was considering only the events listed [here](https://www.w3.org/TR/DOM-Level-3-Events/#event-types-list). – Louys Patrice Bessette Jun 15 '16 at 13:48
  • I agree, out of box, there are several events, which many aren't aware of.. :) – Guruprasad J Rao Jun 15 '16 at 13:49
  • But, the problem is with cursor position... If I understand well. I may have a small code chunk wich may interest. Hold on. – Louys Patrice Bessette Jun 15 '16 at 13:54
  • Yea, exactly.. cursor position here... Just post what you've.. I'll cross verify it. :) – Guruprasad J Rao Jun 15 '16 at 13:55
  • It comes from an external js I made a couple time ago that I use for field verifications... – Louys Patrice Bessette Jun 15 '16 at 14:06
  • I've tried your method, but unfortunately, **[it did not go with me well](https://jsfiddle.net/Guruprasad_Rao/4vj5rkzv/4/)**.. Anyways.. +1 for your effort.. – Guruprasad J Rao Jun 15 '16 at 17:36
  • It still has some issues buddy.. Like you type any number for ex `12547.34` and then press backspace completely untill you see `£0.00`. Let the cursor be where it is. Now type 2.45 or just move the cursor anywhere between long int and type `.` in between.. Its totally gone.. Anyways.. I'l have to go with `blur` or `change` since this getting complicated now.. :( I've accepted your answer and appreciate your effort.. :) – Guruprasad J Rao Jun 15 '16 at 18:36
  • Yeah! Another update on [your Fiddle](https://jsfiddle.net/Bes7weB/4vj5rkzv/7/) then : `if(this.value.length==1){offset=1;} else if(this.value=="£0.00"){offset=3;}else{offset=0;}` – Louys Patrice Bessette Jun 15 '16 at 18:41
  • Maybe there is some other user behavior cases... But you get the cursor offset logic! ;) – Louys Patrice Bessette Jun 15 '16 at 18:46
  • 1
    I have another fine update for you.... Since I wasn't satisfied of leaving you with "other user behavior cases" See [final updated fiddle](https://jsfiddle.net/Bes7weB/4vj5rkzv/8/) ;) I will edit my answer to make it straith to the right fix for the other readers. ;) – Louys Patrice Bessette Jun 15 '16 at 20:22
  • I appreciate your efforts buddy.. Hatsoff.. :) This update looks more precised and good. One issue am still seeing is as soon as you type more than 4 digits, it misplaces the cursor.. :( Other than that all is well.. :) – Guruprasad J Rao Jun 16 '16 at 13:22
  • You're right... I just updated to manage the added thousand comas. ;) – Louys Patrice Bessette Jun 16 '16 at 15:04
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/114856/discussion-between-louys-patrice-bessette-and-guruprasad-rao). – Louys Patrice Bessette Jun 16 '16 at 15:06
1

When you change an input value programmatically, you need to restore the cursor position as well, as the browser loses that information.

The simplest way would be to just restore the position that was in use before the change.

// pseudocode
.on('input', function () {
    var cursorPosition = this.selectionEnd;

    var val = this.value;
    var hasCurrencySymbol = val.startsWith('£');
    var formatted = accounting.formatMoney(val.substr(hasCurrencySymbol ? 1 : 0), options);

    this.value = formatted;
    this.setSelectionRange(cursorPosition, cursorPosition);
});

But probably the more desired is to put the cursor after the digit that was just inserted. The following code shifts the last remembered cursor position by the length difference between new and old input text, which is not ideal solution (especially for first inserted digit), but is a good starting point for further improvements.

.on('input', function () {
    var cursorPosition = this.selectionEnd;

    var val = this.value;
    var hasCurrencySymbol = val.startsWith('£');
    var formatted = accounting.formatMoney(val.substr(hasCurrencySymbol ? 1 : 0), options);
    var lengthDiff = formatted.length - val.length;

    this.value = formatted;
    this.setSelectionRange(cursorPosition + diff, cursorPosition + diff);
});

The code uses setSelectionRange which is supported by all popular browsers (including IE9+).

Rafael
  • 18,349
  • 5
  • 58
  • 67
  • Thanks for the answer.. But your 2nd method did not work for me well. However, the first method works good but the only problem is cursor is not placed in right position. May the currency symbol is the problem.. **[Here is the updated fiddle](https://jsfiddle.net/Guruprasad_Rao/4vj5rkzv/5/)**. As you type in you can see that it shifts one position back than it should be.. – Guruprasad J Rao Jun 15 '16 at 17:41