Hello and welcome to our community! Is this your first visit?
Register
Enjoy an ad free experience by logging in. Not a member yet? Register.
Results 1 to 9 of 9
  1. #1
    Regular Coder
    Join Date
    Jul 2002
    Posts
    301
    Thanks
    7
    Thanked 2 Times in 2 Posts

    Spinner, first onclick lost and how to listen for onclick events?

    I have developed a very simple spinner using this code:
    JS:
    Code:
    function spin(id){
    if(document.getElementById(id).style.display=='none'){document.getElementById(id).style.display='block';}else {document.getElementById(id).style.display='none';}
    }//from: function spin(id){
    my test xhtml code is:
    Code:
    <h6 class="spinTrigger">Sample spinner 1</h6>
    <div class="spinContent" style="display: none;">test text and copy here</div>
    <br/><br/>
    <a name="two2" class="spinTrigger" onclick="spin('two');return false;">Sample spinner 2</a><br/>
    <div id="two" class="spinContent" style="display: none;">test text and copy here</div>
    My problems are this:
    The first click on "Sample spinner 2" appears to be lost when I'm testing. That is only the 2nd and 3rd clicks on the link onwards show/hide the content.

    I'd also like too:
    Remove the onlick attribute markup to the JS file itself. I have heard you can do this via simply:
    Code:
    document.getElementById('one1').onclick=spin('one');
    within the JS file but I can't get it to work and don't really know how to accomplish it.

    My ultimate goal is to rewrite so that the JS code listens for onclick events from elements with the class "spinnerTrigger" and change the display value for the next following element (note not child element) with the class "spinnerContent". Thats what the "Sample spinner 1" xhtml code was all about. That would be ultimately unobtrusive however, I quickly realised I hadn't a clue how to do that. So until then fixing the spinner 2 link/code is my goal.

    I need to keep the code cross browser compatible (assuming it allready is).

    It's a long time since I played with javascript. Any pointers and help gratefully received.

    Matt
    Last edited by MattyUK; 06-28-2006 at 09:01 PM.

  • #2
    Senior Coder
    Join Date
    Jul 2005
    Location
    New York, NY
    Posts
    1,084
    Thanks
    4
    Thanked 19 Times in 19 Posts
    Ok, so let's tackle the easier ones first:

    Code:
    spin(2);
    This actually calls your spin code. Which is good when you want your spin code called. But you don't want it called when you're setting up your event handlers. That's like setting the bomb off while you're wiring it up. Instead, you want this:

    Code:
    document.getElementById('whatever').onclick = function(){spin(2);};
    That will create a function that will call spin with the argument of 2 and assigns that function to the DOM Level 1 event handler for clicks. This is the simplest, cross-browser-est (not perfect mind you) way of doing what you want. You could move to DOM Level 2 which provides less obtrusive event handling, but you'd need to learn a bit more and write some code to get around the browser differences. Entirely doable mind you, but let's just get you started here.

    Second, don't use anchor tags if you don't need to. Then you don't need to worry about returning false:

    Code:
    replace:
    <a class="whatever" onclick="spin(2);return false">SPIN!</a>
    with:
    <span class="whatever" name="spinner" onclick="spin(2);">SPIN!</span>
    And then just use stylesheets to get everything looking right. Don't forget the style cursor: pointer;

    Then, for another of your questions, I would use the name attribute that I put in above to do what you want instead of the class attribute. Names do not have to be unique across the page, so you can name all of your spinners something like "spinner" and then do this:

    Code:
    ...
    <!-- other spinners defined above -->
    <span id="lastSpinner" name="spinner" class="spinner">Spin Me</span>
    <script type="text/javascript">
    var spinners = document.getElementsByName("spinner");
    for (var i = spinners.length - 1; i >= 0; --i)
    {
      spinners[i].onclick = function(){ spin(); };
    }
    </script>
    That would attach the event handler to all of them.

    Now, to make life EVEN EASIER (amazing, I know) you can use a functionally structured DOM to accomplish your relationships between spinners and content:

    Code:
    <div class="spinnerContainer">
      <span name="spinner" class="spinnerLink">SPIN!</spin>
      <div class="spinnerContent"></div>
    </div>
    
    <script type="text/javascript">
    function spinClickEventHandler(evt)
    {
      if (!evt)  // *** IE fix ***
        evt = window.event;
    
       var spinner; // *** element that got clicked ***
    
        // *** Credit to PPK from quirksmode.org for this block ***
        if (evt.target) spinner = evt.target;
    	else if (evt.srcElement) spinner = evt.srcElement;
    	if (spinner.nodeType == 3) // defeat Safari bug
    		spinner = spinner.parentNode;
    
       var contentDiv = spinner.nextSibling;
       if (contentDiv.nodeType == 3)
         contentDiv = contentDiv.nextSibling;
    
      spin(contentDiv);
    }
    </script>
    And BAM! No more IDs! Everything based of the logical structure and layout of your document. If you want to figure out how to use the DOM Level 2 event attaching model, check out PPK's site http://www.quirksmode.org/ and go through his JavaScript -> Events articles.

    Hope that helps, good luck!

  • #3
    Regular Coder
    Join Date
    Jul 2002
    Posts
    301
    Thanks
    7
    Thanked 2 Times in 2 Posts
    Hi Beagle

    That is great. Thank you. Thank you especially for the time you put in. Very helpful. I wanted to separate the code from the markup and it does just that.

    It introduced a few new concepts to me however and I have a few questions.

    How do I use the spinClickEventHandler()? Do I attach it to window.onload or some such?
    Shouldn't it possible to change the style.display directly from the one function rather than retaining and calling spin()?
    If we are removing IDs from the markup the original spin() code used getElementById(), so it would fail. Am I right in thinking we can reference the object directly? If so that what about when it is called with no var as in spin() rather than spin(contentDiv)?

    After some structural condesment to your code (just my personal prefference, your version is actually easier to read) am I right in thinking the following for the complete working setup? I made a new spin function?
    XHTML code:
    Code:
    <div class="spinnerContainer">
    <span name="spinner" class="spinnerLink">SPIN!</span>
    <div class="spinnerContent">Content</div>
    <br/><br/>
    <div class="spinnerContainer">
    <span name="spinner" class="spinnerLink">SPIN! 1</span>
    <div class="spinnerContent">Content 1</div>
    CSS code:
    Code:
    .spinnerContainer {}
    .spinnerLink {cursor: pointer;}
    .spinnerContent {display: none;}
    JS code:
    Code:
    /*function spin(id){
    if(document.getElementById(id).style.display=='none'){document.getElementById(id).style.display='block';}else {document.getElementById(id).style.display='none';}
    }//from: function spin(id){
    */
    
    function spin(obj){
    if(obj.style.display=='none'){obj.style.display='block';}else{obj.style.display='none';}
    }//from: function spin(obj){
    
    var spinners=document.getElementsByName("spinner");
    for (var i=spinners.length-1;i>= 0;--i){
    //Credit too Beagle and coding forums
    //http://www.codingforums.com/showthread.php?t=90062
    spinners[i].onclick=function(){spinClickEventHandler();};
    }//from: for (var i=spinners.length-1;i>= 0;--i){
    
    
    function spinClickEventHandler(evt){
    //Credit too Beagle and coding forums
    //http://www.codingforums.com/showthread.php?t=90062
    if(!evt){evt=window.event;}// *** IE fix ***
    var spinner;// *** element that got clicked ***
    // *** Credit to PPK from quirksmode.org for this block ***
    if(evt.target){spinner=evt.target;}
    else if (evt.srcElement){spinner=evt.srcElement;}
    if(spinner.nodeType==3){spinner=spinner.parentNode;}// defeat Safari bug
    var contentDiv=spinner.nextSibling;
    if(contentDiv.nodeType==3){contentDiv=contentDiv.nextSibling;}
    spin(contentDiv);
    //if(contentDiv.style.display=='none'){contentDiv.style.display='block';}else{contentDiv.style.display='none';}
    }//from: function spinClickEventHandler(evt){
    
    //spinClickEventHandler();
    //window.onload=spinClickEventHandler;
    //document.getElementsByName('spinnerLink').onclick=function(){spinClickEventHandler();}
    However when testing in FF and IE nothing occurs.
    Obviously I'm going wrong somewhere. Could I beg your help again please?

    I've left my thoughts and attempts commented out with the code. Maybe I was close at some point.

    I've started reading at quirksmode, ther eis a lot there. I'll no doubt be a while. Thanks for the link.

    Matt
    Last edited by MattyUK; 06-29-2006 at 07:38 PM.

  • #4
    Senior Coder
    Join Date
    Jul 2005
    Location
    New York, NY
    Posts
    1,084
    Thanks
    4
    Thanked 19 Times in 19 Posts
    basically to attach the handler you do element.onclick = spinClickEventHandler; instead of element.onclick = function(){spin();};

    Yes, you can reference objects directly. The event target is a reference to an HTMLElement, not it's ID. So because of that, we can make spin take an object instead of an ID and skip the whole getElementById part.

    I've been corrected about the getElementsByName thing. It is not standard for spans to have a name attribute. It will work if you don't worry about validation, but that can't be ensured for the future. Instead, you could getElementsByTagName and loop over them checking for their classNames, OR you could try this:

    Code:
    document.onclick = function(evt)
    {
      if (!evt)  // *** IE fix ***
        evt = window.event;
    
       var spinner; // *** element that got clicked ***
    
        // *** Credit to PPK from quirksmode.org for this block ***
        if (evt.target) spinner = evt.target;
    	else if (evt.srcElement) spinner = evt.srcElement;
    	if (spinner.nodeType == 3) // defeat Safari bug
    		spinner = spinner.parentNode;
    
      if (spinner.className == "whatever")
      {
         var contentDiv = spinner.nextSibling;
         if (contentDiv.nodeType == 3)
           contentDiv = contentDiv.nextSibling;
    
        spin(contentDiv);
      }
    }
    This would handle all clicks anywhere on the page and if the element had the right class name, it would fire the event.

    Whether you loop over all your elements at the beginning and attach the event handler, or you attach it to the document, is a question of where do you want to spend the most time.

    Looping over all of the elements at the page load can make the page load slower, especially with a big page. But if you're only going to the load the page once, no big deal.

    Checking the event at the document level requires that the event bubble up through every layer of the DOM before you catch it, which could make the responsiveness of the page a bit slower, not nearly as much as the loop will, but it will happen every time there's a click.

    So if you're only going to load the page once, I would say loop over all elements and test their className, and if you're loading the page frequently with form posts or navigation, attach the event handler at the root of the document.

  • #5
    Regular Coder
    Join Date
    Jul 2002
    Posts
    301
    Thanks
    7
    Thanked 2 Times in 2 Posts
    Wonderful.

    Thank you Beagle.

    I now have a working version of:
    Code:
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>Spinner Test</title>
    <script language="JavaScript" type="text/JavaScript">
    /*function spin(obj){
    if(obj.style.display=='none'){obj.style.display='block';}else{obj.style.display='none';}
    }//from: function spin(obj){
    */
    document.onclick=function(evt){
    //Credit too Beagle and coding forums
    //http://www.codingforums.com/showthread.php?t=90062
    if(!evt){evt=window.event;}  // *** IE fix ***
    var spinner;// *** element that got clicked ***
    // *** Credit to PPK from quirksmode.org for this block ***
    if(evt.target){spinner=evt.target;}
    else if(evt.srcElement){spinner=evt.srcElement;}
    if(spinner.nodeType==3){spinner=spinner.parentNode;} // defeat Safari bug
    if(spinner.className=='spinnerLink'){
    	var contentDiv=spinner.nextSibling;
    	if(contentDiv.nodeType==3){contentDiv=contentDiv.nextSibling;}
    	//Action
    	if(contentDiv.style.display=='none'){contentDiv.style.display='block';}else{contentDiv.style.display='none';}
    }//from: if(spinner.className=='spinnerLink'){
    }//from: document.onclick=function(evt){
    </script>
    <style type="text/css">
    <!--
    .spinnerContainer {}
    .spinnerLink {cursor: pointer;}
    .spinnerContent {display: none;}
    -->
    </style>
    </head><body><br/><br/>
    <div class="spinnerContainer">
    <span class="spinnerLink">SPIN!</span>
    <div class="spinnerContent">Content</div>
    </div>
    <br/><br/>
    <div class="spinnerContainer">
    <span class="spinnerLink">SPIN! 1</span>
    <div class="spinnerContent">Content 1</div>
    </div>
    </body></html>
    I hear your point on responsiveness and where to attach the event. However I'll try it for all clicks and see. I do need to worry about validation. I've a little to relearn about client side scripting but your efforts have helped a lot. Thanks.

    Only one drawback so far. The first click is lost.
    The first click on the element produces no change, all clicks after that work fine. Do you happen to know what causes this or if there is a way around it?

    Matt

  • #6
    Senior Coder
    Join Date
    Jul 2005
    Location
    New York, NY
    Posts
    1,084
    Thanks
    4
    Thanked 19 Times in 19 Posts
    Hmm, weird, I just tried it myself and I get the same thing.

    [working on it]

    AHA! Figured it out.

    You're testing to see if the div's display is none and if so, setting it to block, otherwise, set it to none.

    But at the very beginning, the div's display property is set to nothing (it's a weird quirk with javascript and stylesheets, read up on it at quirksmode)

    So instead, you should test if the div's display is block and if not set it to block, otherwise hide it, because that is inline with the behavior you're expecting.

  • #7
    Regular Coder
    Join Date
    Jul 2002
    Posts
    301
    Thanks
    7
    Thanked 2 Times in 2 Posts
    Wonderful.

    Thank you. It works perfectly. Thank you for your help, in both the script and my re-learning curve.

    Complete code to aid other thread users:
    Code:
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>Spinner Test</title>
    <style type="text/css">
    <!--
    .spinnerContainer {}
    .spinnerLink {cursor: pointer;}
    .spinnerContent {display: block;}
    -->
    </style>
    <script language="JavaScript" type="text/JavaScript">
    document.onclick=function(evt){
    //Credit too Beagle and coding forums http://www.codingforums.com/showthread.php?t=90062
    if(!evt){evt=window.event;}  // *** IE fix ***
    var spinner;// *** element that got clicked ***
    // *** Credit to PPK from quirksmode.org for this block ***
    if(evt.target){spinner=evt.target;}
    else if(evt.srcElement){spinner=evt.srcElement;}
    if(spinner.nodeType==3){spinner=spinner.parentNode;} // defeat Safari bug
    if(spinner.className=='spinnerLink'){
    	var contentDiv=spinner.nextSibling;
    	if(contentDiv.nodeType==3){contentDiv=contentDiv.nextSibling;}
    	//Action
    	if(contentDiv.style.display!='block'){contentDiv.style.display='block';}else{contentDiv.style.display='none';}
    }//from: if(spinner.className=='spinnerLink'){
    }//from: document.onclick=function(evt){
    
    document.write('<style type="text/css">.spinnerContent {display: none;}<\/style>');
    </script>
    </head><body><br/><br/>
    <div class="spinnerContainer">
    <span class="spinnerLink">SPIN!</span>
    <div class="spinnerContent">Content</div>
    </div>
    <br/><br/>
    <div class="spinnerContainer">
    <span class="spinnerLink">SPIN! 1</span>
    <div class="spinnerContent">Content 1</div>
    </div>
    </body></html>
    I changed the logic as you suggested Beagle, good catch.

    I added code to write css to the document. This way users with no JS see the content. for this reason the initial CSS has been moved above the script.

    This now suits my purposes. I guess desired upgrades to suit it more for general purpose and reuse would be:
    1> Replace document.write with a way of altering the CSS class rule property. Hard part on this after reading quirksmode would be in targeting the CSS rule in a cross browser, not to intensive way.
    2> Find a better cross browser trigger attachment than the current firing on every click.

    Certainly both goals are outside of my current skill level.

    Beagle, thanks again, do you mind If I post this in the post a script forum for other users?

    Matt

  • #8
    Senior Coder
    Join Date
    Jul 2005
    Location
    New York, NY
    Posts
    1,084
    Thanks
    4
    Thanked 19 Times in 19 Posts
    Once it's out on the forums, it's fair game, go for it.

  • #9
    Regular Coder
    Join Date
    Jul 2002
    Posts
    301
    Thanks
    7
    Thanked 2 Times in 2 Posts
    Great, ta.


  •  

    Posting Permissions

    • You may not post new threads
    • You may not post replies
    • You may not post attachments
    • You may not edit your posts
    •