Creating a modal form field

From Joomla! Documentation

This tutorial will demonstrate how to create a custom form field that will open a list view in a modal window for the selection of a single item (similar to the Select Article modal in com_content). In this case, the form field will be added to the default.xml of a view so that the field/value pair will be added to the menu item URL. For our example, assume we have a component named com_library, which includes views for "books" and "book".

Add modal form field to XML file[edit]

Edit /components/com_library/views/book/default.xml and add the new field and its attributes.

<fields name="request" addfieldpath="administrator/components/com_library/models/fields">
	<fieldset name="request">
		<field 
			name="id" 
			type="modal_book" 
			label="COM_LIBRARY_BOOK_FIELD_ID_LABEL" 
			description="COM_LIBRARY_BOOK_FIELD_ID_DESC"
			required="true"
		/>
	</fieldset>
</fields>

Notice that in both the "fields" and "fieldset" declarations, we've included name="request". This tells the menu item to add the field name and its value to the URL. The custom type as been identified as modal_book and we've given a path where the class definition for this type can be found with addfieldpath.

Create custom form field class[edit]

Create the new file administrator/components/com_library/models/fields/modal/book.php. This file will define the JFormFieldModal_Book class which extends the JFormField class. More information on custom form fields can also be found at Creating a custom form field type.

 // No direct access
 defined('_JEXEC') or die('Restricted access');
 
 jimport('joomla.form.formfield');
 
 /**
  * Book form field class
  */
 class JFormFieldModal_Book extends JFormField
 {
 	/**
 	 * field type
 	 * @var string
 	 */
 	protected $type = 'Modal_Book';
 	
 }

Override getInput() method[edit]

Next, we'll override the getInput method of the parent class. This method builds the HTML for the input field.

  /**
   * Method to get the field input markup
   */
  protected function getInput()
  {
	  // Load modal behavior
	  JHtml::_('behavior.modal', 'a.modal');
	  
	  // Build the script
	  $script = array();
	  $script[] = '    function jSelectBook_'.$this->id.'(id, title, object) {';
	  $script[] = '        document.id("'.$this->id.'_id").value = id;';
	  $script[] = '        document.id("'.$this->id.'_name").value = title;';
	  $script[] = '        SqueezeBox.close();';
	  $script[] = '    }';
	  
	  // Add to document head
	  JFactory::getDocument()->addScriptDeclaration(implode("\n", $script));
	  
	  // Setup variables for display
	  $html = array();
	  $link = 'index.php?option=com_library&amp;view=books&amp;layout=modal'.
                  '&amp;tmpl=component&amp;function=jSelectBook_'.$this->id;
	  
	  $db = JFactory::getDbo();
	  $query = $db->getQuery(true);
	  $query->select('title');
	  $query->from('#__books');
	  $query->where('id='.(int)$this->value);
	  $db->setQuery($query);
	  if (!$title = $db->loadResult()) {
		  JError::raiseWarning(500, $db->getErrorMsg());
	  }
	  if (empty($title)) {
		  $title = JText::_('COM_LIBRARY_FIELD_SELECT_BOOK');
	  }
	  $title = htmlspecialchars($title, ENT_QUOTES, 'UTF-8');
	  
	  // The current book input field
	  $html[] = '<div class="fltlft">';
	  $html[] = '  <input type="text" id="'.$this->id.'_name" value="'.$title.'" disabled="disabled" size="35" />';
	  $html[] = '</div>';
	  
	  // The book select button
	  $html[] = '<div class="button2-left">';
	  $html[] = '  <div class="blank">';
	  $html[] = '    <a class="modal" title="'.JText::_('COM_LIBRARY_SELECT_BOOK_TITLE').'" href="'.$link.
                         '" rel="{handler: \'iframe\', size: {x:800, y:450}}">'.
                         JText::_('COM_LIBRARY_BUTTON_SELECT_BOOK').'</a>';
	  $html[] = '  </div>';
	  $html[] = '</div>';
	  
	  // The active book id field
	  if (0 == (int)$this->value) {
		  $value = '';
	  } else {
		  $value = (int)$this->value;
	  }
	  
	  // class='required' for client side validation
	  $class = '';
	  if ($this->required) {
		  $class = ' class="required modal-value"';
	  }
	  
	  $html[] = '<input type="hidden" id="'.$this->id.'_id"'.$class.' name="'.$this->name.'" value="'.$value.'" />';
	  
	  return implode("\n", $html);
  }

Let's take a closer look at the method.

  • The modal behavior is loaded.
  • The javascript function SelectBook_jform_request_id is defined and will be inserted into the <head> of the document with addScriptDeclaration(). Notice that $this->id = jform_request_id. 'jform' is the form name given in the loadForm method in our model. 'request' reflects that this is a request field, and 'id' is the name of our field.
  • There are several important points in the link that is created to open the modal window. We include layout=modal, as this will be an alternate list view layout for 'books'. function=jSelectBook_.$this->id will be used in the modal layout to identify the function to be called when a book is selected.
  • Next, we obtain the title of the currently selected book from the database or return a generic "Select Book" phrase to be shown in the input field.
  • The HTML output is created. Note that this includes the hidden field that will have the id 'jform_request_id_id' and will accept the id value of the book selected in the modal window.

Update the view[edit]

Since we won't need a toolbar in the modal layout, we'll revise the books view. /administrator/components/com_library/views/books/view.html.php

  public function display($tpl = null)
  {
	  // Get data from the model
	  $items = $this->get('Items');
	  $pagination = $this->get('Pagination');
	  $state = $this->get('State');
	  
	  // Check for errors
	  if (count($errors = $this->get('Errors'))) {
		  JError::raiseError(500, implode('<br />', $errors));
		  return false;
	  }
	  
	  // Assign data to the view
	  $this->items = $items;
	  $this->pagination = $pagination;
	  $this->state = $state;
	  
	  // Only set the toolbar if not modal
	  if ($this->getLayout() !== 'modal') {
		  $this->addToolBar();
	  }
	  
	  // Display the template
	  parent::display($tpl);
	  
	  // Set the document
	  $this->setDocument();
  }

Create the modal layout[edit]

When developers have had difficulty in implementing a modal form field, this is likely the step that is most commonly overlooked. Creating a separate modal layout is necessary in order to include links with an onclick action that will call the SelectBook_jform_request_id function. In many respects, this list view will mirror the default list view, but you may choose to leave out some columns such as the select checkboxes.

Create the new file /administrator/components/com_library/views/books/tmpl/modal.php.

 // No direct access
 defined('_JEXEC') or die('Restricted access');
 
 // Load tooltip behavior
 JHtml::_('behavior.tooltip');
 $listOrder	= $this->escape($this->state->get('list.ordering'));
 $listDirn	= $this->escape($this->state->get('list.direction'));
 
 $function = JFactory::getApplication()->input->getCmd('function', 'jSelectBook');
?>
<form action="<?php echo $this->action; ?>" method="post" name="adminForm" id="adminForm">
<table class="adminlist">
    <thead>
        <tr>
            <th>
                <?php echo JHtml::_('grid.sort', 'COM_LIBRARY_BOOKS_HEADING_TITLE', 'title', $listDirn, $listOrder); ?>
            </th>
            <th>
                <?php echo JHtml::_('grid.sort', 'COM_LIBRARY_BOOKS_HEADING_STATE', 'state', $listDirn, $listOrder); ?>
            </th>
            <th>
                <?php echo JHtml::_('grid.sort', 'COM_LIBRARY_BOOKS_HEADING_HITS', 'hits', $listDirn, $listOrder); ?>
            </th>
            <th>
                <?php echo JHtml::_('grid.sort', 'COM_LIBRARY_BOOKS_HEADING_CREATED', 'created', $listDirn, $listOrder); ?>
            </th>
            <th>
                <?php echo JHtml::_('grid.sort', 'COM_LIBRARY_BOOKS_HEADING_MODIFIED', 'modified', $listDirn, $listOrder); ?>
            </th>
            <th width="5">
                <?php echo JHtml::_('grid.sort', 'COM_LIBRARY_BOOKS_HEADING_ID', 'id', $listDirn, $listOrder); ?>
            </th>
        </tr>
    </thead>
    <tfoot>
        <tr>
            <td colspan="6"><?php echo $this->pagination->getListFooter(); ?></td>
        </tr>
    </tfoot>
    <tbody>
<?php foreach ($this->items as $i => $item) : ?>
        <tr class="row<?php echo $i % 2; ?>">
            <td>
                <a class="pointer" onclick="if (window.parent) window.parent.<?php echo $this->escape($function);?>('<?php echo $item->id; ?>', '<?php echo $this->escape(addslashes($item->title)); ?>');"><?php echo $this->escape($item->title); ?></a>
            </td>
            <td align="center"><?php echo JHtml::_('jgrid.published', $item->state, $i, 'books.'); ?></td>
            <td align="right"><?php echo $item->hits; ?></td>
            <td align="center"><?php echo $item->created; ?></td>
            <td align="center"><?php echo $item->modified; ?></td>
            <td><?php echo $item->id; ?></td>
        </tr>
<?php endforeach; ?>
    </tbody>
</table>
<div>
    <input type="hidden" name="task" value="" />
    <input type="hidden" name="boxchecked" value="0" />
    <input type="hidden" name="filter_order" value="<?php echo $listOrder; ?>" />
    <input type="hidden" name="filter_order_Dir" value="<?php echo $listDirn; ?>" />
    <?php echo JHtml::_('form.token'); ?>
</div>
</form>

This layout has two important differences when compared to the default layout.

  • We obtain the name of the javascript function using JFactory::getApplication->input->getCmd('function', 'jSelectBook').
  • The value of $function is then used in the onclick event on the title links.

Contributors[edit]

Denise McLaurin