1

In a custom component, I'm developing an admin form with 2 fields, a combo-box for jobTitles and checkboxes for technologies. Both fields are loaded from a database table.

The values of the checkbox shall be reloaded from the database when the user selects a different value in the combo-box.

Example:

Job 1: Java Developer
Technologies: Java, Hibernate, Spring Boot

Job 2: C# Developer
Technologies: C#, MS Sql

These jobs are stored in a table.

In my admin form, I'm seeing:

Combobox: Java Developer
Checkbox:
Java
Hibernate
Spring Boot

When I'm selecting C# Developer in the combo-box I want to get the possible values for the checkbox being reloaded from the table:

C#
MS SQL

A possible solution could be that I use an event in JavaScript that got fired when I'm changing the combobox-value.

But I have no idea how I can reload the checkbox-values from JavaScript.

jobTitle.php

<?php
// Check to ensure this file is included in Joomla!
defined('_JEXEC') or die('Restricted access');

JFormHelper::loadFieldClass('combo');

class JFormFieldJobTitle extends JFormFieldCombo {

    protected $type = 'JobTitle';

    public function getOptions() {
        $db = JFactory::getDbo();
        $query = $db->getQuery(true);
        $query->select('jobtitle')->from('`#__jobs`');
        $rows = $db->setQuery($query)->loadObjectlist();
        foreach($rows as $row){
            $jobTitles[] = $row->jobtitle;
        }
        return $jobTitles;
    }

    protected function getInput()
    {
        // Get the field options.
        $options = $this->getOptions();
        $jobTitle = $this->form->getData()->toObject()->jobtitle;

        parent::getInput();
        $result = '<script>
            function logger() { console.log("Hello"); }
            function reload() { window.opener.location.reload(); }
            </script>
            <button type="button" onclick="logger()">Try it</button>';
        $result .= '<div class="combobox input-append">';
        $result .= '<input type="text" name="jform[jobtitle]" id="jform_jobtitle" onchange=logger() ';
        $result .= 'value="' . $jobTitle . '" class="combobox list" autocomplete="off"/>';
        $result .= '<div class="btn-group">';
        $result .= '<button type="button" class="btn dropdown-toggle">';
        $result .= '    <span class="caret"></span>';
        $result .= '</button>';
        $result .= '<ul class="dropdown-menu">';

        foreach ($options as $option) {
            $result .= '<li><a href = "#" onclick="reload()">' . $option . '</a></li>';
        }
        $result .= '</ul></div></div>';

        return $result;
    }
}

technologies.php

<?php
// Check to ensure this file is included in Joomla!
defined('_JEXEC') or die('Restricted access');

JFormHelper::loadFieldClass('checkboxes');
JLoader::register('ActionlogsHelper', JPATH_ADMINISTRATOR . '/components/com_actionlogs/helpers/actionlogs.php');
class JFormFieldTechnologies extends JFormFieldCheckboxes {

    protected $type = 'technologies';

    public function getOptions() {
        $jobTitle = $this->form->getData()->toObject()->jobtitle;
        $currentBackend = $this->form->getData()->toObject()->backend;
        $currentBackend = explode("\r\n", $currentBackend);
        $db = JFactory::getDbo();
        $query = $db->getQuery(true);
        $query->select('technologies')
            ->from('`#__jobs`')
            ->where($db->quoteName('jobtitle') . ' = ' . $db->quote($jobTitle));
        $backend = $db->setQuery($query)->loadRow()[0];
        $backend = explode("\r\n", $backend);

        // create the result array
        foreach ($backend as $b) {
            if (in_array($b, $currentBackend))
                $result[] = [$b, true];
            else
                $result[] = [$b, false];
        }

        return $result;
    }

    protected function getInput()
    {
        // Get the field options.
        $options = $this->getOptions();

        $html = '<fieldset id="jform_technologies" class="list checkboxes">';
        $i = 0;
        foreach ($options as $option) {
            $backend = $option[0];
            $selected = $option[1];
            $name = 'jform_backend' . $i;
            $html .= '<label for="' . $name . '" class="checkbox">';
            if ($selected)
                $html .= '<input type="checkbox" id="' . $name . '" name="jform[technologies][]" value="' . $backend . '" checked>' . $backend . '</label>';
            else
                $html .= '<input type="checkbox" id="' . $name . '" name="jform[technologies][]" value="' . $backend . '">' . $backend . '</label>';
            $i++;
        }
        $html .= '</fieldset>';

        return $html;
    }
}
1
  • Thanks for joining us, Stefan. Please take our tour. Please read these non-resolving tips which will improve your code: 3v4l.org/hIUlA Dec 30, 2020 at 10:17

2 Answers 2

2

I consider this a very valuable question that does not have good write-ups/tutorials yet in Joomla documents (we should create one). I am trying to give here some workable info on this.

Loading Form Fields using Joomla implemented show-on conditions is not for loading dynamically changing checkboxes, lists, or combo fields' values and fields. For loading Form Fields dynamically with dynamically changing values based on other selected List type form field values is a very important, very frequently required task in any web-framework. It is more true and needed if we consider asynchronous operations on webpages where only the focused fractions of the webpage are updated continually based on the user's interaction, so we do not want to reload the whole webpage because of a tiny dynamic change on the page. Today almost every web-developer wants to use this technique (hence the great popularity of the newer front-end JavaScript frameworks, like React, Angular and so on).

The subjects we have to study/know a bit for using this are the following:

  1. Basic Javascript/jQuery understanding.
  2. Basic jQuery Ajax knowledge (any asynchronous request techniques. I consider jQuery as the most handy for Joomla).
  3. How to use (request and get) JSON data only in Joomla.
  4. How to create and load custom form fields in Joomla.
  5. Basics of Joomla MVC.

Anybody can study these on the net of course. Quite a lot of things at first glance, but after a short time of studying these, it is getting relatively simple (while Joomla CMS stays extremely complex still).

Now, let's start with this. I just created a sample Joomla component for myself earlier to do tests on that. The component is called Mycomponent (com_mycomponent in Joomla). And created the Model, View and Controller folders and files called: hello.

First, we have to create a Table for the Form Fields possible options' values. For the task in the original question, I would create two tables; one for the Job titles, and one for the Technologies connecting them with a Foreign Key. But it looks like the OP is inserting these data in one Table so I'll use that concept now.

The basic sample Table called #__mycomponent_jobs should look like this:

+--------------+--------------+------+-----+---------+----------------+
| Field        | Type         | Null | Key | Default | Extra          |
+--------------+--------------+------+-----+---------+----------------+
| id           | int(11)      | NO   | PRI | NULL    | auto_increment |
| jobtitle     | varchar(255) | NO   |     |         |                |
| technologies | varchar(255) | NO   |     |         |                |
+--------------+--------------+------+-----+---------+----------------+

In /administrator/components/com_mycomponent/sql/install.mysql.utf8.sql you just simply create this:

CREATE TABLE IF NOT EXISTS `#__mycomponent_jobs` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `jobtitle` varchar(255) NOT NULL DEFAULT '',
  `technologies` varchar(255) NOT NULL DEFAULT '',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE=utf8mb4_unicode_ci;

Next is creating the Form XML file with the particular Fields in the models/forms folder: /administrator/components/com_mycomponent/models/forms/hello.xml

<?xml version="1.0" encoding="utf-8"?>
<form>
  <fieldset name="hellofields">
    <fields name="jobs" label="COM_MYCOMPONENT_FIELD_LABEL">
      <field
        name="jobtitle"
        type="jobtitle"
        label="JOBTITLES"
        description="MYCOMPONENT_HELLOFORM_JOBTITLE_DESC"
        class="mycombo"
        default="Please choose a jobtitle"
      />    
      <field
        name="technologies"
        type="technologies"
        label="TECHNOLOGIES"
        description="MYCOMPONENT_HELLOFORM_TECHNOLOGIES_DESC"
        default="0"
        readonly="false"
      />
    </fields>
  </fieldset>
</form>

This was the boring stuff (however the Joomla XML form fields creating system is a really great system!)

Then, we have to create the two custom From Field types (extending them from Joomla combo and checkboxes Form fields, which both are extended from Joomla List field type basically). Now, here I have to note that Joomla docs have a good write-up on 'how to create custom form fields in Joomla'. It could be extended a bit. So, first we create a custom Jobtitle combo field file. in /administrator/components/com_mycomponent/models/fields/jobtitle.php.

<?php
/**
 * @package     None yet
 * @subpackage  com_mycomponent
 *
 * @copyright   Copyright (C) 2021 plugit, Inc.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('_JEXEC') or die;

use Joomla\CMS\Form\FormHelper;

FormHelper::LoadFieldClass('combo');

class JFormFieldJobtitle extends JFormFieldCombo
{

    protected $type = 'jobtitle';

    public function getOptions() {
        
        // we get the field options values data from the db./from our table.
        $db = JFactory::getDbo();
        $query = $db->getQuery(true);
        $query->select('DISTINCT jobtitle')->from('`#__mycomponent_jobs`');
        $rows = $db->setQuery($query)->loadRowList();

        // we create the correct object structure for the combo field type
        // in the next steps from the returned database values 
        // then return it back - all other things handled by parent 
        $options = array();
        foreach($rows as $key => $value)
        {
            $options[] = array("value" => $key, "text" => $value[0]);
        }

        $options = array_merge(parent::getOptions(), $options);

        $correctOptions = array();
        foreach($options as $option)
        {
            $correctOptions[] = (object) $option;
        }

        $correctOptions = (object) $correctOptions;

        return $correctOptions;
    }

}

some notes: since the jobtitles's values are coming from the same table where the technologies' values are, in that table we are giving the same jobtitle to many technology names in the other column, thus in our code we make sure that every jobtitle will only be in our returned options object once for the Form field. That is why I would prefer these data in separate tables in the first place and not in one table together. We could save many lines of codes here.

But more importantly: when we create these custom form fields, most of the times the getOptions() method is enough in our class, because all of the other things will be handled by the parent class of the Field. So we do not need to create other methods in our custom combo field class. No more get this and get that...

For this combo type form field - which is basically an altered list form field - we have to return back the option's values in the following format (usually this is required by every list type form field in Joomla):

 'options' => 
    object(stdClass)[356]
      public '0' => 
        object(stdClass)[361]
          public 'value' => int 0
          public 'text' => string 'Java Developer' (length=14)
      public '1' => 
        object(stdClass)[362]
          public 'value' => int 1
          public 'text' => string 'C# Developer' (length=12)

The next is creating our custom checkboxes form field type called Technologies now. At this field - for fun and for being a more good boy - I created a separate method in my hello model to get the values from the table. In /administrator/components/com_mycomponent/models/hello.php.

  public function getFieldOptionsData($jobTitle)
  {
    $db = Factory::getDbo();
    $query = $db->getQuery(true);
    $query->select('technologies')
        ->from('`#__mycomponent_jobs`')
        ->where($db->quoteName('jobtitle') . ' = ' . $db->quote($jobTitle));
    $techForJob = $db->setQuery($query)->loadRowList();

    return $techForJob;
  }

and the Technologies custom form field in: /administrator/components/com_mycomponent/models/fields/technologies.php.

<?php
/**
 * @package     None yet
 * @subpackage  com_mycomponent
 *
 * @copyright   Copyright (C) 2021 plugit, Inc.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('_JEXEC') or die;

use Joomla\CMS\Form\FormHelper;
use Joomla\CMS\MVC\Model\AdminModel;
use Joomla\CMS\Factory;

FormHelper::LoadFieldClass('checkboxes');

class JFormFieldTechnologies extends JFormFieldCheckboxes {

    protected $type = 'technologies';

    public function getOptions()
    {
        // get our model
        $model = AdminModel::getInstance('Hello', 'MycomponentModel');

        // set the jobTitle variable for first loading of fields
        // if we want to load this field before job title is selected
        $jobTitle = '';

        $input = Factory::getApplication()->input;

        if ($input->getString('jobtitle') === null)
        {
            $job = "Java Developer";
        }
        else
        {
            $jobTitle = $input->getString('jobtitle');
            $job = $jobTitle;
        }

        // we will get the tech stack options' data for this field from our model/table
        // based on the selected jobtitle - getting a RowList
        $techForJob = $model->getFieldOptionsData($job);

        // we have to set the options data in the correct format
        // for list/checkboxes field
        $options = array();
        foreach($techForJob as $key => $value)
        {
            $options[] = array("value" => $key, "text" => $value[0], "checked" => '');
        }

        // we can get additional values from the XML field too
        $options = array_merge(parent::getOptions(), $options);

        // we finalize our options into an object list, then create an Object
        // from the whole to return that back in a correct format for this field type
        $correctOptions = array();
        foreach($options as $option)
        {
            $correctOptions[] = (object) $option;
        }
        $correctOptions = (object) $correctOptions;

        return $correctOptions;
    }

And this field requires for returning an object with values that looks like this:

object(stdClass)[349]
  public '0' => 
    object(stdClass)[343]
      public 'value' => int 0
      public 'text' => string 'Java' (length=4)
      public 'checked' => string '' (length=0)
  public '1' => 
    object(stdClass)[347]
      public 'value' => int 1
      public 'text' => string 'Hibernate' (length=9)
      public 'checked' => string '' (length=0)
  public '2' => 
    object(stdClass)[348]
      public 'value' => int 2
      public 'text' => string 'Spring Boot' (length=11)
      public 'checked' => string '' (length=0)

And then the parent checkboxes Joomla field class handles everything fine, you do not have to create anything else in your custom field code. (The above codes could be still optimized, simplified a bit, if I'll have some more spare time I'll update them. Anybody is welcomed to edit/improve these of course if someone is in the mood to do that.)

Let's now create the view and a simple layout (template) where we load the Form and the fields. I will not copy here the hello view (view.html.php file in the views/hello folder), I only place here the layout file from views/hello/tmpl folder because that is where our short jQuery script is which partially handles the dynamic field loadings too. /administrator/components/com_mycomponent/views/hello/tmpl/default.php

<?php
/**
 * @package     None yet
 * @subpackage  com_mycomponent
 *
 * @copyright   Copyright (C) 2021 plugit, Inc.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('_JEXEC') or die;

JHtml::_('behavior.formvalidator');
JHtml::_('behavior.keepalive');

$app = JFactory::getApplication();
$input = $app->input;

?>

<form id="myform">
  <?php  echo $this->form->renderField('jobtitle', 'jobs'); ?>
  <div id="mine2">
  <?php // echo $this->form->renderField('technologies', 'jobs'); // if we want to preload this ?>
  </div>

  <input type="hidden" name="task" value="" />
  <input type="hidden" name="return" value="<?php //echo $input->get('return', null, 'BASE64'); ?>" />
  <?php echo JHtml::_('form.token'); ?>
</form>

<script type="text/javascript">

jQuery(document).ready(function($) {
  let selected = $(".mycombo").next();

  selected.find('li').on('click', function() {
      let chosenJobTitle = $(this).text();
      sendajax(chosenJobTitle);
  });

  function sendajax(jobTitle) {
      $.ajax({
          url: "/administrator/index.php?option=com_mycomponent&task=hello.gettechfield&jobtitle=" + escape(jobTitle) + "&format=json",
          method: 'get',
          dataType: 'json',
          contentType: "application/json",
      }).done(function(data, result, msg) {
          // console.log(data);
          $("#mine2").html(data.data);
      }).fail(function(jqXHR, textStatus, errorThrown) {
          console.log( 'got post but: ' + textStatus + ': ' + errorThrown);
      });
  };

});

</script>

In the above view layout we create the html form and we render the form fields which we want to be included in the form. That is basic. We have to also include a <div> where we will load the technologies form field dynamically with jQuery. That is <div id="mine2"> in the form and by id we can target it with the jQuery script.

The Joomla combo form field is a bit tricky because it does not have changing value attributes or selected/checked attributes and it does not trigger change events in javascript contrary to list form fields' selected values. Thus we have get the chosen field's values by getting it from the combo field dropdown li text values. Then we just send the value to our controller and that is what's next here.

How to request and get JSON values properly in Joomla with a jQuery ajax call?

This is one of the most interesting parts of this whole subject regarding Joomla in my opinion. Specifically the question: how to request and get JSON values properly in Joomla with a jQuery AJAX call? Very rarely can you find a good tutorial on that (all of them have some problems).

In the jQuery script in the above layout I targeted a controller and a method in the controller with this segment of the AJAX request URL:

&task=hello.gettechfield

Many people know this. This targets the hello controller of your component and the gettechfield() method of that controller.

At the end of the request URL you will also find:

&format=json

And this is where a lot of unclear data you can find regarding Joomla. I'd like to clear two of those with the following.

  1. If the requested data format is JSON then please create a JSON controller in your component's controllers folder where you can have two of the same or very similar controller files, but one is called hello.php and the other one is called hello.json.php.. Thus if you call your controller function with an AJAX call and you'd like to get a JSON response only then the hello.json.php controller will answer that (without any headache about the response and with a clear json response).
  2. use \Joomla\CMS\Response\JsonResponse;is a just great in Joomla regarding JSON and AJAX calls. If you use this JsonResponse object in your controller, you do not have to be worried about the document header MIME encoding, or closing the application after the response was returned, and jexit... and these kind of things. All is handled by including this Joomla class. So you will see how simple a controller method is that provides and loads the whole Form field to your layout (using JSON). So the controller file:

/administrator/components/com_mycomponent/controllers/hello.json.php

<?php
/**
 * @package     None yet
 * @subpackage  com_mycomponent
 *
 * @copyright   Copyright (C) 2021 jooplugit,
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('_JEXEC') or die;

use Joomla\CMS\MVC\Controller\AdminController;
use Joomla\CMS\Factory;
use \Joomla\CMS\Response\JsonResponse;

class MycomponentControllerHello extends AdminController
{

  public function getTechField()
  {
      $input = Factory::getApplication()->input;
      $jobTitle = $input->getString('jobtitle');

      $model = $this->getModel('hello');

      $result = $model->getTechField($jobTitle);

      echo new JsonResponse($result);
  }
  
}

This controller calls the hello.php model that provides the full form field dynamically: /administrator/components/com_mycomponent/models/hello.php

  public function getTechField($jobTitle)
  {
      try
      {
        $this->form = $this->getForm();
        $result = $this->form->renderField('technologies', 'jobs');
        return $result;
      }
      catch(Exception $e)
      {
        Factory::getApplication()->enqueueMessage($e->getMessage(), 'warning');
        return false;
      }
  }

The full hello.php model file has 3 methods for this form:

defined('_JEXEC') or die;

use Joomla\CMS\MVC\Model\AdminModel;
use \Joomla\CMS\Response\JsonResponse;
use Joomla\CMS\Factory;

class MycomponentModelHello extends AdminModel
{
  public function getTable($type = 'Jobs', $prefix = 'MycomponentTable', $config = array())
    {
        return JTable::getInstance($type, $prefix, $config);
    }

  public function getForm($data = array(), $loadData = true)
  {
    // Get the form.
        $form = $this->loadForm(
            'com_mycomponent.hello',
            'hello',
            array(
                'control' => 'jform',
                'load_data' => $loadData
            )
        );

        if (empty($form))
        {
            $errors = $this->getErrors();
            throw new Exception(implode("\n", $errors), 500);
        }

        return $form;
  }

  public function getTechField($jobTitle)
  {
      try
      {
        $this->form = $this->getForm();
        $result = $this->form->renderField('technologies', 'jobs');
        return $result;
      }
      catch(Exception $e)
      {
        Factory::getApplication()->enqueueMessage($e->getMessage(), 'warning');
        return false;
      }
  }

  public function getFieldOptionsData($jobTitle)
  {
    $db = Factory::getDbo();
    $query = $db->getQuery(true);
    $query->select('technologies')
        ->from('`#__mycomponent_jobs`')
        ->where($db->quoteName('jobtitle') . ' = ' . $db->quote($jobTitle));
    $techForJob = $db->setQuery($query)->loadRowList();

    return $techForJob;
  }

}

I just hope that researchers can follow and use the above info when they want to create these fields dynamically and they can take a few things out of this. Finally I include a gif of what the result of these codes looks like at the end:

enter image description here

10
  • 1
    This is a massively generous contribution. I am on vacation and only have my phone with me so I am not prepared to make a non-trivial edit. I will invite you to read my demo link under the question and pull some of my resultset-to-array techniques into your answer. Great work Zollie! Jan 8, 2021 at 4:25
  • 1
    @mickmackusa - Hi Mick and thank you for the comment! I’ll have to clear a lot in the code and will use your suggestion regarding the arrays too. I wish you a great vacation time!
    – Zollie
    Jan 8, 2021 at 4:35
  • How can this be GPL and "all rights reserved" at the same time? Maybe I'm confused but it seems contradictory.
    – tripleee
    Jan 17 at 9:36
  • 1
    @tripleee - ohh, I see now what you mean. I forgot to delete those when I created these snippets in my code editor and I used my own component skeleton files' headers. I'll delete those from here as I get to my laptop. Thank you for pointing that out! :)
    – Zollie
    Jan 17 at 12:15
  • 1
    @hhgh4269 - yes, I am trying to find some time for doing that soon.
    – Zollie
    Feb 28 at 20:04
1

First of all you may well be able to use the Joomla SQL Form Field for your Job Title field, instead of having to write a custom field.

To make the Technologies dropdown dependent upon the Job Title field you may be able to use the showon attribute as described in Enabling showon to work in list options. In your Technologies custom field you'd have to select and include in the form field all the technologies from the database table, but set up the showon attribute on each so that an individual item was shown only if what was set in the Job Title field matched, if you understand what I mean.

I haven't tried this out, so I can't confirm if it does exactly what you need, but I'd be fairly hopeful that it would.

It would be better going down this route rather than using the option of reloading from the database as it doesn't involve any additional HTTP requests to the server.

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.