phpjqueryajaxjoomlahtml-select

Joomla JForm Chained Select with Foreign Key, Ajax and jQuery


I was wondering how I might integrate chained select lists in the administrator edit view of my component.

I have 2 tables: Manufacturer & Model

Both tables are indexed with a column id and the Model table has a foreign key linking to the Manufacturer's id column.

Currently the select fields are JForm "sql" type fields that have a query in the xml to pull all of the models from the database.

I know I need to add a function for getting Model values based on Manufacturer in the Controller and call it using AJAX with POST (using "task=myFunction"). I just can't think how to get the returned values back into JForm so the getInput function can create a select field based on them.

I've seen jQuery plugins that just filter the Model based on the previously selected box, but that seems a tad like cheating and I'd like to do it the "correct" way.

Many thanks


Solution

  • To answer my own question...

    I created a custom form field based on the 'SQL' type joomla standard form field.

    In the custom form field I added the jQuery/AJAX that once this parent select field is changed, takes the selected value, add's it to an ajax $_GET request and calls a task in the sub-controller. The subcontroller then gets the selected value from jinput and passes it to a method in the model that uses it to return JSON data for the child field.

    I didn't know it at the time of asking but Joomla uses the jQuery Chosen plugin to hide the bog standard html select lists and create nice live searchable drop down lists in their place. Also I've just noticed joomla 3.1 now includes the Ajax Chosen plugin but I've got a feeling it's implementation is only for tags? Maybe someone can shed light on this?

    The custom field's getOptions function looks a little like this:

    protected function getOptions()
    {
        // Build the script.
        $script = array();
        $script[] = 'jQuery(document).ready(function() {                                                                            ';
        $script[] = '   jQuery("#jform_child").trigger("liszt:updated");                                                            ';
        $script[] = '   jQuery("#jform_parent").chosen().change( function() {                                                       ';
        $script[] = '       var selectedValue = jQuery("#jform_parent").val();                                                      ';
        $script[] = '       jQuery.ajax({                                                                                           ';
        $script[] = '           type: "GET",                                                                                        ';
        $script[] = '           dataType: "json",                                                                                   ';
        $script[] = '           url: "index.php?option=com_mycomponent&task=myview.controllerTask",                         ';
        $script[] = '           data: {                                                                                             ';
        $script[] = '               "selectedValue": selectedValue                                                                  ';
        $script[] = '           },                                                                                                  ';
        $script[] = '           success:function(data) {                                                                            ';
        $script[] = '               jQuery("select#jform_child option").remove();                                                   ';
        $script[] = '               jQuery.each(data, function(i, item) {                                                           ';
        $script[] = '                   jQuery("select#jform_child").append( "<option value="+ i +">" + item + "</option>" );       ';
        $script[] = '               });                                                                                             ';
        $script[] = '               jQuery("select").trigger("liszt:updated");                                                      ';
        $script[] = '           }                                                                                                   ';
        $script[] = '       });                                                                                                     ';
        $script[] = '   });                                                                                                         ';
        $script[] = '});                                                                                                            ';
    
        // Add the script to the document head.
        JFactory::getDocument()->addScriptDeclaration(implode("\n", $script));
    
        $options = array();
    
        // Initialize some field attributes.
        $key = $this->element['key_field'] ? (string) $this->element['key_field'] : 'value';
        $value = $this->element['value_field'] ? (string) $this->element['value_field'] : (string) $this->element['name'];
        $translate = $this->element['translate'] ? (string) $this->element['translate'] : false;
        $query = (string) $this->element['query'];
    
        // Get the database object.
        $db = JFactory::getDbo();
    
        // Set the query and get the result list.
        $db->setQuery($query);
        $items = $db->loadObjectlist();
    
        // Build the field options.
        if (!empty($items))
        {
            foreach ($items as $item)
            {
                if ($translate == true)
                {
                    $options[] = JHtml::_('select.option', $item->$key, JText::_($item->$value));
                }
                else
                {
                    $options[] = JHtml::_('select.option', $item->$key, $item->$value);
                }
            }
        }
    
        // Merge any additional options in the XML definition.
        $options = array_merge(parent::getOptions(), $options);
    
        return $options;
    }
    

    As you can see the jQuery removes the current select options from the child field and populates it with new options grabbed from the sub-controller. The parent select field's selected value is passed to the sub-controller as the variable "selectedValue" data: { }. Once the new options are populated we call .trigger("liszt:updated") on the child field, this will make the Chosen plugin rebuild the select menu with the new options.

    Anyway - here's the sub-controller's task:

    public function controllerTask()
    {
        JFactory::getDocument()->setMimeEncoding( 'application/json' );
        JResponse::setHeader('Content-Disposition','attachment;filename="progress-report-results.json"');
    
        $jinput = JFactory::getApplication()->input;
        $selectedValue = $jinput->get('selectedValue');
        $model = $this->getModel();
        echo json_encode($model->modelMethod($selectedValue));
    
        JFactory::getApplication()->close();
    }
    

    It takes the "selectedValue" variable we passed with Ajax and uses it with modelMethod. In our case modelMethod takes the "selectedValue" variable and returns the list of child data to be displayed in the child field.

    Here's that model method:

    public function modelMethod($selectedValue) {
        $db = JFactory::getDbo();
        $db->setQuery("SELECT 0 AS `id`, '- Please select a child -' AS `child` UNION SELECT id, child FROM #__mycomponent_children WHERE #__mycomponent_children.parent = " . $selectedValue);
        $list = $db->loadObjectList();
        foreach ($list as $option) {
            $options[$option->id] = $option->child;
        }
        return $options;
    }
    

    I hope this helps someone!