|
| 1 | +--- |
| 2 | +title: Overriding Core Methods |
| 3 | +description: Replace a framework method with your own and delegate to the original via the super-prefixed convention — in models, controllers, and view helpers. |
| 4 | +type: howto |
| 5 | +sidebar: |
| 6 | + order: 16 |
| 7 | +--- |
| 8 | + |
| 9 | +import { Aside, CardGrid, LinkCard } from '@astrojs/starlight/components'; |
| 10 | + |
| 11 | +This page shows you how to override a core Wheels method — a model finder, a controller helper, or a view helper like `linkTo()` — while still being able to call the framework's original implementation from inside your override. You'll use the `super`-prefixed method convention, pick the right place for each kind of override, and see when a pass-through argument (like `dataConfirm`) makes an override unnecessary in the first place. |
| 12 | + |
| 13 | +**You'll learn:** |
| 14 | + |
| 15 | +- How the `super<name>` convention works and why it replaced `super.methodName()` |
| 16 | +- How to override a model method and delegate with `superFindAll()` |
| 17 | +- How to override a controller or view helper and delegate with `superLinkTo()` |
| 18 | +- Where each override lives: a single controller, `app/controllers/Controller.cfc`, or `app/views/helpers.cfm` |
| 19 | +- When to skip the override entirely — the `data-*` pass-through pattern for confirm dialogs |
| 20 | +- Which 4.0.x versions support controller/view overrides, and the workaround for older builds |
| 21 | + |
| 22 | +## How the convention works |
| 23 | + |
| 24 | +Wheels assembles models and controllers by mixing framework methods into your components at startup. Because the framework's `findAll()`, `linkTo()`, and friends arrive as mixins rather than through CFML inheritance, `super.findAll()` does not reach them — that syntax stopped working in Wheels 3.0. |
| 25 | + |
| 26 | +Instead, when your component defines a method whose name collides with a framework mixin, Wheels keeps **your** version and registers the framework original under the same name prefixed with `super`. Override `findAll()` and the original becomes `superFindAll()`; override `linkTo()` and the original becomes `superLinkTo()`. Method names are case-insensitive, following normal CFML rules. |
| 27 | + |
| 28 | +The alias is only created when you actually override something. A controller or model that overrides nothing gains no extra `super*` keys. |
| 29 | + |
| 30 | +<Aside type="note" title="Model-only in 3.x, symmetric in 4.0.x"> |
| 31 | +In Wheels 3.x this convention worked for **model** methods only. Controller and view helper parity arrived in the 4.0.x patch release carrying the fix for [#3325](https://github.com/wheels-dev/wheels/issues/3325) — see [the version callout below](#version-notes-and-the-pre-fix-workaround) if you're on an earlier 4.0.x build. |
| 32 | +</Aside> |
| 33 | + |
| 34 | +## Overriding a model method |
| 35 | + |
| 36 | +Define the method in your model CFC. Delegate to the original with `argumentCollection = arguments` so every argument your caller passed flows through unchanged: |
| 37 | + |
| 38 | +```cfm {test:compile} title="app/models/Post.cfc" |
| 39 | +component extends="Model" { |
| 40 | +
|
| 41 | + function config() { |
| 42 | + } |
| 43 | +
|
| 44 | + public any function findAll() { |
| 45 | + // custom logic before calling the framework original |
| 46 | + return superFindAll(argumentCollection = arguments); |
| 47 | + } |
| 48 | +
|
| 49 | +} |
| 50 | +``` |
| 51 | + |
| 52 | +This works for any public framework method mixed into models — `findOne()`, `columnNames()`, `save()`, and so on. |
| 53 | + |
| 54 | +## Overriding a controller or view helper |
| 55 | + |
| 56 | +The same convention applies to everything mixed into controllers, which includes all view helpers (views execute in the controller's `variables` scope). Overriding `linkTo()` and delegating to the framework original looks like this: |
| 57 | + |
| 58 | +```cfm {test:compile} title="app/controllers/Posts.cfc" |
| 59 | +component extends="Controller" { |
| 60 | +
|
| 61 | + function config() { |
| 62 | + } |
| 63 | +
|
| 64 | + public string function linkTo() { |
| 65 | + // example: give every generated link a default class |
| 66 | + if (!StructKeyExists(arguments, "class")) { |
| 67 | + arguments.class = "app-link"; |
| 68 | + } |
| 69 | + return superLinkTo(argumentCollection = arguments); |
| 70 | + } |
| 71 | +
|
| 72 | +} |
| 73 | +``` |
| 74 | + |
| 75 | +Every call to `#linkTo(...)#` in that controller's views now runs your version, and `superLinkTo()` hands off to the real framework helper — arguments, routing, and HTML escaping all behave exactly as stock. |
| 76 | + |
| 77 | +### Where the override lives |
| 78 | + |
| 79 | +- **One controller** — define the override in that controller's CFC, as above. |
| 80 | +- **Every controller** — define it in `app/controllers/Controller.cfc`, the base controller your controllers extend. |
| 81 | +- **View helpers, app-wide** — define it in `app/views/helpers.cfm`. That file is included into every controller before the framework mixins are integrated, so your version is already in place when the framework helper arrives — your version wins and the original is registered as `super<name>`. |
| 82 | + |
| 83 | +## Version notes and the pre-fix workaround |
| 84 | + |
| 85 | +Controller and view helper overrides gained `super<name>` delegation in the 4.0.x patch release that fixed [#3325](https://github.com/wheels-dev/wheels/issues/3325). Model overrides have supported it since 3.x. |
| 86 | + |
| 87 | +<Aside type="caution" title="On 4.0.x builds predating the #3325 fix"> |
| 88 | +On earlier 4.0.x releases, overriding a controller or view helper works, but calling `superLinkTo()` (or any controller-side `super<name>`) throws at render time — the alias was never registered outside the model layer. If you can't upgrade yet, capture the framework original directly: |
| 89 | + |
| 90 | +```cfm |
| 91 | +component extends="Controller" { |
| 92 | +
|
| 93 | + function config() { |
| 94 | + variables.coreLinkTo = CreateObject("component", "wheels.view.links").linkTo; |
| 95 | + } |
| 96 | +
|
| 97 | + public string function linkTo() { |
| 98 | + return coreLinkTo(argumentCollection = arguments); |
| 99 | + } |
| 100 | +
|
| 101 | +} |
| 102 | +``` |
| 103 | + |
| 104 | +Delete the workaround and switch to `superLinkTo()` once you're on a build containing the fix. |
| 105 | +</Aside> |
| 106 | + |
| 107 | +<Aside type="note" title="What's overridable"> |
| 108 | +The convention covers methods mixed into **models** and **controllers** (including view helpers). Router/mapper internals are not an app-override surface — there is no `super<name>` alias for mapper methods, and overriding them is not supported. |
| 109 | +</Aside> |
| 110 | + |
| 111 | +## Before you override: the `data-*` pass-through |
| 112 | + |
| 113 | +A common reason people reach for a `linkTo()` override is to add behavior like a JavaScript confirm dialog to generated links. You usually don't need an override for that: every HTML helper passes unknown arguments through as HTML attributes, and arguments starting with `data` are automatically hyphenized — `dataConfirm` becomes `data-confirm`, `data_confirm` works too. |
| 114 | + |
| 115 | +```cfm {test:compile} title="app/views/posts/index.cfm" |
| 116 | +<cfoutput> |
| 117 | + #linkTo( |
| 118 | + route = "editPost", |
| 119 | + key = post.id, |
| 120 | + text = "Edit", |
| 121 | + dataConfirm = "Discard your current draft and edit this post?" |
| 122 | + )# |
| 123 | +
|
| 124 | + #buttonTo( |
| 125 | + route = "post", |
| 126 | + key = post.id, |
| 127 | + text = "Delete", |
| 128 | + method = "delete", |
| 129 | + dataConfirm = "Delete this post permanently?" |
| 130 | + )# |
| 131 | +</cfoutput> |
| 132 | +``` |
| 133 | + |
| 134 | +Pair it with one delegated listener instead of per-link JavaScript: |
| 135 | + |
| 136 | +```js title="app/assets/js/confirm.js" |
| 137 | +document.addEventListener("click", function (e) { |
| 138 | + var el = e.target.closest("[data-confirm]"); |
| 139 | + if (el && !confirm(el.dataset.confirm)) { |
| 140 | + e.preventDefault(); |
| 141 | + e.stopImmediatePropagation(); |
| 142 | + } |
| 143 | +}, true); |
| 144 | +``` |
| 145 | + |
| 146 | +This replaces what plugins like `jsconfirm` did in older Wheels versions — no override, no plugin, and the attribute shows up on any helper that renders a tag. |
| 147 | + |
| 148 | +## Migrating from Wheels 2.5 |
| 149 | + |
| 150 | +If you're upgrading an application from Wheels 2.5 that overrode core methods: |
| 151 | + |
| 152 | +1. Search for usages of `super.methodName()` |
| 153 | +2. Replace them with `superMethodName()` — for example, `super.findAll()` becomes `superFindAll()` |
| 154 | +3. Test the overridden behavior to make sure results match |
| 155 | + |
| 156 | +No changes are required for applications that don't override core methods. |
| 157 | + |
| 158 | +## Related guides |
| 159 | + |
| 160 | +<CardGrid> |
| 161 | + <LinkCard title="Conventions over Configuration" href="/v4-0-0/core-concepts/conventions-over-configuration/" description="The naming and placement conventions that decide where overrides live." /> |
| 162 | + <LinkCard title="Controllers and Actions" href="/v4-0-0/basics/controllers-and-actions/" description="How controllers are assembled — the mixin surface your overrides sit on top of." /> |
| 163 | + <LinkCard title="Packages" href="/v4-0-0/digging-deeper/packages/" description="Package mixins can also override framework methods — the same super delegation applies." /> |
| 164 | + <LinkCard title="Upgrading to 4.x" href="/v4-0-0/upgrading/" description="Version-by-version upgrade notes, including behavior changes in the 4.0.x line." /> |
| 165 | +</CardGrid> |
0 commit comments