NAME

Term::Fabulous::Manual::Forms - Forms and input widgets

DESCRIPTION

This page is part of Term::Fabulous::Manual. Previous page: Term::Fabulous::Manual::Events. Next page: Term::Fabulous::Manual::Feedback.

This page is the guide to the input widgets: text fields, text areas, checkboxes, radio buttons, dropdowns, sliders, star ratings, segmented controls and the color picker. It first describes what all inputs have in common (value, Change event, id, focus, disabled and read-only inputs, the visual states, size and checking input), then shows each input widget with a short example and a picture, explains how the text inputs are edited (keys, selection, clipboard, undo), and ends with complete forms: reading all values and building a form from a KDL layout file. The reference of each widget is on its class page; complete programs are in Term::Fabulous::Cookbook::Forms.

The page assumes you know how to build a widget tree ("WIDGETS AND THE WIDGET TREE" in Term::Fabulous::Manual::Layout) and how listeners work ("EVENTS" in Term::Fabulous::Manual::Events).

FORMS AND INPUT WIDGETS

A form is a box with input widgets in it. Term::Fabulous has no form widget of its own: any Term::Fabulous::Widget::Box holds inputs, and because the events of the inputs bubble up to it, one listener on the box sees every change.

The input widgets

Term::Fabulous::Widget::TextField

One line of text, optionally masked for passwords. Fires Submit on Enter. See "Text fields".

Term::Fabulous::Widget::TextArea

Several lines of text, wrapped or scrolled sideways, with a scrollbar. See "Text areas".

Term::Fabulous::Widget::Checkbox

On or off, with an optional "indeterminate" third look. See "Checkboxes".

Term::Fabulous::Widget::RadioGroup and Term::Fabulous::Widget::RadioButton

One choice of several, all visible. See "Radio buttons".

Term::Fabulous::Widget::Dropdown

One choice of several, from a list that opens over the other widgets. See "Dropdowns".

Term::Fabulous::Widget::Slider

A number from a range. See "Sliders".

Term::Fabulous::Widget::StarRating

A number of stars, whole or half, editable or read-only. See "Star ratings".

Term::Fabulous::Widget::SegmentedControl

One choice of a few, shown side by side as one bar. See "Segmented controls".

Term::Fabulous::Widget::ColorPicker

A color, typed, set with sliders or picked from a list of named swatches. See "Color pickers".

examples/form.pl uses all of them in one form. The picture shows it after the user typed a name and a note, chose a color and started an e-mail address; the status line at the bottom shows the last Change or, as here, what is wrong with a value (see "Checking input").

A form with name, password, notes, color, size, volume, newsletter, terms, e-mail and port; the e-mail field holds ada@ in red and the status line says email: Please enter an e-mail address.

All input widgets except the radio group and the color picker are subclasses of Term::Fabulous::Widget::Input, whose page describes the shared parameters and methods in full. The text field and the text area share a second base class, Term::Fabulous::Widget::TextInput, with the editing keys, the placeholder and read_only. A radio group is a box that holds radio buttons; it takes the focus and holds the value for all of them. A color picker is a box built from a text field, a switch, sliders and a table; its parts take the focus, and the picker holds the value.

Values

Every input has a value method that reads its current value:

To set the value from your program, call value with an argument on a text field, text area, radio group, dropdown or slider, and checked on a checkbox (a checkbox's value is read only). A radio button's value is different: it is the value the button gives its group when it is selected, and writing it does not select the button.

$name->value('Ada Lovelace');
$terms->checked(1);
$size->value('l');           # a radio group: selects the button with the value 'l'
$color->value('navy');       # a dropdown: selects the option with the value 'navy'
$volume->value(75);

Setting a value from the program never fires Change. Three methods act like the user instead and do fire it: $checkbox->toggle, $group->choose($button) and $dropdown->choose($index).

The Change and Submit events

An input fires a Term::Fabulous::Event::Change when the user changes its value. $event->value is the new value and $event->target the input that changed. A radio group fires it on the group, not on the button.

use Clay::UI::Enum::Result;

$volume->on(
	Change => sub ($event) {
		say 'Volume: ', $event->value;
		return Clay::UI::Enum::Result->CONTINUE;
	}
);

Change bubbles to the ancestors of the input, unless a listener on the way stops it (see "Return values and bubbling" in Term::Fabulous::Manual::Events), so one listener on the form box sees the changes of all inputs inside it. Tell the inputs apart by their id:

$form->on(
	Change => sub ($event) {
		$status->text( $event->target->id . ' is now ' . ( $event->value // 'nothing' ) );
		return;
	}
);

A text field also fires a Term::Fabulous::Event::Submit when the user presses Enter, with the text as $event->value. A text area uses Enter for a new line and fires no Submit. To react to Enter in other inputs, listen for KeyPress on the form (see "Application shortcuts" in Term::Fabulous::Manual::Events).

Ids

Give every input an id (id => 'email' in Perl, TextField "email" in KDL). The id identifies the input in a form-wide listener ($event->target->id), and find_by_id finds it in the tree, which is how a program gets hold of the inputs of a form built from a layout file. Ids must be unique within a widget tree (see "Widget ids" in Term::Fabulous::Manual::Layout).

Focus

The input that has the focus (see "focus" in Term::Fabulous::Manual::Glossary) receives the key presses. Tab moves the focus to the next input, Shift+Tab (BackTab) to the previous one, and a click on an input focuses it. A radio group is one focus stop for all its buttons. To put the cursor into the first field when the program starts:

$ui->interaction->set_focused_widget($name);

The focused input shows it: its content is painted on its focus_background_color, and a text input shows a block cursor. Keys an input does not use, such as Tab, Escape and the function keys, bubble on to its ancestors, so application shortcuts keep working while the user types. The focus section of the events page describes the focus order, can_focus and how to change the order.

Disabled inputs

disabled greys an input out: it is painted in its disabled_color, ignores keys, clicks and the mouse wheel, and cannot take the focus, so Tab skips it. An input that has the focus loses it when it is disabled. Buttons and radio groups have disabled too; a radio button counts as disabled while its group is.

my $password = Term::Fabulous::Widget::TextField->new( id => 'password', mask => '*', disabled => 1 );

$terms->on(
	Change => sub ($event) {
		$password->disabled( !$event->value );    # enabled while the box is checked
		return;
	}
);

The program can still set the value of a disabled input. See "disabled" in Term::Fabulous::Widget::Input and the recipe Disable inputs until a checkbox is checked.

Read-only text

A text field or text area with read_only => 1 keeps its normal look and can take the focus; the user can move the cursor, select and copy, but not change the text. Use it for text the user should be able to copy, such as a generated key or a log; use disabled when the input should look unavailable. See "read_only" in Term::Fabulous::Widget::TextInput.

Visual states

The inputs show their state themselves; you choose the colors:

  • Focused: the content on focus_background_color. A text input shows the cursor as a block (the character under it in reverse video), a slider paints its thumb in text_color, and a radio group highlights the button the keyboard is on.

  • Disabled: everything in disabled_color.

  • Invalid: while the value is empty but required, or rejected by the validator, the text is drawn in invalid_color and the border, where there is one, in the theme's danger color (a red in the built-in themes). An empty text field or dropdown has no text to color and keeps its gray placeholder. A disabled input shows the disabled look instead. See "Checking input".

  • Placeholder: an empty text input, or a dropdown without a selection, shows its placeholder in placeholder_color.

  • Masked: a text field with a mask shows the mask character for every character of the text.

  • Selected text: selected text in a text input is painted on selection_color.

  • Checked, selected, open: check marks, the selected radio button, the filled part of a slider, the dropdown's arrow and the border of its open list are painted in accent_color.

The colors are parameters and accessors of every input (text_color, disabled_color, invalid_color, accent_color, focus_background_color, see "CONSTRUCTOR" in Term::Fabulous::Widget::Input) and of some inputs only (placeholder_color, selection_color, track_color, list_background_color, highlight_text_color). They take every color format. The pictures in "THE INPUT WIDGETS ONE BY ONE" show each widget in its states.

Your program can ask for the state too: $input->is_focused, $input->is_enabled, $input->is_valid, or $input->has_state('focused') with the state names focused, hovered, pressed and disabled (see "has_state" in Term::Fabulous::Widget; invalid is not a widget state, ask is_valid).

Size

Without a sizing rule in its layout, an input is exactly as big as its content: a text field is preferred_columns wide (default 20) and one row high, a text area preferred_columns by preferred_rows (40 by 5), a checkbox or radio button as wide as its mark and label, a dropdown as wide as its longest label plus the arrow, a slider preferred_columns plus its value label. A sizing rule in the layout wins:

use Clay::XS qw(sizing_grow sizing_fixed);

# A text field that fills the width of its row:
Term::Fabulous::Widget::TextField->new( layout => { sizing => { width => sizing_grow() } } );

# A text area as wide as its parent and eight rows high:
Term::Fabulous::Widget::TextArea->new( layout => { sizing => { width => sizing_grow(), height => sizing_fixed(8) } } );

To line up the labels of a form, put each label into a box of the same width group (see "Equal sizes across the tree" in Term::Fabulous::Manual::Layout), as examples/kdl-form.pl does. The size section of Term::Fabulous::Widget::Input explains how to line up inputs of different natural widths.

Checking input

Two things keep input in shape. A text input can restrict what the user may type at all, with accept; and every input can say whether its value is acceptable, with required and validator. They are separate on purpose: accept is an editing rule, applied as the user types and pastes, validator is a rule about the whole value.

Restricting characters. accept takes the body of a character class ('0-9', 'a-zA-Z ', '^0-9' for everything but digits), a regular expression every character must match (qr/\p{L}/) or a code reference; see "accept" in Term::Fabulous::Widget::TextInput for the details. A key the spec rejects does nothing; pasted text keeps its accepted characters and drops the rest, so pasting +49 170 1234 into a digits-only field inserts the digits. Setting value from the program to a text with a rejected character dies: the restriction is for the user.

my $zip = Term::Fabulous::Widget::TextField->new( id => 'zip', accept => '0-9', max_length => 5 );

Checking the value. required => 1 makes an empty value invalid: empty text, a dropdown without a selection, an unchecked checkbox. validator checks a non-empty value: the name of a built-in validator (email, integer, number, url, hostname, ip, date, time), a regular expression the whole value must match, a code reference that returns what is wrong with the value (or nothing), a list of those, or a Term::Fabulous::Validator object when a validator takes options. An empty value that is not required is always fine; the validator never sees it.

use Term::Fabulous::Validator;

my $name  = Term::Fabulous::Widget::TextField->new( id => 'name',  required  => 1 );
my $email = Term::Fabulous::Widget::TextField->new( id => 'email', validator => 'email', placeholder => 'name@example.com' );
my $port  = Term::Fabulous::Widget::TextField->new( id => 'port',  validator => Term::Fabulous::Validator->integer( min => 1, max => 65535 ) );
my $code  = Term::Fabulous::Widget::TextField->new( id => 'code',  validator => qr/\A[A-Z]{3}\z/ );
my $even  = Term::Fabulous::Widget::TextField->new( id => 'even',  validator => sub ($value) { $value % 2 ? 'Please enter an even number.' : undef } );
my $terms = Term::Fabulous::Widget::Checkbox->new( id => 'terms', label => 'I accept the terms', required => 1 );

The validators that know their characters also restrict typing: integer lets the user type digits and a minus sign only, unless the field has an accept of its own.

What the user sees. An input whose value is invalid shows the invalid look at once, while the user types, not only when the user leaves the field: its text in invalid_color and its border, where it has one, in the theme's danger color. An empty text field or dropdown has no text to color and keeps its gray placeholder, so an empty required field looks invalid only when it has a border, which turns red. The picture shows every case (see "Invalid values" in Term::Fabulous::Widget::Input):

Eleven inputs: a focused e-mail field with ada@exa in red, a valid address in white, ada@ in red without and with a red border, an empty required field that looks normal and one whose border is red, a red unchecked check box, a dropdown showing its gray placeholder, ada@ in purple, ada@ in yellow with a yellow border, and a disabled field in gray

The input does not show the message; the message is yours to place. $input->error is the message right now (undef when the value is fine), $input->is_valid the same as a boolean, and the input fires Term::Fabulous::Event::ValidityChange after every Change whose message differs from the last one reported:

use Term::Fabulous::Widget::Text;

my $hint = Term::Fabulous::Widget::Text->new( text => ' ', text_color => '#e06c75' );
$form->on(
	ValidityChange => sub ($event) {
		$hint->text( $event->error // ' ' );
		return;
	}
);

Checking a whole form. When the user submits, ask the form: $form->invalid_inputs lists the inputs below a widget that are not valid, in layout order, so a Submit listener (or a button) can stop and move the focus to the first one:

$form->on(
	Submit => sub ($event) {
		if ( my @invalid = $form->invalid_inputs ) {
			$hint->text( $invalid[0]->error );
			$ui->interaction->set_focused_widget( $invalid[0] );
			return;
		}
		save( form_values($form) );
		return;
	}
);

Setting a value from the program fires no ValidityChange, as it fires no Change; call $input->validate to report it, or $_->validate foreach $form->invalid_inputs once after building a form to show its initial state. The messages are in English by default; give your own with required_message and the message option of every validator ("CONSTRUCTORS" in Term::Fabulous::Validator).

The recipe A login form checks a whole form this way when the user presses Enter. Check the values of a form uses every built-in validator and shows each message next to its field, and Write your own checks and restrict typing shows accept, patterns, code and lists of validators.

THE INPUT WIDGETS ONE BY ONE

Every picture below comes from a program in examples/widgets/, which shows the widget in its states; run it to try the keys. The class page of each widget is the reference: all parameters, methods, keys, mouse actions, events and KDL properties.

Text fields

A Term::Fabulous::Widget::TextField holds one line of text. It scrolls sideways when the text is wider than the field, shows a placeholder while it is empty, limits the text to max_length characters if you set one, and fires Submit on Enter.

use Term::Fabulous::Widget::TextField;

my $name = Term::Fabulous::Widget::TextField->new(
	id                => 'name',
	placeholder       => 'Your name',
	max_length        => 40,
	preferred_columns => 30,
);
$name->on(
	Submit => sub ($event) {
		say 'Hello, ', $event->value;
		return;
	}
);

The picture shows a focused field in which Lovelace is selected, an empty field with a placeholder, a masked password and a disabled field (examples/widgets/text-field.pl):

Four text fields: a focused field with Ada Lovelace typed and Lovelace selected, a placeholder, a masked password and a disabled field

In KDL:

TextField "name" {
	placeholder "Your name"
	max_length 40
	preferred_columns 30
}

Password fields

A text field with a mask shows every character as the mask character. value and the events still give the real text. A masked text cannot be copied or cut, and the word keys and the double click act on the whole text, so they do not reveal where its spaces are.

my $password = Term::Fabulous::Widget::TextField->new( id => 'password', mask => '*' );

$password->mask(undef);    # show the text, for example while a "Show password" box is checked

In KDL: TextField "password" { mask "*"; }. See "mask" in Term::Fabulous::Widget::TextField.

Text areas

A Term::Fabulous::Widget::TextArea holds several lines of text. Enter starts a new line. Long lines wrap at spaces (or, with wrap => 0, the view scrolls sideways), and a scrollbar shows the position while the text has more rows than the area.

use Clay::XS qw(sizing_grow sizing_fixed);
use Term::Fabulous::Widget::TextArea;

my $notes = Term::Fabulous::Widget::TextArea->new(
	id          => 'notes',
	placeholder => 'Anything else?',
	layout      => { sizing => { width => sizing_grow(), height => sizing_fixed(6) } },
);

my @lines = split /\n/, $notes->value, -1;

The picture shows a focused text area with a wrapped line and a scrollbar (examples/widgets/text-area.pl):

A text area with a shopping list, a wrapped long line and a scrollbar

In KDL:

TextArea "notes" {
	placeholder "Anything else?"
	sizing width=grow height="fixed(6)"
}

Checkboxes

A Term::Fabulous::Widget::Checkbox is on or off. Space, Enter or a click toggles it. Its value is 1 or 0, and you set it with checked. A checkbox can also be indeterminate, a third look for a box that stands for a group of boxes of which only some are checked.

use Term::Fabulous::Widget::Checkbox;

my $news = Term::Fabulous::Widget::Checkbox->new(
	id      => 'newsletter',
	label   => 'Send me the newsletter',
	checked => 1,
);
say $news->checked ? 'subscribed' : 'not subscribed';

The picture shows a focused, an unchecked, a checked, an indeterminate and a disabled checkbox (examples/widgets/checkbox.pl):

Five check boxes: focused, unchecked, checked, indeterminate and disabled

In KDL:

Checkbox "newsletter" {
	label "Send me the newsletter"
	checked #true
}

Radio buttons

A Term::Fabulous::Widget::RadioGroup lets the user choose one of several Term::Fabulous::Widget::RadioButtons, all visible at once. The buttons are children of the group (or deeper inside it, for example in a box). The group holds the value, takes the focus for all its buttons and fires Change; the arrow keys move the selection, and a click selects a button. The buttons are laid out from top to bottom unless the group's layout says otherwise.

use Clay::XS qw(CLAY_LEFT_TO_RIGHT);
use Term::Fabulous::Widget::RadioGroup;
use Term::Fabulous::Widget::RadioButton;

my $size = Term::Fabulous::Widget::RadioGroup->new(
	id     => 'size',
	value  => 'm',
	layout => { layout_direction => CLAY_LEFT_TO_RIGHT, child_gap => 2 },
);
$size->add_child( Term::Fabulous::Widget::RadioButton->new( label => $_->[0], value => $_->[1] ) )
	foreach [ Small => 's' ], [ Medium => 'm' ], [ Large => 'l' ];

A button without a value stands for its label. The picture shows a focused group in a row, a group in a column and a disabled group (examples/widgets/radio.pl):

Radio buttons in a row with Medium chosen, in a column with Express shipping chosen, and a disabled group

In KDL:

RadioGroup "size" {
	value "m"
	layout direction=right gap=2
	RadioButton { label "Small"; value "s"; }
	RadioButton { label "Medium"; value "m"; }
	RadioButton { label "Large"; value "l"; }
}

A Term::Fabulous::Widget::Dropdown shows the selected option and opens a list of all options on Enter, Space, Alt+Down, F4 or a click. The list floats over the other widgets, below the dropdown, or above it when it does not fit below and there is more room above. Every option has a label (what the user sees) and a value (what value and Change give); an option given as a plain string is both. An option can be disabled ({ label => 'Archive', disabled => 1 }): it is shown greyed out and the user cannot choose it, as in a segmented control. Up and Down change the selection without opening the list, and typing the first letters of a label jumps to it.

use Term::Fabulous::Widget::Dropdown;

my $color = Term::Fabulous::Widget::Dropdown->new(
	id          => 'color',
	placeholder => 'Pick a color',
	options     => [ 'Red', 'Green', [ 'Dark blue' => 'navy' ] ],
);
$color->value('navy');
say $color->selected_label;    # Dark blue

The picture shows a dropdown with its placeholder, one with a selection, and a focused one with its list open (examples/widgets/dropdown.pl):

Three dropdowns: a placeholder, France selected, and an open list of colors with Blue highlighted

In KDL, options adds options whose value is their label, and option adds one option with a value of its own:

Dropdown "color" {
	placeholder "Pick a color"
	options "Red" "Green"
	option "Dark blue" value="navy"
}

Sliders

A Term::Fabulous::Widget::Slider chooses a number between min and max, in steps of step. The arrow keys move it by a step, PageUp and PageDown by a page_step, Home and End to the ends; the mouse clicks, drags and turns the wheel. The value is shown right of the track, formatted with value_format.

use Term::Fabulous::Widget::Slider;

my $volume = Term::Fabulous::Widget::Slider->new(
	id           => 'volume',
	min          => 0,
	max          => 100,
	step         => 5,
	value        => 30,
	value_format => '%d%%',
);

The picture shows a focused slider, one that formats its value with a code reference, and a disabled one (examples/widgets/slider.pl):

Three sliders: focused at 65 percent, a temperature of 21.5 degrees, and a disabled one

In KDL:

Slider "volume" {
	step 5
	value 30
	value_format "%d%%"
}

Star ratings

A Term::Fabulous::Widget::StarRating shows max stars (five by default), the first value of them filled, and lets the user choose with the arrow keys, the digits, a click on a star or the wheel; while the pointer hovers over a star, the stars up to it are previewed. With half the value moves in half stars. read_only shows a rating without letting the user change it, for lists and cards, and show_value adds the value as text.

use Term::Fabulous::Widget::StarRating;

my $rating = Term::Fabulous::Widget::StarRating->new(
	id         => 'rating',
	value      => 3,
	show_value => 1,
);

The picture shows a focused rating, one with half stars, a read-only one, one out of ten without gaps and a disabled one (examples/widgets/star-rating.pl):

Star ratings: a focused one with three of five stars, one with half stars and its value, a read-only one, one out of ten with a gap of zero, and a disabled one

In KDL:

StarRating "rating" {
	value 3
	show_value #true
}

Segmented controls

A Term::Fabulous::Widget::SegmentedControl shows a few options side by side as one bar and highlights the selected one: what a radio group does, in one row and without marks. The options are given like a dropdown's (a label, [ label, value ] or a hash, which may also disable the option). The arrow keys, Home, End and the digits choose, so does a click, and the selection changes at once. A vertical control stacks the segments, and a control whose layout makes it wider than its labels shares the space among the segments.

use Term::Fabulous::Widget::SegmentedControl;

my $period = Term::Fabulous::Widget::SegmentedControl->new(
	id      => 'period',
	options => [ [ Day => 'd' ], [ Week => 'w' ], [ Month => 'm' ] ],
	value   => 'w',
);

The picture shows a focused control, one stretched to the full width, one with a disabled segment, a vertical one and a disabled one (examples/widgets/segmented-control.pl):

Segmented controls: a focused one with Week selected, one stretched to the full width, one with a disabled segment, a vertical one, and a disabled one

In KDL:

SegmentedControl "period" {
	options "Day" "Week" "Month"
	value "Week"
}

Color pickers

A Term::Fabulous::Widget::ColorPicker chooses a color in three ways: the user types it into a field (#rrggbb, rgb(...), hsl(...), a color name), moves sliders, or picks a named swatch from a list, when the program gives it one. A sample next to the field shows the color. The sliders show red, green and blue, or hue, saturation and lightness; a segmented control above them switches between the two. An alpha slider below them is left out with alpha => 0, which also makes the field refuse translucent colors.

use Term::Fabulous::Widget::ColorPicker;

my $accent = Term::Fabulous::Widget::ColorPicker->new(
	id       => 'accent',
	value    => '#61afef',
	alpha    => 0,
	swatches => [ [ '#61afef', 'Sky', '#61afef' ], [ '#98c379', 'Mint', '#98c379' ], [ none => 'No color' ] ],
);

The value is a string: what the user typed, when it is a color, the color of the sliders as #rrggbb (#rrggbbaa with an alpha below 255), or the value of the chosen swatch. A swatch is [ $value, $label, $color ], and its value need not be a color at all: a theme's token name, or none for no color. $picker->rgba gives the color as numbers, or undef for a value without one.

The picker fires Change when the user changes the value and Submit on Enter in the field or on a swatch, both with itself as the target. The events of its parts stop inside it, so a listener on a form sees only the picker's. It is no Term::Fabulous::Widget::Input, but it has validate, error and is_valid, those of its field, so it can be a field of a Term::Fabulous::Widget::Prompt: OK then keeps the prompt open while the field holds text that is no color, and puts the focus on the field (see "Ask for a color (ColorPicker in a Prompt)" in Term::Fabulous::Cookbook::Forms).

The picture shows a picker with swatches, the RGB sliders and the alpha slider, and one without swatches and alpha in HSL (examples/widgets/color-picker.pl):

Two color pickers. The upper one has a framed list of named swatches with color samples on the left, Orange selected, an empty field with a sample of the orange next to it, the RGB/HSL switch on RGB and the red, green, blue and alpha sliders at the orange. The lower one has no list and no alpha slider, its field shows #2e8b57 and its sliders show hue, saturation and lightness. Below each picker a line names the chosen value and its channels

In KDL, without swatches, which only Perl can give:

ColorPicker "background" {
	value "#282c34"
	alpha #false
	sliders "hsl"
}

EDITING TEXT

The text field and the text area are edited the same way. What follows is a summary; the reference is the keys and mouse sections of Term::Fabulous::Widget::TextInput.

Editing keys

  • Typing inserts at the cursor and replaces the selection.

  • Left and Right move by a character, Ctrl+Left and Ctrl+Right by a word, Home and End to the start and end of the line, Ctrl+Home and Ctrl+End to the start and end of the text. In a text area, Up, Down, PageUp and PageDown move by rows.

  • Backspace and Delete delete a character, Ctrl+W and Ctrl+Delete a word, Ctrl+U and Ctrl+K everything to the start or the end of the line.

  • In a text field, Enter fires Submit; in a text area, it starts a new line.

Tab is never inserted; it moves the focus. The cursor moves by grapheme clusters (see "grapheme cluster" in Term::Fabulous::Manual::Glossary), so it never stops inside a character that is made of several code points, and wide characters such as CJK take two columns.

Selecting text

Hold Shift with any movement key to select (Shift+Right, Ctrl+Shift+Left, Shift+End, ...), or press Ctrl+A to select everything. With the mouse, drag over the text, or double-click a word. Selected text is painted on the input's selection_color, as in the picture of the text field above. Typing, Backspace or a paste replaces the selection.

The clipboard

Ctrl+Insert copies the selection, Ctrl+X or Shift+Delete cuts it, and Ctrl+V or Shift+Insert pastes. Ctrl+C is not copy: it ends the program, unless the UI was made with stop_on_ctrl_c => 0 (see "Keys Term::Fabulous handles itself" in Term::Fabulous::Manual::Events).

The clipboard is one string shared by all text inputs of the program, not the clipboard of your desktop. Your program reads and writes it with clipboard from Term::Fabulous::Editor:

use Term::Fabulous::Editor;

Term::Fabulous::Editor->clipboard('order-4711');    # Ctrl+V now pastes this
my $copied = Term::Fabulous::Editor->clipboard;      # what the user copied last

See the recipe Copy and paste through the clipboard and, for a key that loads the desktop clipboard, the example Load the system clipboard on a key press.

Undo and redo

Ctrl+Z undoes the last change and Ctrl+Y redoes it. Typing is undone one word (or one run of spaces) at a time; every other edit is one step. Up to 100 steps are kept. Setting the text with value clears the history.

Changing the text from the program

value replaces the whole text. For anything finer, use the Term::Fabulous::Editor of the input, which holds the text, the cursor, the selection and the undo history. After changing it, call mark_changed on the input so that a frame is drawn; the input then scrolls to the cursor and shows the change. Edits through the editor fire no Change.

# Append a line at the end of a text area and show it:
my $editor = $log->editor;
$editor->move_document_end;
$editor->insert( $editor->is_empty ? $line : "\n$line" );
$log->mark_changed;

# Select the whole text of a field:
$name->editor->select_all;
$name->mark_changed;

KEYS OF THE INPUT WIDGETS

The keys each input uses while it has the focus. Keys an input does not use bubble to its ancestors. Each class page lists the details in its KEYS section.

Widget        Keys
------------  --------------------------------------------------------------
TextField     typing, editing keys (see EDITING TEXT), Enter: Submit
TextArea      typing, editing keys, Up/Down/PageUp/PageDown (with Shift:
              select), Enter: new line
Checkbox      Space, Enter: toggle
RadioGroup    Up/Left, Down/Right: previous/next button (wraps around);
              Home, End: first/last button; Space, Enter: select the
              button the keyboard is on
Dropdown      closed: Enter, Space, Alt+Down, F4: open the list;
              Up, Down: previous/next option; Home, End: first/last
              option; letters: jump to a label
              open: Up, Down, PageUp, PageDown, Home, End: move the
              highlight; Enter, Space: choose; Escape: close;
              letters: jump to a label
Slider        Left/Down, Right/Up: one step; PageDown, PageUp: one
              page_step; Home, End: lowest, highest value
StarRating    Left/Down, Right/Up: one star (or half); Home, End: no
              stars, all stars; 0-9: that many stars
Segmented-    Left/Up, Right/Down: previous/next segment (wraps
Control       around); Home, End: first/last segment; 1-9: that segment

The class pages: "KEYS" in Term::Fabulous::Widget::TextInput, "KEYS" in Term::Fabulous::Widget::TextField, "KEYS" in Term::Fabulous::Widget::TextArea, "KEYS" in Term::Fabulous::Widget::Checkbox, "KEYS" in Term::Fabulous::Widget::RadioGroup, "KEYS" in Term::Fabulous::Widget::Dropdown, "KEYS" in Term::Fabulous::Widget::Slider, "KEYS" in Term::Fabulous::Widget::StarRating, "KEYS" in Term::Fabulous::Widget::SegmentedControl.

COMPLETE FORMS

A small form

A complete program with a name field and a checkbox; one listener on the form box reports every change, and Enter in the name field ends the program:

use v5.32;
use warnings;
use feature 'signatures';
no warnings 'experimental::signatures';

use Clay::XS qw(sizing_grow CLAY_TOP_TO_BOTTOM);
use Term::Fabulous;
use Term::Fabulous::Widget::Box;
use Term::Fabulous::Widget::Checkbox;
use Term::Fabulous::Widget::Text;
use Term::Fabulous::Widget::TextField;

my $form = Term::Fabulous::Widget::Box->new(
	layout => {
		layout_direction => CLAY_TOP_TO_BOTTOM,
		sizing           => { width => sizing_grow(), height => sizing_grow() },
		child_gap        => 1,
	},
);
my $name   = Term::Fabulous::Widget::TextField->new( id => 'name', placeholder => 'Your name' );
my $terms  = Term::Fabulous::Widget::Checkbox->new( id => 'terms', label => 'I accept the terms' );
my $status = Term::Fabulous::Widget::Text->new(
	text       => 'Type your name, then press Enter.',
	text_color => [ 220, 220, 220, 255 ],
);
$form->add_child( $name, $terms, $status );

my $ui = Term::Fabulous->new( root => $form, width => 80, height => 24 );

# One listener for every input of the form.
$form->on(
	Change => sub ($event) {
		$status->text( $event->target->id . ' is now ' . $event->value );
		return;
	}
);
$name->on(
	Submit => sub ($event) {
		$ui->loop->stop;
		return;
	}
);

# Start with the cursor in the name field.
$ui->interaction->set_focused_widget($name);
$ui->run;

say 'Name: ', $name->value, ', terms accepted: ', $terms->value;

Reading a whole form

Every input has a value reader, so a short function collects the values of all inputs with an id into a hash, for example to save them:

# { id => value } of every input at or below $node that has an id.
sub form_values ( $node, $values = {} ) {
	my $is_input = $node->isa('Term::Fabulous::Widget::Input') || $node->isa('Term::Fabulous::Widget::RadioGroup');
	$values->{ $node->id } = $node->value
		if $is_input && defined $node->id && !$node->isa('Term::Fabulous::Widget::RadioButton');
	if ( $node->can('children') ) {
		form_values( $_, $values ) foreach @{ $node->children };
	}
	return $values;
}

my $values = form_values($form);    # { name => 'Ada', terms => 1, ... }

Radio buttons are skipped: their group holds the state. The recipe Read all values of a form explains the function in detail; to fill a form from saved values, call value (or checked for a checkbox) on each input in the same way.

A form in a KDL layout file

A form can be described in KDL (see "KDL LAYOUT FILES" in Term::Fabulous::Manual::KDL) and built with Term::Fabulous::Layout. The program then finds the inputs by their ids. examples/kdl-form.pl builds a form with every input widget except the text area, shows each change in a status line and the values of all inputs on F2:

A form built from KDL with name, password, size, color, volume and newsletter, and a status line with all values

The recipe Build a form from a KDL file shows and explains the program.

More form recipes

Term::Fabulous::Cookbook::Forms has complete programs for:

To write an input widget of your own, see "SUBCLASS INTERFACE" in Term::Fabulous::Widget::Input and Term::Fabulous::Manual::CustomWidgets.

SEE ALSO

This page is part of Term::Fabulous::Manual. Previous page: Term::Fabulous::Manual::Events. Next page: Term::Fabulous::Manual::Feedback.

The class pages: Term::Fabulous::Widget::Input, Term::Fabulous::Widget::TextInput, Term::Fabulous::Widget::TextField, Term::Fabulous::Widget::TextArea, Term::Fabulous::Widget::Checkbox, Term::Fabulous::Widget::RadioGroup, Term::Fabulous::Widget::RadioButton, Term::Fabulous::Widget::Dropdown, Term::Fabulous::Widget::Slider, Term::Fabulous::Widget::StarRating, Term::Fabulous::Widget::SegmentedControl, Term::Fabulous::Widget::ColorPicker, Term::Fabulous::Editor, Term::Fabulous::Validator, Term::Fabulous::Event::Change, Term::Fabulous::Event::ValidityChange, Term::Fabulous::Event::Submit.

The recipes: Term::Fabulous::Cookbook::Forms.