[Bio] / FortyEight / Table.pm Repository:
ViewVC logotype

Annotation of /FortyEight/Table.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.6 - (view) (download) (as text)

1 : paczian 1.1 package Table;
2 :    
3 :     use strict;
4 :     use warnings;
5 :     use URI::Escape;
6 :    
7 :     1;
8 :    
9 : paczian 1.3 =over 3
10 :    
11 : parrello 1.6 =head2 NAME
12 : paczian 1.3
13 : parrello 1.6 #TITLE TablePm48
14 : paczian 1.3
15 : parrello 1.6 =head2 DESCRIPTION
16 : paczian 1.3
17 :     The Table Component implements an HTML/javascript table for displaying, filtering and browsing data.
18 :     It supports onClick events and popup menus for each cell. Data can also be grouped by a single column.
19 :     Like all WebApplicationComponents, configuration of columns to display is also supported.
20 :    
21 : parrello 1.6 =head2 ARGUMENTS
22 : paczian 1.3
23 :     Arguments are passed as a single hash. All data must be passed as references (i.e. reference to an array
24 :     instead of an array). A table is instanciated via the I<new> method.
25 :    
26 : parrello 1.6 =head3 Mandatory Arguments
27 :    
28 :     =over 4
29 : paczian 1.3
30 :     =item B<data>
31 :    
32 :     This must be an array of rows, each containing an array of cells. The data type of the cell contents is
33 :     detected for sorting issues. The first cell in a column will determine the data type. Three types of data
34 :     are differentiated:
35 :    
36 :     Integers and Floats
37 :     Dates in the format mm/dd/yyyy
38 :     Strings
39 :    
40 :     The cell data must not include quotation marks or linebreaks of any kind.
41 :    
42 :     =item B<columns>
43 :    
44 :     The columns are passed as an array of column headers, also no quotation marks or linebreaks are allowed.
45 :    
46 : parrello 1.6 =back
47 :    
48 :     =head3 Optional Arguments
49 :    
50 :     =over 4
51 : paczian 1.3
52 :     =item B<id>
53 :    
54 :     The identification of the table. Only neccessary if you have more than one table on a single page. Default is
55 :     'table'.
56 :    
57 :     =item B<image_base>
58 :    
59 :     The directory relative to the cgi directory where all images are stored. 'clear.gif' needs to be present in this
60 :     directory. Default is '../images'.
61 :    
62 :     =item B<offset>
63 :    
64 :     The array index of the first item in the list to be displayed. Default is 0.
65 :    
66 :     =item B<perpage>
67 :    
68 :     Number of items to be displayed on each page. Default is 20.
69 :    
70 :     =item B<show_perpage>
71 :    
72 :     Boolean to determine whether the user may change the number of items to be displayed on each
73 :     page. Default is false.
74 :    
75 :     =item B<show_topbrowse>
76 :    
77 :     Boolean to determine whether the browsing functions are displayed above the table. Default is
78 :     false.
79 :    
80 :     =item B<show_bottombrowse>
81 :    
82 :     Boolean to determine whether the browsing functions are displayed below the table. Default is
83 :     false.
84 :    
85 :     =item B<enable_grouping>
86 :    
87 :     Boolean to determine whether the grouping of a single column should be supported. Grouped
88 :     columns are collapsed if they contain consecutive identical values. They can be expanded
89 :     and collapsed by the user via a click on the symbol in the according cell. Default is false.
90 :    
91 :     =item B<group_col>
92 :    
93 :     Array index of the column to be the grouping column. Default is 0.
94 :    
95 :     =item B<sortable>
96 :    
97 :     Boolean to determine whether the user is allowed to sort the columns. Default is false.
98 :    
99 :     =item B<show_filter>
100 :    
101 :     Boolean to determine whether the user should be able to filter the columns. Default is false.
102 :    
103 :     =item B<available_operators>
104 :    
105 :     An array of operators available for filtering. Must be passed in the following format, which
106 :     is also the default:
107 :    
108 :     ( [ 'like', '&cong;' ],
109 :     [ 'unlike', '!&cong;' ],
110 :     [ 'equal', '=' ],
111 :     [ 'unequal', '!=' ],
112 :     [ 'less', '&lt;' ],
113 :     [ 'more', '&gt;' ] )
114 :    
115 :     Where the first entry is the operator and the second entry is the html to be displayed in the
116 :     select box.
117 :    
118 :     =item B<preselected_operators>
119 :    
120 :     A hash containing the column names as key and the operator to be pre-selected as a value. As
121 :     default, the first operator in the list is selected.
122 :    
123 :     =item B<operands>
124 :    
125 :     A hash containing the column names as key and the operand as values. If show_filter is true,
126 :     columns which are not a key of this hash will not receive a filter box.
127 :    
128 :     =item B<onclicks>
129 :    
130 :     If passed, this must be an array of rows, each containing an array of cells with the same
131 :     dimensions as the data array. The values should be urls that a click on the according cell
132 :     should link to.
133 :    
134 :     =item B<popup_menu>
135 :    
136 :     A hash containing the keys 'titles', 'infos' and 'menus'. Each of these have an array of arrays,
137 :     matching the dimensions of the data array, as a value. For usage of the popup menu, consult
138 :     the popup menu manual.
139 :    
140 :     =back
141 :    
142 :     =cut
143 :    
144 : paczian 1.1 # initialize the table
145 :     sub new {
146 :     my ($params) = @_;
147 :    
148 :     # initialize variables
149 :     my $table = "";
150 :    
151 :     # retrieve mandatory data from params
152 :     my @data;
153 :     if (defined($params->{data})) {
154 :     @data = @{$params->{data}};
155 :     } else {
156 :     return "No data passed to table creator!";
157 :     }
158 :     my @columns;
159 :     if (defined($params->{columns})) {
160 :     @columns = @{$params->{columns}};
161 :     } else {
162 :     return "No columns passed to table creator!";
163 :     }
164 :    
165 :     # check for optional parameters in params
166 :     my @available_operators = (
167 :     [ 'like', '&cong;' ],
168 :     [ 'unlike', '!&cong;' ],
169 :     [ 'equal', '=' ], [ 'unequal', '!=' ],
170 :     [ 'less', '&lt;' ], [ 'more', '&gt;' ]
171 :     );
172 :     if (defined($params->{available_operators})) {
173 :     @available_operators = @{$params->{available_operators}};
174 :     }
175 :    
176 :     my %preselected_operators = ();
177 :     if (defined($params->{preselected_operators})) {
178 :     %preselected_operators = %{$params->{preselected_operators}};
179 :     }
180 :    
181 :     my %operands = ();
182 :     if (defined($params->{operands})) {
183 :     %operands = %{$params->{operands}};
184 :     }
185 :     unless (scalar(keys(%operands))) {
186 :     if (defined($params->{show_filter})) {
187 :     foreach my $column (@columns) {
188 :     $operands{$column} = "";
189 :     }
190 :     }
191 :     }
192 :    
193 :     my $complex_filter = $params->{complex_filter} || 0;
194 :     my $offset = $params->{offset} || 0;
195 :     my $total = scalar(@data) || 0;
196 :     $params->{total} = $total;
197 :     my $perpage = $params->{perpage} || -1;
198 :     $params->{perpage} = $perpage;
199 :     my $id = $params->{id} || "table";
200 :     $id =~ s/_//g;
201 :     $params->{id} = $id;
202 :     my $img_path = $params->{image_base} || "./Html/";
203 :     $params->{img_path} = $img_path;
204 :     my $show_perpage = $params->{show_perpage} || 0;
205 :     my $show_topbrowse = $params->{show_topbrowse} || 0;
206 :     my $show_bottombrowse = $params->{show_bottombrowse} || 0;
207 : paczian 1.3 my $group_col = $params->{group_col} || 0;
208 :     my $enable_grouping = $params->{enable_grouping} || 0;
209 : paczian 1.1 my $sortable = $params->{sortable} || 0;
210 : paczian 1.2 my $sortcols = $params->{sortcols} || { 'all' => 1 };
211 : paczian 1.1 my $column_widths = $params->{column_widths};
212 :     unless (defined($column_widths)) {
213 :     foreach (@columns) {
214 :     push(@$column_widths, -1);
215 :     }
216 :     }
217 :     for (my $i=0; $i<scalar(@columns); $i++) {
218 :     unless (defined($column_widths->[$i])) {
219 :     $column_widths->[$i] = -1;
220 :     }
221 :     }
222 :    
223 :     # create an empty data array
224 :     my $empty_array;
225 :     my @good_data;
226 :     foreach my $row (@data) {
227 :     my $empty_row;
228 :     my $good_row;
229 :     foreach my $cell (@$row) {
230 :     if (defined($cell)) {
231 :     push(@$good_row, $cell);
232 :     } else {
233 :     push(@$good_row, "");
234 :     }
235 :     push(@$empty_row, "");
236 :     }
237 :     push(@$empty_array, $empty_row);
238 :     push(@good_data, $good_row);
239 :     }
240 :     @data = @good_data;
241 :     @good_data = undef;
242 :    
243 :     # create onclick event string
244 :     my $onclicks = "";
245 :     {
246 :     my $onclicks_array = $params->{onclicks} || $empty_array;
247 :     my $rows = [];
248 :     foreach my $row (@$onclicks_array) {
249 :     push(@$rows, join("^", @$row));
250 :     }
251 :     $onclicks = join("~", @$rows);
252 :     }
253 :    
254 :     # create popup menu strings if requested
255 :     my $titles = "";
256 :     my $infos = "";
257 :     my $menus = "";
258 :     my $highlights = "";
259 :     my ($titles_array, $infos_array, $menus_array, $highlights_array);
260 :     {
261 : paczian 1.5 no strict;
262 : paczian 1.1 $titles_array = $params->{popup_menu}->{titles} || $empty_array;
263 :     $infos_array = $params->{popup_menu}->{infos} || $empty_array;
264 :     $menus_array = $params->{popup_menu}->{menus} || $empty_array;
265 :     $highlights_array = $params->{highlights} || $empty_array;
266 :    
267 :     my $rows = [];
268 :     foreach my $row (@$titles_array) {
269 :     push(@$rows, join("^", @$row));
270 :     }
271 :     $titles = join("~", @$rows);
272 :    
273 :     $rows = [];
274 :     foreach my $row (@$infos_array) {
275 :     push(@$rows, join("^", @$row));
276 :     }
277 :     $infos = join("~", @$rows);
278 :    
279 :     $rows = [];
280 :     foreach my $row (@$menus_array) {
281 :     push(@$rows, join("^", @$row));
282 :     }
283 :     $menus = join("~", @$rows);
284 :    
285 :     $rows = [];
286 :     foreach my $row (@$highlights_array) {
287 :     push(@$rows, join("^", @$row));
288 :     }
289 :     $highlights = join("~", @$rows);
290 :    
291 :     # check for unwanted symbols
292 :     $infos =~ s/\n//g;
293 :     $infos =~ s/'/&quot;/g;
294 :     $infos =~ s/"/\\"/g;
295 :     $menus =~ s/\n//g;
296 :     $menus =~ s/'/&quot;/g;
297 :     $menus =~ s/"/\\"/g;
298 :     }
299 :    
300 :     # create stringified data
301 :     my $rows = [];
302 :     foreach my $row (@data) {
303 :     my $quoted_row;
304 :     foreach my $cell (@$row) {
305 :     $cell =~ s/\^/ /g;
306 :     $cell =~ s/\~/ /g;
307 :     push(@$quoted_row, $cell);
308 :     }
309 :     push(@$rows, join("^", @$quoted_row));
310 :     }
311 :     my $data_source = join("~", @$rows);
312 :    
313 :     $data_source =~ s/'/\@1/g;
314 :     $data_source =~ s/"/\@2/g;
315 :    
316 :     # insert hidden fields
317 :     $table .= "\n<input type='hidden' id='table_data_" . $id . "' value='" . $data_source . "'>\n";
318 :     $table .= "<input type='hidden' id='table_onclicks_" . $id . "' value='" . $onclicks . "'>\n";
319 :     $table .= "<input type='hidden' id='table_titles_" . $id . "' value='" . $titles . "'>\n";
320 :     $table .= "<input type='hidden' id='table_infos_" . $id . "' value='" . $infos . "'>\n";
321 :     $table .= "<input type='hidden' id='table_menus_" . $id . "' value='" . $menus . "'>\n";
322 :     $table .= "<input type='hidden' id='table_highlights_" . $id . "' value='" . $highlights . "'>\n";
323 :     $table .= "<input type='hidden' id='table_filtereddata_" . $id . "' value=''>\n";
324 :     $table .= "<input type='hidden' id='table_rows_" . $id . "' value='" . $total . "'>\n";
325 :     $table .= "<input type='hidden' id='table_cols_" . $id . "' value='" . scalar(@columns) . "'>\n";
326 :     $table .= "<input type='hidden' id='table_start_" . $id . "' value='0'>\n";
327 :     $table .= "<input type='hidden' id='table_sortdirection_" . $id . "' value='up'>\n";
328 :    
329 :     # check for title
330 :     if (defined($params->{title})) {
331 :     $table .= "<p class='table_title'>" . $params->{title} . "</p>\n";
332 :     }
333 :    
334 :     # check if table width was passed
335 :     my $table_width = "";
336 :     if (defined($params->{table_width})) {
337 :     $table_width = "width: " . $params->{table_width} . "px;";
338 :     }
339 :    
340 :     # check for display options - select entries per page
341 :     if ($show_perpage) {
342 :     $table .= "<table class='table_table' style='$table_width'>\n<tr><td align=center><span class='table_perpage'>display&nbsp;<input type='text' id='table_perpage_" . $id . "' name='table_perpage_" . $id . "' size='3' value='" . $perpage . "' onkeypress='check_submit_filter(event, \"" . $id . "\")'>&nbsp;items per page</span></td></tr>\n";
343 :     } elsif ($perpage == -1) {
344 :     $table .= "<input type='hidden' id='table_perpage_" . $id . "' name='table_perpage_" . $id . "' value='" . scalar(@data) . "' >\n<table class='table_table' style='$table_width'>\n";
345 :     } else {
346 :     $table .= "<input type='hidden' id='table_perpage_" . $id . "' name='table_perpage_" . $id . "' value='" . $perpage . "' >\n<table class='table_table' style='$table_width'>\n";
347 :     }
348 :    
349 :     # check for display options - display browsing element at the top
350 :     if ($show_topbrowse) {
351 :     $table .= get_browse($params);
352 :     }
353 :    
354 :     # start data table
355 :     $table .= "<tr><td><table id='table_" . $id . "' class='table_table' style='$table_width'>";
356 :    
357 : paczian 1.3 # check for display options - display column filters
358 :     if ($complex_filter) {
359 :     if (scalar(keys(%operands)) > 0) {
360 :     $table .= "<tr>";
361 :    
362 :     # check each value for an existing filter field
363 :     my $i = 0;
364 :     foreach my $col (@columns) {
365 :     if (exists($operands{$col})) {
366 :    
367 :     # check if there is a preselected value for this filter field
368 :     my $preselected_operand = "";
369 :     if (defined($operands{$col})) {
370 :     $preselected_operand = $operands{$col};
371 :     }
372 :    
373 :     # check for filter comparison operator
374 :     $table .= "<td><select name='" . $id . "_" . $col . "_operator' id='table_" . $id . "_operator_" . $i . "'>";
375 :    
376 :     foreach my $operator (@available_operators) {
377 :     my $preselected_operator = "";
378 :    
379 :     if (defined($preselected_operators{$col})) {
380 :     if ($preselected_operators{$col} eq $operator->[0]) {
381 :     $preselected_operator = " selected='selected'";
382 :     }
383 :     }
384 :    
385 :     $table .= "<option value='" . $operator->[0] . "'" . $preselected_operator . ">" . $operator->[1] . "</option>";
386 :     }
387 :     $table .= "</select><input type='text' name='" . $id . $col . "' class='filter_item' value='" . $preselected_operand . "' id='table_" . $id . "_operand_" . $i . "' onkeypress='check_submit_filter(event, \"" . $id . "\")'></td>";
388 :     } else {
389 :     $table .= "<td></td>";
390 :     }
391 :     $i++;
392 :     }
393 :    
394 :     $table .= "</tr>";
395 :     }
396 :     }
397 :    
398 : paczian 1.1 # write table header
399 :     $table .= "<tr>";
400 :     my $i = 1;
401 :    
402 :     # check column widths
403 :     my $colwidths;
404 :    
405 :     # iterate through columns
406 :     foreach my $col (@columns) {
407 :     if ($column_widths->[$i - 1] ne -1) {
408 :     push(@$colwidths, "width: " . $column_widths->[$i - 1] . "px;");
409 :     } else {
410 :     push(@$colwidths, "");
411 :     }
412 :    
413 :     # count up column name
414 :     my $name = $id . "_col_" . $i;
415 :    
416 :     # prepare html for sorting according to column
417 :     my $order_img = "<a href='javascript: table_sort(\"" . $id . "\", \"" . $i . "\", \"ASC\");' class='table_first_row' title='Click to sort'>";
418 :    
419 : paczian 1.3 # create config button
420 :     my $conf_button = qq~<span style="background-color: white; border: 1px solid black; font-size: 7pt; padding: 1px; cursor: pointer; width: 13px; vertical-align: top; text-align: left;" class="hideme" onclick="change_group('$id\_col_$i');" id="$id\_col_$i" name="conf">+/-</span>~;
421 : paczian 1.1
422 :     # check for simple filter
423 :     my $filter = "";
424 :     if (defined($operands{$col})) {
425 : paczian 1.3 $filter = "<br/><input type=hidden name='" . $id . "_" . $col . "_operator' value='like' id='table_" . $id . "_operator_" . $i . "'><input type='text' name='" . $id . $col . "' class='filter_item' value='' size=5 id='table_" . $id . "_operand_" . $i . "' onkeypress='check_submit_filter(event, \"" . $id . "\")' style='width: 100%;' title='Enter Search Text'>";
426 : paczian 1.1 }
427 :    
428 :     # check if colum header click should sort
429 : paczian 1.4 if (($sortable) && ($sortcols->{$col} || $sortcols->{'all'})) {
430 : paczian 1.3 $table .= "<td name='" . $name . "' class='table_first_row' style='" . $colwidths->[$i - 1] . "'>" . $conf_button . $order_img . $col . "&nbsp;<img src='./Html/up-arrow.gif'><img src='./Html/down-arrow.gif'></a>" . $filter . "</td>";
431 : paczian 1.1 } else {
432 : paczian 1.3 $table .= "<td name='" . $name . "' class='table_first_row' style='" . $colwidths->[$i - 1] . "'>" . $conf_button . $col . $filter . "</td>";
433 : paczian 1.1 }
434 :    
435 :     # increase column counter
436 :     $i ++;
437 :     }
438 :     $table .= "</tr>";
439 :    
440 :     # end data table
441 :     $table .= "</table></td></tr>";
442 :    
443 :     # check for display options - display browse element at the bottom
444 :     if ($show_bottombrowse) {
445 :     $table .= get_browse($params);
446 :     }
447 :    
448 :     # end surrounding table
449 :     $table .= "</table>";
450 :    
451 :     # include image for table initialization
452 :     $table .= "<img src='" . $img_path . "clear.gif' onload='initialize_table(\"" . $id . "\")'>";
453 :    
454 :     return $table;
455 :     }
456 :    
457 :     # get the browsing html
458 :     sub get_browse {
459 :     my ($params) = @_;
460 :    
461 :     my $browse = "";
462 :     my $left = "";
463 :     my $right = "";
464 :     my $offset = $params->{offset} || 1;
465 :     my $id = $params->{id} || "table";
466 :     my $perpage = $params->{perpage} || 0;
467 :     my $total = $params->{total} || 2;
468 :    
469 :     if ($offset > 0) {
470 :     $left .= "<a href='javascript: table_first(\"" . $id . "\");' name='table_first_" . $id . "'>&laquo;first</a>&nbsp;&nbsp;<a href='javascript: table_prev(\"" . $id . "\");' name='table_prev_" . $id . "'>&laquo;prev</a>";
471 :     }
472 :    
473 :     if (($offset + $perpage) < $total) {
474 :     $right = "<a href='javascript: table_next(\"" . $id . "\");' name='table_next_" . $id . "'>next&raquo;</a>&nbsp;&nbsp;<a href='javascript: table_last(\"" . $id . "\");' name='table_last_" . $id . "'>last&raquo;</a>";
475 :     }
476 :    
477 :     my $to = $offset + $perpage;
478 :     if (($offset + $perpage) > $total) {
479 :     $to = $total;
480 :     }
481 :    
482 : paczian 1.3 $browse .= "<tr><td><table class='table_browse'><tr><td align='left' width='20%'>" . $left . "</td><td align='center' width='60%'>displaying <input type='text' name='table_start_" . $id . "' class='disp' readonly=1 value='" . ($offset + 1) . "'> - <input type='text' name='table_stop_" . $id . "' class='disp' readonly=1 value='" . $to . "'> of <input type='text' name='table_total_" . $id . "' class='disp' readonly=1 value='" . $total . "'></td><td align='right' width='20%'>" . $right . "</td></tr></table></td></tr>";
483 : paczian 1.1
484 :     return $browse;
485 :     }

MCS Webmaster
ViewVC Help
Powered by ViewVC 1.0.3