Success

User tests: Successful: Unsuccessful:

avatar phproberto
phproberto
6 Jul 2013

Tracker:
http://joomlacode.org/gf/project/joomla/tracker/?action=TrackerItemEdit&tracker_item_id=31352&start=0

This contribution tries to improve layouts usability for component developers without breaking B/C. New methods allow to:

  • Allow devs to add an unlimited list of include paths with new functions: addIncludePath() & addIncludePaths
  • Automatically search base layout overrides in the rendered component folder
  • Automatically search overriden component layouts in the active template folder
  • Add a sublayout function that inherits all the settings (except $layoutId & displayData from the parent folder)

Old method

With the old method a simple call like this:

$layout = new JLayoutFile('joomla.content.tags');
$layout->render($itemTags);

Will search for the layout in this folders (ordered by priority):

    [0] => templates/mytemplate/html/layouts
    [1] => layouts

You also could force a layout folder with:

$layout = new JLayoutFile('joomla.content.tags', JPATH_SITE . '/components/com_mycomponent/layouts');
$layout->render($itemTags);

Which would search for layouts in:

    [0] => templates/mytemplate/html/layouts
    [1] => components/com_mycomponent/layouts

That limits layout search to two folders and doesn't allow users to create layout overrides per component.

New method

Standard call (with default values shown) would be like:

JLayoutFile($layoutId, $basePath = null, $component = 'auto', $client = 'auto', $debug = false)

No B/C issues. Now let's use some examples to describe new features:

1. I want to customise the way that tags are shown in my component.

With the new functions you haven't to do anything. System will automatically search for layouts in the active component:

$layout = new JLayoutFile('joomla.content.tags');
$layout->render($itemTags);

Will search for layouts in this order:

    [0] => templates/mytemplate/html/layouts/com_mycomponent
    [1] => components/com_mycomponent/layouts
    [2] => templates/mytemplate/html/layouts
    [3] => layouts

That means that I can customise tags and also users can specifically override my customisation.

2. Wait I'm in a module that shows my component tags in any component

You can specify/force the component where you want to search for layouts

$layout = new JLayoutFile('joomla.content.tags', null, 'com_mycomponent');
$layout->render($itemTags);

3. But I want to show my tags in backend with the same frontend layout

You can also specify the client where you want to search for layouts:

$layout = new JLayoutFile('joomla.content.tags', null, 'com_mycomponent', 'site');
$layout->render($itemTags);

4. Why sublayouts?

I found that I needed to always use the same includePaths to render a lot of layouts. Let's say that we are on a plugin that creates an invoice. With sublayouts it's easy to allow users to customise exactly the part of the invoice the want. In the main file we will call something like:

$invoiceLayout = new JLayoutFile('invoice', null, 'com_mycomponent', 'site');
$invoiceLayout->render($invoiceData);

And then in the layout we can use:

// Render client information
echo $this->sublayout('invoice.client', $client);

// Render products lines
echo $this->sublayout('invoice.products', $products);

That will create a group of small layouts that clients can customise individually. For example to customise client information they just need to copy the file from:

components/com_mycomponent/layouts/invoice/client.php

to the template:

templates/mytemplate/html/layouts/com_mycomponent/invoice/client.php

5. Debug mode

As you may have noticed all this folders in the include path can cause you some headaches when any of them fails. When enabled debug mode will output something like this when the render() method is called:
layouts_debug

You can also use echo $layout->debugInfo(); anytime.

6. Playground

I also added some new public available functions:

setComponent($component)
setClient($client)
setLayout($layoutId)
setDebug($debug)

They are mainly intended to be used internall but nothing prevents you to go crazy and do something like:

$layout = new JLayoutFile('invoice', null, 'com_mycomponent', 'site');
echo $layout->render($invoiceData);

// Crazy mode
$layout->setLayout('share');
$layout->setComponent('com_weblinks');
$layout->setClient('admin');
echo $layout->render($links);

7. Also available in fast layout mode

If you like to save lines of code you can use the JLayoutHelper which also includes the new features:

public static function render($layoutFile, $displayData = null, $basePath = '', $component = 'auto', $client = 'auto', $debug = false)

Some of the before examples in fast mode:

echo JLayoutHelper::render('joomla.content.tags', $itemTags, null, 'com_mycomponent', 'site')
echo JLayoutHelper::render('invoice', $invoiceData, null, 'com_mycomponent', 'site');
avatar phproberto phproberto - open - 6 Jul 2013
avatar shumisha
shumisha - comment - 8 Jul 2013

Hi Roberto,
I have extremely little time and cannot test the code, but I still wanted to make a few comments on the definition/behavior that you describe above:
1 - I suggest the added params to JLayoutFile be grouped under a $option unique params, to avoid future change of interface issues
2 - It is already possible to template-override layouts on a per component basis. The name of the component is simply the first part of the id ie 'com_example.some.layout', similar to how joomla layouts ids starts with 'joomla' and thus are placed inside /layouts/joomla.
3 - I'm not sure I like so much the automation added around 'component', for a very simple reason: JLayout are not for components, they are for anything, including plugins, and most obviously modules.
Even if you change the "component" handling to "extension", which could be module or plugins as well as component, it seems to me people will thus tend to put their layouts iniside their extension folder, which is ok, but contrary to what I had in mind initially, that is share everything inside the /layouts folder, in exactly the same way medias are shared inside the /media folder, each extension having their own subfolder there.
4 - It seems to me there's a small B/C issue, in that you change the order folders are searched for in the default case:
from:
[0] => templates/mytemplate/html/layouts
[1] => layouts
to
[0] => templates/mytemplate/html/layouts/com_mycomponent
[1] => components/com_mycomponent/layouts
[2] => templates/mytemplate/html/layouts
[3] => layouts

Sorry, won't be able to discuss further, I'll be offline for most of the next 2 weeks.

Rgds

avatar phproberto
phproberto - comment - 8 Jul 2013

H Yannick,

thanks for your time reviewing this.

1 - I suggest the added params to JLayoutFile be grouped under a $option unique params, to avoid future change of interface issues
I agree with you. It will be easier to maintain and to use because you can just pass the params required.
2 - It is already possible to template-override layouts on a per component basis. The name of the component is simply the first part of the id ie 'com_example.some.layout', similar to how joomla layouts ids starts with 'joomla' and thus are placed inside /layouts/joomla.
We haven't nothing like layouts on installer. So component devs would need to manually copy the layouts to the layout folder on install and remove them on uninstall. IMO we should keep the same template workflow. All the component files are inside them. I don't like the idea of messing small parts of components with the core folders.
3 - I'm not sure I like so much the automation added around 'component', for a very simple reason: JLayout are not for components, they are for anything, including plugins, and most obviously modules.
Even if you change the "component" handling to "extension", which could be module or plugins as well as component, it seems to me people will thus tend to put their layouts iniside their extension folder, which is ok, but contrary to what I had in mind initially, that is share everything inside the /layouts folder, in exactly the same way medias are shared inside the /media folder, each extension having their own subfolder there.

I'm open to the extension concept but I think that plugins and modules would probably use the old method to display layouts (a fixed layout path through basePath or addIncludePath). I'm actually using layouts in components and make it easier to display/customise parts. For example we have a cart layout that receives the cart object. You can show it in a module or in any view.
4 - It seems to me there's a small B/C issue, in that you change the order folders are searched for in the default case:
from:
[0] => templates/mytemplate/html/layouts
[1] => layouts
to
[0] => templates/mytemplate/html/layouts/com_mycomponent
[1] => components/com_mycomponent/layouts
[2] => templates/mytemplate/html/layouts
[3] => layouts

I would like to know where is the issue to be able to fix it.

I think I'm going to create a post in the groups to discuss all this and see what thinks the community.

Thanks again for reviewing this,

avatar phproberto
phproberto - comment - 16 Aug 2013

I have updated the pull request. As suggested by @shumisha I added a simple $options parameter to the layouts to receive all the parameters.

Sample call with options:

$layout = new JLayoutFile('joomla.content.tags', null, array('debug' => true, 'client' => 1, 'component' => 'com_tags'));

That will search in:

[0] => /home/roberto/www/jcms3x/templates/protostar/html/layouts/com_tags
[1] => /home/roberto/www/jcms3x/administrator/components/com_tags/layouts
[2] => /home/roberto/www/jcms3x/templates/protostar/html/layouts
[3] => /home/roberto/www/jcms3x/layouts

Another improvement/modification included is that now the sublayout() function only receives the last part of the layout id because it's supposed to be a child of the first layout. Better with an example, this is a sample tags layout that calls a sublayout link:

defined('JPATH_BASE') or die;

JLoader::register('TagsHelperRoute', JPATH_BASE . '/components/com_tags/helpers/route.php');

?>
<?php if (!empty($displayData)) : ?>
    <div class="tags">
        <?php foreach ($displayData as $i => $tag) : ?>
            <?php echo $this->sublayout('link', $tag); ?>
        <?php endforeach; ?>
    </div>
<?php endif; ?>

layoutId will be automatically converted to: joomla.content.tags.link. That's a subfolder with the name of the "parent" layout. So in this example parent layout will be at:
layouts/joomla/content/tags.php

and sublayout will be at:

layouts/joomla/content/tags/link.php

Of course it keeps the overridable behavior so if we want to only override the tag link in our template we can create:

/home/roberto/www/jcms3x/templates/protostar/html/layouts/joomla/content/tags/link.php

We can create override only for this sublayout in our component like:

components/com_content/layouts/joomla/content/tags/link.php

I leave here my link testing sublayout to allow you to use it:

defined('JPATH_BASE') or die;

JLoader::register('TagsHelperRoute', JPATH_BASE . '/components/com_tags/helpers/route.php');

$tag = $displayData;

?>
<?php if (!empty($tag)) : ?>
    <?php if (in_array($tag->access, JAccess::getAuthorisedViewLevels(JFactory::getUser()->get('id')))) : ?>
        <?php $tagParams = new JRegistry($tag->params); ?>
        <?php $link_class = $tagParams->get('tag_link_class', 'label label-info'); ?>
        <span class="tag-<?php echo $tag->tag_id; ?> tag-list">
            <a href="<?php echo JRoute::_(TagsHelperRoute::getTagRoute($tag->tag_id . ':' . $tag->alias)) ?>" class="<?php echo $link_class; ?>">
                <?php echo $this->escape($tag->title); ?>
            </a>
        </span>&nbsp;
    <?php endif; ?>
<?php endif; ?>

For my tests I used the article view in:
components/com_content/views/article/tmpl/default.php

Around line 149 I edited the line to, for example:

        <?php
        $this->item->tagLayout = new JLayoutFile('joomla.content.tags', null, array('debug' => true, 'client' => 1, 'component' => 'com_tags'));

And then start playing moving layouts through components, layouts...

avatar rdeutz
rdeutz - comment - 16 Aug 2013

Just an idea, because I like the way FOF looks for Joomla! version specific templates. We could add a suffix array that implements that functionality

e.g.

suffix = arrray('j31',j3')

looks for files like default.j31.php, default.j3 and finally default.php.

That would makes it easier for component devs to ship layout for different version of Joomla!

avatar phproberto
phproberto - comment - 16 Aug 2013

New version.

Suggested by @mahagr :

  • Change function bindOptions() to setOptions() because it's more descriptive.
  • Convert resetOptions() in a proxy of setOptions()

Suggested by @rdeutz :

  • Add suffixes support to search for different versions of the same layout

Also I removed the crap debug and replaced it with a basic debug messaging system.

Thanks all for your suggestions / tests!!

avatar phproberto
phproberto - comment - 21 Aug 2013

Now that we have suffixes I've been thinking in extended uses for this. For example addresses can be show different for each language. So maybe this could make sense:

address.j30.es-ES.php
address.j30.en-GB.php
address.j30.php
address.j25.es-ES.php
address.j25.en-GB.php
address.j25.php
address.php

I think the language should be automatic too (but disabled if desired). Also I think it would be useful also if we add automatic LTR, RTL suffixes. For example for an RTL language:

address.j30.es-ES.php
address.j30.en-GB.php
address.j30.RTL.php
address.j30.php
address.j25.es-ES.php
address.j25.en-GB.php
address.j25.RTL.php
address.j25.php
address.php

This will give us more flexibility in designs for RTL languages. in this case I'd disable it by default and the suffixes should be added manually when used.

What are your thoughts about this?

avatar javigomez
javigomez - comment - 21 Aug 2013

I like a lot the idea of localised layouts. That will allow you to have different layouts loaded in a multilingual site. Also being able to diferente the RTL and LTR will allow a lot to simplify the layouts.

Thanks @phproberto!!! ^_^

avatar wilsonge
wilsonge - comment - 28 Aug 2013

In the case of com_modules can we search in a modules folder? because I'd quite like to have a module override the accordion tab-pane 's to give some better customizability

avatar mbabker
mbabker - comment - 28 Aug 2013

Maybe after this PR is implemented. But, I'm starting to get a feel of feature creep on this the longer it sits open.

avatar wilsonge
wilsonge - comment - 28 Aug 2013

OK as long as we don't call it a B/C breaker if we then implement this and people try to override com_modules rather than an individual module and start moaning

avatar rdeutz
rdeutz - comment - 28 Aug 2013

yeah merge it before it can make coffee

avatar phproberto
phproberto - comment - 18 Sep 2013

@wilsonge there is no B/C issue. For modules I think is better to use the old behavior. This improvements are mainly for component developers and they really open a new world where you can customise everything in your own component.

For example I just added the tinyMCE layouts. You can decide to change the buttons in your component. With this is possible.

Another example: You find a bug in joomla menu (imagine that it's rendered using layouts - ah no that pull request never got merged! -.- ) you can fix the joomla menu to get it working in your component, change icons, whatever :P

avatar mbabker
mbabker - comment - 18 Sep 2013

And I can finally fix all those toolbar buttons that are still hard coded to a specific layout ;-)

avatar wilsonge
wilsonge - comment - 18 Sep 2013

Yeah I can see the advantages - but I can see the advantages of being able to override some of the accordions in the module edit view etc for module devs. But perhaps outside the scope of this PR :) Let's get this retested an into 3.2 tho!!

Add a Comment

Login with GitHub to post a comment