We help IT Professionals succeed at work.

How to properly bubble events to create new window/tab

Dante Gagliardi
on
560 Views
Last Modified: 2014-05-12
The setup:

My page consists of elements (anchors) that need to make an AJAX call when they are clicked. However, when the elements are left-clicked, the page loads and interrupts the AJAX call. I do not have the ability to modify this AJAX call, and so I cannot just make it synchronous.

To fix this problem, I created an event handler that would capture click events, and use another method that would load the target page after completion of the AJAX call. This also required that I do not bubble the event, lest I interrupt the AJAX call again. However, this broke the ability to middle click/ctrl click/shift click and open the page in a new tab/window.

To fix that problem, I modified the event handler to act on the mouseup event (because Firefox triggers mouseup on middle click). If it is a mouseup event from the left mouse button, with no modifier keys, I use the above mentioned method to load the target page after the AJAX call completes. If the event has a modifier key, or is a middle click, I do nothing and allow the event to bubble (if the target is opened in a new tab/window, the AJAX call will continue processing just fine).

Unfortunately, that gets me here. What is happening now, is the mouseup event triggers one page load, while click event fires immediately afterwards (which I no longer handle) that appears to be firing a second page load. This is doubling my server overhead for these links.

This handler is also bound to the keypress event, so we can capture <Enter> keypresses.

Below is the Handler I am using (any syntax errors are likely from simplifying the handler code):
// This handler is bound to the 'mouseup' and 'keypress' events
function (e){
    // Ignore anything that isn't left click, middle click, or <Enter>
    if(e.which != 1 && e.which != 2 && e.which != 13){
        return true;
    }
    AJAX_CALL();
    // Bubble on modifier keys, or middle click
    if(e.metaKey || e.ctrlKey || e.shiftKey || e.which == 2){
        return true;
    }
    // Else, make sure this element has an href, and follow it with our custom method
    else if(this.attr('href') != '#' && this.attr('href') !== undefined){
        goToUrl(this.attr('href'),false);
        return false;
    }
}

Open in new window


What I'm looking for:

The problem boils down to this: I need to be able to issue an asynchronous AJAX call that isn't interrupted, but the handler that does this must retain default browser behavior for opening new tabs/windows. This solution needs to work on Chrome/Safari/Firefox/IE8+, and preferably also on mobile devices (though mobile isn't a strict requirement).

Ideally, I would like a pure client-side solution (using JS and jQuery 1.9.1).

My investigation thus-far suggests that this isn't a trivial problem to solve on a cross-browser basis. The functionality I am trying to create is important enough that a pre-existing library capable of doing this is not out of the question, but it is not preferred.

I've run up against a wall on this one. Any help would be greatly appreciated.
Comment
Watch Question

CERTIFIED EXPERT
Expert of the Year 2014
Top Expert 2014

Commented:
Not sure if I am following you but

$(".somelink").click(function(e){
        e.preventDefault() // stop default action
        $.ajax({
            ...,
            success: function () {
                window.open($(this).prop("href"));
               }
        });
})
Dante GagliardiDeveloper

Author

Commented:
Gary,

The problem with that, is that I don't want to prevent the default action, I merely want to delay it if the action will cause a page load in the current tab. If the action would result in a new tab/window, I can let the default continue without pause. Unfortunately, I don't have control over the nature of the AJAX call (otherwise I could just make it synchronous), preventing me from adding a success function.
CERTIFIED EXPERT
Expert of the Year 2014
Top Expert 2014

Commented:
That will delay it until the ajax has finished.
You can amend it slightly if the target is _blank e.g.

$(".somelink").click(function(e){
	var attr = $(this).attr("target");
	if (typeof attr !== 'undefined' && attr !== false && attr!="_blank") {
		// either no target attribute or target is anything other than _blank
		e.preventDefault() // stop default action until ajax is finished
	}

        $.ajax({
            ...,
            success: function () {
                window.location = $(this).prop("href"); // change current location
               }
        });
        // original link will be executed if target is _blank even if the 
        // ajax call is still being executed.
}) 

Open in new window


If you give some real code I will probably understand you better as I am assuming these are links.
Dante GagliardiDeveloper

Author

Commented:
The handler is being bound to a simple anchor element. Your approach has two main problems:

One, I cannot modify the AJAX call in any way, so I cannot include a success function. If I could modify the AJAX call, it would be a simple matter of setting {async:false}. This is the purpose of the goToUrl(...) function, which provides the proper delay.

Two, this does not capture middle mouse click in Firefox. To do that, the handler must also fire on 'mouseup'. However, firing on 'mouseup' and 'click' causes the browser to follow the href of the anchor twice for some user events.

I'm not having an issue with making the async call, or with delaying the browser during the async call. My issue is that I am trying to retain the browser's default behavior during all of this.

Thank you for helping, btw. Hopefully we can figure this out.
CERTIFIED EXPERT
Expert of the Year 2014
Top Expert 2014

Commented:
Use mousedown to capture left,middle and right click, not mouseup.

If you cannot modify the ajax call then I am having a hard time understanding how you can manipulate it.
How is the ajax call being bound to the link?
The only way would be to bind on the click and in the event call make a call to the ajax code so it returns back to your click function.
Dante GagliardiDeveloper

Author

Commented:
The handler is the JS anonymous function provided above, bound by using jQuery's .on() function. The goToUrl(...) does the magic that delays the browser long enough for the AJAX call to complete.

I cannot use the goToUrl(...) function for every call, because there is a noticeable delay during the call, and this would be a serious disruption to the user experience. With that, I use it as sparingly as possible.

I can try the use of 'mousedown' and get back to you, but I suspect that I will still have the issue of the 'click' event being fired immediately afterwards, still leaving me with two page loads. I'll be back in a couple hours with results.
Developer
Commented:
This one is on us!
(Get your first solution completely free - no credit card required)
UNLOCK SOLUTION
Dante GagliardiDeveloper

Author

Commented:
I ended up arriving at the solution independent of expert help. While appreciated, the help provided was not targeting the issue, and ignored key parts of the question.
Unlock the solution to this question.
Join our community and discover your potential

Experts Exchange is the only place where you can interact directly with leading experts in the technology field. Become a member today and access the collective knowledge of thousands of technology experts.

*This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

OR

Please enter a first name

Please enter a last name

8+ characters (letters, numbers, and a symbol)

By clicking, you agree to the Terms of Use and Privacy Policy.