Skip to main content
Version: 4.0 preview

Overriding Views & Templates

Version
4.0 preview
Updated
View markdown

The most common customization task: change what a screen looks like without editing core files. This guide covers the two layers that produce a screen — the template (the markup, the usual target) and the view class (the data prep behind it, rarely overridden) — how each resolves, the exact precedence order (with one important asymmetry between them), what variables a template has, and a worked example.

Read com_configbox_customization_overview.md first for the layer map and the resolution rule this guide specialises. All paths are relative to the component root docroot/components/com_configbox/. Source references are point-in-time (component 3.4.1) — verify against the code.


1. View vs. template — which one do you want?

A CBX screen is rendered by two cooperating pieces:

  • The view classviews/<view>/view.html.php, a KenedoView subclass. It prepares the data: loads the record(s), assigns $this->record / $this->records / $this->properties, builds links, computes the page title. It decides what data the screen has.
  • The templateviews/<view>/tmpl/<layout>.php, a plain PHP file. It renders the markup using the data the view prepared. It decides how the data looks.

Rule of thumb: 90% of the time you want a template override. Changing markup, layout, classes, the order of fields on a page, adding a wrapper or a bit of static content — all template work. Override the view class only when you need to change the data a screen has (load an extra record, change a query, alter the page title logic). Template overrides are smaller and drift less across updates.


2. Overriding a template (the common case)

2.1 The resolution chain

Template selection happens in KenedoView::renderView() (external/kenedo/classes/KenedoView.php:459-506). It builds three candidate paths and uses the first that exists:

$viewName = strtolower( basename( dirname($this->getViewPath()) ) ); // e.g. "product"

$templatePaths['templateOverride'] = KenedoPlatform::p()->getTemplateOverridePath($this->component, $viewName, $template);
// → JPATH_SITE/templates/<active-template>/html/<component>/<view>/<layout>.php
$templatePaths['customTemplate'] = KenedoPlatform::p()->getDirCustomization().'/templates/'.$viewName.'/'.$template.'.php';
// → data/customization/templates/<view>/<layout>.php
$templatePaths['defaultTemplate'] = dirname($this->getViewPath()).'/tmpl/'.$template.'.php';
// → views/<view>/tmpl/<layout>.php (the shipped core template)

foreach ($templatePaths as $templatePath) {
if (is_file($templatePath)) { include($templatePath); break; } // first match wins
}

So the precedence, highest first, is:

SlotPathWhen to use
1. Joomla template overridetemplates/<active-template>/html/com_configbox/<view>/<layout>.php (general.php:1040)Markup that is specific to one Joomla site template (the Joomla-native slot).
2. Customization templatedata/customization/templates/<view>/<layout>.phpThe platform-neutral, upgrade-safe slot — use this by default.
3. Core templateviews/<view>/tmpl/<layout>.phpThe shipped default. Don't edit it; copy it up to slot 2.

Use slot 2 (data/customization/templates/) for customization work. Slot 1 is the Joomla-specific escape hatch (handy if the same install runs multiple Joomla templates that need different markup); slot 3 is core and off-limits.

2.2 Identifying the view and layout names

You need two names to place a template: the view name (the directory) and the layout name (the file).

  • View name = the core view directory under views/. E.g. the product page is views/product/, so the view name is product; the radio-button question widget is views/question_radiobuttons/.
  • Layout name = the value of the layout request keyword, defaulting to default (renderView(), :466-472). Most views ship a default.php; some ship extra layouts (views/product/tmpl/ has default.php, notfound.php, metadata.php). The layout name is sanitised against path traversal before use (:474-475), so it is always a bare filename.

Therefore, to override the product page's default layout you create:

data/customization/templates/product/default.php

Copy, don't reference. Start by copying the core template views/<view>/tmpl/<layout>.php to data/customization/templates/<view>/<layout>.php, then edit the copy. There is no "parent template" call — your file fully replaces the core one for that view+layout. Copy only the one layout file you need to change; other layouts of the same view keep using core.

2.3 What a template can use ($this is the view)

The template is include()d inside the view object's scope, so $this is the KenedoView subclass and every property the view prepared is available. The view runs prepareTemplateVars() (:282-293) before rendering, which assigns different variables for list vs form/detail screens:

Detail/form views (prepareTemplateVarsForm(), :375-402) expose:

In the templateIsFrom
$this->recordthe loaded record (or initData() defaults for a new one):380-385
$this->propertiesthe model's KenedoProperty objects (drive the form):390
$this->pageTitlecomputed page title:392-398
$this->formActionthe form's submit URL:387
$this->pageTaskstoolbar tasks (save/delete/…):400

List views (prepareTemplateVarsList(), :295-373) expose $this->records, $this->properties (listing columns), $this->pagination, $this->filters/$this->filterInputs, $this->orderingInfo, $this->listingData, $this->pageTasks.

Custom views (your own frontend screens) typically assign their own variables in the view class via $this->assign($key, $value) (:516-518) or by setting $this->foo directly, then read $this->foo in the template.

Escaping is your job. Templates output directly. Use hsc() on every dynamic value (hsc($this->record->title)); the codebase has no auto-escaping. For property-driven forms, render each property through its own output methods rather than reading raw columns — see the property guide.

2.4 A worked template override

Suppose you want the product page to show a short "configurator help" banner above the configurator. You do not need new data, so a template override is enough.

  1. Find the core template: views/product/tmpl/default.php.
  2. Copy it to the customization slot: data/customization/templates/product/default.php.
  3. Edit the copy — add your banner near the top, keeping the rest intact:
<?php
defined('CB_VALID_ENTRY') or die();
/** @var $this ConfigboxViewProduct */ // $this is the view; see the core template's existing header
?>

<div class="cb-configurator-help">
<?php echo hsc(KText::_('CB_CUSTOM_CONFIGURATOR_HELP')); ?>
</div>

<?php /* …the rest of the original core template, unchanged… */ ?>

That's it: no class, no controller, no core edit. The next component update overwrites views/product/tmpl/default.php but never your data/customization/templates/product/default.php, so your banner survives.

Because your copy fully replaces the core template, re-check it after a component update that changed the original default.php — your copy won't have picked up the upstream change. Keep overrides minimal (ideally a small diff from core) to make this easy. This is the inherent cost of "replace" mechanisms noted in the overview.

Page layouts: an OPT-IN template in the page form's dropdown

The configurator page has a second, gentler mechanism besides replacing its default.php: put an additional template file in

getDirCustomization()/templates/configuratorpage/<name>.php e.g. betacalco.php

and <name> appears as a choice in the page form's "Template" dropdown (ConfigboxModelTemplates::getConfiguratorPageTemplates() scans the folder; file names containing _ are treated as partials and skipped). The page record's layoutname picks it, resolved in ConfigboxViewConfiguratorpage::display().

This is how a redesign ships without touching anything else: pages on layout default keep the stock template, and only the pages an admin explicitly switches use yours. Base the file on the CURRENT views/configuratorpage/tmpl/default.php (same override tax as above), wrap your markup in a marker class the layout's CSS/JS scope to, and let everything else — question rendering, the #configurator-data payload, the finish button — come through unchanged. Worked examples in the cbx-joomla site customization: accordion.php (a wizard flow) and betacalco.php (a brand design with a live product-code bar).


3. Overriding a view class (changing the data)

When you need different data on a screen — load an extra related record, change which model is used, alter the page-title logic, add a variable for your template — override the view class.

3.1 The resolution — and an asymmetry to know about

KenedoView::getView() (:181-226) builds the same two candidates as the controller/model loaders:

$regularPath = KenedoPlatform::p()->getComponentDir($component).'/views/'.$viewName.'/view.html.php';
$customPath = KenedoPlatform::p()->getDirCustomization() .'/views/'.$viewName.'/view.html.php';

if (is_file($regularPath)) { $path = $regularPath; } // ← regular checked FIRST
elseif (is_file($customPath)) { $path = $customPath; }

⚠️ Asymmetry — view classes do not follow "customization wins", but templates do. getView() checks the regular (core) path first (:202-207), so a data/customization/views/<view>/view.html.php is only used when no core view of that name exists — i.e. for brand-new views you add, not for shadowing an existing core view. (Controllers and models behave the same way — core-first, new-only; see com_configbox_overriding_controllers_and_models.md.) The asymmetry that bites is template vs. view class: a customization template (§2.1) shadows core, but a customization view class does not.

Consequences:

  • To add a new screen, put its view class under data/customization/views/<newview>/view.html.php — it loads because there is no core <newview>.
  • To change an existing core view's data, you cannot shadow it via the customization views/ folder. Use one of these instead: (a) a template override if markup is all you need; (b) add/tweak fields on the model via model_property_customization/ (data shape — see the stock-models guide); (c) change the view's data or the controller behind it via a system_overrides/ class override; or (d) as a last resort, a system_overrides/ class override of the view itself (see the boot-hooks guide). Note that the customization models/ and controllers/ folders will not help here (core-first, new-only). Confirm the precedence against getView() for your component version before relying on it.

3.2 Adding a new view (the supported customization views/ use)

A new view is a directory under data/customization/views/<view>/ containing view.html.php (the class) and tmpl/<layout>.php (its template). The class name follows the framework convention ConfigboxView<Viewname> (the component+view derivation in getComponentNameFromClass() / getViewNameFromClass(), :259-265):

// data/customization/views/myreport/view.html.php
<?php
defined('CB_VALID_ENTRY') or die();

class ConfigboxViewMyreport extends KenedoView {

function display() {
// Prepare whatever data the template needs:
$this->assign('rows', ConfigboxMyreportHelper::getRows());
$this->assign('pageTitle', KText::_('CB_MY_REPORT'));

// Render via the standard template chain (slot order from §2.1):
$this->renderView(); // uses tmpl/default.php unless 'layout' says otherwise
}
}
// data/customization/views/myreport/tmpl/default.php
<?php
defined('CB_VALID_ENTRY') or die();
/** @var $this ConfigboxViewMyreport */
?>
<h1><?php echo hsc($this->pageTitle); ?></h1>
<table>
<?php foreach ($this->rows as $row): ?>
<tr><td><?php echo hsc($row->label); ?></td><td><?php echo hsc($row->value); ?></td></tr>
<?php endforeach; ?>
</table>

A new view usually needs a route to reach it (a controller action and/or a SEF/menu entry). See ../platform/joomla/com_configbox_sef_urls.md for giving a custom view its own clean URL (controller hooks + a customview menu item delivered via migration), and the controllers/models guide for the controller side.


4. Deployment checklist

data/customization/
templates/<view>/<layout>.php ← override an existing screen's markup (the common case)
views/<newview>/view.html.php ← a brand-new view class (new screens only — see §3.1)
views/<newview>/tmpl/<layout>.php ← the new view's template
  1. Decide template vs. view. Markup only → template override (§2). Different data → model/controller override or new view (§3 and the controllers/models guide).
  2. Find the view name and layout name (§2.2): view = the views/<dir> name; layout = the layout request keyword (default default).
  3. Copy the core template up to data/customization/templates/<view>/<layout>.php and edit the copy. Keep the diff from core as small as possible.
  4. Escape all output with hsc(); read view-prepared data via $this->….
  5. For a new view, place the class + template under data/customization/views/<newview>/ and wire a route to it.
  6. Verify manually — load the screen, and for XHR-injected admin forms confirm it renders after injection too, not only on full reload. CBX has zero automated tests.
  7. After each component update, re-diff any template override against the (possibly changed) core template so you don't ship a stale copy.

5. Conventions & gotchas

  • Template precedence ≠ view-class precedence. Templates: customization beats core. View classes: core beats customization (regular path first, :202) — the customization views/ slot is for new views only. This is the single most surprising thing in this area (§3.1).
  • Copy only the layout you change. A view can have several templates; overriding default.php does not affect notfound.php etc.
  • $this is the view, not the model. Read $this->record, $this->records, $this->properties, $this->pageTitle. The properties are KenedoProperty objects — render through them, don't hand-roll field HTML from raw columns where a property already does it.
  • Escape everything. No auto-escaping; hsc() on every dynamic value. SQL is hand-built mysqli — never let raw request data reach a query from a view.
  • The layout name is sanitised, the view name is structural. You can't traverse paths via layout (:474-475); the view name comes from the resolved view directory, not from request input.
  • Match the idioms. Plain PHP templates, no namespaces/PSR-4, static helpers. Don't introduce a templating engine or modern Joomla layout APIs into Kenedo views.

See also

  • com_configbox_customization_overview.md — the extension-point map and the resolution rule.
  • com_configbox_overriding_controllers_and_models.md — change the data/behavior behind a screen (controllers and models are core-first too; override existing ones via system_overrides/).
  • com_configbox_custom_properties.md — how form/list fields render (templates lean on property output).
  • technical/com_configbox_kenedo_mvc.md — the view lifecycle (displayprepareTemplateVarsrenderView).
  • technical/com_configbox_kenedo_view.mdKenedoView in depth: the layer under this guide — CRUD vs multi-purpose views, how a view controls its CSS/JS, curryable setters, and the .view-<viewname> CSS-isolation convention your override templates should follow.
  • ../platform/joomla/com_configbox_sef_urls.md — giving a new custom view its own clean URL.
  • external/kenedo/classes/KenedoView.phpgetView() (:181), renderView() (:459), prepareTemplateVars*() (:282, :295, :375).