Hi all,

I have a simple problem. I have this javascript code which I'm using for collapsible tables:

<html>
<head>
   <script type="text/javascript">
    function getItem(id)
    	{
        	var itm = false;
        	if(document.getElementById) { itm = document.getElementById(id); }
        	else if(document.all) { itm = document.all[id]; }
        	else if(document.layers) { itm = document.layers[id]; }
        	return itm;
    	}

    	function toggleItem(id)
    	{
        	itm = getItem(id);
        	if(!itm) { return false; }
	        if(itm.style.display == 'none') { itm.style.display = ''; }   //display minus.gif
        	else { itm.style.display = 'none'; }	//display plus.gif
                return false;
        }
 </script>
</head>
<body>
<table width="100%">
		<tr>
                        <td><img src="/images/add.gif" href="javascript:;" onclick="toggleItem('info')" align="left">Backup Information:</td>
                </tr>
	<tbody id="info">
		<tr class="headers">
			<td><strong>Backup Date:</strong></td>
		</tr>
		<tr>
			<td><?php echo $formatted_date; ?></td>
		</tr>
	</tbody>
</table>
</body>
</html>

So what I'm trying to do is display the image /path/to/plus when the table is closed, and then display the image /path/to/minus when the table is open.

Can anyone help me?
Thanks, ns

Recommended Answers

All 8 Replies

this is not tested but I am about 99% sure that it will work:

<html>
    <head>
        <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js"></script>  
        <script type="text/javascript" language="javascript">
            var blnOpenStatus = false;

            $(document).ready(function()
            {
                $("#linkID").click(function()
                {
                    if(blnOpenStatus)
                    {
                        blnOpenStatus = false;
                        $("#BDInfo").hide();
                        $("#linkIDImg").attr("src","path/to/closed.jpg");
                    }
                    else
                    {
                        blnOpenStatus = true;
                        $("#BDInfo").show();
                        $("#linkIDImg").attr("src","path/to/open.jpg");
                    }
                });
            });
        </script>
    </head>
    <body>
        <a href="#" id="linkID"><img id="linkIDImg" src="path/to/closed.jpg" /></a>
        <!-- I would use a div here but it looks like you are using a table -->
        <table id="BDInfo" cellpadding="0" cellspacing="0" style="display:none;">
            <tr>
                <td>Backup Date:</td>
            </tr>
            <tr>
                <td>2010-12-01 09:09:31</td>
            </tr>
        </table>
    </body>
</html>

NonShatter,

Try this:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Airshow :: Untitled</title>
<style type="text/css">
{}
</style>

<script>
function getItem(id) {
	return (document.getElementById) ? document.getElementById(id) : (document.all) ? document.all[id] : (document.layers) ? document.layers[id] : null;
}
function toggleItem(sectionRef) {
	var imgNode = getItem("image_" + sectionRef);
	var sectionNode = getItem("section_" + sectionRef)
	if(imgNode && sectionNode) {
		if(sectionNode.style.display == 'none') {
			sectionNode.style.display = 'block';
			imgNode.src = imgNode.alt = '/images/minus.gif';
		}
		else {
			sectionNode.style.display = 'none';
			imgNode.src = imgNode.alt = '/images/add.gif';
		}
	}
	return false;
}
</script>
</head>

<body>

<table width="100%">
<tr><td><a href="#" onclick="return toggleItem(1)"><img id="image_1" src="/images/minus.gif" href="" align="left" alt="/images/minus.gif" border="0"></a>&nbsp;Backup Information:</td></tr>
<tbody id="section_1">
<tr class="headers">
<td><strong>Backup Date:</strong></td>
</tr><tr>
<td><?php echo $formatted_date; ?></td>
</tr>
</tbody>
<tr><td><a href="#" onclick="return toggleItem(2)"><img id="image_2" src="/images/minus.gif" href="" align="left" alt="/images/minus.gif" border="0"></a>&nbsp;Backup Information:</td></tr>
<tbody id="section_2">
<tr class="headers">
<td><strong>Backup Date:</strong></td>
</tr><tr>
<td><?php echo $formatted_date; ?></td>
</tr>
</tbody>
</table>

</body>
</html>

As you can see, the code handles two or more blocks if necessary.

You can remove the alt stuff (from javascript and HTML), which is just there to allow me to test in the absence of image files.

Airshow

That's great!

Thanks Airshow. I have the image change working now. Previously, I was using cookies to remember if the user had certain table open or hidden.

Here's the code I was using for that (I was calling <body onload="init()"> to call the function):

function init()
    	{
        	var cookie = getCookie('collapse_obj');
        	if(cookie)
        	{
        		var values = cookie.split(',');

            		for(var i = 0; i < values.length; i++)
            		{
                		var itm = getItem(values[i]);
                		if(itm)
                    			itm.style.display = 'none';
            		}
        	}
    	}

    	function makeCookie(name, value)
    	{
        	var cookie = name + '=' + escape(value) + ';';
        	document.cookie = cookie;
    	}	

    	function getCookie(name)
    	{
        	if(document.cookie == '')
            		return false;
        var firstPos;
        var lastPos;
        var cookie = document.cookie;

        	firstPos = cookie.indexOf(name);

        	if(firstPos != -1)
        	{
            		firstPos += name.length + 1;
            		lastPos = cookie.indexOf(';', firstPos);
            		if(lastPos == -1)
                		lastPos = cookie.length;
            		return unescape(cookie.substring(firstPos, lastPos));
        	}
        	else
            		return false;
    	}
  	function getItem(id)
    	{
        	var itm = false;
        	if(document.getElementById) { itm = document.getElementById(id); }
        	else if(document.all) { itm = document.all[id]; }
        	else if(document.layers) { itm = document.layers[id]; }
        	return itm;
    	}

    	function toggleItem(id)
    	{
        	itm = getItem(id);
        	if(!itm) { return false; }
	        if(itm.style.display == 'none') { itm.style.display = ''; }
        	else { itm.style.display = 'none'; }	
	
		 cookie = getCookie('collapse_obj');
        	values = new Array();
       		newval = new Array();
        	add    = 1;

        	if(cookie)
        	{
        		values = cookie.split(',');

            		for(var i = 0; i < values.length; i++)
            		{
                		if(values[i] == id)
                    			add = 0;
                		else
                    			newval[newval.length] = values[i];
            		}
        	}
        	if(add)
            		newval[newval.length] = id;
        		makeCookie('collapse_obj', newval.join(','));
        	return false;
    	}

So my question is, how can I still include the cookies functionality in your solution?

Your help is appreciated. I need to learn more about Javascript - I'm more of a server-side programmer...

So my question is, how can I still include the cookies functionality in your solution?

Yes, should be quite simple. No time now - must go do some paid work! I'll have a hack this evening - that's about 6 hours from now.

Airshow

Nonshatter,

First let's give you a decent Cookie class. This is the one I use.

//General purpose Cookie class.
var Cookie = function(name, hours, path, domain, secure) {
	this.$name = name;
	this.$expiration = (hours) ? new Date((new Date()).getTime() + hours*3600000) : null;
	this.$path = (path) ? path : null;
	this.$domain = (domain) ? domain : null;
	this.$secure = (secure) ? true : false;
	this.store = function() {
		var prop;
		var cookievalArray = [];
		for(prop in this){
			if((prop.charAt(0)!=='$') && (typeof this[prop]!=='function')) {
				cookievalArray.push( prop + ':' + escape(this[prop]) );
			}
		}
		var cookie = [ this.$name + '=' + cookievalArray.join('&') ];
		cookie.push( (this.$expiration) ? 'expires=' + this.$expiration.toGMTString() : '' );
		cookie.push( (this.$path)       ? 'path='    + this.$path                     : '' );
		cookie.push( (this.$domain)     ? 'domain='  + this.$domain                   : '' );
		cookie.push( (this.$secure)     ? 'secure'                                    : '' );
		document.cookie = cookie.join('; ');
	};
	this.load = function() {
		var i;
		var allcookies = document.cookie;
		if(allcookies==='') { return false; }
		var start = allcookies.indexOf(this.$name + '=');
		if(start===-1) { return false; }
		start += this.$name.length + 1;
		var end = (allcookies.indexOf(';',start)===-1)? allcookies.length : allcookies.indexOf(';',start);
		var cookieval = allcookies.substring(start,end);
		var a = cookieval.split('&');
		for(i=0; i<a.length; i++){
			a[i] = a[i].split(':');
			this[a[i][0]] = unescape(a[i][1]);
		}
		return true;
	};
	this.remove = function() {
		var cookie = [this.$name + '='];
		if(this.$path)   { cookie.push( 'path=' + this.$path ); }
		if(this.$domain) { cookie.push( 'domain=' + this.$domain ); }
		cookie.push( 'expires=Fri, 02-Jan-1970 00:00:00 GMT' );
		document.cookie = cookie.join('; ');
	};
	this.clear = function() {
		var prop;
		for(prop in this){
			if((prop.charAt(0)!=='$') && (typeof this[prop]!=='function')) {
				delete this[prop];
			}
		}
	};
};

Tuck that away in a .js file for reuse.

Now, the rest of the javascript will be something like this:

//Globals
var cookie;
var prefixes = {
	image : 'image_',
	section : 'section_'
};

function getItem(id) {
	return (document.getElementById) ? document.getElementById(id) : (document.all) ? document.all[id] : (document.layers) ? document.layers[id] : null;
}

function toggleItem(sectionRef) {
	var imgNode = getItem(prefixes.image + sectionRef);
	var sectionNode = getItem(prefixes.section + sectionRef)
	if(imgNode && sectionNode) {
		if(sectionNode.style.display == 'none') {
			sectionNode.style.display = 'block';
			imgNode.src = imgNode.alt = '/images/minus.gif';
			cookie[sectionRef] = '1';//open
		}
		else {
			sectionNode.style.display = 'none';
			imgNode.src = imgNode.alt = '/images/add.gif';
			cookie[sectionRef] = '0';//collapsed
		}
		cookie.store();
	}
	return false;
}

onload = function() {
	cookie = new Cookie('sections', 200000);
	cookie.load();
	var table = getItem('myTable');//You will need to give the table an id.
	if(table) {
		var sections = table.getElementsByTagName('tbody');
		var sectionRef;
		for(var i=0; i<sections.length; i++) {
			if(!sections[i].id || sections[i].id.indexOf(prefixes.section) == -1) { continue; }
			sectionRef = sections[i].id.replace(prefixes.section, '');
			if(cookie[sectionRef] === '0') { toggleItem(sectionRef); }
		}
	}
}

Please note that this will only work reliably if the sections have unique references. If you rely on sequence numbers section_1,_2,_3,_4 etc. (as per my earlier HTML) and at some time in the future the order of presentation changes, then the values stored in the cookie will be invalid and will cause the wrong sections to collapse. It may help to use descriptive strings, eg. section_apple , section_pear , section_banana .

toggleItem() has inbuilt safety enough not to generate an error if a cookie-stored section is no longer present in the table.

Airshow

commented: thorough response, thank you! +2

Works perfectly! Thanks again.

Just one last thing - When I add more than one table to the page, I can't seem to get more than one table state (collapsed/open) to be remembered upon page refresh. This probably has a simple solution.

I guess it's this function here that's causing the problem. Is it possible to make this more dynamic.

onload = function() {
	cookie = new Cookie('sections', 200000);
	cookie.load();
	var table = getItem('table1');   //This needs to cater for 2+ tables
	if(table) {
		var sections = table.getElementsByTagName('tbody');
		var sectionRef;
		for(var i=0; i<sections.length; i++) {
			if(!sections[i].id || sections[i].id.indexOf(prefixes.section) == -1) { continue; }
			sectionRef = sections[i].id.replace(prefixes.section, '');
			if(cookie[sectionRef] === '0') { toggleItem(sectionRef); }
		}
	}
}

The tables/images have test id names for now:

<table id="table1" width="100%">
           <tr>
	       <td>
                    <a href="#" onclick="return toggleItem(1)"><img id="image_1" src="/minus.gif" href="" align="left"></a>Backup Information:
               </td>
           </tr>
	   <tbody id="section_1">
	   <tr class="headers">
			<td><strong>Backup Date:</strong></td>
			<td><strong>Total Size Backed Up:</strong></td>
	   </tr>
	   <tr>
			<td>Gb</td>
			<td>cho $backup['duration']</td>
	   </tr>
	   </tbody>
	</table>

	<table id="table2" width="100%">
           <tr>
		<td>
                     <a href="#" onclick="return toggleItem(2)"><img id="image_2" src="/minus.gif" href="" align="left"></a>Backup Information:
                </td>
           </tr>
	 <tbody id="section_2">
		<tr class="headers">
			<td><strong>Backup Date:</strong></td>
			<td><strong>Total Size Backed Up:</strong></td>
		</tr>
		<tr>
			<td>Gb</td>
			<td>cho $backup['duration']</td>
		</tr>
	   </tbody>
      </table>

Not to worry, I managed to solve it using a for loop, although this doesn't allow me to use more descriptive names for the table ids:

onload = function() {
	cookie = new Cookie('sections', 200000);
	cookie.load();
	for(x=0; x < 4; x++) {
		var table = getItem("table_" + x);
	    if(table) {
		    var sections = table.getElementsByTagName('tbody');
		    var sectionRef;
		    for(var i=0; i<sections.length; i++) {
			    if(!sections[i].id || sections[i].id.indexOf(prefixes.section) == -1) { continue; }
			        sectionRef = sections[i].id.replace(prefixes.section, '');
			    if(cookie[sectionRef] === '0') { toggleItem(sectionRef); }
		    }
		}
	}
}

Let me know if you have a better solution. Consider it solved.
Cheers, ns

NonShatter

Yes, your code will work.

Here's a couple of similar ways to do it:

1. Hard-code the table ids:

onload = function() {
	cookie = new Cookie('sections', 200000);
	cookie.load();
	var tables = ["table1", "table2"];
	for(var i=0; i<tables.length; i++) {
		var table = getItem(tables[i]);
		if(table) {
			var sections = table.getElementsByTagName('tbody');
			var sectionRef;
			for(var i=0; i<sections.length; i++) {
				if(!sections[i].id || sections[i].id.indexOf(prefixes.section) == -1) { continue; }
				sectionRef = sections[i].id.replace(prefixes.section, '');
				if(cookie[sectionRef] === '0') { toggleItem(sectionRef); }
			}
		}
	}
}

2. Use a class name to indicate which tables have collapsible sections:

onload = function() {
	cookie = new Cookie('sections', 200000);
	cookie.load();
	var tables = document.getElementsByTagName('table');
	for(var i=0; i<tables.length; i++) {
		var table = getItem(tables[i]);
		if(table.className !== 'collapsible') { continue; }//reject any table not tagged with the right class.
		var sections = table.getElementsByTagName('tbody');
		var sectionRef;
		for(var i=0; i<sections.length; i++) {
			if(!sections[i].id || sections[i].id.indexOf(prefixes.section) == -1) { continue; }
			sectionRef = sections[i].id.replace(prefixes.section, '');
			if(cookie[sectionRef] === '0') { toggleItem(sectionRef); }
		}
	}
}

Neither is really better than what you already coded but would be more appropriate if (for some other reason) the table ids could not be determined by a simple rule.

The second approach is good if the number of tables is unknown/variable.

Airshow

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.