5 include( '/elements/customer-table.html',
12 'header' => [ '#', 'Item' ],
15 sub { my ($row,$param) = @_;
16 $param->{"column$row"};
24 'name_singular' => 'customer', #label
25 'custnum_update_callback' => 'name_of_js_callback' #passed a rownum
28 'types' => ['immutable', ''], # immutable or ''/text
29 'align' => [ 'c', 'l', 'r', '' ],
30 'size' => [], # sizes ignored for immutable
32 'footer' => ['string', '_TOTAL'], # strings or the special
34 'footer_align' => [ 'c', 'l', 'r', '' ],
36 'param' => { column0 => 1 }, # preset column of row 0 to 1
40 Some incomplete notes for javascript programmers:
42 On page load, existing rows are initialized by passing values to addRow
43 based on existing cgi values. An empty row (marked with the 'emptyrow'
44 attribute) is created by invoking addRow without values. After that,
45 to keep the non-empty row count (totalrows) accurate, use newEmptyRow to
46 create the next row. There should only be one empty row at a time.
49 total_el - element for displaying total number of rows
50 totalrows - total number of non-empty rows
51 rownum - really more of a "next row" value, used by addRow
52 allrows - array of tr elements, one for each row
54 Don't confuse the global rownum with the element attribute rownum
55 that is set as a reference point on some of the elements generated
56 by this script. They have different values.
58 Some of the functions:
59 updateTotalRow() - updates total_el based on value of totalrows
60 addDeleteButton(searchrow) - adds delete button to searchrow
61 newEmptyRow() - replaces old empty row
62 deleteRow() - removes the row specified by this.rownum
63 addRow(values) - adds a new row (marked as empty if values aren't specified)
65 This mason element is currently only used by misc/batch-cust_pay.html,
66 and probably should be cleaned up more before being used by anything else.
70 <SCRIPT TYPE="text/javascript">
72 var num_open_invoices = new Array;
74 function clearhint_invnum() {
76 if ( this.value == 'Not found' || this.value == 'Multiple' ) {
78 this.style.color = '#000000';
83 function clearhint_custnum() {
85 if ( this.value == 'Not found' || this.value == 'Multiple' ) {
87 this.style.color = '#000000';
92 function clearhint_customer() {
94 this.style.color = '#000000';
96 if ( this.value == '(last name or company)' || this.value == 'Not found' )
101 function update_customer(searchrow, customerArray) {
103 var display_custnum_obj = document.getElementById('display_custnum'+searchrow);
104 var custnum_obj = document.getElementById('custnum'+searchrow);
105 var customer = document.getElementById('customer'+searchrow);
106 var customer_select = document.getElementById('cust_select'+searchrow);
108 var postPaymentBtn = document.getElementById('btnsubmit');
109 postPaymentBtn.disabled = false;
111 display_custnum_obj.disabled = false;
112 display_custnum_obj.style.backgroundColor = '#ffffff';
113 customer.disabled = false;
114 customer.style.backgroundColor = '#ffffff';
116 if ( customerArray.length == 0 ) {
118 custnum_obj.value = '';
119 display_custnum_obj.value = 'Not found';
120 customer.value = 'Not found';
121 display_custnum_obj.style.color = '#ff0000';
122 customer.style.color = '#ff0000';
124 customer.style.display = '';
125 customer_select.style.display = 'none';
128 } else if ( customerArray.length >= 6 ) {
130 custnum_obj.value = customerArray[0];
131 display_custnum_obj.value = customerArray[6];
132 display_custnum_obj.style.color = '#000000';
133 customer.value = customerArray[1];
135 update_balance_text(searchrow, customerArray[2]);
136 update_status_text( searchrow, customerArray[3]);
137 update_status_color(searchrow, '#'+customerArray[4]);
138 update_num_open(searchrow, customerArray[5]);
140 customer.style.display = '';
141 customer_select.style.display = 'none';
146 function search_invnum() {
148 this.style.color = '#000000'
150 var invnum_obj = this;
151 var searchrow = this.getAttribute('rownum');
152 var invnum = this.value;
154 if ( invnum == 'searching...' || invnum == 'Not found' || invnum == '' )
157 if ( this.getAttribute('magic') == 'nosearch' ) {
158 this.setAttribute('magic', '');
162 if ( document.getElementById('row'+searchrow).emptyrow ) {
163 newEmptyRow(searchrow);
165 var customer = document.getElementById('customer'+searchrow);
166 customer.value = 'searching...';
167 customer.disabled = true;
168 customer.style.color = '#000000';
169 customer.style.backgroundColor = '#dddddd';
171 var customer_select = document.getElementById('cust_select'+searchrow);
173 customer.style.display = '';
174 customer_select.style.display = 'none';
176 update_balance_text(searchrow, '');
177 update_status_text(searchrow, '');
178 update_status_color(searchrow, '#000000');
179 update_num_open(searchrow, 0);
181 function search_invnum_update(customers) {
183 var customerArray = eval('(' + customers + ')');
184 update_customer(searchrow, customerArray);
186 % if ( $opt{invnum_update_callback} ) {
187 <% $opt{invnum_update_callback} %>(searchrow)
192 invnum_search( invnum, search_invnum_update );
196 function search_custnum() {
198 this.style.color = '#000000'
200 var display_custnum_obj = this;
201 var searchrow = this.getAttribute('rownum');
202 var custnum_obj = document.getElementById('custnum'+searchrow);
203 var display_custnum = this.value;
205 if ( display_custnum == 'searching...' || display_custnum == 'Not found' || display_custnum == '' )
208 if ( this.getAttribute('magic') == 'nosearch' ) {
209 this.setAttribute('magic', '');
213 if ( document.getElementById('row'+searchrow).emptyrow ) {
214 newEmptyRow(searchrow);
217 var customer_obj = document.getElementById('customer'+searchrow);
218 customer_obj.value = 'searching...';
219 customer_obj.disabled = true;
220 customer_obj.style.color = '#000000';
221 customer_obj.style.backgroundColor = '#dddddd';
223 var customer_select = document.getElementById('cust_select'+searchrow);
225 customer_obj.style.display = '';
226 customer_select.style.display = 'none';
228 var invnum = document.getElementById('invnum'+searchrow);
231 update_balance_text(searchrow, '');
232 update_status_text( searchrow, '');
233 update_status_color(searchrow, '#000000');
234 update_num_open(searchrow, 0);
236 function search_custnum_update(customers) {
238 var customerArrayArray = eval('(' + customers + ')') || [];
240 if ( customerArrayArray.length == 0 ) {
242 update_customer(searchrow, []);
244 } else if ( customerArrayArray.length == 1 ) {
246 update_customer(searchrow, customerArrayArray[0]);
247 % if ( $opt{custnum_update_callback} ) {
248 <% $opt{custnum_update_callback} %>(searchrow)
253 custnum_obj.value = 'Multiple'; // or something
254 custnum_obj.style.color = '#ff0000';
256 //blank the current list
257 customer_select.options.length = 0;
259 opt(customer_select, '', 'Multiple customers match "' + custnum + '" - select one', '#ff0000');
260 //add the multiple customers
261 for ( var s = 0; s < customerArrayArray.length; s++ ) {
263 JSON.stringify(customerArrayArray[s]),
264 customerArrayArray[s][1],
268 opt(customer_select, 'cancel', '(Edit search string)', '#000000');
270 customer_obj.style.display = 'none';
272 customer_select.style.display = '';
278 custnum_search(display_custnum, search_custnum_update );
282 function search_customer() {
284 var customer_obj = this;
285 var searchrow = this.getAttribute('rownum');
286 var customer = this.value;
288 if ( customer == 'searching...' || customer == 'Not found' || customer == '' )
291 if ( this.getAttribute('magic') == 'nosearch' ) {
292 this.setAttribute('magic', '');
296 if ( document.getElementById('row'+searchrow).emptyrow ) {
297 newEmptyRow(searchrow);
300 var invnum = document.getElementById('invnum'+searchrow);
303 var custnum_obj = document.getElementById('display_custnum'+searchrow);
304 custnum_obj.value = 'searching...';
305 custnum_obj.disabled = true;
306 custnum_obj.style.color = '#000000';
307 custnum_obj.style.backgroundColor = '#dddddd';
309 var customer_select = document.getElementById('cust_select'+searchrow);
311 function search_customer_update(customers) {
313 var customerArrayArray = eval('(' + customers + ')') || [ [] ];
315 custnum_obj.disabled = false;
316 custnum_obj.style.backgroundColor = '#ffffff';
318 if ( customerArrayArray.length == 0 ) {
320 update_customer(searchrow, []);
322 } else if ( customerArrayArray.length == 1 ) {
324 update_customer(searchrow, customerArrayArray[0]);
325 % if ( $opt{custnum_update_callback} ) {
326 <% $opt{custnum_update_callback} %>(searchrow)
331 custnum_obj.value = 'Multiple'; // or something
332 custnum_obj.style.color = '#ff0000';
334 var postPaymentBtn = document.getElementById('btnsubmit');
335 postPaymentBtn.disabled = true;
337 //blank the current list
338 customer_select.options.length = 0;
340 opt(customer_select, '', 'Multiple customers match "' + customer + '" - select one', '#ff0000');
341 //add the multiple customers
342 for ( var s = 0; s < customerArrayArray.length; s++ ) {
344 JSON.stringify(customerArrayArray[s]),
345 customerArrayArray[s][1],
349 opt(customer_select, 'cancel', '(Edit search string)', '#000000');
351 customer_obj.style.display = 'none';
353 customer_select.style.display = '';
359 smart_search( customer, search_customer_update );
363 function select_customer() {
365 var custnum_balance_status = this.options[this.selectedIndex].value;
366 var customer = this.options[this.selectedIndex].text;
368 var searchrow = this.getAttribute('rownum');
369 var display_custnum_obj = document.getElementById('display_custnum'+searchrow);
370 var custnum_obj = document.getElementById('custnum'+searchrow);
371 var customer_obj = document.getElementById('customer'+searchrow);
372 var balance_obj = document.getElementById('balance'+searchrow);
373 var status_obj = document.getElementById('status'+searchrow);
375 if ( custnum_balance_status == '' ) {
377 } else if ( custnum_balance_status == 'cancel' ) {
379 display_custnum_obj.value = '';
380 custnum_obj.value = '';
381 custnum_obj.style.color = '#000000';
383 this.style.display = 'none';
384 customer_obj.style.display = '';
385 customer_obj.focus();
389 update_customer(searchrow, JSON.parse(custnum_balance_status));
391 % if ( $opt{custnum_update_callback} ) {
392 <% $opt{custnum_update_callback} %>(searchrow)
399 function opt(what,value,text,color) {
400 var optionName = new Option(text, value, false, false);
401 optionName.style.color = color;
402 var length = what.length;
403 what.options[length] = optionName;
406 function update_status_text(rownum, newval) {
407 document.getElementById('status'+rownum).value = newval;
408 document.getElementById('status'+rownum+'_text').innerHTML = newval;
411 function update_status_color(rownum, newval) {
412 document.getElementById('statuscolor'+rownum).value = newval;
413 document.getElementById('status'+rownum+'_text').style.color = newval;
416 function update_balance_text(rownum, newval) {
417 document.getElementById('balance'+rownum).value = newval;
418 document.getElementById('balance'+rownum+'_text').innerHTML = newval;
421 function update_num_open(rownum, newval) {
422 document.getElementById('num_open'+rownum).value = newval;
423 num_open_invoices[rownum] = newval;
426 // updates display of total rows based on value of totalrows
427 function updateTotalRow () {
428 if ( totalrows == 1 ) {
432 + ' <% $opt{name_singular} || 'customer' %>';
437 + ' <% PL($opt{name_singular} || 'customer') %>';
441 var total_el, rownum, totalrows, allrows;
443 function addDeleteButton (searchrow) {
444 var td_delete = document.getElementById('delete'+searchrow);
445 var button_delete = document.createElement('INPUT');
446 button_delete.setAttribute('rownum', searchrow);
447 button_delete.setAttribute('type', 'button');
448 button_delete.setAttribute('value', 'X');
449 button_delete.onclick = deleteRow;
450 button_delete.style.color = '#ff0000';
451 button_delete.style.fontWeight = 'bold';
452 button_delete.style.paddingLeft = '2px';
453 button_delete.style.paddingRight = '2px';
454 td_delete.appendChild(button_delete);
457 function newEmptyRow (searchrow) {
458 // add delete button to current row
459 addDeleteButton(searchrow);
460 // mark current row as non-empty
461 var oldemptyrow = document.getElementById('row'+searchrow);
462 oldemptyrow.emptyrow = false;
466 // add a new empty row
470 function deleteRow() {
471 var thisrownum = this.getAttribute('rownum');
472 % if ( $opt{delete_row_callback} ) {
474 <% $opt{delete_row_callback} %>(thisrownum);
476 // remove the actual row
477 var thisrow = document.getElementById('row'+thisrownum);
478 thisrow.parentNode.removeChild(thisrow);
479 // remove row from tally of all rows
481 for (i = 0; i < allrows.length; i++) {
482 if (allrows[i] == thisrownum) continue;
483 newrows.push(allrows[i]);
486 totalrows--; // should never be deleting empty rows
488 // recalculate column totals, if any
490 % foreach my $footer ( @{$opt{footer}} ) {
491 % if ($footer eq '_TOTAL' ) {
492 calc_total<% $col %>()
498 function addRow(values) {
500 var table = document.getElementById('OneTrueTable');
501 var tablebody = table.getElementsByTagName('tbody').item(0);
503 var row = table.insertRow(table.rows.length - 1);
504 var thisrownum = values ? values.rownum : rownum;
505 row.setAttribute('id', 'row'+thisrownum);
506 row.emptyrow = values ? false : true;
508 var invnum_cell = document.createElement('TD');
510 var invnum_input = document.createElement('INPUT');
511 invnum_input.setAttribute('name', 'invnum'+thisrownum);
512 invnum_input.setAttribute('id', 'invnum'+thisrownum);
513 invnum_input.style.textAlign = 'right';
514 invnum_input.setAttribute('size', 8);
515 invnum_input.setAttribute('maxlength', 12);
516 invnum_input.setAttribute('rownum', thisrownum);
517 invnum_input.value = values ? values.invnum : '';
518 invnum_input.onfocus = clearhint_invnum;
519 invnum_input.onchange = search_invnum;
520 invnum_cell.appendChild(invnum_input);
522 row.appendChild(invnum_cell);
524 var custnum_cell = document.createElement('TD');
526 var display_custnum_input = document.createElement('INPUT');
527 display_custnum_input.setAttribute('name', 'display_custnum'+thisrownum);
528 display_custnum_input.setAttribute('id', 'display_custnum'+thisrownum);
529 display_custnum_input.style.textAlign = 'right';
530 display_custnum_input.setAttribute('size', 8);
531 display_custnum_input.setAttribute('maxlength', 12);
532 display_custnum_input.setAttribute('rownum', thisrownum);
533 display_custnum_input.value = values ? values.custnum : '';
534 display_custnum_input.onfocus = clearhint_custnum;
535 display_custnum_input.onchange = search_custnum;
536 custnum_cell.appendChild(display_custnum_input);
538 var custnum_input = document.createElement('INPUT');
539 custnum_input.type = 'hidden';
540 custnum_input.setAttribute('name', 'custnum'+thisrownum);
541 custnum_input.setAttribute('id', 'custnum'+thisrownum);
542 custnum_input.setAttribute('rownum', thisrownum);
543 custnum_input.value = values ? values.custnum : '';
544 custnum_cell.appendChild(custnum_input);
546 row.appendChild(custnum_cell);
548 var status_cell = document.createElement('TD');
549 status_cell.style.textAlign = 'center';
551 var status_span = document.createElement('SPAN');
552 status_span.setAttribute('id', 'status'+thisrownum+'_text');
553 status_span.style.fontWeight = 'bold';
554 status_span.style.color = values ? values.statuscolor : '';
555 status_span.setAttribute('rownum', thisrownum);
556 status_span.appendChild(
557 document.createTextNode(values ? values.status : '')
559 status_cell.appendChild(status_span);
561 var status_input = document.createElement('INPUT');
562 status_input.setAttribute('type', 'hidden');
563 status_input.setAttribute('name', 'status'+thisrownum);
564 status_input.setAttribute('id', 'status'+thisrownum);
565 status_input.setAttribute('rownum', thisrownum);
566 status_input.value = values ? values.status : '';
567 status_cell.appendChild(status_input);
569 var statuscolor_input = document.createElement('INPUT');
570 statuscolor_input.setAttribute('type', 'hidden');
571 statuscolor_input.setAttribute('name', 'statuscolor'+thisrownum);
572 statuscolor_input.setAttribute('id', 'statuscolor'+thisrownum);
573 statuscolor_input.setAttribute('rownum', thisrownum);
574 statuscolor_input.value = values ? values.statuscolor : '';
575 status_cell.appendChild(statuscolor_input);
577 row.appendChild(status_cell);
579 var customer_cell = document.createElement('TD');
581 var customer_input = document.createElement('INPUT');
582 customer_input.setAttribute('name', 'customer'+thisrownum);
583 customer_input.setAttribute('id', 'customer'+thisrownum);
584 customer_input.setAttribute('size', 64);
585 customer_input.setAttribute('value', '(last name or company)' );
586 customer_input.setAttribute('rownum', thisrownum);
587 customer_input.value = values ? values.customer : '';
588 customer_input.onfocus = clearhint_customer;
589 customer_input.onclick = clearhint_customer;
590 customer_input.onchange = search_customer;
591 customer_cell.appendChild(customer_input);
593 var customer_select = document.createElement('SELECT');
594 customer_select.setAttribute('name', 'cust_select'+thisrownum);
595 customer_select.setAttribute('id', 'cust_select'+thisrownum);
596 customer_select.setAttribute('rownum', thisrownum);
597 customer_select.style.color = '#ff0000';
598 customer_select.style.display = 'none';
599 customer_select.onchange = select_customer;
600 customer_cell.appendChild(customer_select);
602 row.appendChild(customer_cell);
604 var balance_cell = document.createElement('TD');
606 balance_cell.style.textAlign = 'right';
607 balance_cell.appendChild(document.createTextNode('<%$money_char%>'));
609 var balance_span = document.createElement('SPAN');
610 balance_span.setAttribute('id', 'balance'+thisrownum+'_text');
611 balance_span.setAttribute('rownum', thisrownum);
612 balance_cell.appendChild(balance_span);
614 balance_cell.appendChild(
615 document.createTextNode(String.fromCharCode(160) + (values ? values.balance : '')) //
618 var balance_input = document.createElement('INPUT');
619 balance_input.setAttribute('type', 'hidden');
620 balance_input.setAttribute('name', 'balance'+thisrownum);
621 balance_input.setAttribute('id', 'balance'+thisrownum);
622 balance_input.setAttribute('rownum', thisrownum);
623 balance_input.value = values ? values.balance : '';
624 balance_cell.appendChild(balance_input);
626 var num_open_input = document.createElement('INPUT');
627 num_open_input.setAttribute('type', 'hidden');
628 num_open_input.setAttribute('name', 'num_open'+thisrownum);
629 num_open_input.setAttribute('id', 'num_open'+thisrownum);
630 num_open_input.setAttribute('rownum', thisrownum);
631 balance_cell.appendChild(num_open_input);
633 row.appendChild(balance_cell);
636 % foreach my $field ( @{$opt{fields}} ) {
638 var my_cell = document.createElement('TD');
639 my_cell.setAttribute('align', '<% $align{ $opt{align}->[$col] || 'l' } %>');
640 % if ($opt{'color'}->[$col]) {
641 my_cell.style.color = '<% $opt{color}->[$col] %>';
644 % if ($types->[$col] eq 'immutable') {
645 var my_text = document.createTextNode(values ? values.<% $field %> : '');
646 my_cell.appendChild(my_text);
649 % my $name = (ref($field) eq 'CODE') ? "column${col}_" : $field;
650 var my_input = document.createElement('INPUT');
651 my_input.setAttribute('name', '<% $name %>'+thisrownum);
652 my_input.setAttribute('id', '<% $name %>'+thisrownum);
653 my_input.style.textAlign = '<% $align{ $opt{align}->[$col] || 'l' } %>';
654 my_input.setAttribute('size', <% $sizes->[$col] || 10 %>);
655 my_input.setAttribute('rownum', thisrownum);
656 % if ( $types->[$col] eq 'immutable' ) {
657 my_input.setAttribute('type', 'hidden');
658 % } elsif ( $types->[$col] eq 'checkbox' ) {
659 my_input.setAttribute('type', 'checkbox');
660 my_input.checked = (values && values.<% $field %>) ? true : false;
662 my_input.value = (values && values.<% $field %>) || '';
663 % if ( $opt{onchange}->[$col] ) {
664 my_input.onchange = <% $opt{onchange}->[$col] %>;
666 % elsif ( $opt{footer}->[$col] eq '_TOTAL' ) {
667 my_input.onchange = calc_total<%$col%>;
668 my_input.onkeyup = calc_total<%$col%>;
670 my_cell.appendChild(my_input);
672 row.appendChild(my_cell);
677 var td_delete = document.createElement('TD');
678 td_delete.setAttribute('id', 'delete'+thisrownum);
679 row.appendChild(td_delete);
681 addDeleteButton(thisrownum);
684 update_num_open(thisrownum, (values ? values.num_open : '0'));
686 % if ( $opt{add_row_callback} ) {
687 <% $opt{add_row_callback} %>(thisrownum, values);
690 // update the total number of rows display
691 allrows.push(thisrownum);
692 if (values) totalrows++;
695 // update the next available row number
696 if (thisrownum >= rownum) {
697 rownum = thisrownum + 1;
705 <TABLE ID="OneTrueTable" CLASS="fsinnerbox">
713 % foreach my $header ( @{$opt{header}} ) {
714 <TH><% $header %></TH>
718 % my @rownums = sort { $a <=> $b } map /^custnum(\d+)$/, keys %$param;
720 <TH COLSPAN=5 ID="_TOTAL_TOTAL">
721 Total <% @rownums || 0 %>
722 <% PL($opt{name_singular} || 'customer', ( @rownums || 0 ) ) %>
725 % foreach my $footer ( @{$opt{footer}} ) {
726 % my $align = $align{ $opt{'footer_align'}->[$col] || 'c' };
727 % if ($footer eq '_TOTAL' ) {
728 % my $id = $opt{'fields'}->[$col];
729 % $id = ref($id) ? "column${col}_TOTAL" : "${id}_TOTAL";
730 <TH ALIGN="<% $align %>" ID="<% $id %>"> <% sprintf('%.2f', $total[$col] ) %></TH>
732 <TH ALIGN="<% $align %>"><% $footer %></TH>
740 <SCRIPT TYPE="text/javascript">
743 document.getElementById("_TOTAL_TOTAL");
745 rownum = 1; // really more of a "next row", used by addrow
746 totalrows = 0; // will not include empty rows
747 allrows = []; // will include empty rows
749 % foreach my $row ( @rownums ) {
750 % if ( grep($param->{$_.$row},qw(invnum display_custnum custnum status statuscolor customer balance),@{$opt{fields}} ) ) {
754 num_open:<% $param->{"num_open$row"} |js_string %>,
755 invnum:<% $param->{"invnum$row"} |js_string %>,
756 display_custnum:<% $param->{"display_custnum$row"} |js_string %>,
757 custnum:<% $param->{"custnum$row"} |js_string %>,
758 status:<% $param->{"status$row"} |js_string %>,
759 statuscolor:<% $param->{"statuscolor$row"} |js_string %>,
760 customer:<% $param->{"customer$row"} |js_string %>,
761 balance:<% $param->{"balance$row"} |js_string %>,
763 % foreach my $field ( @{$opt{fields}} ) {
765 % if ( ref($field) eq 'CODE' ) {
766 % $value = &{$field}($row,$param) || '';
768 % $value = $param->{"$field$row"} || '';
770 % my $name = (ref($field) eq 'CODE') ? "column${col}" : "$field";
771 <% $name %>:<% $value |js_string %>,
781 % foreach my $footer ( @{$opt{footer}} ) {
782 % if ($footer eq '_TOTAL' ) {
783 % my $name = $opt{fields}->[$col];
784 % $name = ref($name) ? "column$col" : $name;
785 var th_el = document.getElementById("<%$name%>_TOTAL");
786 function calc_total<% $col %>() {
789 for (i = 0; i < allrows.length; i++) {
790 var value = document.getElementById("<%$name%>"+allrows[i]).value;
791 value = parseFloat(value);
792 if ( ! isNaN(value) ) {
793 total = total + value;
796 th_el.innerHTML = ' ' + total.toFixed(2);
798 calc_total<% $col %>()
804 <% include('/elements/xmlhttp.html',
805 'url' => $p. 'misc/xmlhttp-cust_main-search.cgi',
806 'subs' => [qw( custnum_search smart_search invnum_search )],
813 my $conf = new FS::Conf;
815 my $types = $opt{'type'} ? [ @{$opt{'type'}} ] : [];
816 my $sizes = $opt{'size'} ? [ @{$opt{'size'}} ] : [];
818 my $param = $opt{param};
819 $param = $cgi->Vars if $cgi->param('error');
821 $opt{$_} ||= [] foreach qw(align color footer footer_align);
823 my @total = map 0, @{$opt{footer}};
831 my $money_char = $conf->config('money_char') || '$';