3
\$\begingroup\$

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.

\$\endgroup\$

2 Answers 2

2
\$\begingroup\$

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.

\$\endgroup\$
1
  • \$\begingroup\$ Wow, really great write up, about the second problem i stored all my html <div id="resource> .. all divs </div> and then clone a selected example, find() seems to work but is it optimized. \$\endgroup\$
    – Kivylius
    Commented May 26, 2013 at 23:27
0
\$\begingroup\$

Besides what Flambino wrote I have a small remark to (3): Why are you storing unnecessary information in the DOM? I would prefer a solution, where you use a simple variable to keep track of whether the button performs an action or not. So you wouldn't have to update the DOM twice for nada. The DOM is a bad place for storing the state of your application.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ I'm not sure if you read my whole question but I need the dom change as the class has styling associated with it. \$\endgroup\$
    – Kivylius
    Commented May 27, 2013 at 11:55
  • 1
    \$\begingroup\$ Oh, ok ;) I overlooked that. \$\endgroup\$ Commented May 27, 2013 at 12:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.