Kind of a long title, but I wasn't sure how to summarize this. I have a page that I use to dynamically build a query into a MySQL database. It has two fields, one for the field and one for the value. There's then JavaScript that adds additional rows if I want to expand the query. So dropdown #1 is Customer/ Month/ Item, etc, and dropdown #2 will list the customers (if that is selected in #1, the months, the items, etc). This page works really well, and I'm able to build a query for a certain Customer, Month and Item. What I want to do now is to be able to link into this page, by sending some parameters in the URL. Of course, I want to parameterize it, and not just take the values as is from the URL and I was planning on doing that in part by only setting the values in the drop down, then 'submitting' the PHP page - the submit button really just kicks off another JS function to build the result table.

So my PHP page looks something like this:

    
<div id="dynamicInput"> <div id="inputSet[1]"> <div id="qryField[1]" style="float:left"> <select name="fieldName[1]" id="fieldName[1]" class="form_data" onChange="getValues(this.value,'1')"> <option value=""></option> <? $sql = "select table_name, field_name, filter_disp from filter_fields order by filter_disp"; $result = mysql_query($sql) or die(mysql_error()); while($row=mysql_fetch_array($result)){ echo '<option value="'.$row['table_name'].'.'.$row['field_name'].'">'.$row['filter_disp'].'</option>'; } ?> </select> </div> <div id="qryValue[1]" style="float:left"> <select name="fieldValue[1]" class="form_data"> </select> </div>

The JS for getValues, then looks like this:

function getValues(value,fieldId){
  if(value != ""){
    xmlhttp=new XMLHttpRequest();
    xmlhttp.onreadystatechange=function()
    {
      if (xmlhttp.readyState==4 && xmlhttp.status==200)
        {
          var newdiv = "";
          newdiv = "qryValue["+fieldId+"]";
          document.getElementById(newdiv).innerHTML=xmlhttp.responseText;
        }
    }
    xmlhttp.open("GET","getValues.php?field="+value+"&divId="+fieldId,true);
    xmlhttp.send();
  }
}

So the PHP page loads with only one row of two drop down boxes, the second of which is blank until you select which filter should be used. I think I'm running into some difficulties with asynchronous JS, but I'm really struggling to wrap my head around this for some reason. Am I thinking about this the wrong way? Is there an easier way to have a dynamic number of items on a page that will be set with parameters in a URL? And retrieved from MySQL based on prior input?

Thanks!

    You are kind of mixing ordinary page requests (reloading the same page) with XHR (where you keep the same page but modifies its contents). Page reloads is the old and clumsy way of doing it. Using page reloads may seem simpler initially, since you don't have to worry about javascript, but it is harder to get good code structure, as well as follow / read the code.

    But for reference, this is one way to go about it. Javascript and xhr in the next post.

    <?php
    header ('content-type: text/html; charset=utf-8');
    
    /* While you may have this code in the exact spot where the output for the second select
     * would be, it's generally considered bad practice to be mixing logic with presentation.
     * If you're ever chaning one, you may affect the other… Thus, fetch and process your data
     * separately.
     */
    function sub_group_select($group, $indent_level = 0)
    {
    	$nl = "\n";
    	$tab = "\t";
    	if (empty($group))
    		return '';
    
    /* I've just removed the logic needed to retrieve the data, and assume
    	that it's returned as an array of rows with fields "value" and "text" */
    if ($group == 1)
    	$rows = get_customer_data();
    if ($group == 2)
    	$rows = get_months();
    if ($group == 3)
    	$rows = get_items();
    
    $options = array();
    foreach ($rows as $row)
    	$options[] = sprintf('<option value="%d">%s</option>',
    		(int) $row['value'] /* always escape data as needed */,
    		htmlspecialchars($row['text']) /* depending on what type it should be */
    	);
    $select = sprintf('%s%s<select name="subgroup">
    %s%s<option value="0">Please select…</option>%s</select>',
    		$nl, str_repeat($tab, $indent_level),
    		$nl, str_repeat($tab, $indent_level + 1),
    		implode("\n" . str_repeat($tab, $indent_level + 1), $options)
    	);
    
    return $select;
    }
    
    /* generate dummy data instead of db calls */
    function get_customer_data()
    {
    	return array(
    		array('value' => 1, 'text' => 'John'),
    		array('value' => 2, 'text' => 'Sue'),
    		array('value' => 3, 'text' => 'Ellen'),
    	);
    }
    /* i realize that this is probably something other than actual months */
    function get_months()
    {
    	return array(
    		array('value' => 1, 'text' => 'January'),
    		array('value' => 2, 'text' => 'February'),
    		array('value' => 3, 'text' => 'March'),
    	);
    }
    /* i realize that this is probably something other than actual months */
    function get_items()
    {
    	return array(
    		array('value' => 1, 'text' => 'Mimes'),
    		array('value' => 2, 'text' => 'Hair Extensions'),
    		array('value' => 3, 'text' => 'Milli Vanilli CDs'),
    	);
    }
    
    /* return "selected" when matching given value */
    function selected_if_get($value, $field, $checked = false)
    {
    	if (!isset($_GET[$field]))
    		return '';
    	if ($value === $_GET[$field])
    		return $checked ? 'checked' : 'selected';
    }
    ?><!DOCTYPE html>
    <html>
    <head>
    <title>Page reloads</title>
    <style>
    form {
    	width: 20em;
    }
    .hide {
    	display: none;
    }
    </style>
    </head>
    <body>
    <div>
    <!-- get method will put the form values in the url as query string parameters.
    	Thus, you may just as well link directly to the page with the same parameters set -->
    <form action="" method="GET">
    	<fieldset>
    		<legend>Type</legend>
    		<select name="group">
    			<option value="0">Please choose…</option>
    			<option value="1" <?php echo selected_if_get('1', 'group'); ?>>Customer</option>
    			<option value="2" <?php echo selected_if_get('2', 'group'); ?>>Month</option>
    			<option value="3" <?php echo selected_if_get('3', 'group'); ?>>Item</option>
    		</select>
    	</fieldset>
    	<fieldset class="<?php echo empty($_GET['group']) ? 'hide' : ''; ?>">
    		<legend>Subgroup, dependent on the above choice</legend>
    	<?php
    		echo sub_group_select(isset($_GET['group']) ? $_GET['group'] : null, 2);
    	?>
    	</fieldset>
    	<div>
    		<input type="submit" value="Submit">
    	</div>
    </form>
    </div>
    </body>
    </html>
    

      I'm using jQuery for the XHR version and I strongly recommend you do the same. Or choose some other javascript library. Otherwise you will have to deal with browser inconsistencies on your own and that's a time sink.

      This solution is one-page. That is, one single html page is all that is requested from the server in the "normal" way. Then xhr is used to fetch more data to modify the existing page.

      <!DOCTYPE html>
      <html>
      <head>
      <title>Page reloads</title>
      <meta charset="utf-8">
      <style>
      form {
      	width: 20em;
      }
      .hide {
      	display: none;
      }
      </style>
      <script src="/js/jquery-1.9.1.min.js"></script>
      <script>
      /* this is how you get a function to run once the document has finished loading
      	when using jQuery. The function will be passed jQuery as a function parameter
      	which means you can bind it to anything you want ($ being the common choice)
      */
      jQuery(document).ready(function ($) {
      	/* set up event listener for button - you'd probably use an onchange listener
      		on the "group" select element instead. But I stick with the button to keep
      		as close to the "page reload" scenario as possible for comparison reasons.
      	*/
      	$('#group-submit').on('click', function(evt) {
      		/* you'd probably use an id for the first select element as well, but this
      			shows how you can use any CSS-style way of selecting elements.
      			this is the element recieving the event notification, i.e. the button
      		*/
      		var val = $(this.form).find('select[name="group"]').prop('value');
      		if (val == 0)
      		{
      			$('#subgroup').parent().addClass('hide');
      			return;
      		}
      		else
      		{
      			/* $(this.form) runs the jQuery constructor on the form element.
      				Assigning its return value means we only run the constructor function
      				once, not once every time we need to access the form
      			*/
      			$f = $(this.form);
      			var opts = {
      				url : $f.attr('action'),
      				method : $f.attr('method'),
      				data : $f.serialize()
      			}
      			/* data is what's returned from the server, possibly pre-parsed according to
      				settings in the options object
      			*/
      			$.ajax(opts).done(function(data, textStatus, jQueryXHRObject) {
      				$('#subgroup').html(data).parent().removeClass('hide');
      			});
      		}
      	});
      
      });
      </script>
      </head>
      <body>
      <div>
      <form action="a_test.php" method="GET">
      	<fieldset>
      		<legend>Type</legend>
      		<select name="group">
      			<option value="0">Please choose…</option>
      			<option value="1">Customer</option>
      			<option value="2">Month</option>
      			<option value="3">Item</option>
      		</select>
      	</fieldset>
      	<fieldset class="hide">
      		<legend>Subgroup, dependent on the above choice</legend>
      		<select id="subgroup"></select>
      	</fieldset>
      	<div>
      		<button id="group-submit" type="button">Submit</button>
      	</div>
      </form>
      </div>
      </body>
      </html>
      
      <?php
      header ('content-type: text/html; charset=utf-8');
      
      /* The good thing about the previous separation of code from presentation
       * is that we can reuse it with little modification. Imagine if this had all been in the middle of
       * the actual html code.
       */
      function sub_group_select($group)
      {
      	if (empty($group))
      		return '';
      
      /* I've just removed the logic needed to retrieve the data, and assume
      	that it's returned as an array of rows with fields "value" and "text" */
      if ($group == 1)
      	$rows = get_customer_data();
      if ($group == 2)
      	$rows = get_months();
      if ($group == 3)
      	$rows = get_items();
      
      $options = array('<option value="0">Please select…</option>');
      foreach ($rows as $row)
      {
      	$options[] = sprintf('<option value="%d">%s</option>',
      		(int) $row['value'] /* always escape data as needed */,
      		htmlspecialchars($row['text']) /* depending on what type it should be */
      	);
      }
      
      return implode('', $options);
      }
      
      /* generate dummy data instead of db calls */
      function get_customer_data()
      {
      	return array(
      		array('value' => 1, 'text' => 'John'),
      		array('value' => 2, 'text' => 'Sue'),
      		array('value' => 3, 'text' => 'Ellen'),
      	);
      }
      /* i realize that this is probably something other than actual months */
      function get_months()
      {
      	return array(
      		array('value' => 1, 'text' => 'January'),
      		array('value' => 2, 'text' => 'February'),
      		array('value' => 3, 'text' => 'March'),
      	);
      }
      /* i realize that this is probably something other than actual months */
      function get_items()
      {
      	return array(
      		array('value' => 1, 'text' => 'Mimes'),
      		array('value' => 2, 'text' => 'Hair Extensions'),
      		array('value' => 3, 'text' => 'Milli Vanilli CDs'),
      	);
      }
      
      if (empty($_GET['group']))
      	echo '';
      else
      	echo sub_group_select($_GET['group']);
      exit
      ?>
      

        Wow - thank you for the very indepth response. I'm reading through it now and will try to apply the principles you mentioned shortly. I had JQuery as a JS source on my page, but I guess I was only using it for a few very small purposes.

          6 days later

          I learned quite a bit from what you posted and ran with a few ideas to change this around a bit. I'm sticking with the simple example that you suggested as a learning tool before I try to apply it to my actual database code. The first thing is that I'm not sure if I clearly explained the intent of the form. I have multiple sets of two selects. The first select is the field to query by, and the second is the value. Then I want the user to click a button to add an additional set of two selects, and so on. Then when the form is finally submitted, if the user added three sets of selects, it would be able to return a table of transactions for a particular item by a customer in a single month. I made those changes to the example that you gave back to me as such:

          query.html:

          <!DOCTYPE html>
          <html>
          <head>
          <title>Page reloads</title>
          <meta charset="utf-8">
          <style>
          form {
          	width: 20em;
          }
          .hide {
          	display: none;
          }
          </style>
          <script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
          <script>
          jQuery(document).ready(function ($) {
              $('#add-query-element').on('click', function(evt) {
              var numFields = $(this.form).find('input[id="numFields"]').prop('value');
              var newFieldId = Number(numFields)+1;
              $('#numFields').val(newFieldId);
              $f = $(this.form);
              var opts = {
                url : $f.attr('action'),
                method : $f.attr('method'),
                data : $f.serialize()+'&cmd=add_field'
              }
              $.ajax(opts).done(function(data, textStatus, jQueryXHRObject) {
                var groupSelect = '<select name="group['+newFieldId+']" id="group['+newFieldId+']" class="group">';
                var subGroupSelect = '</select><select name="subgroup['+newFieldId+']" id="subgroup['+newFieldId+']" class="subgroup"></select>';
                data = groupSelect + data + subGroupSelect;
                $('<div>', 
                { 
                    id: 'input['+newFieldId+']',
                    html: data
                }).appendTo('#inputs');
                $('#group['+newFieldId+']').html(data);
              });
          	});
          	$('#submit-query').on('click', function(evt) {
          			$f = $(this.form);
          			var opts = {
          				url : $f.attr('action'),
          				method : $f.attr('method'),
          				data : $f.serialize()+'&cmd=submit_query'
          			}
          			$.ajax(opts).done(function(data, textStatus, jQueryXHRObject) {
          				$('#show-query').html(data);
          			});
          });
            $('.group').change(function() {
            alert('changed');
              var groupId = $(this).val();
              var groupFieldId = $(this).attr('id');
              $f = $(this.form);
              var opts = {
                url : $f.attr('action'),
                method : $f.attr('method'),
                data : 'group='+groupId+'&cmd=on_change'
              }
              $.ajax(opts).done(function(data, textStatus, jQueryXHRObject) {
          				$f.find('select[name="sub'+groupFieldId+'"]').html(data).parent();
          			});
            });
          });
          </script>
          </head>
          <body>
          <div>
          <form action="form_send.php" method="GET">
          	<fieldset>
          		<legend>Type & Value</legend>
          		<div id='inputs'>
                <input type="text" name="numFields" id="numFields" value=1 /><br />
                <div id='input[1]'>
                  <select name="group[1]" id="group[1]" class="group">
                    <option value="0">Please choose…</option>
                    <option value="1">Customer</option>
                    <option value="2">Month</option>
                    <option value="3">Item</option>
                  </select>
                  <select name="subgroup[1]" id="subgroup[1]" class="subgroup"></select>
                </div>
          		</div>
          	</fieldset>
          	<div id="buttons">
          		<button id="add-query-element" type="button">Add A Qualifier</button>
          		<button id="submit-query" type="button">Submit the Query</button>
          	</div>
          	<div id="show-query"></div>
          </form>
          </div>
          </body>
          </html>

          And then form_send.php:

          <?php 
          header ('content-type: text/html; charset=utf-8'); 
          
          /* The good thing about the previous separation of code from presentation 
           * is that we can reuse it with little modification. Imagine if this had all been in the middle of 
           * the actual html code. 
           */ 
          function get_query_fields()
          {
            $rows = array(array('value' => 1, 'text' => 'Customer'), 
                          array('value' => 2, 'text' => 'Month'), 
                          array('value' => 3, 'text' => 'Item'),);
            $options = array('<option value="0">Please select</option>');
            foreach ($rows as $row) 
              { 
                  $options[] = sprintf('<option value="%d">%s</option>', 
                      (int) $row['value'] /* always escape data as needed */, 
                      htmlspecialchars($row['text']) /* depending on what type it should be */ 
                  ); 
              } 
              return implode('', $options); 
          }
          function sub_group_select($group) 
          { 
              if (empty($group)) 
                  return ''; 
          
          /* I've just removed the logic needed to retrieve the data, and assume 
              that it's returned as an array of rows with fields "value" and "text" */ 
          if ($group == 1) 
              $rows = get_customer_data(); 
          if ($group == 2) 
              $rows = get_months(); 
          if ($group == 3) 
              $rows = get_items(); 
          
          $options = array('<option value="0">Please select…</option>'); 
          foreach ($rows as $row) 
          { 
              $options[] = sprintf('<option value="%d">%s</option>', 
                  (int) $row['value'] /* always escape data as needed */, 
                  htmlspecialchars($row['text']) /* depending on what type it should be */ 
              ); 
          } 
          
          return implode('', $options); 
          } 
          
          /* generate dummy data instead of db calls */ 
          function get_customer_data() 
          { 
              return array( 
                  array('value' => 1, 'text' => 'John'), 
                  array('value' => 2, 'text' => 'Sue'), 
                  array('value' => 3, 'text' => 'Ellen'), 
              ); 
          } 
          /* i realize that this is probably something other than actual months */ 
          function get_months() 
          { 
              return array( 
                  array('value' => 1, 'text' => 'January'), 
                  array('value' => 2, 'text' => 'February'), 
                  array('value' => 3, 'text' => 'March'), 
              ); 
          } 
          /* i realize that this is probably something other than actual months */ 
          function get_items() 
          { 
              return array( 
                  array('value' => 1, 'text' => 'Mimes'), 
                  array('value' => 2, 'text' => 'Hair Extensions'), 
                  array('value' => 3, 'text' => 'Milli Vanilli CDs'), 
              ); 
          }
          function submit_query($group)
          {
            $sql = "select stuff from MySQL: ".implode(', ',$group);
            return ($sql);
          }
          if (empty($_GET['group'])) 
            echo 'nothing sent'; 
          else
            if($_GET['cmd'] == 'add_field')
            {
              echo get_query_fields();
            }elseif($_GET['cmd'] == 'submit_query')
            {
              echo submit_query($_GET['group']);
            }elseif($_GET['cmd'] == 'on_change')
            {
              echo sub_group_select($_GET['group']);
            }else{
              echo sub_group_select($_GET['group']);
            }
          exit
          ?>

          I'm still fumbling through a lot of this and learning along the way, but I'm hitting a wall at one particular point. When I click Add A Qualifier, I get a new set of two selects, so that's great, but then when I change the value in group[2], nothing happens. I don't get the alert that I added in to the $('.group').change(function(). Does .change() not recognize that I added a new field to the form with that class?

          Also - I'm not sure if I didn't come across properly, or if I just haven't had the ah ha moment yet with this, but how will I be able to pass into query.html the different parameters like query.html?group[1]=1&subgroup[1]=2&group[2]=2&subgroup[2]=1 (Customer = John and Month = January)? Will I just have to write some checking of the url parameters to see if they're there? But then how do I add the proper number of sets of selects.... I'm not sure I 'get' that yet, but I'm willing to keep at it.

          Thanks again for the great help!

            4 days later

            Does .change() not recognize that I added a new field to the form with that class?

            scambro;11032501 wrote:

            I

              $('.group').change(function() {
              alert('changed');
            

            No, it doesn't work exactly like that. The reason, or so I believe, is how events "travel around the DOM". Decently short exaplanation here: http://stackoverflow.com/questions/4616694/what-is-event-bubbling-and-capturing. As you add an event listener in jQuery, the full construct is

            $(target element(s)).on('event type (click / change)', 'further filtering (.group / button / etc)', eventHandler)
            

            What in reality happens here isn't that jQuery deals with adding more event handlers in the future. Or even adding several handlers now, unless the selector found several elements (as present in the DOM right now). I.e. if this

            $(target)
            

            finds one target element, then one target element has the event handler attached to itself. If it finds two elements, these get the event handler etc.

            The "further filtering" uses the bubbling functionality to decide what will activate the handler. Thus, if you use

            $('#inputs').on('change', '.group', handler)
            

            then (disregarding event capturing) the event will start at the element on which the action took place, the inner-most element which for example may be either of the selects (.group or .subgroup). Neither of these has an event handler attached (which could have prevented further bubbling - by calling stopPropagation() on the event object) and so the event bubbles upwards in the DOM. Eventually it reaches the element with id "inputs" which does have an event handler. And using jquery, it has been setup to check if the event originated from some element which matches ".group". If it does, your handler is run, if it doens't your handler isn't run.

            Thus, making use of this information, you'd simply move your handler up the tree to the select's parent: '#inputs' and use the "additional filtering" to make it count only for elements which match ".group"

              $('#inputs').on('change', '.group', function() {
            

              You should probably take a moment to thing about why you are adding several elements with numerical indexing.

              Usually, the user would need to specify each of those things at most once. Thus, it'd make more sense to have them all present to begin with in the html code and name them properly

              <select name="months">...</select>
              <select name="items">...</select>
              <select name="stuff">...</select>
              

              And if you want the user to be able to specify several months, several items or whatnot, it'd still make more sense to

              <fieldset>
                <legend>Months</legend>
                <select name="month[]">...</select>
                <button type="button">More (months)...</button>
              </fieldset>
              <fieldset>
                <legend>Items</legend>
                <select name="item[]">...</select>
                <button type="button">More (item)...</button>
              </fieldset>
              

              Also do note that you do not have to specify which item is which in the element order of the array month[] or the array item[]. The browser will create an array for you when serializing the form and sending it. In your php code, they will each get separate index numbers, 0 .. N-1.

              But in case you have a database table where your columns are named "group1", "group2" etc, or month1, month2 etc or similar, then you need to change your db schema by normalizing it.

                Ok, that makes sense about the bubbling stuff. I think I've read that exact stackoverflow page before and it didn't really hit home so it was a bit over my head, but I think I got it this time. I changed it to

                $('#inputs').on('change', '.group', function() {
                

                and it recognizes each 'change' like the onChange() did previously without JQuery.

                I think I realized that the php submission would auto-index those, but with the onChange() previously, I couldn't figure out how to know which field was which. I did some reading and removed the numerical index glue and tape I was doing and just use something like this now:

                  $('#inputs').on('change', '.group', function() {
                    var curGroup = $(this);
                    var groupId = $(this).val();
                    $f = $(this.form);
                    var opts = {
                      url : $f.attr('action'),
                      method : $f.attr('method'),
                      data : 'group='+groupId+'&cmd=on_change'
                    }
                    $.ajax(opts).done(function(data, textStatus, jQueryXHRObject) {
                      curGroup.nextAll('.subgroup').html(data);
                    });
                  });
                

                So that all makes sense as to why I should change the form around like this. My database is setup as you would expect it to be. I consider myself more knowledgeable in MySQL and data management than JS! I do want to allow the user to enter multiple of the same input, and in that case, I would have the query join them together with an OR. And I don't want to lock them into picking one of each of the fields, so I do want it to be completely dynamic. They should be able to say, I want to find every customer that bought a Milli Vanilli CD in the last month and send them a refund. Or in the last three months. Additionally, I want to be able to add more query elements (than those three), by adding stuff to my database, instead of adding stuff to the webpage. Right now, I get the query field (Customer, Month, and Product) from a MySQL query on a table I made just for fields to filter on - currently it has those three, but I figured it'll have far more and keep getting added to via request. That's why I was keeping it generic, like group/ subgroup, or qryValue/ fieldValue that I had in the original post.

                My only remaining question is how to pass a link into this page that presets the drop down boxes. I get how to do that with PHP and understand the $GET variable, but I don't understand how I can read a $GET that has 3 groups and 3 subgroups in it and add 2 sets of inputs, pull the groups and subgroups, populate the drop down boxes, then tell it to execute the query and finally display the return of submit-query. Does that question make sense?

                So I manually set the page to this:

                [ATTACH]4945[/ATTACH]

                I hit submit, my query results run, and I think great, I'm going to send that link to Joe. I look in the JavaScript Console in Chrome and I can see that it does this:

                form_send.php?group%5B%5D=3&subgroup%5B%5D=3&group%5B%5D=2&subgroup%5B%5D=1&group%5B%5D=2&subgroup%5B%5D=2&group%5B%5D=2&subgroup%5B%5D=3&cmd=submit_query
                

                Perfect, but that's to the second php page, not the main HTML page. Is that what you had in mind? Or am I missing something really obvious? Thanks again for all the assistance.

                Screen Shot 2013-08-28 at 8.45.11 PM.png
                  scambro;11032643 wrote:

                  My only remaining question is how to pass a link into this page that presets the drop down boxes. I get how to do that with PHP and understand the $GET variable, but I don't understand how I can read a $GET that has 3 groups and 3 subgroups in it and add 2 sets of inputs, pull the groups and subgroups, populate the drop down boxes, then tell it to execute the query and finally display the return of submit-query. Does that question make sense?

                  After rereading it a couple of times, sure 😉

                  One way would be to change the initial index.html to index.php, go through the _GET array and create select elements in the initial document as needed. But I do not recommend it. If you are building code to handle additions of these select elements in the client, I recommend keeping it in one single place. Else you have code in two places which may need updating as things change.

                  But first off, I'd recommend doing some changes.
                  form_send.php

                  <?php
                  header('content-type: text/html; charset=utf-8');
                  
                  function get_subgroup_data($group)
                  {
                  	$g = (int) $group;
                      if ($g < 1 || 3 < $g)
                          return false;
                  
                  /* I've just removed the logic needed to retrieve the data, and assume
                      that it's returned as an array of rows with fields "value" and "text" */
                  if ($group == 1)
                      $rows = get_customer_data();
                  if ($group == 2)
                      $rows = get_months();
                  if ($group == 3)
                      $rows = get_items();
                  
                  return $rows;
                  }
                  
                  /* generate dummy data instead of db calls */
                  function get_customer_data()
                  {
                      return array(
                          array('value' => 1, 'text' => 'John'),
                          array('value' => 2, 'text' => 'Sue'),
                          array('value' => 3, 'text' => 'Ellen'),
                      );
                  }
                  /* i realize that this is probably something other than actual months */
                  function get_months()
                  {
                      return array(
                          array('value' => 1, 'text' => 'January'),
                          array('value' => 2, 'text' => 'February'),
                          array('value' => 3, 'text' => 'March'),
                      );
                  }
                  /* i realize that this is probably something other than actual months */
                  function get_items()
                  {
                      return array(
                          array('value' => 1, 'text' => 'Mimes'),
                          array('value' => 2, 'text' => 'Hair Extensions'),
                          array('value' => 3, 'text' => 'Milli Vanilli CDs'),
                      );
                  }
                  function submit_query($group)
                  {
                    $sql = "select stuff from MySQL: ".implode(', ',$group);
                    return ($sql);
                  }
                  
                  if(isset($_GET['cmd']) && $_GET['cmd'] == 'get_subgroup')
                  {
                  	$data = false;
                  	if (isset($_GET['group']))
                  		$data = get_subgroup_data($_GET['group']);
                  
                  /* This will work if you make sure that groups that contain no subgroup data
                   * do not return false, but rather empty array or null
                   */
                  if ($data === false)
                  {
                  	header('HTTP/1.1 400 Bad Request');
                  	/* possibly send some useful information back, such as
                  	 * array(
                  	 	'success'	=> false,
                  	 	'message'	=> Invalid group id | Missing parameter: group, or whatever went wrong
                  	 */
                  	exit;
                  }
                  /* You may want to keep data isolated from metadata. Some front end libs use this
                   * structure. Although I fail to see the point of sending success as metadata
                   * when it's handled by http status codes. Still useful for other things. E.g.
                   * sending debug info while developing.
                   */
                  echo json_encode(array(
                  	'success'	=> true,
                  	'data'	=> $data,
                  ));
                  exit;
                  }
                  elseif($_GET['cmd'] == 'submit_query')
                  {
                  	echo submit_query($_GET['group']);
                  }
                  # doing the same "on_change"
                  elseif($_GET['cmd'] == 'on_change')
                  {
                  	echo json_encode(sub_group_select($_GET['group']));
                  }
                  # …as you are here?
                  else{
                  	echo json_encode(sub_group_select($_GET['group']));
                  }
                  # code duplication bad. And whatever the reason in the client is for the request,
                  # the code executed on the server is the same. $_GET['cmd'] = 'fetch_subgroup_data' may
                  # be appropriate and relates to what happens here, rather than to what initiated the request.
                  exit;
                  ?>
                  

                  moreover, I'd split this document into one separate for handling fetches of option data, and another for handling posting the final form. They are after all rather different in nature.

                    Then restructuring of index.html

                    <script>
                    /* Self executing closure is useful to avoid cluttering the
                     * global namespace, while grouping stuff together.
                     */
                    var Subselect = (function($) {
                    	var subselect_options = {};
                    
                    var settings = {
                    	url : '/form_send.php',
                    	method : 'GET'
                    }
                    
                    /* should probably refactor this function into multiple parts.
                     */
                    var createSubselectFor = function($p) {
                    	/* do we need to create select or can we reuse existing?
                    	 */
                    	$s = $p.siblings('select')
                    	if ($s.length) {
                    		$s = $s.first();
                    		/* Not sure if this is an acceptable method of clearing out all existing options.
                    		 * Does jQuery handle it properly for you?
                    		 */
                    		$s.html('');
                    	}
                    	else {
                    		/* Here using built-in method to create element before passing that to jquery.
                    		 * Not necessarily considered "clean". jQuery might have something for createElement-
                    		 */
                    		var s = document.createElement('select');
                    		$s = $(s);
                    		$s.attr('name', 'subgroup[]');
                    		$s.addClass('subgroup');
                    	}
                    
                    	/* Regards which of the parent groups (integer matching month, customer etc)
                    	 */
                    	var optgroup = subselect_options[parseInt($p.prop('value'))];
                    	for (x in optgroup) {
                    		var o = document.createElement('option');
                    		o.value = optgroup[x].value;
                    		o.text = optgroup[x].text;
                    		$s.append(o);
                    	}
                    	 /* Naming might be confusing here. The argument passed to this
                    	  * function is called "parent" in the calling code since it regards
                    	  * the "parent group". But in the dom tree, the select elements are siblings.
                    	  */
                    	$p.parent().append(s);
                    };
                    
                    
                    var getOptions = function(parent) {
                    	/* Only fetch options once from database, then store them for repeated use.
                    	 * Assuming you'd always reuse the same month options, customer options etc.
                    	 * Wether this is functional, or wether putting these choices in a select will
                    	 * be in the future, with possibly thousands of customers, thousands of items etc
                    	 * is another question…
                    	 */
                    	var pval = parseInt(parent.value);
                    	if (typeof subselect_options[pval] == 'undefined' || subselect_options[pval].length == 0) {
                    		var ajaxOptions = {
                    			url		: settings.url,
                    			method	: settings.method,
                    			data	: 'a=1&cmd=get_subgroup&group='+pval,
                    			/* In the done handler for the ajax event, this will reference
                    			 * whatever you specify as context (what this will reference)
                    			 */
                    			context	: $(parent),
                    			/* specifying dataType makes jQuery expect a specific data type
                    			 * of the returned data and parse it as such.
                    			 * In previous examples we sent html strings: <option>...</option>
                    			 * but now we'll use json instead.
                    			 */
                    			dataType : 'json'
                    		};
                    		$.ajax(ajaxOptions)
                    			.done(function(data) {
                    				/* this is now whatever we set as 'context' in the ajax options object */
                    				subselect_options[pval] = [];
                    				for (x in data.data) {
                    					subselect_options[pval].push(data.data[x]);
                    				}
                    
                    				if (data.data.length) {
                    					createSubselectFor($(parent));
                    				}
                    			})
                    			.fail(function(d, t, jxo) {
                    				// inform user of failure?
                    				// try again?
                    			});
                    
                    	}
                    	else {
                    		createSubselectFor($(parent));
                    	}
                    };
                    
                    return {
                    	add : function(v) {
                    		getOptions(v);
                    	},
                    };
                    })(jQuery);
                    
                    jQuery(document).ready(function ($) {
                    	/* Since you are just letting the user select from the same options
                    	 * multiple times, why keep fetching the same data over and over?
                    	 * Just clone it!
                    	 * Subselects, if present, will be cloned with the rest. Remove them as necessary
                    	 */
                        $('#add-query-element').on('click', function(evt) {
                        	$i = $('#inputs')
                        	$e = $i.children('div').first().clone();
                        	$e.find('select.subgroup').remove();
                        	$i.append($e);
                    	});
                    	$('#inputs').on('change', '.group', function() {
                    		Subselect.add(this)
                    	});
                    
                    /* Pass in query string parameters in the same way as you'd send data to the server
                     * when submitting the form.
                     * It's up to you to parse it, so sticking with the original, it'd be something like
                     * ?group[]=month&subgroup[]=january&group[]=...
                     */
                    var s = window.location.search;
                    alert(s);
                    });
                    </script>
                    </head>
                    <body>
                    <div>
                    <form action="form_send.php" method="GET">
                    	<fieldset>
                    		<legend>Type & Value</legend>
                    		<div id='inputs'>
                    			<div>
                    				<select name="group[]" class="group">
                    					<option value="0">Please choose…</option>
                    					<option value="1">Customer</option>
                    					<option value="2">Month</option>
                    					<option value="3">Item</option>
                    				</select>
                    			</div>
                    		</div>
                    	</div>
                    	</fieldset>
                    	<div id="buttons">
                    		<button id="add-query-element" type="button">Add A Qualifier</button>
                    		<button id="submit-query" type="button">Submit the Query</button>
                    	</div>
                    	<div id="show-query"></div>
                    </form>
                    

                    But some things obviously remains to be done. The actual parsing of the query string parameters (if any). I'd also remove the code to add new select group elements to a separate function so that you may call it repeatedly from your code while going through your groups, not just when clicking the button.
                    As you create those selects, you'd then set which option is selected and trigger the onchange event for each such select element, thus triggering the normal flow of events.

                    As a side note: Subselect now only handles stuff regarding the subselects. But in case you didn't clone the parent group selects, but retrieved them in a similar manner from the server, you would most likely have needed something similar to handle the parent group selects. And when/after writing that similar closure, you should probably rewrite it so that you could create instances of it, rather than dealing with a singleton.

                      Write a Reply...