Module: Tuile::Component::HasValidation
- Included in:
- HasBadInput, HasValue
- Defined in:
- lib/tuile/component/has_validation.rb,
sig/tuile.rbs
Overview
A field's verdict slot: one message, written from outside the field, and shown by the field as a red well — Theme#error_bg_color, or Theme#error_active_bg_color while focused.
login = Component::Button.new(caption: "Log in")
login.on_click = lambda do
username. = username.empty? ? "Required" : nil
password. = password.empty? ? "Required" : nil
next if [username, password].any?(&:error_message)
authenticate(username.value, password.value)
end
The field turns red on its own; the message needs cells the field
doesn't own, so whoever has them — a FormLayout, or an app's own
Label — subscribes to #on_error_message_change and paints the text in
Theme#error_color.
Included by HasValue, so every field has it. Include it directly in a component that can be invalid without being a field (a form section wrapping several).
Implementation details
A well, not ink on the glyphs. A field's background is what shows its
boundary in the first place, so an invalid field gets a red one and loses
nothing — where red text is invisible on the empty field that is the
required-field case, and invisible again on content carrying colors of its
own. It takes two tokens rather than one because a focused invalid field
still has to look focused (design/decisions.md D_has_validation).
The well reaches the whole widget with nothing forwarding it: a composed field's inner face is marked BG_INHERIT and a group's List declares no background, so both walk up the ordinary background chain and land on the composer's answer.
The field never writes this. It computes no verdicts — it cannot see
the sibling a rule compares against — so it has nothing to write, and that
is what leaves exactly one writer: whoever validates. The discipline that
writer owes is one sentence: set or clear it on every validate pass,
as the example above does with its : nil branches. Vaadin's custom-field
guide warns that sharing one invalid cell between internal and external
validation ends with each overriding the other; Tuile's answer is that the
field's own report is a different member (Tuile::Component::HasBadInput#bad_input? —
derived on read, never stored), so the two never share a cell.
There is deliberately no invalid?: a second predicate beside
bad_input? gives a caller no way to know which to ask, and invalid is
a non-nil message. Assign "" for a verdict with nothing to say.
Unlike bad_input?, this fact is discrete — asserted at a click or a
binder pass, not recomputed per keystroke — which is why it carries a
change notice where bad_input? deliberately doesn't (design/decisions.md
D_bad_input, D_has_validation).
Instance Attribute Summary collapse
-
#on_error_message_change ⇒ Proc, ...
@return — one-arg callable fired with the new message (or
nil) whenever #error_message actually changes — never on a no-op set.
Instance Method Summary collapse
-
#error_bg_color ⇒ Color?
The invalid well, picked up by everything this component paints — including the inner face of a composed field and the List of a group, neither of which forwards anything: both declare no background of their own, so the ordinary chain walks up to this (overrides #error_bg_color).
-
#error_ink? ⇒ Boolean
Whether to paint the invalid well right now.
-
#error_message ⇒ StyledString?
@return — why the field is invalid, or
nilwhen it is not;niluntil something sets it. -
#error_message=(new_message) ⇒ void
Sets the verdict and repaints the field in Theme#error_color;
nilclears it. -
#inspect_details ⇒ ::Array[String]
Adds
error_message=…to #inspect, omitted while valid — so a Testing.get failure dump says which field is already flagged.
Instance Attribute Details
#on_error_message_change ⇒ Proc, ...
@return — one-arg callable fired with the new message
(or nil) whenever #error_message actually changes — never on a
no-op set. Claimed by the container that paints the message; an app
painting its own takes it instead.
63 64 65 |
# File 'lib/tuile/component/has_validation.rb', line 63 def end |
Instance Method Details
#error_bg_color ⇒ Color?
The invalid well, picked up by everything this component paints — including the inner face of a composed field and the List of a group, neither of which forwards anything: both declare no background of their own, so the ordinary chain walks up to this (overrides Tuile::Component#error_bg_color).
94 95 96 97 98 |
# File 'lib/tuile/component/has_validation.rb', line 94 def error_bg_color return nil unless error_ink? active? ? screen.theme.error_active_bg_color : screen.theme.error_bg_color end |
#error_ink? ⇒ Boolean
Whether to paint the invalid well right now. Its own hook because Tuile::Component::HasBadInput widens it: a field holding input its value cannot represent is invalid on the face too, even with no verdict written.
104 |
# File 'lib/tuile/component/has_validation.rb', line 104 def error_ink? = !.nil? |
#error_message ⇒ StyledString?
@return — why the field is invalid, or nil when it
is not; nil until something sets it.
67 |
# File 'lib/tuile/component/has_validation.rb', line 67 def = |
#error_message=(new_message) ⇒ void
This method returns an undefined value.
Sets the verdict and repaints the field in Theme#error_color; nil
clears it. No-op (no repaint, no listener) when unchanged. A String is
parsed via StyledString.parse, as Tuile::Component::HasCaption#caption= does.
Safe on a detached field — an app validates a form it assembled but has not mounted, and Tuile::Component#invalidate is already a no-op there.
@param new_message
77 78 79 80 81 82 83 84 |
# File 'lib/tuile/component/has_validation.rb', line 77 def () = StyledString.parse() unless .nil? return if == = invalidate &.call() end |
#inspect_details ⇒ ::Array[String]
Adds error_message=… to Tuile::Component#inspect, omitted while valid — so
a Testing.get failure dump says which field is already flagged.
109 110 111 112 |
# File 'lib/tuile/component/has_validation.rb', line 109 def inspect_details m = m.nil? ? super : super + ["error_message=#{m.to_s.inspect}"] end |