=head1 ui-lib.pl
Common functions for generating HTML for Webmin user interface elements.
Some example code :
require '../ui-lib.pl';
ui_print_header(undef, 'My Module', '');
print ui_form_start('save.cgi');
print ui_table_start('My form', undef, 2);
print ui_table_row('Enter your name',
ui_textbox('name', undef, 40));
print ui_table_end();
print ui_form_end([ [ undef, 'Save' ] ]);
ui_print_footer('/', 'Webmin index');
=cut
####################### table generation functions
=head2 ui_table_start(heading, [tabletags], [cols], [&default-tds], [right-heading])
Returns HTML for the start of a form block into which labelled inputs can
be placed. By default this is implemented as a table with another table inside
it, but themes may override this with their own layout.
The parameters are :
=item heading - Text to show at the top of the form.
=item tabletags - HTML attributes to put in the outer
, typically something like width=100%.
=item cols - Desired number of columns for labels and fields. Defaults to 4, but can be 2 for forms with lots of wide inputs.
=item default-tds - An optional array reference of HTML attributes for the
tags in each row of the table.
=item right-heading - HTML to appear in the heading, aligned to the right.
=cut
sub ui_table_start
{
return &theme_ui_table_start(@_) if (defined(&theme_ui_table_start));
my ($heading, $tabletags, $cols, $tds, $rightheading) = @_;
if (defined($main::ui_table_cols)) {
# Push on stack, for nested call
push(@main::ui_table_cols_stack, $main::ui_table_cols);
push(@main::ui_table_pos_stack, $main::ui_table_pos);
push(@main::ui_table_default_tds_stack, $main::ui_table_default_tds);
}
my $colspan = 1;
my $rv;
$rv .= "
\n";
if (defined($heading) || defined($rightheading)) {
$rv .= "
";
if (defined($heading)) {
$rv .= "
$heading
"
}
if (defined($rightheading)) {
$rv .= "
$rightheading
";
$colspan++;
}
$rv .= "
\n";
}
$rv .= "
".
"
\n";
$main::ui_table_cols = $cols || 4;
$main::ui_table_pos = 0;
$main::ui_table_default_tds = $tds;
return $rv;
}
=head2 ui_table_end
Returns HTML for the end of a block started by ui_table_start.
=cut
sub ui_table_end
{
return &theme_ui_table_end(@_) if (defined(&theme_ui_table_end));
my $rv;
if ($main::ui_table_cols == 4 && $main::ui_table_pos) {
# Add an empty block to balance the table
$rv .= &ui_table_row(" ", " ");
}
if (@main::ui_table_cols_stack) {
$main::ui_table_cols = pop(@main::ui_table_cols_stack);
$main::ui_table_pos = pop(@main::ui_table_pos_stack);
$main::ui_table_default_tds = pop(@main::ui_table_default_tds_stack);
}
else {
$main::ui_table_cols = undef;
$main::ui_table_pos = undef;
$main::ui_table_default_tds = undef;
}
$rv .= "
\n";
return $rv;
}
=head2 ui_table_row(label, value, [cols], [&td-tags])
Returns HTML for a row in a table started by ui_table_start, with a 1-column
label and 1+ column value. The parameters are :
=item label - Label for the input field. If this is undef, no label is displayed.
=item value - HTML for the input part of the row.
=item cols - Number of columns the value should take up, defaulting to 1.
=item td-tags - Array reference of HTML attributes for the
tags in this row.
=cut
sub ui_table_row
{
return &theme_ui_table_row(@_) if (defined(&theme_ui_table_row));
my ($label, $value, $cols, $tds) = @_;
$cols ||= 1;
$tds ||= $main::ui_table_default_tds;
my $rv;
if ($main::ui_table_pos+$cols+1 > $main::ui_table_cols &&
$main::ui_table_pos != 0) {
# If the requested number of cols won't fit in the number
# remaining, start a new row
$rv .= "\n";
$main::ui_table_pos = 0;
}
$rv .= "
\n"
if ($main::ui_table_pos%$main::ui_table_cols == 0);
$rv .= "
\n";
$main::ui_table_pos = 0;
}
return $rv;
}
=head2 ui_table_hr
Returns HTML for a row in a block started by ui_table_row, with a horizontal
line inside it to separate sections.
=cut
sub ui_table_hr
{
return &theme_ui_table_hr(@_) if (defined(&theme_ui_table_hr));
my $rv;
if ($ui_table_pos) {
$rv .= "\n";
$ui_table_pos = 0;
}
$rv .= "
".
"
\n";
return $rv;
}
=head2 ui_table_span(text)
Outputs a table row that spans the whole table, and contains the given text.
=cut
sub ui_table_span
{
my ($text) = @_;
return &theme_ui_table_hr(@_) if (defined(&theme_ui_table_hr));
my $rv;
if ($ui_table_pos) {
$rv .= "\n";
$ui_table_pos = 0;
}
$rv .= "
".
"
$text
\n";
return $rv;
}
=head2 ui_columns_start(&headings, [width-percent], [noborder], [&tdtags], [heading])
Returns HTML for the start of a multi-column table, with the given headings.
The parameters are :
=item headings - An array reference of headers for the table's columns.
=item width-percent - Desired width as a percentage, or undef to let the browser decide.
=item noborder - Set to 1 if the table should not have a border.
=item tdtags - An optional reference to an array of HTML attributes for the table's
tags.
=item heading - An optional heading to put above the table.
=cut
sub ui_columns_start
{
return &theme_ui_columns_start(@_) if (defined(&theme_ui_columns_start));
my ($heads, $width, $noborder, $tdtags, $title) = @_;
my $rv;
$rv .= "
\n";
if ($title) {
$rv .= "
".
"
$title
\n";
}
$rv .= "
\n";
my $i;
for($i=0; $i<@$heads; $i++) {
$rv .= "
\n";
return $rv;
}
=head2 ui_columns_row(&columns, &tdtags)
Returns HTML for a row in a multi-column table. The parameters are :
=item columns - Reference to an array containing the HTML to show in the columns for this row.
=item tdtags - An optional array reference containing HTML attributes for the row's
tags.
=cut
sub ui_columns_row
{
return &theme_ui_columns_row(@_) if (defined(&theme_ui_columns_row));
my ($cols, $tdtags) = @_;
my $rv;
$rv .= "
\n";
return $rv;
}
=head2 ui_columns_header(&columns, &tdtags)
Returns HTML for a row in a multi-column table, styled as a header. Parameters
are the same as ui_columns_row.
=cut
sub ui_columns_header
{
return &theme_ui_columns_header(@_) if (defined(&theme_ui_columns_header));
my ($cols, $tdtags) = @_;
my $rv;
$rv .= "
\n";
return $rv;
}
=head2 ui_checked_columns_row(&columns, &tdtags, checkname, checkvalue, [checked?], [disabled])
Returns HTML for a row in a multi-column table, in which the first column
contains a checkbox. The parameters are :
=item columns - Reference to an array containing the HTML to show in the columns for this row.
=item tdtags - An optional array reference containing HTML attributes for the row's
tags.
=item checkname - Name for the checkbox input. Should be the same for all rows.
=item checkvalue - Value for this checkbox input.
=item checked - Set to 1 if it should be checked by default.
=item disabled - Set to 1 if the checkbox should be disabled and thus un-clickable.
=cut
sub ui_checked_columns_row
{
return &theme_ui_checked_columns_row(@_) if (defined(&theme_ui_checked_columns_row));
my ($cols, $tdtags, $checkname, $checkvalue, $checked, $disabled) = @_;
my $rv;
$rv .= "
\n";
return $rv;
}
=head2 ui_textbox(name, value, size, [disabled?], [maxlength], [tags])
Returns HTML for a text input box. The parameters are :
=item name - Name for this input.
=item value - Initial contents for the text box.
=item size - Desired width in characters.
=item disabled - Set to 1 if this text box should be disabled by default.
=item maxlength - Maximum length of the string the user is allowed to input.
=item tags - Additional HTML attributes for the tag.
=cut
sub ui_textbox
{
return &theme_ui_textbox(@_) if (defined(&theme_ui_textbox));
my ($name, $value, $size, $dis, $max, $tags) = @_;
$size = &ui_max_text_width($size);
return "";
}
=head2 ui_filebox(name, value, size, [disabled?], [maxlength], [tags], [dir-only])
Returns HTML for a text box for choosing a file. Parameters are the same
as ui_textbox, except for the extra dir-only option which limits the chooser
to directories.
=cut
sub ui_filebox
{
return &theme_ui_filebox(@_) if (defined(&theme_ui_filebox));
my ($name, $value, $size, $dis, $max, $tags, $dironly) = @_;
return &ui_textbox($name, $value, $size, $dis, $max, $tags)." ".
&file_chooser_button($name, $dironly);
}
=head2 ui_bytesbox(name, bytes, [size], [disabled?])
Returns HTML for entering a number of bytes, but with friendly kB/MB/GB
options. May truncate values to 2 decimal points! The parameters are :
=item name - Name for this input.
=item bytes - Initial number of bytes to show.
=item size - Desired width of the text box part.
=item disabled - Set to 1 if this text box should be disabled by default.
=cut
sub ui_bytesbox
{
my ($name, $bytes, $size, $dis) = @_;
my $units = 1;
if ($bytes >= 10*1024*1024*1024*1024) {
$units = 1024*1024*1024*1024;
}
elsif ($bytes >= 10*1024*1024*1024) {
$units = 1024*1024*1024;
}
elsif ($bytes >= 10*1024*1024) {
$units = 1024*1024;
}
elsif ($bytes >= 10*1024) {
$units = 1024;
}
else {
$units = 1;
}
if ($bytes ne "") {
$bytes = sprintf("%.2f", ($bytes*1.0)/$units);
$bytes =~ s/\.00$//;
}
$size = &ui_max_text_width($size || 8);
return &ui_textbox($name, $bytes, $size, $dis)." ".
&ui_select($name."_units", $units,
[ [ 1, "bytes" ],
[ 1024, "kB" ],
[ 1024*1024, "MB" ],
[ 1024*1024*1024, "GB" ],
[ 1024*1024*1024*1024, "TB" ] ], undef, undef, undef, $dis);
}
=head2 ui_upload(name, size, [disabled?], [tags])
Returns HTML for a file upload input, for use in a form with the form-data
method. The parameters are :
=item name - Name for this input.
=item size - Desired width in characters.
=item disabled - Set to 1 if this text box should be disabled by default.
=item tags - Additional HTML attributes for the tag.
=cut
sub ui_upload
{
return &theme_ui_upload(@_) if (defined(&theme_ui_upload));
my ($name, $size, $dis, $tags) = @_;
$size = &ui_max_text_width($size);
return "";
}
=head2 ui_password(name, value, size, [disabled?], [maxlength])
Returns HTML for a password text input. Parameters are the same as ui_textbox,
and behaviour is identical except that the user's input is not visible.
=cut
sub ui_password
{
return &theme_ui_password(@_) if (defined(&theme_ui_password));
my ($name, $value, $size, $dis, $max) = @_;
$size = &ui_max_text_width($size);
return "";
}
=head2 ui_hidden(name, value)
Returns HTML for a hidden field with the given name and value.
=cut
sub ui_hidden
{
return &theme_ui_hidden(@_) if (defined(&theme_ui_hidden));
my ($name, $value) = @_;
return "\n";
}
=head2 ui_select(name, value|&values, &options, [size], [multiple], [add-if-missing], [disabled?], [javascript])
Returns HTML for a drop-down menu or multiple selection list. The parameters
are :
=item name - Name for this input.
=item value - Either a single initial value, or an array reference of values if this is a multi-select list.
=item options - An array reference of possible options. Each element can either be a scalar, or a two-element array ref containing a submitted value and displayed text.
=item size - Desired vertical size in rows, which defaults to 1. For multi-select lists, this must be set to something larger.
=item multiple - Set to 1 for a multi-select list, 0 for single.
=item add-if-missing - If set to 1, any value that is not in the list of options will be automatically added (and selected).
=item disabled - Set to 1 to disable this input.
=item javascript - Additional HTML attributes for the
\n";
$rv .= "
\n";
$rv .= "
\n";
return $rv;
}
=head2 ui_hidden_table_row_end(name)
MISSING DOCUMENTATION
=cut
sub ui_hidden_table_row_end
{
return &theme_ui_hidden_table_row_end(@_)
if (defined(&theme_ui_hidden_table_row_end));
my ($name) = @_;
return "
\n";
}
=head2 ui_hidden_table_start(heading, [tabletags], [cols], name, status, [&default-tds], [rightheading])
A table with a heading and table inside, and which is collapsible
=cut
sub ui_hidden_table_start
{
return &theme_ui_hidden_table_start(@_)
if (defined(&theme_ui_hidden_table_start));
my ($heading, $tabletags, $cols, $name, $status, $tds, $rightheading) = @_;
my $rv;
if (!$main::ui_hidden_start_donejs++) {
$rv .= &ui_hidden_javascript();
}
my $divid = "hiddendiv_$name";
my $openerid = "hiddenopener_$name";
my $defimg = $status ? "open.gif" : "closed.gif";
my $defclass = $status ? 'opener_shown' : 'opener_hidden';
my $text = defined($tconfig{'cs_text'}) ? $tconfig{'cs_text'} :
defined($gconfig{'cs_text'}) ? $gconfig{'cs_text'} : "000000";
$rv .= "
\n";
my $colspan = 1;
if (defined($heading) || defined($rightheading)) {
$rv .= "
\n";
$main::ui_table_cols = $cols || 4;
$main::ui_table_pos = 0;
$main::ui_table_default_tds = $tds;
return $rv;
}
=head2 ui_hidden_table_end(name)
Returns HTML for the end of table with hiding, as started by
ui_hidden_table_start
=cut
sub ui_hidden_table_end
{
my ($name) = @_;
return &theme_ui_hidden_table_end(@_) if (defined(&theme_ui_hidden_table_end));
return "
\n";
}
=head2 ui_tabs_start(&tabs, name, selected, show-border)
Render a row of tabs from which one can be selected. Each tab is an array
ref containing a name, title and link.
=cut
sub ui_tabs_start
{
return &theme_ui_tabs_start(@_) if (defined(&theme_ui_tabs_start));
my ($tabs, $name, $sel, $border) = @_;
my $rv;
if (!$main::ui_hidden_start_donejs++) {
$rv .= &ui_hidden_javascript();
}
# Build list of tab titles and names
my $tabnames = "[".join(",", map { "\"".&html_escape($_->[0])."\"" } @$tabs)."]";
my $tabtitles = "[".join(",", map { "\"".&html_escape($_->[1])."\"" } @$tabs)."]";
$rv .= "\n";
# Output the tabs
my $imgdir = "$gconfig{'webprefix'}/images";
$rv .= &ui_hidden($name, $sel)."\n";
$rv .= "
\n";
$rv .= "
";
if ($ENV{'HTTP_USER_AGENT'} !~ /msie/i) {
# For some reason, the 1-pixel space above the tabs appears huge on IE!
$rv .= "";
}
$rv .= "
\n";
$rv .= "
\n";
$rv .= "
\n";
foreach my $t (@$tabs) {
if ($t ne $tabs[0]) {
# Spacer
$rv .= "
".
"
\n";
}
my $tabid = "tab_".$t->[0];
$rv .= "
";
$rv .= "
";
if ($t->[0] eq $sel) {
# Selected tab
$rv .= "
".
"
";
$rv .= "
".
" $t->[1]
";
$rv .= "
".
"
";
}
else {
# Other tab (which has a link)
$rv .= "
\n";
if ($border) {
# All tabs are within a grey box
$rv .= "
\n";
$rv .= "
\n";
$rv .= "
\n";
$rv .= "
\n";
$rv .= "
";
}
$main::ui_tabs_selected = $sel;
return $rv;
}
=head2 ui_tabs_end(border)
MISSING DOCUMENTATION
=cut
sub ui_tabs_end
{
return &theme_ui_tabs_end(@_) if (defined(&theme_ui_tabs_end));
my ($border) = @_;
my $rv;
my $imgdir = "$gconfig{'webprefix'}/images";
if ($border) {
$rv .= "
\n";
$rv .= "
\n";
$rv .= "
\n";
$rv .= "
\n";
$rv .= "
\n";
}
return $rv;
}
=head2 ui_tabs_start_tab(name, tab)
Must be called before outputting the HTML for the named tab
=cut
sub ui_tabs_start_tab
{
return &theme_ui_tabs_start_tab(@_) if (defined(&theme_ui_tabs_start_tab));
my ($name, $tab) = @_;
my $defclass = $tab eq $main::ui_tabs_selected ?
'opener_shown' : 'opener_hidden';
my $rv = "
\n";
return $rv;
}
=head2 ui_tabs_start_tabletab(name, tab)
Behaves like ui_tabs_start_tab, but for use within a ui_table_start block
=cut
sub ui_tabs_start_tabletab
{
return &theme_ui_tabs_start_tabletab(@_)
if (defined(&theme_ui_tabs_start_tabletab));
my $div = &ui_tabs_start_tab(@_);
return "
\n".$div."
\n";
}
sub ui_tabs_end_tab
{
return &theme_ui_tabs_end_tab(@_) if (defined(&theme_ui_tabs_end_tab));
return "\n";
}
sub ui_tabs_end_tabletab
{
return &theme_ui_tabs_end_tabletab(@_)
if (defined(&theme_ui_tabs_end_tabletab));
return "
\n";
}
=head2 ui_max_text_width(width, [text-area?])
Returns a new width for a text field, based on theme settings
=cut
sub ui_max_text_width
{
my ($w, $ta) = @_;
my $max = $ta ? $tconfig{'maxareawidth'} : $tconfig{'maxboxwidth'};
return $max && $w > $max ? $max : $w;
}
####################### radio hidden functions
=head2 ui_radio_selector(&opts, name, selected)
Returns HTML for a set of radio buttons, each of which shows a different
block of HTML when selected. &opts is an array ref to arrays containing
[ value, label, html ]
=cut
sub ui_radio_selector
{
return &theme_ui_radio_selector(@_) if (defined(&theme_ui_radio_selector));
my ($opts, $name, $sel) = @_;
my $rv;
if (!$main::ui_radio_selector_donejs++) {
$rv .= &ui_radio_selector_javascript();
}
my $optnames =
"[".join(",", map { "\"".&html_escape($_->[0])."\"" } @$opts)."]";
foreach my $o (@$opts) {
$rv .= &ui_oneradio($name, $o->[0], $o->[1], $sel eq $o->[0],
"onClick='selector_show(\"$name\", \"$o->[0]\", $optnames)'");
}
$rv .= " \n";
foreach my $o (@$opts) {
my $cls = $o->[0] eq $sel ? "selector_shown" : "selector_hidden";
$rv .= "
[0] class=$cls>".$o->[2]."
\n";
}
return $rv;
}
sub ui_radio_selector_javascript
{
return <
.selector_shown {display:inline}
.selector_hidden {display:none}
EOF
}
####################### grid layout functions
=head2 ui_grid_table(&elements, columns, [width-percent], [tds], [tabletags], [title])
Given a list of HTML elements, formats them into a table with the given
number of columns. However, themes are free to override this to use fewer
columns where space is limited.
=cut
sub ui_grid_table
{
return &theme_ui_grid_table(@_) if (defined(&theme_ui_grid_table));
my ($elements, $cols, $width, $tds, $tabletags, $title) = @_;
return "" if (!@$elements);
my $rv = "
\n";
my $i;
for($i=0; $i<@$elements; $i++) {
$rv .= "
" if ($i%$cols == $cols-1);
}
if ($i%$cols) {
while($i%$cols) {
$rv .= "
[$i%$cols]." class='ui_grid_cell'>".
"
\n";
$i++;
}
$rv .= "\n";
}
$rv .= "
\n";
if (defined($title)) {
$rv = "
\n".
($title ? "
$title
\n" : "").
"
$rv
\n".
"
";
}
return $rv;
}
=head2 ui_radio_table(name, selected, &rows)
Returns HTML for a table of radio buttons, each of which has a label and
some associated inputs to the right.
=cut
sub ui_radio_table
{
return &theme_ui_radio_table(@_) if (defined(&theme_ui_radio_table));
my ($name, $sel, $rows) = @_;
return "" if (!@$rows);
my $rv = "
\n";
return $rv;
}
=head2 ui_up_down_arrows(uplink, downlink, up-show, down-show)
Returns HTML for moving some objects in a table up or down
=cut
sub ui_up_down_arrows
{
return &theme_ui_up_down_arrows(@_) if (defined(&theme_ui_up_down_arrows));
my ($uplink, $downlink, $upshow, $downshow) = @_;
my $mover;
my $imgdir = "$gconfig{'webprefix'}/images";
if ($downshow) {
$mover .= "".
"";
}
else {
$mover .= "";
}
if ($upshow) {
$mover .= "".
"";
}
else {
$mover .= "";
}
return $mover;
}
=head2 ui_hr
Returns a horizontal row tag
=cut
sub ui_hr
{
return &theme_ui_hr() if (defined(&theme_ui_hr));
return "\n";
}
=head2 ui_nav_link(direction, url, disabled)
Returns an arrow icon linking to provided url
=cut
sub ui_nav_link
{
return &theme_ui_nav_link(@_) if (defined(&theme_ui_nav_link));
my ($direction, $url, $disabled) = @_;
my $alt = $direction eq "left" ? '<-' : '->';
if ($disabled) {
return "\n";
}
else {
return "\n";
}
}
=head2 ui_confirmation_form(cgi, message, &hiddens, [&buttons], [&otherinputs], [extra-warning])
Returns HTML for a form asking for confirmation before performing some
action, such as deleting a user.
=cut
sub ui_confirmation_form
{
my ($cgi, $message, $hiddens, $buttons, $others, $warning) = @_;
my $rv;
$rv .= "
\n";
return $rv;
}
####################### javascript functions
=head2 js_disable_inputs
js_disable_input(&disable-inputs, &enable-inputs, [tag])
Returns Javascript to disable some form elements and enable others
=cut
sub js_disable_inputs
{
my $rv;
my $f;
foreach $f (@{$_[0]}) {
$rv .= "e = form.elements[\"$f\"]; e.disabled = true; ";
$rv .= "for(i=0; i= 0) {
# When enabling both a _def field and its associated text field,
# disable the text if the _def is set to 1
my $tf = $1;
$rv .= "e = form.elements[\"$f\"]; for(i=0; i";
$rv .= &ui_form_start($cgi) if ($cgi);
# Far left link, if needed
if (@_ > 5) {
if ($farleft) {
$rv .= "\n";
}
else {
$rv .= "\n";
}
}
# Left link
if ($left) {
$rv .= "\n";
}
else {
$rv .= "\n";
}
# Message and inputs
$rv .= $msg;
$rv .= " ".$inputs if ($inputs);
# Right link
if ($right) {
$rv .= "\n";
}
else {
$rv .= "\n";
}
# Far right link, if needed
if (@_ > 5) {
if ($farright) {
$rv .= "\n";
}
else {
$rv .= "\n";
}
}
$rv .= " ".$below if ($below);
$rv .= &ui_form_end() if ($cgi);
$rv .= "\n";
return $rv;
}
=head2 js_checkbox_disable(name, &checked-disable, &checked-enable, [tag])
MISSING DOCUMENTATION
=cut
sub js_checkbox_disable
{
my $rv;
my $f;
foreach $f (@{$_[1]}) {
$rv .= "form.elements[\"$f\"].disabled = $_[0].checked; ";
}
foreach $f (@{$_[2]}) {
$rv .= "form.elements[\"$f\"].disabled = !$_[0].checked; ";
}
return $_[3] ? "$_[3]='$rv'" : $rv;
}
=head2 js_redirect(url, [window-object])
Returns HTML to trigger a redirect to some URL
=cut
sub js_redirect
{
my ($url, $window) = @_;
if (defined(&theme_js_redirect)) {
return &theme_js_redirect(@_);
}
return "\n";
}
1;