Module: Tuile::Component::HasBadInput

Includes:
HasValidation
Included in:
BigDecimalField, DateField, DateTimeField, FloatField, IntegerField, TimeField
Defined in:
lib/tuile/component/has_bad_input.rb,
sig/tuile.rbs

Overview

The one fact a parsing field knows that Tuile::Component::HasValue#on_value_change cannot carry: the input is something the field's value cannot represent.

field = Component::IntegerField.new
# …the user types a lone minus, which no Integer can represent:
field.value                 # => nil, exactly as for an untouched field
field.empty?                # => true, likewise — empty of *value*
field.bad_input?            # => true
field.bad_input_message     # => "not a whole number"

So a form asks this before empty?, on every field that can answer:

bad = fields.select { _1.respond_to?(:bad_input?) && _1.bad_input? }
Component::ConfirmWindow.alert("Cannot save", bad.map(&:bad_input_message).join("\n")) if bad.any?

Include it in a field whose parse is partial — whose input can be something its value cannot represent, as a date field's can. Not in a ComboBox, the near miss: its input is a filter rather than a formatting of the value, so a no-match is not a failed conversion and it reverts the query instead.

Implementation details

An includer overrides #bad_input_message and nothing else. Two rules bind that override, and design/decisions.md D_bad_input has the why:

  • Empty input is not bad input. Return nil for an empty buffer even though it parses to nothing, or every blank optional field blocks a save.
  • One frozen constant per field kind, no interpolation"not a valid date", never "'xyz' is not a valid date". It is read per call, and a future error ink would read it per paint.

Ask at the moment you need the answer and you always get the current one: it is derived on read, never stored. Which fields can answer is a class fact worth caching; what they answer is not. There is deliberately no change notice, because the fact is continuous — every prefix of a valid date is bad input — so anything reacting per keystroke flashes through the act of typing correctly, while a save gate consulted at a click sees one settled state. The ink is the one consumer that cannot be asked at a click, so it has #bad_input_settled?: a field whose whole grammar is prefix-bad latches the well on its commit gesture instead of painting it per keystroke.

Instance Attribute Summary

Attributes included from HasValidation

#on_error_message_change

Instance Method Summary collapse

Instance Method Details

#bad_input?Boolean

@return — true iff the field is holding input its value cannot represent.

Returns:

  • (Boolean)


62
# File 'lib/tuile/component/has_bad_input.rb', line 62

def bad_input? = !bad_input_message.nil?

#bad_input_messageString?

Why the current input cannot be turned into a Tuile::Component::HasValue#value — the single override point.

@return — the reason, or nil when the input converts (a field holding no input converts: it is empty, not bad).

Returns:

  • (String, nil)


58
# File 'lib/tuile/component/has_bad_input.rb', line 58

def bad_input_message = raise(NotImplementedError, "#{self.class} must implement bad_input_message")

#bad_input_settled?Boolean

Whether bad input may paint the well yet. true here, so the well is as continuous as the report: a FloatField reddens at the half-typed "1.", which is a fair warning while the residue is one or two transient buffers. Override it to latch where the grammar makes every prefix bad input, or the well is red for the whole time the user types a correct value:

def bad_input_settled? = @settled   # set on commit, cleared on an edit

It gates the ink only: #bad_input? is a pull, and a save gate asking at a click must get the answer settled or not (design/decisions.md D_bad_input).

Returns:

  • (Boolean)


85
# File 'lib/tuile/component/has_bad_input.rb', line 85

def bad_input_settled? = true

#error_bg_colorColor?

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).

Returns:



9394
# File 'sig/tuile.rbs', line 9394

def error_bg_color: () -> Color?

#error_ink?Boolean

Widens Tuile::Component::HasValidation#error_ink?: bad input paints the invalid well too, with no verdict written — once #bad_input_settled? says the report may be shown.

Returns:

  • (Boolean)


70
# File 'lib/tuile/component/has_bad_input.rb', line 70

def error_ink? = (bad_input? && bad_input_settled?) || super

#error_messageStyledString?

@return — why the field is invalid, or nil when it is not; nil until something sets it.

Returns:



9377
# File 'sig/tuile.rbs', line 9377

def error_message: () -> StyledString?

#error_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

Parameters:



9387
# File 'sig/tuile.rbs', line 9387

def error_message=: ((String | StyledString)? new_message) -> void

#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.

Returns:

  • (::Array[String])


9398
# File 'sig/tuile.rbs', line 9398

def inspect_details: () -> ::Array[String]