Form Helpers in Wheels 4.0: Object Forms, HTML5 Fields, and Error Rendering #3272
bpamiri
announced in
Announcements
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
You've hand-written a Wheels form at least once — a dozen
<input name="user[email]">tags, each with a hand-typed value, a hand-typed id so the<label for="...">lines up, and a loop at the top to print validation errors. It works, until you fat-finger one bracket and that field silently never saves, or validation fails and the value the user typed is gone because you forgot to re-populate one input.This post is the working tour of the helpers that exist precisely so you stop hand-typing — and stop subtly mis-typing — the name, id, value, maxlength, label, and error wrapper.
Read: https://blog.wheels.dev/posts/form-helpers-objects-and-html5
The one mental model
When you call
textField(objectName="user", property="email"),objectNameis the variable name — a string — not the object instance. The helper resolvesvariables["user"]for you, reads.email, and (with the defaultlabelPlacement="around") wraps the input in its label:The
name="user[email]"is the load-bearing part: on submit it round-trips intoparams.user.email. Bind every field toobjectName="user"and the whole form arrives as oneparams.userstruct — which is exactly why controllers domodel("User").new(params.user). Pass the object instance instead of its name and you getWheels.InvalidArgument.What the post covers
userobject, thecfparamthat keeps a failed-save re-render from blowing up, and the controller half that mass-assignsparams.user.emailField,urlField,numberField,telField,dateField,colorField,rangeField,searchField, plustextFieldwith an overridabletype. There is notimeField,dateTimeField/datetimeLocalField,monthField, orweekField— reach for one and you get a runtime error. Date/time-as-dropdowns aredateSelect/timeSelect/dateTimeSelect.startFormTag(method="put")rewrites to POST plus a hidden_methodfield, because HTML forms can't do real PUT/PATCH/DELETE. It also auto-appends the CSRFauthenticityTokenFieldfor non-GET methods. Hand-writemethod="put"on a raw<form>and the browser falls back to GET, leaking the token into the URL.checkBoxdefaults tocheckedValue=1/uncheckedValue=0, emitting a companion hidden input nameduser[active]($checkbox); the dispatcher collapses it into the real param only when the box didn't post. PassuncheckedValue=""and the param goes absent again.textField,select) emitname="user[field]"→ nestedparams.user.field. The*Tagvariants (searchFieldTag,selectTag) emit a flatname→params.q. Use object helpers when a model exists; Tag helpers for search bars, filters, and login forms.errorMessagesForreturns a<ul class="error-messages">of every error on the object;errorMessageOnreturns the first message for a single property, inline. Both return""when clean and both throwWheels.IncorrectArgumentsin development if the named variable isn't an object. The field helpers also auto-wrap themselves in yourerrorElement/errorClasswhen the bound property is invalid.label()— labels come from thelabel=/labelPlacement=args on field helpers, which wirefor=to the auto-derived id.label="false"suppresses it.A couple of sharp edges worth flagging here
textField("user", "email", label="X")mixes positional and named and will misbehave. The moment you pass options, everything is named.errorMessagesForshows duplicates by default (showDuplicates=true) — passshowDuplicates=falseto collapse identical messages.checkedValueauto-checks only on numeric1or booleantrue— a string"true"/"yes"won't. SetcheckedValueexplicitly to match what your column stores.selectuses exact match, so a stored"Doe,John"won't accidentally select a"Doe"option. Onlymultiple=truetreats the value as a comma list.valuearg was passed (or it's empty). Passvalue="..."and it overrides the stored value.Discussion
What's your house style — one consolidated
errorMessagesForblock at the top, or inlineerrorMessageOnnext to each field? Anyone leaning on the*Taghelpers for complex filter bars, or do you wrap those in a throwaway object to get the nested params? And has the missingtimeField/dateTimeFieldever bitten you mid-build? Replies welcome.Beta Was this translation helpful? Give feedback.
All reactions