views:

1938

answers:

3

I have a table that's generated by a normal PHP loop. What I want to do is create a form in the first column of each row that's hidden by default but appears when you click a toggle link in that row.

I can make a normal toggle-able div by creating a CSS id called hidden and setting display: none;. Unfortunately I can't keep creating divs with id=hidden that are automatically associated with the preceding link.

I am pretty inexperienced with both Javascript and CSS, so I've mostly tried to do this by patching together examples but I'm coming up empty. I've read in some places that you can't put divs inside of a table, so maybe I'm going about this all wrong.

Here's an example of what the code does and how I wish it worked, but of course it does not.

<script language="JavaScript" type="text/javascript">
    function toggle(id) {
     var state = document.getElementById(id).style.display;
      if (state == 'block') {
       document.getElementById(id).style.display = 'none';
      } else {
       document.getElementById(id).style.display = 'block';
      }
     }
</script>


<?php

while($array = mysql_fetch_array($sql))
    {
?>
<tr>
    <td>
<?php
     echo $array['some_data'];
?>
     <a href="#" onclick="toggle('hidden');">Toggle</a>
     <div id="hidden"><?php echo $array['hidden_thing']; ?></div>
    </td>
    <td>
     <?php echo $array['some_other_data']; ?>
    </td>
</tr>
<?php
    }
?>
+2  A: 

Make it a span instead of a DIV as I think that some browsers don't support divs inside table elements. Also, instead of referring to it by ID, pass in this.nextSibling() to the toggle, using DOM navigation to get the next sibling (which should be the SPAN) to show/hide.

  function toggle(ctl) {
      var state = ctl.style.display;
      if (state == 'block') {
          document.getElementById(id).style.display = 'none';
      } else {
          document.getElementById(id).style.display = 'block';
      }
  }


  <a href="#" onclick="toggle(this.nextSibling);">Toggle
  </a><div><?php echo $array['hidden_thing']; ?></div>

EDIT: As @tomhaigh suggests (and as shown in the example), for this to work you need to make sure that there is no text/whitespace between the anchor and the div. You could also write a function that, given a DOM element, would select the next non-text DOM element and return it. Then pass this to that function and the result to your toggle function.

tvanfosson
"use DOM navigation to get the next sibling (which should be the SPAN) to show/hide"Can you explain this a little more?
Evan
you should make sure there is no whitespace between the <a> and the <span> otherwise the <a>'s nextSibling will be a text node
Tom Haigh
@tomhaigh -- agreed. Normally, I'd use jQuery's navigation which would automatically skip text nodes, but I didn't want to suggest using a framework. You could also implement a function that would navigate to the next non-text sibling and use it to get the DIV as well.
tvanfosson
+3  A: 

Just use a different ID for each row:

<?php
$count = 0;
while($array = mysql_fetch_array($sql)) {
  $id = 'hidden' . $count++;
  $data = $array['some_data'];
  $hidden = $array['hidden_thing'];
  $other_data = $array['other_data'];
  echo <<<END
<tr>
  <td>$data <a href="#" onclick="toggle('$id');>Toggle</a>
    <div id="$id">$hidden_thing</div>
  </td>
  <td>$other_data</td>
</tr>

END;
}
cletus
BTW this is purely a stylistic issue but I prefer the here-document syntax I posted here over multiple <?php> blocks. I find it easier to read.
cletus
It seems like both answers work but I understand this one better. Is there something I could do in the Javascript to stop it from bringing me to the top of the page when it toggles the hidden data?
Evan
Instead of using # as the href, use javascript:void(0) OR follow up toggle in the onclick handler with "return false;"
tvanfosson
Perfect. You guys are amazing. Thanks very much.
Evan
Just so you know, there is a qualitative difference between the two solutions. This one will search the DOM every time from the beginning for the named element. My solution will start at the given element use the next element. Probably only noticeable in practice if the page is very complex.
tvanfosson
@tvanfosson Yea, I see that. As I mentioned, I'm pretty useless when it comes to JS, so the more PHP-based answer (this one) is easier for me to work with right now, which is why I'm using it. I wish I could mark both as "answers."
Evan
@Evan -- doesn't really matter to me, I just wanted to make sure you understood that they are similar in function, but not equivalent.
tvanfosson
+1  A: 

Here's my recommended (general solution) using jQuery to reference events relatively instead of maintaining ids for each row and form. This also allows you to hide non-active row forms easily, which is a good idea since only one form can be submitted at a time.

HTML:

<table id="tableForms" class="table">
  <tr>
    <td class="rowForm"><form><span>form1 content</span></form></td>
    <td class="showRowForm">click on row to show its form</td>
    </tr>
  <tr>
    <td class="rowForm"><form><span>form2 content</span></form></td>
    <td class="showRowForm">click on row to show its form</td>
    </tr>
  <tr>
    <td class="rowForm"><form><span>form3 content</span></form></td>
    <td class="showRowForm">click on row to show its form</td>
    </tr>
</table>

Javascript:

<script type="text/javascript" src="/assets/js/jquery.min.js"></script>
<script type="text/javascript">
//as soon as the DOM is ready, run this function to manipulate it
$(function() {
    // get all tr elements in the table 'tableForms' and bind a 
    // function to their click event
    $('#tableForms').find('tr').bind('click',function(e){
     // get all of this row's sibblings and hide their forms.
     $(this).siblings().not(this).find('td.rowForm form').hide();

     // now show the current row's form
     $(this).find('td.rowForm form').show();
    }).
    // now that the click event is bound, hide all of the forms in this table
    find('td.rowForm form').hide();
});
</script>

Demo:

A working demo of this can be found here.

yaauie