Developers
Views
A view is two things: a Hubzero\Component\View object that carries data, and
a PHP file — the layout — that turns that data into markup. The controller
fills the object; the layout reads it back as $this.
The split is what makes a component restyleable. A hub can replace one layout from its template without touching your code — but only for work that is in the layout. A query run inside a layout, or a permission decided there, is work a template override silently loses. Decide in the controller or the model; render in the layout.
Where layouts go
app/components/com_bookings/
site/views/
instruments/ the view name, matching the controller
tmpl/
display.php the layout, matching the task
display.xml menu-item metadata for that layout
view.php view.xml
book.php book.xml
_calendar.php partials
_slot.php
The controller has already built a view for you before your task runs: its
name is the controller name and its layout is the task name. A controller
Instruments running task book renders
site/views/instruments/tmpl/book.php. Nothing needs to be wired up.
A layout whose basename starts with an underscore is a partial. The convention matters beyond readability — the menu manager ignores layouts with an underscore in the name when it lists menu item types, so a partial never turns up as something an administrator can link to.
Passing data in
set() chains, which is why most tasks end in one statement:
$this->view
->set('instrument', $instrument)
->set('reservations', $reservations)
->setLayout('view')
->display();
$this->view->article = $article works too, and assign() takes an array or
an object and copies its public properties across. Names beginning with an
underscore are refused, because those are the view's own.
Four variables are already set for you: option, controller, task, and
baseurl.
Reading data out
<h3><?php echo $this->escape($this->instrument->get('title')); ?></h3>
<p><?php echo Lang::txt('COM_BOOKINGS_AVAILABILITY'); ?></p>
A name a person typed is not set by you but by whoever typed it. Anything from
a person goes through $this->escape(); an instrument titled
<script>…</script> is otherwise a stored cross-site scripting hole, and it
renders perfectly until someone tries it. Content that is meant to carry
markup goes through the content parser instead — see Models.
A property that was never set() reads as null and renders as nothing, so a
typo in $this->reservation where the controller set reservations produces
an empty page, not an error.
Every layout starts with the entry guard:
// No direct access
defined('_HZEXEC_') or die();
Choosing a different layout
setLayout($name) changes the file without disturbing the data — the usual
case being a failed save falling back to the edit form. It also accepts the
form template:layout: the part before the colon is recorded as the layout
template, the part after is the layout name.
$this->setView($name, $layout) on the controller replaces the view object
entirely, pointing it at a different view directory. Set your data after that
call.
Partials
$this->view($layout, $name = null) builds a sibling view: same base path,
same option, controller, and task, a different layout. com_kb uses it
to render a comment thread:
$this->view('_list')
->set('parent', 0)
->set('cls', 'odd')
->set('depth', 0)
->set('option', $this->option)
->set('article', $this->article)
->set('comments', $comments)
->set('base', $this->article->link())
->display();
Pass a second argument to reach a partial in another view's directory.
Data does not carry over from the parent — a partial sees only what you
set() on it, which is what makes them safe to reuse.
The search order
View::loadTemplate() looks for {layout}.php in three directories, in this
order:
{template}/html/{option}/{view name}/— the active template's override directory;{base path}/views/{view name}/tmpl/;{base path}/views/{view name}/.
The third exists so a view can skip the tmpl directory entirely. The first
is how a hub restyles a component without editing it: dropping
app/templates/hubzero/html/com_bookings/instruments/view.php into place
replaces that one layout and leaves the rest of com_bookings alone. See
Overrides.
An override is a copy, and copies rot. A layout you change in a later release does not reach a hub that overrode it, and nothing warns either of you.
If none of the three has the requested layout, the search runs again for
default.php. If that is missing too, InvalidLayoutException is thrown with
a 404 status — which is the error you get when a task's name and its layout
file's name have drifted apart. bookTask() with no book.php is a 404 on a
page that plainly exists.
Layout metadata
The .xml file beside a layout describes it to the menu manager, so an
administrator can create a menu item pointing straight at it:
<metadata>
<layout title="Article">
<message>
<![CDATA[Article]]>
</message>
</layout>
<state>
<name>Article</name>
<description>Article</description>
<params>
</params>
</state>
</metadata>
title is the label in the menu item type list and is passed through
Lang::txt(), so it may be a language key. message becomes the description.
Add hidden="true" to the layout element to keep a layout out of the list.
A view with no .xml files at all offers no menu item types.
Building a view by hand
Outside a controller — in a module, a plugin, or a second view within one task — construct one directly:
$view = new \Hubzero\Component\View(array(
'base_path' => PATH_COMPONENT,
'name' => 'instruments',
'layout' => 'display'
));
$view->set('instruments', $instruments)
->display();
display() echoes; casting the view to a string returns the markup instead,
which is what you want when the result has to be embedded rather than emitted.
Rewritten and checked against 2.4-main @ 348f0057c2 on 2026-09-10.