Link to home
Start Free TrialLog in
Avatar of kcalder
kcalderFlag for United Kingdom of Great Britain and Northern Ireland

asked on

Ajax PHP MySQL cascading html elements

I am just starting to get to grips with Ajax as a way of providing asynchronous support for interaction with a MySQL database without resorting to form submission.

I am working on a project in which one of the pages has two selects and a textfield. These elements need to cascade. The first select is on the page to begin with, the second appears once a selection has been made. The textfield appears once  selection has been made in the second select. The first option on both selects is a null option inviting the user to make a selection. What I want is for the second select and textfield to disappear if the null option is subsequently selected on select 1, and for the textfield to disappear if the null option is selected on select 2 - i.e., graceful management of the cascading process.

I have four files I am using:
- the file containing the form and the code for querying the database to populate select 1 (mgtAddDocs.php)
- one php file containing the routine for querying the database to populate select 2 (getAdminGroups.php)
- one php file containing the routine for querying the database to populate the textfield (getAGPassword.php)
- one javascript file containing the ajax script (processSelections.js)

At the moment the mechanisms work up to the point at which, once initial selections have been made and all three elements are visible, I select the null option on one of the selects at which time the following errors appear:

Notice: Trying to get property of non-object in /Users/mike/Sites/projectRed/mgt/addDocs/getAdminGroups.php on line 9 [...or getAGPassword.php if select 2 null has been selected]

Fatal error: Call to a member function fetch_assoc() on a non-object in /Users/mike/Sites/projectRed/mgt/addDocs/getAdminGroups.php on line 9 [...or getAGPassword.php if select 2 null has been selected]

Presumably this has something to do with the $_GET array being empty when the null option is selected even though the javascript is still triggered by 'onchange' but I am not sure what's wrong and should really appreciate some assistance to sort the code out please.
<!-- Container file (mgtAddDocs.php) -->
<!-- js declaration in the head -->
<script type="text/javascript" src="processSelections.js"></script>
 
<!-- This is an excerpt from the container file body with the key parts of the table -->
<td class="twentyFive">
    <select name="selectDocZone" class="fullWidth" onchange="showAdminGroups(this.value)">
        <option>Select a document zone...</option>
	    <?php
	    do 
	    { ?>
	        <option value="<?php echo $row_rstDocumentZones['zone_pk']; ?>">
                <?php echo $row_rstDocumentZones['zoneTitle']; ?>
		</option>
	    <?php } while ($row_rstDocumentZones = mysql_fetch_assoc($rstDocumentZones)); ?>
    </select>
</td>
<td class="twenty" colspan="3"><div id="ag" name="ag"></div></td>
<td class="ten"><div id="agpw" name="agpw"></div></td>
<!-- 
--
php file to populate select 2 (getAdminGroups.php)
--
-->
<?php
// Get the zone number from the $_GET array
$z = $_GET['zone'];
	
// Create a database connection
@ $db = new mysqli('localhost', 'user', 'password', 'database');
if (!$db) die ('Could not connect to the database: '.mysql_error());
		
// Get the admin group relating to the zone value
$query = sprintf("SELECT adminGroup_pk, agMembers FROM adminGroup WHERE zoneA_id = %s OR zoneB_id = %s", $z, $z);
$result = $db->query($query);
$num_results = $result->num_rows;
		
echo '<select name="selectAdminGroup" class="fullWidth" onchange="showAGPassword(this.value)">'; 
echo '<option>Select an admin group...</option>';
for ($i = 0; $i < $num_results; $i++)
{ 
	$row = $result->fetch_assoc();
	echo '<option value="';
	echo $row['adminGroup_pk'].'"'; 
	echo '>';
	echo $row['agMembers'];
	echo '</option>';
}
echo '</select>';
	
// free up the resultset
$result->free();
?>
<!-- 
--
php file to populate textfield (getAGPassword.php)
--
-->
<?php
// Get the admin group number from the $_GET array
$ag = $_GET['ag'];
	
// Create a database connection
@ $db = new mysqli('localhost', 'user', 'password', 'database');
if (!$db) die ('Could not connect to the database: '.mysql_error());
		
// Get the ag password relating to the ag value
$query = sprintf("SELECT agPassword FROM adminGroup WHERE adminGroup_pk = %s", $ag);
$result = $db->query($query);
$num_results = $result->num_rows;	// There is only one
$row = $result->fetch_assoc();
		
// Create the textfield element
echo '<input type="text" class="agPassword" readonly="true" size="10" maxlength="10" value="'.$row['agPassword'].'" />';
		
// free up the resultset
$result->free();
?>
<!-- 
--
js file (processSelections.js)
--
-->
var xmlHttp;
 
function GetXmlHttpObject()
{
	var xmlHttp=null;
	try
	{
		// Firefox, Opera 8.0+, Safari
		xmlHttp=new XMLHttpRequest();
	}
	catch (e)
	{
		//Internet Explorer
	 	try
		{
			xmlHttp=new ActiveXObject("Msxml2.XMLHTTP");
		}
		catch (e)
		{
			xmlHttp=new ActiveXObject("Microsoft.XMLHTTP");
		}
	 }
	return xmlHttp;
}
 
function zoneStateChanged() 
{ 
	if (xmlHttp.readyState==4 || xmlHttp.readyState=="complete")
	{ 
		document.getElementById("ag").innerHTML=xmlHttp.responseText;
	} 
}
 
function agStateChanged() 
{ 
	if (xmlHttp.readyState==4 || xmlHttp.readyState=="complete")
	{ 
		document.getElementById("agpw").innerHTML=xmlHttp.responseText;
	} 
}
 
function showAdminGroups(str)
{ 
	xmlHttp=GetXmlHttpObject();
	if (xmlHttp==null)
	{
		alert ("Browser does not support HTTP Request");
	 	return;
	}
	var url="getAdminGroups.php";
	url=url+"?zone="+str;
	url=url+"&sid="+Math.random();
	xmlHttp.onreadystatechange=zoneStateChanged;
	xmlHttp.open("GET",url,true);
	xmlHttp.send(null);
}
 
function showAGPassword(str)
{ 
	xmlHttp=GetXmlHttpObject();
	if (xmlHttp==null)
	{
		alert ("Browser does not support HTTP Request");
	 	return;
	}
	var url="getAGPassword.php";
	url=url+"?ag="+str;
	url=url+"&sid="+Math.random();
	xmlHttp.onreadystatechange=agStateChanged;
	xmlHttp.open("GET",url,true);
	xmlHttp.send(null);
}

Open in new window

Avatar of Lord_Garfield
Lord_Garfield

Hi,

Not yet a solution but a just a tip.
Learn JQuery or Prototype
Both are javascript frameworks who simplifie Ajax. And best of all. They are mostly immediatly cross browser.

kind regards
ASKER CERTIFIED SOLUTION
Avatar of Roger Baklund
Roger Baklund
Flag of Norway image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of kcalder

ASKER

Unfortunately, I already tried testing for a blank value in the way you describe and I still get the error message.
Can you post your code? It should work.
Avatar of kcalder

ASKER

The code for the getAdminGroups.php is per my original posting but for the empty value check included. I have inserted the same statements into getAGPasswords.php. I though this should work too which is why I don't quite understand what's happening.
<?php
// Get the zone number from the $_GET array
$z = $_GET['zone'];
	
// Create a database connection if $_GET is not blank
if ($z > '')
{
	// @ $db = new mysqli('localhost', 'kcalder', 'kC->RED%zERO', 'intranet');
	@ $db = new mysqli('localhost', 'root', 'hpfHPF840', 'intranet');
	if (!$db) die ('Could not connect to the database: '.mysql_error());
			
	// Get the admin group relating to the zone value
	$query = sprintf("SELECT adminGroup_pk, agMembers FROM adminGroup WHERE zoneA_id = %s OR zoneB_id = %s", $z, $z);
	$result = $db->query($query);
	$num_results = $result->num_rows;
			
	echo '<select name="selectAdminGroup" class="fullWidth" onchange="showAGPassword(this.value)">'; 
	echo '<option>Select an admin group...</option>';
	for ($i = 0; $i < $num_results; $i++)
	{ 
		$row = $result->fetch_assoc();
		echo '<option value="';
		echo $row['adminGroup_pk'].'"'; 
		// if ($_GET && !(strcmp($_GET['selectAdminGroup'], $row['adminGroup_pk']))) echo 'selected="selected"';
		echo '>';
		echo $row['agMembers'];
		echo '</option>';
	}
	echo '</select>';
		
	// free up the resultset
	$result->free();
}
else ; // Write a message
?>

Open in new window

When you call this without a zone, you get error in the line with $num_results = $result->num_rows; ?

Try running it directly in the browser.
Avatar of kcalder

ASKER

I have attached a screen grab showing the error when I select the initial (empty) option for the admin group. The same thing happens when I select the initial (empty) option for the zone.
grab.jpg
Avatar of kcalder

ASKER

Here's what the screen looks like when the selects are properly initialized
grabOK.jpg
It says error in line 47, can you post that line?
Avatar of kcalder

ASKER

Dreamweaver doesn't do a very good job of identifying the actual line numbers associated with errors. Here are lines 45-49 inclusive...

// Get the zone number from the $_GET array
$z = $_GET['zone'];
      
// Create a database connection if $_GET is not blank
if ($z > '')
Are you sure those are lines 45-47 in file getAGPassword.php?

You can open the file in any editor.
Not 45-47, 45-49!
Avatar of kcalder

ASKER

Yes. Actually line 47 is blank!
Avatar of kcalder

ASKER

Here's a screen grab of the relevant section of the file just to prove it
getAGPassword-41ff.jpg
That is getAdminGroup.php, I need to see getAGPassword.php
Avatar of kcalder

ASKER

Sorry about that. Correct file below..
getAGPassword-41ff.jpg
Could it be that you have a different set of files that are actually executed, and you are editing the wrong file? When PHP errors out and reports the line number, it is usually correct. In this case, we get Fatal Error "call to a member function fetch_assoc() on a non-object" on line 47. Then i would expect line 47 to actually contain a call to fetch_assoc(). Please check that you are working with the correct file.
Avatar of kcalder

ASKER

The files I have displayed are the only ones involved in the procedures. There are no other files with those filenames in the site. Just to demonstrate this is the case, the screen grab below indicates the full file listing.
files.jpg
Test it again. Is it still line 47?

Then edit the file. Just input three blank lines somewhere before line 47, so that this line is moved to line 50. Then test again. Does it report line 50 now?
Avatar of kcalder

ASKER

Indeed it does. If I move the code down three lines (inserting three more blanks after line 47), the error message reports line 50
And line 50 is blank? I assume you mean you inserted three blank lines BEFORE line 47, otherwise line 47 would not move.

Have you tried running these scripts directly in the browser? Do that, with and without parameters. Report back with your results.
Avatar of kcalder

ASKER

Surely, if line 47 is blank it doesn't make any difference whether I add three blanks before or after it, since I then get four blank lines: 47-50.

Can you explain how to run the scripts directly in the browser?
Just enter the name of the php script and the parameters in the address bar of the browser:

http://.../getAGPassword.php?ag=1&sid=1
http://.../getAGPassword.php?ag=&sid=2
Avatar of kcalder

ASKER

OK, here are the results below screen-grabbed. I have tried it with both scripts, one for AdminGroups and the other for AGPassword and what I get is the form element. However I noticed that if I remove the "&" from the url the same two messages are displayed as when operating the select dropdown from the site.
grab-getAdmingroups.jpg
grab-getAGPassword.jpg
Ok, ag=1&sid=1 works as expected, but how did ag=&sid=2 (and zone=&sid=2) work?

When you remnove the "&" you get ag=sid, so that SHOULD fail with that error. It's ag=& we want to test, blank value for ag (and zone).

You can also try without the parameter:

http://.../getAGPassword.php?sid=1
Avatar of kcalder

ASKER

Results as follows...
(1) getAdminGroups.php?zone=1&sid=1 (or 2) - select displayed but only with the initial (null) option
(2) getAGPassword.php?ag=1&sid=1 (or 2) - blank textfield displayed
(3) getAdminGroups.php?zone=&sid=1 (or 2) - nothing displayed
(4) getAGPassword.php?ag=&sid=1 (or 2) - nothing displayed
(5)  getAdminGroups.php?sid=1 (or 2) -
Notice: Undefined index: zone in /Users/kcalder/Sites/RED/mgt/addDocs/getAdminGroups.php on line 38
(6) getAGPassword.php?sid=1 (or 2) -
Notice: Undefined index: ag in /Users/kcalder/Sites/RED/mgt/addDocs/getAGPassword.php on line 38
Avatar of kcalder

ASKER

Are you able to assist me any further with this?
Ok, (1) and (2) are the "normal" cases, they seems to work, except for the empty dropdown. Both produce output. (3) and (4) is when the selected item has no value. These are the ones that should be handled when you test for a blank input. They give no output now, which seems to be correct. (5) and (6) fails, but these should never be executed from your ajax call:

        var url="getAdminGroups.php";
        url=url+"?zone="+str;
        url=url+"&sid="+Math.random();

The zone parameter is allways set. When it is blank it becomes "?zone=&sid=..."

I noticed you are using mysql_error() to report sql errors. When using mysqli, you should use a different error function, mysqli_error($db) or fetch the error property from the $db instance: $db->error.

Try it with the ajax again. Still getting the "call to a member function fetch_assoc()" error?
Avatar of kcalder

ASKER

I have modified the php to mysqli_error($db). The error is still  occurring. There is nothing in the js file to prevent the three lines you quote from executing at the moment.
Insert some debug output, so we can see what is happening:
<?php
// Get the zone number from the $_GET array
$z = isset($_GET['zone']) ? $_GET['zone'] : '';
        
// Create a database connection if $_GET is not blank
if ($z > '')
{
        $db = new mysqli('localhost', 'root', 'hpfHPF840', 'intranet');
        if (!$db) die ('Could not connect to the database.');
                        
        // Get the admin group relating to the zone value
        $query = sprintf("SELECT adminGroup_pk, agMembers FROM adminGroup WHERE zoneA_id = %s OR zoneB_id = %s", $z, $z);
        $result = $db->query($query);
        if(!$result) die('Query: '.$query.'<br />'.mysqli_error($db));
        $num_results = $result->num_rows;
        echo '$num_results='.$num_results.'<br />';  # debug
        echo '<select name="selectAdminGroup" class="fullWidth" onchange="showAGPassword(this.value)">'; 
        echo '<option>Select an admin group...</option>';
        for ($i = 0; $i < $num_results; $i++)
        { 
                $row = $result->fetch_assoc();
                echo '<option value="';
                echo $row['adminGroup_pk'].'"'; 
                // if ($_GET && !(strcmp($_GET['selectAdminGroup'], $row['adminGroup_pk']))) echo 'selected="selected"';
                echo '>';
                echo $row['agMembers'];
                echo '</option>';
        }
        echo '</select>';
                
        // free up the resultset
        $result->free();
}
else echo 'No zone selected'; // Write a message
?>

Open in new window

Avatar of kcalder

ASKER

OK, good. When I do an initial zone selection, the second select is displayed and then I reselect the empty option on the zone select I get the following MySQL error:

Query: SELECT adminGroup_pk, agMembers FROM adminGroup WHERE zoneA_id = Select a document zone... OR zoneB_id = Select a document zone...
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'Select a document zone... OR zoneB_id = Select a document zone...' at line 1

Presumably this is because I have <option>Select a document zone...</option> prior to the repeat region?
Change it to this:

<option value="">Select a document zone...</option>
Avatar of kcalder

ASKER

I think I have found a way around this problem. If I create a dummy entry in the database with a primary key of 1 for the initial selection and then only run the getAdminGroup.php routine to display the second select if the value of 'zone' is greater than 1 gain control without forcing errors. I don't like entering dummy values in the database but it does seem to work, at least partially. I then have the problem that the final textfield remins visible if I reselect the zone while an admin group is displayed.
Did you try <option value=""> ? A dummy database record should not be necessary.
Avatar of kcalder

ASKER

Yes, you're quite right and doing that provides exactly the same result, which is to say that it does work with one small glitch. I no longer get the error when I change the selects but the textfield stays visible if, when both selects are visible and with real options selected, I change the zone selection back to empty. This is presumably because $_GET['ag'] still has a value in even though it is not displayed because $_GET['zone'] is now empty?
Looking at your ajax calls:

      var url="getAdminGroups.php";
      url=url+"?zone="+str;
      url=url+"&sid="+Math.random();

      var url="getAGPassword.php";
      url=url+"?ag="+str;
      url=url+"&sid="+Math.random();

One of them use parameter 'zone', the other use 'ag', so $_GET['zone'] and $_GET['ag'] is not used simultaneously.

Not sure if I understand exactly what you need: when blank zone is selected, hide password field? If so, try modifying the zoneStateChanged() function like below.
function zoneStateChanged() 
{ 
	if (xmlHttp.readyState==4 || xmlHttp.readyState=="complete")
	{ 
                document.getElementById("ag").innerHTML=xmlHttp.responseText;
                if(xmlHttp.responseText=='')
                  document.getElementById("agpw").innerHTML='';
	} 
}

Open in new window

Avatar of kcalder

ASKER

OK, I have provided some screen shots to show you what happens.
The first state is where I have selected a zone, the admin group list appears, I select an admin group and the password field appears.
The second state is where I deselect the admin group leaving the zone as originally selected.
The third state (an alternative to the second) is where I have left the admin group selection but deselected the admin group, which leaves the text field still displayed.
selectState-bothSelected.jpg
selectState-onlyZoneSelected.jpg
selectState-revertToNoneSelected.jpg
So, image 1 and 2 is like it should be, nothing wrong there? The only problem now is as shown in image 3, the password field is not hidden? What makes it disappear in image 2?

I would think the new version of zoneStateChanged() removed the password field, it sets document.getElementById("agpw").innerHTML to blank. Can you verify that this is what is happening? Is the password field within an element with id=agpw ?
Avatar of kcalder

ASKER

The 'plan' is that each item after the first select rolls back appropriately. So from a state where they are all visible (a zone and an admin group both selected and the text field showing), if I then select the null option for admin group the text field disappears - this is image 2. If I then select a null zone then the admin group select disappears. That's how it should work. The state shown in image three occurs if, with both a zone and admin group selected I go straight to reselecting a null zone. In that case the admin group select disappears, which is correct, but the password remains. In answer to your question, yes the password field is within a div called agpw. I think the problem might be that because I have not reselected the admin group to the null option before reselecting the zone to null, the value of $_GET['ag'] is not null, so when tested by the routine in getAGPassword.php.....

$ag = isset($_GET['ag']) ? $_GET['ag'] : '';
if ($ag > '')
{
   do the database query and show the password in a text field
}

it passes the test.
When you select a zone (or select no zone), this code gets executed:

var url="getAdminGroups.php";
url=url+"?zone="+str;
url=url+"&sid="+Math.random();

getAGPassword.php is NOT executed, and the 'ag' parameter is not used, but when the ajax call returns, it executes this:

                document.getElementById("ag").innerHTML=xmlHttp.responseText;
                if(xmlHttp.responseText=='')
                  document.getElementById("agpw").innerHTML='';

When the response from getAdminGroups.php is blank, both divs 'ag' and 'agpw' is set to blank, i.e. the password field should be hidden.

This is happening in some cases, but not in the case with your image 3? Why?

Am I missing something here? Is getAGPassword.php called in this case? How?
Avatar of kcalder

ASKER

I don't think that it is AGPassword.php being called; if it was being called then I would expect the text field to disappear because we are testing for $_GET['ag']. Is it not because AGPassword.php is NOT being called and therefore the text field remains visible because the value of $_GET['ag'] remains as it was originally? Since everything is driven by the 'onchange' behaviour, if there is no change then presumably the state of the element remains as is until the admin group select is used.
Avatar of kcalder

ASKER

I don't think that it is AGPassword.php being called; if it was being called then I would expect the text field to disappear because we are testing for $_GET['ag']. Is it not because AGPassword.php is NOT being called and therefore the text field remains visible because the value of $_GET['ag'] remains as it was originally? Since everything is driven by the 'onchange' behaviour, if there is no change then presumably the state of the element remains as is until the admin group select is used.
The value of $_GET['ag'] is only tested within AGPassword.php, if this script is not called, the $_GET['ag'] parameter is not used.

When zone is set to blank, the 'agpw' div should be hidden. This does not seem to be the case... maybe AGPassword.php IS beeing called?

Insert some alert() calls to see what is going on. Within the function showAdminGroups(str), add this as the last line:

alert('showAdminGroups, str='+str);

Do the same for showAGPassword(str):

alert('showAGPassword, str='+str);

Then test again. When you un-select the zone, is showAGPassword() executed?
Avatar of kcalder

ASKER

I don't think that it is AGPassword.php being called; if it was being called then I would expect the text field to disappear because we are testing for $_GET['ag']. Is it not because AGPassword.php is NOT being called and therefore the text field remains visible because the value of $_GET['ag'] remains as it was originally? Since everything is driven by the 'onchange' behaviour, if there is no change then presumably the state of the element remains as is until the admin group select is used.
$_GET is a PHP variable, it does not have a value in the client. If AGPassword.php is not executed, $_GET['ag'] is not evaluated.

I was suspecting that when the second dropdown is hidden, somehow the onchange event is triggered, and because the value is still whatever it was last, AGPassword.php is called with the "old" value.

The alert() statements i suggested above would tell us if this is happening. Did you try that?
Avatar of kcalder

ASKER

I don't think that it is AGPassword.php being called; if it was being called then I would expect the text field to disappear because we are testing for $_GET['ag']. Is it not because AGPassword.php is NOT being called and therefore the text field remains visible because the value of $_GET['ag'] remains as it was originally? Since everything is driven by the 'onchange' behaviour, if there is no change then presumably the state of the element remains as is until the admin group select is used.
Avatar of kcalder

ASKER

I'm sorry but one of my answers appears to be repeating itself for some reason. Please ignore it. I shall implement the alert routines and get back to you in a bit. Thanks.
Avatar of kcalder

ASKER

Sorry about the delay, bearing in mind your earlier comment that the routine should work I have reconfigured the page and, lo and behold, it now works fine.

I am very grateful to you for going the extra mile with me on this problem, and for all your valuable input.
Avatar of kcalder

ASKER

It is great that this kind of support is available out there. In particular strengthens my belief in the open-source philosphy and the generous attitude of the open-source community.