Forums

Populate form with Dual dropdown + AJAX

rubenc 04 May, 2013
Good Day
I am having trouble on re-populating my form with dual drop-downs after a confirmation page, any idea on how can I solve this?

Form:
- Dropdown(1), dynamicaly populated via a DB multi record loader.
- Dropdown(2), dynamicaly populated via "Dynamic dropdown" which calls an action via AJAX
- The action calls a DB multi record loader to fetch the data and pass-it back to the Dropdown(2)
- Dropdown(3), same/same, but depending on changes on dropdown(2).

On Confirmation page I get the id´s of this dropdowns, and with some custom php code I fetch the data to show it properly in the confirmation page.

Problem:
- When confirmation page is cancelled, only the 1st Dropdown is populated.

Question:
- What can I do in order to "trigger" the Dynamic drop-down actions so that Dropdown(2) and Dropdown(3) are re-populated as well?

Any advice will be appreciated
Thankyou.
GreyHead 14 May, 2013
Hi rubenc,

There isn't an easy answer to this. I do it by adding a custom code element in the form Preview that looks to see if there is a value set for the first drop-down, if there is then it looks up the corresponding data, builds the option set for the drop-down and sets the selected option. If there is nothing set them it just shows the default 'Please select an xyz' message.

Bob
menchee 30 Jul, 2013
This is also relevant for a basic record edit action.
Reloading the form with the usual technique of using the DB Record Loader and submitting with the same unique id.

Without this feature (of populating the chain of dropdown select boxes), the Dynamic Dropdown is too limited...

I suggest that there will be a mini tutorial / FAQ to accomplish this... until the ONLOAD event will be added to the action (I hope...).
GreyHead 12 Aug, 2013
2 Likes
Hi rubenc & menchee,

I worked out how to do this for a client this morning. This works OK but could still be improved so I'll post it here for the moment.

The code below goes into a Load JS action in your form On Load event and you need to makes some small changes to customise it. a) add the form name and AJAX event name into the URL and b) replace 'aaa' and 'bbb' with the ids of the primary and secondary drop-downs.
<?php
// a little PHP to check if the secondary drop-down has a value
// and add it to a JavaScript variable
// replace bbb here
if ( !$form->data['bbb'] ) {
  $form->data['bbb'] = '';
}
$doc =& JFactory::getDocument();
$script = "
var secondary_value = '{$form->data['bbb']}';
";
$doc->addScriptDeclaration($script);
?>
window.addEvent('domready', function() {
  var primary, secondary;
  // replace aaa and bbb here
  primary = $('aaa');
  secondary = $('bbb');
  if ( primary.value ) {
    var load_req = new Request({
      // replace event_name and form_name in the next line
      url: 'index.php?option=com_chronoforms&chronoform=form_name&format=raw&event=event_name',
      method: 'get',
      onRequest: function(){
        secondary.empty();
        new Element('option', {
          'value': '',
          'text': 'Loading...'
        }).inject(secondary);
      },
      onSuccess: function(responseText){
        secondary.empty();
        var response_data = responseText.trim().split("\n");
        response_data.each( function(line) {
          var line_data = line.split("=");
          var selected = ( line_data[0] == secondary_value );
          new Element('option', {
            'value': line_data[0],
            'text': line_data[1],
            'selected': selected
          }).inject(secondary);
        });
        secondary.fireEvent('change');
      },
      onFailure: function(){
        secondary.empty();
        new Element('option', {'value': '', 'text': 'Loading failed.'}).inject(secondary);
      }
    });
    load_req.send(primary.get('name')+'='+primary.value);
  }
});

Bob
menchee 12 Aug, 2013
Bob, thank you very much for sharing this code!
I wish I had it when I posted my question :wink:

Meanwhile, I solved it my way, which is similar, but I didn't base it on the Dynamic Dropdown action, since I have a lot of other ajax and php stuff running in this form.

I'll detail here my solution, focusing just on the mission of rendering the second dropdown select input for editing:

I use a custom code (php), which is inserted AFTER the DB RECORD LOADER action (the order is important!).
Tip: instead of writing straight into the action box, I link it to an external php file. This way the coding and debugging is way faster and easier to maintain.
So this is the code of the CUSTOM CODE action (defined as a Controller):


<?php
 $path = JPATH_SITE;
 require $path.'/templates/my_template/lib/my_form.php';
?>

Then, this is the relevant code in my_form.php:

<?php
defined('_JEXEC') or die('Restricted access');    
$document =& JFactory::getDocument();
if ( !$form->data['select_b'] ) {
  $form->data['select_b'] = '';
}

$script = "
var selectBid = '{$form->data['select_b']}';//later we use it to set relevant OPTION as SELECTED

//in the next url, the 'my_form' should be replaced with the name of the form where the 'ajax2' is whithin.
var url = '".JURI::root()."index.php?option=com_chronoforms&chronoform=my_form&event=ajax2&format=raw';
window.addEvent('domready', function() {
var select_a = $('select_a').value;
var select_b = $('select_b');

//function to set the options of the second dropdown
function setSelectB (value, text){
     var newoption = new Option(text, value);
     if(value == selectBid){
          newoption.setProperty('selected', 'selected');
     }
     try {select_b.add(newoption, null);}
     catch (e){select_b.add(newoption);}
}

//function to get by AJAX the options for the second dropdown
function getSelectB(){
var jSonRequest = new Request.JSON({
                                url:url,
                                data:{'select_a':select_a,'action':'getoptions'},
                                onComplete: function(r) {
                                    if(r.action_fb == 'ok'){
                                        select_b.empty();
                                        for(var i=0;i< r.options.length;i++){
                                            setSelectB(r.options[i].value, r.options[i].text);
                                        }
                                    }else{
                                      alert(r.action_fb);
                                      setSelectB('','Options loading failed');
                                    }
                                }
                }).send();
            /* >>>>>>>>>> JSON END ********* */
}

//The previous two functions can be inside the next IF, but I seprated them for my reasons.
if(select_a){//this is an edit mode and the query has what to work with
    select_b.empty();//Empty the second dropedown
    setSelectB('','Loading Options');//Set a temporary optoins with an empty value
    getSelectB();//calling for the AJAX which loads the options and add them to the second dropdown
}

});/* END OF DOMREADY */
";

$document->addScriptDeclaration($script);
?>


I added an EVENT and set it's name to AJAX2 and linked to it another file (the same way I mentioned before) - my_form_ajax.php:

<?php
defined('_JEXEC') or die('Restricted access');
$action = JRequest::getString('action','','post');
$select_a = JRequest::getInt('select_a','','post');

$response = array();//this array will be sent back with the query results and a feedback message

//I use $select_a which was sent by JSON to query the database
//You have to build the query following the structure of your table
//The $action is not realy necessary, but I use it since I have other AJAX requests for other purpeses.

if ($action == 'getoptions'){
    if ( $select_a ){
        $db =& JFactory::getDBO();
        $query =& $db->getQuery(true);
        $query
            ->select(array('c.cf_id AS value','c.qcat_name AS category'))
            ->from('#__q_categories AS c')
            ->where('c.access_level='.$select_a)
            ->order('category ASC');
        $db->setQuery($query);
        $qcat = $db->loadObjectList();
                 
        if ($qcat){
             $response['action_fb'] = 'ok';
             foreach($qcat as $row){
                $response['options'][] = array('value'=>$row->value,'text'=>$row->category);
             }
        }else{
            $response['action_fb'] = 'The system could not find any relevant option for the second dropedown.';
        }
    }else{
        $response['action_fb'] = 'The system is trying to find the options, but no value was detected in the first drowpdown';
    }
          
}else{
    $response['action_fb'] = 'The system does not recognize the requested action';
}

echo json_encode($response); 
?>


Now I don't really need the Dynamic Dropdown Action since I can use the same AJAX request, but this is of course up to the developer.

It is not as compact as your's, Bob, but it works and once the logic is clear, it is possible to build a complex AJAX form, with data pulled and sent to many other tables and of course real time dynamic data presentation within the form.

Thanks again Bob, I got some ideas from your solution which help me with other stuff I'm struggling with.
GreyHead 13 Aug, 2013
Hi menchee,

Reading through your code it is structurally very similar to my version. The main differences are that you aren't using the ChronoForms action (which is fine) and that you are using JSON to return the data (which I think is a better approach). The JSON is one of the reasons why I don't think mine is a 'finished' solution.

Here's a snippet from another form using JSON that you might find interesting:
  inlet_req = new Request.JSON({
    url: 'index.php?option=com_chronoforms&chronoform=hose_configurator_a&event=ajax_a&r=inlet',
    onRequest: function() {
      inlet.empty();
      new Element('option', {
        'value': '',
        'text': 'Loading . . .'
      }).inject(inlet);
    },
    onSuccess: function(resp) {
      inlet.empty();
      new Element('option', {
        'value': '',
        'text': '(please select)'
      }).inject(inlet);
      resp.each(function(r) {
        new Element('option', {
          'value': r['value'],
          'text': r['text'],
          'data-part': r['data'],
          'data-image': r['data_image']
        }).inject(inlet);
      });
      inlet.disabled = false;
      inlet.fireEvent('change');
    },
    onFailure: function() {
      inlet.empty();
      new Element('option', {
        'value': '',
        'text': 'Loading failed.'
      }).inject(inlet);
    }
  });

These two lines
          'data-part': r['data'],
          'data-image': r['data_image']
are sending back data that is added to the options in the second drop-down as data- attributes which can then be read and used for other purposes in the form. (In this case they identify part numbers and image names; in another form I used them to pass serial numbers and costs.)

Bob
menchee 13 Aug, 2013
Thanks a lot Bob!!!
The data-attribute idea came just on time. You are an angel :wink:
I used to attache many data items to the DOM element's ID and then split it from there, but using the data attribute is much easier.

By the way, using JSON I learned from you quite a long time ago and use it ever since...

Emanuel.
GreyHead 13 Aug, 2013
Hi Emanuel,

Glad to hear that it's useful. I started to use the data-mootools library with it. That seemed to work well on another Ajaxy project.

Bob
This topic is locked and no more replies can be posted.