JQuery events and a possible scoping error on my part?

bsenftner - July 3, 2009 - 21:44

Just learning Java Script & JQuery, but an experienced developer, I've created a page with a series of elements that use the .slideDown() and .slideUp() functions to reveal and hide the different areas of content on the page.

To facilitate this, the page's html uses links with id pairs similar to: { "dataRevealLink1", "dataToReveal1" }, { "dataHideLink1", "dataToHide1" }. Clicking on a "data-reveal-link" causes a .slideDown() animation on "data to reveal", with appropriate reverse logic attached for the data hiding as well.

The page in question has 12 such sections, and writing the java script long form (no loops) works fine. However, I would like to generalize this logic, but my first attempt at a loop to attach the DOM element callbacks does not work, and I suspect it's a scoping issue with java script:

     for (var i = 1; i <= 12; i++) {
         var eventElem = "#dataRevealLink" + i;
         var actOnElem = "#dataToReveal" + i;
        
         $(eventElem).click( function() {
                            $(actOnElem).slideDown();
                            return false;           // prevents the link from processing this click
          });
     }

I suspect that in the above logic, the mouse-click handler is attaching to the dataRevealLink1-12 fine, but once inside the handler, "actOnElem" is not what I'm expecting - possibly due to the scope of actOnElem? I'm not entirely sure...

I've added console.log() calls to the above logic, and everything is as I expect during the above looping, but I am never getting any of the callbacks to run, so I can never see any console.log() output from inside a mouse-click handler...

Any advice anyone?

Could you show an example of

JoepH - July 3, 2009 - 23:13

Could you show an example of the page?

-----------------------------------------
Joep
CompuBase, Drupal, websites and webdesign

I'm creating the site on my

bsenftner - July 3, 2009 - 23:54

I'm creating the site on my local machine, so it's not yet online visible. However, I've created a zip file with the key elements and so on:

  • the html from the drupal page's editor
  • two jpegs, one of the page as loaded and one jpeg after a content section is revealed
  • the key java script / jquery logic, both the working and non-working versions are side by side with the non-working one commented out.

The zip file containing this can be downloaded here:
http://www.popdigitaltech.com/scopeIssue/scopeIssue.zip

The tag ids are not exactly the same as I described in my first post. I described them for clarity, but in my actual code I use "OS_lh" for an element-link-that-reveals, "OS_ld" for an element that gets revealed, and "OS_ldh" for an element-link-that-hides.

However, I feel like I need to point out that you shouldn't need to see the zipped files. The issue is in that little loop I posted at the start of this thread. The key question is if the "actOnElem" variable out of scope when the callback fires?

The basic problem is by the

nevets - July 4, 2009 - 04:37

The basic problem is by the time the event fires the variable does not have the same value as when you created the event. Here is one possible (untested) approach. Lets start with

    <li><a id="OS_lh2" href="#">Stain and Varnish</a>
      <ul>
        <li id="OS_ld2">This is additional information describing this service
                        <br><a id="OS_ldh2" href="#">(hide)</a>
        </li>
      </ul>
    </li>

and change to this
    <li><a id="OS_lh2" class="open-content" rel="OS_ld2" href="#">Stain and Varnish</a>
      <ul>
        <li id="OS_ld2">This is additional information describing this service
                        <br><a id="OS_ldh2"  class="close-content" rel="OS_ld2" href="#">(hide)</a>
        </li>
      </ul>
    </li>

Notice each link now has a rel tag set to the same value as the id of the element we want to open/close. They also have a class that reflects the purpose of the link. So now we do something like
$('.open-content').each(
  function() {
     $(this).click(
       function() {
         var rel = $(this).attr('rel');
         if ( rel ) {
           $('#' + rel).slideDown();
        }
      }
     );
  }
);

and
$('.close-content').each(
  function() {
     $(this).click(
       function() {
         var rel = $(this).attr('rel');
         if ( rel ) {
           $('#' + rel).slideUp();
        }
      }
     );
  }
);

(Code may note be optimal and may need some tweaking)

solved, multiple ways!

bsenftner - July 4, 2009 - 21:29

Thank you very much nevets! Your solution is very elegant.
I was just returning to post a solution I devised when I saw your post. As I suspected, as you point out as well, my variable's value has changed by the time the callback fires.

The solution I devised is as follows:

For any page that I want content to be available in sliding down and up sections, I do this:

  • any links for data to be revealed have the class "peekaboo"
  • each link for data to be revealed has an id of the form "reveal_someNumber", as in "reveal_1" and "reveal_3678"
  • each link for data to be hidden has an id of the form "hide_someNumber"
  • each block of content that gets revealed and hidden has an id of the form "peekaboo_someNumber"
  • And here's the jquery that makes the magic:

         // locate all page elements with the class "peekaboo":
         $(".peekaboo").each( function() {
                                // attach a click callback to our reveal-link:
                                $(this).click( function() {
                                                 var parts = this.id.split( "_" );
                                                 $("#peekaboo_" + parts[1]).slideDown();
                                                 return false;
                                             });
                               
                                // 'this' is an element of class 'peekaboo',
                                // with an id holding key info:
                                var elems = this.id.split( "_" ); // id is of the form reveal_num
                               
                                // attach a click callback to our hide-link:
                                $("#hide_" + elems[1]).click( function() {
                                                                   var parts = this.id.split( "_" );
                                                                   $("#peekaboo_" + parts[1]).slideUp();
                                                                   return false;
                                                               });
                              } // close anonymous function each callback
                            );  // close 'each'

    This works fine. However, with the added insight from your post, nevets, I can simplify this even more. Thank you kindly!

 
 

Drupal is a registered trademark of Dries Buytaert.