HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavascriptMinor

Character and word count functionality

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
characterwordandcountfunctionality

Problem

I have this JS that has a lot of redundant functionality, and it works but looks unseemly.

I would love some assistance with making this more DRY (Don't Repeat Yourself):

// This provides character & word counter functionality

counter = function() {
    var body_value = $('#post_body').val();
    var title_value = $('#post_title').val();       

    if (body_value.length == 0) {
        $('#wordCountBody').html(0);
        $('#totalCharsBody').html(0);
        return;
    }

    if (title_value.length == 0) {
        $('#wordCountTitle').html(0);
        return;
    }

    var regex = /\s+/gi;
    var wordCountBody = body_value.trim().replace(regex, ' ').split(' ').length;
    var totalCharsBody = body_value.length;
    var wordCountTitle = title_value.trim().replace(regex, ' ').split(' ').length;

    $('#wordCountBody').html(wordCountBody);
    $('#totalCharsBody').html(totalCharsBody);
    $('#wordCountTitle').html(wordCountTitle);
};

$(document).ready(function() {
    $('#count').click(counter);
    $('#post_body').change(counter);
    $('#post_body').keydown(counter);
    $('#post_body').keypress(counter);
    $('#post_body').keyup(counter);
    $('#post_body').blur(counter);
    $('#post_body').focus(counter);
    $('#post_title').change(counter);
    $('#post_title').keydown(counter);
    $('#post_title').keypress(counter);
    $('#post_title').keyup(counter);
    $('#post_title').blur(counter);
    $('#post_title').focus(counter);        
});

Solution

Your code looks mostly fine—if you're just performing this specific functionality in one place, some repetition is okay—but there's one thing that could definitely be simplified. Your ready function could be reduced to simply this:

$(document).ready(function () {
    $('#count').click(counter);
    $('#post_body, #post_title').on('change keydown keypress keyup blur focus', counter);
});


If, however, you really wanted to reduce all repetition, you could generalize the counter function until you ended up with a fairly reusable component.

Your counter function currently performs two actions (word counting and character counting) on two separate elements (the body and the title). To make the code as DRY as possible, you should employ the single responsibility principle to break up the function into composable bits.

function countUpdater(element, transformations) {
    return function () {
        var value = element.val();
        transformations.forEach(function (transformation) {
            transformation.display.html(value.length === 0 ? 0 : transformation.counter(value));
        });
    }
}

function wordCounter(value) {
    return value.trim().replace(/\s+/g, ' ').split(' ').length;
}

function characterCounter(value) {
    return value.length;
}


Your event handler code would then become this:

$(document).ready(function () {
    var changeEvents = 'change keydown keypress keyup blur focus';

    var bodyUpdater = countUpdater($('#post_body'), [
        { display: $('#wordCountBody'), counter: wordCounter },
        { display: $('#totalCharsBody'), counter: characterCounter },
    ]);
    var titleUpdater = countUpdater($('#post_title'), [
        { display: $('#wordCountTitle'), counter: wordCounter },
    ]);

    $('#count').click(function () {
        bodyUpdater();
        titleUpdater();
    });
    $('#post_body').on(changeEvents, bodyUpdater);
    $('#post_title').on(changeEvents, titleUpdater);
});


Is that better? Worse? Well, it's certainly less clear, but it's also much more reusable. If you're going to use this type of functionality many times, it might be worth it, but otherwise, I might prefer the simpler, more explicit version.

Code Snippets

$(document).ready(function () {
    $('#count').click(counter);
    $('#post_body, #post_title').on('change keydown keypress keyup blur focus', counter);
});
function countUpdater(element, transformations) {
    return function () {
        var value = element.val();
        transformations.forEach(function (transformation) {
            transformation.display.html(value.length === 0 ? 0 : transformation.counter(value));
        });
    }
}

function wordCounter(value) {
    return value.trim().replace(/\s+/g, ' ').split(' ').length;
}

function characterCounter(value) {
    return value.length;
}
$(document).ready(function () {
    var changeEvents = 'change keydown keypress keyup blur focus';

    var bodyUpdater = countUpdater($('#post_body'), [
        { display: $('#wordCountBody'), counter: wordCounter },
        { display: $('#totalCharsBody'), counter: characterCounter },
    ]);
    var titleUpdater = countUpdater($('#post_title'), [
        { display: $('#wordCountTitle'), counter: wordCounter },
    ]);

    $('#count').click(function () {
        bodyUpdater();
        titleUpdater();
    });
    $('#post_body').on(changeEvents, bodyUpdater);
    $('#post_title').on(changeEvents, titleUpdater);
});

Context

StackExchange Code Review Q#63184, answer score: 5

Revisions (0)

No revisions yet.