Search code examples
javascriptjquerydomtraversaljquery-traversing

How can I avoid traversing the same DOM route multiple times?


I have an on function that has two mouse events inside of it mouseenter and mouseleave. When these events are triggered they run different functions, one adds a class, the other removes it.

$(this).siblings('.testimonial').find('p').addClass('unseen');

$(this).siblings('.testimonial').find('p').removeClass('unseen');

The thing is, I’m doing the following DOM traversal twice:

$(this).siblings('.testimonial').find('p')

But I don’t see how I can save this traversal as a variable in one function and use it as another. Here is my full JS code:

JavaScript

(function ($) {

    var testimonial = $('.testimonial');
    var testimonialHeight = testimonial.outerHeight();
    var testimonialWidth = testimonial.outerWidth();

    testimonial.find('p').addClass('unseen');

    testimonial.css({
        height: testimonialHeight,
        width: testimonialWidth
    });

    $('.client').on({
        mouseenter: function() {
            $(this).siblings('.testimonial').find('p').removeClass('unseen');
        },
        mouseleave: function() {
            $(this).siblings('.testimonial').find('p').addClass('unseen');
        }
    });

})(jQuery);

HTML

<ul class="list-unstyled list-inline">
  <li>
    <div class="testimonial"><p>Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged.<p></div>
      <img class="client" src="https://s3.amazonaws.com/uifaces/faces/twitter/jsa/128.jpg" alt="" />
  </li>
  <li>
    <div class="testimonial"><p>Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged.</p></div>
    <img class="client" src="https://s3.amazonaws.com/uifaces/faces/twitter/gerrenlamson/128.jpg" alt="" />
  </li>
  <li>
    <div class="testimonial"><p>Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.</p></div>
    <img class="client" src="https://s3.amazonaws.com/uifaces/faces/twitter/jadlimcaco/128.jpg" alt="" />
  </li>
</ul>

Can anybody suggest a better way of doing this?

Thanks.


Solution

  • You can change to have a common event handler for both events and set the operation depending upon which event it was:

    $('.client').on("mouseenter mouseleave", function(e) {
        var method = e.type === "mouseenter" ? "removeClass" : "addClass";
        $(this).siblings('.testimonial').find('p')[method]('unseen');
    });
    

    Here's an explanation of what's going on:

    • .on("mouseenter mouseleave", function(e) {...}) hooks up multiple events to the same event handler.
    • e.type is the name of the event for the current event so when you have multiple events triggering the same event handler, you can see which event it was that triggered.
    • var method = e.type === "mouseenter" ? "removeClass" : "addClass" is like an if/else statement where it assigns either "removeClass" or "addClass" to the method variable based on the value of e.type. It's called the ternary operator.
    • obj[method] is a property reference using a variable for the name of the property instead of a string literal. So obj.addClass is the same as obj[method] when method is "addClass". Adding the () onto the end to make it a function call, then obj.addClass('unseen') is the same as obj[method]('unseen') when method is "addClass".

    So, to break that last line down again:

    // find the right paragraphs
    $(this).siblings('.testimonial').find('p')
    
    // get the property whose name is in the method variable
    $(this).siblings('.testimonial').find('p')[method]
    
    // call that property as a function and pass it 'unseen'
    $(this).siblings('.testimonial').find('p')[method]('unseen');
    

    One possible useful tool for DRY is .hover() because it is a shortcut for mouseenter and mouseleave. If you know that the relevant paragraphs are always marked as unseen before hover and no other code in the page ever messes with the unseen class (something you don't say anything about in your question), then you can use a shortcut using .hover()

    $('.client').hover(function() {
        $(this).siblings('.testimonial').find('p').toggleClass('unseen');
    });
    

    The more common way of just moving repeated code into a common function that you can use in both places would look like this:

    function getSiblingParagraphs(parent) {
        return $(parent).siblings('.testimonial').find('p');
    }
    
    $('.client').on({
        mouseenter: function() {
            getSiblingParagraphs(this).removeClass('unseen');
        },
        mouseleave: function() {
            getSiblingParagraphs(this).addClass('unseen');
        }
     });