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 'type' => ['immutable', ''], # immutable, checkbox, date 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.
69 <LINK REL="stylesheet" TYPE="text/css" HREF="<%$fsurl%>elements/calendar-win2k-2.css" TITLE="win2k-2">
70 <SCRIPT TYPE="text/javascript" SRC="<%$fsurl%>elements/calendar_stripped.js"></SCRIPT>
71 <SCRIPT TYPE="text/javascript" SRC="<%$fsurl%>elements/calendar-en.js"></SCRIPT>
72 <SCRIPT TYPE="text/javascript" SRC="<%$fsurl%>elements/calendar-setup.js"></SCRIPT>
73 <SCRIPT TYPE="text/javascript">
75 var num_open_invoices = new Array;
77 function clearhint_invnum() {
79 if ( this.value == 'Not found' || this.value == 'Multiple' ) {
81 this.style.color = '#000000';
86 function clearhint_custnum() {
88 if ( this.value == 'Not found' || this.value == 'Multiple' ) {
90 this.style.color = '#000000';
95 function clearhint_customer() {
97 this.style.color = '#000000';
99 if ( this.value == '(last name or company)' || this.value == 'Not found' )
104 function update_customer(searchrow, customerArray) {
106 var display_custnum_obj = document.getElementById('display_custnum'+searchrow);
107 var custnum_obj = document.getElementById('custnum'+searchrow);
108 var customer = document.getElementById('customer'+searchrow);
109 var customer_select = document.getElementById('cust_select'+searchrow);
111 var postPaymentBtn = document.getElementById('btnsubmit');
112 postPaymentBtn.disabled = false;
114 display_custnum_obj.disabled = false;
115 display_custnum_obj.style.backgroundColor = '#ffffff';
116 customer.disabled = false;
117 customer.style.backgroundColor = '#ffffff';
119 if ( customerArray.length == 0 ) {
121 custnum_obj.value = '';
122 display_custnum_obj.value = 'Not found';
123 customer.value = 'Not found';
124 display_custnum_obj.style.color = '#ff0000';
125 customer.style.color = '#ff0000';
127 customer.style.display = '';
128 customer_select.style.display = 'none';
131 } else if ( customerArray.length >= 6 ) {
133 custnum_obj.value = customerArray[0];
134 display_custnum_obj.value = customerArray[6];
135 display_custnum_obj.style.color = '#000000';
136 customer.value = customerArray[1];
138 update_balance_text(searchrow, customerArray[2]);
139 update_status_text( searchrow, customerArray[3]);
140 update_status_color(searchrow, '#'+customerArray[4]);
141 update_num_open(searchrow, customerArray[5]);
143 customer.style.display = '';
144 customer_select.style.display = 'none';
149 function search_invnum() {
151 this.style.color = '#000000'
153 var invnum_obj = this;
154 var searchrow = this.getAttribute('rownum');
155 var invnum = this.value;
157 if ( invnum == 'searching...' || invnum == 'Not found' || invnum == '' )
160 if ( this.getAttribute('magic') == 'nosearch' ) {
161 this.setAttribute('magic', '');
165 if ( document.getElementById('row'+searchrow).emptyrow ) {
166 newEmptyRow(searchrow);
168 var customer = document.getElementById('customer'+searchrow);
169 customer.value = 'searching...';
170 customer.disabled = true;
171 customer.style.color = '#000000';
172 customer.style.backgroundColor = '#dddddd';
174 var customer_select = document.getElementById('cust_select'+searchrow);
176 customer.style.display = '';
177 customer_select.style.display = 'none';
179 update_balance_text(searchrow, '');
180 update_status_text(searchrow, '');
181 update_status_color(searchrow, '#000000');
182 update_num_open(searchrow, 0);
184 function search_invnum_update(customers) {
186 var customerArray = eval('(' + customers + ')');
187 update_customer(searchrow, customerArray);
189 % if ( $opt{invnum_update_callback} ) {
190 <% $opt{invnum_update_callback} %>(searchrow)
195 invnum_search( invnum, search_invnum_update );
199 function search_custnum() {
201 this.style.color = '#000000'
203 var display_custnum_obj = this;
204 var searchrow = this.getAttribute('rownum');
205 var custnum_obj = document.getElementById('custnum'+searchrow);
206 var display_custnum = this.value;
208 if ( display_custnum == 'searching...' || display_custnum == 'Not found' || display_custnum == '' )
211 if ( this.getAttribute('magic') == 'nosearch' ) {
212 this.setAttribute('magic', '');
216 if ( document.getElementById('row'+searchrow).emptyrow ) {
217 newEmptyRow(searchrow);
220 var customer_obj = document.getElementById('customer'+searchrow);
221 customer_obj.value = 'searching...';
222 customer_obj.disabled = true;
223 customer_obj.style.color = '#000000';
224 customer_obj.style.backgroundColor = '#dddddd';
226 var customer_select = document.getElementById('cust_select'+searchrow);
228 customer_obj.style.display = '';
229 customer_select.style.display = 'none';
231 var invnum = document.getElementById('invnum'+searchrow);
234 update_balance_text(searchrow, '');
235 update_status_text( searchrow, '');
236 update_status_color(searchrow, '#000000');
237 update_num_open(searchrow, 0);
239 function search_custnum_update(customers) {
241 var customerArrayArray = eval('(' + customers + ')') || [];
243 if ( customerArrayArray.length == 0 ) {
245 update_customer(searchrow, []);
247 } else if ( customerArrayArray.length == 1 ) {
249 update_customer(searchrow, customerArrayArray[0]);
250 % if ( $opt{custnum_update_callback} ) {
251 <% $opt{custnum_update_callback} %>(searchrow)
256 custnum_obj.value = 'Multiple'; // or something
257 custnum_obj.style.color = '#ff0000';
259 //blank the current list
260 customer_select.options.length = 0;
262 opt(customer_select, '', 'Multiple customers match "' + custnum + '" - select one', '#ff0000');
263 //add the multiple customers
264 for ( var s = 0; s < customerArrayArray.length; s++ ) {
266 JSON.stringify(customerArrayArray[s]),
267 customerArrayArray[s][1],
271 opt(customer_select, 'cancel', '(Edit search string)', '#000000');
273 customer_obj.style.display = 'none';
275 customer_select.style.display = '';
281 custnum_search(display_custnum, search_custnum_update );
285 function search_customer() {
287 var customer_obj = this;
288 var searchrow = this.getAttribute('rownum');
289 var customer = this.value;
291 if ( customer == 'searching...' || customer == 'Not found' || customer == '' )
294 if ( this.getAttribute('magic') == 'nosearch' ) {
295 this.setAttribute('magic', '');
299 if ( document.getElementById('row'+searchrow).emptyrow ) {
300 newEmptyRow(searchrow);
303 var invnum = document.getElementById('invnum'+searchrow);
306 var custnum_obj = document.getElementById('display_custnum'+searchrow);
307 custnum_obj.value = 'searching...';
308 custnum_obj.disabled = true;
309 custnum_obj.style.color = '#000000';
310 custnum_obj.style.backgroundColor = '#dddddd';
312 var customer_select = document.getElementById('cust_select'+searchrow);
314 function search_customer_update(customers) {
316 var customerArrayArray = eval('(' + customers + ')') || [ [] ];
318 custnum_obj.disabled = false;
319 custnum_obj.style.backgroundColor = '#ffffff';
321 if ( customerArrayArray.length == 0 ) {
323 update_customer(searchrow, []);
325 } else if ( customerArrayArray.length == 1 ) {
327 update_customer(searchrow, customerArrayArray[0]);
328 % if ( $opt{custnum_update_callback} ) {
329 <% $opt{custnum_update_callback} %>(searchrow)
334 custnum_obj.value = 'Multiple'; // or something
335 custnum_obj.style.color = '#ff0000';
337 var postPaymentBtn = document.getElementById('btnsubmit');
338 postPaymentBtn.disabled = true;
340 //blank the current list
341 customer_select.options.length = 0;
343 opt(customer_select, '', 'Multiple customers match "' + customer + '" - select one', '#ff0000');
344 //add the multiple customers
345 for ( var s = 0; s < customerArrayArray.length; s++ ) {
347 JSON.stringify(customerArrayArray[s]),
348 customerArrayArray[s][1],
352 opt(customer_select, 'cancel', '(Edit search string)', '#000000');
354 customer_obj.style.display = 'none';
356 customer_select.style.display = '';
362 smart_search( customer, search_customer_update );
366 function select_customer() {
368 var custnum_balance_status = this.options[this.selectedIndex].value;
369 var customer = this.options[this.selectedIndex].text;
371 var searchrow = this.getAttribute('rownum');
372 var display_custnum_obj = document.getElementById('display_custnum'+searchrow);
373 var custnum_obj = document.getElementById('custnum'+searchrow);
374 var customer_obj = document.getElementById('customer'+searchrow);
375 var balance_obj = document.getElementById('balance'+searchrow);
376 var status_obj = document.getElementById('status'+searchrow);
378 if ( custnum_balance_status == '' ) {
380 } else if ( custnum_balance_status == 'cancel' ) {
382 display_custnum_obj.value = '';
383 custnum_obj.value = '';
384 custnum_obj.style.color = '#000000';
386 this.style.display = 'none';
387 customer_obj.style.display = '';
388 customer_obj.focus();
392 update_customer(searchrow, JSON.parse(custnum_balance_status));
394 % if ( $opt{custnum_update_callback} ) {
395 <% $opt{custnum_update_callback} %>(searchrow)
402 function opt(what,value,text,color) {
403 var optionName = new Option(text, value, false, false);
404 optionName.style.color = color;
405 var length = what.length;
406 what.options[length] = optionName;
409 function update_status_text(rownum, newval) {
410 document.getElementById('status'+rownum).value = newval;
411 document.getElementById('status'+rownum+'_text').innerHTML = newval;
414 function update_status_color(rownum, newval) {
415 document.getElementById('statuscolor'+rownum).value = newval;
416 document.getElementById('status'+rownum+'_text').style.color = newval;
419 function update_balance_text(rownum, newval) {
420 document.getElementById('balance'+rownum).value = newval;
421 document.getElementById('balance'+rownum+'_text').innerHTML = newval;
424 function update_num_open(rownum, newval) {
425 document.getElementById('num_open'+rownum).value = newval;
426 num_open_invoices[rownum] = newval;
429 // updates display of total rows based on value of totalrows
430 function updateTotalRow () {
431 if ( totalrows == 1 ) {
435 + ' <% $opt{name_singular} || 'customer' %>';
440 + ' <% PL($opt{name_singular} || 'customer') %>';
444 var total_el, rownum, totalrows, allrows;
446 function addDeleteButton (searchrow) {
447 var td_delete = document.getElementById('delete'+searchrow);
448 var button_delete = document.createElement('INPUT');
449 button_delete.setAttribute('rownum', searchrow);
450 button_delete.setAttribute('type', 'button');
451 button_delete.setAttribute('value', 'X');
452 button_delete.onclick = deleteRow;
453 button_delete.style.color = '#ff0000';
454 button_delete.style.fontWeight = 'bold';
455 button_delete.style.paddingLeft = '2px';
456 button_delete.style.paddingRight = '2px';
457 td_delete.appendChild(button_delete);
460 function newEmptyRow (searchrow) {
461 // add delete button to current row
462 addDeleteButton(searchrow);
463 // mark current row as non-empty
464 var oldemptyrow = document.getElementById('row'+searchrow);
465 oldemptyrow.emptyrow = false;
469 // add a new empty row
473 function deleteRow() {
474 var thisrownum = this.getAttribute('rownum');
475 % if ( $opt{delete_row_callback} ) {
477 <% $opt{delete_row_callback} %>(thisrownum);
479 // remove the actual row
480 var thisrow = document.getElementById('row'+thisrownum);
481 thisrow.parentNode.removeChild(thisrow);
482 // remove row from tally of all rows
484 for (i = 0; i < allrows.length; i++) {
485 if (allrows[i] == thisrownum) continue;
486 newrows.push(allrows[i]);
489 totalrows--; // should never be deleting empty rows
491 // recalculate column totals, if any
493 % foreach my $footer ( @{$opt{footer}} ) {
494 % if ($footer eq '_TOTAL' ) {
495 calc_total<% $col %>()
501 function addRow(values) {
503 var table = document.getElementById('OneTrueTable');
504 var tablebody = table.getElementsByTagName('tbody').item(0);
506 var row = table.insertRow(table.rows.length - 1);
507 var thisrownum = values ? values.rownum : rownum;
508 row.setAttribute('id', 'row'+thisrownum);
509 row.emptyrow = values ? false : true;
511 var invnum_cell = document.createElement('TD');
513 var invnum_input = document.createElement('INPUT');
514 invnum_input.setAttribute('name', 'invnum'+thisrownum);
515 invnum_input.setAttribute('id', 'invnum'+thisrownum);
516 invnum_input.style.textAlign = 'right';
517 invnum_input.setAttribute('size', 8);
518 invnum_input.setAttribute('maxlength', 12);
519 invnum_input.setAttribute('rownum', thisrownum);
520 invnum_input.value = values ? values.invnum : '';
521 invnum_input.onfocus = clearhint_invnum;
522 invnum_input.onchange = search_invnum;
523 invnum_cell.appendChild(invnum_input);
525 row.appendChild(invnum_cell);
527 var custnum_cell = document.createElement('TD');
529 var display_custnum_input = document.createElement('INPUT');
530 display_custnum_input.setAttribute('name', 'display_custnum'+thisrownum);
531 display_custnum_input.setAttribute('id', 'display_custnum'+thisrownum);
532 display_custnum_input.style.textAlign = 'right';
533 display_custnum_input.setAttribute('size', 8);
534 display_custnum_input.setAttribute('maxlength', 12);
535 display_custnum_input.setAttribute('rownum', thisrownum);
536 display_custnum_input.value = values ? values.custnum : '';
537 display_custnum_input.onfocus = clearhint_custnum;
538 display_custnum_input.onchange = search_custnum;
539 custnum_cell.appendChild(display_custnum_input);
541 var custnum_input = document.createElement('INPUT');
542 custnum_input.type = 'hidden';
543 custnum_input.setAttribute('name', 'custnum'+thisrownum);
544 custnum_input.setAttribute('id', 'custnum'+thisrownum);
545 custnum_input.setAttribute('rownum', thisrownum);
546 custnum_input.value = values ? values.custnum : '';
547 custnum_cell.appendChild(custnum_input);
549 row.appendChild(custnum_cell);
551 var status_cell = document.createElement('TD');
552 status_cell.style.textAlign = 'center';
554 var status_span = document.createElement('SPAN');
555 status_span.setAttribute('id', 'status'+thisrownum+'_text');
556 status_span.style.fontWeight = 'bold';
557 status_span.style.color = values ? values.statuscolor : '';
558 status_span.setAttribute('rownum', thisrownum);
559 status_span.appendChild(
560 document.createTextNode(values ? values.status : '')
562 status_cell.appendChild(status_span);
564 var status_input = document.createElement('INPUT');
565 status_input.setAttribute('type', 'hidden');
566 status_input.setAttribute('name', 'status'+thisrownum);
567 status_input.setAttribute('id', 'status'+thisrownum);
568 status_input.setAttribute('rownum', thisrownum);
569 status_input.value = values ? values.status : '';
570 status_cell.appendChild(status_input);
572 var statuscolor_input = document.createElement('INPUT');
573 statuscolor_input.setAttribute('type', 'hidden');
574 statuscolor_input.setAttribute('name', 'statuscolor'+thisrownum);
575 statuscolor_input.setAttribute('id', 'statuscolor'+thisrownum);
576 statuscolor_input.setAttribute('rownum', thisrownum);
577 statuscolor_input.value = values ? values.statuscolor : '';
578 status_cell.appendChild(statuscolor_input);
580 row.appendChild(status_cell);
582 var customer_cell = document.createElement('TD');
584 var customer_input = document.createElement('INPUT');
585 customer_input.setAttribute('name', 'customer'+thisrownum);
586 customer_input.setAttribute('id', 'customer'+thisrownum);
587 customer_input.setAttribute('size', 64);
588 customer_input.setAttribute('value', '(last name or company)' );
589 customer_input.setAttribute('rownum', thisrownum);
590 customer_input.value = values ? values.customer : '';
591 customer_input.onfocus = clearhint_customer;
592 customer_input.onclick = clearhint_customer;
593 customer_input.onchange = search_customer;
594 customer_cell.appendChild(customer_input);
596 var customer_select = document.createElement('SELECT');
597 customer_select.setAttribute('name', 'cust_select'+thisrownum);
598 customer_select.setAttribute('id', 'cust_select'+thisrownum);
599 customer_select.setAttribute('rownum', thisrownum);
600 customer_select.style.color = '#ff0000';
601 customer_select.style.display = 'none';
602 customer_select.onchange = select_customer;
603 customer_cell.appendChild(customer_select);
605 row.appendChild(customer_cell);
607 var balance_cell = document.createElement('TD');
609 balance_cell.style.textAlign = 'right';
610 balance_cell.appendChild(document.createTextNode('<%$money_char%>'));
612 var balance_span = document.createElement('SPAN');
613 balance_span.setAttribute('id', 'balance'+thisrownum+'_text');
614 balance_span.setAttribute('rownum', thisrownum);
615 balance_cell.appendChild(balance_span);
617 balance_cell.appendChild(
618 document.createTextNode(String.fromCharCode(160) + (values ? values.balance : '')) //
621 var balance_input = document.createElement('INPUT');
622 balance_input.setAttribute('type', 'hidden');
623 balance_input.setAttribute('name', 'balance'+thisrownum);
624 balance_input.setAttribute('id', 'balance'+thisrownum);
625 balance_input.setAttribute('rownum', thisrownum);
626 balance_input.value = values ? values.balance : '';
627 balance_cell.appendChild(balance_input);
629 var num_open_input = document.createElement('INPUT');
630 num_open_input.setAttribute('type', 'hidden');
631 num_open_input.setAttribute('name', 'num_open'+thisrownum);
632 num_open_input.setAttribute('id', 'num_open'+thisrownum);
633 num_open_input.setAttribute('rownum', thisrownum);
634 balance_cell.appendChild(num_open_input);
636 row.appendChild(balance_cell);
639 % foreach my $field ( @{$opt{fields}} ) {
641 var my_cell = document.createElement('TD');
642 my_cell.setAttribute('align', '<% $align{ $opt{align}->[$col] || 'l' } %>');
643 % if ($opt{'color'}->[$col]) {
644 my_cell.style.color = '<% $opt{color}->[$col] %>';
647 % if ($types->[$col] eq 'immutable') {
648 var my_text = document.createTextNode(values ? values.<% $field %> : '');
649 my_cell.appendChild(my_text);
652 % my $name = (ref($field) eq 'CODE') ? "column${col}_" : $field;
653 var my_input = document.createElement('INPUT');
654 my_input.setAttribute('name', '<% $name %>'+thisrownum);
655 my_input.setAttribute('id', '<% $name %>'+thisrownum);
656 my_input.style.textAlign = '<% $align{ $opt{align}->[$col] || 'l' } %>';
657 my_input.setAttribute('size', <% $sizes->[$col] || 10 %>);
658 my_input.setAttribute('rownum', thisrownum);
659 % if ( $types->[$col] eq 'immutable' ) {
660 my_input.setAttribute('type', 'hidden');
661 % } elsif ( $types->[$col] eq 'checkbox' ) {
662 my_input.setAttribute('type', 'checkbox');
663 my_input.checked = (values && values.<% $field %>) ? true : false;
664 % } elsif ( $types->[$col] eq 'date' ) {
665 my_input_button = document.createElement('IMG');
666 my_input_button.setAttribute('src', '<% $fsurl %>images/calendar.png');
667 my_input_button.setAttribute('title', <% mt('Select date') |js_string %>);
668 my_input_button.setAttribute('name', '<% $name %>'+thisrownum+'button');
669 my_input_button.setAttribute('id', '<% $name %>'+thisrownum+'button');
671 my_input.value = (values && values.<% $field %>) || '';
672 % if ( $opt{onchange}->[$col] ) {
673 my_input.onchange = <% $opt{onchange}->[$col] %>;
675 % elsif ( $opt{footer}->[$col] eq '_TOTAL' ) {
676 my_input.onchange = calc_total<%$col%>;
677 my_input.onkeyup = calc_total<%$col%>;
679 my_cell.appendChild(my_input);
680 % if ( $types->[$col] eq 'date' ) {
681 my_cell.appendChild(my_input_button);
684 row.appendChild(my_cell);
686 % if ( $types->[$col] eq 'date' ) {
688 inputField: '<% $name %>'+thisrownum,
689 ifFormat: "<% $date_format %>",
690 button: '<% $name %>'+thisrownum+'button',
698 var td_delete = document.createElement('TD');
699 td_delete.setAttribute('id', 'delete'+thisrownum);
700 row.appendChild(td_delete);
702 addDeleteButton(thisrownum);
705 update_num_open(thisrownum, (values ? values.num_open : '0'));
707 % if ( $opt{add_row_callback} ) {
708 <% $opt{add_row_callback} %>(thisrownum, values);
711 // update the total number of rows display
712 allrows.push(thisrownum);
713 if (values) totalrows++;
716 // update the next available row number
717 if (thisrownum >= rownum) {
718 rownum = thisrownum + 1;
726 <TABLE ID="OneTrueTable" CLASS="fsinnerbox">
734 % foreach my $header ( @{$opt{header}} ) {
735 <TH><% $header %></TH>
739 % my @rownums = sort { $a <=> $b } map /^custnum(\d+)$/, keys %$param;
741 <TH COLSPAN=5 ID="_TOTAL_TOTAL">
742 Total <% @rownums || 0 %>
743 <% PL($opt{name_singular} || 'customer', ( @rownums || 0 ) ) %>
746 % foreach my $footer ( @{$opt{footer}} ) {
747 % my $align = $align{ $opt{'footer_align'}->[$col] || 'c' };
748 % if ($footer eq '_TOTAL' ) {
749 % my $id = $opt{'fields'}->[$col];
750 % $id = ref($id) ? "column${col}_TOTAL" : "${id}_TOTAL";
751 <TH ALIGN="<% $align %>" ID="<% $id %>"> <% sprintf('%.2f', $total[$col] ) %></TH>
753 <TH ALIGN="<% $align %>"><% $footer %></TH>
761 <SCRIPT TYPE="text/javascript">
764 document.getElementById("_TOTAL_TOTAL");
766 rownum = 1; // really more of a "next row", used by addrow
767 totalrows = 0; // will not include empty rows
768 allrows = []; // will include empty rows
770 % foreach my $row ( @rownums ) {
771 % if ( grep($param->{$_.$row},qw(invnum display_custnum custnum status statuscolor customer balance),@{$opt{fields}} ) ) {
775 num_open:<% $param->{"num_open$row"} |js_string %>,
776 invnum:<% $param->{"invnum$row"} |js_string %>,
777 display_custnum:<% $param->{"display_custnum$row"} |js_string %>,
778 custnum:<% $param->{"custnum$row"} |js_string %>,
779 status:<% $param->{"status$row"} |js_string %>,
780 statuscolor:<% $param->{"statuscolor$row"} |js_string %>,
781 customer:<% $param->{"customer$row"} |js_string %>,
782 balance:<% $param->{"balance$row"} |js_string %>,
784 % foreach my $field ( @{$opt{fields}} ) {
786 % if ( ref($field) eq 'CODE' ) {
787 % $value = &{$field}($row,$param) || '';
789 % $value = $param->{"$field$row"} || '';
791 % my $name = (ref($field) eq 'CODE') ? "column${col}" : "$field";
792 <% $name %>:<% $value |js_string %>,
802 % foreach my $footer ( @{$opt{footer}} ) {
803 % if ($footer eq '_TOTAL' ) {
804 % my $name = $opt{fields}->[$col];
805 % $name = ref($name) ? "column$col" : $name;
806 var th_el = document.getElementById("<%$name%>_TOTAL");
807 function calc_total<% $col %>() {
810 for (i = 0; i < allrows.length; i++) {
811 var value = document.getElementById("<%$name%>"+allrows[i]).value;
812 value = parseFloat(value);
813 if ( ! isNaN(value) ) {
814 total = total + value;
817 th_el.innerHTML = ' ' + total.toFixed(2);
819 calc_total<% $col %>()
825 <% include('/elements/xmlhttp.html',
826 'url' => $p. 'misc/xmlhttp-cust_main-search.cgi',
827 'subs' => [qw( custnum_search smart_search invnum_search )],
834 my $conf = new FS::Conf;
835 my $date_format = $conf->config('date_format') || '%m/%d/%Y';
837 my $types = $opt{'type'} ? [ @{$opt{'type'}} ] : [];
838 my $sizes = $opt{'size'} ? [ @{$opt{'size'}} ] : [];
840 my $param = $opt{param};
841 $param = $cgi->Vars if $cgi->param('error');
843 $opt{$_} ||= [] foreach qw(align color footer footer_align);
845 my @total = map 0, @{$opt{footer}};
853 my $money_char = $conf->config('money_char') || '$';