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:
- Basic Javascript/jQuery understanding.
- Basic jQuery Ajax knowledge (any asynchronous request techniques. I consider jQuery as the most handy for Joomla).
- How to use (request and get) JSON data only in Joomla.
- How to create and load custom form fields in Joomla.
- 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.
- 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).
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:
