Module: Tuile::Component::HasValidation
- Extended by:
- Listeners::Declare
- 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 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
#shown_message 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 Tuile::ComponentBackground::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 — so its notice fires straight
off the write, where Tuile::Component::HasBadInput#on_bad_input_change has a continuous
fact to settle first (design/decisions.md D_bad_input,
D_has_validation).
Defined Under Namespace
Classes: ErrorMessageChangeEvent
Class Method Summary collapse
-
.listener ⇒ void
Defines the slot's reader, which returns the Listeners — or, given a block, registers it and returns the
Proc.
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. -
#on_error_message_change ⇒ Listeners
Fired with an ErrorMessageChangeEvent whenever #error_message actually changes — never on a no-op set.
-
#shown_message ⇒ StyledString, ...
The message a consumer with cells of its own paints beside the field: the verdict here, and HasBadInput widens it to prefer the field's own report, which outranks a verdict computed a pass ago.
Methods included from Listeners::Declare
Class Method Details
.listener ⇒ void
This method returns an undefined value.
Defines the slot's reader, which returns the Listeners — or, given a
block, registers it and returns the Proc.
An optional block is the slot's transition block, run on the owner whenever the list becomes non-empty or empty again. It is what lets a widget install a bridge only while somebody is listening:
listener :on_enter do |claimed|
claimed ? editor.on_enter << @bridge : editor.on_enter.remove(@bridge)
end
The list is built on first read, so no mixin has to remember a
constructor line — which means in-class code goes through the reader and
never touches @on_foo, nil until somebody asks.
@param name — the slot's full name, on_-prefixed. Spelling it out is what keeps on_value_change greppable from its declaration.
@return — name.
13128 |
# File 'sig/tuile.rbs', line 13128 def self.listener: (Symbol name) ?{ (bool claimed) -> void } -> Symbol |
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).
120 121 122 123 124 |
# File 'lib/tuile/component/has_validation.rb', line 120 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.
130 |
# File 'lib/tuile/component/has_validation.rb', line 130 def error_ink? = !.nil? |
#error_message ⇒ StyledString?
@return — why the field is invalid, or nil when it
is not; nil until something sets it.
81 |
# File 'lib/tuile/component/has_validation.rb', line 81 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
91 92 93 94 95 96 97 98 |
# File 'lib/tuile/component/has_validation.rb', line 91 def () = StyledString.parse() unless .nil? return if == = invalidate .fire(ErrorMessageChangeEvent.new(source: self, error_message: )) 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.
135 136 137 138 |
# File 'lib/tuile/component/has_validation.rb', line 135 def inspect_details m = m.nil? ? super : super + ["error_message=#{m.to_s.inspect}"] end |
#on_error_message_change ⇒ Listeners
Fired with an ErrorMessageChangeEvent whenever #error_message actually changes — never on a no-op set. The container that paints the message registers here, and so may an app painting its own: the list takes both, which a single slot could not.
77 |
# File 'lib/tuile/component/has_validation.rb', line 77 listener :on_error_message_change |
#shown_message ⇒ StyledString, ...
The message a consumer with cells of its own paints beside the field: the verdict here, and Tuile::Component::HasBadInput widens it to prefer the field's own report, which outranks a verdict computed a pass ago.
field.on_error_message_change { message_label.caption = field.shown_message.to_s }
field.on_bad_input_change { message_label.caption = field.shown_message.to_s }
Register on both: two channels with two writers, either of which moves it.
@return — nil when there is nothing to show;
a plain String when it is the field's own bad-input report.
110 |
# File 'lib/tuile/component/has_validation.rb', line 110 def = |