Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations strongm on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Dependent Select Boxes

Status
Not open for further replies.

PCHomepage

Programmer
Feb 24, 2009
609
US
I created a relatively simply function that gets its input from another select box. There are actualy six of each in two rows making twelve select boxes all together that are used for building up a dynamic query but I'm have a little trouble figuring out the logic. They are working togeother but not as needed.

In the blue lines below, I am trying to retrieve the values being submitted by the previous select box so that I can have the next one bring up only valid results but when I select one, no values appear in any.

In the red area, I am trying to create an array of values from multiple choices but the choices are not numeric and I can't seen to get then enquoted even though I have a function for doing so. When I try, it gives an array of all values including the empty ones. It is the array that is to be used in the next select box to know what to choose but as it exists only in the previous select box, I am not sure how to pass it on from select box to select box AND accumulate the array values from one to the next so that each successive select box fetches only valid entries. Only the Value2 and forward select boxes need the information

Also, the boxes need to auto-submit AND allow multiple choices but it seems to remember only one choice. Any ideas?

The function input variables are:

[bold]$NameVal[/bold] is the name of the select box, ie: Value1, Value2 etc.
[bold]$DefaultVal[/bold] is what shows at the top of the list box as a label, ie "Value 1", "Value 2" etc.
[bold]$SelectVal[/bold] is the value coming into it from the select box above it that tells it what data to select

Code:
function Value($NameVal, $DefaultVal, $SelectVal, $mysqli) {
	global $Selected;
	global $Where;

	[COLOR=blue]// Create variables from posts
	$Value1 = ($NameVal != "Value1") ? $_POST['Value1'][0]: $Value1 = "";	
	$Value2 = ($NameVal != "Value2") ? $_POST['Value2'][0]: $Value2 = "";
	$Value3 = ($NameVal != "Value3") ? $_POST['Value3'][0]: $Value3 = "";
	$Value4 = ($NameVal != "Value4") ? $_POST['Value4'][0]: $Value4 = "";
	$Value5 = ($NameVal != "Value5") ? $_POST['Value5'][0]: $Value5 = "";
	$Value6 = ($NameVal != "Value6") ? $_POST['Value6'][0]: $Value6 = "";
	
	$Values = array($Value1, $Value2, $Value3, $Value4, $Value5, $Value6);[/color]

	[COLOR=red]// Build array of values already selected
	if (is_array($Values) && count(array_filter($Values)) > 0):
		$Value = array_filter($Values);
		$AndID = " (";		
		$AndID .= implode(",", $Value);
		$AndID .= ") ";
	endif;[/color]
		
	if ($SelectVal) $Where = " WHERE KeyID = ".$SelectVal;
	if (!$SelectVal) $Where = " WHERE KeyID = IS NULL"; // Keeps select box depopulated until needed
	if ($AndID) $Where .= " AND QEValue IN ".$AndID;
	
	$Query = " SELECT DISTINCT Value, ID FROM keys ".$Where." GROUP BY Value ORDER BY Value";
	
	$Multiple = " multiple=\"multiple\"";
	$NumRows = " size=\"5\"";
	$AddRow = "<option value=\"\"></option>\n";
	$OnChange = " onchange='this.form.submit()'";

	$Output = "<select name=\"".$NameVal."[]\" id=\"".$NameVal."[]\" class=\"SelectBox\"".$OnChange.$Multiple.$NumRows.">\n";
	$Output .= "<option value=\"\">".$DefaultVal."</option>\n";
	if ($Where):
		if ($result = $mysqli->query($Query)):
			$i = 0;
			while ($row = $result->fetch_row()):
				$Selected = ($row[0] == $_POST[$NameVal][0]) ? $Selected = " SELECTED" : $Selected = "";
				$Output .= "<option value=\"".$row[0]."\"".$Selected.">".$row[0]."</option>\n";
				$i++;
			endwhile;
			$result->close();
		endif;
	endif;
	$Output .= "</select>";
	echo $Output;
}
 
I am not exactly sure what you mean as I don't have $value = $_POST['Value1']; anywhere but I DO have $Value1 = $_POST['Value1];. Is this what you meant? If so, I had tried it before posting with it remarked out and it's remarked out now but it has made no appreciable difference.

The array is actually being built and a variable created with it for use in the query. However, the values are not integer so need to somehow be quoted to work in a query but they are not. When I try to enquote them as below using another function I have for the purpose, then the variable also includes any empty fields for all six choices so the query breaks.

Code:
[COLOR=red]// Build array of values already selected
if (is_array($Values) && count(array_filter($Values)) > 0):
	$Value = array_filter($Values);
	$AndID = " (";		
	$AndID .= enquote(implode(",", $Value));
	$AndID .= ") ";
endif;[/color]

Used as above, the value gets quoted but then all select boxes no longer contain any data so it is breaking it somehow.

That aside, each sequential select box somehow needs to build on the set of values from the previous one rather than creating its own and I am rather stumpted on how that could be done! Any ideas?

Also, I changed the onchange to onblur and it now lets me choose multiple selections but it doesn't mark them as "selected". If only a single value is selected, it is properly marked but multiple ones are not and none are marked. If it works with one, shouldn't it work with multiple or did I miss something?
 
sorry, the field $_POST['A_Select_ID'] doesn't have indecies, so don't use them.

PHP:
$value1 = $_POST['Value1'][s][0][/s];

The red part depends on the blue part. The blue part doesn't work, thus, the red part won't work. Once you fix the blue part, the red part should work. Also, [tt]array_filter[/tt] is used to apply a callback function. There is no pratical use here. You can reduce the red part:

Code:
$AndID = (is_array($Values) && count($Values)) ? " (".implode(",", $Values).")";

-Geates

"I do not offer answers, only considerations."
- Geates's Disclaimer

 
Thank you! I thought that because it was sending multiple choices that it needed to have indecies but I was thinking of something I had just done with checkboxes that did indeed need them. Your conditional is much cleaner than mine too so thanks for that. I had to use array_filter() originally to make it give a proper array without empty values that it was giving before. I'm not sure if it needs it now or not.

I figured out how to enquote the values too:

Code:
$AndID = (is_array($Values) && count($Values)) ? " (".implode(",", array_map('enquote', $Values)).")";

It seems to somehow have lost the ability to generate search results as the result section is expecting an array so the explode() and array_map() functions used there give errors.

However, no matter how many selections are made in the Value select boxes, it submits (and now remembers) only a single one. It must remember every one and I see nothing to account for it not working since it's inside a loop! I can't imagine that it might need a secondary loop for the SELECTED status but maybe it does.

 
it looks like quite a bit of your sql is wrong.

i've not focused on the bit of your code below the sql generation but first looks are that you've not picked an optimum/easy route for the task. but i may not really understand the aim without seeing the rendered html.

here is some recast code that deals with the sql generation

Code:
<?php

function enquote($val){
	if(!is_array($val) && !is_object($val)) return "'" . mysql_real_escape_string($val) ."'";
}

function Value($NameVal, $DefaultVal, $SelectVal, $mysqli){
	
	global $Selected, $Where;
	
	$Values = array();
	for($i=1; $i<=6; $i++):
		$s = 'Value'.$i;
		if( $NameVal != $s && isset( $_POST[$s] ) $Values[] = $_POST[$s];
	endfor;
	
	//reduce the array to unique values
	$Values = array_unique($Values);
	
	
	$Where = $SelectVal ? " WHERE KeyID = " . enquote($SelectVal)
						: " WHERE KeyID IS NULL"; // Keeps select box depopulated until needed
	
	//enquote the values
	if(count($Values) > 0):
		$Values = array_map('enquote', $Values);
		$Where .= " AND QEValue IN (" . implode( ',', $Values . ')';
	endif;
	
	$Query = " SELECT DISTINCT Value, ID FROM keys " . $Where . " GROUP BY Value ORDER BY Value";
	//....	
}
 
Thank you, I'll take a look tonight when I'm back home. In the meantime, I realized that I would need to change the tactic slightly in order to get successive selectors to give the proper information so rather than fetching the posts within the function, I am feeding them in as an array from the search page that has all the function calls. I don't have it here with me though. It is now to the point where even with multiple selections it is submitting them all but remembering only one of them.
 
the usual way is to use form input to modify a session variable and then use the session variable to build an query .
that way state is maintained.
 
Thank you. I thought of that but I wasn't sure if it needed to be done as an array or separate values for each submit. How would you approach it? This particular form is the most difficult because it has the twelve selects with selections in the top row affecting choices in the rest of the top row by not allowing the same choices (that's working) but also showing in the bottom row any valid resulte from the accumulation of all the choices (not wprking). The other forms have just a single row like the first form's bottom row.
 
Without seeing a mock up of the rendered form and a list of business rules for each choice I cannot visualise the task I'm afraid.
 
I'll see what I can do but the main concern at the moment is to fix the issue of only one choice remembering the selection. Without that working, it's impossible to test the rest.
 
in specie, your sql is incorrect and will not have worked. perhaps fixing that will allow the testing to continue.
 
ps. by 'mock up' I mean that you create the form you want in html not php. then draw or type or whatever the relationships and which bits are disabled for which choices, and then explain the business rules. as if you were presenting the rules to a designer to have the form built for you.

then we can advise on the best approach and how to implement the approach.
 
Thank you and I'll see what I can do but maybe the outline below will help. I was trying to avoid another long post!

In the meantime I ran with your idea and created a separate Value() function that outputs the WHERE when called within the other function but it gives an usuable output of AND QEValue IN ('Array'). I am actually inputting an array of values to it but I am still trying to verify the input to be sure it's valid for the needs of the function although I think it is. I know you hadn't intended on it being a separate function but it seemed a good idea since the one I had posted origianlly is now totally different.

To encapsulate the needs or this process, the form has 12 selectors. The upper row of single-item select boxes and uses one function and there is now a second function for the second row of multi-selects but my original posting used a different function name and was an attempt to combine them but I think the two rows are too dissimilar to make that feasable.

There are six select boxes in the top row and only the first should have any values by default. It's okay that all are fully populated by default but it's not preferred. Once a value has been selected, the results should appear in the select box immediately below it AND all other choices EXCEPT the one just selected in the first box should be appear in the second box but ONLY when the choices are also part of the selections in the previous second row select box. (It is this bit that has me stuck. I know SQL but can't seem to work out the programming to make it happen.)

When a choice is made in the second box of the first row, the box immediate below it needs to show values appropriate for it AND for the previous choice, and so on down the line. Ultimately this is generating a list of files that contain all the values selected in the second row. Note that the first row submits the ID from a keywords table while the second row submits the actual value itself. Each selection or selections submits itself so the resulting list of files schrinks as the choices are narrowed down.

The generated form is rather too long to post but here is a cut-down version of it with only three dynamic values and the selected results:

Code:
<p>Select Keywords:<br>
<select name="Name1" id="Name1" class="SelectBox" onchange='this.form.submit()' size="1">
<option value="">Name 1</option>
<option value="28" SELECTED>chipname</option>
<option value="30">datatype</option>
<option value="31">date</option>
</select>

<select name="Name2" id="Name2" class="SelectBox" onchange='this.form.submit()' size="1">
<option value="">Name 2</option>
<option value="28">chipname</option>
<option value="30" SELECTED>datatype</option>
<option value="31">date</option>
</select>

<select name="Name3" id="Name3" class="SelectBox" onchange='this.form.submit()' size="1">
<option value="">Name 3</option>
<option value="28">chipname</option>
<option value="30">datatype</option>
<option value="31" SELECTED>date</option>
</select>

<select name="Name4" id="Name4" class="SelectBox" onchange='this.form.submit()' size="1">
<option value="">Name 4</option>
<option value="28">chipname</option>
<option value="30">datatype</option>
<option value="31">date</option>
</select>

<select name="Name5" id="Name5" class="SelectBox" onchange='this.form.submit()' size="1">
<option value="">Name 5</option>
<option value="28">chipname</option>
<option value="30">datatype</option>
<option value="31">date</option>
</select>

<select name="Name6" id="Name6" class="SelectBox" onchange='this.form.submit()' size="1">
<option value="">Name 6</option>
<option value="28">chipname</option>
<option value="30">datatype</option>
<option value="31">date</option>
</select>

<p>Select Values:<br>
<select name="Value1[]" id="Value1" class="SelectBox" onblur='this.form.submit()' multiple="multiple" size="5">
<option value="">Value 1</option>
<option value="C1BA">C1BA</option>
<option value="C1CA">C1CA</option>
<option value="C1EA">C1EA</option>
</select>

<select name="Value2[]" id="Value2" class="SelectBox" onblur='this.form.submit()' multiple="multiple" size="5">
<option value="">Value 2</option>
<option value="exp">exp</option>
</select>

<select name="Value3[]" id="Value3" class="SelectBox" onblur='this.form.submit()' multiple="multiple" size="5">
<option value="">Value 3</option>
<option value="2007.11.06">2007.11.06</option>
<option value="2008.01.14">2008.01.14</option>
<option value="2008.09.18">2008.09.18</option>
<option value="2008.10.27">2008.10.27</option>
</select>

<select name="Value4[]" id="Value4" class="SelectBox" onblur='this.form.submit()' multiple="multiple" size="5">
<option value="">Value 4</option>
</select>

<select name="Value5[]" id="Value5" class="SelectBox" onblur='this.form.submit()' multiple="multiple" size="5">
<option value="">Value 5</option>
</select>

<select name="Value6[]" id="Value6" class="SelectBox" onblur='this.form.submit()' multiple="multiple" size="5">
<option value="">Value 6</option>
</select><br>

<p>Other Keywords: <input type="text" name="Keywords" size="25" value="">
<input type="submit" value="Submit">
<input type="reset" value="Clear" name="Clear" onClick="window.location.href='/dcstest/indextest.php'">
</form>
 
I had this partially working and it is still selecting data but somehow it no longer de-selects the chosen values in the first row. $InNames is an array of values being submitted by the first row of select boxes, $InValues is an array of values being submitted by the second row. This function is supposed to create the select boxes for only the first row of select boxes.

Code:
function QEKeyword($NameVal, $DefaultName, $InNames, $InValues, $mysqli) {
	global $Output;

	// Build variable of values already selected, prevent deselecting self
	$AndID = (is_array($InNames) && count($InNames) > 0 && $InNames != "()" && $InNames != "(".$NameVal.")") ? " (".implode(",", array_map('enquote',array_filter($InNames))).")": "";

	// Create variable from Value posts to find only relevant values
	$ValuesIn = (is_array($InValues) && count($InValues)) ? " (".implode(",", array_map('enquote',array_filter($InValues))).")": "";
	$NamesIn = (is_array($InNames) && count($InNames)) ? " (".implode(",", array_map('enquote',array_filter($InNames))).")": "";
	
	$Exceptions = array('color','CCM','SNR','NG','Max','Mean','peak' );

	reset($Exceptions);

	while (list (, $value) = each ($Exceptions)):
		$NotLikeWhere .= " AND KeyName NOT LIKE '".$value."%' ";
	endwhile;

	$Where = " WHERE KeyName IS NOT NULL ".$NotLikeWhere;

	if (isset($_POST[$NameVal])):
		$Where .= Value($InNames, $_POST[$NameVal], " NOT IN ", "KeyID", $mysqli);
	endif;
		
	$Query = " SELECT DISTINCT KeyName, KeyID 
				FROM dcs_keys ".$Where." 
				GROUP BY KeyName 
				ORDER BY KeyName";							
				
	$Multiple = "";
	$NumRows = " size=\"1\"";
	$AddRow = "";
	$OnChange = " onchange='this.form.submit()'";

	$Output .= "<select name=\"".$NameVal."\" id=\"".$NameVal."\" class=\"SelectBox\"".$OnChange.$Multiple.$NumRows.">\n";
	$Output .= "<option value=\"\">".$DefaultName."</option>\n";
	
	if ($Where):
		$Output .= "$AddRow";
		if ($result = $mysqli->query($Query)):
			$i = 0;
			while ($row = $result->fetch_row()):
				$Selected = ($row[1] == $_POST[$NameVal]) ? $Selected = " SELECTED" : $Selected = "";
				$Output .= "<option value=\"".$row[1]."\"".$Selected.">".substr(basename($row[0], '.csv'),0,50)."</option>\n";
				$i++;
			endwhile;
			$result->close();
		endif;
	endif;
	echo $Output;
}

This is the function that creates the second select box row. It has the same arrays available but I've not yet tried to incorporate the values:

Code:
function QEValue($NameValue, $DefaultValue, $NameIn, $ValuesIn, $mysqli) {
	global $Selected;

	$AndID = (is_array($ValuesIn) && count($ValuesIn)) ? " AND QEValue IN (".implode(",", array_map('enquote', array_filter($ValuesIn))).")": "";

	$Where = ($NameIn)  ? " WHERE KeyID = ".$NameIn: " WHERE KeyID = IS NULL";
	$Where .= (is_array($AndID)) ? $AndID: "";
	
	$Query = " SELECT DISTINCT QEValue, ID FROM dcs_keys ".$Where." GROUP BY QEValue ORDER BY QEValue";
	
	$Multiple = " multiple=\"multiple\"";
	$NumRows = " size=\"5\"";
	$AddRow = "<option value=\"\"></option>\n";
	$OnChange = " onblur='this.form.submit()'";

	$Output = "<select name=\"".$NameValue."[]\" id=\"".$NameValue."\" class=\"SelectBox\"".$OnChange.$Multiple.$NumRows.">\n";
	$Output .= "<option value=\"\">".$DefaultValue."</option>\n";
	if ($Where):
		if ($result = $mysqli->query($Query)):
			$i = 0;
			while ($row = $result->fetch_row()):
				$Selected = ($row[0] == $_POST[$NameValue]) ? $Selected = " SELECTED" : $Selected = "";
				$Output .= "<option value=\"".$row[0]."\"".$Selected.">".substr(basename($row[0], '.csv'),0,50)."</option>\n";
				$i++;
			endwhile;
			$result->close();
		endif;
	endif;
	$Output .= "</select>";
	echo $Output;
}
 
I think I see.

so each column is a filter and you can have up to six filters on the dataset?
but from a UI perspective you don't want a filter visible until the previous filter has been set?

Assumedly each filter is an AND but the internals of each filter are OR (given that you allow multiple values).

Assuming this is right, it's very straightforward.
 
given that each value select is a multiple, I'm a little confused as to why there are three options for the first select but up to six columns in all. surely the last three are nugatory?
 
For the sake of space, I edited the source code down to three Name values but even so, a given Name selection can pull up dozens of Value selections. I suppose for clarity I should have left six of them there but in actuality there are about twenty or so choices all together and what I am really looking for is a straightforward function(s) similar to the one I've started but, of course, working!
 
We are miscommunicating.
In row 1 col 1 there is a select with only three choices. So there are only three things on which a user can filter.
The filtering values are selectable in multi selects.
So why do you need more columns than there are values in the first select?

Until I get a handle on the choices it is too hard for me to derive a general rule with which to assemble a function to generate a query.

 
No, I showed only three for the sake of compactness in the posting here on this forum but there are actually 19 choices for each. The user doesn't necessary need all of the filters every time to get what they're looking for but at times they do. As the database grows, the need to filter becomes more and more important.

In any event, this is now working as far as the upper selects not repeating but, because the second row of selects from the other function allow for multiple selections, it submits a multi-dimensional array that this is not quite handling properly. Instead of creating (see the area in red, below) as it should:

[bold]WHERE KeyName IN ('C1BA','C1CA','K46B')[/bold]

It is getting only:

[bold]WHERE KeyName IN ('Array')[/bold]

The array from this function is simple:

Code:
Array
(
    [0] => 28
    [1] => 
    [2] => 
    [3] => 
    [4] => 
    [5] => 
)

while the second row from the other function submits this and I don't know how to handle the indices:

Code:
Array
(
    [0] => Array
        (
            [0] => C1BA
            [1] => C1CA
            [2] => K46B
        )

    [1] => 
    [2] => 
    [3] => 
    [4] => 
    [5] => 
)

Code:
function QEKeyword($NameVal, $DefaultName, $InNames, $InValues, $mysqli) {

	$Exceptions = array('color','CCM','SNR','NG','Max','Mean','peak' );

	reset($Exceptions);
	while (list (, $value) = each ($Exceptions)):
		$NotLikeWhere .= " AND KeyName NOT LIKE '".$value."%' ";
	endwhile;

	$Where = " WHERE KeyName IS NOT NULL ".$NotLikeWhere;

	// Clean up the array, remove empty values and self
	$InNames = array_diff(array_filter($InNames), array($_POST[$NameVal]));
	$InValues = array_filter($InValues);

	// Build variable of values already selected, prevent deselecting self
	if (is_array($InNames) && count($InNames) > 0):
		$AndID = " (";		
		$AndID .= implode(",", $InNames);
		$AndID .= ") ";
		$Where .= " AND KeyID NOT IN ".$AndID;
	endif;	

	[COLOR=red]// Build variable of values already selected, select only relevant entries
	if (is_array($InValues) && count($InValues) > 0):
		$AndValue = " (";		
		$AndValue .= implode(",", array_map('enquote', $InValues));
		$AndValue .= ") ";
		$Where .= " AND KeyID IN SELECT KeyID FROM dcs_keys WHERE KeyName IN ".$AndValue;
	endif;[/color]

	$Query = " SELECT DISTINCT KeyName, KeyID 
				FROM dcs_keys ".$Where." 
				GROUP BY KeyName 
				ORDER BY KeyName";							

	$Multiple = "";
	$NumRows = " size=\"1\"";
	$AddRow = "";
	$OnChange = " onchange='this.form.submit()'";

	$Output = "<select name=\"".$NameVal."\" id=\"".$NameVal."\" class=\"SelectBox\"".$OnChange.$Multiple.$NumRows.">\n";
	$Output .= "<option value=\"\">".$DefaultName."</option>\n";

	if ($Where):
		$Output .= "$AddRow";
		if ($result = $mysqli->query($Query)):
			$i = 0;
			while ($row = $result->fetch_row()):
				$Selected = ($row[1] == $_POST[$NameVal]) ? $Selected = " SELECTED" : $Selected = "";
				$Output .= "<option value=\"".$row[1]."\"".$Selected.">".substr(basename($row[0], '.csv'),0,50)."</option>\n";
				$i++;
			endwhile;
			$result->close();
		endif;
	endif;
	$Output .= "</select>";
	echo $Output;
}
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top