summaryrefslogtreecommitdiffstats
path: root/jquery.countable.js
blob: 9f9af2bb623eaa5cc9832a2e433223241c6327f6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
/*! Copyright (c) 2010 Brandon Aaron (http://brandonaaron.net)
 * Licensed under the MIT License (LICENSE.txt).
 *
 * Version 1.0
 *
 * Contributions by:
 *   - Neil Monroe (neil.monroe[at]gmail.com)
 */

(function($) {

$.fn.extend({
    countable: function(givenOptions) {
        return this.each(function() {
            var $this = $(this), interval, prev_char_diff, $el,
                options = $.extend({
                    threshold: .5,
                    appendMethod: 'insertAfter', // insertBefore || insertAfter || prependTo || appendTo
                    target: $this, // relative element with which to place the counter
                    startOpacity: .25,
                    maxLength: parseInt( $this.attr('maxlength'), 10 ) || 0,
                    maxClassName: 'maxed',
                    className: 'counter',
                    tagName: 'span',
                    interval: 750,
                    positiveCopy: 'You have {n} characters left.',
                    negativeCopy: 'You are {n} characters over.',
                    fadeDuration: 'normal',
                    defaultText: '' // text to disregard in the character count
                }, givenOptions);
            
            // create counter element
            $el = $('<'+options.tagName+'/>')
                .html( options.positiveCopy.replace('{n}', '<span class="num"></span>') )
                .addClass( options.className );
            
            // set initial opacity to 0 if opacity is supported
            if ( $.support.opacity ) $el.css({ opacity: 0 }); // don't set opacity for IE to avoid clear text issues.
            
            // sppend counter element to the DOM
            $el[options.appendMethod](options.target);
            
            // hook up events for the input/textarea being monitored
            $this
                .bind('keyup', check)
                .bind('focus blur', function(event) {
                    if ( event.type == 'blur' ) clearInterval( interval );
                    if ( event.type == 'focus' && !interval ) setInterval(check, options.interval);
                });
            
            // actual function to do the character counting and notification
            function check() {
                var val = $this.val(), 
                    length = (val == options.defaultText ? 0 : val.length), 
                    percentage_complete = length/options.maxLength,
                    char_diff = options.maxLength - length,
                    opacity;

                // return if we haven't made any progress
                if ( prev_char_diff != undefined && char_diff == prev_char_diff ) return;
                
                // if counter element is hidden and we are past the given threshold, show it
                if ( $el.is(':hidden') && percentage_complete >= options.threshold )
                    $el.show();
                // if the counter element is visible and we are now under the given threshold, hide it
                if ( $el.is(':visible') && percentage_complete < options.threshold )
                    $el.hide();
                
                if ( $.support.opacity ) { // don't set opacity for IE to avoid clear type issues.
                    // calculate the correct opacity
                    opacity = options.startOpacity + ((options.threshold - percentage_complete) * ((options.startOpacity * 2) - 2));
                    // animate to the correct opacity
                    $el.stop().fadeTo( options.fadeDuration, percentage_complete >= options.threshold ? opacity : 0 );
                }
                
                // set the correct copy if under or over the max number of characters
                if ( char_diff >= 0 ) {
                    if ( $el.is( '.'+options.maxClassName ) )
                        $el.html( options.positiveCopy.replace('{n}', '<span class="num"></span>') );
                } else {
                    if ( !$el.is( '.'+options.maxClassName ) )
                        $el.html( options.negativeCopy.replace('{n}', '<span class="num"></span>') );
                }
                
                // add or remove the max class name
                $el[ (char_diff < 0 ? 'add' : 'remove') + 'Class' ]( options.maxClassName );
                
                // set the number of characters left or number of characters over the limit
                $el.find('.num').text( Math.abs(char_diff) );
                
                // make sure the plural is necessary or not
                if ( char_diff == -1 || char_diff == 1 )
                    $el.html( $el.html().replace(/characters\b/, 'character') );
                else
                    $el.html( $el.html().replace(/character\b/, 'characters') );
                    
                prev_char_diff = char_diff;
            };
            // run an initial check
            check();
        });
    }
});

})(jQuery);