10 Ways to Reinvent the Wheels on jQuery and JavaScript

21 November 2012
By Oleg Poludnenko, Senior Developer

Once again, opening my peers’ code I’ve terrified and decided to write this article. I hope for someone that would be useful at the same time and I find it easier to explain to newcomers that they have in the code is not just throwing a link to this article.

Of course the number of these things are very, very large, so the article is limited to a few.

Constants

This problem concerns no only javascript but programming in general. Let’s consider an example:

$elem.on('keydown', function(e) {
    if (e.keyCode == 27) {
        //...
    }
});

What is the magic number 27? People who are often faced with the codes immediately say – this is the key ESC. But most developers, especially beginners, do not remember the codes, and faced with the codes have to once again get into the search engine and wasting time.

You can of course add a comment in the code that is handling pressing ESC, but much more effective it would be to introduce a constant, for example, KEY_ESC = 27.

Getting of the identifier

Often you need to get the ID of the element (comment, post, user, etc.) to perform some action (for example, to assess the comments with ajax). And you can often find this approach:

var id = $(this).attr('id').substring(8);

As in the previous example, the developer has to wonder – what is that number 8. He has to climb in the html code, etc.

There are worse examples (pattern copied from a real project):

var last_id = $('#answer_pid' + id + ' li:first div')     
    .attr('id').substr(7);

As the result the slightest change in layout will lead to change js code.

Sometimes we can see something like this:

<div class="comment" id="comment_123"></div>
 
var id = $(this).attr('id').substring("comment_".length);

Even better (or at least no hardcoded numbers), but still, this approach too strongly attached js code to html.

In my opinion much better to use data-* options, such as:

<div class="comment" data-id="123"></div>

then get the ID will be very simple:

var id = $(this).attr('data-id');

or

var id = $(this).data('id');

About the differences in the data and attr methods there is a set of articles.

Event handlers to multiple items

It is often necessary to add event handlers to page elements (such as a button “delete message”). And often you can find a similar approach:

$('.comment a.delete').click(function(){
    //
});

And we have a problem: how to add the same handler to the new item (such as a dynamically loaded comment). And here I saw a lot of decisions, including the redefinition of all the handlers again (often by copy-paste content of the handlers):

$('.comment a.delete').unbind('click').click(function() {
    //
});

Solution: jQuery 1.7 has a method on, which binds handlers, filtering elements by selector. Example:

$('body').on('click', 'a.external', function(e) {
    // Function will be called when you click on any link
    // with a class external
});

It is important that the handler works for dynamically created objects.

It should also be noted that this approach should be used wisely. For example the following code can result in poor performance and freezes the browser:

$('body').on('mousemove', selector, function() {
    //
});

Namespaced events

Despite the fact that the namespaced events were added in jQuery 1.2 – few people enjoy them (I think most people just do not know about them). Consider this example:

$('a').on('click', function() {
    // handler 1
});
$('a').on('click', function() {
    // handler 2
});

Now, suppose that we need to remove the second handler of reference. But then the bad luck – $(‘a’).off(‘click’) will remove both the handlers. To the aid come namespaced events. Let’s rewrite the code above:

$('a').on('click.namespace1', function() {
    // handle 1
});
$('a').on('click.namespace2', function() {
    // handle 2
});

Now it is possible to remove the second handler by calling $(‘a’).off(‘click.namespace2’);

More about namespaced events can be found here: docs.jquery.com/Namespaced_Events.

Write as you say it out loud

Your task is to show other programmers what you do, not how you do it.

Before writing the next portion of code, think and speak out loud or to yourself that you are going to do. For example: “We need to clean the item”. Many people do that not very well:

$('.info').html('')

because in the past they’ve used the convenient phrase .innerHTML = “”. And above the preferred option:

$('.info').empty()

These programmers often clean item before placing a new information in it:

$('.info').html('').html('<b>Ok</b>')

But do not forget – you write on jQuery! And he cares about you, and .html() will clear itself before inserting a new element value.

And it will make it more wisely than to do it through .innerHTML. The fact that within the cleaning element can hold such items for which you have previously hung event handlers or bind data with .data(). jQuery will clean it and no memory leaks will appear.

By the way, sometimes in order to hide this information, it is better not just clean an item, but remove it at all:

$('.info').remove()

Furthermore… I think when you write like so:

$('#history').css('display', 'none');
$('#description').css('display', 'none');
$('#category').css('display', 'none');
$('#contact').css('display', 'none');

you say “Hide history, hide the description, hide categories, hide contacts.” Most likely, you said: “Hide history, description, category, and contacts.” So write it:

$('#history, #description, #category, #contact').hide();

and do not forget that jQuery loves you, and there are methods to hide – .hide(), and to show – .show() – elements.

mouseenter/mouseleave vs. mouseover/mouseout

You may have noticed trouble: sometimes when you hang a pair of events on the element mouseover / mouseout to display tooltip, this tooltip is flashing. And it happens because of the fact that inside the element to which you hung handlers, there is another element. If you hover the mouse cursor over it for your browser generates an external element event mouseout, and for internal – mouseover, and then again to the outside – mouseover, which leads to distortion.

But jQuery loves us and offers us another couple of events – mouseenter / mouseleave, which solve this problem as follows. For the transmitted handlers a certain wrapper is made. At a time when the cursor moves to the inner element mouseout event is generated for the external element. Skeptical wrapper function checks the event.relatedTarget – the element which is hovered – and if it is located within the outer element, does not cause transmitted handler mouseleave. And no flicker.

Do not forget about the function .hover(handlerIn, handlerOut), which takes two handlers and handles them as mouseenter and mouseleave:

$(selector).hover(function() {
    tooltip.show()
}, function() {
    tolltip.hide()
})

Note, that starting with version 1.8 .hover() function is deprecated.

$.parent() vs. $.closest()

Often I see something like this:

var person = $('.name').parent().parent().parent();

It is clear that here is an attempt to get to the needed parent, which has important information, or has another item you want. And what if while you were on vacation, your $(‘.name’) nestles in a different place, but under all the same person? Many craftsmen cyclically cause .parent() to the desired item. More experienced know that there is .parentsUntil(selector), which will return all parents to a specified (excluding itself). But still cumbersome decision:

var person = $('.name').parentsUntil('.person').last().parent();

But there’s an obvious way:

var person = $('.name').closest('.person');

If we remember that we write code the way we put it into words, then this option is more suitable because of its transparency and brevity.

$.ajax() – less is better!

As you know – there is a method in jquery to work with ajax – $.ajax. There are some shorthand functions of this method such as $.get, $.load, $.post, etc. These features have been added specifically to alleviate some of your actions (upload script, json, perform post request), but in the implementation of all of these methods refer to the $.ajax.

Personally, I never use a shorthand function, and here’s why.

In the code of beginners or inexperienced developers we can see several different stages.

1. Initial:

$.post(url, data, function(data) {
    data = $.parseJSON(data);
    // ...
});

2. Added try-catch block:

$.post(url, data, function(data) {
    try {
        data = $.parseJSON(data);
    } catch (e) {
        return;
    }
    // ...
});

3. Learn from the documentation that to the $.post() method could be passed dataType as last parameter (which lost in the abyss of the code, if success function does not fit the screen):

$.post(url, data, function(data) {
    // ...
}, 'json');

Very few web developers add handlers for the error situations. This is mainly due to laziness or unwillingness to spend the extra 5 minutes of time, or the developers just believe that mistakes will never happen. If the developer decided to add an error handler, you get something like:

$.post(url, data, function(data) {
    // ...
}, 'json').error(function() {
   // ...
});

In my opinion – it’s awful unreadable. Also writing each time the error handler – it tiresome. Therefore, you can set the default error handler for all ajax requests, such as:

$.ajaxSetup({
    error: function() {
        // ...
    }
});

You can define any other repeated ajax parameters:

$.ajaxSetup({
    dataType: "json",
    data: {
        user_id: userId
    },
    error: function() {
        // ...
    }
});

Now we do not have to specify every time these parameters in queries:

$.ajax({
    data: {
        product_id: productId
    },
    success: function(json) {
        console.log(json)
    },
    alert: "Loading..." // For what? See below...
})

$.ajax() – notify users

And now we can give the user a message when AJAX-requests? I’m sure you thought about it, but avoided due to the fact that we must often write the same thing.

For a simple example will show messages like this:

<div id="popup" class="centered-bold-red"></div>

And link it to our requests:

$('#popup').ajaxSend(function(event, xhr, options) {
    $(this).text(options.alert || 'Please wait... ').show()
}).ajaxStop(function() {
    $(this).fadeOut('fast')
})

.ajaxSend() calls the handler every time there is an AJAX-request. Here we derive the transmitted (see above) a message or default one.

.ajaxStop () is called at the end, after all the AJAX-requests worked, i.e. if you have a parallel processed several AJAX-requests, the handler is invoked only once – after the last operation.

$.ajax() – Alert the code

Now let’s look at our example above from the other side. Suppose one person is the developer of a composite widget, which displays information about the current events on the page. And the other person develops very business logic and implements AJAX-requests. Obviously, the need in the widget could arise after it had been written a lot of AJAX-requests. If we love our code and our future colleagues as well as jQuery do, then we would have to predict the need of other code in notification of the completion of any of our AJAX-actions. And would do so:

$.ajax({
    data: {
        action: 'search-name',
        name: $('#name').val()
    },
    beforeSend: function() {
        $(searchForm).trigger('start.search')
    },
    complete: function() {
        $(searchForm).trigger('finish.search')
    }
})

Now we can just tell (e.g. in the comments) that anyone can subscribe to the events start.search and finish.search:

$(searchForm)
    .on('start.search', function() {
        // Show preloader
    })
    .on('finish.search', function() {
        // Hide preloader
    })

Instead of a conclusion

This is only a small part of the problems that I encounter on a regular basis in a foreign code. I hope that this post will help to improve the quality of the code.

Tags: ,


Add Comment

Name Mail Website Comment