1

This may be a stupid question. I know I am a little green. I was set with a task of modifying this old, old system's navigation. There are two nav bars. The second has only search buttons. I was asked to remove the second nav bar, and replace it with a drop down that shows the search functions. I am restricted on what I can change due to the age of this system. There are no restrictions on the JS I can write. They are running jQuery 1.11.1, on an Adobe ColdFusion system (two months ago they upgraded from 1.3.2)

First: when the target is clicked, both the mouseenter and the click event trigger. The mouseenter fires first. This causes a problem on a desktop that is visible to the keen viewer, but on mobile, this creates a horriable usability issue. A: From my understanding mouse events do not happen on a mobile device but do for me. And B: since the mouseenter event runs first, it activates the closeDropDown function before the click event is processed. With the closeDropDown running, its .on('click', f(...eventstuff...)) hears the open click that is intended to trigger the openDropDown function, thus the drop down does not open.

Here are the functions. The console.logs are for checking what runs when.

function openDropDown(){
    $('div.dropdown').parent().on('click.open mouseenter', function(event){

      $subject = $(this).find('.dropdown-menu')
      // console.log(event.type, $subject, "first o");

       if(!$subject.is(":visible")){
          // console.log($subject, 'second o');

          $subject.show()
       }else {
         if(event.type == 'click'){
           // console.log('third o');

            $subject.toggle()
        }
    }

    closeDropDown($subject)
    // console.log('open complete');
 })
}


function closeDropDown($x){
    // console.log('first c');

    $(document).on("click.close",function(e){
        // console.log("second c", e.type, "this type");

        if(!$(e.target).closest(".dropdown-menu").parent().length){
            // console.log("third c");

            if($x.is(":visible")){
              // console.log('forth c');
              $x.hide()
            }
        }

        $(document).off("click.close")
        // console.log('complete close');
    })
}

openDropDown()
onSearchClick()

I have read a few posts hoping for some help (like this and that

Over all, I know I need to condense my code. I understand a few ways to fix this (add an if(... are we on a mobile device...) or some counter/check that prevents the closeDropDown from running when the dropdown is closed)

I really want to understand the fundamentals of event listeners and why one runs before the other stuff.

Although suggestions on how to fix this are great, I am looking to understand the fundamentals of what I am doing wrong. Any fundamental pointers are very helpful.

Of note: I just read this: .is(':visible') not working. I will be rewriting the code with out the .is('visible').

Other things that might help: This is the Chrome Dev Tools console when all my console.log(s) are active. First, click after page load.... enter image description here Drop down opens and quickly closes.

Second click.... enter image description here

Thanks! All your help is appreciated!

davidhartman00
  • 353
  • 4
  • 14

1 Answers1

2

This is a pretty broad question. I'll try to be terse. I don't think ColdFusion should be tagged here, because it seems like it only has to do with HTML/CSS/JS.

Configuring Events

First, I'd like to address the way you have your script configured. You'd probably benefit from looking at the event handling examples from jquery.

Most people will create events like the following. It just says that on a click for any document element with the ID of "alerter", run the alert function.

// Method 1
$(document).on(click, "#alerter", function(event){
   alert("Hi!");
});

OR

// Method 2 
$(document).on("click", "#alerter", ClickAlerter); 

function ClickAlerter(event) {
    alert("Hi!"); 
}

Both methods are totally valid. However, it is my opinion that the second method is more readable and maintainable. It separates event delegation from logic.

For your code, I would highly recommend removing the mixing of event assignment and logic. (It removes at least one layer of nesting).

Incidentally, your event listeners don't appear to be configured correctly. See the correct syntax and this example from jQuery.

$( "#dataTable tbody" ).on( "click", "tr", function() {
    console.log( $( this ).text() );
});

Regarding Multiple Events

If you have multiple event listeners on an object, then they will be fired in the order which they are registered. This SO question already covers this and provides an example.

However, this doesn't mean that a click will occur before a mouseenter. Because your mouse has to literally enter the element to be able to click it, the event for mouseenter is going to be fired first. In other words, you have at least 2 factors at play when thinking about the order of events.

  1. The order in which the browser will fire the events
  2. The order in which they were registered

Because of this, there isn't really such a thing as "simultaneous" events, per se. Events are fired when the browser wants to fire them, and they will go through events and fire the matches in the order that you assigned them.

You always have the option of preventDefault and stopPropagation on these kinds of events if you want to alter the default event behavior. That will stop the browser's default action, and prevent the event from bubbling up to parent elements, respectively.

Regarding Mobile Mouse Events

Mouse events absolutely happen on mobile devices, and it's not safe to assume they don't. This article covers in great depth the scope of events that get fired. To quote:

"[Y]ou have to be careful when designing more advanced touch interactions: when the user uses a mouse it will respond via a click event, but when the user touches the screen both touch and click events will occur. For a single click the order of events is:

touchstart
touchmove
touchend
mouseover
mousemove
mousedown
mouseup
click

I think you would benefit from reading that article. It covers common problems and concepts regarding events in mobile and non-mobile environments. Again, a relevant statement about your situation:

Interestingly enough, though, the CSS :hover pseudoclass CAN be triggered by touch interfaces in some cases - tapping an element makes it :active while the finger is down, and it also acquires the :hover state. (With Internet Explorer, the :hover is only in effect while the user’s finger is down - other browsers keep the :hover in effect until the next tap or mouse move.)

An Example

I took all these concepts and made an example on jsFiddle to show you some of these things in action. Basically, I'm detecting whether the user is using a touchscreen by listening for the touchstart event and handling the click differently in that case. Because I don't have your HTML, I had to make a primitive interface. These are the directives:

  1. We need to determine if the user has a touchscreen
  2. When the user hovers over the button, the menu should appear
  3. On a mobile device, when a user taps the button, the menu should appear
  4. We need to close the menu when the user clicks outside of the button
  5. Leaving the button should close the menu (mobile or otherwise)

As you will see, I created all my events in one place:

$(document).on("mouseover", "#open", $app.mouseOver);
$(document).on("mouseout", "#open", $app.mouseOut);
$(document).on("click", "#open", $app.click);
$(document).on("touchstart", $app.handleTouch);
$(document).on("touchstart", "#open", $app.click);

I also created an object to wrap all the logic in, $app which gives us greater flexibility and readability down the road. Here's a fragment of it:

var $app = $app || {}; 
$app = {

  hasTouchScreen: false,

  handleTouch:function(e){
    // fires on the touchstart event
    $app.hasTouchScreen = true;
    $("#hasTouchScreen").html("true");
    $(document).off("touchstart", $app.handleTouch);     
  }, 

  click: function(e) {
    // fires when a click event occurrs on the button
    if ($app.hasTouchScreen) {
        e.stopPropagation();
        e.preventDefault();
        return;
    }

    // since we don't have a touchscreen, close on click.
    $app.toggleMenu(true);
  }, 

  touch: function(e) {
   // fires when a touchstart event occurs on the button
    if ($("#menu").hasClass("showing")) {
       $app.toggleMenu(true);
    } else {
       $app.toggleMenu();
    }    
  } 

};
luxdvie
  • 902
  • 8
  • 16