Count characters/sms using jQuery

Firstly, character counting is very easy. You just need to use the length property on a string. To count the number of SMS messages needed, you'll need to divide by 160 and round up (because 161 characters requires 2 messages). Your code should probably look something like this:

HTML:

<textarea name="message" value="" id="message"></textarea>
<p>
    <span id="remaining">160 characters remaining</span>
    <span id="messages">1 message(s)</span>
</p>

jQuery:

$(document).ready(function(){
    var $remaining = $('#remaining'),
        $messages = $remaining.next();

    $('#message').keyup(function(){
        var chars = this.value.length,
            messages = Math.ceil(chars / 160),
            remaining = messages * 160 - (chars % (messages * 160) || messages * 160);

        $remaining.text(remaining + ' characters remaining');
        $messages.text(messages + ' message(s)');
    });
});

See jsFiddle example.


Beware that SMS is more complicated than you seem to indicate.

Standard "160-character" SMS uses a strange 7-bit encoding that covers most ASCII, some European accents, misc symbols such as €, some capital Greek letters (the ones that don't look like Roman characters).

If your message uses other characters, it can instead be encoded as Unicode using UCS-2 (like UTF-16 but only BMP), for a limit of 70 characters. Actually, the specification says UCS-2, but emoji (non-BMP Unicode) can be sent in SMS so that implies UTF-16 is being used, in which case each emoji must "use up" 2 characters out of the 70 total.

But some languages can use "national language shift tables" which use an alternative 7-bit encoding. Most notably Turkish, but also Spanish, Portuguese, and ten Indian sub-continent languages. These are relatively new additions to the standard.

Even in the 7-bit encoding, a few characters are "escaped" which means they "use up" 2 characters. In the default 7-bit encoding, these are: {}[]\|^~€.

If your message goes beyond 160 characters, it can use "concatenated SMS", but then a small header is added to each message, meaning each segment only has room for something like 153 characters. There are two different versions of that header, of different sizes, thus it may not be 153 characters, but 152 (from memory). Similarly for Unicode concatenated SMS, the small header makes it 67 characters per segment.

Good luck with all that!


Once the message has been decoded as Craig McQueen stated in a this thread post and you have your actual character count, to count the needed SMS amount the following is enough:

function cntsms(len){ return len<=0? 0: (len>160? Math.ceil(len/153): 1); }

...I like one-row solutions so much.


Here is small plugin for you. It is my first jQuery plugin i give it for free ;) you just need to start it with:

$('#smsText').smsArea();

The HTML:

 <b id="smsCount"></b> SMS (<b id="smsLength"></b>) Characters left
 <textarea id="smsText"></textarea>

The Javascript (updated 18.8.2014):

(function($){
    $.fn.smsArea = function(options){

    var
    e = this,
    cutStrLength = 0,

    s = $.extend({

        cut: true,
        maxSmsNum: 3,
        interval: 400,

        counters: {
            message: $('#smsCount'),
            character: $('#smsLength')
        },

        lengths: {
            ascii: [160, 306, 459],
            unicode: [70, 134, 201]
        }
    }, options);


    e.keyup(function(){

        clearTimeout(this.timeout);
        this.timeout = setTimeout(function(){

            var
            smsType,
            smsLength = 0,
            smsCount = -1,
            charsLeft = 0,
            text = e.val(),
            isUnicode = false;

            for(var charPos = 0; charPos < text.length; charPos++){
                switch(text[charPos]){
                    case "\n": 
                    case "[":
                    case "]":
                    case "\\":
                    case "^":
                    case "{":
                    case "}":
                    case "|":
                    case "€":
                        smsLength += 2;
                    break;

                    default:
                        smsLength += 1;
                }


                if(text.charCodeAt(charPos) > 127 && text[charPos] != "€") isUnicode = true;
            }

            if(isUnicode){
                smsType = s.lengths.unicode;

            }else{
                smsType = s.lengths.ascii;
            }

            for(var sCount = 0; sCount < s.maxSmsNum; sCount++){

                cutStrLength = smsType[sCount];
                if(smsLength <= smsType[sCount]){

                    smsCount = sCount + 1;
                    charsLeft = smsType[sCount] - smsLength;
                    break
                }
            }

            if(s.cut) e.val(text.substring(0, cutStrLength));
            smsCount == -1 && (smsCount = s.maxSmsNum, charsLeft = 0);

            s.counters.message.html(smsCount);
            s.counters.character.html(charsLeft);

        }, s.interval)
    }).keyup()
}}(jQuery));

DEMO: http://jsfiddle.net/t32h0gj4/1/

NOTE: there are some basic options

$('#smsText').smsArea({cut:false}); //Do not cut the SMS
$('#smsText').smsArea({maxSmsNum:2}); //2 SMS Max