More POD-ification
[webmin.git] / ui-lib.pl
1 =head1 ui-lib.pl
2
3 Common functions for generating HTML for Webmin user interface elements.
4 Some example code :
5
6  require '../ui-lib.pl';
7  ui_print_header(undef, 'My Module', '');
8
9  print ui_form_start('save.cgi');
10  print ui_table_start('My form', undef, 2);
11
12  print ui_table_row('Enter your name',
13         ui_textbox('name', undef, 40));
14
15  print ui_table_end();
16  print ui_form_end([ [ undef, 'Save' ] ]);
17
18  ui_print_footer('/', 'Webmin index');
19
20 =cut
21
22 ####################### table generation functions
23
24 =head2 ui_table_start(heading, [tabletags], [cols], [&default-tds], [right-heading])
25
26 Returns HTML for the start of a form block into which labelled inputs can
27 be placed. By default this is implemented as a table with another table inside
28 it, but themes may override this with their own layout.
29
30 The parameters are :
31
32 =item heading - Text to show at the top of the form.
33
34 =item tabletags - HTML attributes to put in the outer <table>, typically something like width=100%.
35
36 =item cols - Desired number of columns for labels and fields. Defaults to 4, but can be 2 for forms with lots of wide inputs.
37
38 =item default-tds - An optional array reference of HTML attributes for the <td> tags in each row of the table.
39
40 =item right-heading - HTML to appear in the heading, aligned to the right.
41
42 =cut
43 sub ui_table_start
44 {
45 return &theme_ui_table_start(@_) if (defined(&theme_ui_table_start));
46 my ($heading, $tabletags, $cols, $tds, $rightheading) = @_;
47 if (defined($main::ui_table_cols)) {
48         # Push on stack, for nested call
49         push(@main::ui_table_cols_stack, $main::ui_table_cols);
50         push(@main::ui_table_pos_stack, $main::ui_table_pos);
51         push(@main::ui_table_default_tds_stack, $main::ui_table_default_tds);
52         }
53 my $colspan = 1;
54 my $rv;
55 $rv .= "<table class='ui_table' border $tabletags>\n";
56 if (defined($heading) || defined($rightheading)) {
57         $rv .= "<tr $tb class='ui_table_head'>";
58         if (defined($heading)) {
59                 $rv .= "<td><b>$heading</b></td>"
60                 }
61         if (defined($rightheading)) {
62                 $rv .= "<td align=right>$rightheading</td>";
63                 $colspan++;
64                 }
65         $rv .= "</tr>\n";
66         }
67 $rv .= "<tr $cb class='ui_table_body'> <td colspan=$colspan>".
68        "<table width=100%>\n";
69 $main::ui_table_cols = $cols || 4;
70 $main::ui_table_pos = 0;
71 $main::ui_table_default_tds = $tds;
72 return $rv;
73 }
74
75 =head2 ui_table_end
76
77 Returns HTML for the end of a block started by ui_table_start.
78
79 =cut
80 sub ui_table_end
81 {
82 return &theme_ui_table_end(@_) if (defined(&theme_ui_table_end));
83 my $rv;
84 if ($main::ui_table_cols == 4 && $main::ui_table_pos) {
85         # Add an empty block to balance the table
86         $rv .= &ui_table_row(" ", " ");
87         }
88 if (@main::ui_table_cols_stack) {
89         $main::ui_table_cols = pop(@main::ui_table_cols_stack);
90         $main::ui_table_pos = pop(@main::ui_table_pos_stack);
91         $main::ui_table_default_tds = pop(@main::ui_table_default_tds_stack);
92         }
93 else {
94         $main::ui_table_cols = undef;
95         $main::ui_table_pos = undef;
96         $main::ui_table_default_tds = undef;
97         }
98 $rv .= "</table></td></tr></table>\n";
99 return $rv;
100 }
101
102 =head2 ui_table_row(label, value, [cols], [&td-tags])
103
104 Returns HTML for a row in a table started by ui_table_start, with a 1-column
105 label and 1+ column value. The parameters are :
106
107 =item label - Label for the input field. If this is undef, no label is displayed.
108
109 =item value - HTML for the input part of the row.
110
111 =item cols - Number of columns the value should take up, defaulting to 1.
112
113 =item td-tags - Array reference of HTML attributes for the <td> tags in this row.
114
115 =cut
116 sub ui_table_row
117 {
118 return &theme_ui_table_row(@_) if (defined(&theme_ui_table_row));
119 my ($label, $value, $cols, $tds) = @_;
120 $cols ||= 1;
121 $tds ||= $main::ui_table_default_tds;
122 my $rv;
123 if ($main::ui_table_pos+$cols+1 > $main::ui_table_cols &&
124     $main::ui_table_pos != 0) {
125         # If the requested number of cols won't fit in the number
126         # remaining, start a new row
127         $rv .= "</tr>\n";
128         $main::ui_table_pos = 0;
129         }
130 $rv .= "<tr class='ui_table_row'>\n"
131         if ($main::ui_table_pos%$main::ui_table_cols == 0);
132 $rv .= "<td valign=top $tds->[0] class='ui_label'><b>$label</b></td>\n"
133         if (defined($label));
134 $rv .= "<td valign=top colspan=$cols $tds->[1] class='ui_value'>$value</td>\n";
135 $main::ui_table_pos += $cols+(defined($label) ? 1 : 0);
136 if ($main::ui_table_pos%$main::ui_table_cols == 0) {
137         $rv .= "</tr>\n";
138         $main::ui_table_pos = 0;
139         }
140 return $rv;
141 }
142
143 =head2 ui_table_hr
144
145 Returns HTML for a row in a block started by ui_table_row, with a horizontal
146 line inside it to separate sections.
147
148 =cut
149 sub ui_table_hr
150 {
151 return &theme_ui_table_hr(@_) if (defined(&theme_ui_table_hr));
152 my $rv;
153 if ($ui_table_pos) {
154         $rv .= "</tr>\n";
155         $ui_table_pos = 0;
156         }
157 $rv .= "<tr class='ui_table_hr'> ".
158        "<td colspan=$main::ui_table_cols><hr></td> </tr>\n";
159 return $rv;
160 }
161
162 =head2 ui_table_span(text)
163
164 Outputs a table row that spans the whole table, and contains the given text.
165
166 =cut
167 sub ui_table_span
168 {
169 my ($text) = @_;
170 return &theme_ui_table_hr(@_) if (defined(&theme_ui_table_hr));
171 my $rv;
172 if ($ui_table_pos) {
173         $rv .= "</tr>\n";
174         $ui_table_pos = 0;
175         }
176 $rv .= "<tr class='ui_table_span'> ".
177        "<td colspan=$main::ui_table_cols>$text</td> </tr>\n";
178 return $rv;
179 }
180
181 =head2 ui_columns_start(&headings, [width-percent], [noborder], [&tdtags], [heading])
182
183 Returns HTML for the start of a multi-column table, with the given headings.
184 The parameters are :
185
186 =item headings - An array reference of headers for the table's columns.
187
188 =item width-percent - Desired width as a percentage, or undef to let the browser decide.
189
190 =item noborder - Set to 1 if the table should not have a border.
191
192 =item tdtags - An optional reference to an array of HTML attributes for the table's <td> tags.
193
194 =item heading - An optional heading to put above the table.
195
196 =cut
197 sub ui_columns_start
198 {
199 return &theme_ui_columns_start(@_) if (defined(&theme_ui_columns_start));
200 my ($heads, $width, $noborder, $tdtags, $title) = @_;
201 my $rv;
202 $rv .= "<table".($noborder ? "" : " border").
203                 (defined($width) ? " width=$width%" : "")." class='ui_columns'>\n";
204 if ($title) {
205         $rv .= "<tr $tb class='ui_columns_heading'>".
206                "<td colspan=".scalar(@$heads)."><b>$title</b></td></tr>\n";
207         }
208 $rv .= "<tr $tb class='ui_columns_heads'>\n";
209 my $i;
210 for($i=0; $i<@$heads; $i++) {
211         $rv .= "<td ".$tdtags->[$i]."><b>".
212                ($heads->[$i] eq "" ? "<br>" : $heads->[$i])."</b></td>\n";
213         }
214 $rv .= "</tr>\n";
215 return $rv;
216 }
217
218 =head2 ui_columns_row(&columns, &tdtags)
219
220 Returns HTML for a row in a multi-column table. The parameters are :
221
222 =item columns - Reference to an array containing the HTML to show in the columns for this row.
223
224 =item tdtags - An optional array reference containing HTML attributes for the row's <td> tags.
225
226 =cut
227 sub ui_columns_row
228 {
229 return &theme_ui_columns_row(@_) if (defined(&theme_ui_columns_row));
230 my ($cols, $tdtags) = @_;
231 my $rv;
232 $rv .= "<tr $cb class='ui_columns_row'>\n";
233 my $i;
234 for($i=0; $i<@$cols; $i++) {
235         $rv .= "<td ".$tdtags->[$i].">".
236                ($cols->[$i] !~ /\S/ ? "<br>" : $cols->[$i])."</td>\n";
237         }
238 $rv .= "</tr>\n";
239 return $rv;
240 }
241
242 =head2 ui_columns_header(&columns, &tdtags)
243
244 Returns HTML for a row in a multi-column table, styled as a header. Parameters
245 are the same as ui_columns_row.
246
247 =cut
248 sub ui_columns_header
249 {
250 return &theme_ui_columns_header(@_) if (defined(&theme_ui_columns_header));
251 my ($cols, $tdtags) = @_;
252 my $rv;
253 $rv .= "<tr $tb class='ui_columns_header'>\n";
254 my $i;
255 for($i=0; $i<@$cols; $i++) {
256         $rv .= "<td ".$tdtags->[$i]."><b>".
257                ($cols->[$i] eq "" ? "<br>" : $cols->[$i])."</b></td>\n";
258         }
259 $rv .= "</tr>\n";
260 return $rv;
261 }
262
263 =head2 ui_checked_columns_row(&columns, &tdtags, checkname, checkvalue, [checked?], [disabled])
264
265 Returns HTML for a row in a multi-column table, in which the first column 
266 contains a checkbox. The parameters are :
267
268 =item columns - Reference to an array containing the HTML to show in the columns for this row.
269
270 =item tdtags - An optional array reference containing HTML attributes for the row's <td> tags.
271
272 =item checkname - Name for the checkbox input. Should be the same for all rows.
273
274 =item checkvalue - Value for this checkbox input.
275
276 =item checked - Set to 1 if it should be checked by default.
277
278 =item disabled - Set to 1 if the checkbox should be disabled and thus un-clickable.
279
280 =cut
281 sub ui_checked_columns_row
282 {
283 return &theme_ui_checked_columns_row(@_) if (defined(&theme_ui_checked_columns_row));
284 my ($cols, $tdtags, $checkname, $checkvalue, $checked, $disabled) = @_;
285 my $rv;
286 $rv .= "<tr $cb class='ui_checked_columns'>\n";
287 $rv .= "<td class='ui_checked_checkbox' ".$tdtags->[0].">".
288        &ui_checkbox($checkname, $checkvalue, undef, $checked, undef, $disabled).
289        "</td>\n";
290 my $i;
291 for($i=0; $i<@$cols; $i++) {
292         $rv .= "<td ".$tdtags->[$i+1].">";
293         if ($cols->[$i] !~ /<a\s+href|<input|<select|<textarea/) {
294                 $rv .= "<label for=\"".
295                         &quote_escape("${checkname}_${checkvalue}")."\">";
296                 }
297         $rv .= ($cols->[$i] !~ /\S/ ? "<br>" : $cols->[$i]);
298         if ($cols->[$i] !~ /<a\s+href|<input|<select|<textarea/) {
299                 $rv .= "</label>";
300                 }
301         $rv .= "</td>\n";
302         }
303 $rv .= "</tr>\n";
304 return $rv;
305 }
306
307 =head2 ui_radio_columns_row(&columns, &tdtags, checkname, checkvalue, [checked], [disabled])
308
309 Returns HTML for a row in a multi-column table, in which the first
310 column is a radio button. The parameters are :
311
312 =item columns - Reference to an array containing the HTML to show in the columns for this row.
313
314 =item tdtags - An optional array reference containing HTML attributes for the row's <td> tags.
315
316 =item checkname - Name for the radio button input. Should be the same for all rows.
317
318 =item checkvalue - Value for this radio button option.
319
320 =item checked - Set to 1 if it should be checked by default.
321
322 =item disabled - Set to 1 if the radio button should be disabled and thus un-clickable.
323
324 =cut
325 sub ui_radio_columns_row
326 {
327 return &theme_ui_radio_columns_row(@_) if (defined(&theme_ui_radio_columns_row));
328 my ($cols, $tdtags, $checkname, $checkvalue, $checked, $dis) = @_;
329 my $rv;
330 $rv .= "<tr $cb class='ui_radio_columns'>\n";
331 $rv .= "<td class='ui_radio_radio' ".$tdtags->[0].">".
332     &ui_oneradio($checkname, $checkvalue, "", $checked, undef, $dis)."</td>\n";
333 my $i;
334 for($i=0; $i<@$cols; $i++) {
335         $rv .= "<td ".$tdtags->[$i+1].">";
336         if ($cols->[$i] !~ /<a\s+href|<input|<select|<textarea/) {
337                 $rv .= "<label for=\"".
338                         &quote_escape("${checkname}_${checkvalue}")."\">";
339                 }
340         $rv .= ($cols->[$i] !~ /\S/ ? "<br>" : $cols->[$i]);
341         if ($cols->[$i] !~ /<a\s+href|<input|<select|<textarea/) {
342                 $rv .= "</label>";
343                 }
344         $rv .= "</td>\n";
345         }
346 $rv .= "</tr>\n";
347 return $rv;
348 }
349
350 =head2 ui_columns_end
351
352 Returns HTML to end a table started by ui_columns_start.
353
354 =cut
355 sub ui_columns_end
356 {
357 return &theme_ui_columns_end(@_) if (defined(&theme_ui_columns_end));
358 return "</table>\n";
359 }
360
361 =head2 ui_columns_table(&headings, width-percent, &data, &types, no-sort, title, empty-msg)
362
363 Returns HTML for a complete table, typically generated internally by
364 ui_columns_start, ui_columns_row and ui_columns_end. The parameters are :
365
366 =item headings - An array ref of heading HTML.
367
368 =item width-percent - Preferred total width
369
370 =item data - A 2x2 array ref of table contents. Each can either be a simple string, or a hash ref like :
371
372   { 'type' => 'group', 'desc' => 'Some section title' }
373   { 'type' => 'string', 'value' => 'Foo', 'colums' => 3,
374     'nowrap' => 1 }
375   { 'type' => 'checkbox', 'name' => 'd', 'value' => 'foo',
376     'label' => 'Yes', 'checked' => 1, 'disabled' => 1 }
377   { 'type' => 'radio', 'name' => 'd', 'value' => 'foo', ... }
378
379 =item types - An array ref of data types, such as 'string', 'number', 'bytes' or 'date'
380
381 =item no-sort - Set to 1 to disable sorting by theme.
382
383 =item title - Text to appear above the table.
384
385 =item empty-msg - Message to display if no data.
386
387 =cut
388 sub ui_columns_table
389 {
390 return &theme_ui_columns_table(@_) if (defined(&theme_ui_columns_table));
391 my ($heads, $width, $data, $types, $nosort, $title, $emptymsg) = @_;
392 my $rv;
393
394 # Just show empty message if no data
395 if ($emptymsg && !@$data) {
396         $rv .= &ui_subheading($title) if ($title);
397         $rv .= "<span class='ui_emptymsg'><b>$emptymsg</b></span><p>\n";
398         return $rv;
399         }
400
401 # Are there any checkboxes in each column? If so, make those columns narrow
402 my @tds = map { "valign=top" } @$heads;
403 my $maxwidth = 0;
404 foreach my $r (@$data) {
405         my $cc = 0;
406         foreach my $c (@$r) {
407                 if (ref($c) &&
408                     ($c->{'type'} eq 'checkbox' || $c->{'type'} eq 'radio')) {
409                         $tds[$cc] .= " width=5" if ($tds[$cc] !~ /width=/);
410                         }
411                 $cc++;
412                 }
413         $maxwidth = $cc if ($cc > $maxwidth);
414         }
415 $rv .= &ui_columns_start($heads, $width, 0, \@tds, $title);
416
417 # Add the data rows
418 foreach my $r (@$data) {
419         my $c0;
420         if (ref($r->[0]) && ($r->[0]->{'type'} eq 'checkbox' ||
421                              $r->[0]->{'type'} eq 'radio')) {
422                 # First column is special
423                 $c0 = $r->[0];
424                 $r = [ @$r[1..(@$r-1)] ];
425                 }
426         # Turn data into HTML
427         my @rtds = @tds;
428         my @cols;
429         my $cn = 0;
430         $cn++ if ($c0);
431         foreach my $c (@$r) {
432                 if (!ref($c)) {
433                         # Plain old string
434                         push(@cols, $c);
435                         }
436                 elsif ($c->{'type'} eq 'checkbox') {
437                         # Checkbox in non-first column
438                         push(@cols, &ui_checkbox($c->{'name'}, $c->{'value'},
439                                                  $c->{'label'}, $c->{'checked'},
440                                                  undef, $c->{'disabled'}));
441                         }
442                 elsif ($c->{'type'} eq 'radio') {
443                         # Radio button in non-first column
444                         push(@cols, &ui_oneradio($c->{'name'}, $c->{'value'},
445                                                  $c->{'label'}, $c->{'checked'},
446                                                  undef, $c->{'disabled'}));
447                         }
448                 elsif ($c->{'type'} eq 'group') {
449                         # Header row that spans whole table
450                         $rv .= &ui_columns_header([ $c->{'desc'} ],
451                                                   [ "colspan=$width" ]);
452                         next;
453                         }
454                 elsif ($c->{'type'} eq 'string') {
455                         # A string, which might be special
456                         push(@cols, $c->{'value'});
457                         if ($c->{'columns'} > 1) {
458                                 splice(@rtds, $cn, $c->{'columns'},
459                                        "colspan=".$c->{'columns'});
460                                 }
461                         if ($c->{'nowrap'}) {
462                                 $rtds[$cn] .= " nowrap";
463                                 }
464                         }
465                 $cn++;
466                 }
467         # Add the row
468         if (!$c0) {
469                 $rv .= &ui_columns_row(\@cols, \@rtds);
470                 }
471         elsif ($c0->{'type'} eq 'checkbox') {
472                 $rv .= &ui_checked_columns_row(\@cols, \@rtds, $c0->{'name'},
473                                                $c0->{'value'}, $c0->{'checked'},
474                                                $c0->{'disabled'});
475                 }
476         elsif ($c0->{'type'} eq 'radio') {
477                 $rv .= &ui_radio_columns_row(\@cols, \@rtds, $c0->{'name'},
478                                              $c0->{'value'}, $c0->{'checked'},
479                                              $c0->{'disabled'});
480                 }
481         }
482
483 $rv .= &ui_columns_end();
484 return $rv;
485 }
486
487 =head2 ui_form_columns_table(cgi, &buttons, select-all, &otherlinks, &hiddens, &headings, width-percent, &data, &types, no-sort, title, empty-msg)
488
489 Similar to ui_columns_table, but wrapped in a form. Parameters are :
490
491 =item cgi - URL to submit the form to.
492
493 =item buttons - An array ref of buttons at the end of the form, similar to that taken by ui_form_end.
494
495 =item select-all - If set to 1, include select all / invert links.
496
497 =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).
498
499 =item hiddens - An array ref of hidden fields, each of which is a 2-element array ref containing the name and value.
500
501 All other parameters are the same as ui_columns_table.
502
503 =cut
504 sub ui_form_columns_table
505 {
506 return &theme_ui_form_columns_table(@_)
507         if (defined(&theme_ui_form_columns_table));
508 my ($cgi, $buttons, $selectall, $others, $hiddens,
509        $heads, $width, $data, $types, $nosort, $title, $emptymsg) = @_;
510 my $rv;
511
512 # Build links
513 my @leftlinks = map { "<a href='$_->[0]'>$_->[1]</a>" }
514                        grep { $_->[2] ne 'right' } @$others;
515 my @rightlinks = map { "<a href='$_->[0]'>$_->[1]</a>" }
516                        grep { $_->[2] eq 'right' } @$others;
517 my $links;
518
519 # Add select links
520 if (@$data) {
521         if ($selectall) {
522                 my $cbname;
523                 foreach my $r (@$data) {
524                         foreach my $c (@$r) {
525                                 if (ref($c) && $c->{'type'} eq 'checkbox') {
526                                         $cbname = $c->{'name'};
527                                         last;
528                                         }
529                                 }
530                         }
531                 if ($cbname) {
532                         unshift(@leftlinks, &select_all_link($cbname),
533                                             &select_invert_link($cbname));
534                         }
535                 }
536         }
537
538 # Turn to HTML
539 if (@rightlinks) {
540         $links = &ui_grid_table([ &ui_links_row(\@leftlinks),
541                                   &ui_links_row(\@rightlinks) ], 2, 100,
542                                 [ undef, "align=right" ]);
543         }
544 elsif (@leftlinks) {
545         $links = &ui_links_row(\@leftlinks);
546         }
547
548 # Start the form, if we need one
549 if (@$data) {
550         $rv .= &ui_form_start($cgi, "post");
551         foreach my $h (@$hiddens) {
552                 $rv .= &ui_hidden(@$h);
553                 }
554         $rv .= $links;
555         }
556
557 # Add the table
558 $rv .= &ui_columns_table($heads, $width, $data, $types, $nosort, $title,
559                          $emptymsg);
560
561 # Add form end
562 $rv .= $links;
563 if (@$data) {
564         $rv .= &ui_form_end($buttons);
565         }
566
567 return $rv;
568 }
569
570 ####################### form generation functions
571
572 =head2 ui_form_start(script, method, [target], [tags])
573
574 Returns HTML for the start of a a form that submits to some script. The
575 parameters are :
576
577 =item script - CGI script to submit to, like save.cgi.
578
579 =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.
580
581 =item target - Optional target window or frame for the form.
582
583 =item tags - Additional HTML attributes for the form tag.
584
585 =cut
586 sub ui_form_start
587 {
588 return &theme_ui_form_start(@_) if (defined(&theme_ui_form_start));
589 my ($script, $method, $target, $tags) = @_;
590 my $rv;
591 $rv .= "<form class='ui_form' action='".&html_escape($script)."' ".
592         ($method eq "post" ? "method=post" :
593          $method eq "form-data" ?
594                 "method=post enctype=multipart/form-data" :
595                 "method=get").
596         ($target ? " target=$target" : "").
597         " ".$tags.
598        ">\n";
599 return $rv;
600 }
601
602 =head2 ui_form_end([&buttons], [width])
603
604 Returns HTML for the end of a form, optionally with a row of submit buttons.
605 These are specified by the buttons parameter, which is an array reference
606 of array refs, with the following elements :
607
608 =item HTML value for the submit input for the button, or undef for none.
609
610 =item Text to appear on the button.
611
612 =item HTML or other inputs to appear after the button.
613
614 =item Set to 1 if the button should be disabled.
615
616 =item Additional HTML attributes to appear inside the button's input tag.
617
618 =cut
619 sub ui_form_end
620 {
621 return &theme_ui_form_end(@_) if (defined(&theme_ui_form_end));
622 my ($buttons, $width) = @_;
623 my $rv;
624 if ($buttons && @$buttons) {
625         $rv .= "<table class='ui_form_end_buttons' ".($width ? " width=$width" : "")."><tr>\n";
626         my $b;
627         foreach $b (@$buttons) {
628                 if (ref($b)) {
629                         $rv .= "<td".(!$width ? "" :
630                                       $b eq $buttons->[0] ? " align=left" :
631                                       $b eq $buttons->[@$buttons-1] ?
632                                         " align=right" : " align=center").">".
633                                &ui_submit($b->[1], $b->[0], $b->[3], $b->[4]).
634                                ($b->[2] ? " ".$b->[2] : "")."</td>\n";
635                         }
636                 elsif ($b) {
637                         $rv .= "<td>$b</td>\n";
638                         }
639                 else {
640                         $rv .= "<td>&nbsp;&nbsp;</td>\n";
641                         }
642                 }
643         $rv .= "</tr></table>\n";
644         }
645 $rv .= "</form>\n";
646 return $rv;
647 }
648
649 =head2 ui_textbox(name, value, size, [disabled?], [maxlength], [tags])
650
651 Returns HTML for a text input box. The parameters are :
652
653 =item name - Name for this input.
654
655 =item value - Initial contents for the text box.
656
657 =item size - Desired width in characters.
658
659 =item disabled - Set to 1 if this text box should be disabled by default.
660
661 =item maxlength - Maximum length of the string the user is allowed to input.
662
663 =item tags - Additional HTML attributes for the <input> tag.
664
665 =cut
666 sub ui_textbox
667 {
668 return &theme_ui_textbox(@_) if (defined(&theme_ui_textbox));
669 my ($name, $value, $size, $dis, $max, $tags) = @_;
670 $size = &ui_max_text_width($size);
671 return "<input class='ui_textbox' name=\"".&quote_escape($name)."\" ".
672        "value=\"".&quote_escape($value)."\" ".
673        "size=$size ".($dis ? "disabled=true" : "").
674        ($max ? " maxlength=$max" : "").
675        " ".$tags.
676        ">";
677 }
678
679 =head2 ui_filebox(name, value, size, [disabled?], [maxlength], [tags], [dir-only])
680
681 Returns HTML for a text box for choosing a file. Parameters are the same
682 as ui_textbox, except for the extra dir-only option which limits the chooser
683 to directories.
684
685 =cut
686 sub ui_filebox
687 {
688 return &theme_ui_filebox(@_) if (defined(&theme_ui_filebox));
689 my ($name, $value, $size, $dis, $max, $tags, $dironly) = @_;
690 return &ui_textbox($name, $value, $size, $dis, $max, $tags)."&nbsp;".
691        &file_chooser_button($name, $dironly);
692 }
693
694 =head2 ui_bytesbox(name, bytes, [size], [disabled?])
695
696 Returns HTML for entering a number of bytes, but with friendly kB/MB/GB
697 options. May truncate values to 2 decimal points! The parameters are :
698
699 =item name - Name for this input.
700
701 =item bytes - Initial number of bytes to show.
702
703 =item size - Desired width of the text box part.
704
705 =item disabled - Set to 1 if this text box should be disabled by default.
706
707 =cut
708 sub ui_bytesbox
709 {
710 my ($name, $bytes, $size, $dis) = @_;
711 my $units = 1;
712 if ($bytes >= 10*1024*1024*1024*1024) {
713         $units = 1024*1024*1024*1024;
714         }
715 elsif ($bytes >= 10*1024*1024*1024) {
716         $units = 1024*1024*1024;
717         }
718 elsif ($bytes >= 10*1024*1024) {
719         $units = 1024*1024;
720         }
721 elsif ($bytes >= 10*1024) {
722         $units = 1024;
723         }
724 else {
725         $units = 1;
726         }
727 if ($bytes ne "") {
728         $bytes = sprintf("%.2f", ($bytes*1.0)/$units);
729         $bytes =~ s/\.00$//;
730         }
731 $size = &ui_max_text_width($size || 8);
732 return &ui_textbox($name, $bytes, $size, $dis)." ".
733        &ui_select($name."_units", $units,
734                  [ [ 1, "bytes" ],
735                    [ 1024, "kB" ],
736                    [ 1024*1024, "MB" ],
737                    [ 1024*1024*1024, "GB" ],
738                    [ 1024*1024*1024*1024, "TB" ] ], undef, undef, undef, $dis);
739 }
740
741 =head2 ui_upload(name, size, [disabled?], [tags])
742
743 Returns HTML for a file upload input, for use in a form with the form-data
744 method. The parameters are :
745
746 =item name - Name for this input.
747
748 =item size - Desired width in characters.
749
750 =item disabled - Set to 1 if this text box should be disabled by default.
751
752 =item tags - Additional HTML attributes for the <input> tag.
753
754 =cut
755 sub ui_upload
756 {
757 return &theme_ui_upload(@_) if (defined(&theme_ui_upload));
758 my ($name, $size, $dis, $tags) = @_;
759 $size = &ui_max_text_width($size);
760 return "<input class='ui_upload' type=file name=\"".&quote_escape($name)."\" ".
761        "size=$size ".
762        ($dis ? "disabled=true" : "").
763        ($tags ? " ".$tags : "").">";
764 }
765
766 =head2 ui_password(name, value, size, [disabled?], [maxlength])
767
768 Returns HTML for a password text input. Parameters are the same as ui_textbox,
769 and behaviour is identical except that the user's input is not visible.
770
771 =cut
772 sub ui_password
773 {
774 return &theme_ui_password(@_) if (defined(&theme_ui_password));
775 my ($name, $value, $size, $dis, $max) = @_;
776 $size = &ui_max_text_width($size);
777 return "<input class='ui_password' ".
778        "type=password name=\"".&quote_escape($name)."\" ".
779        "value=\"".&quote_escape($value)."\" ".
780        "size=$size ".($dis ? "disabled=true" : "").
781        ($max ? " maxlength=$max" : "").
782        ">";
783 }
784
785 =head2 ui_hidden(name, value)
786
787 Returns HTML for a hidden field with the given name and value.
788
789 =cut
790 sub ui_hidden
791 {
792 return &theme_ui_hidden(@_) if (defined(&theme_ui_hidden));
793 my ($name, $value) = @_;
794 return "<input class='ui_hidden' type=hidden ".
795        "name=\"".&quote_escape($name)."\" ".
796        "value=\"".&quote_escape($value)."\">\n";
797 }
798
799 =head2 ui_select(name, value|&values, &options, [size], [multiple], [add-if-missing], [disabled?], [javascript])
800
801 Returns HTML for a drop-down menu or multiple selection list. The parameters
802 are :
803
804 =item name - Name for this input.
805
806 =item value - Either a single initial value, or an array reference of values if this is a multi-select list.
807
808 =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.
809
810 =item size - Desired vertical size in rows, which defaults to 1. For multi-select lists, this must be set to something larger.
811
812 =item multiple - Set to 1 for a multi-select list, 0 for single.
813
814 =item add-if-missing - If set to 1, any value that is not in the list of options will be automatically added (and selected).
815
816 =item disabled - Set to 1 to disable this input.
817
818 =item javascript - Additional HTML attributes for the <select> input.
819
820 =cut
821 sub ui_select
822 {
823 return &theme_ui_select(@_) if (defined(&theme_ui_select));
824 my ($name, $value, $opts, $size, $multiple, $missing, $dis, $js) = @_;
825 my $rv;
826 $rv .= "<select class='ui_select' name=\"".&quote_escape($name)."\"".
827        ($size ? " size=$size" : "").
828        ($multiple ? " multiple" : "").
829        ($dis ? " disabled=true" : "")." ".$js.">\n";
830 my ($o, %opt, $s);
831 my %sel = ref($value) ? ( map { $_, 1 } @$value ) : ( $value, 1 );
832 foreach $o (@$opts) {
833         $o = [ $o ] if (!ref($o));
834         $rv .= "<option value=\"".&quote_escape($o->[0])."\"".
835                ($sel{$o->[0]} ? " selected" : "")." ".$o->[2].">".
836                ($o->[1] || $o->[0])."\n";
837         $opt{$o->[0]}++;
838         }
839 foreach $s (keys %sel) {
840         if (!$opt{$s} && $missing) {
841                 $rv .= "<option value=\"".&quote_escape($s)."\"".
842                        "selected>".($s eq "" ? "&nbsp;" : $s)."\n";
843                 }
844         }
845 $rv .= "</select>\n";
846 return $rv;
847 }
848
849 =head2 ui_multi_select(name, &values, &options, size, [add-if-missing], [disabled?], [options-title, values-title], [width])
850
851 Returns HTML for selecting many of many from a list. By default, this is
852 implemented using two <select> lists and Javascript buttons to move elements
853 between them. The resulting input value is \n separated.
854
855 Parameters are :
856
857 =item name - HTML name for this input.
858
859 =item values - An array reference of two-element array refs, containing the submitted values and descriptions of items that are selected by default.
860
861 =item options - An array reference of two-element array refs, containing the submitted values and descriptions of items that the user can select from.
862
863 =item size - Vertical size in rows.
864
865 =item add-if-missing - If set to 1, any entries that are in values but not in options will be added automatically.
866
867 =item disabled - Set to 1 to disable this input by default.
868
869 =item options-title - Optional text to appear above the list of options.
870
871 =item values-title - Optional text to appear above the list of selected values.
872
873 =item width - Optional width of the two lists in pixels.
874
875 =cut
876 sub ui_multi_select
877 {
878 return &theme_ui_multi_select(@_) if (defined(&theme_ui_multi_select));
879 my ($name, $values, $opts, $size, $missing, $dis,
880        $opts_title, $vals_title, $width) = @_;
881 my $rv;
882 my %already = map { $_->[0], $_ } @$values;
883 my $leftover = [ grep { !$already{$_->[0]} } @$opts ];
884 if ($missing) {
885         my %optsalready = map { $_->[0], $_ } @$opts;
886         push(@$opts, grep { !$optsalready{$_->[0]} } @$values);
887         }
888 if (!defined($width)) {
889         $width = "200";
890         }
891 my $wstyle = $width ? "style='width:$width'" : "";
892
893 if (!$main::ui_multi_select_donejs++) {
894         $rv .= &ui_multi_select_javascript();
895         }
896 $rv .= "<table cellpadding=0 cellspacing=0 class='ui_multi_select'>";
897 if (defined($opts_title)) {
898         $rv .= "<tr class='ui_multi_select_heads'> ".
899                "<td><b>$opts_title</b></td> ".
900                "<td></td> <td><b>$vals_title</b></td> </tr>";
901         }
902 $rv .= "<tr class='ui_multi_select_row'>";
903 $rv .= "<td>".&ui_select($name."_opts", [ ], $leftover,
904                          $size, 0, 0, $dis, $wstyle)."</td>\n";
905 $rv .= "<td>".&ui_button("->", undef, $dis,
906                  "onClick='multi_select_move(\"$name\", form, 1)'")."<br>".
907               &ui_button("<-", undef, $dis,
908                  "onClick='multi_select_move(\"$name\", form, 0)'")."</td>\n";
909 $rv .= "<td>".&ui_select($name."_vals", [ ], $values,
910                          $size, 0, 0, $dis, $wstyle)."</td>\n";
911 $rv .= "</tr></table>\n";
912 $rv .= &ui_hidden($name, join("\n", map { $_->[0] } @$values));
913 return $rv;
914 }
915
916 =head2 ui_multi_select_javascript
917
918 Returns <script> section for left/right select boxes. For internal use only.
919
920 =cut
921 sub ui_multi_select_javascript
922 {
923 return &theme_ui_multiselect_javascript()
924         if (defined(&theme_ui_multiselect_javascript));
925 return <<EOF;
926 <script>
927 // Move an element from the options list to the values list, or vice-versa
928 function multi_select_move(name, f, dir)
929 {
930 var opts = f.elements[name+"_opts"];
931 var vals = f.elements[name+"_vals"];
932 var opts_idx = opts.selectedIndex;
933 var vals_idx = vals.selectedIndex;
934 if (dir == 1 && opts_idx >= 0) {
935         // Moving from options to selected list
936         var o = opts.options[opts_idx];
937         vals.options[vals.options.length] = new Option(o.text, o.value);
938         opts.remove(opts_idx);
939         }
940 else if (dir == 0 && vals_idx >= 0) {
941         // Moving the other way
942         var o = vals.options[vals_idx];
943         opts.options[opts.options.length] = new Option(o.text, o.value);
944         vals.remove(vals_idx);
945         }
946 // Fill in hidden field
947 var hid = f.elements[name];
948 if (hid) {
949         var hv = new Array();
950         for(var i=0; i<vals.options.length; i++) {
951                 hv.push(vals.options[i].value);
952                 }
953         hid.value = hv.join("\\n");
954         }
955 }
956 </script>
957 EOF
958 }
959
960 =head2 ui_radio(name, value, &options, [disabled?])
961
962 Returns HTML for a series of radio buttons, of which one can be selected. The
963 parameters are :
964
965 =item name - HTML name for the radio buttons.
966
967 =item value - Value of the button that is selected by default.
968
969 =item options - Array ref of radio button options, each of which is an array ref containing the submitted value and description for each button.
970
971 =item disabled - Set to 1 to disable all radio buttons by default.
972
973 =cut
974 sub ui_radio
975 {
976 return &theme_ui_radio(@_) if (defined(&theme_ui_radio));
977 my ($name, $value, $opts, $dis) = @_;
978 my $rv;
979 my $o;
980 foreach $o (@$opts) {
981         my $id = &quote_escape($name."_".$o->[0]);
982         my $label = $o->[1] || $o->[0];
983         my $after;
984         if ($label =~ /^(.*?)((<a\s+href|<input|<select|<textarea)[\000-\377]*)$/i) {
985                 $label = $1;
986                 $after = $2;
987                 }
988         $rv .= "<input class='ui_radio' type=radio ".
989                "name=\"".&quote_escape($name)."\" ".
990                "value=\"".&quote_escape($o->[0])."\"".
991                ($o->[0] eq $value ? " checked" : "").
992                ($dis ? " disabled=true" : "").
993                " id=\"$id\"".
994                " $o->[2]> <label for=\"$id\">".
995                $label."</label>".$after."\n";
996         }
997 return $rv;
998 }
999
1000 =head2 ui_yesno_radio(name, value, [yes], [no], [disabled?])
1001
1002 Like ui_radio, but always displays just two inputs (yes and no). The parameters
1003 are :
1004
1005 =item name - HTML name of the inputs.
1006
1007 =item value - Option selected by default, typically 1 or 0.
1008
1009 =item yes - The value for the yes option, defaulting to 1.
1010
1011 =item no - The value for the no option, defaulting to 0.
1012
1013 =item disabled - Set to 1 to disable all radio buttons by default.
1014
1015 =cut
1016 sub ui_yesno_radio
1017 {
1018 my ($name, $value, $yes, $no, $dis) = @_;
1019 return &theme_ui_yesno_radio(@_) if (defined(&theme_ui_yesno_radio));
1020 $yes = 1 if (!defined($yes));
1021 $no = 0 if (!defined($no));
1022 $value = int($value);
1023 return &ui_radio($name, $value, [ [ $yes, $text{'yes'} ],
1024                                   [ $no, $text{'no'} ] ], $dis);
1025 }
1026
1027 =head2 ui_checkbox(name, value, label, selected?, [tags], [disabled?])
1028
1029 Returns HTML for a single checkbox. Parameters are :
1030
1031 =item name - HTML name of the checkbox.
1032
1033 =item value - Value that will be submitted if it is checked.
1034
1035 =item label - Text to appear next to the checkbox.
1036
1037 =item selected - Set to 1 for it to be checked by default.
1038
1039 =item tags - Additional HTML attributes for the <input> tag.
1040
1041 =item disabled - Set to 1 to disable the checkbox by default.
1042
1043 =cut
1044 sub ui_checkbox
1045 {
1046 return &theme_ui_checkbox(@_) if (defined(&theme_ui_checkbox));
1047 my ($name, $value, $label, $sel, $tags, $dis) = @_;
1048 my $after;
1049 if ($label =~ /^([^<]*)(<[\000-\377]*)$/) {
1050         $label = $1;
1051         $after = $2;
1052         }
1053 return "<input class='ui_checkbox' type=checkbox ".
1054        "name=\"".&quote_escape($name)."\" ".
1055        "value=\"".&quote_escape($value)."\" ".
1056        ($sel ? " checked" : "").($dis ? " disabled=true" : "").
1057        " id=\"".&quote_escape("${name}_${value}")."\"".
1058        " $tags> ".
1059        ($label eq "" ? $after :
1060          "<label for=\"".&quote_escape("${name}_${value}").
1061          "\">$label</label>$after")."\n";
1062 }
1063
1064 =head2 ui_oneradio(name, value, label, selected?, [tags], [disabled?])
1065
1066 Returns HTML for a single radio button. The parameters are :
1067
1068 =item name - HTML name of the radio button.
1069
1070 =item value - Value that will be submitted if it is selected.
1071
1072 =item label - Text to appear next to the button.
1073
1074 =item selected - Set to 1 for it to be selected by default.
1075
1076 =item tags - Additional HTML attributes for the <input> tag.
1077
1078 =item disabled - Set to 1 to disable the radio button by default.
1079
1080 =cut
1081 sub ui_oneradio
1082 {
1083 return &theme_ui_oneradio(@_) if (defined(&theme_ui_oneradio));
1084 my ($name, $value, $label, $sel, $tags, $dis) = @_;
1085 my $id = &quote_escape("${name}_${value}");
1086 my $after;
1087 if ($label =~ /^([^<]*)(<[\000-\377]*)$/) {
1088         $label = $1;
1089         $after = $2;
1090         }
1091 return "<input class='ui_radio' type=radio name=\"".&quote_escape($name)."\" ".
1092        "value=\"".&quote_escape($value)."\" ".
1093        ($sel ? " checked" : "").($dis ? " disabled=true" : "").
1094        " id=\"$id\"".
1095        " $tags> <label for=\"$id\">$label</label>$after\n";
1096 }
1097
1098 =head2 ui_textarea(name, value, rows, cols, [wrap], [disabled?], [tags])
1099
1100 Returns HTML for a multi-line text input. The function parameters are :
1101
1102 =item name - Name for this HTML <textarea>.
1103
1104 =item value - Default value. Multiple lines must be separated by \n.
1105
1106 =item rows - Number of rows, in lines.
1107
1108 =item cols - Number of columns, in characters.
1109
1110 =item wrap - Wrapping mode. Can be one of soft, hard or off.
1111
1112 =item disabled - Set to 1 to disable this text area by default.
1113
1114 =item tags - Additional HTML attributes for the <textarea> tag.
1115
1116 =cut
1117 sub ui_textarea
1118 {
1119 return &theme_ui_textarea(@_) if (defined(&theme_ui_textarea));
1120 my ($name, $value, $rows, $cols, $wrap, $dis, $tags) = @_;
1121 $cols = &ui_max_text_width($cols, 1);
1122 return "<textarea class='ui_textarea' name=\"".&quote_escape($name)."\" ".
1123        "rows=$rows cols=$cols".($wrap ? " wrap=$wrap" : "").
1124        ($dis ? " disabled=true" : "").
1125        ($tags ? " $tags" : "").">".
1126        &html_escape($value).
1127        "</textarea>";
1128 }
1129
1130 =head2 ui_user_textbox(name, value, [form], [disabled?], [tags])
1131
1132 Returns HTML for an input for selecting a Unix user. Parameters are the
1133 same as ui_textbox.
1134
1135 =cut
1136 sub ui_user_textbox
1137 {
1138 return &theme_ui_user_textbox(@_) if (defined(&theme_ui_user_textbox));
1139 return &ui_textbox($_[0], $_[1], 13, $_[3], undef, $_[4])." ".
1140        &user_chooser_button($_[0], 0, $_[2]);
1141 }
1142
1143 =head2 ui_group_textbox(name, value, [form], [disabled?], [tags])
1144
1145 Returns HTML for an input for selecting a Unix group. Parameters are the
1146 same as ui_textbox.
1147
1148 =cut
1149 sub ui_group_textbox
1150 {
1151 return &theme_ui_group_textbox(@_) if (defined(&theme_ui_group_textbox));
1152 return &ui_textbox($_[0], $_[1], 13, $_[3], undef, $_[4])." ".
1153        &group_chooser_button($_[0], 0, $_[2]);
1154 }
1155
1156 =head2 ui_opt_textbox(name, value, size, option1, [option2], [disabled?], [&extra-fields], [max])
1157
1158 Returns HTML for a text field that is optional, implemented by default as
1159 a field with radio buttons next to it. The parameters are :
1160
1161 =item name - HTML name for the text box. The radio buttons will have the same name, but with _def appended.
1162
1163 =item value - Initial value, or undef if you want the default radio button selected initially.
1164
1165 =item size - Width of the text box in characters.
1166
1167 =item option1 - Text for the radio button for selecting that no input is being given, such as 'Default'.
1168
1169 =item option2 - Text for the radio button for selecting that you will provide input.
1170
1171 =item disabled - Set to 1 to disable this input by default.
1172
1173 =item extra-fields - An optional array ref of field names that should be disabled by Javascript when this field is disabled.
1174
1175 =item max - Optional maximum allowed input length, in characters.
1176
1177 =cut
1178 sub ui_opt_textbox
1179 {
1180 return &theme_ui_opt_textbox(@_) if (defined(&theme_ui_opt_textbox));
1181 my ($name, $value, $size, $opt1, $opt2, $dis, $extra, $max) = @_;
1182 my $dis1 = &js_disable_inputs([ $name, @$extra ], [ ]);
1183 my $dis2 = &js_disable_inputs([ ], [ $name, @$extra ]);
1184 my $rv;
1185 $size = &ui_max_text_width($size);
1186 $rv .= &ui_radio($name."_def", $value eq '' ? 1 : 0,
1187                  [ [ 1, $opt1, "onClick='$dis1'" ],
1188                    [ 0, $opt2 || " ", "onClick='$dis2'" ] ], $dis)."\n";
1189 $rv .= "<input class='ui_opt_textbox' name=\"".&quote_escape($name)."\" ".
1190        "size=$size value=\"".&quote_escape($value)."\" ".
1191        ($value eq "" || $dis ? "disabled=true" : "").
1192        ($max ? " maxlength=$max" : "").">\n";
1193 return $rv;
1194 }
1195
1196 =head2 ui_submit(label, [name], [disabled?], [tags])
1197
1198 Returns HTML for a form submit button. Parameters are :
1199
1200 =item label - Text to appear on the button.
1201
1202 =item name - Optional HTML name for the button. Useful if the CGI it submits to needs to know which of several buttons was clicked.
1203
1204 =item disabled - Set to 1 if this button should be disabled by default.
1205
1206 =item tags - Additional HTML attributes for the <input> tag.
1207
1208 =cut
1209 sub ui_submit
1210 {
1211 return &theme_ui_submit(@_) if (defined(&theme_ui_submit));
1212 my ($label, $name, $dis, $tags) = @_;
1213 return "<input class='ui_submit' type=submit".
1214        ($name ne '' ? " name=\"".&quote_escape($name)."\"" : "").
1215        " value=\"".&quote_escape($label)."\"".
1216        ($dis ? " disabled=true" : "").
1217        ($tags ? " ".$tags : "").">\n";
1218                         
1219 }
1220
1221 =head2 ui_reset(label, [disabled?])
1222
1223 Returns HTML for a form reset button, which clears all fields when clicked.
1224 Parameters are :
1225
1226 =item label - Text to appear on the button.
1227
1228 =item disabled - Set to 1 if this button should be disabled by default.
1229
1230 =cut
1231 sub ui_reset
1232 {
1233 return &theme_ui_reset(@_) if (defined(&theme_ui_reset));
1234 my ($label, $dis) = @_;
1235 return "<input type=reset value=\"".&quote_escape($label)."\"".
1236        ($dis ? " disabled=true" : "").">\n";
1237                         
1238 }
1239
1240 =head2 ui_button(label, [name], [disabled?], [tags])
1241
1242 Returns HTML for a form button, which doesn't do anything when clicked unless
1243 you add some Javascript to it. The parameters are :
1244
1245 =item label - Text to appear on the button.
1246
1247 =item name - HTML name for this input.
1248
1249 =item disabled - Set to 1 if this button should be disabled by default.
1250
1251 =item tags - Additional HTML attributes for the <input> tag, typically Javascript inside an onClick attribute.
1252
1253 =cut
1254 sub ui_button
1255 {
1256 return &theme_ui_button(@_) if (defined(&theme_ui_button));
1257 my ($label, $name, $dis, $tags) = @_;
1258 return "<input type=button".
1259        ($name ne '' ? " name=\"".&quote_escape($name)."\"" : "").
1260        " value=\"".&quote_escape($label)."\"".
1261        ($dis ? " disabled=true" : "").
1262        ($tags ? " ".$tags : "").">\n";
1263 }
1264
1265 =head2 ui_date_input(day, month, year, day-name, month-name, year-name, [disabled?])
1266
1267 Returns HTML for a date-selection field, with day, month and year inputs.
1268 XXX
1269
1270 =cut
1271 sub ui_date_input
1272 {
1273 my ($day, $month, $year, $dayname, $monthname, $yearname, $dis) = @_;
1274 my $rv;
1275 $rv .= "<span class='ui_data'>";
1276 $rv .= &ui_textbox($dayname, $day, 3, $dis);
1277 $rv .= "/";
1278 $rv .= &ui_select($monthname, $month,
1279                   [ map { [ $_, $text{"smonth_$_"} ] } (1 .. 12) ],
1280                   1, 0, 0, $dis);
1281 $rv .= "/";
1282 $rv .= &ui_textbox($yearname, $year, 5, $dis);
1283 $rv .= "</span>";
1284 return $rv;
1285 }
1286
1287 sub ui_buttons_start
1288 {
1289 return &theme_ui_buttons_start(@_) if (defined(&theme_ui_buttons_start));
1290 return "<table width=100% class='ui_buttons_table'>\n";
1291 }
1292
1293 sub ui_buttons_end
1294 {
1295 return &theme_ui_buttons_end(@_) if (defined(&theme_ui_buttons_end));
1296 return "</table>\n";
1297 }
1298
1299 =head2 ui_buttons_row(script, button-label, description, [hiddens], [after-submit], [before-submit]) 
1300
1301 MISSING DOCUMENTATION
1302
1303 =cut
1304 sub ui_buttons_row
1305 {
1306 return &theme_ui_buttons_row(@_) if (defined(&theme_ui_buttons_row));
1307 my ($script, $label, $desc, $hiddens, $after, $before) = @_;
1308 return "<form action=$script class='ui_buttons_form'>\n".
1309        $hiddens.
1310        "<tr class='ui_buttons_row'> ".
1311        "<td nowrap width=20% valign=top class=ui_buttons_label>".
1312        ($before ? $before." " : "").
1313        &ui_submit($label).($after ? " ".$after : "")."</td>\n".
1314        "<td valign=top width=80% valign=top class=ui_buttons_value>".
1315        $desc."</td> </tr>\n".
1316        "</form>\n";
1317 }
1318
1319 =head2 ui_buttons_hr([title])
1320
1321 MISSING DOCUMENTATION
1322
1323 =cut
1324 sub ui_buttons_hr
1325 {
1326 my ($title) = @_;
1327 return &theme_ui_buttons_hr(@_) if (defined(&theme_ui_buttons_hr));
1328 if ($title) {
1329         return "<tr class='ui_buttons_hr'> <td colspan=2><table cellpadding=0 cellspacing=0 width=100%><tr> <td width=50%><hr></td> <td nowrap>$title</td> <td width=50%><hr></td> </tr></table></td> </tr>\n";
1330         }
1331 else {
1332         return "<tr class='ui_buttons_hr'> <td colspan=2><hr></td> </tr>\n";
1333         }
1334 }
1335
1336 ####################### header and footer functions
1337
1338 =head2 ui_post_header([subtext])
1339
1340 Returns HTML to appear directly after a standard header() call
1341
1342 =cut
1343 sub ui_post_header
1344 {
1345 return &theme_ui_post_header(@_) if (defined(&theme_ui_post_header));
1346 my ($text) = @_;
1347 my $rv;
1348 $rv .= "<center class='ui_post_header'><font size=+1>$text</font></center>\n" if (defined($text));
1349 if (!$tconfig{'nohr'} && !$tconfig{'notophr'}) {
1350         $rv .= "<hr id='post_header_hr'>\n";
1351         }
1352 return $rv;
1353 }
1354
1355 =head2 ui_pre_footer
1356
1357 Returns HTML to appear directly before a standard footer() call
1358
1359 =cut
1360 sub ui_pre_footer
1361 {
1362 return &theme_ui_pre_footer(@_) if (defined(&theme_ui_pre_footer));
1363 my $rv;
1364 if (!$tconfig{'nohr'} && !$tconfig{'nobottomhr'}) {
1365         $rv .= "<hr id='pre_footer_hr'>\n";
1366         }
1367 return $rv;
1368 }
1369
1370 =head2 ui_print_header(subtext, args...)
1371
1372 Print HTML for a header with the post-header line. The args are the same
1373 as those passed to header()
1374
1375 =cut
1376 sub ui_print_header
1377 {
1378 &load_theme_library();
1379 return &theme_ui_print_header(@_) if (defined(&theme_ui_print_header));
1380 my ($text, @args) = @_;
1381 &header(@args);
1382 print &ui_post_header($text);
1383 }
1384
1385 =head2 ui_print_unbuffered_header(subtext, args...)
1386
1387 Like ui_print_header, but ensures that output for this page is not buffered
1388 or contained in a table.
1389
1390 =cut
1391 sub ui_print_unbuffered_header
1392 {
1393 &load_theme_library();
1394 return &theme_ui_print_unbuffered_header(@_) if (defined(&theme_ui_print_unbuffered_header));
1395 $| = 1;
1396 $theme_no_table = 1;
1397 &ui_print_header(@_);
1398 }
1399
1400 =head2 ui_print_footer(args...)
1401
1402 Print HTML for a footer with the pre-footer line. Args are the same as those
1403 passed to footer()
1404
1405 =cut
1406 sub ui_print_footer
1407 {
1408 return &theme_ui_print_footer(@_) if (defined(&theme_ui_print_footer));
1409 my @args = @_;
1410 print &ui_pre_footer();
1411 &footer(@args);
1412 }
1413
1414 =head2 ui_config_link(text, &subs)
1415
1416 Returns HTML for a module config link. The first non-null sub will be
1417 replaced with the appropriate URL.
1418
1419 =cut
1420 sub ui_config_link
1421 {
1422 return &theme_ui_config_link(@_) if (defined(&theme_ui_config_link));
1423 my ($text, $subs) = @_;
1424 my @subs = map { $_ || "../config.cgi?$module_name" }
1425                   ($subs ? @$subs : ( undef ));
1426 return "<p>".&text($text, @subs)."<p>\n";
1427 }
1428
1429 =head2 ui_print_endpage(text)
1430
1431 Prints HTML for an error message followed by a page footer with a link to
1432 /, then exits. Good for main page error messages.
1433
1434 =cut
1435 sub ui_print_endpage
1436 {
1437 return &theme_ui_print_endpage(@_) if (defined(&theme_ui_print_endpage));
1438 my ($text) = @_;
1439 print $text,"<p class='ui_footer'>\n";
1440 print "</p>\n";
1441 &ui_print_footer("/", $text{'index'});
1442 exit;
1443 }
1444
1445 =head2 ui_subheading(text, ...)
1446
1447 Returns HTML for a section heading
1448
1449 =cut
1450 sub ui_subheading
1451 {
1452 return &theme_ui_subheading(@_) if (defined(&theme_ui_subheading));
1453 return "<h3 class='ui_subheading'>".join("", @_)."</h3>\n";
1454 }
1455
1456 =head2 ui_links_row(&links)
1457
1458 Returns HTML for a row of links, like select all / invert selection / add..
1459
1460 =cut
1461 sub ui_links_row
1462 {
1463 return &theme_ui_links_row(@_) if (defined(&theme_ui_links_row));
1464 my ($links) = @_;
1465 return @$links ? join("\n|\n", @$links)."<br>\n"
1466                : "";
1467 }
1468
1469 ########################### collapsible section / tab functions
1470
1471 =head2 ui_hidden_javascript
1472
1473 Returns <script> and <style> sections for hiding functions and CSS
1474
1475 =cut
1476 sub ui_hidden_javascript
1477 {
1478 return &theme_ui_hidden_javascript(@_)
1479         if (defined(&theme_ui_hidden_javascript));
1480 my $rv;
1481 my $imgdir = "$gconfig{'webprefix'}/images";
1482 my ($jscb, $jstb) = ($cb, $tb);
1483 $jscb =~ s/'/\\'/g;
1484 $jstb =~ s/'/\\'/g;
1485
1486 return <<EOF;
1487 <style>
1488 .opener_shown {display:inline}
1489 .opener_hidden {display:none}
1490 </style>
1491 <script>
1492 // Open or close a hidden section
1493 function hidden_opener(divid, openerid)
1494 {
1495 var divobj = document.getElementById(divid);
1496 var openerobj = document.getElementById(openerid);
1497 if (divobj.className == 'opener_shown') {
1498   divobj.className = 'opener_hidden';
1499   openerobj.innerHTML = '<img border=0 src=$imgdir/closed.gif>';
1500   }
1501 else {
1502   divobj.className = 'opener_shown';
1503   openerobj.innerHTML = '<img border=0 src=$imgdir/open.gif>';
1504   }
1505 }
1506
1507 // Show a tab
1508 function select_tab(name, tabname, form)
1509 {
1510 var tabnames = document[name+'_tabnames'];
1511 var tabtitles = document[name+'_tabtitles'];
1512 for(var i=0; i<tabnames.length; i++) {
1513   var tabobj = document.getElementById('tab_'+tabnames[i]);
1514   var divobj = document.getElementById('div_'+tabnames[i]);
1515   var title = tabtitles[i];
1516   if (tabnames[i] == tabname) {
1517     // Selected table
1518     tabobj.innerHTML = '<table cellpadding=0 cellspacing=0><tr>'+
1519                        '<td valign=top $jscb>'+
1520                        '<img src=$imgdir/lc2.gif alt=""></td>'+
1521                        '<td $jscb nowrap>'+
1522                        '&nbsp;<b>'+title+'</b>&nbsp;</td>'+
1523                        '<td valign=top $jscb>'+
1524                        '<img src=$imgdir/rc2.gif alt=""></td>'+
1525                        '</tr></table>';
1526     divobj.className = 'opener_shown';
1527     }
1528   else {
1529     // Non-selected tab
1530     tabobj.innerHTML = '<table cellpadding=0 cellspacing=0><tr>'+
1531                        '<td valign=top $jstb>'+
1532                        '<img src=$imgdir/lc1.gif alt=""></td>'+
1533                        '<td $jstb nowrap>'+
1534                        '&nbsp;<a href=\\'\\' onClick=\\'return select_tab("'+
1535                        name+'", "'+tabnames[i]+'")\\'>'+title+'</a>&nbsp;</td>'+
1536                        '<td valign=top $jstb>'+
1537                        '<img src=$imgdir/rc1.gif alt=""></td>'+
1538                        '</tr></table>';
1539     divobj.className = 'opener_hidden';
1540     }
1541   }
1542 if (document.forms[0] && document.forms[0][name]) {
1543   document.forms[0][name].value = tabname;
1544   }
1545 return false;
1546 }
1547 </script>
1548 EOF
1549 }
1550
1551 =head2 ui_hidden_start(title, name, status, thisurl)
1552
1553 Returns HTML for the start of a collapsible hidden section, such as for
1554 advanced options.
1555
1556 =cut
1557 sub ui_hidden_start
1558 {
1559 return &theme_ui_hidden_start(@_) if (defined(&theme_ui_hidden_start));
1560 my ($title, $name, $status, $url) = @_;
1561 my $rv;
1562 if (!$main::ui_hidden_start_donejs++) {
1563         $rv .= &ui_hidden_javascript();
1564         }
1565 my $divid = "hiddendiv_$name";
1566 my $openerid = "hiddenopener_$name";
1567 my $defimg = $status ? "open.gif" : "closed.gif";
1568 my $defclass = $status ? 'opener_shown' : 'opener_hidden';
1569 $rv .= "<a href=\"javascript:hidden_opener('$divid', '$openerid')\" id='$openerid'><img border=0 src='$gconfig{'webprefix'}/images/$defimg' alt='*'></a>\n";
1570 $rv .= "<a href=\"javascript:hidden_opener('$divid', '$openerid')\">$title</a><br>\n";
1571 $rv .= "<div class='$defclass' id='$divid'>\n";
1572 return $rv;
1573 }
1574
1575 =head2 ui_hidden_end(name)
1576
1577 Returns HTML for the end of a hidden section
1578
1579 =cut
1580 sub ui_hidden_end
1581 {
1582 return &theme_ui_hidden_end(@_) if (defined(&theme_ui_hidden_end));
1583 my ($name) = @_;
1584 return "</div>\n";
1585 }
1586
1587 =head2 ui_hidden_table_row_start(title, name, status, thisurl)
1588
1589 Similar to ui_hidden_start, but for use within a table started with
1590 ui_table_start
1591
1592 =cut
1593 sub ui_hidden_table_row_start
1594 {
1595 return &theme_ui_hidden_table_row_start(@_)
1596         if (defined(&theme_ui_hidden_table_row_start));
1597 my ($title, $name, $status, $url) = @_;
1598 my ($rv, $rrv);
1599 if (!$main::ui_hidden_start_donejs++) {
1600         $rv .= &ui_hidden_javascript();
1601         }
1602 my $divid = "hiddendiv_$name";
1603 my $openerid = "hiddenopener_$name";
1604 my $defimg = $status ? "open.gif" : "closed.gif";
1605 my $defclass = $status ? 'opener_shown' : 'opener_hidden';
1606 $rrv .= "<a href=\"javascript:hidden_opener('$divid', '$openerid')\" id='$openerid'><img border=0 src='$gconfig{'webprefix'}/images/$defimg'></a>\n";
1607 $rrv .= "<a href=\"javascript:hidden_opener('$divid', '$openerid')\">$title</a><br>\n";
1608 $rv .= &ui_table_row(undef, $rrv, $main::ui_table_cols);
1609 $rv .= "</table>\n";
1610 $rv .= "<div class='$defclass' id='$divid'>\n";
1611 $rv .= "<table width=100%>\n";
1612 return $rv;
1613 }
1614
1615 =head2 ui_hidden_table_row_end(name)
1616
1617 MISSING DOCUMENTATION
1618
1619 =cut
1620 sub ui_hidden_table_row_end
1621 {
1622 return &theme_ui_hidden_table_row_end(@_)
1623         if (defined(&theme_ui_hidden_table_row_end));
1624 my ($name) = @_;
1625 return "</table></div><table width=100%>\n";
1626 }
1627
1628 =head2 ui_hidden_table_start(heading, [tabletags], [cols], name, status, [&default-tds], [rightheading])
1629
1630 A table with a heading and table inside, and which is collapsible
1631
1632 =cut
1633 sub ui_hidden_table_start
1634 {
1635 return &theme_ui_hidden_table_start(@_)
1636         if (defined(&theme_ui_hidden_table_start));
1637 my ($heading, $tabletags, $cols, $name, $status, $tds, $rightheading) = @_;
1638 my $rv;
1639 if (!$main::ui_hidden_start_donejs++) {
1640         $rv .= &ui_hidden_javascript();
1641         }
1642 my $divid = "hiddendiv_$name";
1643 my $openerid = "hiddenopener_$name";
1644 my $defimg = $status ? "open.gif" : "closed.gif";
1645 my $defclass = $status ? 'opener_shown' : 'opener_hidden';
1646 my $text = defined($tconfig{'cs_text'}) ? $tconfig{'cs_text'} : 
1647               defined($gconfig{'cs_text'}) ? $gconfig{'cs_text'} : "000000";
1648 $rv .= "<table class='ui_table' border $tabletags>\n";
1649 my $colspan = 1;
1650 if (defined($heading) || defined($rightheading)) {
1651         $rv .= "<tr $tb> <td>";
1652         if (defined($heading)) {
1653                 $rv .= "<a href=\"javascript:hidden_opener('$divid', '$openerid')\" id='$openerid'><img border=0 src='$gconfig{'webprefix'}/images/$defimg'></a> <a href=\"javascript:hidden_opener('$divid', '$openerid')\"><b><font color=#$text>$heading</font></b></a></td>";
1654                 }
1655         if (defined($rightheading)) {
1656                 $rv .= "<td align=right>$rightheading</td>";
1657                 $colspan++;
1658                 }
1659         $rv .= "</td> </tr>\n";
1660         }
1661 $rv .= "<tr $cb> <td colspan=$colspan><div class='$defclass' id='$divid'><table width=100%>\n";
1662 $main::ui_table_cols = $cols || 4;
1663 $main::ui_table_pos = 0;
1664 $main::ui_table_default_tds = $tds;
1665 return $rv;
1666 }
1667
1668 =head2 ui_hidden_table_end(name)
1669
1670 Returns HTML for the end of table with hiding, as started by
1671 ui_hidden_table_start
1672
1673 =cut
1674 sub ui_hidden_table_end
1675 {
1676 my ($name) = @_;
1677 return &theme_ui_hidden_table_end(@_) if (defined(&theme_ui_hidden_table_end));
1678 return "</table></div></td></tr></table>\n";
1679 }
1680
1681 =head2 ui_tabs_start(&tabs, name, selected, show-border)
1682
1683 Render a row of tabs from which one can be selected. Each tab is an array
1684 ref containing a name, title and link.
1685
1686 =cut
1687 sub ui_tabs_start
1688 {
1689 return &theme_ui_tabs_start(@_) if (defined(&theme_ui_tabs_start));
1690 my ($tabs, $name, $sel, $border) = @_;
1691 my $rv;
1692 if (!$main::ui_hidden_start_donejs++) {
1693         $rv .= &ui_hidden_javascript();
1694         }
1695
1696 # Build list of tab titles and names
1697 my $tabnames = "[".join(",", map { "\"".&html_escape($_->[0])."\"" } @$tabs)."]";
1698 my $tabtitles = "[".join(",", map { "\"".&html_escape($_->[1])."\"" } @$tabs)."]";
1699 $rv .= "<script>\n";
1700 $rv .= "document.${name}_tabnames = $tabnames;\n";
1701 $rv .= "document.${name}_tabtitles = $tabtitles;\n";
1702 $rv .= "</script>\n";
1703
1704 # Output the tabs
1705 my $imgdir = "$gconfig{'webprefix'}/images";
1706 $rv .= &ui_hidden($name, $sel)."\n";
1707 $rv .= "<table border=0 cellpadding=0 cellspacing=0 class='ui_tabs'>\n";
1708 $rv .= "<tr><td bgcolor=#ffffff colspan=".(scalar(@$tabs)*2+1).">";
1709 if ($ENV{'HTTP_USER_AGENT'} !~ /msie/i) {
1710         # For some reason, the 1-pixel space above the tabs appears huge on IE!
1711         $rv .= "<img src=$imgdir/1x1.gif>";
1712         }
1713 $rv .= "</td></tr>\n";
1714 $rv .= "<tr>\n";
1715 $rv .= "<td bgcolor=#ffffff width=1><img src=$imgdir/1x1.gif></td>\n";
1716 foreach my $t (@$tabs) {
1717         if ($t ne $tabs[0]) {
1718                 # Spacer
1719                 $rv .= "<td width=2 bgcolor=#ffffff class='ui_tab_spacer'>".
1720                        "<img src=$imgdir/1x1.gif></td>\n";
1721                 }
1722         my $tabid = "tab_".$t->[0];
1723         $rv .= "<td id=${tabid} class='ui_tab'>";
1724         $rv .= "<table cellpadding=0 cellspacing=0 border=0><tr>";
1725         if ($t->[0] eq $sel) {
1726                 # Selected tab
1727                 $rv .= "<td valign=top $cb class='selectedTabLeft'>".
1728                        "<img src=$imgdir/lc2.gif alt=\"\"></td>";
1729                 $rv .= "<td $cb nowrap class='selectedTabMiddle'>".
1730                        "&nbsp;<b>$t->[1]</b>&nbsp;</td>";
1731                 $rv .= "<td valign=top $cb class='selectedTabRight'>".
1732                        "<img src=$imgdir/rc2.gif alt=\"\"></td>";
1733                 }
1734         else {
1735                 # Other tab (which has a link)
1736                 $rv .= "<td valign=top $tb>".
1737                        "<img src=$imgdir/lc1.gif alt=\"\"></td>";
1738                 $rv .= "<td $tb nowrap>".
1739                        "&nbsp;<a href='$t->[2]' ".
1740                        "onClick='return select_tab(\"$name\", \"$t->[0]\")'>".
1741                        "$t->[1]</a>&nbsp;</td>";
1742                 $rv .= "<td valign=top $tb>".
1743                        "<img src=$imgdir/rc1.gif ".
1744                        "alt=\"\"></td>";
1745                 $rv .= "</td>\n";
1746                 }
1747         $rv .= "</tr></table>";
1748         $rv .= "</td>\n";
1749         }
1750 $rv .= "<td bgcolor=#ffffff width=1><img src=$imgdir/1x1.gif></td>\n";
1751 $rv .= "</table>\n";
1752
1753 if ($border) {
1754         # All tabs are within a grey box
1755         $rv .= "<table width=100% cellpadding=0 cellspacing=0 border=0 ".
1756                "class='ui_tabs_box'>\n";
1757         $rv .= "<tr> <td bgcolor=#ffffff rowspan=3 width=1><img src=$imgdir/1x1.gif></td>\n";
1758         $rv .= "<td $cb colspan=3 height=2><img src=$imgdir/1x1.gif></td> </tr>\n";
1759         $rv .= "<tr> <td $cb width=2><img src=$imgdir/1x1.gif></td>\n";
1760         $rv .= "<td valign=top>";
1761         }
1762 $main::ui_tabs_selected = $sel;
1763 return $rv;
1764 }
1765
1766 =head2 ui_tabs_end(border)
1767
1768 MISSING DOCUMENTATION
1769
1770 =cut
1771 sub ui_tabs_end
1772 {
1773 return &theme_ui_tabs_end(@_) if (defined(&theme_ui_tabs_end));
1774 my ($border) = @_;
1775 my $rv;
1776 my $imgdir = "$gconfig{'webprefix'}/images";
1777 if ($border) {
1778         $rv .= "</td>\n";
1779         $rv .= "<td $cb width=2><img src=$imgdir/1x1.gif></td>\n";
1780         $rv .= "</tr>\n";
1781         $rv .= "<tr> <td $cb colspan=3 height=2><img src=$imgdir/1x1.gif></td> </tr>\n";
1782         $rv .= "</table>\n";
1783         }
1784 return $rv;
1785 }
1786
1787 =head2 ui_tabs_start_tab(name, tab)
1788
1789 Must be called before outputting the HTML for the named tab
1790
1791 =cut
1792 sub ui_tabs_start_tab
1793 {
1794 return &theme_ui_tabs_start_tab(@_) if (defined(&theme_ui_tabs_start_tab));
1795 my ($name, $tab) = @_;
1796 my $defclass = $tab eq $main::ui_tabs_selected ?
1797                         'opener_shown' : 'opener_hidden';
1798 my $rv = "<div id='div_$tab' class='$defclass ui_tabs_start'>\n";
1799 return $rv;
1800 }
1801
1802 =head2 ui_tabs_start_tabletab(name, tab)
1803
1804 Behaves like ui_tabs_start_tab, but for use within a ui_table_start block
1805
1806 =cut
1807 sub ui_tabs_start_tabletab
1808 {
1809 return &theme_ui_tabs_start_tabletab(@_)
1810         if (defined(&theme_ui_tabs_start_tabletab));
1811 my $div = &ui_tabs_start_tab(@_);
1812 return "</table>\n".$div."<table width=100%>\n";
1813 }
1814
1815 sub ui_tabs_end_tab
1816 {
1817 return &theme_ui_tabs_end_tab(@_) if (defined(&theme_ui_tabs_end_tab));
1818 return "</div>\n";
1819 }
1820
1821 sub ui_tabs_end_tabletab
1822 {
1823 return &theme_ui_tabs_end_tabletab(@_)
1824         if (defined(&theme_ui_tabs_end_tabletab));
1825 return "</table></div><table width=100%>\n";
1826 }
1827
1828 =head2 ui_max_text_width(width, [text-area?])
1829
1830 Returns a new width for a text field, based on theme settings
1831
1832 =cut
1833 sub ui_max_text_width
1834 {
1835 my ($w, $ta) = @_;
1836 my $max = $ta ? $tconfig{'maxareawidth'} : $tconfig{'maxboxwidth'};
1837 return $max && $w > $max ? $max : $w;
1838 }
1839
1840 ####################### radio hidden functions
1841
1842 =head2 ui_radio_selector(&opts, name, selected)
1843
1844 Returns HTML for a set of radio buttons, each of which shows a different
1845 block of HTML when selected. &opts is an array ref to arrays containing
1846 [ value, label, html ]
1847
1848 =cut
1849 sub ui_radio_selector
1850 {
1851 return &theme_ui_radio_selector(@_) if (defined(&theme_ui_radio_selector));
1852 my ($opts, $name, $sel) = @_;
1853 my $rv;
1854 if (!$main::ui_radio_selector_donejs++) {
1855         $rv .= &ui_radio_selector_javascript();
1856         }
1857 my $optnames =
1858         "[".join(",", map { "\"".&html_escape($_->[0])."\"" } @$opts)."]";
1859 foreach my $o (@$opts) {
1860         $rv .= &ui_oneradio($name, $o->[0], $o->[1], $sel eq $o->[0],
1861             "onClick='selector_show(\"$name\", \"$o->[0]\", $optnames)'");
1862         }
1863 $rv .= "<br>\n";
1864 foreach my $o (@$opts) {
1865         my $cls = $o->[0] eq $sel ? "selector_shown" : "selector_hidden";
1866         $rv .= "<div id=sel_${name}_$o->[0] class=$cls>".$o->[2]."</div>\n";
1867         }
1868 return $rv;
1869 }
1870
1871 sub ui_radio_selector_javascript
1872 {
1873 return <<EOF;
1874 <style>
1875 .selector_shown {display:inline}
1876 .selector_hidden {display:none}
1877 </style>
1878 <script>
1879 function selector_show(name, value, values)
1880 {
1881 for(var i=0; i<values.length; i++) {
1882         var divobj = document.getElementById('sel_'+name+'_'+values[i]);
1883         divobj.className = value == values[i] ? 'selector_shown'
1884                                               : 'selector_hidden';
1885         }
1886 }
1887 </script>
1888 EOF
1889 }
1890
1891 ####################### grid layout functions
1892
1893 =head2 ui_grid_table(&elements, columns, [width-percent], [tds], [tabletags], [title])
1894
1895 Given a list of HTML elements, formats them into a table with the given
1896 number of columns. However, themes are free to override this to use fewer
1897 columns where space is limited.
1898
1899 =cut
1900 sub ui_grid_table
1901 {
1902 return &theme_ui_grid_table(@_) if (defined(&theme_ui_grid_table));
1903 my ($elements, $cols, $width, $tds, $tabletags, $title) = @_;
1904 return "" if (!@$elements);
1905 my $rv = "<table class='ui_grid_table'".
1906             ($width ? " width=$width%" : "").
1907             ($tabletags ? " ".$tabletags : "").
1908             ">\n";
1909 my $i;
1910 for($i=0; $i<@$elements; $i++) {
1911         $rv .= "<tr class='ui_grid_row'>" if ($i%$cols == 0);
1912         $rv .= "<td ".$tds->[$i%$cols]." valign=top class='ui_grid_cell'>".
1913                $elements->[$i]."</td>\n";
1914         $rv .= "</tr>" if ($i%$cols == $cols-1);
1915         }
1916 if ($i%$cols) {
1917         while($i%$cols) {
1918                 $rv .= "<td ".$tds->[$i%$cols]." class='ui_grid_cell'>".
1919                        "<br></td>\n";
1920                 $i++;
1921                 }
1922         $rv .= "</tr>\n";
1923         }
1924 $rv .= "</table>\n";
1925 if (defined($title)) {
1926         $rv = "<table class=ui_table border ".
1927               ($width ? " width=$width%" : "").">\n".
1928               ($title ? "<tr $tb> <td><b>$title</b></td> </tr>\n" : "").
1929               "<tr $cb> <td>$rv</td> </tr>\n".
1930               "</table>";
1931         }
1932 return $rv;
1933 }
1934
1935 =head2 ui_radio_table(name, selected, &rows)
1936
1937 Returns HTML for a table of radio buttons, each of which has a label and
1938 some associated inputs to the right.
1939
1940 =cut
1941 sub ui_radio_table
1942 {
1943 return &theme_ui_radio_table(@_) if (defined(&theme_ui_radio_table));
1944 my ($name, $sel, $rows) = @_;
1945 return "" if (!@$rows);
1946 my $rv = "<table class='ui_radio_table'>\n";
1947 foreach my $r (@$rows) {
1948         $rv .= "<tr>\n";
1949         $rv .= "<td valign=top".(defined($r->[2]) ? "" : " colspan=2").
1950                "><b>".
1951                &ui_oneradio($name, $r->[0], $r->[1], $r->[0] eq $sel).
1952                "</b></td>\n";
1953         if (defined($r->[2])) {
1954                 $rv .= "<td valign=top>".$r->[2]."</td>\n";
1955                 }
1956         $rv .= "</tr>\n";
1957         }
1958 $rv .= "</table>\n";
1959 return $rv;
1960 }
1961
1962 =head2 ui_up_down_arrows(uplink, downlink, up-show, down-show)
1963
1964 Returns HTML for moving some objects in a table up or down
1965
1966 =cut
1967 sub ui_up_down_arrows
1968 {
1969 return &theme_ui_up_down_arrows(@_) if (defined(&theme_ui_up_down_arrows));
1970 my ($uplink, $downlink, $upshow, $downshow) = @_;
1971 my $mover;
1972 my $imgdir = "$gconfig{'webprefix'}/images";
1973 if ($downshow) {
1974         $mover .= "<a href=\"$downlink\">".
1975                   "<img src=$imgdir/movedown.gif border=0></a>";
1976         }
1977 else {
1978         $mover .= "<img src=$imgdir/movegap.gif>";
1979         }
1980 if ($upshow) {
1981         $mover .= "<a href=\"$uplink\">".
1982                   "<img src=$imgdir/moveup.gif border=0></a>";
1983         }
1984 else {
1985         $mover .= "<img src=$imgdir/movegap.gif>";
1986         }
1987 return $mover;
1988 }
1989
1990 =head2 ui_hr
1991
1992 Returns a horizontal row tag
1993
1994 =cut
1995 sub ui_hr
1996 {
1997 return &theme_ui_hr() if (defined(&theme_ui_hr));
1998 return "<hr>\n";
1999 }
2000
2001 =head2 ui_nav_link(direction, url, disabled)
2002
2003 Returns an arrow icon linking to provided url
2004
2005 =cut
2006 sub ui_nav_link
2007 {
2008 return &theme_ui_nav_link(@_) if (defined(&theme_ui_nav_link));
2009 my ($direction, $url, $disabled) = @_;
2010 my $alt = $direction eq "left" ? '<-' : '->';
2011 if ($disabled) {
2012         return "<img alt=\"$alt\" align=\"middle\""
2013              . "src=\"$gconfig{'webprefix'}/images/$direction-grey.gif\">\n";
2014         }
2015 else {
2016         return "<a href=\"$url\"><img alt=\"$alt\" align=\"middle\""
2017              . "src=\"$gconfig{'webprefix'}/images/$direction.gif\"></a>\n";
2018         }
2019 }
2020
2021 =head2 ui_confirmation_form(cgi, message, &hiddens, [&buttons], [&otherinputs], [extra-warning])
2022
2023 Returns HTML for a form asking for confirmation before performing some
2024 action, such as deleting a user.
2025
2026 =cut
2027 sub ui_confirmation_form
2028 {
2029 my ($cgi, $message, $hiddens, $buttons, $others, $warning) = @_;
2030 my $rv;
2031 $rv .= "<center class=ui_confirmation>\n";
2032 $rv .= &ui_form_start($cgi, "post");
2033 foreach my $h (@$hiddens) {
2034         $rv .= &ui_hidden(@$h);
2035         }
2036 $rv .= "<b>$message</b><p>\n";
2037 if ($warning) {
2038         $rv .= "<b><font color=#ff0000>$warning</font></b><p>\n";
2039         }
2040 if ($others) {
2041         $rv .= $others."<p>\n";
2042         }
2043 $rv .= &ui_form_end($buttons);
2044 $rv .= "</center>\n";
2045 return $rv;
2046 }
2047
2048 ####################### javascript functions
2049
2050 =head2 js_disable_inputs
2051
2052 js_disable_input(&disable-inputs, &enable-inputs, [tag])
2053 Returns Javascript to disable some form elements and enable others
2054
2055 =cut
2056 sub js_disable_inputs
2057 {
2058 my $rv;
2059 my $f;
2060 foreach $f (@{$_[0]}) {
2061         $rv .= "e = form.elements[\"$f\"]; e.disabled = true; ";
2062         $rv .= "for(i=0; i<e.length; i++) { e[i].disabled = true; } ";
2063         }
2064 foreach $f (@{$_[1]}) {
2065         $rv .= "e = form.elements[\"$f\"]; e.disabled = false; ";
2066         $rv .= "for(i=0; i<e.length; i++) { e[i].disabled = false; } ";
2067         }
2068 foreach $f (@{$_[1]}) {
2069         if ($f =~ /^(.*)_def$/ && &indexof($1, @{$_[1]}) >= 0) {
2070                 # When enabling both a _def field and its associated text field,
2071                 # disable the text if the _def is set to 1
2072                 my $tf = $1;
2073                 $rv .= "e = form.elements[\"$f\"]; for(i=0; i<e.length; i++) { if (e[i].checked && e[i].value == \"1\") { form.elements[\"$tf\"].disabled = true } } ";
2074                 }
2075         }
2076 return $_[2] ? "$_[2]='$rv'" : $rv;
2077 }
2078
2079 =head2 ui_page_flipper(message, [inputs, cgi], left-link, right-link, [far-left-link], [far-right-link], [below])
2080
2081 Returns HTML for moving left and right in some large list, such as an inbox
2082 or database table. If only 5 parameters are given, no far links are included.
2083 If any link is undef, that array will be greyed out.
2084
2085 =cut
2086 sub ui_page_flipper
2087 {
2088 return &theme_ui_page_flipper(@_) if (defined(&theme_ui_page_flipper));
2089 my ($msg, $inputs, $cgi, $left, $right, $farleft, $farright, $below) = @_;
2090 my $rv = "<center>";
2091 $rv .= &ui_form_start($cgi) if ($cgi);
2092
2093 # Far left link, if needed
2094 if (@_ > 5) {
2095         if ($farleft) {
2096                 $rv .= "<a href='$farleft'><img src=/images/first.gif ".
2097                        "border=0 align=middle></a>\n";
2098                 }
2099         else {
2100                 $rv .= "<img src=/images/first-grey.gif ".
2101                        "border=0 align=middle></a>\n";
2102                 }
2103         }
2104
2105 # Left link
2106 if ($left) {
2107         $rv .= "<a href='$left'><img src=/images/left.gif ".
2108                "border=0 align=middle></a>\n";
2109         }
2110 else {
2111         $rv .= "<img src=/images/left-grey.gif ".
2112                "border=0 align=middle></a>\n";
2113         }
2114
2115 # Message and inputs
2116 $rv .= $msg;
2117 $rv .= " ".$inputs if ($inputs);
2118
2119 # Right link
2120 if ($right) {
2121         $rv .= "<a href='$right'><img src=/images/right.gif ".
2122                "border=0 align=middle></a>\n";
2123         }
2124 else {
2125         $rv .= "<img src=/images/right-grey.gif ".
2126                "border=0 align=middle></a>\n";
2127         }
2128
2129 # Far right link, if needed
2130 if (@_ > 5) {
2131         if ($farright) {
2132                 $rv .= "<a href='$farright'><img src=/images/last.gif ".
2133                        "border=0 align=middle></a>\n";
2134                 }
2135         else {
2136                 $rv .= "<img src=/images/last-grey.gif ".
2137                        "border=0 align=middle></a>\n";
2138                 }
2139         }
2140
2141 $rv .= "<br>".$below if ($below);
2142 $rv .= &ui_form_end() if ($cgi);
2143 $rv .= "</center>\n";
2144 return $rv;
2145 }
2146
2147 =head2 js_checkbox_disable(name, &checked-disable, &checked-enable, [tag])
2148
2149 MISSING DOCUMENTATION
2150
2151 =cut
2152 sub js_checkbox_disable
2153 {
2154 my $rv;
2155 my $f;
2156 foreach $f (@{$_[1]}) {
2157         $rv .= "form.elements[\"$f\"].disabled = $_[0].checked; ";
2158         }
2159 foreach $f (@{$_[2]}) {
2160         $rv .= "form.elements[\"$f\"].disabled = !$_[0].checked; ";
2161         }
2162 return $_[3] ? "$_[3]='$rv'" : $rv;
2163 }
2164
2165 =head2 js_redirect(url, [window-object])
2166
2167 Returns HTML to trigger a redirect to some URL
2168
2169 =cut
2170 sub js_redirect
2171 {
2172 my ($url, $window) = @_;
2173 if (defined(&theme_js_redirect)) {
2174         return &theme_js_redirect(@_);
2175         }
2176 return "<script>${window}.location = '".&quote_escape($url)."';</script>\n";
2177 }
2178
2179 1;
2180