RT#33484: Delete lines from quick payment entry
[freeside.git] / httemplate / elements / customer-table.html
1 <%doc>
2
3 Example:
4
5   include( '/elements/customer-table.html',
6
7              ###
8              # required
9              ###
10
11              #listrefs...
12              'header'        => [ '#', 'Item' ],
13              'fields'        => [
14                                   'column',
15                                   sub { my ($row,$param) = @_;
16                                         $param->{"column$row"};
17                                       },
18                                 ],
19
20              ###
21              # optional
22              ###
23
24              'name_singular' => 'customer', #label
25              'custnum_update_callback' => 'name_of_js_callback' #passed a rownum
26
27              #listrefs
28              'types'         => ['immutable', ''], # immutable or ''/text
29              'align'         => [ 'c', 'l', 'r', '' ],
30              'size'          => [],                # sizes ignored for immutable
31              'color'         => [],
32              'footer'        => ['string', '_TOTAL'], # strings or the special
33                                                       #value _TOTAL
34              'footer_align'  => [ 'c', 'l', 'r', '' ],
35
36              'param'         => { column0 => 1 },  # preset column of row 0 to 1
37
38          )
39
40 </%doc>
41
42 <SCRIPT TYPE="text/javascript">
43
44   var num_open_invoices = new Array;
45
46   function clearhint_invnum() {
47
48     if ( this.value == 'Not found' || this.value == 'Multiple' ) {
49       this.value = '';
50       this.style.color = '#000000';
51     }
52
53   }
54
55   function clearhint_custnum() {
56
57     if ( this.value == 'Not found' || this.value == 'Multiple' ) {
58       this.value = '';
59       this.style.color = '#000000';
60     }
61
62   }
63
64   function clearhint_customer() {
65
66     this.style.color = '#000000';
67
68     if ( this.value == '(last name or company)' || this.value == 'Not found' )
69       this.value = '';
70
71   }
72
73   function update_customer(searchrow, customerArray) {
74       
75       var display_custnum_obj = document.getElementById('display_custnum'+searchrow);
76       var custnum_obj = document.getElementById('custnum'+searchrow);
77       var customer = document.getElementById('customer'+searchrow);
78       var customer_select = document.getElementById('cust_select'+searchrow);
79
80       display_custnum_obj.disabled = false;
81       display_custnum_obj.style.backgroundColor = '#ffffff';
82       customer.disabled = false;
83       customer.style.backgroundColor = '#ffffff';
84
85       if ( customerArray.length == 0 ) {
86
87           custnum_obj.value = '';
88           display_custnum_obj.value = 'Not found';
89           customer.value = 'Not found';
90           display_custnum_obj.style.color = '#ff0000';
91           customer.style.color = '#ff0000';
92
93           customer.style.display = '';
94           customer_select.style.display = 'none';
95           return false;
96
97       } else if ( customerArray.length >= 6 ) {
98
99           custnum_obj.value = customerArray[0];
100           display_custnum_obj.value = customerArray[6];
101           display_custnum_obj.style.color = '#000000';
102           customer.value = customerArray[1];
103
104           update_balance_text(searchrow, customerArray[2]);
105           update_status_text( searchrow, customerArray[3]);
106           update_status_color(searchrow, '#'+customerArray[4]);
107           update_num_open(searchrow, customerArray[5]);
108
109           customer.style.display = '';
110           customer_select.style.display = 'none';
111           return true;
112       }
113   }
114
115   function <% $opt{prefix} %>search_invnum() {
116
117     this.style.color = '#000000'
118
119     var invnum_obj = this;
120     var searchrow = this.getAttribute('rownum');
121     var invnum = this.value;
122
123     if ( invnum == 'searching...' || invnum == 'Not found' || invnum == '' )
124       return;
125
126     if ( this.getAttribute('magic') == 'nosearch' ) {
127       this.setAttribute('magic', '');
128       return;
129     }
130
131     if ( document.getElementById('row'+searchrow).emptyrow ) {
132       <% $opt{prefix} %>newEmptyRow(searchrow);
133     }
134     var customer = document.getElementById('customer'+searchrow);
135     customer.value = 'searching...';
136     customer.disabled = true;
137     customer.style.color = '#000000';
138     customer.style.backgroundColor = '#dddddd';
139
140     var customer_select = document.getElementById('cust_select'+searchrow);
141
142     customer.style.display = '';
143     customer_select.style.display = 'none';
144     
145     update_balance_text(searchrow, '');
146     update_status_text(searchrow, '');
147     update_status_color(searchrow, '#000000');
148     update_num_open(searchrow, 0);
149
150     function search_invnum_update(customers) {
151       
152       var customerArray = eval('(' + customers + ')');
153       update_customer(searchrow, customerArray);
154
155 % if ( $opt{invnum_update_callback} ) {
156         <% $opt{invnum_update_callback} %>(searchrow, '<% $opt{prefix} %>')
157 % }
158
159     }
160
161     invnum_search( invnum, search_invnum_update );
162
163   }
164
165   function <% $opt{prefix} %>search_custnum() {
166
167     this.style.color = '#000000'
168
169     var display_custnum_obj = this;
170     var searchrow = this.getAttribute('rownum');
171     var custnum_obj = document.getElementById('custnum'+searchrow);
172     var display_custnum = this.value;
173
174     if ( display_custnum == 'searching...' || display_custnum == 'Not found' || display_custnum == '' )
175       return;
176
177     if ( this.getAttribute('magic') == 'nosearch' ) {
178       this.setAttribute('magic', '');
179       return;
180     }
181
182     if ( document.getElementById('row'+searchrow).emptyrow ) {
183       <% $opt{prefix} %>newEmptyRow(searchrow);
184     }
185
186     var customer_obj = document.getElementById('customer'+searchrow);
187     customer_obj.value = 'searching...';
188     customer_obj.disabled = true;
189     customer_obj.style.color = '#000000';
190     customer_obj.style.backgroundColor = '#dddddd';
191
192     var customer_select = document.getElementById('cust_select'+searchrow);
193
194     customer_obj.style.display = '';
195     customer_select.style.display = 'none';
196
197     var invnum = document.getElementById('invnum'+searchrow);
198     invnum.value = '';
199
200     update_balance_text(searchrow, '');
201     update_status_text( searchrow, '');
202     update_status_color(searchrow, '#000000');    
203     update_num_open(searchrow, 0);
204
205     function search_custnum_update(customers) {
206
207       var customerArrayArray = eval('(' + customers + ')') || [];
208
209       if ( customerArrayArray.length == 0 ) {
210
211         update_customer(searchrow, []);
212
213       } else if ( customerArrayArray.length == 1 ) {
214
215         update_customer(searchrow, customerArrayArray[0]);
216 % if ( $opt{custnum_update_callback} ) {
217           <% $opt{custnum_update_callback} %>(searchrow, '<% $opt{prefix} %>')
218 % }
219
220       } else {
221
222         custnum_obj.value = 'Multiple'; // or something
223         custnum_obj.style.color = '#ff0000';
224
225         //blank the current list
226         customer_select.options.length = 0;
227
228         opt(customer_select, '', 'Multiple customers match "' + custnum + '" - select one', '#ff0000');
229         //add the multiple customers
230         for ( var s = 0; s < customerArrayArray.length; s++ ) {
231           opt(customer_select,
232               JSON.stringify(customerArrayArray[s]),
233               customerArrayArray[s][1],
234               '#000000');
235         }
236
237         opt(customer_select, 'cancel', '(Edit search string)', '#000000');
238
239         customer_obj.style.display = 'none';
240
241         customer_select.style.display = '';
242
243       }
244
245     }
246
247     custnum_search(display_custnum, search_custnum_update );
248
249   }
250
251   function <% $opt{prefix} %>search_customer() {
252
253     var customer_obj = this;
254     var searchrow = this.getAttribute('rownum');
255     var customer = this.value;
256
257     if ( customer == 'searching...' || customer == 'Not found' || customer == '' )
258       return;
259
260     if ( this.getAttribute('magic') == 'nosearch' ) {
261       this.setAttribute('magic', '');
262       return;
263     }
264
265     if ( document.getElementById('row'+searchrow).emptyrow ) {
266       <% $opt{prefix} %>newEmptyRow(searchrow);
267     }
268     
269     var invnum = document.getElementById('invnum'+searchrow);
270     invnum.value = '';
271
272     var custnum_obj = document.getElementById('display_custnum'+searchrow);
273     custnum_obj.value = 'searching...';
274     custnum_obj.disabled = true;
275     custnum_obj.style.color = '#000000';
276     custnum_obj.style.backgroundColor = '#dddddd';
277
278     var customer_select = document.getElementById('cust_select'+searchrow);
279     
280     function search_customer_update(customers) {
281
282       var customerArrayArray = eval('(' + customers + ')') || [ [] ];
283
284       custnum_obj.disabled = false;
285       custnum_obj.style.backgroundColor = '#ffffff';
286
287       if ( customerArrayArray.length == 0 ) {
288
289         update_customer(searchrow, []);
290
291       } else if ( customerArrayArray.length == 1 ) {
292
293         update_customer(searchrow, customerArrayArray[0]);
294 % if ( $opt{custnum_update_callback} ) {
295         <% $opt{custnum_update_callback} %>(searchrow, '<% $opt{prefix} %>')
296 % }
297
298       } else {
299
300         custnum_obj.value = 'Multiple'; // or something
301         custnum_obj.style.color = '#ff0000';
302
303         //blank the current list
304         customer_select.options.length = 0;
305
306         opt(customer_select, '', 'Multiple customers match "' + customer + '" - select one', '#ff0000');
307         //add the multiple customers
308         for ( var s = 0; s < customerArrayArray.length; s++ ) {
309           opt(customer_select,
310               JSON.stringify(customerArrayArray[s]),
311               customerArrayArray[s][1],
312               '#000000');
313         }
314
315         opt(customer_select, 'cancel', '(Edit search string)', '#000000');
316
317         customer_obj.style.display = 'none';
318
319         customer_select.style.display = '';
320
321       }
322
323     }
324
325     smart_search( customer, search_customer_update );
326
327   }
328
329   function select_customer() {
330
331     var custnum_balance_status = this.options[this.selectedIndex].value;
332     var customer = this.options[this.selectedIndex].text;
333
334     var searchrow = this.getAttribute('rownum');
335     var display_custnum_obj = document.getElementById('display_custnum'+searchrow);
336     var custnum_obj = document.getElementById('custnum'+searchrow);
337     var customer_obj = document.getElementById('customer'+searchrow);
338     var balance_obj = document.getElementById('balance'+searchrow);
339     var status_obj = document.getElementById('status'+searchrow);
340
341     if ( custnum_balance_status == '' ) {
342
343     } else if ( custnum_balance_status == 'cancel' ) {
344
345       display_custnum_obj.value = '';
346       custnum_obj.value = '';
347       custnum_obj.style.color = '#000000';
348
349       this.style.display = 'none';
350       customer_obj.style.display = '';
351       customer_obj.focus();
352
353     } else {
354     
355       update_customer(searchrow, JSON.parse(custnum_balance_status));
356
357 % if ( $opt{custnum_update_callback} ) {
358       <% $opt{custnum_update_callback} %>(searchrow, '<% $opt{prefix} %>')
359 % }
360
361     }
362
363   }
364
365   function opt(what,value,text,color) {
366     var optionName = new Option(text, value, false, false);
367     optionName.style.color = color;
368     var length = what.length;
369     what.options[length] = optionName;
370   }
371
372   function update_status_text(rownum, newval) {
373     document.getElementById('status'+rownum).value = newval;
374     document.getElementById('status'+rownum+'_text').innerHTML = newval;
375   }
376
377   function update_status_color(rownum, newval) {
378     document.getElementById('statuscolor'+rownum).value = newval;
379     document.getElementById('status'+rownum+'_text').style.color = newval;
380   }
381
382   function update_balance_text(rownum, newval) {
383     document.getElementById('balance'+rownum).value = newval;
384     document.getElementById('balance'+rownum+'_text').innerHTML = newval;
385   }
386
387   function update_num_open(rownum, newval) {
388     document.getElementById('num_open'+rownum).value = newval;
389     num_open_invoices[rownum] = newval;
390   }
391
392   function <% $opt{prefix} %>updateTotalRow () {
393     if ( <% $opt{prefix} %>totalrows == 1 ) {
394       <% $opt{prefix} %>total_el.innerHTML =
395         'Total '
396           + <% $opt{prefix} %>totalrows
397           + ' <% $opt{name_singular} || 'customer' %>';
398     } else {
399       <% $opt{prefix} %>total_el.innerHTML =
400         'Total '
401           + <% $opt{prefix} %>totalrows
402           + ' <% PL($opt{name_singular} || 'customer') %>';
403     }
404   }
405
406   var <% $opt{prefix} %>total_el, <% $opt{prefix} %>rownum, <% $opt{prefix} %>totalrows, <% $opt{prefix} %>allrows;
407
408   function <% $opt{prefix} %>addDeleteButton (searchrow) {
409     var td_delete = document.getElementById('delete'+searchrow);
410     var button_delete = document.createElement('INPUT');
411     button_delete.setAttribute('rownum', searchrow);
412     button_delete.setAttribute('type', 'button');
413     button_delete.setAttribute('value', 'X');
414     button_delete.onclick = <% $opt{prefix} %>deleteRow;
415     button_delete.style.color = '#ff0000';
416     button_delete.style.fontWeight = 'bold';
417     button_delete.style.paddingLeft = '2px';
418     button_delete.style.paddingRight = '2px';
419     td_delete.appendChild(button_delete);
420   }
421
422   function <% $opt{prefix} %>newEmptyRow (searchrow) {
423     // add delete button to current row
424     <% $opt{prefix} %>addDeleteButton(searchrow);
425     // mark current row as non-empty
426     var oldemptyrow = document.getElementById('row'+searchrow);
427     oldemptyrow.emptyrow = false;
428     // update totalrows
429     <% $opt{prefix} %>totalrows++
430     <% $opt{prefix} %>updateTotalRow();
431     // add a new empty row
432     <% $opt{prefix} %>addRow();
433   }
434
435   function <% $opt{prefix} %>deleteRow() {
436     var thisrownum = this.getAttribute('rownum');
437 % if ( $opt{delete_row_callback} ) {
438     // callback
439     <% $opt{delete_row_callback} %>(thisrownum,'<% $opt{prefix} %>');
440 % }
441     // remove the actual row
442     var thisrow = document.getElementById('row'+thisrownum);
443     thisrow.parentNode.removeChild(thisrow);
444     // remove row from tally of all rows
445     var newrows = [];
446     for (i = 0; i < allrows.length; i++) {
447       if (allrows[i] == thisrownum) continue;
448       newrows.push(allrows[i]);
449     }
450     allrows = newrows;
451     <% $opt{prefix} %>totalrows--; // should never be deleting empty rows
452     <% $opt{prefix} %>updateTotalRow();
453     // recalculate column totals, if any
454 % my $col = 0;
455 % foreach my $footer ( @{$opt{footer}} ) {
456 %   if ($footer eq '_TOTAL' ) {
457     <% $opt{prefix} %>calc_total<% $col %>()
458 %   }
459 %   $col++;
460 % }
461   }
462
463   function <% $opt{prefix} %>addRow(values) {
464
465     var table = document.getElementById('<% $opt{prefix} %>OneTrueTable');
466     var tablebody = table.getElementsByTagName('tbody').item(0);
467
468     var row = table.insertRow(table.rows.length - 1);
469     var thisrownum = values ? values.rownum : <% $opt{prefix} %>rownum;
470     row.setAttribute('id', 'row'+thisrownum);
471     row.emptyrow = values ? false : true;
472     
473     var invnum_cell = document.createElement('TD');
474
475       var invnum_input = document.createElement('INPUT');
476       invnum_input.setAttribute('name', 'invnum'+thisrownum);
477       invnum_input.setAttribute('id',   'invnum'+thisrownum);
478       invnum_input.style.textAlign = 'right';
479       invnum_input.setAttribute('size', 8);
480       invnum_input.setAttribute('maxlength', 12);
481       invnum_input.setAttribute('rownum', thisrownum);
482       invnum_input.value = values ? values.invnum : '';
483       invnum_input.onfocus = clearhint_invnum;
484       invnum_input.onchange = <% $opt{prefix} %>search_invnum;
485       invnum_cell.appendChild(invnum_input);
486
487     row.appendChild(invnum_cell);
488
489     var custnum_cell = document.createElement('TD');
490
491       var display_custnum_input = document.createElement('INPUT');
492       display_custnum_input.setAttribute('name', 'display_custnum'+thisrownum);
493       display_custnum_input.setAttribute('id',   'display_custnum'+thisrownum);
494       display_custnum_input.style.textAlign = 'right';
495       display_custnum_input.setAttribute('size', 8);
496       display_custnum_input.setAttribute('maxlength', 12);
497       display_custnum_input.setAttribute('rownum', thisrownum);
498       display_custnum_input.value = values ? values.custnum : '';
499       display_custnum_input.onfocus = clearhint_custnum;
500       display_custnum_input.onchange = <% $opt{prefix} %>search_custnum;
501       custnum_cell.appendChild(display_custnum_input);
502
503       var custnum_input = document.createElement('INPUT');
504       custnum_input.type = 'hidden';
505       custnum_input.setAttribute('name', 'custnum'+thisrownum);
506       custnum_input.setAttribute('id',   'custnum'+thisrownum);
507       custnum_input.setAttribute('rownum', thisrownum);
508       custnum_input.value = values ? values.custnum : '';
509       custnum_cell.appendChild(custnum_input);
510
511     row.appendChild(custnum_cell);
512     
513     var status_cell = document.createElement('TD');
514       status_cell.style.textAlign = 'center';
515         
516       var status_span = document.createElement('SPAN');
517       status_span.setAttribute('id', 'status'+thisrownum+'_text');
518       status_span.style.fontWeight = 'bold';
519       status_span.style.color = values ? values.statuscolor : '';
520       status_span.setAttribute('rownum', thisrownum);
521       status_span.appendChild(
522         document.createTextNode(values ? values.status : '')
523       );
524       status_cell.appendChild(status_span);
525         
526       var status_input = document.createElement('INPUT');
527       status_input.setAttribute('type', 'hidden');
528       status_input.setAttribute('name', 'status'+thisrownum);
529       status_input.setAttribute('id',   'status'+thisrownum);
530       status_input.setAttribute('rownum', thisrownum);
531       status_input.value = values ? values.status : '';
532       status_cell.appendChild(status_input);
533
534       var statuscolor_input = document.createElement('INPUT');
535       statuscolor_input.setAttribute('type', 'hidden');
536       statuscolor_input.setAttribute('name', 'statuscolor'+thisrownum);
537       statuscolor_input.setAttribute('id',   'statuscolor'+thisrownum);
538       statuscolor_input.setAttribute('rownum', thisrownum);
539       statuscolor_input.value = values ? values.statuscolor : '';
540       status_cell.appendChild(statuscolor_input);
541
542     row.appendChild(status_cell);
543
544     var customer_cell = document.createElement('TD');
545
546       var customer_input = document.createElement('INPUT');
547       customer_input.setAttribute('name', 'customer'+thisrownum);
548       customer_input.setAttribute('id',   'customer'+thisrownum);
549       customer_input.setAttribute('size', 64);
550       customer_input.setAttribute('value', '(last name or company)' );
551       customer_input.setAttribute('rownum', thisrownum);
552       customer_input.value = values ? values.customer : '';
553       customer_input.onfocus = clearhint_customer;
554       customer_input.onclick = clearhint_customer;
555       customer_input.onchange = <% $opt{prefix} %>search_customer;
556       customer_cell.appendChild(customer_input);
557
558       var customer_select = document.createElement('SELECT');
559       customer_select.setAttribute('name', 'cust_select'+thisrownum);
560       customer_select.setAttribute('id',   'cust_select'+thisrownum);
561       customer_select.setAttribute('rownum', thisrownum);
562       customer_select.style.color = '#ff0000';
563       customer_select.style.display = 'none';
564       customer_select.onchange = select_customer;
565       customer_cell.appendChild(customer_select);
566
567     row.appendChild(customer_cell);
568     
569     var balance_cell = document.createElement('TD');
570
571       balance_cell.style.textAlign = 'right';
572       balance_cell.appendChild(document.createTextNode('<%$money_char%>'));
573
574       var balance_span = document.createElement('SPAN');
575       balance_span.setAttribute('id', 'balance'+thisrownum+'_text');
576       balance_span.setAttribute('rownum', thisrownum);
577       balance_cell.appendChild(balance_span);
578
579       balance_cell.appendChild(
580         document.createTextNode(String.fromCharCode(160) + (values ? values.balance : '')) //&nbsp;
581       );
582
583       var balance_input = document.createElement('INPUT');
584       balance_input.setAttribute('type', 'hidden');
585       balance_input.setAttribute('name', 'balance'+thisrownum);
586       balance_input.setAttribute('id',   'balance'+thisrownum);
587       balance_input.setAttribute('rownum', thisrownum);
588       balance_input.value = values ? values.balance : '';
589       balance_cell.appendChild(balance_input);
590
591       var num_open_input = document.createElement('INPUT');
592       num_open_input.setAttribute('type', 'hidden');
593       num_open_input.setAttribute('name', 'num_open'+thisrownum);
594       num_open_input.setAttribute('id',   'num_open'+thisrownum);
595       num_open_input.setAttribute('rownum', thisrownum);
596       balance_cell.appendChild(num_open_input);
597
598     row.appendChild(balance_cell);
599
600 %   my $col = 0;
601 %   foreach my $field ( @{$opt{fields}} ) {
602
603       var my_cell = document.createElement('TD');
604       my_cell.setAttribute('align', '<% $align{ $opt{align}->[$col] || 'l' } %>');
605
606 %     if ($types->[$col] eq 'immutable') {
607         var my_text = document.createTextNode(values ? values.<% $field %> : '');
608         my_cell.appendChild(my_text);
609 %     }
610
611 %     my $name  = (ref($field) eq 'CODE') ? "column${col}_" : $field;
612       var my_input = document.createElement('INPUT');
613       my_input.setAttribute('name', '<% $name %>'+thisrownum);
614       my_input.setAttribute('id',   '<% $name %>'+thisrownum);
615       my_input.style.textAlign = '<% $align{ $opt{align}->[$col] || 'l' } %>';
616       my_input.setAttribute('size', <% $sizes->[$col] || 10 %>);
617       my_input.setAttribute('rownum', thisrownum);
618 %     if ( $types->[$col] eq 'immutable' ) {
619       my_input.setAttribute('type', 'hidden');
620 %     } elsif ( $types->[$col] eq 'checkbox' ) {
621       my_input.setAttribute('type', 'checkbox');
622       my_input.checked = (values && values.<% $field %>) ? true : false;
623 %     }
624       my_input.value = (values && values.<% $field %>) || '';
625 %     if ( $opt{onchange}->[$col] ) {
626         my_input.onchange   = <% $opt{onchange}->[$col] %>;
627 %     }
628 %     elsif ( $opt{footer}->[$col] eq '_TOTAL' ) {
629         my_input.onchange   = <% $opt{prefix} %>calc_total<%$col%>;
630         my_input.onkeyup    = <% $opt{prefix} %>calc_total<%$col%>;
631 %     }
632       my_cell.appendChild(my_input);
633
634     row.appendChild(my_cell);
635
636 %     $col++;
637 %   }
638
639     var td_delete = document.createElement('TD');
640     td_delete.setAttribute('id', 'delete'+thisrownum);
641     row.appendChild(td_delete);
642     if (values) {
643       <% $opt{prefix} %>addDeleteButton(thisrownum);
644     }
645
646     update_num_open(thisrownum, (values ? values.num_open : '0'));
647
648 % if ( $opt{add_row_callback} ) {
649     <% $opt{add_row_callback} %>(thisrownum,
650                                  '<% $opt{prefix} %>', values);
651 % }
652
653     // update the total number of rows display
654     allrows.push(thisrownum);
655     if (values) <% $opt{prefix} %>totalrows++;
656     <% $opt{prefix} %>updateTotalRow();
657
658     // update the next available row number
659     if (thisrownum >= <% $opt{prefix} %>rownum) {
660       <% $opt{prefix} %>rownum = thisrownum + 1;
661     }
662
663   } // end of addRow
664
665
666 </SCRIPT>
667
668 <TABLE ID="<% $opt{prefix} %>OneTrueTable" BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0>
669
670 <TR>
671   <TH>Inv #</TH>
672   <TH>Cust #</TH>
673   <TH>Status</TH>
674   <TH>Customer</TH>
675   <TH>Balance</TH>
676 % foreach my $header ( @{$opt{header}} ) {
677     <TH><% $header %></TH>
678 % }
679 </TR>
680
681 % my @rownums = sort { $a <=> $b } map /^custnum(\d+)$/, keys %$param;
682 <TR id="row_total">
683   <TH COLSPAN=5 ID="<% $opt{'prefix'} %>_TOTAL_TOTAL">
684     Total <% @rownums || 0 %>
685     <% PL($opt{name_singular} || 'customer', ( @rownums || 0 ) ) %>
686   </TH>
687 % my $col = 0;
688 % foreach my $footer ( @{$opt{footer}} ) {
689 %   my $align = $align{ $opt{'footer_align'}->[$col] || 'c' };
690 %   if ($footer eq '_TOTAL' ) {
691 %     my $id = $opt{'fields'}->[$col];
692 %     $id = ref($id) ? "column${col}_TOTAL" : "${id}_TOTAL";
693       <TH ALIGN="<% $align %>" ID="<% $id %>">&nbsp;<% sprintf('%.2f', $total[$col] ) %></TH>
694 %   } else {
695       <TH ALIGN="<% $align %>"><% $footer %></TH>
696 %   }
697 %   $col++;
698 % }
699 </TR>
700
701 </TABLE>
702
703 <SCRIPT TYPE="text/javascript">
704
705 <% $opt{prefix} %>total_el =
706   document.getElementById("<% $opt{'prefix'} %>_TOTAL_TOTAL");
707
708 <% $opt{prefix} %>rownum = 1; // really more of a "next row", used by addrow
709 <% $opt{prefix} %>totalrows = 0; // will not include empty rows
710 <% $opt{prefix} %>allrows = []; // will include empty rows
711
712 % foreach my $row ( @rownums ) {
713 %   if ( grep($param->{$_.$row},qw(invnum display_custnum custnum status statuscolor customer balance),@{$opt{fields}} ) ) {
714
715 <% $opt{prefix} %>addRow({
716   rownum:<% $row %>,
717   num_open:<% $param->{"num_open$row"} |js_string %>,
718   invnum:<% $param->{"invnum$row"} |js_string %>,
719   display_custnum:<% $param->{"display_custnum$row"} |js_string %>,
720   custnum:<% $param->{"custnum$row"} |js_string %>,
721   status:<% $param->{"status$row"} |js_string %>,
722   statuscolor:<% $param->{"statuscolor$row"} |js_string %>,
723   customer:<% $param->{"customer$row"} |js_string %>,
724   balance:<% $param->{"balance$row"} |js_string %>,
725 %     my $col = 0;
726 %     foreach my $field ( @{$opt{fields}} ) {
727 %       my $value;
728 %       if ( ref($field) eq 'CODE' ) {
729 %         $value = &{$field}($row,$param) || '';
730 %       } else {
731 %         $value = $param->{"$field$row"} || '';
732 %       }
733 %       my $name  = (ref($field) eq 'CODE') ? "column${col}" : "$field";
734   <% $name %>:<% $value |js_string %>,
735 %       $col++;
736 %     }
737 });
738 %   }
739 % }
740
741 <% $opt{prefix} %>addRow();
742
743 % my $col = 0;
744 % foreach my $footer ( @{$opt{footer}} ) {
745 %   if ($footer eq '_TOTAL' ) {
746 %     my $name = $opt{fields}->[$col];
747 %     $name = ref($name) ? "column$col" : $name;
748       var <% $opt{prefix} %>th_el = document.getElementById("<%$name%>_TOTAL");
749       function <% $opt{prefix} %>calc_total<% $col %>() {
750         var row = 0;
751         var total = 0;
752         for (i = 0; i < <% $opt{prefix} %>allrows.length; i++) {
753           var value = document.getElementById("<%$name%>"+<% $opt{prefix} %>allrows[i]).value;
754           value = parseFloat(value);
755           if ( ! isNaN(value) ) {
756             total = total + value;
757           }
758         }
759         <% $opt{prefix} %>th_el.innerHTML = '&nbsp;' + total.toFixed(2);
760       }
761       <% $opt{prefix} %>calc_total<% $col %>()
762 %   }
763 %   $col++;
764 % }
765 </SCRIPT>
766
767 <% include('/elements/xmlhttp.html',
768               'url'  => $p. 'misc/xmlhttp-cust_main-search.cgi',
769               'subs' => [qw( custnum_search smart_search invnum_search )],
770            )
771 %>
772
773 <%init>
774
775 my(%opt) = @_;
776 my $conf = new FS::Conf;
777
778 ## caution when using prefix, it isn't consistently applied to tag id/name
779 $opt{prefix} = '' unless defined $opt{prefix};
780 $opt{prefix} .= '_' if $opt{prefix};
781
782 my $types = $opt{'type'} ? [ @{$opt{'type'}} ] : [];
783 my $sizes = $opt{'size'} ? [ @{$opt{'size'}} ] : [];
784
785 my $param = $opt{param};
786 $param = $cgi->Vars if $cgi->param('error');
787
788 $opt{$_} ||= [] foreach qw(align color footer footer_align);
789
790 my @total = map 0, @{$opt{footer}};
791
792 my %align = (
793   'l' => 'left',
794   'r' => 'right',
795   'c' => 'center',
796 );
797
798 my $money_char = $conf->config('money_char') || '$';
799 </%init>