Adding a Modal to Joomla Component
Modals are the pop-up-like windows which appear in several places within Joomla. For example, when as an administrator you create a new menu item, and select a Menu Item Type of Single Article, then when you click to select the article, a pop-up appears displaying the details of the articles available and allowing you to select one.
These modals use CSS and Javascript to give the appearance of pop-ups, and Joomla uses the Bootstrap Modal framework. The HTML elements which comprise the modal are already within the HTML of the page, but hidden. When you click on an appropriate button, then the javascript code behind the button click does the following:
- HTML hidden elements of the modal are made visible.
- A new div element covering the whole of the browser window is created. This backdrop is coloured black, and has partial opacity, so this gives the appearance of greying out what's behind the modal.
- In the CSS the z-index is defined so that the modal appears above the backdrop.
When you close the modal by selecting an item or by clicking the close button, then the HTML elements of the modal are made hidden again, the backdrop div is removed and any item you selected is passed by javascript to the appropriate field on the main form.
Each modal consists of three parts:
- Header at the top, usually with the title and an X button to close the modal.
- Body, where the main information appears.
- Footer, with buttons such as Close, Save, and others depending upon the context.
Step 1: Define Field in XML Form
The definition of what is displayed when you select a menu item is defined in the xml file. You need to add the field definition to be a custom field type which is called modal_<fieldfilename>.
For example,
<field
name="id"
type="modal_helloworld"
required="true"
label="COM_HELLOWORLD_HELLOWORLD_FIELD_GREETING_LABEL"
description="COM_HELLOWORLD_HELLOWORLD_FIELD_GREETING_DESC"
/>
Step 2: Field Definition
The field definition for new custom field goes into the admin/models/fields directory. Because the field type is called modal_<fieldfilename>, with an underscore in between, Joomla will look in a /modal subdirectory.
The name of the file will be fieldfilename.php
<?php
defined('JPATH_BASE') or die;
/**
* Supports a modal for selecting a helloworld record
*
*/
class JFormFieldModal_Helloworld extends JFormField
{
/**
* Method to get the html for the input field.
*
* @return string The field input html.
*/
protected function getInput()
{
// Load language
JFactory::getLanguage()->load('com_helloworld', JPATH_ADMINISTRATOR);
// $this->value is set if there's a default id specified in the xml file
$value = (int) $this->value > 0 ? (int) $this->value : '';
// $this->id will be jform_request_xxx where xxx is the name of the field in the xml file
$modalId = 'Helloworld_' . $this->id;
// Add the modal field script to the document head.
JHtml::_('jquery.framework');
JHtml::_('script', 'system/modal-fields.js', array('version' => 'auto', 'relative' => true));
// our callback function from the modal to the main window:
JFactory::getDocument()->addScriptDeclaration("
function jSelectHelloworld_" . $this->id . "(id, title, catid, object, url, language) {
window.processModalSelect('Helloworld', '" . $this->id . "', id, title, catid, object, url, language);
}
");
// if a default id is set, then get the corresponding greeting to display it
if ($value)
{
$db = JFactory::getDbo();
$query = $db->getQuery(true)
->select($db->quoteName('greeting'))
->from($db->quoteName('#__helloworld'))
->where($db->quoteName('id') . ' = ' . (int) $value);
$db->setQuery($query);
try
{
$title = $db->loadResult();
}
catch (RuntimeException $e)
{
JError::raiseWarning(500, $e->getMessage());
}
}
// display the default greeting or "Select" if no default specified
$title = empty($title) ? JText::_('COM_HELLOWORLD_MENUITEM_SELECT_HELLOWORLD') : htmlspecialchars($title, ENT_QUOTES, 'UTF-8');
$html = '<span class="input-append">';
$html .= '<input class="input-medium" id="' . $this->id . '_name" type="text" value="' . $title . '" disabled="disabled" size="35" />';
// html for the Select button
$html .= '<a'
. ' class="btn hasTooltip' . ($value ? ' hidden' : '') . '"'
. ' id="' . $this->id . '_select"'
. ' data-toggle="modal"'
. ' role="button"'
. ' href="#ModalSelect' . $modalId . '"'
. ' title="' . JHtml::tooltipText('COM_HELLOWORLD_MENUITEM_SELECT_BUTTON_TOOLTIP') . '">'
. '<span class="icon-file" aria-hidden="true"></span> ' . JText::_('JSELECT')
. '</a>';
// html for the Clear button
$html .= '<a'
. ' class="btn' . ($value ? '' : ' hidden') . '"'
. ' id="' . $this->id . '_clear"'
. ' href="#"'
. ' onclick="window.processModalParent(\'' . $this->id . '\'); return false;">'
. '<span class="icon-remove" aria-hidden="true"></span>' . JText::_('JCLEAR')
. '</a>';
$html .= '</span>';
// url for the iframe
$linkHelloworlds = 'index.php?option=com_helloworld&view=helloworlds&layout=modal&tmpl=component&' . JSession::getFormToken() . '=1';
$urlSelect = $linkHelloworlds . '&function=jSelectHelloworld_' . $this->id;
// title to go in the modal header
$modalTitle = JText::_('COM_HELLOWORLD_MENUITEM_SELECT_MODAL_TITLE');
// html to set up the modal iframe
$html .= JHtml::_(
'bootstrap.renderModal',
'ModalSelect' . $modalId,
array(
'title' => $modalTitle,
'url' => $urlSelect,
'height' => '400px',
'width' => '800px',
'bodyHeight' => '70',
'modalWidth' => '80',
'footer' => '<a role="button" class="btn" data-dismiss="modal" aria-hidden="true">' . JText::_('JLIB_HTML_BEHAVIOR_CLOSE') . '</a>',
)
);
// class='required' for client side validation.
$class = $this->required ? ' class="required modal-value"' : '';
// hidden input field to store the helloworld record id
$html .= '<input type="hidden" id="' . $this->id . '_id" ' . $class
. ' data-required="' . (int) $this->required . '" name="' . $this->name
. '" data-text="' . htmlspecialchars(JText::_('COM_HELLOWORLD_MENUITEM_SELECT_HELLOWORLD', true), ENT_COMPAT, 'UTF-8')
. '" value="' . $value . '" />';
return $html;
}
/**
* Method to get the html for the label field.
*
* @return string The field label html.
*/
protected function getLabel()
{
return str_replace($this->id, $this->id . '_id', parent::getLabel());
}
}
When the field definition class extends JFormField, Joomla expects two methods to be present in the class: getInput() which should return the html for the input field elements and getLabel() which should return the html for the label.
There are a many things returned by the getInput() method:
-
The input field which displays the current title selected, or the text "Select" if none has been selected.
-
The Select button - this appears if no title has been selected. The attribute data-toggle="modal" indicates that clicking on this button will cause the modal to appear.
-
The Clear button - this appears if a title has been selected. When clicked, it runs a javascript function within the system/modal-fields.js code which replaces the selected title with the "Select" text, makes visible the Select button, and hides the Clear button.
-
The modal elements - these are output through including sections of the Bootstrap html and javascript code. When the modal appears the iframe is created and populated through an HTTP GET to the URL in $urlSelect.
-
A hidden field which stores the id of the record selected, and this is used to pass it in the HTTP POST parameters when the form is submitted.
Here, the callback function is called jSelectHelloworld_jform_request_id, and this is the function that is called from the modal to pass the id and title of the selected record. Knowing this, window.processModalSelect will set the input fields for the title and id, and set the visibility of the Select and Clear buttons.
The modal knows that this is the name of the function to call because it's passed as the &function=jSelectHelloworld_jform_request_id parameter within the URL of the iframe.
The &tmpl=component parameter in the URL means that the component.php file within the directory of the installed template is used, which presents an html page without the Joomla menus, and other things that are not required by the modal.
For the modal display, you use the same list of items functionality. So, the controller and the model will be the same.
Step 3: The View File
In the view file, you don't display the toolbar buttons nor the sidebar menu.
// Set the sidebar submenu and toolbar, but not on the modal window
if ($this->getLayout() !== 'modal')
{
HelloWorldHelper::addSubmenu('helloworlds');
$this->addToolBar();
}
Step 4: Modal Layout File
You have to create a new modal layout file, modal.php, in the tmpl folder. This file is similar to the default.php layout file, but differs in some important aspects.
-
The function parameter is embedded in the iframe URL as a query parameter, and you use this in the URL target of the <form> element and in the data-function attribute of the each greeting value.
-
There are several other data attributes associated with each title value. These are used to pass this information to the parent window via the function call. Also, each title has a CSS class of "select-link".
-
You need to set the target URL of the <form> to be the same as the URL of the iframe. This is because whenever the administrator uses the search and filter tools, the form sends an HTTP request to that URL, and you need to ensure it's the same data that gets presented.
-
The checkboxes have been removed, as they are now irrelevant.
Step 5: JavaScript File
You also need a javascript file which sets an onclick listener on each of the title (identified through having the class "select-link"), and calls the required function when triggered.