# Overriding Views & Templates

> 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 …

Source: CBX documentation, version 3.x (released). Canonical page: https://docs.configbox.at/docs/customization/overriding_views_and_templates. Last updated 2026-08-14.

---
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 class** — `views/<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 template** — `views/<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**:

```php
$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:

| Slot | Path | When to use |
|---|---|---|
| **1. Joomla template override** | `templates/<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 template** | `data/customization/templates/<view>/<layout>.php` | **The platform-neutral, upgrade-safe slot — use this by default.** |
| **3. Core template** | `views/<view>/tmpl/<layout>.php` | The 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 template | Is | From |
|---|---|---|
| `$this->record` | the loaded record (or `initData()` defaults for a new one) | `:380-385` |
| `$this->properties` | the model's `KenedoProperty` objects (drive the form) | `:390` |
| `$this->pageTitle` | computed page title | `:392-398` |
| `$this->formAction` | the form's submit URL | `:387` |
| `$this->pageTasks` | toolbar 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
<?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:

```php
$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`):

```php
// 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
    }
}
```

```php
// 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 (`display` → `prepareTemplateVars` →
  `renderView`).
- `technical/com_configbox_kenedo_view.md` — **`KenedoView` 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.php` — `getView()` (`:181`), `renderView()` (`:459`),
  `prepareTemplateVars*()` (`:282`, `:295`, `:375`).
