User tests: Successful: Unsuccessful:
This contribution tries to improve layouts usability for component developers without breaking B/C. New methods allow to:
addIncludePath()
& addIncludePaths
$layoutId
& displayData
from the parent folder)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.
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:
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.
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);
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);
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
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:
You can also use echo $layout->debugInfo();
anytime.
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);
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');
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,
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>
<?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...
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!
New version.
Suggested by @mahagr :
bindOptions()
to setOptions()
because it's more descriptive.resetOptions()
in a proxy of setOptions()
Suggested by @rdeutz :
Also I removed the crap debug and replaced it with a basic debug messaging system.
Thanks all for your suggestions / tests!!
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?
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!!! ^_^
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
Maybe after this PR is implemented. But, I'm starting to get a feel of feature creep on this the longer it sits open.
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
yeah merge it before it can make coffee
@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
And I can finally fix all those toolbar buttons that are still hard coded to a specific layout ;-)
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!!
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