pagination patch from UNTD - limit the number of page links displayed and have a...
[freeside.git] / httemplate / search / elements / search.html
1 %  # options example...  
2 %  # (everything not commented required is optional)
3 %  #
4 %  # # basic options, required
5 %  # 'title'         => 'Page title',
6 %  # 
7 %  # 'name_singular' => 'item',  #singular name for the records returned
8 %  #    #OR#                     # (preferred, will be pluralized automatically)
9 %  # 'name'          => 'items', #plural name for the records returned
10 %  #                             # (deprecated, will be singularlized
11 %  #                             #  simplisticly)
12 %  #
13 %  # # some HTML callbacks...
14 %  # 'menubar'          => '', #menubar arrayref
15 %  # 'html_init'        => '', #after the header/menubar and before the pager
16 %  # 'html_foot'        => '', #at the bottom
17 %  # 'html_posttotal'   => '', #at the bottom
18 %  #                           # (these three can be strings or coderefs)
19 %  # 
20 %  #
21 %  # #literal SQL query string or qsearch hashref, required
22 %  # 'query'       => {
23 %  #                    'table'     => 'tablename',
24 %  #                    #everything else is optional...
25 %  #                    'hashref'   => { 'field' => 'value',
26 %  #                                     'field' => { 'op'    => '<',
27 %  #                                                  'value' => '54',
28 %  #                                                },
29 %  #                                   },
30 %  #                    'select'    => '*',
31 %  #                    'addl_from' => '', #'LEFT JOIN othertable USING ( key )',
32 %  #                    'extra_sql' => '', #'AND otherstuff', #'WHERE onlystuff',
33 %  #                    
34 %  #
35 %  #                  },
36 %  #                  # "select * from tablename";
37 %  #
38 %  # #required unless 'query' is an SQL query string (shouldn't be...)
39 %  # 'count_query' => 'SELECT COUNT(*) FROM tablename',
40 %  #
41 %  # 'count_addl' => [], #additional count fields listref of sprintf strings
42 %  #                     # [ $money_char.'%.2f total paid', ],
43 %  #
44 %  # #listref of column labels, <TH>
45 %  # #required unless 'query' is an SQL query string
46 %  # # (if not specified the database column names will be used)
47 %  # 'header'      => [ '#', 'Item' ],
48 %  #
49 %  # 'disable_download' => '', # set true to hide the CSV/Excel download links
50 %  # 'disable_nonefound' => '', # set true to disable the "No matching Xs found"
51 %  #                            # message
52 %  #
53 %  # #listref - each item is a literal column name (or method) or coderef
54 %  # #if not specified all columns will be shown
55 %  # 'fields'      => [
56 %  #                    'column',
57 %  #                    sub { my $row = shift; $row->column; },
58 %  #                  ],
59 %  #
60 %  # #listref of column footers
61 %  # 'footer'      => [],
62 %  # 
63 %  # #listref - each item is the empty string, or a listref of ...
64 %  # 'links'       =>
65 %  #
66 %  #
67 %  # 'align'       => 'lrc.', #one letter for each column, left/right/center/none
68 %  #                          # can also pass a listref with full values:
69 %  #                          # [ 'left', 'right', 'center', '' ]
70 %  #
71 %  # #listrefs...
72 %  # #currently only HTML, maybe eventually Excel too
73 %  # 'color'       => [],
74 %  # 'size'        => [],
75 %  # 'style'       => [],
76 %  # 
77 %  # #redirect if there's only one item...
78 %  # # listref of URL base and column name (or method)
79 %  # # or a coderef that returns the same
80 %  # 'redirect' =>
81 %
82 %  my $DEBUG = 0;
83 %
84 %  my(%opt) = @_;
85 %  #warn join(' / ', map { "$_ => $opt{$_}" } keys %opt ). "\n";
86 %
87 %  my %align = (
88 %    'l' => 'left',
89 %    'r' => 'right',
90 %    'c' => 'center',
91 %    ' ' => '',
92 %    '.' => '',
93 %  );
94 %  $opt{align} = [ map $align{$_}, split(//, $opt{align}) ],
95 %    unless !$opt{align} || ref($opt{align});
96 %
97 %  my $type = '';
98 %  my $limit = '';
99 %  my($confmax, $maxrecords, $total, $offset, $count_arrayref);
100 %
101 %  if ( $cgi->param('_type') =~ /^(csv|\w*\.xls)$/ ) {
102 %  
103 %    $type = $1;
104 %
105 %  } else { #setup some pagination things if we're in html mode
106 %
107 %    unless (exists($opt{'count_query'}) && length($opt{'count_query'})) {
108 %      ( $opt{'count_query'} = $opt{'query'} ) =~
109 %        s/^\s*SELECT\s*(.*?)\s+FROM\s/SELECT COUNT(*) FROM /i; #silly vim:/
110 %    }
111 %
112 %    my $conf = new FS::Conf;
113 %    $confmax = $conf->config('maxsearchrecordsperpage');
114 %    if ( $cgi->param('maxrecords') =~ /^(\d+)$/ ) {
115 %      $maxrecords = $1;
116 %    } else {
117 %      $maxrecords ||= $confmax;
118 %    }
119 %
120 %    $limit = $maxrecords ? "LIMIT $maxrecords" : '';
121 %
122 %    $offset = $cgi->param('offset') || 0;
123 %    $limit .= " OFFSET $offset" if $offset;
124 %
125 %    my $count_sth = dbh->prepare($opt{'count_query'})
126 %      or die "Error preparing $opt{'count_query'}: ". dbh->errstr;
127 %    $count_sth->execute
128 %      or die "Error executing $opt{'count_query'}: ". $count_sth->errstr;
129 %    $count_arrayref = $count_sth->fetchrow_arrayref;
130 %    $total = $count_arrayref->[0];
131 %
132 %  }
133 %
134 %  # run the query
135 %
136 %  my $header = $opt{'header'};
137 %  my $rows;
138 %  if ( ref($opt{'query'}) ) {
139 %
140 %    #eval "use FS::$opt{'query'};";
141 %    $rows = [ qsearch(
142 %      $opt{'query'}->{'table'}, 
143 %      $opt{'query'}->{'hashref'} || {}, 
144 %      $opt{'query'}->{'select'},
145 %      $opt{'query'}->{'extra_sql'}. " $limit",
146 %      '',
147 %      (exists($opt{'query'}->{'addl_from'}) ? $opt{'query'}->{'addl_from'} : '')
148 %    ) ];
149 %
150 %  } else {
151 %
152 %    my $sth = dbh->prepare("$opt{'query'} $limit")
153 %      or die "Error preparing $opt{'query'}: ". dbh->errstr;
154 %    $sth->execute
155 %      or die "Error executing $opt{'query'}: ". $sth->errstr;
156 %
157 %    #can get # of rows without fetching them all?
158 %    $rows = $sth->fetchall_arrayref;
159 %
160 %    $header ||= $sth->{NAME};
161 %
162 %  }
163 %
164 %  warn scalar(@$rows). ' rows returned from '.
165 %       ( ref($opt{'query'}) ? 'qsearch query' : 'literal SQL query' )
166 %    if $DEBUG || $opt{'debug'};
167 %
168 %  # display the results - csv, xls or html
169 %
170 %  if ( $type eq 'csv' ) {
171 %
172 %    #http_header('Content-Type' => 'text/comma-separated-values' ); #IE chokes
173 %    http_header('Content-Type' => 'text/plain' );
174 %
175 %    my $csv = new Text::CSV_XS { 'always_quote' => 1,
176 %                                 'eol'          => "\n", #"\015\012", #"\012"
177 %                               };
178 %
179 %    $csv->combine(@$header); #or die $csv->status;
180 %    
181 <% $csv->string %>
182 %
183 %
184 %    foreach my $row ( @$rows ) {
185 %
186 %      if ( $opt{'fields'} ) {
187 %
188 %        my @line = ();
189 %
190 %        foreach my $field ( @{$opt{'fields'}} ) {
191 %          if ( ref($field) eq 'CODE' ) {
192 %            push @line, map {
193 %                              ref($_) eq 'ARRAY'
194 %                                ? '(N/A)' #unimplemented
195 %                                : $_;
196 %                            }
197 %                            &{$field}($row);
198 %          } else {
199 %            push @line, $row->$field();
200 %          }
201 %        }
202 %
203 %        $csv->combine(@line); #or die $csv->status;
204 %
205 %      } else {
206 %        $csv->combine(@$row); #or die $csv->status;
207 %      }
208 %
209 %      
210 <% $csv->string %>
211 %
212 %
213 %    }
214 %
215 %  #} elsif ( $type eq 'excel' ) {
216 %  } elsif ( $type =~ /\.xls$/ ) {
217 %
218 %    #http_header('Content-Type' => 'application/excel' ); #eww
219 %    http_header('Content-Type' => 'application/vnd.ms-excel' );
220 %    #http_header('Content-Type' => 'application/msexcel' ); #alas
221 %
222 %    my $data = '';
223 %    my $XLS = new IO::Scalar \$data;
224 %    my $workbook = Spreadsheet::WriteExcel->new($XLS)
225 %      or die "Error opening .xls file: $!";
226 %
227 %    my $worksheet = $workbook->add_worksheet(substr($opt{'title'},0,31));
228 %
229 %    my($r,$c) = (0,0);
230 %
231 %    $worksheet->write($r, $c++, $_) foreach @$header;
232 %
233 %    foreach my $row ( @$rows ) {
234 %      $r++;
235 %      $c = 0;
236 %
237 %      if ( $opt{'fields'} ) {
238 %
239 %        #my $links = $opt{'links'} ? [ @{$opt{'links'}} ] : '';
240 %        #my $aligns = $opt{'align'} ? [ @{$opt{'align'}} ] : '';
241 %
242 %        foreach my $field ( @{$opt{'fields'}} ) {
243 %          #my $align = $aligns ? shift @$aligns : '';
244 %          #$align = " ALIGN=$align" if $align;
245 %          #my $a = '';
246 %          #if ( $links ) {
247 %          #  my $link = shift @$links;
248 %          #  $link = &{$link}($row) if ref($link) eq 'CODE';
249 %          #  if ( $link ) {
250 %          #    my( $url, $method ) = @{$link};
251 %          #    if ( ref($method) eq 'CODE' ) {
252 %          #      $a = $url. &{$method}($row);
253 %          #    } else {
254 %          #      $a = $url. $row->$method();
255 %          #    }
256 %          #    $a = qq(<A HREF="$a">);
257 %          #  }
258 %          #}
259 %          if ( ref($field) eq 'CODE' ) {
260 %            foreach my $value ( &{$field}($row) ) {
261 %              if ( ref($value) eq 'ARRAY' ) { 
262 %                $worksheet->write($r, $c++, '(N/A)' ); #unimplemented
263 %              } else {
264 %                $worksheet->write($r, $c++, $value );
265 %              }
266 %            }
267 %          } else {
268 %            $worksheet->write($r, $c++, $row->$field() );
269 %          }
270 %        }
271 %
272 %      } else {
273 %        $worksheet->write($r, $c++, $_) foreach @$row;
274 %      }
275 %
276 %    }
277 %
278 %    $workbook->close();# or die "Error creating .xls file: $!";
279 %
280 %    http_header('Content-Length' => length($data) );
281 %    
282 <% $data %>
283 %
284 %
285 %  } else { # regular HTML
286 %
287 %    if ( exists($opt{'redirect'}) && scalar(@$rows) == 1 && $total == 1 ) {
288 %      my $redirect = $opt{'redirect'};
289 %      $redirect = &{$redirect}($rows->[0]) if ref($redirect) eq 'CODE';
290 %      my( $url, $method ) = @$redirect;
291 %      redirect( $url. $rows->[0]->$method() );
292 %    } else {
293 %      if ( $opt{'name_singular'} ) {
294 %        $opt{'name'} = PL($opt{'name_singular'});
295 %      }
296 %      ( my $xlsname = $opt{'name'} ) =~ s/\W//g;
297 %      if ( $total == 1 ) {
298 %        if ( $opt{'name_singular'} ) {
299 %          $opt{'name'} = $opt{'name_singular'}
300 %        } else {
301 %          #$opt{'name'} =~ s/s$// if $total == 1;
302 %          $opt{'name'} =~ s/((s)e)?s$/$2/ if $total == 1;
303 %        }
304 %      }                                               
305 %
306 %      my @menubar = ();
307 %      if ( $opt{'menubar'} ) {
308 %        @menubar = @{ $opt{'menubar'} };
309 %      } else {
310 %        @menubar = ( 'Main menu' => $p );
311 %      }
312 %
313 %
314 %  
315
316   <% include( '/elements/header.html', $opt{'title'},
317                  include( '/elements/menubar.html', @menubar )
318              )
319   %>
320   <% defined($opt{'html_init'}) 
321         ? ( ref($opt{'html_init'})
322               ? &{$opt{'html_init'}}()
323               : $opt{'html_init'}
324           )
325         : ''
326   %>
327 %  
328 % unless ( $total ) { 
329 % unless ( $opt{'disable_nonefound'} ) { 
330
331       No matching <% $opt{'name'} %> found.<BR>
332 % } 
333 % } else { 
334
335     <TABLE>
336       <TR>
337
338         <TD VALIGN="bottom">
339
340           <FORM>
341
342           <% $total %> total <% $opt{'name'} %>
343
344 %         if ( $confmax && $total > $confmax ) {
345 %           $cgi->delete('maxrecords');
346 %           $cgi->param('_dummy', 1);
347
348 %#           ( show <SELECT NAME="maxrecords" onChange="this.form.submit();">
349             ( show <SELECT NAME="maxrecords" onChange="window.location = '<% $cgi->self_url %>;maxrecords=' + this.options[this.selectedIndex].value;">
350
351 %             foreach my $max ( map { $_ * $confmax } qw( 1 5 10 25 ) ) {
352                 <OPTION VALUE="<% $max %>" <% ( $maxrecords == $max ) ? 'SELECTED' : '' %>><% $max %></OPTION>
353 %             }
354
355             </SELECT> per page )
356
357 %           $cgi->param('maxrecords', $maxrecords);
358 %         }
359
360
361           <% defined($opt{'html_posttotal'}) 
362                 ? ( ref($opt{'html_posttotal'})
363                       ? &{$opt{'html_posttotal'}}()
364                       : $opt{'html_posttotal'}
365                   )
366                 : ''
367           %>
368           <BR>
369
370 % if ( $opt{'count_addl'} ) { 
371 %   my $n=0; foreach my $count ( @{$opt{'count_addl'}} ) { 
372
373       <% sprintf( $count, $count_arrayref->[++$n] ) %><BR>
374
375 %   } 
376 % } 
377           </FORM>
378
379         </TD>
380
381 % unless ( $opt{'disable_download'} ) { 
382
383           <TD ALIGN="right">
384 % $cgi->param('_type', "$xlsname.xls" ); 
385
386             Download full results<BR>
387             as <A HREF="<% $cgi->self_url %>">Excel spreadsheet</A><BR>
388 % $cgi->param('_type', 'csv'); 
389
390             as <A HREF="<% $cgi->self_url %>">CSV file</A>
391           </TD>
392 % $cgi->param('_type', "html" ); 
393 % } 
394
395       </TR>
396       <TR>
397         <TD COLSPAN=2>
398
399             <% my $pager = include ( '/elements/pager.html',
400                            'offset'     => $offset,
401                            'num_rows'   => scalar(@$rows),
402                            'total'      => $total,
403                            'maxrecords' => $maxrecords,
404             ) %>
405
406             <% include('/elements/table-grid.html') %>
407
408               <TR>
409
410 %                 foreach my $header ( @$header ) { 
411
412                    <TH CLASS="grid" BGCOLOR="#cccccc"><% $header %></TH>
413 % } 
414
415               </TR>
416 % my $bgcolor1 = '#eeeeee';
417 %                 my $bgcolor2 = '#ffffff';
418 %                 my $bgcolor;
419 %                 foreach my $row ( @$rows ) {
420 %                   if ( $bgcolor eq $bgcolor1 ) {
421 %                     $bgcolor = $bgcolor2;
422 %                   } else {
423 %                     $bgcolor = $bgcolor1;
424 %                   }
425 %              
426
427                    <TR>
428 % if ( $opt{'fields'} ) {
429 %
430 %                        my $links  = $opt{'links'} ? [ @{$opt{'links'}} ] : '';
431 %                        my $aligns = $opt{'align'} ? [ @{$opt{'align'}} ] : '';
432 %                        my $colors = $opt{'color'} ? [ @{$opt{'color'}} ] : [];
433 %                        my $sizes  = $opt{'size'}  ? [ @{$opt{'size'}}  ] : [];
434 %                        my $styles = $opt{'style'} ? [ @{$opt{'style'}} ] : [];
435 %
436 %                        foreach my $field (
437 %
438 %                          map {
439 %                                if ( ref($_) eq 'ARRAY' ) {
440 %
441 %                                  my $tableref = $_;
442 %
443 %                                  '<TABLE CLASS="inv" CELLSPACING=0 CELLPADDING=0>'.
444 %
445 %                                  join('', map {
446 %
447 %                                    my $rowref = $_;
448 %
449 %                                    '<tr>'.
450 %
451 %                                    join('', map {
452 %
453 %                                      my $element = $_;
454 %
455 %                                      '<TD'.
456 %                                      ( $element->{'align'}
457 %                                          ? ' ALIGN="'. $element->{'align'}. '"'
458 %                                          : ''
459 %                                      ). '>'.
460 %                                      ( $element->{'link'}
461 %                                          ? '<A HREF="'. $element->{'link'}.'">'
462 %                                          : ''
463 %                                      ).
464 %                                      $element->{'data'}.
465 %                                      ( $element->{'link'}
466 %                                          ? '</A>'
467 %                                          : ''
468 %                                      ).
469 %                                      '</td>';
470 %
471 %                                    } @$rowref ).
472 %
473 %                                    '</tr>';
474 %                                  } @$tableref ).
475 %
476 %                                  '</table>';
477 %
478 %                                } else {
479 %                                  $_;
480 %                                }
481 %                              }
482 %
483 %                          map {
484 %                                if ( ref($_) eq 'CODE' ) {
485 %                                  &{$_}($row);
486 %                                } else {
487 %                                  $row->$_();
488 %                                }
489 %                              }
490 %                          @{$opt{'fields'}}
491 %
492 %                        ) {
493 %
494 %                          my $class = ( $field =~ /^<TABLE/i ) ? 'inv' : 'grid';
495 %
496 %                          my $align = $aligns ? shift @$aligns : '';
497 %                          $align = " ALIGN=$align" if $align;
498 %
499 %                          my $a = '';
500 %                          if ( $links ) {
501 %                            my $link = shift @$links;
502 %                            $link = &{$link}($row) if ref($link) eq 'CODE';
503 %                            if ( $link ) {
504 %                              my( $url, $method ) = @{$link};
505 %                              if ( ref($method) eq 'CODE' ) {
506 %                                $a = $url. &{$method}($row);
507 %                              } else {
508 %                                $a = $url. $row->$method();
509 %                              }
510 %                              $a = qq(<A HREF="$a">);
511 %                            }
512 %                          }
513 %
514 %                          my $font = '';
515 %                          my $color = shift @$colors;
516 %                          $color = &{$color}($row) if ref($color) eq 'CODE';
517 %                          my $size = shift @$sizes;
518 %                          $size = &{$size}($row) if ref($size) eq 'CODE';
519 %                          if ( $color || $size ) {
520 %                            $font = '<FONT '.
521 %                                    ( $color ? "COLOR=#$color "   : '' ).
522 %                                    ( $size  ? qq(SIZE="$size" )  : '' ).
523 %                                    '>';
524 %                          }
525 %
526 %                          my($s, $es) = ( '', '' );
527 %                          my $style = shift @$styles;
528 %                          $style = &{$style}($row) if ref($style) eq 'CODE';
529 %                          if ( $style ) {
530 %                            $s = join( '', map "<$_>", split('', $style) );
531 %                            $es = join( '', map "</$_>", split('', $style) );
532 %                          }
533 %
534 %                       
535
536                        <TD CLASS="<% $class %>" BGCOLOR="<% $bgcolor %>"<% $align %>><% $font %><% $a %><% $s %><% $field %><% $es %><% $a ? '</A>' : '' %><% $font ? '</FONT>' : '' %></TD>
537 % } 
538 % } else { 
539 % foreach ( @$row ) { 
540
541                           <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $_ %></TD>
542 % } 
543 % } 
544
545                    </TR>
546 % } 
547 % if ( $opt{'footer'} ) { 
548
549                 <TR>
550 % foreach my $footer ( @{ $opt{'footer'} } ) { 
551
552                      <TD CLASS="grid" BGCOLOR="#dddddd" STYLE="border-top: dashed 1px black;"><i><% $footer %></i></TH>
553 % } 
554
555                 </TR>
556 % } 
557
558             
559             </TABLE>
560             <% $pager %>
561   
562           </TD>
563         </TR>
564       </TABLE>
565 % } 
566
567   <% defined($opt{'html_foot'}) 
568         ? ( ref($opt{'html_foot'})
569               ? &{$opt{'html_foot'}}()
570               : $opt{'html_foot'}
571           )
572         : ''
573   %>
574   <% include( '/elements/footer.html' ) %>
575 % } 
576 % }