use vars qw($theme_no_table $ui_radio_selector_donejs $module_name $ui_multi_select_donejs, $ui_formcount); =head1 ui-lib.pl Common functions for generating HTML for Webmin user interface elements. Some example code : use WebminCore; init_config(); 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 \n"; $main::ui_table_pos = 0; } $rv .= "\n" if ($main::ui_table_pos%$main::ui_table_cols == 0); $rv .= "\n" if (defined($label)); $rv .= "\n"; $main::ui_table_pos += $cols+(defined($label) ? 1 : 0); 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 .= " ". "\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 ". "\n"; } $rv .= "\n"; my $i; for($i=0; $i<@$heads; $i++) { $rv .= "\n"; } $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 \n"; my $i; for($i=0; $i<@$cols; $i++) { $rv .= "\n"; } $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"; my $i; for($i=0; $i<@$cols; $i++) { $rv .= "\n"; } $rv .= "\n"; return $rv; } =head2 ui_checked_columns_row(&columns, &tdtags, checkname, checkvalue, [checked?], [disabled], [tags]) 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 \n"; $rv .= "\n"; my $i; for($i=0; $i<@$cols; $i++) { $rv .= "\n"; $rv .= "\n"; my $i; for($i=0; $i<@$cols; $i++) { $rv .= " ". "\n". "\n". "\n"; } =head2 ui_buttons_hr([title]) Returns HTML for a separator row, for use inside a ui_buttons_start block. =cut sub ui_buttons_hr { my ($title) = @_; return &theme_ui_buttons_hr(@_) if (defined(&theme_ui_buttons_hr)); if ($title) { return "\n"; } else { return "\n"; } } ####################### header and footer functions =head2 ui_post_header([subtext]) Returns HTML to appear directly after a standard header() call. This is never called directly - instead, ui_print_header calls it. But it can be overridden by themes. =cut sub ui_post_header { return &theme_ui_post_header(@_) if (defined(&theme_ui_post_header)); my ($text) = @_; my $rv; $rv .= "
$text
\n" if (defined($text)); if (!$tconfig{'nohr'} && !$tconfig{'notophr'}) { $rv .= "
\n"; } return $rv; } =head2 ui_pre_footer Returns HTML to appear directly before a standard footer() call. This is never called directly - instead, ui_print_footer calls it. But it can be overridden by themes. =cut sub ui_pre_footer { return &theme_ui_pre_footer(@_) if (defined(&theme_ui_pre_footer)); my $rv; if (!$tconfig{'nohr'} && !$tconfig{'nobottomhr'}) { $rv .= "\n"; } return $rv; } =head2 ui_print_header(subtext, image, [help], [config], [nomodule], [nowebmin], [rightside], [head-stuff], [body-stuff], [below]) Print HTML for a header with the post-header line. The args are the same as those passed to header(), defined in web-lib-funcs.pl, with the addition of the subtext parameter : =item subtext - Text to display below the title =item title - The text to show at the top of the page =item image - An image to show instead of the title text. This is typically left blank. =item help - If set, this is the name of a help page that will be linked to in the title. =item config - If set to 1, the title will contain a link to the module's config page. =item nomodule - If set to 1, there will be no link in the title section to the module's index. =item nowebmin - If set to 1, there will be no link in the title section to the Webmin index. =item rightside - HTML to be shown on the right-hand side of the title. Can contain multiple lines, separated by
. Typically this is used for links to stop, start or restart servers. =item head-stuff - HTML to be included in the section of the page. =item body-stuff - HTML attributes to be include in the tag. =item below - HTML to be displayed below the title. Typically this is used for application or server version information. =cut sub ui_print_header { &load_theme_library(); return &theme_ui_print_header(@_) if (defined(&theme_ui_print_header)); my ($text, @args) = @_; &header(@args); print &ui_post_header($text); } =head2 ui_print_unbuffered_header(subtext, args...) Like ui_print_header, but ensures that output for this page is not buffered or contained in a table. This should be called by scripts that are producing output while performing some long-running process. =cut sub ui_print_unbuffered_header { &load_theme_library(); return &theme_ui_print_unbuffered_header(@_) if (defined(&theme_ui_print_unbuffered_header)); $| = 1; $theme_no_table = 1; &ui_print_header(@_); } =head2 ui_print_footer(args...) Print HTML for a footer with the pre-footer line. Args are the same as those passed to footer(). =cut sub ui_print_footer { return &theme_ui_print_footer(@_) if (defined(&theme_ui_print_footer)); my @args = @_; print &ui_pre_footer(); &footer(@args); } =head2 ui_config_link(text, &subs) Returns HTML for a module config link. The first non-null sub will be replaced with the appropriate URL for the module's config page. =cut sub ui_config_link { return &theme_ui_config_link(@_) if (defined(&theme_ui_config_link)); my ($text, $subs) = @_; my @subs = map { $_ || "../config.cgi?$module_name" } ($subs ? @$subs : ( undef )); return "

".&text($text, @subs)."

\n"; } =head2 ui_print_endpage(text) Prints HTML for an error message followed by a page footer with a link to /, then exits. Good for main page error messages. =cut sub ui_print_endpage { return &theme_ui_print_endpage(@_) if (defined(&theme_ui_print_endpage)); my ($text) = @_; print $text,"

\n"; &ui_print_footer("/", $text{'index'}); exit; } =head2 ui_subheading(text, ...) Returns HTML for a section heading whose message is the given text strings. =cut sub ui_subheading { return &theme_ui_subheading(@_) if (defined(&theme_ui_subheading)); return "

".join("", @_)."

\n"; } =head2 ui_links_row(&links) Returns HTML for a row of links, like select all / invert selection / add.. Each element of the links array ref should be an HTML fragment like : Create new user =cut sub ui_links_row { return &theme_ui_links_row(@_) if (defined(&theme_ui_links_row)); my ($links) = @_; return @$links ? join("\n|\n", @$links)."
\n" : ""; } ########################### collapsible section / tab functions =head2 ui_hidden_javascript Returns EOF } =head2 ui_hidden_start(title, name, status, thisurl) Returns HTML for the start of a collapsible hidden section, such as for advanced options. When clicked on, the section header will expand to display whatever is between this function and ui_hidden_end. The parameters are : =item title - Text for the start of this hidden section. =item name - A unique name for this section. =item status - 1 if it should be initially open, 0 if not. =item thisurl - URL of the current page. This is used by themes on devices that don't support Javascript to implement the opening and closing. =cut sub ui_hidden_start { return &theme_ui_hidden_start(@_) if (defined(&theme_ui_hidden_start)); my ($title, $name, $status, $url) = @_; 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'; $rv .= "*\n"; $rv .= "$title
\n"; $rv .= "
\n"; return $rv; } =head2 ui_hidden_end(name) Returns HTML for the end of a hidden section, started by ui_hidden_start. =cut sub ui_hidden_end { return &theme_ui_hidden_end(@_) if (defined(&theme_ui_hidden_end)); my ($name) = @_; return "
\n"; } =head2 ui_hidden_table_row_start(title, name, status, thisurl) Similar to ui_hidden_start, but for use within a table started with ui_table_start. I recommend against using this where possible, as it can be difficult for some themes to implement. =cut sub ui_hidden_table_row_start { return &theme_ui_hidden_table_row_start(@_) if (defined(&theme_ui_hidden_table_row_start)); my ($title, $name, $status, $url) = @_; my ($rv, $rrv); 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'; $rrv .= "\n"; $rrv .= "$title
\n"; $rv .= &ui_table_row(undef, $rrv, $main::ui_table_cols); $rv .= "
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 .= "" } if (defined($rightheading)) { $rv .= ""; $colspan++; } $rv .= "\n"; } $rv .= "
$heading$rightheading
". "\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 .= "
[0] class='ui_label'>$label[1] class='ui_value'>$value

$text
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
[$i].">". ($heads->[$i] eq "" ? "
" : $heads->[$i])."
tags. =cut sub ui_columns_row { return &theme_ui_columns_row(@_) if (defined(&theme_ui_columns_row)); my ($cols, $tdtags) = @_; my $rv; $rv .= "
[$i].">". ($cols->[$i] !~ /\S/ ? "
" : $cols->[$i])."
[$i].">". ($cols->[$i] eq "" ? "
" : $cols->[$i])."
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. =item tags - Extra HTML tags to include in the radio button. =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, $tags) = @_; my $rv; $rv .= "
[0].">". &ui_checkbox($checkname, $checkvalue, undef, $checked, $tags, $disabled). "[$i+1].">"; if ($cols->[$i] !~ /"; } $rv .= ($cols->[$i] !~ /\S/ ? "
" : $cols->[$i]); if ($cols->[$i] !~ / tags. =item checkname - Name for the radio button input. Should be the same for all rows. =item checkvalue - Value for this radio button option. =item checked - Set to 1 if it should be checked by default. =item disabled - Set to 1 if the radio button should be disabled and thus un-clickable. =item tags - Extra HTML tags to include in the radio button. =cut sub ui_radio_columns_row { return &theme_ui_radio_columns_row(@_) if (defined(&theme_ui_radio_columns_row)); my ($cols, $tdtags, $checkname, $checkvalue, $checked, $dis, $tags) = @_; my $rv; $rv .= "
[0].">". &ui_oneradio($checkname, $checkvalue, "", $checked, undef, $dis)."[$i+1].">"; if ($cols->[$i] !~ /"; } $rv .= ($cols->[$i] !~ /\S/ ? "
" : $cols->[$i]); if ($cols->[$i] !~ /\n"; } =head2 ui_columns_table(&headings, width-percent, &data, &types, no-sort, title, empty-msg) Returns HTML for a complete table, typically generated internally by ui_columns_start, ui_columns_row and ui_columns_end. The parameters are : =item headings - An array ref of heading HTML. =item width-percent - Preferred total width =item data - A 2x2 array ref of table contents. Each can either be a simple string, or a hash ref like : { 'type' => 'group', 'desc' => 'Some section title' } { 'type' => 'string', 'value' => 'Foo', 'colums' => 3, 'nowrap' => 1 } { 'type' => 'checkbox', 'name' => 'd', 'value' => 'foo', 'label' => 'Yes', 'checked' => 1, 'disabled' => 1 } { 'type' => 'radio', 'name' => 'd', 'value' => 'foo', ... } =item types - An array ref of data types, such as 'string', 'number', 'bytes' or 'date' =item no-sort - Set to 1 to disable sorting by theme. =item title - Text to appear above the table. =item empty-msg - Message to display if no data. =cut sub ui_columns_table { return &theme_ui_columns_table(@_) if (defined(&theme_ui_columns_table)); my ($heads, $width, $data, $types, $nosort, $title, $emptymsg) = @_; my $rv; # Just show empty message if no data if ($emptymsg && !@$data) { $rv .= &ui_subheading($title) if ($title); $rv .= "$emptymsg

\n"; return $rv; } # Are there any checkboxes in each column? If so, make those columns narrow my @tds = map { "valign=top" } @$heads; my $maxwidth = 0; foreach my $r (@$data) { my $cc = 0; foreach my $c (@$r) { if (ref($c) && ($c->{'type'} eq 'checkbox' || $c->{'type'} eq 'radio')) { $tds[$cc] .= " width=5" if ($tds[$cc] !~ /width=/); } $cc++; } $maxwidth = $cc if ($cc > $maxwidth); } $rv .= &ui_columns_start($heads, $width, 0, \@tds, $title); # Add the data rows foreach my $r (@$data) { my $c0; if (ref($r->[0]) && ($r->[0]->{'type'} eq 'checkbox' || $r->[0]->{'type'} eq 'radio')) { # First column is special $c0 = $r->[0]; $r = [ @$r[1..(@$r-1)] ]; } # Turn data into HTML my @rtds = @tds; my @cols; my $cn = 0; $cn++ if ($c0); foreach my $c (@$r) { if (!ref($c)) { # Plain old string push(@cols, $c); } elsif ($c->{'type'} eq 'checkbox') { # Checkbox in non-first column push(@cols, &ui_checkbox($c->{'name'}, $c->{'value'}, $c->{'label'}, $c->{'checked'}, $c->{'tags'}, $c->{'disabled'})); } elsif ($c->{'type'} eq 'radio') { # Radio button in non-first column push(@cols, &ui_oneradio($c->{'name'}, $c->{'value'}, $c->{'label'}, $c->{'checked'}, $c->{'tags'}, $c->{'disabled'})); } elsif ($c->{'type'} eq 'group') { # Header row that spans whole table $rv .= &ui_columns_header([ $c->{'desc'} ], [ "colspan=$width" ]); next; } elsif ($c->{'type'} eq 'string') { # A string, which might be special push(@cols, $c->{'value'}); if ($c->{'columns'} > 1) { splice(@rtds, $cn, $c->{'columns'}, "colspan=".$c->{'columns'}); } if ($c->{'nowrap'}) { $rtds[$cn] .= " nowrap"; } } $cn++; } # Add the row if (!$c0) { $rv .= &ui_columns_row(\@cols, \@rtds); } elsif ($c0->{'type'} eq 'checkbox') { $rv .= &ui_checked_columns_row(\@cols, \@rtds, $c0->{'name'}, $c0->{'value'}, $c0->{'checked'}, $c0->{'disabled'}, $c0->{'tags'}); } elsif ($c0->{'type'} eq 'radio') { $rv .= &ui_radio_columns_row(\@cols, \@rtds, $c0->{'name'}, $c0->{'value'}, $c0->{'checked'}, $c0->{'disabled'}, $c0->{'tags'}); } } $rv .= &ui_columns_end(); return $rv; } =head2 ui_form_columns_table(cgi, &buttons, select-all, &otherlinks, &hiddens, &headings, width-percent, &data, &types, no-sort, title, empty-msg, form-no) Similar to ui_columns_table, but wrapped in a form. Parameters are : =item cgi - URL to submit the form to. =item buttons - An array ref of buttons at the end of the form, similar to that taken by ui_form_end. =item select-all - If set to 1, include select all / invert links. =item otherslinks - An array ref of other links to put at the top of the table, each of which is a 3-element hash ref of url, text and alignment (left or right). =item hiddens - An array ref of hidden fields, each of which is a 2-element array ref containing the name and value. All other parameters are the same as ui_columns_table. =cut sub ui_form_columns_table { return &theme_ui_form_columns_table(@_) if (defined(&theme_ui_form_columns_table)); my ($cgi, $buttons, $selectall, $others, $hiddens, $heads, $width, $data, $types, $nosort, $title, $emptymsg, $formno) = @_; my $rv; # Build links my @leftlinks = map { "$_->[1]" } grep { $_->[2] ne 'right' } @$others; my @rightlinks = map { "$_->[1]" } grep { $_->[2] eq 'right' } @$others; my $links; # Add select links if (@$data) { if ($selectall) { my $cbname; foreach my $r (@$data) { foreach my $c (@$r) { if (ref($c) && $c->{'type'} eq 'checkbox') { $cbname = $c->{'name'}; last; } } } if ($cbname) { unshift(@leftlinks, &select_all_link($cbname, $formno), &select_invert_link($cbname, $formno)); } } } # Turn to HTML if (@rightlinks) { $links = &ui_grid_table([ &ui_links_row(\@leftlinks), &ui_links_row(\@rightlinks) ], 2, 100, [ undef, "align=right" ]); } elsif (@leftlinks) { $links = &ui_links_row(\@leftlinks); } # Start the form, if we need one if (@$data) { $rv .= &ui_form_start($cgi, "post"); foreach my $h (@$hiddens) { $rv .= &ui_hidden(@$h); } $rv .= $links; } # Add the table $rv .= &ui_columns_table($heads, $width, $data, $types, $nosort, $title, $emptymsg); # Add form end $rv .= $links; if (@$data) { $rv .= &ui_form_end($buttons); } return $rv; } ####################### form generation functions =head2 ui_form_start(script, method, [target], [tags]) Returns HTML for the start of a a form that submits to some script. The parameters are : =item script - CGI script to submit to, like save.cgi. =item method - HTTP method, which must be one of 'get', 'post' or 'form-data'. If form-data is used, the target CGI must call ReadParseMime to parse parameters. =item target - Optional target window or frame for the form. =item tags - Additional HTML attributes for the form tag. =cut sub ui_form_start { $ui_formcount ||= 0; return &theme_ui_form_start(@_) if (defined(&theme_ui_form_start)); my ($script, $method, $target, $tags) = @_; my $rv; $rv .= "

\n"; return $rv; } =head2 ui_form_end([&buttons], [width]) Returns HTML for the end of a form, optionally with a row of submit buttons. These are specified by the buttons parameter, which is an array reference of array refs, with the following elements : =item HTML value for the submit input for the button, or undef for none. =item Text to appear on the button. =item HTML or other inputs to appear after the button. =item Set to 1 if the button should be disabled. =item Additional HTML attributes to appear inside the button's input tag. =cut sub ui_form_end { $ui_formcount++; return &theme_ui_form_end(@_) if (defined(&theme_ui_form_end)); my ($buttons, $width) = @_; my $rv; if ($buttons && @$buttons) { $rv .= "\n"; my $b; foreach $b (@$buttons) { if (ref($b)) { $rv .= "[0] ? " align=left" : $b eq $buttons->[@$buttons-1] ? " align=right" : " align=center").">". &ui_submit($b->[1], $b->[0], $b->[3], $b->[4]). ($b->[2] ? " ".$b->[2] : "")."\n"; } elsif ($b) { $rv .= "\n"; } else { $rv .= "\n"; } } $rv .= "
$b  
\n"; } $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. =item tags - Additional HTML attributes for the tag. =item defaultunits - Units mode selected by default =cut sub ui_bytesbox { my ($name, $bytes, $size, $dis, $tags, $defaultunits) = @_; my $units = 1; if ($bytes eq '' && $defaultunits) { $units = $defaultunits; } elsif ($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, undef, $tags)." ". &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], [tags]) 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, $tags) = @_; $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"; my ($o, %opt, $s); my %sel = ref($value) ? ( map { $_, 1 } @$value ) : ( $value, 1 ); foreach $o (@$opts) { $o = [ $o ] if (!ref($o)); $rv .= "
". ($before ? $before." " : ""). &ui_submit($label).($after ? " ".$after : "")."". $desc."

$title

\n"; $rv .= "
\n"; $rv .= "\n"; return $rv; } =head2 ui_hidden_table_row_end(name) Returns HTML to end a block started by ui_hidden_table_start. =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]) Returns HTML for the start of a form block into which labelled inputs can be placed, which is collapsible by clicking on the header. Basically the same as ui_table_start, and must contain HTML generated by ui_table_row. 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 name - A unique name for this table. =item status - Set to 1 if initially open, 0 if initially closed. =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_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 .= ""; } if (defined($rightheading)) { $rv .= ""; $colspan++; } $rv .= "\n"; } $rv .= "
"; if (defined($heading)) { $rv .= " $heading$rightheading
\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 a form block 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) Returns a row of tabs from which one can be selected, displaying HTML associated with that tab. The parameters are : =item tabs - An array reference of array refs, each of which contains the value and user-visible text for a tab. =item name - Name of the HTML field into which the selected tab will be placed. =item selected - Value for the tab selected by default. =item show-border - Set to 1 if there should be a border around the contents of the tabs. Example code : @tabs = ( [ 'list', 'List services' ], [ 'install', 'Install new service' ] ); print ui_tabs_start(\@tabs, 'mode', 'list'); print ui_tabs_start_tab('mode', 'list'); generate_service_list(); print ui_tabs_end_tab('mode', 'list'); print ui_tabs_start_tab('mode', 'install'); generate_install_form(); print ui_tabs_end_tab('mode', 'install); print ui_tabs_end(); =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 { "\""."e_escape($_->[0])."\"" } @$tabs)."]"; my $tabtitles = "[".join(",", map { "\""."e_escape($_->[1])."\"" } @$tabs)."]"; $rv .= "\n"; # Output the tabs my $imgdir = "$gconfig{'webprefix'}/images"; $rv .= &ui_hidden($name, $sel)."\n"; $rv .= "\n"; $rv .= "\n"; $rv .= "\n"; $rv .= "\n"; foreach my $t (@$tabs) { if ($t ne $$tabs[0]) { # Spacer $rv .= "\n"; } my $tabid = "tab_".$t->[0]; $rv .= "\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 .= "
". ""; $rv .= ""; if ($t->[0] eq $sel) { # Selected tab $rv .= ""; $rv .= ""; $rv .= ""; } else { # Other tab (which has a link) $rv .= ""; $rv .= ""; $rv .= ""; $rv .= "\n"; } $rv .= "
". "\"\"". " $t->[1] ". "\"\"". "\"\"". " ". "$t->[1] ". "
"; $rv .= "
\n"; if ($border) { # All tabs are within a grey box $rv .= "\n"; $rv .= "\n"; $rv .= "\n"; $rv .= "\n"; $rv .= "\n"; $rv .= "\n"; $rv .= "\n"; $rv .= "\n"; $rv .= "
"; } $main::ui_tabs_selected = $sel; return $rv; } =head2 ui_tabs_end(show-border) Returns HTML to end a block started by ui_tabs_start. The show-border parameter must match the parameter with the same name in the start function. =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"; } return $rv; } =head2 ui_tabs_start_tab(name, tab) Must be called before outputting the HTML for the named tab, and returns HTML for the required
block. =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. I recommend against using this where possible, as it is difficult for themes to implement. =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"; } =head2 ui_tabs_end_tab Returns HTML for the end of a block started by ui_tabs_start_tab. =cut sub ui_tabs_end_tab { return &theme_ui_tabs_end_tab(@_) if (defined(&theme_ui_tabs_end_tab)); return "\n"; } =head2 ui_tabs_end_tabletab Returns HTML for the end of a block started by ui_tabs_start_tabletab. =cut 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. For internal use only. =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. The parameters are : =item opts - An array ref to arrays containing [ value, label, html ] =item name - HTML name for the radio buttons =item selected - Value for the initially selected button. =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. Parameters are : =item elements - An array reference of table elements, each of which can be any HTML you like. =item columns - Desired number of columns in the grid. =item width-percent - Optional desired width as a percentage. =item tds - Array ref of HTML attributes for
tags in the tables. =item tabletags - HTML attributes for the tag. =item title - Optional title to add to the top of the grid. =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 == 0); $rv .= "\n"; $rv .= "" if ($i%$cols == $cols-1); } if ($i%$cols) { while($i%$cols) { $rv .= "\n"; $i++; } $rv .= "\n"; } $rv .= "
[$i%$cols]." valign=top class='ui_grid_cell'>". $elements->[$i]."
[$i%$cols]." class='ui_grid_cell'>". "
\n"; if (defined($title)) { $rv = "\n". ($title ? "\n" : ""). "\n". "
$title
$rv
"; } return $rv; } =head2 ui_radio_table(name, selected, &rows, [no-bold]) Returns HTML for a table of radio buttons, each of which has a label and some associated inputs to the right. The parameters are : =item name - Unique name for this table, which is also the radio buttons' name. =item selected - Value for the initially selected radio button. =item rows - Array ref of array refs, one per button. The elements of each are the value for this option, a label, and option additional HTML to appear next to it. =item no-bold - When set to 1, labels in the table will not be bolded =cut sub ui_radio_table { return &theme_ui_radio_table(@_) if (defined(&theme_ui_radio_table)); my ($name, $sel, $rows, $nobold) = @_; return "" if (!@$rows); my $rv = "\n"; foreach my $r (@$rows) { $rv .= "\n"; $rv .= "\n"; if (defined($r->[2])) { $rv .= "\n"; } $rv .= "\n"; } $rv .= "
[2]) ? "" : " colspan=2").">". ($nobold ? "" : ""). &ui_oneradio($name, $r->[0], $r->[1], $r->[0] eq $sel, $r->[3]). ($nobold ? "" : ""). "".$r->[2]."
\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. The parameters are : =item uplink - URL for the up-arrow link. =item downlink - URL for the down-arrow link. =item up-show - Set to 1 if the up-arrow should be shown, 0 if not. =item down-show - Set to 1 if the down-arrow should be shown, 0 if not. =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, typically just an
=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 the 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 "\"$alt\"\n"; } else { return "\"$alt\"\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. The parameters are : =item cgi - Script to which the confirmation form submits, like delete.cgi. =item message - Warning message for the user to see. =item hiddens - Array ref of two-element array refs, containing hidden form field names and values. =item buttons - Array ref of two-element array refs, containing form button names and labels. =item otherinputs - HTML for extra inputs to include in ther form. =item extra-warning - An additional separate warning message to show. =cut sub ui_confirmation_form { my ($cgi, $message, $hiddens, $buttons, $others, $warning) = @_; my $rv; $rv .= "
\n"; $rv .= &ui_form_start($cgi, "post"); foreach my $h (@$hiddens) { $rv .= &ui_hidden(@$h); } $rv .= "$message

\n"; if ($warning) { $rv .= "$warning

\n"; } if ($others) { $rv .= $others."

\n"; } $rv .= &ui_form_end($buttons); $rv .= "

\n"; return $rv; } ####################### javascript functions =head2 js_disable_inputs(&disable-inputs, &enable-inputs, [tag]) Returns Javascript to disable some form elements and enable others. Mainly for internal use. =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 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]) For internal use only. =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(@_); } $window ||= "window"; if ($url =~ /^\//) { # If the URL is like /foo , add webprefix $url = $gconfig{'webprefix'}.$url; } return "\n"; } 1;