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