2 use Webmin::JavascriptButton;
5 =head2 new Webmin::Table(&headings, [width], [name], [heading])
6 Create a multi-column table, with support for sorting, paging and so on
10 if (defined(&Webmin::Theme::Table::new) &&
11 caller() !~ /Webmin::Theme::Table/) {
12 return new Webmin::Theme::Table(@_[1..$#_]);
14 my ($self, $headings, $width, $name, $heading) = @_;
15 $self = { 'sorter' => [ map { \&default_sorter } @$headings ] };
17 $self->set_headings($headings);
18 $self->set_name($name) if (defined($name));
19 $self->set_width($width) if (defined($width));
20 $self->set_heading($heading) if (defined($heading));
21 $self->set_all_sortable(1);
26 =head2 add_row(&fields)
27 Adds a row to the table. Each element in the row can be either an input of some
28 kind, or a piece of text.
32 my ($self, $fields) = @_;
33 push(@{$self->{'rows'}}, $fields);
37 Returns the HTML for this table. The actual ordering may depend upon sort headers
38 clicked by the user. The rows to display may be limited by the page size.
43 my @srows = @{$self->{'rows'}};
44 my $thisurl = $self->{'form'}->{'page'}->get_myurl();
45 my $name = $self->get_name();
49 if ($self->get_heading()) {
50 $rv .= &ui_subheading($self->get_heading())."\n";
53 my $sm = $self->get_searchmax();
54 if (defined($sm) && @srows > $sm) {
55 # Too many rows to show .. add a search form. This will need to close
56 # the parent form, and then re-open it after the search form, as nested
57 # forms aren't allowed!
58 if ($self->get_searchmsg()) {
59 $rv .= $self->get_searchmsg()."<br>\n";
62 my $form = new Webmin::Form($thisurl, "get");
63 $form->set_input($self->{'form'}->{'in'});
64 my $section = new Webmin::Section(undef, 2);
65 $form->add_section($section);
67 my $col = new Webmin::Select("ui_searchcol_".$name, undef);
69 foreach my $h (@{$self->get_headings()}) {
70 if ($self->{'sortable'}->[$i]) {
71 $col->add_option($i, $h);
75 $section->add_input($text{'ui_searchcol'}, $col);
77 my $for = new Webmin::Textbox("ui_searchfor_".$name, undef, 30);
78 $section->add_input($text{'ui_searchfor'}, $for);
80 $rv .= $section->html();
81 my $url = $self->make_url(undef, undef, undef, undef, 1);
82 my $jsb = new Webmin::JavascriptButton($text{'ui_searchok'},
83 "window.location = '$url'+'&'+'ui_searchfor_${name}'+'='+escape(form.ui_searchfor_${name}.value)+'&'+'ui_searchcol_${name}'+'='+escape(form.ui_searchcol_${name}.selectedIndex)");
87 # Limit records to current search
88 if (defined($col->get_value())) {
89 my $sf = $for->get_value();
90 @srows = grep { $_->[$col->get_value()] =~ /\Q$sf\E/i } @srows;
97 # Prepare the selector
98 my $selc = $self->{'selectcolumn'};
99 my $seli = $self->{'selectinput'};
101 if (defined($selc)) {
103 foreach my $r (@srows) {
104 $selmap{$r,$selc} = $seli->one_html($i);
110 my ($sortcol, $sortdir) = $self->get_sortcolumn();
111 if (defined($sortcol)) {
112 my $func = $self->{'sorter'}->[$sortcol];
113 @srows = sort { my $so = &$func($a->[$sortcol], $b->[$sortcol], $sortcol);
114 $sortdir ? -$so : $so } @srows;
117 # Build the td attributes
118 my @tds = map { "valign=top" } @{$self->{'headings'}};
119 if ($self->{'widths'}) {
121 foreach my $w (@{$self->{'widths'}}) {
122 $tds[$i++] .= " width=$w";
125 if ($self->{'aligns'}) {
127 foreach my $a (@{$self->{'aligns'}}) {
128 $tds[$i++] .= " align=$a";
132 # Find the page we want
133 my $page = $self->get_pagepos();
134 my ($start, $end, $origsize);
135 if ($self->get_paging() && $self->get_pagesize()) {
136 # Restrict view to rows within some page
137 $start = $self->get_pagesize()*$page;
138 $end = $self->get_pagesize()*($page+1) - 1;
139 if ($start >= @srows) {
142 $end = $self->get_pagesize()-1;
144 if ($end >= @srows) {
148 $origsize = scalar(@srows);
149 @srows = @srows[$start..$end];
152 # Generate the headings, with sorters
153 $thisurl .= $thisurl =~ /\?/ ? "&" : "?";
156 foreach my $h (@{$self->get_headings()}) {
157 if ($self->{'sortable'}->[$i]) {
158 # Column can be sorted!
159 my $hh = "<table cellpadding=0 cellspacing=0 width=100%><tr>";
160 $hh .= "<td><b>$h</b></td> <td align=right>";
161 if (!defined($sortcol) || $sortcol != $i) {
162 # Not sorting on this column .. show grey button
163 my $url = $self->make_url($i, 0, undef, undef);
164 $hh .= "<a href='$url'>".
165 "<img src=$gconfig{'webprefix'}/images/nosort.gif border=0></a>";
168 # Sorting .. show button to switch mode
169 my $notsort = !$sortdir;
170 my $url = $self->make_url($i, $sortdir ? 0 : 1, undef, undef);
171 $hh .= "<a href='$url'>".
172 "<img src=$gconfig{'webprefix'}/images/sort.gif border=0></a>";
174 $hh .= "</td></tr></table>";
175 push(@sheadings, $hh);
178 push(@sheadings, $h);
183 # Get any errors for inputs
184 my @errs = map { $_->get_errors() } $self->list_inputs();
186 foreach my $e (@errs) {
187 $rv .= "<font color=#ff0000>$e</font><br>\n";
191 # Build links for top and bottom
193 if (ref($seli) =~ /Checkboxes/) {
194 # Add select all/none links
195 my $formno = $self->{'form'}->get_formno();
196 $links .= &select_all_link($seli->get_name(), $formno,
197 $text{'ui_selall'})."\n";
198 $links .= &select_invert_link($seli->get_name(), $formno,
199 $text{'ui_selinv'})."\n";
200 $links .= " \n";
202 foreach my $l (@{$self->{'links'}}) {
203 $links .= "<a href='$l->[0]'>$l->[1]</a>\n";
205 $links .= "<br>" if ($links);
207 # Build list of inputs for bottom
209 foreach my $i (@{$self->{'inputs'}}) {
210 $inputs .= $i->html()."\n";
212 $inputs .= "<br>" if ($inputs);
215 if ($self->get_paging() && $origsize) {
216 my $lastpage = int(($origsize-1)/$self->get_pagesize());
219 # Add start and left arrows
220 my $surl = $self->make_url(undef, undef, undef, 0);
221 $rv .= "<a href='$surl'><img src=$gconfig{'webprefix'}/images/first.gif border=0 align=middle></a>\n";
222 my $lurl = $self->make_url(undef, undef, undef, $page-1);
223 $rv .= "<a href='$lurl'><img src=$gconfig{'webprefix'}/images/left.gif border=0 align=middle></a>\n";
226 # Start and left are disabled
227 $rv .= "<img src=$gconfig{'webprefix'}/images/first-grey.gif border=0 align=middle>\n";
228 $rv .= "<img src=$gconfig{'webprefix'}/images/left-grey.gif border=0 align=middle>\n";
230 $rv .= &text('ui_paging', $start+1, $end+1, $origsize);
231 if ($end < $origsize-1) {
232 # Add right and end arrows
233 my $rurl = $self->make_url(undef, undef, undef, $page+1);
234 $rv .= "<a href='$rurl'><img src=$gconfig{'webprefix'}/images/right.gif border=0 align=middle></a>\n";
235 my $eurl = $self->make_url(undef, undef, undef, $lastpage);
236 $rv .= "<a href='$eurl'><img src=$gconfig{'webprefix'}/images/last.gif border=0 align=middle></a>\n";
239 # Right and end are disabled
240 $rv .= "<img src=$gconfig{'webprefix'}/images/right-grey.gif border=0 align=middle>\n";
241 $rv .= "<img src=$gconfig{'webprefix'}/images/last-grey.gif border=0 align=middle>\n";
243 $rv .= "</center>\n";
246 # Create actual table
249 $rv .= &ui_columns_start(\@sheadings, $self->{'width'}, 0, \@tds);
250 foreach my $r (@srows) {
252 for(my $i=0; $i<@$r || $i<@sheadings; $i++) {
253 if (ref($r->[$i]) eq "ARRAY") {
254 my $j = $r->[$i]->[0] &&
255 $r->[$i]->[0]->isa("Webmin::TableAction")
256 ? " | " : " ";
257 $row[$i] = $selmap{$r,$i}.
258 join($j, map { ref($_) ? $_->html() : $_ }
261 elsif (ref($r->[$i])) {
262 $row[$i] = $selmap{$r,$i}.$r->[$i]->html();
265 $row[$i] = $selmap{$r,$i}.$r->[$i];
268 $rv .= &ui_columns_row(\@row, \@tds);
270 $rv .= &ui_columns_end();
272 elsif ($self->{'emptymsg'}) {
273 $rv .= $self->{'emptymsg'}."<p>\n";
280 =head2 set_form(form)
281 Called by the Webmin::Form object when this table is added to it
285 my ($self, $form) = @_;
286 $self->{'form'} = $form;
287 foreach my $i ($self->list_inputs()) {
292 =head2 set_sorter(function, [column])
293 Sets a function used for sorting fields. Will be called with two field values to
294 compare, and a column number.
298 my ($self, $func, $col) = @_;
300 $self->{'sorter'}->[$col] = $func;
303 $self->{'sorter'} = [ map { $func } @{$self->{'headings'}} ];
307 =head2 default_sorter(value1, value2, col)
311 my ($value1, $value2, $col) = @_;
312 if (ref($value1) && $value1->isa("Webmin::TableAction")) {
313 $value1 = $value1->get_value();
314 $value2 = $value2->get_value();
316 return lc($value1) cmp lc($value2);
319 =head2 numeric_sorter(value1, value2, col)
323 my ($value1, $value2, $col) = @_;
324 return $value1 <=> $value2;
327 =head2 set_sortable(column, sortable?)
328 Tells the table if some column should allow sorting or not. By default, all are.
332 my ($self, $col, $sortable) = @_;
333 $self->{'sortable'}->[$col] = $sortable;
336 =head2 set_all_sortable(sortable?)
337 Enabled or disables sorting for all columns
341 my ($self, $sortable) = @_;
342 $self->{'sortable'} = [ map { $sortable } @{$self->{'headings'}} ];
345 =head2 get_sortcolumn()
346 Returns the column to sort on and the order (1 for descending), or undef for none
351 my $in = $self->{'form'} ? $self->{'form'}->{'in'} : undef;
352 my $name = $self->get_name();
353 if ($in && defined($in->{"ui_sortcolumn_".$name})) {
354 return ( $in->{"ui_sortcolumn_".$name},
355 $in->{"ui_sortdir_".$name} );
358 return ( $self->{'sortcolumn'}, $self->{'sortdir'} );
362 =head2 set_sortcolumn(num, descending?)
363 Sets the default column on which sorting will be done, unless overridden by
368 my ($self, $col, $desc) = @_;
369 $self->{'sortcolumn'} = $col;
370 $self->{'sortdir'} = $desc;
374 Returns 1 if page-by-page display should be used
379 my $in = $self->{'form'} ? $self->{'form'}->{'in'} : undef;
380 my $name = $self->get_name();
381 if ($in && defined($in->{"ui_paging_".$name})) {
382 return ( $in->{"ui_paging_".$name} );
385 return ( $self->{'paging'} );
389 =head2 set_paging(paging?)
390 Turns page-by-page display of the table on or off
394 my ($self, $paging) = @_;
395 $self->{'paging'} = $paging;
400 my ($self, $name) = @_;
401 $self->{'name'} = $name;
405 Returns the name for indentifying this table in HTML
410 if (defined($self->{'name'})) {
411 return $self->{'name'};
413 elsif ($self->{'form'}) {
414 my $secs = $self->{'form'}->{'sections'};
415 for(my $i=0; $i<@$secs; $i++) {
416 return "table".$i if ($secs->[$i] eq $self);
424 my ($self, $headings) = @_;
425 $self->{'headings'} = $headings;
431 return $self->{'headings'};
434 =head2 set_selector(column, input)
435 Takes a Webmin::Checkboxes or Webmin::Radios object, and uses it to add checkboxes
436 in the specified column.
440 my ($self, $col, $input) = @_;
441 $self->{'selectcolumn'} = $col;
442 $self->{'selectinput'} = $input;
443 $input->set_form($form);
446 =head2 get_selector()
447 Returns the UI element used for selecting rows
452 return wantarray ? ( $self->{'selectinput'}, $self->{'selectcolumn'} )
453 : $self->{'selectinput'};
456 =head2 set_widths(&widths)
457 Given an array reference of widths (like 50 or 20%), uses them for the columns
462 my ($self, $widths) = @_;
463 $self->{'widths'} = $widths;
466 =head2 set_width([number|number%])
467 Sets the width of this entire table. Can be called with 100%, 500 or undef to use
468 the minimum possible width.
472 my ($self, $width) = @_;
473 $self->{'width'} = $width;
476 =head2 set_aligns(&aligns)
477 Given an array reference of horizontal alignments (like left or right), uses them
478 for the columns in the table.
482 my ($self, $aligns) = @_;
483 $self->{'aligns'} = $aligns;
487 Validates all inputs, and returns a list of error messages
492 my $seli = $self->{'selectinput'};
495 push(@errs, map { [ $seli->get_name(), $_ ] } $seli->validate());
497 foreach my $i ($self->list_inputs()) {
498 foreach my $e ($i->validate()) {
499 push(@errs, [ $i->get_name(), $e ]);
505 =head2 get_value(input-name)
506 Returns the value of the input with the given name.
510 my ($self, $name) = @_;
511 if ($self->{'selectinput'} && $self->{'selectinput'}->get_name() eq $name) {
512 return $self->{'selectinput'}->get_value();
514 foreach my $i ($self->list_inputs()) {
515 if ($i->get_name() eq $name) {
516 return $i->get_value();
523 Returns all inputs in all form sections
528 my @rv = @{$self->{'inputs'}};
529 push(@rv, $self->{'selectinput'}) if ($self->{'selectinput'});
533 =head2 set_searchmax(num, [message])
534 Sets the maximum number of table rows to display before showing a search form
538 my ($self, $searchmax, $searchmsg) = @_;
539 $self->{'searchmax'} = $searchmax;
540 $self->{'searchmsg'} = $searchmsg;
546 return $self->{'searchmax'};
552 return $self->{'searchmsg'};
555 =head2 add_link(link, message)
556 Adds a link to the table, for example for adding a new entry
560 my ($self, $link, $msg) = @_;
561 push(@{$self->{'links'}}, [ $link, $msg ]);
564 =head2 add_input(input)
565 Adds some input to be displayed at the bottom of the table
569 my ($self, $input) = @_;
570 push(@{$self->{'inputs'}}, $input);
571 $input->set_form($self->{'form'});
574 =head2 set_emptymsg(message)
575 Sets the message to display when the table is empty
579 my ($self, $emptymsg) = @_;
580 $self->{'emptymsg'} = $emptymsg;
583 =head2 set_heading(text)
584 Sets the heading text to appear above the table
588 my ($self, $heading) = @_;
589 $self->{'heading'} = $heading;
595 return $self->{'heading'};
598 =head2 set_pagesize(pagesize)
599 Sets the size of a page. Set to 0 to turn off completely.
603 my ($self, $pagesize) = @_;
604 $self->{'pagesize'} = $pagesize;
607 =head2 get_pagesize()
608 Returns the size of a page, or 0 if paging is turned off totally
613 return $self->{'pagesize'};
619 my $in = $self->{'form'} ? $self->{'form'}->{'in'} : undef;
620 my $name = $self->get_name();
621 if ($in && defined($in->{"ui_pagepos_".$name})) {
622 return ( $in->{"ui_pagepos_".$name} );
625 return ( $self->{'pagepos'} );
629 =head2 make_url(sortcol, sortdir, paging, page, [no-searchargs], [no-pagearg])
630 Returns a link to this table's page, with the defaults for the various state
631 fields overriden by the parameters (where defined)
635 my ($self, $newsortcol, $newsortdir, $newpaging, $newpagepos,
636 $nosearch, $nopage) = @_;
637 my ($sortcol, $sortdir) = $self->get_sortcolumn();
638 $sortcol = $newsortcol if (defined($newsortcol));
639 $sortdir = $newsortdir if (defined($newsortdir));
640 my $paging = $self->get_paging();
641 $paging = $newpaging if (defined($newpaging));
642 my $pagepos = $self->get_pagepos();
643 $pagepos = $newpagepos if (defined($newpagepos));
645 my $thisurl = $self->{'form'}->{'page'}->get_myurl();
646 my $name = $self->get_name();
647 $thisurl .= $thisurl =~ /\?/ ? "&" : "?";
648 my $in = $self->{'form'}->{'in'};
649 return "${thisurl}ui_sortcolumn_${name}=$sortcol".
650 "&ui_sortdir_${name}=$sortdir".
651 "&ui_paging_${name}=$paging".
652 ($nopage ? "" : "&ui_pagepos_${name}=$pagepos").
653 ($nosearch ? "" : "&ui_searchfor_${name}=".
654 &urlize($in->{"ui_searchfor_${name}"}).
655 "&ui_searchcol_${name}=".
656 &urlize($in->{"ui_searchcol_${name}"}));