Written by

Roberto Segura

Category:

Blog

24 May 2013

This article explains how to use it to avoid messing HTML, JS & CSS code in complex fields.

To illustrate the problem we will use the existing core field to select users:

libraries/cms/form/field/user.php

This is the content of the getInput() function inside that field:

/**
 * Method to get the user field input markup.
 *
 * @return  string  The field input markup.
 *
 * @since   1.6.0
 */
protected function getInput()
{
    $html = array();
    $groups = $this->getGroups();
    $excluded = $this->getExcluded();
    $link = 'index.php?option=com_users&view=users&layout=modal&tmpl=component&field=' . $this->id
        . (isset($groups) ? ('&groups=' . base64_encode(json_encode($groups))) : '')
        . (isset($excluded) ? ('&excluded=' . base64_encode(json_encode($excluded))) : '');

    // Initialize some field attributes.
    $attr = $this->element['class'] ? ' class="' . (string) $this->element['class'] . '"' : '';
    $attr .= $this->element['size'] ? ' size="' . (int) $this->element['size'] . '"' : '';

    // Initialize JavaScript field attributes.
    $onchange = (string) $this->element['onchange'];

    // Load the modal behavior script.
    JHtml::_('behavior.modal', 'a.modal_' . $this->id);

    // Build the script.
    $script = array();
    $script[] = '   function jSelectUser_' . $this->id . '(id, title) {';
    $script[] = '       var old_id = document.getElementById("' . $this->id . '_id").value;';
    $script[] = '       if (old_id != id) {';
    $script[] = '           document.getElementById("' . $this->id . '_id").value = id;';
    $script[] = '           document.getElementById("' . $this->id . '_name").value = title;';
    $script[] = '           ' . $onchange;
    $script[] = '       }';
    $script[] = '       SqueezeBox.close();';
    $script[] = '   }';

    // Add the script to the document head.
    JFactory::getDocument()->addScriptDeclaration(implode("\n", $script));

    // Load the current username if available.
    $table = JTable::getInstance('user');
    if ($this->value)
    {
        $table->load($this->value);
    }
    else
    {
        $table->username = JText::_('JLIB_FORM_SELECT_USER');
    }

    // Create a dummy text field with the user name.
    $html[] = '<div class="input-append">';
    $html[] = ' <input class="input-medium" type="text" id="' . $this->id . '_name" value="' . htmlspecialchars($table->name, ENT_COMPAT, 'UTF-8') . '"'
        . ' disabled="disabled"' . $attr . ' />';

    // Create the user select button.
    if ($this->element['readonly'] != 'true')
    {
        $html[] = '     <a class="btn btn-primary modal_' . $this->id . '" title="' . JText::_('JLIB_FORM_CHANGE_USER') . '" href="' . $link . '"'
            . ' rel="{handler: \'iframe\', size: {x: 800, y: 500}}">';
        $html[] = '<i class="icon-user"></i></a>';
    }
    $html[] = '</div>';

    // Create the real field, hidden, that stored the user id.
    $html[] = '<input type="hidden" id="' . $this->id . '_id" name="' . $this->name . '" value="' . (int) $this->value . '" />';

    return implode("\n", $html);
}

As you can see there is some HTML and JS code embedded and messed with the PHP. The first reason to avoid/replace that is for readability. Also imagine that you have a template that doesn't use Bootstrap markup like Hathor or you want to load a different modal that doesn't use Mootools.

Can JLayouts help us here? Yes. In fact is probably the best solution. Let's do it.

First we will to move almost all the code to a layout. Our new getInput() function will be:

/**
     * Method to get the user field input markup.
     *
     * @return  string  The field input markup.
     *
     * @since   1.6.0
     */
    protected function getInput()
    {
        $this->groups   = $this->getGroups();
        $this->excluded = $this->getExcluded();

        return JLayoutHelper::render("libraries.cms.forms.fields.user", $this);
    }

Just the basic to get the required data and pass it to the layout. Now we have to create the layout. You probably have noticed by the render function call that we are going to create it in:

layouts/libraries/cms/forms/fields/user.php

The content of the file is:

<?php
/**
 * @package     Joomla.Site
 * @subpackage  Layout
 *
 * @copyright   Copyright (C) 2005 - 2013 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_BASE') or die;

$data = $displayData;

$html = array();

$link = 'index.php?option=com_users&amp;view=users&amp;layout=modal&amp;tmpl=component&amp;field=' . $data->id
    . (isset($data->groups) ? ('&amp;groups=' . base64_encode(json_encode($data->groups))) : '')
    . (isset($data->excluded) ? ('&amp;excluded=' . base64_encode(json_encode($data->excluded))) : '');

// Initialize some field attributes.
$attr = $data->element['class'] ? ' class="' . (string) $data->element['class'] . '"' : '';
$attr .= $data->element['size'] ? ' size="' . (int) $data->element['size'] . '"' : '';

// Initialize JavaScript field attributes.
$onchange = (string) $data->element['onchange'];

// Load the modal behavior script.
JHtml::_('behavior.modal', 'a.modal_' . $data->id);

// Build the script.
$script = "
    function jSelectUser_" . $data->id . "(id, title) {
        var old_id = document.getElementById('" . $data->id . "_id').value;
        if (old_id != id) {
            document.getElementById('" . $data->id . "_id').value = id;
            document.getElementById('" . $data->id . "_name').value = title;
            " . $onchange . "
        }
        SqueezeBox.close();
    }
";

// Add the script to the document head.
JFactory::getDocument()->addScriptDeclaration($script);

// Load the current username if available.
$table = JTable::getInstance('user');

if ($data->value)
{
    $table->load($data->value);
}
else
{
    $table->username = JText::_('JLIB_FORM_SELECT_USER');
}
?>
<?php // Create a dummy text field with the user name. ?>
<div class="input-append">
    <input class="input-medium" type="text" id="<?php echo $data->id; ?>_name" value="<?php echo  htmlspecialchars($table->name, ENT_COMPAT, 'UTF-8'); ?>" disabled="disabled" <?php echo $attr; ?> />
    <?php
    // Create the user select button.
    if ($data->element['readonly'] != 'true') : ?>
        <a class="btn btn-primary modal_<?php echo $data->id; ?>" title="<?php echo JText::_('JLIB_FORM_CHANGE_USER'); ?>" href="/<?php echo $link; ?>" rel="{handler: 'iframe', size: {x: 800, y: 500}}">
            <i class="icon-user"></i>
        </a>
    <?php endif; ?>
</div>

<?php // Create the real field, hidden, that stored the user id. ?>
<input type="hidden" id="<?php echo $data->id; ?>_id" name="<?php echo $data->name; ?>" value="<?php echo (int) $data->value; ?>" />

Now the HTML code is separated from the PHP. Also users can override it easily creating a file in the active template like:

templates/TEMPLATE_NAME/html/layouts/libraries/cms/forms/fields/user.php

All can be changed inside the template except the source data.

I sent a pull request to the core with this changes. You can see the full code submitted on Github.