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

Replacing simple jQuery methods for better use

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

Problem

There are a few common jQuery call I find my self calling when creating my app. I need some help and maybe a better way to do all this or rewrite it.
1) Singleton Selector

If I want to select only one class I would do it like this:

$("#tempo").on("click", ".entity", function(e){
    $(".selected").removeClass("selected");
    $(this).addClass('selected');
    // ....


2) Resource Selector

I have a div with all the resource elements (divs,ul) with formated code added class and even children). This is the code I write a lot to select it, clone it and append it.

// $R = $('div.resources'); - Create only once.
$R.find(".folder").clone()
    .addClass(id++)
    .appendTo("#folders");


3) Prevent Double Actions

I have quite a lot of ajax request to the server once a button gets clicked and have to disable the button from being double clicked each time.

$("button#new_game").on("click",function(e){
    if( $(this).hasClass("clicked") == false ){
        var $pre = $(this);
            $pre.addClass("clicked");
        
        createAjaxCall(function(result){
           $pre.removeClass("clicked");      
        }
    }
});


Adding the clicked class can allow me to customize the button a bit I should mention, So I can't really use .data()

These are the three main jQuery repetitive/non optimized problems I run into.

Solution

I'd say you're on the right track with all your code. The trick is probably to abstract some of the oft-repeated code into functions/plugins.

For the "singleton selector", wrapping your code in a plugin would seem the way to go. But note that your current code might need tweaking. You have this line:

$(".selected").removeClass("selected")


which will remove the selected class from elements anywhere on the page. In your example, you'd probably want only to remove the class from .entity elements within the #tempo element.

Here's a (very quick) sketch of a plugin, that should do what you want (and keep it limited to the container element):

$.fn.singleSelection = function(classSelector, handler) {
    return this.each(function() {
        var container = $(this);
        container.on("click", classSelector, function (event) {
            container.find(classSelector + ".selected").removeClass("selected");
            $(this).addClass("selected");
            if( typeof handler === "function" ) {
                handler.call(this, event);
            }
        });
    });
};


Your code can then be shortened to

$("#tempo").singleSelection(".entity"); // you can add a click handler too


Here's a demo.

But... jQueryUI already has this functionality, so if you feel like using that, go right ahead. It's certainly been tested better than what you see above :)

However, for such a simple thing, jQueryUI seems like overkill. But there are probably other, leaner plugins you can find, which'll do what you need - this is just an example.

For your second point, I'd probably wrap the code in an addFolder function somewhere. But it's a little hard to be more specific without knowing more about the context.

For your third point, you could again make a simple jQuery plugin that'd only forward events to your click handler if the element doesn't have the "clicked" class. There are also various "debounce" plugins out there to throttle clicks, but most simply impose a time limit rather than explicitly wait for an ajax operation to finish.

In any event, here's another (very quick) sketch of a jQuery plugin

$.fn.clickOnce = function(handler) {
    return this.each(function() {
        var element = $(this);
        element.on("click", function (event) {
            if( !element.hasClass("clicked") ) {
                element.addClass("clicked");
                if( typeof handler === "function" ) {
                    handler.call(this, event);
                }
            }
        });
    });
};


In your code you could then do:

$("button#new_game").clickOnce(function (event) {
    // this will only be called if the button wasn't already clicked
    var button = $(this);
    createAjaxCall(function (result) {
        // it's your responsibility to remove the "clicked" class
        button.removeClass("clicked");
    });   
});


Here's a demo

Again, there are no doubt ready-made plugins that do this and do it better.

By the way, I personally find it easier to pass jQuery's XHR objects around, as they act as deferred objects/promises. I.e.:

var xhr = $.ajax(...); // create an XHR obj from any of jQuery's ajax functions

xhr.done(function (data) {
  // success handler
});

xhr.fail(function (err) {
  // failure handler
});

xhr.always(function () {
  // complete handler
});


So if your createAjaxCall() function returns an xhr object, you can attach a handler to the always "event", and use that to clear the clicked class. That seems more robust (and readable) to me.

p.s. You could use a data-* attribute (set with .attr() instead of .data()), and still have custom styling with CSS like

button[data-clicked='true'] { // CSS attribute selectors are neat!
  ...
}


but browser support is limited compared to using simple classes.

Code Snippets

$(".selected").removeClass("selected")
$.fn.singleSelection = function(classSelector, handler) {
    return this.each(function() {
        var container = $(this);
        container.on("click", classSelector, function (event) {
            container.find(classSelector + ".selected").removeClass("selected");
            $(this).addClass("selected");
            if( typeof handler === "function" ) {
                handler.call(this, event);
            }
        });
    });
};
$("#tempo").singleSelection(".entity"); // you can add a click handler too
$.fn.clickOnce = function(handler) {
    return this.each(function() {
        var element = $(this);
        element.on("click", function (event) {
            if( !element.hasClass("clicked") ) {
                element.addClass("clicked");
                if( typeof handler === "function" ) {
                    handler.call(this, event);
                }
            }
        });
    });
};
$("button#new_game").clickOnce(function (event) {
    // this will only be called if the button wasn't already clicked
    var button = $(this);
    createAjaxCall(function (result) {
        // it's your responsibility to remove the "clicked" class
        button.removeClass("clicked");
    });   
});

Context

StackExchange Code Review Q#26640, answer score: 2

Revisions (0)

No revisions yet.